目录
一、为什么要使用程序架构
二、移植实战
三、系统执行流程讲解
四、队列算法讲解
1、提高程序的稳定性,避免代码冲突。
2、解决开发产品中的常见痛点。
比如说按键短按、短按释放、长按、长安释放等功能。 比如说LED要实现多种闪烁效果,2秒闪一次,隔5秒闪一次等等。比如说大量串口数 据收发,如何实现不丢失一个字节。
总之,写复杂的产品,一定要有好的程序架构做支撑!就像用ucos,rtos系统,其实就是解决程序架构问题。
1、移植流程
第一步:创建OS_System.c和OS_System.h文件放到工程目录并添加到Keil工程里
第二步:把void OS_ClockInterruptHandle(void)放在stem32的systick 10ms中断函数里面,为系统提供一个时钟节拍基准。
/****************************** OS_System.c ******************************************* * @函数名 OS_ClcokInterruptHandle * @描述 系统任务调度函数 * @参数 无 * @返回值 无 * @注意 为了保证任务实时性,这个必须放到10ms的定时器或系统时钟或时钟中断函数里面 *************************************************************************/ volatile OS_TaskTypeDef OS_Task[OS_Task_SUM]; % 定义一个任务数组 void OS_ClockInterruptHandle(void) { unsigned char i; for(i=0;i<OS_Task_SUM;i++) // 对所有的任务进行遍历 { if(OS_Task[i].task) // 通过task函数指针指向不等于0来判断任务是否被创建 { OS_Task[i].Runtime++; if(OS_Task[i].Runtime >= OS_Task[i].RunPeriod) // 判断是否达到了任务需要执行的时间 { OS_Task[i].Runtime = 0; OS_Task[i].RunFlag = OS_RUN; // 把任务的状态设置为执行,任务调度函数会一直判断 //这个变量的值,如果OS_RUN就会执行task指向的函数 } } } } /********************************* OS_System.h******************************************/ // 系统任务ID typedef enum{ OS_Task1, OS_Task_SUM }OS_TaskIDTypeDef; // 系统任务运行状态 typedef enum { OS_SLEEP, OS_RUN=!OS_SLEEP }OS_TaskStatusTypeDef; // 系统任务结构体 typedef struct { void (*task)(void); // 任务函数指针 OS_TaskStatusTypeDef RunFlag; unsigned short RunPeriod; // 任务周期 unsigned short Runtime; }OS_TaskTypeDef; /********************************* cpu.c ******************************************/ static void hal_CoreClockInit(void) { SysTick_Config(SystemCoreClock / 100); //使用48M作为系统时钟,那么计数器减1等于 1/48M(ms), (1/48000000hz)*(48000000/100)=0.01S=10ms } void SysTick_Handler(void) { OS_ClockInterruptHandle(); // 每10ms中断一次进行系统任务调度 }
第三步:调用OS_CPUInterruptCBSRegister(CPUInterrupt_CallBack_t pCPUInterruptCtrlCBS)函数,传入开关总中断函数。
/******************************* OS_System.c ****************************************/ // 通过宏 CPUInterruptCtrlCBS 来声明一个函数指针 CPUInterrupt_CallBack_t CPUInterruptCtrlCBS; /************************************************************************* * @函数名 OS_CPUInterruptCBSRegister * @描述 注册CPU中断控制函数 * @参数 pCPUInterruptCtrlCBS 中断回调函数地址 * @返回值 无 * @注意 无 *************************************************************************/ void OS_CPUInterruptCBSRegister(CPUInterrupt_CallBack_t pCPUInterruptCtrlCBS) { if(CPUInterruptCtrlCBS == 0) { CPUInterruptCtrlCBS = pCPUInterruptCtrlCBS; } } /******************************* OS_System.h ****************************************/ typedef enum { CPU_ENTER_CRITICAL, // CPU进入临界 CPU_EXIT_CRITICAL, // CPU退出临界 }CPU_EA_TYPEDEF; // 宏定义一个CPU中断控制回调函数指针类型 typedef void (*CPUInterrupt_CallBack_t)(CPU_EA_TYPEDEF cmd,unsigned char *pSta); /******************************* cpu.c ****************************************/ // CPU初始化 void hal_CPUInit(void) { hal_CoreClockInit(); OS_CPUInterruptCBSRegister(hal_CPU_Critical_Control); } /******************************************************************************************************** /******************************************************************************************************** * @函数名 hal_getprimask * @描述 获取CPU总中断状态 * @参数 无 * @返回值 0-总中断关闭 1-总中断打开 * @注意 无 ********************************************************************************************************/ static unsigned char hal_getprimask(void) { return (!__get_PRIMASK()); //0是中断打开,1是中断关闭,所以要取反 } * @函数名 hal_CPU_Critical_Control * @描述 CPU临界处理控制 * @参数 cmd-控制命令 *pSta-总中断状态 * @返回值 无 * @注意 无 ********************************************************************************************************/ static void hal_CPU_Critical_Control(CPU_EA_TYPEDEF cmd,unsigned char *pSta) { if(cmd == CPU_ENTER_CRITICAL) { *pSta = hal_getprimask(); // 保存中断状态 __disable_irq(); //关CPU总中断 } else if(cmd == CPU_EXIT_CRITICAL) { if(*pSta) { __enable_irq(); //打开中断 } else { __disable_irq(); //关闭中断 } } }
注意要编写开关总中断函数。
开关中断在操作系统中一般称为临界,作用就是放置操作数据的时候,数据被意外改变导致不可预知的BUG。单片机在跑的时候,可能会被中断打断,然后先去执行中断里的程序。在执行中断程序的时候又可能被优先级更高的中断打断,这个时候又去执行优先级更高的中断程序了。如果这三个地方对同一个指针或者变量进行操作,就有可能出现不可预知的问题。而程序大了以后,这种情况又是难免的。
第四步:创建任务,测试移植是否成功
/*********************************** main.c *************************/ int main(void) { hal_CPUInit(); hal_TimeInit(); OS_TaskInit(); hal_LedInit(); OS_CreatTask(OS_Task1,hal_LedProc,1,OS_RUN); //创建一个任务,让LED 1秒钟亮灭闪烁 OS_Start(); } /***********************************************************************/ /*********************************** hal_led.c *************************/ void hal_LedInit(void) { hal_ledConfig(); } void hal_LedProc(void) { static unsigned short i = 0; i++; if(i > 100) { i=0; hal_LedTurn(); } } void hal_LedTurn(void) { GPIO_WriteBit(GPIOA,GPIO_Pin_1,(BitAction)(1-GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1))); }
1、系统任务结构体OS_TaskTypeDef
// 系统任务ID typedef enum { OS_Task1, // 任务1,需要多个任务的时候依次添加即可 OS_Task_SUM }OS_TaskIDTypeDef; // 系统任务运行状态 typedef enum { OS_SLEEP, // 睡眠状态 OS_RUN=!OS_SLEEP // 执行状态 }OS_TaskStatusTypeDef; // 系统任务结构体 typedef struct { void (*task)(void); // 任务函数指针 OS_TaskStatusTypeDef RunFlag; // 任务函数运行标志 unsigned short RunPeriod; // 任务函数周期 unsigned short Runtime; // 任务函数计时器 }OS_TaskTypeDef;
用结构体把所有任务函数的共同特征参数进行定义,依次是一个任务函数指针,运行标志,运行的周期,运行计时器。用枚举将任务ID和运行状态进行定义。
2、任务初始化函数OS_TaskInit
/******************************************************************************************************** * @函数名 OS_TaskInit * @描述 系统任务初始化 * @参数 无 * @返回值 无 * @注意 无 ********************************************************************************************************/ void OS_TaskInit(void) { unsigned char i; for(i=0; i<OS_Task_SUM; i++) { OS_Task[i].task = 0; OS_Task[i].RunFlag = OS_SLEEP; OS_Task[i].RunPeriod = 0; OS_Task[i].Runtime = 0; } }
初始化任务函数,遍历所有任务函数,将函数指针指向的地址设为0,即没有指向任何一个任务函数,系统进行休眠,运行周期0,此时不计时。
3、任务创建函数OS_CreatTask
/************************************************************************* * @函数名 OS_CreatTask(unsigned cahr ID,void (*proc)(void),unsigned short Period,OS_TaskStatusTypeDef flag) * @描述 创建任务函数 * @参数 - ID:任务ID * - (*proc)() 用户函数入口地址 * - TimeDly 任务执行频率,单位ms * - flag 任务就绪状态 OS_SLEEP-休眠 OS_RUN-运行 * @返回值 无 * @注意 无 *************************************************************************/ void OS_CreatTask(unsigned char ID,void (*proc)(void),unsigned short Period,OS_TaskStatusTypeDef flag) { if(!OS_Task[ID].task) // 如果没有指向一个任务函数 { OS_Task[ID].task = proc; // 用一个void型的函数指针指向任务函数 OS_Task[ID].RunFlag = OS_SLEEP; OS_Task[ID].RunPeriod = Period; OS_Task[ID].Runtime = 0; } }
先判断有没有其他任务函数是否在执行,如果没有,按照任务枚举列表依次执行。
4、任务调度函数OS_ClockInterruptHandle
/************************************************************************* * @函数名 OS_ClcokInterruptHandle * @描述 系统任务调度函数 * @参数 无 * @返回值 无 * @注意 为了保证任务实时性,这个必须放到10ms的定时器或系统时钟或时钟中断函数里面 *************************************************************************/ void OS_ClockInterruptHandle(void) { unsigned char i; for(i=0;i<OS_Task_SUM;i++) // 对所有的任务进行遍历 { if(OS_Task[i].task) // 通过task函数指针指向不等于0来判断任务是否被创建 { OS_Task[i].Runtime++; if(OS_Task[i].Runtime >= OS_Task[i].RunPeriod) // 判断是否达到了任务需要执行的时间 { OS_Task[i].Runtime = 0; OS_Task[i].RunFlag = OS_RUN; // 把任务的状态设置为执行,任务调度函数会一直判断这个变量的值,如果OS_RUN就会执行task指向的函数 } } } }
5、开始任务函数OS_Start
/************************************************************************* * @函数名 OS_Start * @描述 开始任务 * @参数 无 * @返回值 无 * @注意 无 *************************************************************************/ void OS_Start(void) { unsigned char i; while(1) { for(i=0;i<OS_Task_SUM;i++) // 循环执行任务 { if(OS_Task[i].RunFlag == OS_RUN) { OS_Task[i].RunFlag = OS_SLEEP; (*(OS_Task[i].task))(); // 执行task指向的函数 } } } }
这个程序架构最大的作用:
1、能为每个任务分配单独的执行频率,一般都是10ms,最小也是10ms,当然可根据实际需要进行修改,这样一些不需要经常执行的任务,就可以把调度时间调长一点,释放CPU的资源。这里的任务指的就是函数,比如说LED执行函数,按键处理函数等等
2、提供产品开发中常见的算法:队列
后续未完。