ARM Cordio WSF(三)——WSF在nRF52840平台移植

时间:2024-04-15 13:15:16

前文介绍了WSF架构及其WSF API,本文将介绍如何在一个硬件平台上使用WSF,这里基于stack项目下的nRF52840平台进行介绍。

3、PAL实现

对于要在一个处理器上运行WSF(逻辑情况),需要处理系统的调度、系统SysTick、中断上下文(Critical Section)、平台定时器相关,主要涉及内容如下:

  • timer移植,这里在平台相关的文件:pal_timer.c/h中实现;
  • 中断,Critical Section相关处理,避免中断执行上下文的破坏,平台相关文件:pal_sys.c/h;
  • 若需要使用NVM,则需要pal_flash.h/c
  • 对于wsf_heap相关操作,必要也需要在pal_sys.c/h中进行适配。

在WSF的架构中,均以PAL_*的形式来定义,即以平台抽象层(PAL,Platform Abstraction Layer)形式实现。

3.1 pal_sys

对于系统相关的PAL实现,关键接口如下(pal_sys.h):

/* Initialization */
void PalSysInit(void);

/* Diagnostics */
void PalSysAssertTrap(void);
void PalSysSetTrap(bool_t enable);
uint32_t PalSysGetAssertCount(void);
uint32_t PalSysGetStackUsage(void);

/* Power Management */
void PalSysSleep(void);  //休眠相关处理
bool_t PalSysIsBusy(void);
void PalSysSetBusy(void);
void PalSysSetIdle(void);

/* Critical Section */
void PalEnterCs(void);
void PalExitCs(void);

包括系统初始、诊断相关、功耗管理相关、以及Critical Section的处理。

/*! \brief      Free memory for pool buffers (align to word boundary). */
uint32_t palSysFreeMem[FREE_MEM_SIZE/sizeof(uint32_t)];

uint8_t *SystemHeapStart = (uint8_t *) palSysFreeMem;
uint32_t SystemHeapSize = FREE_MEM_SIZE;

关于Critical Section的处理,主要为中断的开启与关闭:

void PalEnterCs(void)
{
  #ifdef __IAR_SYSTEMS_ICC__
      __disable_interrupt();
  #endif
  #ifdef __GNUC__
      __asm volatile ("cpsid i");
  #endif
  #ifdef __CC_ARM
      __disable_irq();
  #endif
}
void PalSysInit(void)
{
  /* Enable Flash cache */
  NRF_NVMC->ICACHECNF |= (NVMC_ICACHECNF_CACHEEN_Enabled << NVMC_ICACHECNF_CACHEEN_Pos);

  /* Use 16 MHz crystal oscillator (system starts up using 16MHz RC oscillator). */
  NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
  NRF_CLOCK->TASKS_HFCLKSTART    = 1;
  while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) { }

  palSysAssertCount = 0;
  PalSysAssertTrapEnable = TRUE;
  palSysBusyCount = 0;

  PalRtcInit();
}

在平台初始化函数中,完成RTC初始化、系统相关设置,配置时钟等操作。

3.2 pal_timer

platform/pal_timer.c中,实现定时器驱动程序,用于调度程序和其他低功耗相关任务。其在实现时依赖于pal_rtc.c和一些调度程序以及nRF528**基带相关API。

对于一个调度定时器而言,通常需要实现以下相关接口(pal_timer.h):

/* Initialization */
void PalTimerInit(PalTimerCompCback_t expCback);
void PalTimerDeInit(void);

/* Control and Status */
PalTimerState_t PalTimerGetState(void);
void PalTimerStart(uint32_t expUsec);
void PalTimerStop(void);
uint32_t PalTimerGetCurrentTime(void);

以下为在NRF处理器上实现调度定时器,包括控制块、调度定时器初始化等。

/*! \brief      Scheduler timer driver control block. */
static struct
{
  PalTimerState_t    state;            /*!< State. */
  uint32_t           compareVal;       /*!<  Absolute compare value for timer expiry interrupt. */
  PalTimerCompCback_t expCback;         /*!< Timer expiry call back function. */
} palTimerCb;

