UCOS-III移植到STM32F103C8T6

UCOS-III与STM32F103C8T6的融合

前些天学校开设了一门实验课,让我对UCOS有了一些了解,不过,实验使用的是飞思卡尔的MC9X128XS的开发板,系统使用的是UCOS-II,实验课结束后,我就寻思着将UCOS移植到我手头的F103C8核心板,上网查看了一波后,发现UCOS-II有点老了,1998年的产品,本着用新不用旧的态度,选择了UCOS-III,这是2009年出来的,目前最新版本是V3.07.03,不过这是我后来知道的,我是移植完成才写的这篇博客,我移植的是V3.03.01,移植过程中找资料才发现还有V3.07.03,但是懒得换了,看更新说明变换还挺大。不过V3.03.01也够了,F103资源本来就不多。

移植教程网上一大堆,但几乎都是基于标准库的,我个人是不用标准库的,但变化不是很大,主要就是系统时钟初始化有区别,还有就是启动文件不一样,HAL库附带的启动文件更简洁一些。

使用HAL库就不得不提及几个工具,STM32CubeMX是ST推出的一个代码生成工具,这个软件使用图形化界面配置,包括系统时钟,外设(ADC,SPI,IIC,UART,TIM,EXIT等等),还可以加入软件包,比如加入FATFS以支持文件系统,FREEOS实时操作系统,USB FS。配置完成,就可以生成工程模板,用相应的IDE打开就行了。支持KEIL,IAR TRUESTUDIO,Make等等一些主流IDE。

但是更新到V5版本之后,ST推出了一个大杀器STM32CubeIDE,这个软件集成了STM32CubeMX,Eclipse,stlink,jlink,如果你用过stm32F系列芯片,就知道这意味着什么。秒天秒地的存在啊。

本次移植也是基于STM32CubeIDE的。所以开始之前,请确保你有STM32CubeIDE,或者keil也可以,不过麻烦一点,或者你熟悉的一款IDE,但是你知道相应操作可以实现同样的效果就可以了。

说明一下

软件 版本
STM32CubeIDE V1.0.2
HAL库 V1.8.0
UCOS-III v3.03.01

