嵌入式单片机三种应用程序架构Word格式.docx
- 文档编号:20043676
- 上传时间:2023-01-16
- 格式:DOCX
- 页数:20
- 大小:27.25KB
嵌入式单片机三种应用程序架构Word格式.docx
《嵌入式单片机三种应用程序架构Word格式.docx》由会员分享,可在线阅读,更多相关《嵌入式单片机三种应用程序架构Word格式.docx(20页珍藏版)》请在冰豆网上搜索。
None
*ReturnValue
**************************************************************************************/
intmain(void)
{
uint8keyValue;
InitSys();
//初始化
while
(1)
{
TaskDisplayClock();
keyValue=TaskKeySan();
switch(keyValue)
{
casex:
TaskDispStatus();
break;
...
default:
}
}
二、时间片轮询法时间片轮询法,在很多书籍中有提到,而且有很多时候都是与操作系统一起出现,也就是说很多时候是操作系统中使用了这一方法。
不过我们这里要说的这个时间片轮询法并不是挂在操作系统下,而是在前后台程序中使用此法。
也是本贴要详细说明和介绍的方法。
对于时间片轮询法,虽然有不少书籍都有介绍,但大多说得并不系统,只是提提概念而已。
下面本人将详细介绍这种模式,并参考别人的代码建立的一个时间片轮询架构程序的方法,我想将给初学者有一定的借鉴性。
在这里我们先介绍一下定时器的复用功能。
使用1个定时器,可以是任意的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工作:
1.
初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。
2.
定义一个数值:
代码
#defineTASK_NUM
(3)
//
这里定义的任务数为3,表示有三个任务会使用此定时器定时。
uint16TaskCount[TASK_NUM]
;
//
这里为三个任务定义三个变量来存放定时值
uint8
TaskMark[TASK_NUM];
同样对应三个标志位,为0表示时间没到,为1表示定时时间到。
3.
在定时器中断服务函数中添加:
*FunctionName:
TimerInterrupt()
*Description:
定时中断服务函数
*ReturnValue:
voidTimerInterrupt(void)
uint8i;
for(i=0;
i<
TASKS_NUM;
i++)
if(TaskCount[i])
TaskCount[i]--;
if(TaskCount[i]==0)
TaskMark[i]=0x01;
代码解释:
定时中断服务函数,在中断中逐个判断,如果定时值为0了,表示没有使用此定时器或此定时器已经完成定时,不着处理。
否则定时器减一,知道为零时,相应标志位值1,表示此任务的定时值到了。
4.
在我们的应用程序中,在需要的应用定时的地方添加如下代码,下面就以任务1为例:
TaskCount[0]=20;
//延时20ms
TaskMark[0]
=0x00;
//启动此任务的定时器
到此我们只需要在任务中判断TaskMark[0]
是否为0x01即可。
其他任务添加相同,至此一个定时器的复用问题就实现了。
用需要的朋友可以试试,效果不错。
通过上面对1个定时器的复用我们可以看出,在等待一个定时的到来的同时我们可以循环判断标志位,同时也可以去执行其他函数。
循环判断标志位:
那么我们可以想想,如果循环判断标志位,是不是就和上面介绍的顺序执行程序是一样的呢?
一个大循环,只是这个延时比普通的for循环精确一些,可以实现精确延时。
执行其他函数:
那么如果我们在一个函数延时的时候去执行其他函数,充分利用CPU时间,是不是和操作系统有些类似了呢?
但是操作系统的任务管理和切换是非常复杂的。
下面我们就将利用此方法架构一直新的应用程序。
时间片轮询法的架构:
1.设计一个结构体:
代码//任务结构
typedefstruct_TASK_COMPONENTS
uint8Run;
//程序运行标记:
0-不运行,1运行
uint8Timer;
//计时器
uint8ItvTime;
//任务运行间隔时间
void(*TaskHook)(void);
//要运行的任务函数
}TASK_COMPONENTS;
//任务定义
这个结构体的设计非常重要,一个用4个参数,注释说的非常详细,这里不在描述。
2.
任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服务函数中调用此函数,这里独立出来,并于移植和理解。
TaskRemarks()
任务标志处理
voidTaskRemarks(void)
TASKS_MAX;
//逐个任务时间处理
if(TaskComps[i].Timer)
//时间不为0
TaskComps[i].Timer--;
//减去一个节拍
if(TaskComps[i].Timer==0)
//时间减完了
TaskComps[i].Timer=TaskComps[i].ItvTime;
//恢复计时器值,从新下一次
TaskComps[i].Run=1;
//任务可以运行
大家认真对比一下次函数,和上面定时复用的函数是不是一样的呢?
任务处理:
TaskProcess()
任务处理
voidTaskProcess(void)
if(TaskComps[i].Run)
TaskComps[i].TaskHook();
//运行任务
TaskComps[i].Run=0;
//标志清0
}
此函数就是判断什么时候该执行那一个任务了,实现任务的管理操作,应用者只需要在main()函数中调用此函数就可以了,并不需要去分别调用和处理任务函数。
到此,一个时间片轮询应用程序的架构就建好了,大家看看是不是非常简单呢?
此架构只需要两个函数,一个结构体,为了应用方面下面将再建立一个枚举型变量。
下面就说说怎样应用吧,假设我们有三个任务:
时钟显示,按键扫描,和工作状态显示。
1.
定义一个上面定义的那种结构体变量:
*Variabledefinition
staticTASK_COMPONENTSTaskComps[]=
{0,60,60,TaskDisplayClock},
//显示时钟
{0,20,20,TaskKeySan},
//按键扫描
{0,30,30,TaskDispStatus},
//显示工作状态
//这里添加你的任务。
。
};
在定义变量时,我们已经初始化了值,这些值的初始化,非常重要,跟具体的执行时间优先级等都有关系,这个需要自己掌握。
①大概意思是,我们有三个任务,没1s执行以下时钟显示,因为我们的时钟最小单位是1s,所以在秒变化后才显示一次就够了。
②由于按键在按下时会参数抖动,而我们知道一般按键的抖动大概是20ms,那么我们在顺序执行的函数中一般是延伸20ms,而这里我们每20ms扫描一次,是非常不错的出来,即达到了消抖的目的,也不会漏掉按键输入。
③为了能够显示按键后的其他提示和工作界面,我们这里设计每30ms显示一次,如果你觉得反应慢了,你可以让这些值小一点。
后面的名称是对应的函数名,你必须在应用程序中编写这函数名称和这三个一样的任务。
任务列表:
代码//任务清单
typedefenum_TASK_LIST
TAST_DISP_CLOCK,
TAST_KEY_SAN,
TASK_DISP_WS,
//工作状态显示
TASKS_MAX
//总的可供分配的定时任务数目
}TASK_LIST;
好好看看,我们这里定义这个任务清单的目的其实就是参数TASKS_MAX的值,其他值是没有具体的意义的,只是为了清晰的表面任务的关系而已。
编写任务函数:
TaskDisplayClock()
显示任务*EntryParameter:
voidTaskDisplayClock(void)
}/**************************************************************************************
TaskKeySan()
扫描任务
voidTaskKeySan(void)
TaskDispStatus()
工作状态显示
voidTaskDispStatus(void)
}//这里添加其他任务。
现在你就可以根据自己的需要编写任务了。
主函数:
TaskProcess();
//任务处理
到此我们的时间片轮询这个应用程序的架构就完成了,你只需要在我们提示的地方添加你自己的任务函数就可以了。
是不是很简单啊,有没有点操作系统的感觉在里面?
不防试试把,看看任务之间是不是相互并不干扰?
并行运行呢?
当然重要的是,还需要,注意任务之间进行数据传递时,需要采用全局变量,除此之外还需要注意划分任务以及任务的执行时间,在编写任务时,尽量让任务尽快执行完成。
三、操作系统操作系统的本身是一个比较复杂的东西,任务的管理,执行本事并不需要我们去了解。
但是光是移植都是一件非常困难的是,虽然有人说过“你如果使用过系统,将不会在去使用前后台程序”。
但是真正能使用操作系统的人并不多,不仅是因为系统的使用本身很复杂,而且还需要购买许可证。
这里本人并不想过多的介绍操作系统本身,因为不是一两句话能过说明白的,下面列出UCOS下编写应该程序的模型。
大家可以对比一下,这三种方式下的各自的优缺点。
OSInit();
//初始化uCOS-II
OSTaskCreate((void(*)(void*))TaskStart,
//任务指针
(void
*)0,
//参数
(OS_STK*)&
TaskStartStk[TASK_START_STK_SIZE-1],//堆栈指针
(INT8U
)TASK_START_PRIO);
//任务优先级
OSStart();
//启动多任务环境
return(0);
TaskStart()
任务创建,只创建任务,不完成其他工作
voidTaskStart(void*p_arg)
OS_CPU_SysTickInit();
//InitializetheSysTick.
#if(OS_TASK_STAT_EN>
0)
OSStatInit();
//这东西可以测量CPU使用量
#endif
OSTaskCreate((void(*)(void*))TaskLed,
//任务1
//不带参数
TaskLedStk[TASK_LED_STK_SIZE-1],
//堆栈指针
)TASK_LED_PRIO);
//优先级
//Herethetaskofcreatingyour
OSTimeDlyHMSM(0,0,0,100);
不难看出,时间片轮询法优势还是比较大的,即由顺序执行法的优点,也有操作系统的优点。
结构清晰,简单,非常容易理解。
延伸阅读:
初学单片机时,往往都会纠结于其各个模块功能的应用,如串口(232,485)对各种功能IC的控制,电机控制PWM,中断应用,定时器应用,人机界面应用,CAN总线等.这是一个学习过程中必需的阶段,是基本功。
很庆幸,在参加电子设计大赛赛前培训时,MCU周围的控制都训练的很扎实。
经过这个阶段后,后来接触不同的MCU就会发现,都大同小异,各有各的优势而已,学任何一种新的MCU都很容易入手包括一些复杂的处理器。
而且对MCU的编程控制会提升一个高度概况——就是对各种外围进行控制(如果是对复杂算法的运算就会用DSP了),而外围与MCU的通信方式一般也就几种时序:
IIC,SPI,intel8080
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式 单片机 应用程序 架构