学习嵌入式实时操作系统(RTOS),以uc/OS-III为例,将其移植到stm32F103上,构建至少3个任务(task):其中两个task分别以1s和3s周期对LED等进行点亮-熄灭的控制;另外一个task以2s周期通过串口发送“hello uc/OS! 欢迎来到RTOS多任务环境!”。记录详细的移植过程。

一、准备工作

  1. 开发环境

  2. 安装 Keil μVision 或其他支持 STM32 开发的 IDE(如 IAR EWARM)。
  3. 确保安装了 STM32F103 的支持包,包括相应的编译器和调试工具。
  4. 获取 μC/OS-III 源码

  5. 从 Micrium 官方网站下载 μC/OS-III 的源码。
  6. 硬件准备

  7. STM32F103 开发板(如 STM32F103C8T6)。
  8. LED 连接到合适的 GPIO 引脚(例如 PA5)。
  9. 确保串口连接(例如 USART1,PA9 为 TX,PA10 为 RX)。

二、创建项目

  1. 新建 Keil 项目

  2. 打开 Keil μVision,创建一个新的 STM32F103 项目。
  3. 选择 STM32F103C8T6 作为目标设备。
  4. 添加 μC/OS-III 源码

  5. 将 μC/OS-III 源码添加到项目目录中。
  6. 在项目中添加 μC/OS-III 的源文件和头文件,包括 os_core.cos_flag.cos_task.c 等文件及其相应的头文件。

三、移植 μC/OS-III

  1. 修改 os_cfg.h

  2. 打开 os_cfg.h 文件,根据项目需求配置 μC/OS-III 的特性,例如:
#define OS_CFG_APP_HOOKS_EN           1u
#define OS_CFG_SCHED_LOCK_TIME_MEAS_EN 1u
#define OS_CFG_STAT_TASK_EN         1u
#define OS_CFG_STAT_TASK_STK_CHK_EN  1

  1. 这些配置可以启用或禁用某些 μC/OS-III 的功能,根据需要进行调整。
  2. 修改 os_cpu.h

  3. 对于 STM32F103,需要设置正确的 CPU 类型和数据类型大小,例如:

#define CPU_CFG_NAME_EN DEF_ENABLED

#define CPU_CFG_INT_DIS_MEAS_EN DEF_DISABLED

