大家好,欢迎来到IT知识分享网。
目录
一、前后台系统和RTOS系统
1.1 前后台系统
STM32学习的都是裸机形式,通常把程序分为两部分:前台系统和后台系统。前台是中断级,后台是任务级。
- 裸机:没有任何操作系统
- 前台系统:中断
- 后台系统:主程序中的循环
1.2 RTOS系统
RTOS实时操作系统就是类似于linux,windows的一个多任务操作系统。理论上仍然只有一个程序在运行,但它会在各个任务之间进行资源调度,看上去像是在并发执行每一个任务(多线程/多进程)。
- RTOS操作系统:UCOS,FreeRTOS,RTX,RT-Thread,DJYOS等。
- RTOS操作系统的核心内容在于:实时内核。
二、UCOS
UCOS的任务优先级有64个,范围是0~63。都比中断低。
三、UCOS移植
平台上有很多,自己去搜
四、任务创建
任务都是单向不循环的链表
数据域是一个OS_TCB的任务结构体。一般情况下,先独立创建一个起始任务,该起始任务负者初始化中间件,滴答定时器和基本外设。然后再创建其他子任务。要求起始任务的优先级大于其他子任务的优先级,这样起始任务在初始化过程中,不会被其他子任务打断。最后销毁掉起始任务,让其他子任务争夺CPU运行。
4.1 起始任务创建
OSTaskCreate((OS_TCB *)&AppTaskStartTCB, //任务结构体 (CPU_CHAR *)"App Task Start", //任务名称 (OS_TASK_PTR )AppTaskStart, //任务需要执行的函数 (void *)0u, //任务参数 (OS_PRIO )APP_CFG_TASK_START_PRIO,//任务优先级 (CPU_STK *)&AppTaskStartStk[0u], //堆栈空间 (CPU_STK_SIZE )AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE / 10u],//堆栈限深 (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE,//堆栈空间大小 (OS_MSG_QTY )0u,//消息队列 (OS_TICK )0u,//时间片大小 (void *)0u,//扩展存储器 (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务标志位清零 (OS_ERR *)&err);//出错判断结构体
工作任务在AppTaskStart中,只需对它进行改写,在任务函数中,对BSP_Init()进行改写。BSP_Init()函数中存放着模块和片上外设的初始化。
static void AppTaskStart (void *p_arg) { OS_ERR err; (void)p_arg; BSP_Init(); /* 中间件初始化 */ BSP_Tick_Init(); /* 滴答定时器初始化 */ #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif while (DEF_TRUE) { printf("task start\n"); OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_DLY,&err); } }
在stm32中使用printf时重写puts()函数,需要将stdio.h中的函数全部设置为弱声明。
int fputc(int ch,FILE* f) { //printf输出的是串口1 USART_SendData(USART1, ch); while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET ); return ch; }
4.2 子任务创建
4.2.1 时间片轮转开启
时间片是多个任务/线程/进程在并发执行时,每个任务执行的基本时间单位,它是由滴答定时器来管理的。
例如:设置任务A有10个时间片,任务B有20个时间片。然后1个时间片是1ms。表示任务A运行10ms以后,CPU会切换到任务B运行。任务B运行20ms以后,切换到任务A运行。两者交替。因为时间片一般都很小,所以看上去任务A和任务B是在同时运行。
在os_cfg.h文件中使能sched_round_robin (轮转调度)
#define OS_CFG_SCHED_ROUND_ROBIN_EN 1u
通过修改宏定义为1,开启条件编译,得到函数
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u void OSSchedRoundRobinCfg (CPU_BOOLEAN en, //使能或者失能 OS_TICK dflt_time_quanta,//节拍数的单位 OS_ERR *p_err); //错误管理结构体
注意:一个任务的时间片长度,必须大于它执行一次的时间
当任务调度时间到了后会直接打断当前的运行,导致有的任务没执行完直接被打断,这时候设置临界区。
4.2.2 临界区设置
任务调度是通过时间片累计完成的,当时间片累计到一定程度时切换;而时间片是通过滴答定时器中断累计的,那么反过来,只要关闭中断,时间片就不会再累计,那么任务调度就不会被执行。
临界区的本质: 关闭STM32上的所有中断
CPU_SR_ALLOC(); //临界区初始化
CPU_CRITICAL_ENTER(); //进入临界区
CPU_CRITICAL_EXIT(); //退出临界区
//第一个子任务 static OS_TCB taskA_TCB; static CPU_STK taskA_Stk[128]; static void taskA(void* p_arg); //第二个子任务 static OS_TCB taskB_TCB; static CPU_STK taskB_Stk[128]; static void taskB(void* p_arg); static void AppTaskStart (void *p_arg) { OS_ERR err; (void)p_arg; BSP_Init(); /* 中间件初始化 */ BSP_Tick_Init(); /* 滴答定时器初始化 */ #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif //开启时间片调度 OSSchedRoundRobinCfg(ENABLE,0,&err); //创建子任务A OSTaskCreate((OS_TCB *)&taskA_TCB, //任务结构体 (CPU_CHAR *)"taskA", //任务名称 (OS_TASK_PTR )taskA, //任务需要执行的函数 (void *)0u, //任务参数 (OS_PRIO )4,//任务优先级 (CPU_STK *)&taskA_Stk[0u], //堆栈空间 (CPU_STK_SIZE )taskA_Stk[APP_CFG_TASK_START_STK_SIZE / 10u],//堆栈限深 (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE,//堆栈空间大小 (OS_MSG_QTY )0u,//消息队列 (OS_TICK )300u,//时间片大小 (void *)0u,//扩展存储器 (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务标志位清零 (OS_ERR *)&err);//出错判断结构体 //创建子任务B OSTaskCreate((OS_TCB *)&taskB_TCB, //任务结构体 (CPU_CHAR *)"taskB", //任务名称 (OS_TASK_PTR )taskB, //任务需要执行的函数 (void *)0u, //任务参数 (OS_PRIO )4,//任务优先级 (CPU_STK *)&taskB_Stk[0u], //堆栈空间--单位 字 (CPU_STK_SIZE )taskB_Stk[128 / 10u],//堆栈限深 (CPU_STK_SIZE )128,//堆栈空间大小--单位 字 (OS_MSG_QTY )0u, //消息队列 (OS_TICK )600u, //时间片大小 (void *)0u, //扩展存储器 (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务标志位清零 (OS_ERR *)&err);//出错判断结构体 //销毁当前任务 OSTaskDel(&AppTaskStartTCB,&err); } //子任务A的执行 void taskA(void *p_arg) { OS_ERR err; CPU_SR_ALLOC();//临界区初始化 while(1) { CPU_CRITICAL_ENTER(); //开启临界区 printf("taskA running\n"); CPU_CRITICAL_EXIT(); //关闭临界区 OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_DLY,&err); } } //子任务B的执行 void taskB(void *p_arg) { OS_ERR err; int ret; CPU_SR_ALLOC();//临界区初始化 while(1) { CPU_CRITICAL_ENTER(); //开启临界区 ret = dht11_begin(); if( ret == 0) //启动成功 { dht11_get_data(); } else { printf("taskB dht11 failed ret=%d\n",ret); } CPU_CRITICAL_EXIT(); //关闭临界区 OSTimeDlyHMSM(0,0,2,0,OS_OPT_TIME_DLY,&err); } }
五、时间延时
在裸机系统中,滴答定时器完成毫秒和微秒级别的延时。而在RTOS实时操作系统中,滴答定时器已经被作为时间片的单位使用,所以微秒和毫秒级别的延时,需要使用其他的外设来实现。一共有3种方法。
5.1 使用TIM定时器
初始化某一个定时器为1us触发一次中断并且关闭中断。当需要延时的时候,临时打开该定时器,触发中断则延时结束。
这种方法一般用于高性能的单片机,因为跳转到中断服务函数也是需要时间的,只有主频足够高的情况下,这种延时才有效。
#include "stm32f4xx.h" volatile uint32_t delay_counter = 0; //1us触发中断,APB1总线频率为84KHZ void TIM6_init() { TIM_TimeBaseInitTypeDef a;//定时器结构体 NVIC_InitTypeDef b;//中断结构体 //1.使能TIM6的时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6 , ENABLE); //2.时基单元初始化 a.TIM_CounterMode = TIM_CounterMode_Up;//递增模式 a.TIM_Prescaler = 84-1;//预分频数值 a.TIM_Period = 1-1;//重载寄存器数值 a.TIM_ClockDivision = 0; a.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM6,&a); //3.配置定时器中断 b.NVIC_IRQChannel = TIM6_DAC_IRQn;//通道54--->TIM6_DAC_IRQHandler b.NVIC_IRQChannelPreemptionPriority = 0; b.NVIC_IRQChannelSubPriority = 0; b.NVIC_IRQChannelCmd =ENABLE;//使能 NVIC_Init(&b); // 开启定时器中断 TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); } void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) { // 清除TIM6更新中断标志 TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 递减延时计数器 if (delay_counter > 0) { delay_counter--; } } void delay_us(uint32_t microseconds) { // 设置延时结束值 delay_counter = microseconds; // 启用TIM6中断 TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // 启动TIM6 TIM_Cmd(TIM6, ENABLE); // 等待延时结束 while (delay_counter > 0); TIM_Cmd(TIM6, DISABLE); }
5.2 使用时间戳定时器
时间戳寄存器一般使用来计算代码运行时间的。开始时清空时间戳,运行完毕后获取时间戳数值,该数值就是代码运行的时间
5.3 使用滴答定时器
仍然使用滴答定时器,但是使用时,必须关闭任务调度,然后去获取滴答定时器的计数值来延时。
//滴答定时器延时 //此时滴答定时器已经被开启,不能够去关闭它 //如果关闭了它,那么时间片轮转会出问题,所以这里只能禁止调度,不能关闭所有中断 void delay_ms(int xms) { //计算延时时间内需要发生的脉冲次数---滴答定时器中断是自由运行时钟,等于SYSCLK //定义成int,因为表示负数时要退出循环 int time_cnt = * xms; OS_ERR err; uint32_t now,old; //关闭任务调度,防止被中途打断 OSSchedLock(&err); //获取当前计数值 old = SysTick->VAL; while(time_cnt > 0) { //获取当前计数值 now = SysTick->VAL; //计数器没有重载,新数值小于旧数值 if( now < old) { time_cnt -= old-now; } //发生了重载,新数值大于旧数值 else if( now >= old) { time_cnt -= old+(SysTick->LOAD - now); } old = now; } //解锁任务调度 OSSchedUnlock(&err); }
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/111465.html



