UCOS实时操作系统

目录

一、前后台系统和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学习的都是裸机形式,通常把程序分为两部分:前台系统和后台系统。前台是中断级,后台是任务级。

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

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 = 168000 * 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);
}

  • 56
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值