调度定时器初始化:

void PalTimerInit(PalTimerCompCback_t expCback)
{
  #if SCH_TIMER_REQUIRED == TRUE
    #if BB_CLK_RATE_HZ == 32768
      PalRtcIrqRegister(RTC_CHANNEL_START_BB, palTimerRtcIrqHandler);
    #else
      /* Give scheduler timer the highest priority. */
      NVIC_SetPriority(TIMER1_IRQn, 0);  /* highest priority */
      NVIC_DisableIRQ(TIMER1_IRQn);

      /* stop timer if it was somehow running (timer must be stopped for configuration) */
      NRF_TIMER1->TASKS_STOP  = 1;

      /* clear timer to zero count */
      NRF_TIMER1->TASKS_CLEAR = 1;

      /* configure timer */
      NRF_TIMER1->MODE      = TIMER_MODE_MODE_Timer;
      NRF_TIMER1->BITMODE   = TIMER_BITMODE_BITMODE_32Bit;
      NRF_TIMER1->PRESCALER = PAL_TIMER_1MHZ_PRESCALER;  /* f = 16MHz / (2 ^ TIMER_PRESCALER) */

      /* timer1 is a free running clock. */
      NRF_TIMER1->TASKS_START = 1;

      /* Clear out and enable timer1 interrupt at system level. */
      NRF_TIMER1->INTENCLR = 0xFFFFFFFF;
      NRF_TIMER1->EVENTS_COMPARE[TIMER_CHANNEL_START_BB] = 0;
      NVIC_ClearPendingIRQ(TIMER1_IRQn);
      NVIC_EnableIRQ(TIMER1_IRQn);
    #endif
  #endif

  palTimerCb.compareVal = 0;
  palTimerCb.expCback = expCback;
  palTimerCb.state = PAL_TIMER_STATE_READY;
}

调度定时器的源根据宏定义,可以设置两种 :一种是RTC 32.768kHz的源;另一种是基于定时器Timer1来实现。

通过void PalTimerStart(uint32_t expTimeUsec)void PalTimerStop()来实现定时器的开启与停止。

相应的,根据设置情况,需要在palTimerRtcIrqHandlerTIMER1_IRQn中断中处理定时器相关。

void TIMER1_IRQHandler(void)
{
  /* Callback function could restart timer1. However, we blindly stop timer1 first. */
  NRF_TIMER1->INTENCLR = TIMER_INTENCLR_COMPARE0_Msk;
  /* Clear event again just in case. */
  NRF_TIMER1->EVENTS_COMPARE[TIMER_CHANNEL_START_BB] = 0;
  palTimerCb.state = PAL_TIMER_STATE_READY;
  if (palTimerCb.expCback)  //初始化中设定的定时器回调函数
  {
    palTimerCb.expCback();  
  }
}

3.3 pal_flash

对于平台上Flash相关操作,提供NVM的使用,通过pal_flash.h来实现。

/* Initialization */
void PalFlashInit(PalFlashCback_t actCback);
void PalFlashDeInit(void);

/* Control and Status */
PalFlashState_t PalNvmGetState(void);
uint32_t PalNvmGetTotalSize(void);
uint32_t PalNvmGetSectorSize(void);

/* Data Transfer */
void PalFlashRead(void *pBuf, uint32_t size, uint32_t srcAddr);
void PalFlashWrite(void *pBuf, uint32_t size, uint32_t dstAddr);
void PalFlashEraseSector(uint32_t size, uint32_t startAddr);
void PalFlashEraseChip(void);

以nRF52840为例,为QSPI Flash,其实现上,通过完成对底层驱动的初始化,进而可实现对Flash的访问。

