目录
一、为什么要使用程序架构
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、提*品开发中常见的算法:队列
后续未完。