UCOS实时操作系统

UCOS实时操作系统STM32 学习的都是裸机形式 通常把程序分为两部分 前台系统和后台系统

大家好,欢迎来到IT知识分享网。

目录

一、前后台系统和RTOS系统

1.1 前后台系统

1.2 RTOS系统

二、UCOS

三、UCOS移植

四、任务创建

4.1 起始任务创建

4.2 子任务创建

4.2.1 时间片轮转开启

4.2.2 临界区设置

五、时间延时

5.1 使用TIM定时器

5.3 使用滴答定时器


一、前后台系统和RTOS系统

1.1 前后台系统

STM32学习的都是裸机形式,通常把程序分为两部分:前台系统和后台系统。前台是中断级,后台是任务级。

  • 裸机:没有任何操作系统
  • 前台系统:中断
  • 后台系统:主程序中的循环

UCOS实时操作系统

1.2 RTOS系统

RTOS实时操作系统就是类似于linux,windows的一个多任务操作系统。理论上仍然只有一个程序在运行,但它会在各个任务之间进行资源调度,看上去像是在并发执行每一个任务(多线程/多进程)。

  • RTOS操作系统:UCOS,FreeRTOS,RTX,RT-Thread,DJYOS等。
  • RTOS操作系统的核心内容在于:实时内核。

二、UCOS

UCOS的任务优先级有64个,范围是0~63。都比中断低。

UCOS实时操作系统

三、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中的函数全部设置为弱声明。

UCOS实时操作系统

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,开启条件编译,得到函数

UCOS实时操作系统

#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

(0)
上一篇 2026-01-24 16:45
下一篇 2026-01-24 17:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信