文章目录
一、关于uc/OS系统
1.操作系统与裸机的区别
裸机运行的程序代码,一般由一个main函数中的while死循环和各种中断服务程序组成,平
时CPU执行while循环中的代码,出现其他事件时,跳转到中断服务程序进行处理,没有多任
务、线程的概念。而引人操作系统后,程序运行时可以把一个应用流程分割为多个任务,每个任务完成一部分工作,并且每个任务都可以写成死循环。操作系统根据任务的优先级,通过调度器使CPU分时执行各个任务,保证每个任务都能够得到运行。若调度方法优良,则可使各任务看起来是并行执行的,减少了CPU的空闲时间,提高了CPU的利用率。
2.uc/OS运行流程
在运行uC/OS系统的设备上,当程序执行时,首先会初始化系统任务管理所需要的各种链表等数据结构,接着根据应用程序需求创建任务,最后由调度器管理各个任务,而中断可由操作系统使能和除能,若使能中断则可以在其他任务运行时跳转到中断服务程序。
uc/OS工作流程图
1)初始化所有全局变量、数据结构,创建最低优先级空闲任务OSTaskIde (如果使用了统计任务,也在此创建),创建6个空数据链表:
- 空任务控制块链表
- 空事件控制块链表
- 空队列控制块链表
- 空标志组链表
- 空内存控制块链表
- 空闲定时器控制块链表
2)至少创建一个任务。一般创建一个最高优先级别
TaskStart任务(建议), 任务调度后,在这个任务中再创建其他任务,初始化硬件并开中断。
3)进入多任务管理阶段,将就绪表中最高优先级任务的
栈指针加载到SP中,并强制中断返回。
4) μC/OS 的任务调度工作。任务调度是内核的主要服务,是区分裸机和多任务系统的最大特点,好的调度策略能更好地发挥系统的效率。调度工作主要包括:查找就绪表中最高优先级任务和实现任务切换。而任务切换又分为两种,分别为任务级的调度器OSSched和中断级的调度器OSIntExt。
5)运行用户任务,某些用户任务会因为主动让出CPU、延时、请求临界资源或优先级不够高而挂起,由调度器调度运行其他任务。
6) μC/OS的任务调度是靠周期时钟中断来实现的,每个时钟节拍到来就会产生一次定时中断,中断后启动调度器,运行就绪表中优先级最高的任务(非抢占型内核中断后继续运行被中断任务)。即过一段时间就检测是否有重要任务需要运行,若是就转而运行更重要的任务,从而确保实时性(裸机程序就无法这样做了)。在CM3芯片平台上,这个周期时钟中断由SysTick提供。
二、详细移植过程
将uc/OS-III移植到stm32上,构建3个任务:其中两个task分别以1s和3s周期对LED等进行点亮-熄灭的控制;另外一个task以2s周期通过串口发送“hello uc/OS! 欢迎来到RTOS多任务环境!”。
1.STM32Cubex创建工程
新建project
,选择要用的芯片,这里选择STM32F103C8
,选中星号开启项目。
配置调试接口为Serial Wire
。
配置时钟源,选择Crystal/Ceramic Resonator
。
将PC13、PA3设置为GPIO_OUTPUT
模式,控制LED灯的亮灭,作为判断是否移值成功的依据。
设置串口USART1
为异步模式Asynchronous
。
设置工程相关信息,并点击GENERATE CODE
。
2.为工程添加源码
在工程相同目录下新建下图所示文件夹,提前把下的源码整理进去,后面操作起来就会方便很多。
用Keil打开生成的工程,为项目添加如下图所示文件夹。
点击ADD FILES
,选择ALL FILES
,将提前整理好的源码添加进项目中的CPU
文件夹。
点击LIB
,将下图所示文件添加进项目中。
点击PORT
,将下图所示文件夹加入项目中。
点击Source,将下图所示文件加入项目中。
点击 CONFIG
,将下图所示文件添加进项目中。
点击BSP
,将下图所示文件添加进项目中。
3.添加头文件路径
点击魔法棒,选择C/C++
,点进Include Paths
。
将下图所示的头文件路径添加进项目中。
4.添加代码
1)bsp.h和bsp.c
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;
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
2)main.c
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* 任务优先级 */
#define START_TASK_PRIO 3
#define LED0_TASK_PRIO 4
#define MSG_TASK_PRIO 5
#define LED1_TASK_PRIO 6
/* 任务堆栈大小 */
#define START_STK_SIZE 96
#define LED0_STK_SIZE 64
#define MSG_STK_SIZE 64
#define LED1_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];
CPU_STK LED1_TASK_STK[LED1_STK_SIZE];
/* 任务控制块 */
OS_TCB StartTaskTCB;
OS_TCB Led0TaskTCB;
OS_TCB MsgTaskTCB;
OS_TCB Led1TaskTCB;
/* 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);
static void led_pa3(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 * )&Led1TaskTCB,
(CPU_CHAR * )"led_pa3",
(OS_TASK_PTR )led_pa3,
(void * )0,
(OS_PRIO )LED1_TASK_PRIO,
(CPU_STK * )&LED1_TASK_STK[0],
(CPU_STK_SIZE)LED1_STK_SIZE/10,
(CPU_STK_SIZE)LED1_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);
/* 创建MSG任务 */
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, 1, 0,OS_OPT_TIME_HMSM_STRICT,&err);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
OSTimeDlyHMSM(0, 0, 1, 0,OS_OPT_TIME_HMSM_STRICT,&err);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
static void led_pa3 (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(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET);
OSTimeDlyHMSM(0, 0, 3, 0,OS_OPT_TIME_HMSM_STRICT,&err);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);
OSTimeDlyHMSM(0, 0, 3, 0,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 uc/OS \r\n");
OSTimeDlyHMSM(0, 0, 2, 0,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****/
3)usart.c
添加以下代码对printf函数进行重定向。
int fputc(int ch,FILE *f){
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
4)修改其他代码
找到Application/MDK-ARM
下的startup_stm32f103xb.s
文件,将PendSV_Handler
和SysTick_Handler
改为OS_CPU_PendSVHandler
和OS_CPU_SysTickHandler
。
找到CONFIG
下的app_cfg.h
文件,将DEF_ENABLED
改为DIABLED
。
将#define APP_TRACE BSP_Ser_Printf
改为#define APP _TRACE (void)
。
在该目录下找到文件includes.h
,添加包含两个头文件gpio.h
、app_cfg.h
。
将#include < stm32f10x lib.h>
改为#include "stm32f1xx hal.h"
在该目录下找到lib_cfg.h
,修改堆空间的大小,改成5k,因为STM32F103C8RAM太小了,不修改编译会报错。
5.其他设置
如下图所示修改设置,这个为Initialization File
添加在同一目录下debug.ini
文件的路径,这里为待会使用逻辑分析仪抓取引脚波形做准备,如果不添加待会仿真时按下全速运行会自动停止并报错没有读取权限。
debug.ini
map 0x40000000, 0x40007FFF read write // APB1
map 0x40010000, 0x400157FF read write // APB2
map 0x40020000, 0x4007FFFF read write // AHB1
map 0x50000000, 0x50060BFF read write // AHB2
map 0x60000000, 0x60000FFF read write // AHB3
map 0xE0000000, 0xE00FFFFF read write // CORTEX-M4 internal peripherals
IRAM也设置的大一些。
6.实际效果
将生成的hex文件烧录进芯片中,然后将boot0置0
,看到实际效果如下:两个LED按不同的周期点亮、熄灭,串口调试助手上输出“hello uc/OS! 欢迎来到RTOS多任务环境!”。
三、分析引脚波形
分别使用Keil虚拟仿真逻辑仪和真实逻辑仪(SaleaeLogic16)抓取LED输出电平和串口通信的波形,进行协议分析。
1.Keil虚拟仿真逻辑仪
进入Keil,按Ctrl+F5
进入仿真调试,打开逻辑分析仪。
点击左上角的SET UP
,添加要观察的引脚PA3、PC13和串口,输入时输入PORTA.3
,然后回车
就会自动生成下图所示的表示形式,注意Display Type
要设置成Bit
形式。
按F5
全速运行,可以看到下图所示的波形:
一个网格是0.5S,两只LED灯一只以1S的周期,一只以3秒的周期在高低电平转换,串口发送数据的周期是2S,在逻辑分析仪上不好详细分析串口的波形,但在SaleaeLogic16的软件上就能比较直观地分析。
2.真实逻辑仪(SaleaeLogic16)
想要详细了解SaleaeLogic16可以看我这篇博客用 Saleae Logic 16 示波器测量并分析 I2C、SPI、串口的信号
这里使用SaleaeLogic16的CH0、CH1、CH2通道,分别接STM32的PA3、PC13和PA9,再通过USB将电源接到电脑上,逻辑分析仪的Ground线接到STM32的GND引脚,注意高、低电平最好来自同一个电源,否则可能出现严重的毛刺,影响观测效果。
然后在软件界面上点击Analyzers
右边的加号,都选择Async Serial
,设置对应的通道号,点击左上角的Start
即可抓取在系统运作时的引脚波形。
抓取的波形如下图所示,可以看到PA3、PC13分别以接近1S、3S在高低电平转换,串口发送数据的周期是接近2S。
将高低电平跳变处放大仔细看一下串口的波形。
Saleae Logic 16软件的有数据解析功能,软件能够解析出框选中的时序波形00001011011对应"h",其中:
第1个bit 0:数据起始位。
中间8个bit 00010110:有效数据位。从左往右依次为高位,即0110 1000对应"h"的ASCII码0x68。
第9个bit 1:停止位。
第10个bit 1:空闲位。
根据上述内容,框选中的时序波形01010011011有效数据位10100110从左到右依次为高位,即0110 0101对应"e"的ASCII码0x65,所观察到的内容与串口通信的时序波形相符合。
四、总结
将uc/OS-III移植到stm32后,程序运行时可以把一个应用流程分割为多个任务,操作系统根据任务的优先级,通过调度器使CPU分时执行各个任务,使各任务看起来是并行执行的,减少了CPU的空闲时间,提高了CPU的利用率。另外,真实逻辑分析仪使用起来真的比Keil的逻辑分析仪使用起来方便许多,通过对这些图形化的时序波形的观察,能迅速定位到实际中软件或硬件的错误并排除故障。
五、参考资料
Saleae Logic 16安装文件及上手指南
提取码:650v
STM32F103C8T6移植uC/OS-III基于HAL库超完整详细过程
源码
提取码:1234