#define CPU_CFG_CRITICAL_METHOD 3u

  • 这里 CPU_CFG_CRITICAL_METHOD 表示临界区保护的方法,3 表示使用 __disable_irq() 和 __enable_irq() 函数。
    1. 修改 os_cpu_a.asm(汇编文件)

    2. 这个文件包含了一些与 CPU 相关的汇编代码,如上下文切换等。对于 STM32F103,需要确保以下几点:
    3. 实现 OSStartHighRdy() 函数,用于启动最高优先级任务。
    4. 实现 OSCtxSw() 函数,用于任务切换。
    5. 实现 OSIntCtxSw() 函数,用于中断时的任务切换。

    四、编写 BSP(板级支持包)代码

    1. 初始化系统时钟

    2. 编写 bsp.c 和 bsp.h 文件,在 bsp.c 中实现系统时钟初始化函数,
    #include "bsp.h"
    #include "stm32f10x.h"
    
    void BSP_Init(void)
    {
        SystemInit();
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    }

    GPIO 初始化

  • 初始化用于控制 LED 的 GPIO 引脚,例如:
  • void BSP_LED_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
    }

    串口初始化

  • 初始化 USART1 用于发送数据
  • void BSP_USART1_Init(void)
    {
        USART_InitTypeDef USART_InitStructure;
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_Init(USART1, &USART_InitStructure);
        USART_Cmd(USART1, ENABLE);
    }

    五、创建任务

    1. 定义任务函数

    2. 创建三个任务函数,分别控制 LED 和发送串口数据
    #include "os.h"
    #include "bsp.h"
    
    void Task1(void *p_arg)
    {
        while (1)
        {
            GPIO_SetBits(GPIOA, GPIO_Pin_5);
            OSTimeDlyHMSM(0, 0, 1, 0); // 1s 延时
            GPIO_ResetBits(GPIOA, GPIO_Pin_5);
            OSTimeDlyHMSM(0, 0, 1, 0); // 1s 延时
        }
    }
    
    void Task2(void *p_arg)
    {
        while (1)
        {
            GPIO_SetBits(GPIOA, GPIO_Pin_5);
            OSTimeDlyHMSM(0, 0, 3, 0); // 3s 延时
            GPIO_ResetBits(GPIOA, GPIO_Pin_5);
            OSTimeDlyHMSM(0, 0, 3, 0); // 3s 延时
        }
    }
    
    void Task3(void *p_arg)
    {
        while (1)
        {
            char *msg = "hello uc/OS! 欢迎来到 RTOS 多任务环境!\n";
            for (int i = 0; msg[i]!= '\0'; i++)
            {
                while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
                USART_SendData(USART1, msg[i]);
            }
            OSTimeDlyHMSM(0, 0, 2, 0); // 2s 延时
        }
    }

    创建任务启动函数

  • 在 main 函数中创建并启动任务
  • #include "os.h"
    #include "bsp.h"
    
    int main(void)
    {
        OS_ERR err;
        BSP_Init();
        BSP_LED_Init();
        BSP_USART1_Init();
    
        OSInit(&err);
        if (err!= OS_ERR_NONE)
        {
            while (1);
        }
    
        OSTaskCreate((OS_TCB *)0,
                    (CPU_CHAR *)"Task1",
                    (OS_TASK_PTR)Task1,
                    (void *)0,
                    (OS_PRIO)1,
                    (CPU_STK *)&Task1Stk[0],
                    (CPU_STK_SIZE)Task1StkSize / 10,
                    (CPU_STK_SIZE)Task1StkSize,
                    (OS_MSG_QTY)0,
                    (OS_TICK)0,
                    (void *)0,
                    (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                    (OS_ERR *)&err);
    
        OSTaskCreate((OS_TCB *)0,
                    (CPU_CHAR *)"Task2",
                    (OS_TASK_PTR)Task2,
                    (void *)0,
                    (OS_PRIO)2,
                    (CPU_STK *)&Task2Stk[0],
                    (CPU_STK_SIZE)Task2StkSize / 10,
                    (CPU_STK_SIZE)Task2StkSize,
                    (OS_MSG_QTY)0,
                    (OS_TICK)0,
                    (void *)0,
                    (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                    (OS_ERR *)&err);
    
        OSTaskCreate((OS_TCB *)0,
                    (CPU_CHAR *)"Task3",
                    (OS_TASK_PTR)Task3,
                    (void *)0,
                    (OS_PRIO)3,
                    (CPU_STK *)&Task3Stk[0],
                    (CPU_STK_SIZE)Task3StkSize / 10,
                    (CPU_STK_SIZE)Task3StkSize,
                    (OS_MSG_QTY)0,
                    (OS_TICK)0,
                    (void *)0,
                    (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                    (OS_ERR *)&err);
    
        OSStart(&err);
        while (1);
    }

    六、配置和编译

    1. 配置项目

    2. 在 Keil 中配置项目,确保包含正确的头文件路径和库文件。
    3. 配置编译器选项,如优化级别、调试信息等。

    具体操作过程:

     

    准备uCOSIII源码
    进入官网下载:http://micrium.com/downloadcenter/

    或链接:https://pan.baidu.com/s/10RqsDRecbmVteWmDv2oUNQ
    提取码:1234

    网盘下载,打开目录如下(文件夹uC-BSP和uC-CONFIG是自己新建的两个文件夹)

    移植前准备
    1.为uC-BSP文件夹新建bsp.c和bsp.h文件
    2.给文件夹uC-CONFIG添加以下文件(从以下路径复制过来)

     3.将uCOS相关文件复制到HAL工程的MDK-ARM文件夹下

     

    开始移植

    回到Keil打开的HAL工程

     

    分别给新增的文件夹添加文件

    点击CPU–>Add Files…,选中以下文件,Add

     

     

     

    bsp.h 

    // bsp.h
    #ifndef  __BSP_H__
    #define  __BSP_H__

    #include "stm32f1xx_hal.h"

    void BSP_Init(void);

    #endif
     

    bsp.c

    // bsp.c
    #include "includes.h"

    #define  DWT_CR      *(CPU_REG32 *)0xE0001000
    #define  DWT_CYCCNT  *(CPU_REG32 *)0xE0001004
    #define  DEM_CR      *(CPU_REG32 *)0xE000EDFC
    #define  DBGMCU_CR   *(CPU_REG32 *)0xE0042004

    #define  DEM_CR_TRCENA                   (1 << 24)
    #define  DWT_CR_CYCCNTENA                (1 <<  0)

    CPU_INT32U  BSP_CPU_ClkFreq (void)
    {
        return HAL_RCC_GetHCLKFreq();
    }

    void BSP_Tick_Init(void)
    {
        CPU_INT32U cpu_clk_freq;
        CPU_INT32U cnts;
        cpu_clk_freq = BSP_CPU_ClkFreq();
        
        #if(OS_VERSION>=3000u)
            cnts = cpu_clk_freq/(CPU_INT32U)OSCfg_TickRate_Hz;
        #else
            cnts = cpu_clk_freq/(CPU_INT32U)OS_TICKS_PER_SEC;
        #endif
        OS_CPU_SysTickInit(cnts);
    }

    void BSP_Init(void)
    {
        BSP_Tick_Init();
        MX_GPIO_Init();
    }

    #if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
    void  CPU_TS_TmrInit (void)
    {
        CPU_INT32U  cpu_clk_freq_hz;

        DEM_CR         |= (CPU_INT32U)DEM_CR_TRCENA;                /* Enable Cortex-M3's DWT CYCCNT reg.                   */
        DWT_CYCCNT      = (CPU_INT32U)0u;
        DWT_CR         |= (CPU_INT32U)DWT_CR_CYCCNTENA;

        cpu_clk_freq_hz = BSP_CPU_ClkFreq();
        CPU_TS_TmrFreqSet(cpu_clk_freq_hz);
    }
    #endif

    #if (CPU_CFG_TS_TMR_EN == DEF_ENABLED)
    CPU_TS_TMR  CPU_TS_TmrRd (void)
    {
        return ((CPU_TS_TMR)DWT_CYCCNT);
    }
    #endif

    #if (CPU_CFG_TS_32_EN == DEF_ENABLED)
    CPU_INT64U  CPU_TS32_to_uSec (CPU_TS32  ts_cnts)
    {
        CPU_INT64U  ts_us;
      CPU_INT64U  fclk_freq;

     
      fclk_freq = BSP_CPU_ClkFreq();
      ts_us     = ts_cnts / (fclk_freq / DEF_TIME_NBR_uS_PER_SEC);

      return (ts_us);
    }
    #endif
     
     
    #if (CPU_CFG_TS_64_EN == DEF_ENABLED)
    CPU_INT64U  CPU_TS64_to_uSec (CPU_TS64  ts_cnts)
    {
        CPU_INT64U  ts_us;
        CPU_INT64U  fclk_freq;

      fclk_freq = BSP_CPU_ClkFreq();
      ts_us     = ts_cnts / (fclk_freq / DEF_TIME_NBR_uS_PER_SEC);
        
      return (ts_us);
    }
    #endif
     

    3.修改main.c文件代码

    /* USER CODE END Header */
    /* Includes ——————————————————————*/
    #include "main.h"
    #include "gpio.h"
    #include "usart.h"
    /* Private includes ———————————————————-*/
    /* USER CODE BEGIN Includes */
    #include <includes.h>
    #include "stm32f1xx_hal.h"
    /* USER CODE END Includes */

    /* Private typedef ———————————————————–*/
    /* USER CODE BEGIN PTD */

    /* USER CODE END PTD */

    /* Private define ————————————————————*/
    /* USER CODE BEGIN PD */
    /* 任务优先级 */
    #define START_TASK_PRIO        3
    #define LED0_TASK_PRIO        4
    #define MSG_TASK_PRIO        5

    /* 任务堆栈大小    */
    #define START_STK_SIZE         64
    #define LED0_STK_SIZE         64
    #define MSG_STK_SIZE         64//任务堆大小过大会报错,可以试着改小一点

    /* 任务栈 */    
    CPU_STK START_TASK_STK[START_STK_SIZE];
    CPU_STK LED0_TASK_STK[LED0_STK_SIZE];
    CPU_STK MSG_TASK_STK[MSG_STK_SIZE];
    /* 任务控制块 */
    OS_TCB StartTaskTCB;
    OS_TCB Led0TaskTCB;
    OS_TCB MsgTaskTCB;
    /* USER CODE END PD */

    /* Private macro ————————————————————-*/
    /* USER CODE BEGIN PM */

    /* USER CODE END PM */

    /* Private variables ———————————————————*/

    /* USER CODE BEGIN PV */

    /* 任务函数定义 */
    void start_task(void *p_arg);
    static  void  AppTaskCreate(void);
    static  void  AppObjCreate(void);
    static  void  led_pc13(void *p_arg);
    static  void  send_msg(void *p_arg);
    /* USER CODE END PV */

    /* Private function prototypes ———————————————–*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */

    /* USER CODE END PFP */

    /* Private user code ———————————————————*/
    /* USER CODE BEGIN 0 */
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

      /**Initializes the CPU, AHB and APB busses clocks 
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
      /**Initializes the CPU, AHB and APB busses clocks 
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }

    /* USER CODE END 0 */

    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
        OS_ERR  err;
        OSInit(&err);
      HAL_Init();
        SystemClock_Config();
        //MX_GPIO_Init(); 这个在BSP的初始化里也会初始化
      MX_USART1_UART_Init();    
        /* 创建任务 */
        OSTaskCreate((OS_TCB     *)&StartTaskTCB,                /* Create the start task                                */
                     (CPU_CHAR   *)"start task",
                     (OS_TASK_PTR ) start_task,
                     (void       *) 0,
                     (OS_PRIO     ) START_TASK_PRIO,
                     (CPU_STK    *)&START_TASK_STK[0],
                     (CPU_STK_SIZE) START_STK_SIZE/10,
                     (CPU_STK_SIZE) START_STK_SIZE,
                     (OS_MSG_QTY  ) 0,
                     (OS_TICK     ) 0,
                     (void       *) 0,
                     (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                     (OS_ERR     *)&err);
        /* 启动多任务系统,控制权交给uC/OS-III */
        OSStart(&err);            /* Start multitasking (i.e. give control to uC/OS-III). */
                   
    }

    void start_task(void *p_arg)
    {
        OS_ERR err;
        CPU_SR_ALLOC();
        p_arg = p_arg;
        
        /* YangJie add 2021.05.20*/
      BSP_Init();                                                   /* Initialize BSP functions */
      //CPU_Init();
      //Mem_Init();                                                 /* Initialize Memory Management Module */

    #if OS_CFG_STAT_TASK_EN > 0u
       OSStatTaskCPUUsageInit(&err);          //统计任务                
    #endif
        
    #ifdef CPU_CFG_INT_DIS_MEAS_EN            //如果使能了测量中断关闭时间
        CPU_IntDisMeasMaxCurReset();    
    #endif

    #if    OS_CFG_SCHED_ROUND_ROBIN_EN          //当使用时间片轮转的时候
         //使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
        OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);  
    #endif        
        
        OS_CRITICAL_ENTER();    //进入临界区
        /* 创建LED0任务 */
        OSTaskCreate((OS_TCB     * )&Led0TaskTCB,        
                     (CPU_CHAR    * )"led_pc13",         
                     (OS_TASK_PTR )led_pc13,             
                     (void        * )0,                    
                     (OS_PRIO      )LED0_TASK_PRIO,     
                     (CPU_STK   * )&LED0_TASK_STK[0],    
                     (CPU_STK_SIZE)LED0_STK_SIZE/10,    
                     (CPU_STK_SIZE)LED0_STK_SIZE,        
                     (OS_MSG_QTY  )0,                    
                     (OS_TICK      )0,                    
                     (void       * )0,                    
                     (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
                     (OS_ERR     * )&err);                
                     
        /* 创建LED1任务 */
        OSTaskCreate((OS_TCB     * )&MsgTaskTCB,        
                     (CPU_CHAR    * )"send_msg",         
                     (OS_TASK_PTR )send_msg,             
                     (void        * )0,                    
                     (OS_PRIO      )MSG_TASK_PRIO,         
                     (CPU_STK   * )&MSG_TASK_STK[0],    
                     (CPU_STK_SIZE)MSG_STK_SIZE/10,    
                     (CPU_STK_SIZE)MSG_STK_SIZE,        
                     (OS_MSG_QTY  )0,                    
                     (OS_TICK      )0,                    
                     (void       * )0,                
                     (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, 
                     (OS_ERR     * )&err);
                     
        OS_TaskSuspend((OS_TCB*)&StartTaskTCB,&err);        //挂起开始任务             
        OS_CRITICAL_EXIT();    //进入临界区
    }
    /**
      * 函数功能: 启动任务函数体。
      * 输入参数: p_arg 是在创建该任务时传递的形参
      * 返 回 值: 无
      * 说    明:无
      */
    static  void  led_pc13 (void *p_arg)
    {
      OS_ERR      err;

      (void)p_arg;

      BSP_Init();                                                 /* Initialize BSP functions                             */
      CPU_Init();

      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();

      AppTaskCreate();                                            /* Create Application Tasks                             */

      AppObjCreate();                                             /* Create Application Objects                           */

      while (DEF_TRUE)
      {
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
            OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
            OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    static  void  send_msg (void *p_arg)
    {
      OS_ERR      err;

      (void)p_arg;

      BSP_Init();                                                 /* Initialize BSP functions                             */
      CPU_Init();

      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();

      AppTaskCreate();                                            /* Create Application Tasks                             */

      AppObjCreate();                                             /* Create Application Objects                           */

      while (DEF_TRUE)
      {
                printf("hello world \r\n");
            OSTimeDlyHMSM(0, 0, 0, 500,OS_OPT_TIME_HMSM_STRICT,&err);
        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }

    /* USER CODE BEGIN 4 */
    /**
      * 函数功能: 创建应用任务
      * 输入参数: p_arg 是在创建该任务时传递的形参
      * 返 回 值: 无
      * 说    明:无
      */
    static  void  AppTaskCreate (void)
    {
      
    }

    /**
      * 函数功能: uCOSIII内核对象创建
      * 输入参数: 无
      * 返 回 值: 无
      * 说    明:无
      */
    static  void  AppObjCreate (void)
    {

    }

    /* USER CODE END 4 */

    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */

      /* USER CODE END Error_Handler_Debug */
    }

    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)

      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */

    /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

    参考链接:

    https://blog.csdn.net/qq_45659777/article/details/121570886

    https://blog.csdn.net/qq_46467126/article/details/121441622

    ​​​​​​

    作者:₩﹎

    物联沃分享整理
    物联沃-IOTWORD物联网 » stm32多任务程序设计

    发表回复