freeRTOS与裸机程序相比有什么区别??
FreeRTOS命名及变量规则
初学FreeRTOS的用户对其变量和函数的命名比较迷惑, FreeRTOS的核心源代码遵从MISRA编码标准指南,关于MISRA编码标准,可以查看文章https://wenku.baidu.com/view/5e7b2f4ee518964bcf847c99.html。下面专门做一下介绍:
变量
uint32_t定义的变量都加上前缀ul。u代表unsigned 无符号,l代表long长整型。
uint16_t定义的变量都加上前缀us。u代表unsigned无符号,s代表short短整型。
uint8_t定义的变量都加上前缀uc。u代表unsigned无符号,c代表char字符型。
stdint.h文件中未定义的变量类型,在定义变量时需要加上前缀x,比如BaseType_t和TickType_t定义的变量。
stdint.h文件中未定义的无符号变量类型,在定义变量时要加上前缀u,比如UBaseType_t 定义的变量要加上前缀ux。
size_t 定义的变量也要加上前缀ux。
枚举变量会加上前缀e。
指针变量会加上前缀p,比如uint16_t定义的指针变量会加上前缀pus。
根据MISRA代码规则,char定义的变量只能用于ASCII字符,前缀使用c。
根据MISRA代码规则,char *定义的指针变量只能用于ASCII字符串,前缀使用pc。
函数
加上了static声明的函数,定义时要加上前缀prv,这个是单词private的缩写。
带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即void类型 ,函数的前缀加上字母v。
根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,比如tasks.c文件中函数vTaskDelete,函数中的task就是文件名中的task。
宏定义
根据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h里面。宏定义中的config就是文件名中的config。另外注意,前缀要小写。
除了前缀,其余部分全部大写,同时用下划线分开。
数据类型
FreeRTOS使用的数据类型主要分为stdint.h文件中定义的和自己定义的两种。其中char和char *定义的变量要特别注意。
- char:与MISRA编码标准指南一致,char类型变量仅被允许保存ASCII字符
- char *:与MISRA编码标准指南一致,char *类型变量仅允许指向ASCII字符串。当标准库函数期望一个char *参数时,这样做可以消除一些编译器警告;特别是考虑到有些编译器将char类型当做signed类型,还有些编译器将char类型当做unsigned类型。
FreeRTOS主要自定义了以下四种数据类型:
TickType_t 如果用户使能了宏定义 configUSE_16_BIT_TICKS,那么TickType_t定义的就是16位无符号数,如果没有使能,那么TickType_t定义的就是32位无符号数。对于32位架构的处理器,一定要禁止此宏定义,即设置此宏定义数值为0即可。
BaseType_t 这个数据类型根据系统架构的位数而定,对于32位架构,BaseType_t定义的是32位有符号数,对于16位架构,BaseType_t定义的是16位有符号数。如果BaseType_t被定义成了char型,要特别注意将其设置为有符号数,因为部分函数的返回值是用负数来表示错误类型。
UBaseType_t 这个数据类型是BaseType_t类型的有符号版本。
StackType_栈变量数据类型定义,这个数量类型由系统架构决定,对于16位系统架构,StackType_t定义的是 16位变量,对于32位系统架构,StackType_t定义的是32位变量。
示例如下:
/* 首先在这里包含库文件... */ #include <stdlib.h> /* ...然后是FreeRTOS的头文件... */ #include "FreeRTOS.h" /* ...紧接着包含其它头文件. */ #include "HardwareSpecifics.h" /* 随后是#defines, 在合理的位置添加括号. */ #define A_DEFINITION ( 1 ) /* * 随后是Static (文件内部的)函数原型, * 如果注释有多行,参照本条注释风格---每一行都以’*’起始. */ static void prvAFunction( uint32_t ulParameter ); /* 文件作用域变量(本文件内部使用)紧随其后,要在函数体定义之前. */ static BaseType_t xMyVariable. /* 每一个函数的结束都有一行破折号,破折号与下面的第一个函数之间留一行空白。*/ /*-----------------------------------------------------------*/ void vAFunction( void ) { /* 函数体在此定义,注意要用大括号括住 */ } /*-----------------------------------------------------------*/ static UBaseType_t prvNextFunction( void ) { /* 函数体在此定义. */ } /*-----------------------------------------------------------*/ /* * 函数名字总是占一行,包括返回类型。 左括号之前没有空格左括号之后有一个空格, * 每个参数后面有一个空格参数的命名应该具有一定的描述性. */ void vAnExampleFunction( long lParameter1, unsigned short usParameter2 ) { /* 变量声明没有缩进. */ uint8_t ucByte; /* 代码要对齐. 大括号占独自一行. */ for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ ) { /* 这里再次缩进. */ } } /* * for、while、do、if结构具有相似的模式。这些关键字和左括号之间没有空格。 * 左括号之后有一个空格,右括号前面也有一个空格,每个分号后面有一个空格。 * 每个运算符的前后各一个空格。使用圆括号明确运算符的优先级。不允许有0 * 以外的数字(魔鬼数)出现,必要时将这些数字换成能表示出数字含义的常量或 * 宏定义。 */ for( ucByte = 0U; ucByte < fileBUFFER_LENGTH; ucByte++ ) { } while( ucByte < fileBUFFER_LENGTH ) { } /* * 由于运算符优先级的复杂性,我们不能相信自己对运算符优先级时刻保持警惕 * 并能正确的使用,因此对于多个表达式运算时,使用括号明确优先级顺序 */ if( ( ucByte < fileBUFFER_LENGTH ) && ( ucByte != 0U ) ) { ulResult = ( ( ulValue1 + ulValue2 ) - ulValue3 ) * ulValue4; } /* 条件表达式也要像其它代码那样对齐。 */ #if( configUSE_TRACE_FACILITY == 1 ) { /* 向TCB增加一个用于跟踪的计数器. */ pxNewTCB->uxTCBNumber = uxTaskNumber; } #endif /*方括号前后各留一个空格*/ ucBuffer[ 0 ] = 0U; ucBuffer[ fileBUFFER_LENGTH - 1U ] = 0U;
裸机程序的运行缺点
采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了:
必须在中断 (ISR) 内处理时间关键运算 内处理时间关键运算 :
ISR ISR 函数变得非常复杂,并且需要很长执行时间 。
ISR ISR 嵌套可能产生不预测的执行时间和堆栈 需求。
超级循环和 ISR 之间的 数据交换是通过全局共享变量进行:
应用程序的员必须确保数据一致性 。
超级循环可以与系统计时器轻松同步,但:
如果系统需要多种不同的周期时间,则会很难实现 。
超过 超级循环周期的耗时函数需要做拆分。
增加 软件开销,应用程序难以理解 。
超级循环 使得 应用程序变得非常复杂,因此难以扩展 :
一个简单的更改就可能产生不预测副作用 ,对这种副作用进行分析非常耗时。
超级循环概念的这些缺点可以通过使用实时操作系统 (RTOS) 来解决。
多任务的优点
针对这些情况,使用多任务系统就可以解决这些问题了。下面是一个多任务系统的流程图:
多任务系统或者说RTOS的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。如上图所示的那样,创建了任务并完成OS初始化后,就可以通过调度器来决定任务A,任务B和任务C的运行,从而实现多任务系统。另外需要初学者注意的是,这里所说的多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更好的说明这个问题,再举一个详细的运行例子,运行条件如下:
使用抢占式调度器。
1个空闲任务,优先级最低。
2个应用任务,一个高优先级和一个低优先级,优先级都比空闲任务优先级高。
中断服务程序,含USB中断,串口中断和系统滴答定时器中断。
下图是任务的运行过程,其中横坐标是任务优先级由低到高排列,纵坐标是运行时间,时间刻度有小到大。
多任务系统运行过程
(1) 启动RTOS,首先执行高优先级任务(vTaskStartScheduler)。
(2) 高优先级任务等待事件标志(xEventGroupWaitBits)被阻塞,低优先级任务得到执行。
(3) 低优先级任务执行的过程中产生USB中断,进入USB中断服务程序。
(4) 退出USB中断复位程序,回到低优先级任务继续执行。
(5) 低优先级任务执行过程中产生串口接收中断,进入串口接收中断服务程序。
(6) 退出串口接收中断复位程序,并发送事件标志设置消息(xEventGroupSetBitsFromISR),被阻塞的高优先级任务就会重新进入就绪状态,这个时候高优先级任务和低优先级任务都在就绪态,抢占式调度器就会让高优先级的任务先执行,所以此时就会进入高优先级任务。
(7) 高优先级任务由于等待事件标志(xEventGroupWaitBits)会再次被阻塞,低优先级任务开始继续执行。
(8) 低优先级任务调用函数vTaskDelay,低优先级任务被挂起,从而空闲任务得到执行。
(9) 空闲任务执行期间发生滴答定时器中断,进入滴答定时器中断服务程序。
(10) 退出滴答定时器中断,由于低优先级任务延时时间到,低优先级任务继续执行。
(11) 低优先级任务再次调用延迟函数vTaskDelay,低优先级任务被挂起,从而切换到空闲任务。空闲任务得到执行。
FreeRTOS就是一款支持多任务运行的实时操作系统,具有时间片,抢占式和合作式三种调度方法。通过FreeRTOS实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。
FreeRTOSreeRTOS reeRTOSreeRTOS 的运行支持以下 四种状态 :
Run ninging —运行态
当任务处于实际运行状态被称之为运行态,即 CPU 的使用权被这个任务占用。
Ready —就绪态
处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务, 因为同优先级或更高优先级的任务正在运行。
Blocked locked —阻塞态
由于等待信号量,消息等待信号量,消息 队列 ,事件标志组等而处于的状态被称之为阻塞态,另外任务调用延迟函数也会处于阻塞态。
Suspended—挂起态
类似阻塞态,通过调用函数 vTaskSuspend( vTaskSuspend( )对指定任务进行挂起,后这个将不被执只对指定任务进行挂起,后这个将不被执只有调用函数 xTaskResume()才可以将这个将这个任务从挂起态恢复 。
下面是任务 在各个状态之间切换的关系图 ,通过这个图 ,大家基本可以对任务的运行状态有了一个整体的认识。
事件状态组的转换