以下就是移植步骤:
!!!!移植之前建议看一下《野火-UCOS-III内核实现与应用开发实战-基于STM32》 了解一下UCOS-III

  1. 使用STM32CubeIDE创建一个裸板工程,点亮一个LED即可。具体操作不再赘述。
  2. micrium官网的下载中心->STMicroelectronics下载一个例程Micrium_uC-Eval-STM32F107_uCOS-III,下载需要注册。
  3. 打开例程文件夹 ucOS\Micrium_uC-Eval-STM32F107_uCOS-III\Micrium\Software ,应该是以下结构的:

    返回到 ucOS\Micrium_uC-Eval-STM32F107_uCOS-III ,将整个 Software 复制到 项目的根目录下,复制完成后,在CubeIDE里应该是这样的目录结构:

  4. 设置源文件夹,打开项目属性对话框,添加完成后:

    设置 include 路径,设置完成后如下图:

  1. 添加文件到 Src 文件夹。

    • 打开 \Micrium\Software\EvalBoards\Micrium\uC-Eval-STM32F107\uCOS-III 目录,将以下文件复制到 Src

  2. 修改文件。

    • 修改 includes.h。 删除 stm32f1xx_lib.h, 添加 stm32f1xx_hal.hvoid Error_Handler (void);
    • 修改启动文件 startup_stm32f103c8tx.s。将 PendSV_Handler 替换成OS_CPU_PendSVHandler,有三处要替换, SysTick_Handler 替换 OS_CPU_SysTickHandler, 也有三处要替换。
      一
      二
    • 修改 os_app_hooks.c。在 void App_OS_TimeTickHook (void) 添加 HAL_IncTick();

      void  App_OS_TimeTickHook (void){
      HAL_IncTick();
      }
      
      • 修改 bsp.c。与 BSP 相关的代码都可以注释掉,只保留与 DWT 相关的。
      • 修改 aap.c。将 main.c 关于 void SystemClock_Config(void) 的声明和定义复制到 app.c ;之后就可以删除 main.cmain.h。接下来开始修改初始化系统和硬件的代码了,容易踩坑也是这里。cubeIEDE生成的代码里,一开始就调用了 HAL_Init(),在这个函数中,调用了 HAL_InitTick(), 这个函数就是坑,原因在于这个函数初始化了Systemtick 也就是所说的滴答时钟,一旦初始化完成,滴答时钟就开始运行,产生定时中断,而这个中断被UCOS接管了(OS_CPU_SysTickHandler()),这个函数体:
      void  OS_CPU_SysTickHandler (void)
      {
      CPU_SR_ALLOC();
      CPU_CRITICAL_ENTER();
      OSIntNestingCtr++;  /* Tell uC/OS-III that we are starting an ISR  */
      CPU_CRITICAL_EXIT();
      
      OSTimeTick(); /* Call  uC/OS-III's OSTimeTick()    */
      
      OSIntExit(); /* Tell uC/OS-III that we are leaving the ISR  */
      }
      

    可以看到,这个函数对 OSIntNestingCtr 进行了 ++ 操作,而在 OSInit() 函数里调用了 OS_IdleTaskInit()OS_IdleTaskInit()函数调用 OSTaskCreate() 创建一个空闲任务,而在 OSTaskCreate() 里有这样的语句:

      #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
      if (OSIntNestingCtr > (OS_NESTING_CTR)0) {  /*  ---------- CANNOT CREATE A TASK FROM AN ISR ---------- */
        *p_err = OS_ERR_TASK_CREATE_ISR;
         return;
      }
      #endif
    

    明显的 OSIntNestingCtr 大于 0,所以 OSInit()会失败。解决办法有两个(我采用第一种):

    • 注释掉 HAL_Init() 里面的 HAL_InitTick()
    • 先调用 OSInit() 后调用 HAL_Init()

    void SystemClock_Config(void)这个函数最终会初始化SystemTick,所以 void OS_CPU_SysTickInit (CPU_INT32U cnts) 可以不用调用。
    最终初始化程序为:

     int  main (void){
      OS_ERR  err;
      HAL_Init();
      OSInit(&err);  /* Init uC/OS-III.  */
      OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,  /* Create the start task*/
                  (CPU_CHAR   *)"App Task Start",
                  (OS_TASK_PTR ) AppTaskStart,
                  (void       *) 0,
                  (OS_PRIO     ) APP_TASK_START_PRIO,
                  (CPU_STK    *)&AppTaskStartStk[0],
                  (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
                  (CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
                  (OS_MSG_QTY  ) 5u,
                  (OS_TICK     ) 0u,
                  (void       *) 0,
                  (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                  (OS_ERR     *)&err);
    
      OSStart(&err);   /* Start multitasking (i.e. give control to uC/OS-III). */
      for(;;){}
     }
    

    static void AppTaskStart (void *p_arg)

     static  void  AppTaskStart (void *p_arg){
      // CPU_INT32U  cpu_clk_freq;
      //CPU_INT32U  cnts;
      OS_ERR      err;
      (void)p_arg;
      SystemClock_Config();
      HAL_NVIC_EnableIRQ(SysTick_IRQn);
      LED_Init();
      CPU_Init();
      //    cpu_clk_freq = HAL_RCC_GetHCLKFreq();  /* Determine SysTick reference freq.  */
      //    cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;  /* Determine nbr SysTick increments    */
      //    OS_CPU_SysTickInit(cnts);     /* Init uC/OS periodic time src (SysTick).   */
      Mem_Init(); /* Initialize Memory Management Module  */
      #if OS_CFG_STAT_TASK_EN > 0u
       OSStatTaskCPUUsageInit(&err);   /* Compute CPU capacity with no task running */
      #endif
      CPU_IntDisMeasMaxCurReset();
      #if (APP_CFG_SERIAL_EN == DEF_ENABLED)
      //    BSP_Ser_Init(115200);     /* Enable Serial Interface   */
      #endif
      AppTaskCreate(); /* Create Application Tasks   */
      AppObjCreate();   /* Create Application Objects    */
      while(DEF_TRUE) {
      /* Task body, always written as an infinite loop. */
      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
      //OSTimeDly(100, OS_OPT_TIME_DLY, &err);
      OSTimeDlyHMSM(0, 0, 0, 100, OS_OPT_TIME_HMSM_STRICT, &err);
      }
     }
    

    至此,移植完成。
    附上
    项目文件

根据上面的步骤,添加 TIM4 作为 HAL 库的超时检测机制
项目文件