主要介绍UCOSIII嵌入式操作系统,并实操STM32F103C8T6基于HAL库移植uC/OS-III的操作过程以及使用逻辑分析仪抓取波形进行协议分析
一、题目要求
- 学习嵌入式实时操作系统(RTOS),以uc/OS-III为例,将其移植到stm32F103上,构建至少3个任务(task):
其中两个task分别以1s和3s周期对LED等进行点亮-熄灭的控制; 另外一个task以2s周期通过串口发送“hello uc/OS! 欢迎来到RTOS多任务环境!”。
记录详细的移植过程。
- 在上述实验中,在掌握Keil的仿真调试代码功能之外,也学习使用仪器对代码运行进行故障排查和功能调测。
- 练习使用示波器去观察LED输出电平和串口通信的波形,分析故障;
- 分别使用Keil虚拟仿真逻辑仪和 真实逻辑仪(SaleaeLogic16)抓取LED输出电平和串口通信的波形,进行协议分析。
二、UCOSIII简单介绍
UCOS是Micrium公司出品的RTOS类实时操作系统, UCOS目前有两个版本:UCOSII和UCOSIII。
UCOSIII是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制,提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。
UCOSIII是用C和汇编来写的,其中绝大部分都是用C语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的, UCOSIII结构简洁,可读性很强!非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。
什么是任务?
任务(线程)是简单的程序。单CPU 中,在任何时刻只能是一个任务被执行。 任务看起来像C 函数。在大多数嵌入式系统中,任务通常是无限循环的。任务不能像C 函数那样,它是不能return 的。
在UCOSIII中任务是以何种面貌存在的呢?
在UCOSIII中任务就是程序实体,UCOSIII能够管理和调度这些小任务(程序)。UCOSIII中的任务由三部分组成:任务堆栈、任务控制块和任务函数。
- 任务堆栈:上下文切换的时候用来保存任务的工作环境,就是STM32的内部寄存器值。
任务堆栈是任务的重要部分,堆栈是在RAM中按照“先进先出(FIFO)”的原则组织的一块连续的存储空间。为了满足任务切换和响应中断时保存CPU寄存器中的内容及任务调用其它函数时的需要,每个任务都应该有自己的堆栈。
#define START_STK_SIZE 512 //堆栈大小 CPU_STK START_TASK_STK[START_STK_SIZE]; //定义一个数组来作为任务堆栈 任务堆栈初始化
任务如何才能切换回上一个任务并且还能接着从上次被中断的地方开始运行?
恢复现场即可,现场就是CPU的内部各个寄存器。因此在创建一个新任务时,必须把系统启动这个任务时所需的CPU各个寄存器初始值事先存放在任务堆栈中。这样当任务获得CPU使用权时,就把任务堆栈的内容复制到CPU的各个寄存器,从而可以任务顺利地启动并运行。
把任务初始数据存放到任务堆栈的工作就叫做任务堆栈的初始化,UCOSIII提供了完成堆栈初始化的函数:OSTaskStkInit()。
当然,用户一般不会直接操作堆栈初始化函数,任务堆栈初始化函数由任务创建函数OSTaskCreate()调用。不同的CPU对于的寄存器和对堆栈的操作方式不同,因此在移植UCOSIII的时候需要用户根据各自所选的CPU来编写任务堆栈初始化函数。
任务控制块是用来记录与任务相关的信息的数据结构,每个任务都要有自己的任务控制块。我们使用OSTaskCreate()函数来创建任务的时候就会给任务分配一个任务控制块。任务控制块由用户自行创建。
OS_TCB StartTaskTCB; //创建一个任务控制块
**USOCIII提供了用于任务控制块初始化的函数:OS_TaskInitTCB()。**但是,用户不需要自行初始化任务控制块。因为和任务堆栈初始化函数一样,函数OSTaskCreate()在创建任务的时候会对任务的任务控制块进行初始化。
- 任务函数:由用户编写的任务处理代码,是实实在在干活的,任务函数通常是一个无限循环,也可以是一个只执行一次的任务。任务的参数是一个void类型的,可以可以传递不同类型的数据甚至是函数。
任务函数其实就是一个C语言的函数,但是在使用UCOIII的情况下这个函数不能有用户自行调用,任务函数何时执行执行,何时停止完全有操作系统来控制。
UCOSIII支持时间片轮转调度,因此在一个优先级下会有多个任务,那么我们就要对这些任务做一个管理,这里使用OSRdyList[]数组管理这些任务。
OSRdyList[]数组中的每个元素对应一个优先级,比如OSRdyList[0]就用来管理优先级0下的所有任务。OSRdyList[0]为OS_RDY_LIST类型,从上面OS_RDY_LIST结构体可以看到成员变量:HeadPtr和TailPtr分别指向OS_TCB,我们知道OS_TCB是可以用来构造链表的,因此同一个优先级下的所有任务是通过链表来管理的,HeadPtr和TailPtr分别指向这个链表的头和尾,NbrEntries用来记录此优先级下的任务数量,图5.5.2表示了优先级4现在有3个任务时候的就绪任务列表。
同一优先级下如果有多个任务的话最先运行的永远是HeadPtr所指向的任务
三、实验过程
以uc/OS-III为例,将其移植到stm32F103上,构建至少3个任务(task) 下面进行实操
1、建立CubeMX工程
打开stm32 cubeMX ,选择芯片stm32f103c8 ,配置系统时钟为72M. 配置RCC 配置SYS
设置PA3 PC13作为两个LED灯的端口 将与LED相连的两个端口PA3 PC13配置为GPIO_Output,可根据LED现象作为是否移植成功的依据。 串口配置USART1
2、获取uCOS-III源码
进入 Micrium 公司官网下载中心:http://micrium.com/downloadcenter/ 选择ST系列,点击 View all STMicroelectronics ,点击 STMicroelectronics STM32F107 之后按照提示注册下载即可。
可直接下载此网盘链接内容 链接:https://pan.baidu.com/s/10RqsDRecbmVteWmDv2oUNQ 提取码:1234
3、移植前的文件准备
打开下载好的源码
- 文件夹下的文件已经复制添加好了,如下图所示
- 将uCOS的5个相关文件复制到cubeMX工程的
MDK-ARM 文件夹下
4、移植过程
打开cubeMX生成的keil文件
1) 将uCOS文件添加到项目中
点击Manage Project Items ,按下图所示操作,为项目新建文件夹,在对应文件夹下添加文件。
- 点击
CPU –>Add Files…
MDK-ARM\uC-CPU 路径下选中以下文件,Add添加 MDK-ARM\uC-CPU\ARM-Cortex-M3\RealView 路径下选中以下文件,Add添加
-
点击LIB –>Add Files… MDK-ARM\uC-LIB 路径下选中下图红框 文件,Add添加 MDK-ARM\uC-LIB\Ports\ARM-Cortex-M3\RealView 路径下选中下图绿框 文件,Add添加 -
点击PORT –>Add Files… MDK-ARM\uCOS-III\Ports\ARM-Cortex-M3\Generic\RealView 路径下选中以下文件,Add添加 -
点击SOURCE –>Add Files… MDK-ARM\uCOS-III\Source 路径下选中以下全部 .c .h 文件,Add添加 -
点击CONFIG –>Add Files… MDK-ARM\uC-CONFIG 路径下选中以下全部文件,Add添加 -
点击BSP –>Add Files… MDK-ARM\uC-BSP 路径下选中以下全部文件,Add添加 -
全部添加完毕后,点击OK
2) 导入文件路径
按下图所示步骤操作即可
5、构建三个任务
1) 代码添加
- 为bsp.c和bsp.h添加代码
bsp.h
#ifndef __BSP_H__
#define __BSP_H__
#include "stm32f1xx_hal.h"
void BSP_Init(void);
#endif
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
- 修改部分文件相关代码
打开startup_stm32f103xb.s 文件 在以下位置处将PendSV_Handler改为OS_CPU_PendSVHandler, SysTick_Handler改为OS_CPU_SysTickHandler
打开app_cfg.h 文件 DEF_ENABLED 改为 DEF_DISABLED #define APP_TRACE BSP_Ser_Printf 改为 #define APP_TRACE(void) 打开includes.h 文件 在#include <bsp.h>下面添加 #include “gpio.h” #include “app_cfg.h” 将#include <stm32f10x_lib.h> 改为 #include “stm32f1xx_hal.h” 打开lib_cfg.h 文件 修改为5(该处宏定义设置堆空间的大小,STM32F103C8T6的RAM只有20K,所以要改小一点) 打开usart.c 文件,添加代码完成printf重定向
typedef struct __FILE FILE;
int fputc(int ch,FILE *f){
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
2) 初始化管脚
在gpio.c 文件中修改代码
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
3)撰写主函数
修改main.c 文件
#include "main.h"
#include "gpio.h"
#include "usart.h"
#include <includes.h>
#include "stm32f1xx_hal.h"
#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;
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);
void SystemClock_Config(void);
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
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();
}
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();
}
}
int main(void)
{
OS_ERR err;
OSInit(&err);
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
OSTaskCreate((OS_TCB *)&StartTaskTCB,
(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);
OSStart(&err);
}
void start_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
BSP_Init();
#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
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER();
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);
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);
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();
}
static void led_pc13 (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
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);
}
}
static void led_pa3 (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
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);
}
}
static void send_msg (void *p_arg)
{
OS_ERR err;
(void)p_arg;
BSP_Init();
CPU_Init();
Mem_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err);
#endif
CPU_IntDisMeasMaxCurReset();
AppTaskCreate();
AppObjCreate();
while (DEF_TRUE)
{
printf("hello uc/OS \r\n");
OSTimeDlyHMSM(0, 0, 2, 0,OS_OPT_TIME_HMSM_STRICT,&err);
}
}
static void AppTaskCreate (void)
{
}
static void AppObjCreate (void)
{
}
void Error_Handler(void)
{
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
编译没有错误,进行烧录。
6、实现效果
四、逻辑分析仪抓取波形及串口分析
使用Keil仿真抓取波形
仿真设置
- 设置下面的“Dialog DLL”项为“DARMSTM.DLL”和“TARMSTM.DLL”;parameter项为“-pSTM32F103ZE”,用于设置支持STM32F103ZE的软硬件仿真。
因为在观测波形时发现波形卡住不动,因此进行如下设置,如果没有遇到此情况,则可忽略下面2步骤。
- 在工程下新建一个debug.ini文件,在文件中写入
map 0x40000000, 0x40007FFF read write
map 0x40010000, 0x400157FF read write
map 0x40020000, 0x4007FFFF read write
map 0x50000000, 0x50060BFF read write
map 0x60000000, 0x60000FFF read write
map 0xE0000000, 0xE00FFFFF read write
在Debug下的Initialization File 下添加debug.ini文件,如下图所示 观察PA3 PC13 PA9三个端口 仿真结果
使用Saleae Logic 16抓取波形
进行如下设置,通道、采样速率等 点击Start 展开第三通道波形
与仿真观察到的波形一致
协议分析
Saleae Logic 16 逻辑分析仪支持 18 种串行通讯协议,新版本的软件除了提供以往的线上协议解析之外,还提供了列表的方式显示解析内容,并内嵌了SQLITE3 来进行数据的相似查找和检索,我们来看一下协议解析的设置和检索。数字信号采集回来之后,我们可以通过查看波形进行分析。
在这里可以看到真实的时间,和目标设置时间的一致,说明时间上是成功的。
在采样前已经将通道2设置为了异步传输协议,传输数据为hello uc/OS! 因此选用Async Serial 下面是针对此次实验进行的逻辑分析仪采集结果分析 标注每个采集点高低电平,与发送目标字母的二进制比较,可以发现比特顺序是逆序的,但是与发送的目标值是一致的。
可以将解析出来的协议内容以列表形式罗列出来。 以上,所有目标要求已完成。
五、总结
如果我们需要处理器完成的任务比较单一,可以不使用操作系统; 如果需要处理器完成的任务比较多,为了便于管理可以引入轻巧的实时操作系统。
在进行uCOS移植时,务必要仔细,否则在编译时很容易报一大堆错误,究其原因只是因为在移植过程中某一个步骤忘记了,参数、文件路径的设置等问题,务必谨慎!
逻辑分析仪是一个较好的波形分析设备,操作并不困难。
整个实验过程并不复杂,只需要按部就班的进行即可完成移植和任务执行。
参考 [1] https://blog.csdn.net/weixin_43116606/article/details/105532222 [2] https://blog.csdn.net/qq_38410730/article/details/80752684 [3] https://www.cnblogs.com/xiangtingshen/p/10948689.html [4] https://blog.csdn.net/qq_21805743/article/details/120780866
|