写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.04.01:UCOSIII第二十八节:消息队列实验
- 四十三、UCOSIII:消息队列实验
- 1、消息队列使用注意事项
- 2、消息队列实验
- 3、消息队列实验现象
- 4、常见错误
- 1. Default Compiler Version 5
- 2. core_cm3.h(1213): error: unknown type name 'inline'
- 3. 程序正常运行无报错,烧录到板子上无反应
四十三、UCOSIII:消息队列实验
1、消息队列使用注意事项
在使用μC/OS提供的消息队列函数的时候,需要了解以下几点:
-
使用OSQPend()、OSQPost()等这些函数之前应先创建需消息队列, 并根据队列句柄(队列控制块)进行操作。
-
队列读取采用的是先进先出(FIFO)模式,会先读取先存储在队列中的数据。 当然也μC/OS也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进队列的数据。
-
无论是发送或者是接收消息都是以 数据引用的方式进行。
-
队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同一队列写入和读出。 一个队列由多任务或中断写入是经常的事,但由多个任务读出倒是用的比较少。
-
消息的传递实际上只是传递传送内容的指针和传送内容的字节大小。这在使用消息队列的时候就要注意了, 获取消息之前不能释放存储在消息中的指针内容,比如中断定义了一个局部变量,然后将其地址放在消息中进行传递, 中断退出之前消息并没有被其他任务获取,退出中断的时候 CPU已经释放了中断中的这个局部变量,后面任务获取这个地址的内容就会出错。 所以一定要保证在获取内容地址之前不能释放内容这个内存单元。有三种方式可以避免这种情况:
- 将变量定义为静态变量,即在其前面加上 static,这样内存单元就不会被释放。
- 将变量定义为全局变量。
- 将要传递的内容当做指针传递过去。比如地址 0x12345678存放一个变量的值为 5, 常规是把0x12345678这个地址传递给接收消息的任务, 任务接收到这个消息后,取出这个地址的内容 5。
但是如果我们把 5 当做“地址”传递给任务, 最后接收消息的任务直接拿着这个“地址”当做内容去处理即可。不过这种方法不能传递结构体等比较复杂的数据结构, 因为消息中存放地址的变量内存大小是有限的(一个指针大小)。
2、消息队列实验
消息队列实验是在μC/OS中创建了两个任务AppTaskPost()和 AppTaskPend()。
- 任务 AppTaskPost() 用于发送消息。
- 任务 AppTaskPend()用于接收消息。
两个任务独立运行,并把接收到的消息通过串口调试助手打印出来。
#include <includes.h>
/**************************************************************************
LOCAL DEFINES
**************************************************************************/
OS_Q queue; //声明消息队列
/**************************************************************************
TCB
************************************************************************/
static OS_TCB AppTaskStartTCB; //任务控制块
static OS_TCB AppTaskPostTCB;
static OS_TCB AppTaskPendTCB;
/*************************************************************************
STACKS
************************************************************************/
static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; //任务栈
static CPU_STK AppTaskPostStk [ APP_TASK_POST_STK_SIZE ];
static CPU_STK AppTaskPendStk [ APP_TASK_PEND_STK_SIZE ];
/*************************************************************************
FUNCTION PROTOTYPES
*************************************************************************/
static void AppTaskStart (void *p_arg); //任务函数声明
static void AppTaskPost ( void * p_arg );
static void AppTaskPend ( void * p_arg );
/*
***********************************************************************
* main()
*
* Description : This is the standard entry point for C code. It is assumed that
* your code will call main() once you have performed all necessary
* initialization.
* Arguments : none
*
* Returns : none
**************************************************************************/
int main (void)
{
OS_ERR err;
OSInit(&err); //初始化 μC/OS-III
/* 创建起始任务 */
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, //任务控制块地址
(CPU_CHAR *)"App Task Start", //任务名称
(OS_TASK_PTR ) AppTaskStart, //任务函数
(void *) 0,
//传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_START_PRIO, //任务的优先级
(CPU_STK *)&AppTaskStartStk[0],
//任务栈的基地址
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
//任务栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
//任务栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 5u,
//任务可接收的最大消息数
(OS_TICK ) 0u,
//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0,
//任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err); //返回错误类型
OSStart(&err);
//启动多任务管理(交由μC/OS-III控制)
}
/*************************************************************************
* STARTUP TASK
*
* Description : This is an example of a startup task. As mentioned in
* the book's text, you MUST initialize the ticker only once mu
* ltitasking has started.
* Arguments : p_arg is the argument passed to 'AppTaskStart()' by
* 'OSTaskCreate()'.
* Returns : none
*
* Notes : 1) The first line of code is used to prevent a compiler
warning because 'p_arg' is not
* used. The compiler should not generate any code for
this statement.
***********************************************************
*/
static void AppTaskStart (void *p_arg)
{
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
OS_ERR err;
(void)p_arg;
BSP_Init(); //板级初始化
CPU_Init();
//初始化 CPU组件(时间戳、关中断时间测量和主机名)
cpu_clk_freq = BSP_CPU_ClkFreq();
//获取 CPU内核时钟频率(SysTick 工作时钟)
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
//根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值
OS_CPU_SysTickInit(cnts);
//调用 SysTick初始化函数,设置定时器计数值和启动定时器
Mem_Init();
//初始化内存管理组件(堆内存池和内存池表)
#if OS_CFG_STAT_TASK_EN > 0u
//如果启用(默认启用)了统计任务
OSStatTaskCPUUsageInit(&err);
//计算没有应用任务(只有空闲任务)运行时 CPU的(最大)
#endif//容量(决定 OS_Stat_IdleCtrMax的值,为后面计算 CPU使用率使用)。
CPU_IntDisMeasMaxCurReset();
//复位(清零)当前最大关中断时间
/* 创建消息队列 queue */
OSQCreate ((OS_Q *)&queue, //指向消息队列的指针
(CPU_CHAR *)"Queue For Test", //队列的名字
(OS_MSG_QTY )20, //最多可存放消息的数目
(OS_ERR *)&err); //返回错误类型
/* 创建 AppTaskPost 任务 */
OSTaskCreate((OS_TCB *)&AppTaskPostTCB, //任务控制块地址
(CPU_CHAR *)"App Task Post", //任务名称
(OS_TASK_PTR ) AppTaskPost, //任务函数
(void *) 0,
//传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_POST_PRIO, //任务的优先级
(CPU_STK *)&AppTaskPostStk[0],
//任务栈的基地址
(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE / 10,
//任务栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_POST_STK_SIZE,
//任务栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 5u,
//任务可接收的最大消息数
(OS_TICK ) 0u,
//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0,
//任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err); //返回错误类型
/* 创建 AppTaskPend 任务 */
OSTaskCreate((OS_TCB *)&AppTaskPendTCB, //任务控制块地址
(CPU_CHAR *)"App Task Pend", //任务名称
(OS_TASK_PTR ) AppTaskPend, //任务函数
(void *) 0,
//传递给任务函数(形参p_arg)的实参
(OS_PRIO ) APP_TASK_PEND_PRIO,//任务的优先级
(CPU_STK *)&AppTaskPendStk[0],
//任务栈的基地址
(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE / 10,
//任务栈空间剩下1/10时限制其增长
(CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE,
//任务栈空间(单位:sizeof(CPU_STK))
(OS_MSG_QTY ) 5u,
//任务可接收的最大消息数
(OS_TICK ) 0u,
//任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
(void *) 0,
//任务扩展(0表不扩展)
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err); //返回错误类型
OSTaskDel ( & AppTaskStartTCB, & err );
//删除起始任务本身,该任务不再运行
}
/************************************************************************
POST TASK
***********************************************************************/
static void AppTaskPost ( void * p_arg )
{
OS_ERR err;
(void)p_arg;
while (DEF_TRUE) //任务体
{
/* 发布消息到消息队列 queue */
OSQPost ((OS_Q *)&queue, //消息变量指针
(void *)"Fire μC/OS-III",
//要发送的数据的指针,将内存块首地址通过队列“发送出去”
(OS_MSG_SIZE )sizeof ( "Fire μC/OS-III" ),//数据字节大小
(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL,
//先进先出和发布给全部任务的形式
(OS_ERR *)&err); //返回错误类型
OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );
}
}
/*************************************************************************
PEND TASK
*************************************************************************/
static void AppTaskPend ( void * p_arg )
{
OS_ERR err;
OS_MSG_SIZE msg_size;
CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
// SR(临界段关中断只需保存SR),开中断时将该值还原。
char * pMsg;
(void)p_arg;
while (DEF_TRUE) //任务体
{
/* 请求消息队列 queue 的消息 */
pMsg = OSQPend ((OS_Q *)&queue, //消息变量指针
(OS_TICK )0, //等待时长为无限
(OS_OPT )OS_OPT_PEND_BLOCKING,
//如果没有获取到信号量就等待
(OS_MSG_SIZE *)&msg_size, //获取消息的字节大小
(CPU_TS *)0, //获取任务发送时的时间戳
(OS_ERR *)&err); //返回错误
if ( err == OS_ERR_NONE ) //如果接收成功
{
OS_CRITICAL_ENTER(); //进入临界段
printf ( "\r\n接收消息的长度:%d字节,内容:%s\r\n", msg_size, pMsg );
OS_CRITICAL_EXIT();
}
}
}
3、消息队列实验现象
将程序编译好,用USB线连接计算机和开发板的USB接口(对应丝印为USB转串口), 用DAP仿真器把配套程序下载到野火STM32开发板。
在计算机上打开串口调试助手,然后复位开发板就可以在调试助手中看到串口的打印信息,具体如下
4、常见错误
1. Default Compiler Version 5
*** Target 'Fire_uCOS' uses ARM-Compiler 'Default Compiler Version 5' which is not available.
此错误是ARM编译器缺失导致的,解决办法是换成已安装的编译器
2. core_cm3.h(1213): error: unknown type name ‘inline’
错误是找不到 inline变量或函数,这个inline是在core_cm3.h里第1213行用到的
解决办法是把包含 inline的文件加入到工程里
inline可以是别的值,core_cm3.h也可以是别的文件
这些都是可以更改的
3. 程序正常运行无报错,烧录到板子上无反应
可能是库的问题,试试改成Micro Lib
Micro Lib是一个针对用C编写的基于ARM的嵌入式应用程序的高度优化的库。
与包含在ARM编译器工具链中的标准C库相比,MicroLib提供了许多嵌入式系统所需的代码大小的显著优势。
下图对使用标准库和使用微库代码大小进行了对比
MicroLib和标准C库之间的主要区别是:
1、MicroLib是专为深度嵌入式应用程序而设计的。
2、MicroLib经过优化,比使用ARM标准库使用更少的代码和数据内存。
3、MicroLib被设计成在没有操作系统的情况下工作,但是这并不妨碍它与任何操作系统或RTOS一起使用,如Keil RTX。
4、MicroLib不包含文件I/O或宽字符支持。
5、由于MicroLib已经优化到最小化代码大小,一些函数将比ARM编译工具中可用的标准C库例程执行得更慢。
6、MicroLib和ARM标准库都包含在Keil MDK-ARM中。
参考链接:keil勾选Use MicroLIB 的作用