STM32程序架构内核移植(一)

目录

一、为什么要使用程序架构

二、移植实战

三、系统执行流程讲解

四、队列算法讲解


一、为什么要使用程序架构

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、提*品开发中常见的算法:队列

后续未完。

上一篇:报错:Hal_StatusTypeDef is undefined,解决办法


下一篇:定时器总结(TIM1和TIM6)