void PalFlashInit(PalFlashCback_t actCback)
{
  uint32_t status;
  uint8_t  temp = 0x40;

  (void)actCback;

  nrfx_qspi_config_t config =
  {                                                                       \
      .xip_offset  = NRFX_QSPI_CONFIG_XIP_OFFSET,                         \
      .pins = {                                                           \
         .sck_pin     = BSP_QSPI_SCK_PIN,                                 \
         .csn_pin     = BSP_QSPI_CSN_PIN,                                 \
         .io0_pin     = BSP_QSPI_IO0_PIN,                                 \
         .io1_pin     = BSP_QSPI_IO1_PIN,                                 \
         .io2_pin     = BSP_QSPI_IO2_PIN,                                 \
         .io3_pin     = BSP_QSPI_IO3_PIN,                                 \
      },                                                                  \
      .irq_priority   = (uint8_t)NRFX_QSPI_CONFIG_IRQ_PRIORITY,           \
      .prot_if = {                                                        \
          .readoc     = (nrf_qspi_readoc_t)NRFX_QSPI_CONFIG_READOC,       \
          .writeoc    = (nrf_qspi_writeoc_t)NRFX_QSPI_CONFIG_WRITEOC,     \
          .addrmode   = (nrf_qspi_addrmode_t)NRFX_QSPI_CONFIG_ADDRMODE,   \
          .dpmconfig  = false,                                            \
      },                                                                  \
      .phy_if = {                                                         \
          .sck_freq   = (nrf_qspi_frequency_t)NRFX_QSPI_CONFIG_FREQUENCY, \
          .sck_delay  = (uint8_t)NRFX_QSPI_CONFIG_SCK_DELAY,              \
          .spi_mode   = (nrf_qspi_spi_mode_t)NRFX_QSPI_CONFIG_MODE,       \
          .dpmen      = false                                             \
      },                                                                  \
  }
  ;

  /* Verify palFlashCacheBuf size is at least 2. */
  PAL_FLASH_PARAM_CHECK(PAL_FLASH_CACHE_BUF_SIZE >= 2);

  status = nrfx_qspi_init(&config, NULL, NULL);

  PAL_FLASH_PARAM_CHECK(status == NRFX_SUCCESS);

  nrf_qspi_cinstr_conf_t cinstr_cfg = {
      .opcode    = QSPI_STD_CMD_RSTEN,
      .length    = NRF_QSPI_CINSTR_LEN_1B,
      .io2_level = 1,
      .io3_level = 1,
      .wipwait   = 1,
      .wren      = 1
  };

  /* Send reset enable. */
  status = nrfx_qspi_cinstr_xfer(&cinstr_cfg, NULL, NULL);

  /* Send reset command */
  cinstr_cfg.opcode = QSPI_STD_CMD_RST;

  status = nrfx_qspi_cinstr_xfer(&cinstr_cfg, NULL, NULL);

  PAL_FLASH_PARAM_CHECK(status == NRFX_SUCCESS);

  /* Switch to qspi mode */
  cinstr_cfg.opcode = QSPI_STD_CMD_WRSR;
  cinstr_cfg.length = NRF_QSPI_CINSTR_LEN_2B;

  status = nrfx_qspi_cinstr_xfer(&cinstr_cfg, &temp, NULL);

  PAL_FLASH_PARAM_CHECK(status == NRFX_SUCCESS);

  memset(&palFlashCb, 0, sizeof(palFlashCb));

  palFlashCb.state = PAL_FLASH_STATE_READY;

  (void)status;

}

对于Flash的读与写实现:

