程序的编写裸机有几大类,分别是
顺序执行
前后台程序
时间片任务轮询
带系统的程序
我们平常学习裸机开发程序中最常使用的可能就是顺序执行和前后台程序
程序顺序执行的示例
简单直接,直接往while(1)循环里放就是了
前后台程序则是在顺序执行的基础上加上了中断,用于处理一些外部信号和比较紧急的事件
但是这样出来的程序,实际的运行效果就比较乱了,我们无法确定某段程序能不能在我们需要的时候调用,比如让一些实时性高的调用频率高些,让一些实时性不那么高的功能调用频率低一点。程序不大的时候,那也没啥问题,但是东西稍微多点,那就比较混乱了。
所以,一些程序系统就出现了,例如linux,freeos之类的,不过它们对于单片机,尤其是像51这种来说,还是太大了,就算能放下去,也没有代码空间写自己要的功能了,所以就出现了一些建议的代码框架来管理程序的调用
比如时间片轮询法
时间片任务轮询是一种简易的任务管理办法
它本质上是利用定时器的定时功能,使得任务可以间断固定时间执行
这样能够让我们写代码时更容易整理各部分功能逻辑
程序内部执行示意图
示意图中每一个框都是都是一段固定的时间,每一个功能程序的运行间隔时间也是固定的
而中断中一般只需要实现一个计时功能,时间很短,所以可以忽略
得到新的示意图
这样便能够实现每个功能模块都能够定时执行,如果上一个功能模块已经执行完了,但下一个功能模块的定时时间还没到,便会产生空闲时间,这样便给了我们空间,让我们能够在不影响其他功能模块的同时再添加其他功能模块。虽然这样可能有点浪费资源吧,但是整体逻辑性更强了,而且有些东西也不是塞得满满当当才算好嘛
多个功能模块运行的示意图
给人的感觉其实就是,每个功能模块的执行上都独立了,增删功能上就不那么需要考虑整体了
程序用STM32解释,改成51的也是一样的,也就是定时器的初始化和中断不一样而已
基本结构像下面这段代码:
//任务定时参数
int TaskTime1=0;
int TaskTime2=0;
//任务执行标志位
char Led1Flag=0;
char Led1Flag=0;
//假设这是个1ms的定时中断
void ISR_CallBlack(){
if(!Led1Flag){ //任务还为就绪
if(++TaskTime1>=1000){ //任务1定时
TaskTime1=0; //定时时间到,定时时间复位
Led1Flag=1; //任务就绪
}
}
if(!Led2Flag){
if(++TaskTime2>=500){ //任务2定时
TaskTime2=0;
Led2Flag=1;
}
}
}
void main(){
SysInit();
while(1){
//任务在主函数中执行,不会影响定时的准确
if(Led1Flag){ //任务就绪时,执行相应的任务,并复位任务执行状态,使其开始新的定时
Led1Flag=0;
LED1();
}
if(Led2Flag){
Led2Flag=0;
LED2();
}
}
}
这样我们如果需要添加新的功能模块,就只需要按照上面的样式,在定时中断添加一个定时参数,再在主函数中添加任务主体函数就能添加一个任务,而不需要太顾及其他任务的影响。
当然,这样每添加一个任务都需要新建一个变量,然后定时器中断和主函数中都得添加对应代码,还是有点麻烦了,所以我又又又又把它重写了好几次,最后得到了这个样子。最后这个是用STM32写的,解释直接写代码里了。
.h
#ifndef __PEACHOS_H__
#define __PEACHOS_H__
#include "stm32f10x.h"
//框架运行所需的函数声明
void PeachOSRun(void);
void PeachOSInit(void);
void TIMCallBlack(void);
/*
用与存储一个任务运行的数据
TaskTickNow 用于计时
TaskTickMax 设置计时时间
TaskStatus 任务运行标志位
void (*FC)() 任务函数指针
*/
struct TaskStruct{
u16 TaskTickNow;
u16 TaskTickMax;
u8 TaskStatus;
void (*FC)();
};
extern struct TaskStruct TaskST[]; //声明为结构体数据,那样多个任务时方便管理
//用于示例的任务函数声明
void LedFlash(void);
void UARTFC(void);
void UserTestMode(void);
#endif
.c
#include "PeachOS.h"
#include "stdlib.h"
/*
任务初始化
每一个结构体都代表一个任务
添加任务和删减任务都在这里完成
*/
struct TaskStruct TaskST[]={
{0,1000,0,LedFlash},
{0,1000,0,UARTFC},
{0,10,0,UserTestMode},
};
//定时器初始化
void TIM2INIT(u16 arr,u16 psr){
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = arr;
TIM_TimeBaseInitStruct.TIM_Prescaler = psr;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
TIM_ITConfig(TIM2, TIM_IT_Update,ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
//中断初始化
void NVICITINIT(){
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_Init(&NVIC_InitStruct);
}
/*
框架初始化
主要是初始化定时器和计算一些框架运行需要的数据
*/
u8 TaskCount; //记录任务数量
void PeachOSInit(){
TIM2INIT(71,1000);
NVICITINIT();
TaskCount = sizeof(TaskST)/sizeof(TaskST[0]); //利用sizeof计算数组长度的方法可得到任务数量
}
//放中断中执行,用于任务计时
void OS_IT_RUN(){
u8 i;
for(i=0;i<TaskCount;i++){ //遍历所有循环
if(!TaskST[i].TaskStatus){ //当任务不在挂起状态,执行以下步骤
if(++TaskST[i].TaskTickNow >= TaskST[i].TaskTickMax){ //计时,并判断是否到达定时时间
//如果到了定时时间,则将任务挂起,并复位计时(也可以执行完后复位,效果一样)
TaskST[i].TaskTickNow = 0;
TaskST[i].TaskStatus = 1;
}
}
}
}
//放在main函数中执行,自带死循环,用于执行挂起的任务
void PeachOSRun(){
u8 j=0;
while(1){
if(TaskST[j].TaskStatus){ //判断一个任务是否被挂起
TaskST[j].FC(); //执行该任务函数
TaskST[j].TaskStatus=0; //取消任务的挂起状态
}
if(++j>=TaskCount)j=0; //相当于不断循环遍历所有的任务
}
}
//定时器中断
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET){
OS_IT_RUN();
TIMCallBlack();
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
main.c
void main(void){
SysInit();
PeachOSInit();
PeachOSRun();
}
。。。
/*
当然,因为这个框架是不抢占内核类型的,
不能像占用内核型的系统一样,能利用线程的性质在某个任务中写个死循环都能切换任务,
所以编写自己的任务代码时还是要注意任务中的软件延时还是要少用,
不然的话还是会把整个框架卡住,
让整个代码无法流畅运行
*/
/*
定时器中的任务遍历,判断和自加等操作计算还是很快的,一般不会影响到任务正常运行,至少我现在还没遇到过
*/
/*
emmm,暂时就这样吧,有不懂的可以留言,以后应该会配图重新解释一遍
*/