RTOS几点思考
为什么使用RTOS?
1. 先考虑OS有什么特点?
无OS开发情况下,就是一个while循环中执行所有模块功能
- 顺序执行
模块依次执行,不能调换顺序,更不能同步实时执行
void main()
{
BSP_Init();//硬件初始化
while(1)
{
task1();//模块1执行
.......
task4();//模块4执行
}
}
- 状态调度
模块根据状态变化执行,可以调换顺序,不能同步实时执行
unsigned int switchflag;
void main()
{
BSP_Init();//硬件初始化
while(1)
{
switch(switchflag)
{
case status1:
task1();//模块1执行
break;
case status2:
task2();//模块2执行
break;
.......
}
}
}
上面的任务都需要执行完才能跳转到其它任务,不能在任务运行期间跳转。也有一种情况是任务自己提前break,但是你能让自己break,你不能让别人break啊,所以我们需要一个东西管理任务切换执行,这个就是OS了。
基于这个最初的需求可以看一下现在的OS实现了什么功能?
- 支持抢占切换/合作切换/时间片切换任务
- 支持低功耗模式
- 支持多种系统架构
- 支持任务同步机制
- 支持堆栈溢出检测
- 工具免费,源码免费,版权免费
2. 再考虑什么情况下使用OS?
1.项目功能单一,程序简单,各模块没有强烈同步执行的需求(实时性需求不强),不需要OS
2.项目功能复杂,模块交织,多个模块有强烈同步执行的需求(实时性需求强),需要OS
根据项目考量,没有明确界限
FreeRTOS调试技巧
- 重定向printf/scanf函数进行串口打印
/*重定向printf函数*/
int fputc(int data,FILE* file)
{
USART_SendData(USARTx,(char)data);//发送一个字节
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);//等待发送成功
return data;
}
/*重定向scanf函数*/
int fgetc(FILE* file)
{
char data;
while(USART_GetFlagStatus(USARTx,USART_FLAG_RXNE)==RESET);//等待接收成功
data=USART_ReceiveData(USARTx);//接收一个字节
return (int)data;
}
- 调试任务运行状态,任务栈余量,CPU占用率
- 实现计数器
volatile uint32_t OS_DebugTimeTicks=0;//调试心跳
void BSP_Timex()
{
//实现20KHz频率的定时器
//开定时器中断
//OS_DebugTimeTicks在中断中自增
}
- FreeRTOSConfig.h文件宏配置
extern uint32_t OS_DebugTimeTicks;
#define configUSE_TRACE_FACILITY 1
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (OS_DebugTimeTicks= 0UL)
#define portGET_RUN_TIME_COUNTER_VALUE() OS_DebugTimeTicks
- 调用RTOS调试函数vTaskList(),vTaskGetRunTimeStats()输出任务信息
static void vTaskTaskUserIF(void *pvParameters)
{
uint8_t ucKeyCode;
uint8_t DebugInfoBuff[500];
while(1)
{
printf("=================================================\r\n");
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n");
vTaskList((char *)&DebugInfoBuff);
printf("%s\r\n", DebugInfoBuff);
printf("\r\n任务名 运行计数 使用率\r\n");
vTaskGetRunTimeStats((char *)&DebugInfoBuff);
printf("%s\r\n", DebugInfoBuff);
vTaskDelay(20);
}
}
任务管理
1. 任务调度流程
2. 任务栈大小,栈溢出检测
- 栈大小(最小栈空间)
栈 | 内容 |
---|---|
函数 | 每层函数:局部变量+形参+返回地址+状态保存 |
任务切换 | 部分CPU寄存器 +FPU寄存器 |
中断 | 部分CPU寄存器 +FPU寄存器 |
可变参函数,函数指针,递归 | 无法确定栈大小 |
标准栈空间:最小栈大小的1.5~2倍大小
函数栈大小及嵌套深度可以参考工程编译文件.htm文件
RTOS调试技巧测试栈空间,适当调整
- 栈溢出检测
栈生长地址由高到低,如果栈指针下移的地址越过了任务栈的基地址则栈溢出。
软件预设检测
在栈生长方向的末端开辟一段空间专门存放固定值,定时判断固定值有没有被修改,被修改说明栈溢出了。存在栈溢出超过判断区间直接宕机的可能。
MPU内存保护
通过MPU对栈生长方向的末端区间进行保护,如果出现栈溢出触发MPU异常,在异常中分析栈溢出情况。
FreeRTOS栈溢出检测
方法1:任务切换时检测栈指针是否越界,栈溢出触发溢出回调函数vApplicationStackOverflowHook()
#define configCHECK_FOR_STACK_OVERFLOW 1
void vApplicationStackOverflowHook( TaskHandle_t xTask,signed char *pcTaskName);//溢出回调函数
方法2: 任务栈初始化为固定值,任务切换时检测栈尾16个字节是否都是初始化设置的固定值。
#define configCHECK_FOR_STACK_OVERFLOW 2
void vApplicationStackOverflowHook( TaskHandle_t xTask,signed char *pcTaskName);//溢出回调函数
任务切换时进行栈溢出检测无法确保任务运行期间栈不溢出。
任务切换时进行栈溢出检测无问题只能说明栈尾的数据是固定值,但是不能说明是初始时设置的固定值还是运行时保存的固定值,存在第2种情况的可能性。
3. 中断管理
M3/M4内核MCU中断优先级取寄存器8位中的高4位表示,共有16种组合。
几点原则:
- RTOS采用中断分组4,也就是0~15级全抢占式优先级
- 高抢占级中断可以抢占低抢占级中断
- 同抢占级中断不能互相抢占,先到先行
- Reset,NMI不可屏蔽中断,Hard Fault高于普通中断,优先级固定
RTOS中断配置:
Systick中断:产生心跳,触发PendSV中断
PendSV中断:任务切换
SVC中断:启动任务,只执行一次
#define configPRIO_BITS 4//优先级占用bits
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15//RTOS用的Systick和PendSV中断优先级
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )//Systick和PendSV中断配置的优先级左移4位写进优先级配置寄存器
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2//可以使用RTOS函数的最高优先级,2~15优先级中断中可用RTOS接口
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )//可以使用RTOS函数的最高优先级左移4位写进优先级配置寄存器
RTOS管理的中断会出现中断延迟的可能,对于实时性要求强的中断不能被RTOS管理,否则会出现中断无法及时响应的情况。
FreeRTOS使用basepri寄存器,uCOS使用primask寄存器。
寄存器 | 功能 |
---|---|
primask | 这是个只有 1 个 bit 的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下NMI 和硬 fault 可以响应。它的缺省值是 0,表示没有关中断 |
faultmask | 这是个只有 1 个 bit 的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常,甚至是硬 fault,也通通闭嘴。它的缺省值也是 0,表示没有关异常 |
basepri | 这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值 |
4. 内存管理
FreeRTOS提供5种内存管理接口
FreeRTOS API接口
1. 任务创建
函数 | 描述 |
---|---|
xTaskCreate( ) | 动态创建一个任务,从RTOS的堆上分配栈空间 |
xTaskCreateStatic( ) | 静态创建一个任务,用户手动分配栈空间 |
xTaskCreateRestricted( ) | 任务控制块从RTOS堆上分配,用户手动分配栈空间 (和FreeRTOS-MPU一起使用) |
xTaskCreateRestrictedStatic( ) | 用户手动分配任务控制块和栈空间 (和FreeRTOS-MPU一起使用) |