void PalFlashRead(void *pBuf, uint32_t size, uint32_t srcAddr)
{
  uint32_t readSize = PAL_FLASH_WORD_ALIGN(size);
  uint32_t actualSize = size;
  uint32_t status;
  uint16_t addrOffset = 0;

  do
  {
    if (readSize <= sizeof(palFlashCacheBuf))
    {
      /* Read data. */
      status = nrfx_qspi_read(palFlashCacheBuf, readSize, srcAddr + addrOffset);
      memcpy((uint8_t*)pBuf + addrOffset, palFlashCacheBuf, actualSize);

      readSize = 0;
    }
    else
    {
      /* Read data. */
      status = nrfx_qspi_read(palFlashCacheBuf, sizeof(palFlashCacheBuf), srcAddr + addrOffset);

      memcpy((uint8_t*)pBuf + addrOffset, palFlashCacheBuf, sizeof(palFlashCacheBuf));

      addrOffset += sizeof(palFlashCacheBuf);
      readSize -= sizeof(palFlashCacheBuf);
      actualSize -= sizeof(palFlashCacheBuf);
    }
  } while (readSize != 0);
  (void)status;
}
void PalFlashWrite(void *pBuf, uint32_t size, uint32_t dstAddr)
{
  uint32_t writeSize = PAL_FLASH_WORD_ALIGN(size);
  uint32_t actualSize = size;
  uint32_t status;
  uint16_t addrOffset = 0;

  do
  {
    if (writeSize <= sizeof(palFlashCacheBuf))
    {
      memcpy(palFlashCacheBuf, (uint8_t*)pBuf + addrOffset, actualSize);
      memset((uint8_t*)palFlashCacheBuf + actualSize, 0xFF, sizeof(palFlashCacheBuf) - actualSize);

      /* Write data. */
      status = nrfx_qspi_write(palFlashCacheBuf, writeSize, dstAddr + addrOffset);

      writeSize = 0;
    }
    else
    {
      memcpy(palFlashCacheBuf, (uint8_t*)pBuf + addrOffset, sizeof(palFlashCacheBuf));

      /* Write data. */
      status = nrfx_qspi_write(palFlashCacheBuf, sizeof(palFlashCacheBuf), dstAddr + addrOffset);

      addrOffset += sizeof(palFlashCacheBuf);
      writeSize -= sizeof(palFlashCacheBuf);
      actualSize -= sizeof(palFlashCacheBuf);
    }
  } while (writeSize != 0);
  (void)status;
}

3.4 SysTick实现

对于Cortex-M系列的处理器,都可以使用SysTick_Handler来实现系统的tick,这样更具有移植性。
对于SysTick_Handler的处理,
1)关键管理一个系统的tick,这里的示例使用32bit全局变量来实现;
2)调用WSF中的Timer来更新Tick,对于设置的wsf_timer,超时的,将触发定时器任务,进而触发相应的事件。

//由于在中断上下文处理,使用volatile关键字
volatile uint32_t g_sys_tick_count = 0;

void SysTick_Handler(void)  
{
    g_sys_tick_count++;
    WsfTimerUpdateTicks();
}

关于SysTick时钟的设备,则,根据系统期望的tick情况(如10ms)来设置。

    /* Configure SysTick to generate an interrupt every millisecond */
    SysTick_Config(WSF_MS_PER_TICK * GetSystemCoreClock() / 1000);     

其中,GetSystemCoreClock获取系统时钟情况,不同平台根据设置的系统时钟来设置。

基本调用逻辑与思路如下图所示:
在这里插入图片描述
WsfTimerUpdate中,将已经超时的Task设置定时器事件。

/* timer expired; set task for this timer as ready */
WsfTaskSetReady(pElem->handlerId, WSF_TIMER_EVENT);

void WsfTaskSetReady(wsfHandlerId_t handlerId, wsfTaskEvent_t event)
{
    /* Unused parameter */
    (void)handlerId;

    WSF_CS_INIT(cs);

    uint32_t lock = WSF_CS_ENTER();
    wsfOs.task.taskEventMask |= event;
    WSF_CS_EXIT(lock);

    /* set event in OS */
}

最终,在wsfOsDispatcher中调用。

    /*--- Start OS Dispatcher in a loop ---*/
    while(1)
    {
        wsfOsDispatcher();
    }

持续更新,系列文章,收藏关注吧!

1、ARM Cordio WSF(一)——架构介绍
2、ARM Cordio WSF(二)——API接口介绍