主要是采用超级循环系统(前后台系统),应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看做后台行为;中断服务程序处理异步事件,这部分可以看做是前台行为。后台也可以叫做任务级,前台也叫作中断级。
前后台系统的编程思路有两种:轮询方式(实时性得不到保障,紧急与非紧急消息不能有效管理)、中断方式(可以保证一定的实时性,紧急消息可以得到响应)。
采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了:
采用多任务系统可以以上的裸机开发遇到的4大缺点。
RTOS的实现重点就在这个OS任务调度器上,调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。FreeRTOS就是一款支持多任务运行的实时操作系统,具有时间片、抢占式和合作式三种调度方式。通过 FreeRTOS 实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。
栈大小 0x400 = 1024,单位字节
在RTOS下,上面截图里设置的栈大小有了一个新名字叫做系统栈空间,而任务栈是不使用这里的空间,哪里使用这里的栈空间呢,实际上是中断函数和中断嵌套。
- 由于 Cortex-M3 和 M4 内核具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针,或者叫 PSP
任务堆栈指针也是可以的。在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进
程堆栈指针 PSP 是给任务栈使用的。也就是说,在 FreeRTOS 任务中,所有栈空间的使用都是通过
PSP 指针进行指向的。一旦进入了中断函数以及可能发生的中断嵌套都是用的 MSP 指针。这个知识
点要记住它,当前可以不知道这是为什么,但是一定要记住。- 实际应用中系统栈空间分配多大,主要是看可能发生的中断嵌套层数,下面我们就按照最坏执行情况
进行考虑,所有的寄存器都需要入栈,此时分为两种情况:
* 64 字节:
对于 Cortex-M3 内核和未使用 FPU(浮点运算单元)功能的 Cortex-M4 内核在发生中断时需要将 16 个通用寄存器全部入栈,每个寄存器占用 4 个字节,也就是 16*4 = 64 字节的空间。可能发生几次中断嵌套就是要 64 乘以几即可。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。(注:任务执行的过程中发生中断的话,有 8 个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈)
* 200 字节
对于具有 FPU(浮点运算单元)功能的 Cortex-M4 内核,如果在任务中进行了浮点运算,那么在发生中断的时候除了 16 个通用寄存器需要入栈,还有 34 个浮点寄存器也是要入栈的,也就是(16+34)*4 = 200 字节的空间。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
栈生长方向从高地址向低地址生长(M4 和 M3 是这种方式)
- 上图标识 3 的位置是局部变量 int i 和 int array[10]占用的栈空间,但申请了栈空间后已经越界了。这个就是所谓的栈溢出了。如果用户在函数 test 中通过数组 array 修改了这部分越界区的数据且这部分越界的栈空间暂时没有用到或者数据不是很重要,情况还不算严重,但是如果存储的是关键数据,会直接导致系统崩溃。
- 上图标识 4 的位置是局部变量申请了栈空间后,栈指针向下偏移(返回地址+变量 i+10 个数组元素)*4 =48 个字节。
- 上图标识 5 的位置可能是其它任务的栈空间,也可能是全局变量或者其它用途的存储区,如果 test函数在使用中还有用到栈的地方就会从这里申请,这部分越界的空间暂时没有用到或者数据不是很重要,情况还不算严重,但是如果存储的是关键数据,会直接导致系统崩溃。
FreeRTOS 提供了两种栈溢出检测机制,这两种检测都是在任务切换时才会进行:
#define configCHECK_FOR_STACK_OVERFLOW 1
#define configCHECK_FOR_STACK_OVERFLOW 2
除了 FreeRTOS 提供的这两种栈溢出检测机制,还有其它的栈溢出检测机制,大家可以在 Mircrium 官方发布的如下这个博文中学习:
https://www.micrium.com/detecting-stack-overflows-part-2-of-2/
钩子函数:
钩子函数的主要作用就是对原有函数的功能进行扩展,用户可以根据自己的需要往里面添加相关的测试代码,大家可以在 FreeRTOS 工程中检索这个钩子函数 vApplicationStackOverflowHook 所在的位置。
FreeRTOS的任务状态(4种)
1.运行态(Running) 2.就绪态(Ready) 3.阻塞态(Blocked) 4.挂起态(Suspended)
ucos的任务状态(5种)
1.睡眠状态 2.就绪状态 3.等待状态 4.中断服务状态 5.执行状态
特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可),只有这样,高优先级任务才会释放 CPU 的使用权,,从而低优先级任务才有机会得到执行。这里的优先级分配方案是我们推荐的一种方式,实际项目也可以不采用这种方法。 调试出适合项目需求的才是最好的。
这两个之间没有任何关系,不管中断的优先级是多少,中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序。
另外对于 STM32F103,F407 和 F429 来说,中断优先级的数值越小,优先级越高。 而 FreeRTOS的任务优先级是,任务优先级数值越小,任务优先级越低。
FreeRTOS就是一款支持多任务运行的实时操作系统,具有时间片、抢占式和合作式三种调度方式。
调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的
特性:
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。
使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。 比如,当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。 又比如,如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。使用抢占式调度器,使得最高优先级的任务什么时候可以得到 CPU 的控制权并运行是可知的,同时使得任务级响应时间得以最优化。
总的来说,学习抢占式调度要掌握的最关键一点是:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务。
在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中禁止使用时间片调度,那么每个任务必须配
置不同的优先级。当 FreeRTOS 多任务启动执行后,基本会按照如下的方式去执行:
- Task1由于延迟时间到,接收到信号量消息等方面的原因,使得 Task1从挂起状态恢复到就绪态,
在抢占式调度器的作用下,Task2 的执行会被 Task1 抢占。- Task2 会一直运行直到遇到系统阻塞式的 API 函数,比如延迟,事件标志等待,信号量等待,Task2任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。
在小型的嵌入式 RTOS 中,最常用的的时间片调度算法就是 Round-robin 调度算法。这种调度算法可以用于抢占式或者合作式的多任务中。另外,时间片调度适合用于不要求任务实时响应的情况。
实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度,另外还需要用户在FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_TIME_SLICING 1
默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能了,用户可以不用在FreeRTOSConfig.h 文件中再单独使能。
示例:
代码的临界临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界区代码
的执行不被中断,在进入临界区之前须关中断,而临界区代码执行完毕后,要立即开中断。
FreeRTOS 的源码中有多处临界段的地方, 临界段虽然保护了关键代码的执行不被打断, 但也会影响系统的实时性。比如此时某个任务正在调用系统 API 函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行, 如果关中断时间超过了紧急中断能够容忍的限度, 危害是可想而知的。
FreeRTOS 源码中就有多处临界段的处理,跟 FreeRTOS 一样,uCOS-II 和 uCOS-III 源码中都是有临界段的,而 RTX 的源码中不存在临界段。 另外,除了 FreeRTOS 操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:
- 重入函数的实现特征
一般而言,重入函数具有如下特征:
- 函数内部无整个软件生命周期的变量(静态变量)
- 未引用或者访问整个软件生命周期的变量(全局变量)
可重入函数示例:
/*A parameter is passed into the function. This will either be passed on the stack or in a CPU register. Either way is safe as each task maintains its own stack and its own set of register values.*/
long lAddOneHunderad ( long lVal1)
{
/*This function scope variable will also be allocated to the stack or a register, depending on compiler and optimization level. Each task or interrupt that calls this function will have its own copy of lVar2.*/
long lVal2;
lVar2 = lVar1 + 100;
/*Most likely the return value will be placed in a CPU register,although it too could be placed on the stack.*/
return lVar2;
}
不可重入函数示例
/*In this case lVar1 is a global variable so every task that calls the function will be accessing the same single copy of the variable.*/
long lVal1;
long lNonsenseFunction ( void )
{
/*This variable is static so is not allocated on the stack.Each task that calls the function will be accessing the single copy of the variable.*/
static long lState = 0; // 与函数内部无整个软件生命周期的变量(静态变量)相悖
long lReturn;
switch( lState )
{
case 0:
lReturn = lVar1 + 10;
lState = 1;
break;
case 1:
lReturn = lVar1 +20;
1State = 0;
break;
}
}
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
再进两步跟踪宏定义的实现如下:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so assert() if it is
being called from an interrupt context. Only API functions that end in "FromISR" can be used
in an interrupt. Only assert if the critical nesting count is 1 to protect against
recursive calls if the assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
上面的这两个函数都对变量 uxCriticalNesting 进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。 通过嵌套计数就有效地防止了用户嵌套调用函数 taskENTER_CRITICAL 和 taskEXIT_CRITICAL 时出错。
直接的开关中断方式不支持嵌套调用例子说明
比如下面的例子:
void FunctionA()
{
taskDISABLE_INTERRUPTS(); 关闭中断
FunctionB(); 调用函数 B
FunctionC(); 调用函数 C
taskENABLE_INTERRUPTS(); 打开中断
}
void FunctionB()
{
taskDISABLE_INTERRUPTS(); 关闭中断
//代码
taskENABLE_INTERRUPTS(); 打开中断
}
工程中调用了 FunctionA 就会出现执行完 FunctionB 后中断被打开的情况,此时 FunctionC 将不被保护了。
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
再进两步跟踪宏定义的实现如下:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
/*-----------------------------------------------------------*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
中断服务程序里面的临界段代码的开关中断也是通过寄存器 basepri 实现的。这里怎么没有中断嵌套计数了呢?
是的,这里换了另外一种实现方法,通过保存和恢复寄存器 basepri 的数值就可以实现嵌套使用。如果大家研究
过 uCOS-II 或者 III 的源码,跟这里的实现方式是一样的,使用的时候一定要保证成对使用。
void vTaskSuspendAll( void );
BaseType_t xTaskResumeAll(void)
函数 vTaskSuspendAll 用于实现 FreeRTOS 调度锁关闭。
使用这个函数要注意以下问题:
函数 xTaskResumeAll用于实现 FreeRTOS 调度锁开启
调度锁关闭后,如果需要任务切换,此函数返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
任何操作系统都需要提供一个时钟节拍,以供系统处理诸如延时、 超时等与时间相关的事件。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳。 中断之间的时间间隔取决于不同的应用,一般是 1ms – 100ms。时钟的节拍中断使得内核可以将任务延迟若干个时钟节拍,以及当任务等待事件发生时,提供等待超时等依据。时钟节拍率越快,系统的额外开销就越大。任何操作系统都需要提供一个时钟节拍,以供系统处理诸如延时、 超时等与时间相关的事件。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳。 中断之间的时间间隔取决于不同的应用,一般是 1ms – 100ms。时钟的节拍中断使得内核可以将任务延迟若干个时钟节拍,以及当任务等待事件发生时,提供等待超时等依据。时钟节拍率越快,系统的额外开销就越大。
对于 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407 以及 F429,教程配套的例子都是用滴答定时器来实现系统时钟节拍的。
FreeRTOS 的系统时钟节拍可以在配置文件 FreeRTOSConfig.h 里面设置:
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
如上所示的宏定义配置表示系统时钟节拍是 1KHz,即 1ms。
时间管理功能是 FreeRTOS 操作系统里面最基本的功能,同时也是必须要掌握好的。
- 为周期性执行的任务提供延迟。
- 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放 CPU 使用权,从而让低优先级任务可以得到执行。
通过如下的框图来说明一下延迟函数对任务运行状态的影响,有一个形象的认识。
对任务 Task1 的运行状态做说明,调度器支持时间片调度和抢占式调度。
运行过程描述如下:
void vTaskDelay(const TickType_t xTicksToDelay ); /* 延迟时间长度 ,用于任务的延迟。*/
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, /* 存储任务上次处于非阻塞状态时刻的变量地址 */
const TickType_t xTimeIncrement ); /* 周期性延迟时间 */
volatile TickType_t xTaskGetTickCount( void );/* 用于获取系统当前运行的时钟节拍数。此函数用于在任务代码里面调用,如果在中断服务程序里面调用的话,需要使用函数xTaskGetTickCountFromISR,这两个函数切不可混用。 */
volatile TickType_t xTaskGetTickCountFromISR( void );/* 用于获取系统当前运行的时钟节拍数。此函数用于在中断服务程序里面调用,如果在任务里面调用的话,需要使用函数 xTaskGetTickCount,这两个函数切不可混用。 */
举例
运行条件:
- 有一个 bsp_KeyScan 函数,这个函数处理时间大概耗时 2ms。
- 有两个任务,一个任务 Task1 是用的 vTaskDelay 延迟,延迟 10ms,另一个任务 Task2 是用的vTaskDelayUntil 延迟,延迟 10ms。
- 不考虑任务被抢占而造成的影响。
实际运行过程效果:- Task1:
bsp_KeyScan+ vTaskDelay (10) —> bsp_KeyScan + vTaskDelay (10)
|----2ms + 10ms 为一个周期------| |----2ms + 10ms 为一个周期----|
这个就是相对性的含义- Task2:
bsp_KeyScan + vTaskDelayUntil ---------> bsp_KeyScan + vTaskDelayUntil
|----10ms 为一个周期(2ms 包含在 10ms 内)—| |----10ms 为一个周期------|
这就是周期性的含义
这里经过自己的理解,现将通信与同步机制做一个比喻,方便初学者理解。
场景:A 和 B 相约做地铁去公司,A/B互相告知自己去公司的经历。
可能发生的情景:
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单?其实不然,在裸机编程时,使用全局变量的确比较方便,但是在加上 RTOS 后就是另一种情况了。 使用全局变量相比事件标志组主要有如下三个问题:
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。
下面我们来说说 FreeRTOS 中事件标志的实现,根据用户在 FreeRTOSConfig.h 文件中的配置:
#define configUSE_16_BIT_TICKS 1
配置宏定义 configUSE_16_BIT_TICKS 为 1 时,每创建一个事件标志组,用户可以使用的事件标志是8 个。
#define configUSE_16_BIT_TICKS 0
配置宏定义 configUSE_16_BIT_TICKS 为 0 时,每创建一个事件标志组,用户可以使用的事件标志是24 个。
上面说的 8 个和 24 个事件标志应该怎么理解呢?其实就是定义了一个 16 位变量,仅使用了低 8bit或者定义了一个 32 位变量,仅使用了低 24bit。每一个 bit 用 0 和 1 两种状态来代表事件标志。 反映到FreeRTOS 上就是将事件标志存储到了 EventBits_t 类型的变量中。
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
not need to be guarded with a critical section. */
#define portTICK_TYPE_IS_ATOMIC 1
#endif
TickType_t 数据类型可以是 16 位数或者 32 位数,这样就跟上面刚刚说的configUSE_16_BIT_TICKS 宏定义呼应上了。推荐配置宏定义 configUSE_16_BIT_TICKS为 0,即用户每创建一个事件标志组,有 24 个标志可以设置。
FreeRTOS 任务间事件标志组的实现是指任务间使用事件标志。
FreeRTOS 中断方式事件标志组的实现是指中断函数和 FreeRTOS 任务之间使用事件标志。
实际应用中,中断方式的消息机制要注意以下四个问题:
使用如下 11 个函数可以实现 FreeRTOS 的事件标志组:
- xEventGroupCreate()
- xEventGroupCreateStatic()
- vEventGroupDelete()
- xEventGroupWaitBits()
- xEventGroupSetBits()
- xEventGroupSetBitsFromISR()
- xEventGroupClearBits()
- xEventGroupClearBitsFromISR()
- xEventGroupGetBits()
- xEventGroupGetBitsFromISR()
- xEventGroupSync()
重点介绍这四个函数:
EventGroupHandle_t xEventGroupCreate( void );
返回值,如果创建成功,此函数返回事件标志组的句柄,如果 FreeRTOSConfig.h 文件中定义的 heap空间不足会返回 NULL。
(a) 调用此函数的过程中,其它高优先级的任务就绪了,并且也修改了事件标志,此函数返回的事件标志位会发生变化。
(b) 调用此函数的任务是一个低优先级任务,通过此函数设置了事件标志后,让一个等待此事件标志的高优先级任务就绪了,会立即切换到高优先级任务去执行,相应的事件标志位会被函数xEventGroupWaitBits 清除掉,等从高优先级任务返回到低优先级任务后,函数xEventGroupSetBits 的返回值已经被修改。
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToSet, /* 事件标志位设置 */
BaseType_t *pxHigherPriorityTaskWoken ); /* 高优先级任务是否被唤醒的状态保存 */
函数 xEventGroupSetBits 用于设置指定的事件标志位为 1。
第 1 个参数:是事件标志组句柄。
第 2 个参数:表示 24 个可设置的事件标志位,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。变量 uxBitsToSet 的低 24 位的某个位设置为 1,那么被设置的事件标志组的相应位就设置为 1。 变量 uxBitsToSet 设置为 0 的位对事件标志相应位没有影响。比如设置变量 uxBitsToSet = 0x0003 就表示将事件标志的位 0 和位 1 设置为 1,其余位没有变化。
第 3 个参数:用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,说明有高优先级任务要执行,否则没有。
返回值:如果消息成功发送给 daemon 任务(就是 FreeRTOS 的定时器任务)返回 pdPASS,否则返回 pdFAIL,另外 daemon 任务中的消息队列满了也会返回 pdFAIL。
这个函数注意事项:
(1)使用前一定要保证事件标志已经通过函数 xEventGroupCreate 创建了。同时要在 FreeRTOSConfig.h文件中使能如下三个宏定义:
#define INCLUDE_xEventGroupSetBitFromISR 1
#define configUSE_TIMERS 1
#define INCLUDE_xTimerPendFunctionCall 1
(2)函数 xEventGroupSetBitsFromISR 是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是 xEventGroupSetBits。
(3) 函数 xEventGroupSetBitsFromISR 对事件标志组的操作是不确定性操作,因为不知道当前有多少个任务在等待此事件标志。而 FreeRTOS 不允许在中断服务程序和临界段中执行不确定性操作。 为了不在中断服务程序中执行,就通过此函数给 FreeRTOS 的 daemon 任务(就是 FreeRTOS 的定时器任务)发送消息,在 daemon 任务中执行事件标志的置位操作。 同时也为了不在临界段中执行此不确定操作,将临界段改成由调度锁来完成。这样不确定性操作在中断服务程序和临界段中执行的问题就都得到解决了。
(4)由于函数 xEventGroupSetBitsFromISR 对事件标志的置位操作是在 daemon 任务里面执行的,如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置 daemon 任务的优先级高于使用此事件标志组的所有其它任务。
(5) 通过下面的使用举例重点一下函数 xEventGroupSetBitsFromISR 第三个参数的规范用法,初学者务必要注意。
static void TIM2_IRQHandler(void)
{
BaseType_t xResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 中断消息处理, 此处省略 */
……
/* 向任务 vTaskMsgPro 发送事件标志 */
xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */
BIT_0 , /* 设置 bit0 */
&xHigherPriorityTaskWoken );
/* 消息被成功发出 */
if( xResult != pdFAIL )
{
/* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup, /* 事件标志组句柄 */
const EventBits_t uxBitsToWaitFor, /* 等待被设置的事件标志位 */
const BaseType_t xClearOnExit, /* 选择是否清零被置位的事件标志位 */
const BaseType_t xWaitForAllBits, /* 选择是否等待所有标志位都被设置 */
TickType_t xTicksToWait ); /* 设置等待时间 */
函数 xEventGroupWaitBits 等待事件标志被设置。
第 1 个参数:是事件标志组句柄。
第 2 个参数:表示等待 24 个事件标志位中的指定标志,EventBits_t 是定义的 32 位变量,低 24 位用于事件标志设置。比如设置变量 uxBitsToWaitFor = 0x0003 就表示等待事件标志的位 0 和位 1 设置为 1。 此参数切不可设置为 0。
第 3 个参数:选择是否清除已经被置位的事件标志,如果这个参数设置为 pdTRUE,且函数xEventGroupWaitBits 在参数 xTicksToWait 设置的溢出时间内返回,那么相应被设置的事件标志位会被清零。 如果这个参数设置为 pdFALSE,对已经被设置的事件标志位没有影响。
第 4 个参数:选择是否等待所有的标志位都被设置,如果这个参数设置为 pdTRUE,要等待第 2 个参数 uxBitsToWaitFor 所指定的标志位全部被置 1,函数才可以返回。当然,超出了在参数xTicksToWait 设置的溢出时间也是会返回的。如果这个参数设置为 pdFALSE,第 2 个参数uxBitsToWaitFor 所指定的任何标志位被置 1,函数都会返回,超出溢出时间也会返回。
第 5 个参数:设置等待时间,单位时钟节拍周期。 如果设置为 portMAX_DELAY,表示永久等待。
返回值:由于设置的时间超时或者指定的事件标志位被置 1,导致函数退出时返回的事件标志组数值。
这个函数注意事项:
(1)此函数切不可在中断服务程序中调用。
(2)这里再着重说明下这个函数的返回值,通过返回值用户可以检测是哪个事件标志位被置 1 了。
(a)如果由于设置的等待时间超时,函数的返回值可会有部分事件标志位被置 1。
(b)如果由于指定的事件标志位被置1而返回,并且设置了这个函数的参数xClearOnExit为pdTRUE,那么此函数的返回值是清零前的事件标志组数值。
另外,调用此函数的任务在离开阻塞状态到退出函数 xEventGroupWaitBits 之间这段时间,如果一个高优先级的任务抢占执行了,并且修改了事件标志位,那么此函数的返回值会跟当前的事件标志组数值不同。
FreeRTOS支持的定时器组,或者叫软件定时器,又或者叫用户定时器均可。软件定时器的功能比较简单,也容易掌握。被称为定时器组是因为用户可以创建多个定时器,创建的个数是可配置的。
FreeRTOS软件定时器组的时基是基于系统时钟节拍实现的,之所以叫软件定时器是因为它的实现不需要使用任何硬件定时器,而且可以创建很多个,综合这些因素,这个功能就被称之为软件定时器组。 既然是定时器,那么它实现的功能与硬件定时器也是类似的。在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。
FreeRTOS提供的软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。
定时器任务(Daemon 任务)
为了更好的管理FreeRTOS的定时器组件,专门创建了一个定时器任务,或者称之为Daemon任务。关于这个任务,我们上章节在讲解事件标志组的时候有用到。
FreeRTOS定时器组的大部分API函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。为了更好的说明这个问题,我们将官方在线版手册中的这个截图贴出来进行说明:
左侧图是用户应用程序,右侧是定时器任务。在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。消息队列在此处的作用有一个专门的名字:Timer command queue,即专门发送定时器组命令的队列。
使用软件定时器组注意事项:
定时器回调函数是在定时器任务中执行的,实际应用中切不可在定时器回调函数中调用任何将定时 器任务挂起的函数,比如vTaskDelay(),
vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起,会导致定时器任务负责的相关功能都不能正确执行了。
使用如下 20 个函数可以实现 FreeRTOS 的事件标志组:
重点介绍这3个函数:
xTimerCreate()
xTimerStart ()
pvTimerGetTimerID ()
函数 xTimerCreate
TimerHandle_t xTimerCreate
( const char * const pcTimerName, /* 定时器名字 */
const TickType_t xTimerPeriod, /* 定时器周期,单位系统时钟节拍
const UBaseType_t uxAutoReload, /* 选择单次模式或者周期模式 */
void * const pvTimerID, /* 定时器 ID */
TimerCallbackFunction_t pxCallbackFunction ); /* 定时器回调函数 */
函数描述: 函数xTimerCreate用于创建软件定时器。
函数 xTimerStart
函数原型:
BaseType_t xTimerStart( TimerHandle_t xTimer, /* 定时器句柄 */
TickType_t xBlockTime ); /* 成功启动定时器前的最大等待时间设置,单位系统时钟节拍 */
函数描述:函数xTimerStart用于启动软件定时器。
使用这个函数要注意以下问题:
- 使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
- 在FreeRTOSConfig.h文件中使能宏定义: #define configUSE_TIMERS 1
- 对于已经被激活的定时器,即调用过函数xTimerStart进行启动,再次调用此函数相当于调用了函数xTimerReset对定时器时间进行了复位。
- 如果在启动FreeRTOS调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate中设置的单次或者周期性延迟时间才会执行相应的回调函数。
函数 pvTimerGetTimerID
函数原型:
void *pvTimerGetTimerID( TimerHandle_t xTimer ); /* 定时器句柄 */
函数描述: 函数pvTimerGetTimerID用于返回使用函数xTimerCreate创建的软件定时器ID。
使用这个函数要注意以下问题:
- 使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
- 在FreeRTOSConfig.h文件中使能宏定义: #define configUSE_TIMERS 1
- 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。
消息队列的概念及其作用
消息队列就是通过 RTOS 内核提供的服务,任务或中断服务子程序可以将一个消息(注意,FreeRTOS
消息队列传递的是实际数据,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址)放入到队列。
同样,一个或者多个任务可以通过 RTOS 内核服务从队列中得到消息。通常,先进入消息队列的消息先传
给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO),FreeRTOS
的消息队列支持 FIFO 和 LIFO 两种数据存取方式。
需要说明的是,freertos消息队列是通过副本机制传递的,而不是引用。
查看源码可以看到底层实现xQueueSend
函数的宏定义如下
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
xQueueGenericSend()
函数内有这个函数
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
prvCopyDataToQueue()
函数内可以看到
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. */
freertos的消息队列发送函数是通过使用memcpy复制的内容。
以简单的数据元素为例:
uint8_t ucCount = 0;
xQueueSend(xQueue1,(void *) &ucCount,(TickType_t)10);
这里是发送队列消息函数,下面看接收:
uint8_t ucQueueMsgValue;
xQueueReceive(xQueue1, /* 消息队列句柄 */
(void *)&ucQueueMsgValue, /* 存储接收到的数据到变量ucQueueMsgValue中 */
(TickType_t)xMaxBlockTime)
这里是最简单的uint_8类型元素,要想把发送函数的uint_8定义的数据,包括该数据在发送函数之前被更改后的值发送给接收函数,我们需要传递给发送函数send一个uint_8定义数据的地址,这样可以通过地址传递到memcpy函数,实现复制,这也就是为什么上面说的freertos的消息队列不是引用而是复制,要是引用的话,可以直接传这个uint_8类型的数据,而我们此时在freertos操作系统上,是副本传递,通过memcpy,所以需要给uint_8类型数据的地址。
同样的在本次实验中采用了消息队列传结构体指针地址来实现大数据量的传输提高效率。
/* 发送函数 */
MSG * TXMSG;
TXMSG = &DATAMSG;//DATAMSG是一个结构实体而且是全局区定义
/* 初始化结构体内容 */
TXMSG->ucMessageID = 1;
TXMSG->ucData[0] = 0;
/* 消息队列传递结构体指针的地址 */
if(xQueueSend(myQueue02Handle,(void*)&TXMSG,0) ==errQUEUE_FULL)
{
printf("myQueue02Handle errQUEUE_FULL\r\n");
}
/* 接收函数 */
MSG *RXMSG;
/* 初始化结构体内容 */
TXMSG->ucMessageID = 1;
TXMSG->ucData[0] = 0;
/* 接收消息队列结构体指针的地址 */
if(xQueueReceive( myQueue02Handle, (void*)&RXMSG, 10) == pdPASS)
{
printf("\r\nRXMSG->ucMessageID = %d \r",RXMSG->ucMessageID);
printf("RXMSG->ucData[0] = ");
for(i=0;i<20;i++)
printf(" %03d",RXMSG->ucData[i]);
printf("\r\n");
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
这里的关键就在第二个参数ptMsg,它已经是指针了,为什么还要取地址,这样不是一个二级指针了吗,而它的参数是void *
,给人的感觉应该就是传一个地址,虽然二级指针也是地址,但是总觉得不应该设计成二级指针赋值给一个一级指针,哪怕你是void*。但是我们既然使用了freertos,就要遵循别人的设计,别人这样做,肯定有自己的道理,我们做到熟练应用即可。试想,消息发送函数,要发送数据,要得到这个数据的地址以给memcpy,如果传递的数据本身就是地址(指针),那么我们要把这个地址传到接收函数去,就应该得到此时指针的地址才行,也就是传递一个指针的值,注意不是指针指向的值。关键我们要通过memcpy函数,传递一个指针的值通过memcpy必然是需要二级指针的,这样才可以操作一级指针的值,这样也就可以理解为什么ptMsg已经是指针了,却还是要传递ptMsg的地址,因为只有这样,才可以通过memcpy函数把ptMsg指针的值给到接收函数的指针,这样在接收函数中操作这个结构体类型的指针,就可以得到发送端的数据。这样做的好处是,避免了大数据的拷贝,只拷贝指针,提高了效率,但是使用指针,一定不要在栈空间开辟,这也是为什么我们定义g_tMsg结构体实体在全局区。但是freertos任务中一直有while(1),元素生命周期一直都在,此时还是可以使用局部变量做数据传递工具,但是这样的编程模式应该摒弃,我们采用全局区开辟的空间。
主要内容:
STM32F07开发板实验,关键代码实现
/* cubemx自动生成的创建2个消息队列 */
/* 创建10个uint8_t型消息队列 */
osMessageQDef(myQueue01, 10, uint8_t);
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
/* 创建10个存储指针变量的消息队列 */
osMessageQDef(myQueue02, 1, MSG*);
myQueue02Handle = osMessageCreate(osMessageQ(myQueue02), NULL);
freertos.c源文件
/* USER CODE BEGIN Header */
/**
******************************************************************************
* File Name : freertos.c
* Description : Code for freertos applications
******************************************************************************
* This notice applies to any and all portions of this file
* that are not between comment pairs USER CODE BEGIN and
* USER CODE END. Other portions of this file, whether
* inserted by the user or by software development tools
* are owned by their respective copyright owners.
*
* Copyright (c) 2019 STMicroelectronics International N.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted, provided that the following conditions are met:
*
* 1. Redistribution of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of other
* contributors to this software may be used to endorse or promote products
* derived from this software without specific written permission.
* 4. This software, including modifications and/or derivative works of this
* software, must execute solely and exclusively on microcontroller or
* microprocessor devices manufactured by or for STMicroelectronics.
* 5. Redistribution and use of this software other than as permitted under
* this license is void and will automatically terminate your rights under
* this license.
*
* THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
* RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
* SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_usart.h"
#include "bsp_gpio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
volatile unsigned long ulHighFrequencyTimerTicks = 0ul;
typedef struct AMessage
{
char ucMessageID;
char ucData[ 20 ];
}MSG;
MSG DATAMSG;
/* USER CODE END Variables */
osThreadId PrintfTaskHandle;
osThreadId myTask02Handle;
osThreadId myTask03Handle;
osMessageQId myQueue01Handle;
osMessageQId myQueue02Handle;
osMessageQId myQueue03Handle;
osMessageQId myQueue04Handle;
osMessageQId myQueue05Handle;
osTimerId myTimer01Handle;
osTimerId myTimer02Handle;
osMutexId myMutex01Handle;
osSemaphoreId myBinarySem01Handle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
void Show_SYS_INFO_Task(void);
/* USER CODE END FunctionPrototypes */
void StartPrintfTask(void const * argument);
void StartTask02(void const * argument);
void StartTask03(void const * argument);
void Callback01(void const * argument);
void Callback02(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* Hook prototypes */
void configureTimerForRunTimeStats(void);
unsigned long getRunTimeCounterValue(void);
uint8_t buff[50] ={0x01,0x02,0x03} ;
/* USER CODE BEGIN 1 */
/* Functions needed when configGENERATE_RUN_TIME_STATS is on */
void configureTimerForRunTimeStats(void)
{
ulHighFrequencyTimerTicks = 0ul;
}
unsigned long getRunTimeCounterValue(void)
{
return ulHighFrequencyTimerTicks;
}
/* USER CODE END 1 */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Create the mutex(es) */
/* definition and creation of myMutex01 */
osMutexDef(myMutex01);
myMutex01Handle = osMutexCreate(osMutex(myMutex01));
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* Create the semaphores(s) */
/* definition and creation of myBinarySem01 */
osSemaphoreDef(myBinarySem01);
myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* Create the timer(s) */
/* definition and creation of myTimer01 */
osTimerDef(myTimer01, Callback01);
myTimer01Handle = osTimerCreate(osTimer(myTimer01), osTimerPeriodic, NULL);
/* definition and creation of myTimer02 */
osTimerDef(myTimer02, Callback02);
myTimer02Handle = osTimerCreate(osTimer(myTimer02), osTimerPeriodic, NULL);
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* Create the thread(s) */
/* definition and creation of PrintfTask */
osThreadDef(PrintfTask, StartPrintfTask, osPriorityNormal, 0, 512);
PrintfTaskHandle = osThreadCreate(osThread(PrintfTask), NULL);
/* definition and creation of myTask02 */
osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 256);
myTask02Handle = osThreadCreate(osThread(myTask02), NULL);
/* definition and creation of myTask03 */
osThreadDef(myTask03, StartTask03, osPriorityNormal, 0, 256);
myTask03Handle = osThreadCreate(osThread(myTask03), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* Create the queue(s) */
/* definition and creation of myQueue01 */
/* what about the sizeof here??? cd native code */
osMessageQDef(myQueue01, 10, uint8_t);
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
// myQueue01Handle = xQueueCreate(10,4);
/* definition and creation of myQueue02 */
/* what about the sizeof here??? cd native code */
osMessageQDef(myQueue02, 1, MSG*);
myQueue02Handle = osMessageCreate(osMessageQ(myQueue02), NULL);
//// myQueue02Handle = xQueueCreate(10,sizeof(struct AMessage*));
// myQueue02Handle = xQueueCreate(10,sizeof(MSG *));
// /* definition and creation of myQueue03 */
///* what about the sizeof here??? cd native code */
// osMessageQDef(myQueue03, 32, uint8_t);
// myQueue03Handle = osMessageCreate(osMessageQ(myQueue03), NULL);
// /* definition and creation of myQueue04 */
///* what about the sizeof here??? cd native code */
// osMessageQDef(myQueue04, 32, uint8_t);
// myQueue04Handle = osMessageCreate(osMessageQ(myQueue04), NULL);
// /* definition and creation of myQueue05 */
///* what about the sizeof here??? cd native code */
// osMessageQDef(myQueue05, 32, uint8_t);
// myQueue05Handle = osMessageCreate(osMessageQ(myQueue05), NULL);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
}
/* USER CODE BEGIN Header_StartPrintfTask */
/**
* @brief Function implementing the PrintfTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartPrintfTask */
void StartPrintfTask(void const * argument)
{
/* USER CODE BEGIN StartPrintfTask */
MSG * TXMSG;
TXMSG = &DATAMSG;
uint8_t j = 0;
uint8_t i;
TXMSG->ucMessageID = 1;
TXMSG->ucData[0] = 0;
/* Infinite loop */
for(;;)
{
if(key0_value == KEY0_PRES)
{
osSemaphoreRelease(myBinarySem01Handle);
key0_value = 0;
}
if(key1_value == KEY1_PRES)
{
TXMSG->ucMessageID++;
for(i=0;i<20;i++,j++)
{
TXMSG->ucData[i]=j;
}
if(xQueueSend(myQueue02Handle,(void*)&TXMSG,0) ==errQUEUE_FULL)
{
printf("myQueue02Handle errQUEUE_FULL\r\n");
}
key1_value = 0;
}
if(key2_value == KEY2_PRES)
{
if(xQueueSend(myQueue01Handle,(void*)&key2_value,10) == errQUEUE_FULL)
{
printf("myQueue01Handle errQUEUE_FULL\r\n");
}
key2_value = 0;
}
if(keyup_value == WKUP_PRES)
{
Show_SYS_INFO_Task();
keyup_value = 0;
}
j++;
if(j%10 == 0)
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
osDelay(10);
}
/* USER CODE END StartPrintfTask */
}
/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
osSemaphoreWait(myBinarySem01Handle, osWaitForever);
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
printf("StartTask02 osSemaphoreWait(myBinarySem01Handle);\r\n");
osDelay(10);
}
/* USER CODE END StartTask02 */
}
/* USER CODE BEGIN Header_StartTask03 */
/**
* @brief Function implementing the myTask03 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void const * argument)
{
/* USER CODE BEGIN StartTask03 */
uint8_t pvBuffer ;
MSG *RXMSG;
uint16_t i = 0;
/* Infinite loop */
for(;;)
{
if(xQueueReceive( myQueue02Handle, (void*)&RXMSG, 10) == pdPASS)
{
printf("\r\nRXMSG->ucMessageID = %d \r",RXMSG->ucMessageID);
printf("RXMSG->ucData[0] = ");
for(i=0;i<20;i++)
printf(" %03d",RXMSG->ucData[i]);
printf("\r\n");
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
if(xQueueReceive( myQueue01Handle, (void *) &pvBuffer, 10) == pdPASS)
{
printf("myQueue01Handle %03d \r\n",pvBuffer);
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
osDelay(10);
}
/* USER CODE END StartTask03 */
}
/* Callback01 function */
void Callback01(void const * argument)
{
/* USER CODE BEGIN Callback01 */
/* USER CODE END Callback01 */
}
/* Callback02 function */
void Callback02(void const * argument)
{
/* USER CODE BEGIN Callback02 */
/* USER CODE END Callback02 */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void Show_SYS_INFO_Task(void)
{
uint8_t pcWriteBuffer[200];
printf("=================================================\r\n");
printf("\r\ntask_name \tstate\t prior\trtack\t Id\r\n");
vTaskList((char *)&pcWriteBuffer);
printf("%s\r\n", pcWriteBuffer);
printf("\r\ntask_name time_count(10us) usage_pec\r\n");
vTaskGetRunTimeStats((char *)&pcWriteBuffer);
printf("%s\r\n", pcWriteBuffer);
}
/* USER CODE END Application */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
FreeRTOSConfig.h头文件
/*
FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.
All rights reserved
VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.
This file is part of the FreeRTOS distribution.
FreeRTOS is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License (version 2) as published by the
Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception.
***************************************************************************
>>! NOTE: The modification to the GPL is included to allow you to !<<
>>! distribute a combined work that includes FreeRTOS without being !<<
>>! obliged to provide the source code for proprietary components !<<
>>! outside of the FreeRTOS kernel. !<<
***************************************************************************
FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. Full license text is available on the following
link: http://www.freertos.org/a00114.html
***************************************************************************
* *
* FreeRTOS provides completely free yet professionally developed, *
* robust, strictly quality controlled, supported, and cross *
* platform software that is more than just the market leader, it *
* is the industry's de facto standard. *
* *
* Help yourself get started quickly while simultaneously helping *
* to support the FreeRTOS project by purchasing a FreeRTOS *
* tutorial book, reference manual, or both: *
* http://www.FreeRTOS.org/Documentation *
* *
***************************************************************************
http://www.FreeRTOS.org/FAQHelp.html - Having a problem? Start by reading
the FAQ page "My application does not run, what could be wrong?". Have you
defined configASSERT()?
http://www.FreeRTOS.org/support - In return for receiving this top quality
embedded software for free we request you assist our global community by
participating in the support forum.
http://www.FreeRTOS.org/training - Investing in training allows your team to
be as productive as possible as early as possible. Now you can receive
FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
Ltd, and the world's leading authority on the world's leading RTOS.
http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
including FreeRTOS+Trace - an indispensable productivity tool, a DOS
compatible FAT file system, and our tiny thread aware UDP/IP stack.
http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.
http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
Integrity Systems ltd. to sell under the OpenRTOS brand. Low cost OpenRTOS
licenses offer ticketed support, indemnification and commercial middleware.
http://www.SafeRTOS.com - High Integrity Systems also provide a safety
engineered and independently SIL3 certified version for use in safety and
mission critical applications that require provable dependability.
1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html.
*----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* Section where include file can be added */
/* USER CODE END Includes */
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include
extern uint32_t SystemCoreClock;
/* USER CODE BEGIN 0 */
extern void configureTimerForRunTimeStats(void);
extern unsigned long getRunTimeCounterValue(void);
/* USER CODE END 0 */
#endif
#define configUSE_PREEMPTION 1
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ((TickType_t)1000)
#define configMAX_PRIORITIES ( 7 )
#define configMINIMAL_STACK_SIZE ((uint16_t)64)
#define configTOTAL_HEAP_SIZE ((size_t)15360)
#define configMAX_TASK_NAME_LEN ( 16 )
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configUSE_16_BIT_TICKS 0
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 128
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_pcTaskGetTaskName 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_eTaskGetState 1
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
/* USER CODE BEGIN 1 */
#define configASSERT( x ) if ((x) == 0) {taskDISABLE_INTERRUPTS(); for( ;; );}
/* USER CODE END 1 */
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
/* IMPORTANT: This define MUST be commented when used with STM32Cube firmware,
to prevent overwriting SysTick_Handler defined within STM32Cube HAL */
/* #define xPortSysTickHandler SysTick_Handler */
/* USER CODE BEGIN 2 */
/* Definitions needed when configGENERATE_RUN_TIME_STATS is on */
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS configureTimerForRunTimeStats
#define portGET_RUN_TIME_COUNTER_VALUE getRunTimeCounterValue
/* USER CODE END 2 */
/* USER CODE BEGIN Defines */
/* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
/* USER CODE END Defines */
#endif /* FREERTOS_CONFIG_H */
FreeRTOS使用queue的注意事项
osMessageQDef(myQueue01, 32, uint8_t);
中的最后一个参数type可以为uint8_t或uint16_t或uint32_t等。TXMSG
用来存放发送数据,数据完成打包后,使用在xQueueSend(myQueue02Handle,(void*)&TXMSG,0)
将结构体指针的地址用消息队列发送出去。在消息队列接收端同样定义一个相同类型的结构体指针RXMSG
来接收数据xQueueReceive( myQueue02Handle, (void*)&RXMSG, 10)
,这样即可以实现多字节大量数据的传输。typedef struct AMessage
{
char ucMessageID;
char ucData[ 20 ];
}MSG;
在定义接受和发送结构体指针时一定记得要赋初始值以便在在程序运行时即分配空间,否则易出现传输数据异常。
MSG DATAMSG; MSG * TXMSG = &DATAMSG;
源代码1上传github
源代码2上传github
源代码3上传github
信号量(semaphores)是20世纪60年代中期Edgser Dijkstra发明的。使用信号量的最初目的是为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。 实际的应用中,信号量的作用又该如何体现呢?比如有个30人的电脑机房,我们就可以创建信号量的初始化值是30,表示30个可用资源,不理解的初学者表示信号量还有初始值?是的,信号量说白了就是共享资源的数量。另外我们要求一个同学使用一台电脑,这样每有一个同学使用一台电脑,那么信号量的数值就减一,直到30台电脑都被占用,此时信号量的数值就是0。如果此时还有几个同学没有电脑可以使用,那么这几个同学就得等待,直到有同学离开。有一个同学离开,那么信号量的数值就加1,有两个就加2,依此类推。刚才没有电脑用的同学此时就有电脑可以用了,有几个同学用,信号量就减几,直到再次没有电脑可以用,这么一个过程就是使用信号量来管理共享资源的过程。
平时使用信号量主要实现以下两个功能:
三者的关系
二值信号量是计数信号量的特例,二值信号量与互斥信号量原理大同,不同点在互斥信号量可以避免多任务访问共享变量时出现的优先级反转。
防止优先级翻转的过程
运行条件:
运行过程描述如下:
FreeRTOS计数信号量的另一种实现方式----基于任务通知(Task Notifications)的计数信号量,这里我们将这种方式实现的计数信号量称之为任务计数信号量。任务计数信号量效率更高,需要的RAM空间更小。当然,缺点也是有的,它没有前面介绍的计数信号量实现的功能全面。
任务通知的介绍
FreeRTOS每个已经创建的任务都有一个任务控制块(task control block),任务控制块就是一个结构体变量,用于记录任务的相关信息。结构体变量中有一个32位的变量成员ulNotifiedValue是专门用于任务通知的。 通过任务通知方式可以实现计数信号量,二值信号量,事件标志组和消息邮箱(消息邮箱就是消息队列长度为1的情况)。使用方法与前面章节讲解的事件标志组和信号量基本相同,只是换了不同的函数来实现。
任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量ulNotifiedValue实现的:
采用这种方式有什么优势呢?根据官方的测试数据,唤醒由于信号量和事件标志组而处于阻塞态的任务,速度提升了45%,而且这种方式需要的RAM空间更小。但这种方式实现的信号量和事件标志组也有它的局限性,主要表现在以下两个方面:
任务计数信号量/任务二值信号量/任务事件标志组消息邮箱 的源码追踪
osThreadDef(LED_Task, LED_TaskStart, osPriorityBelowNormal, 0, 256);
LED_TaskHandle = osThreadCreate(osThread(LED_Task), NULL);
xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),&handle) != pdPASS)
TCB_t *pxNewTCB;//这个就是任务控制块
typedef tskTCB TCB_t;
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif