嵌入式单片机三种应用程序架构_第1页
嵌入式单片机三种应用程序架构_第2页
嵌入式单片机三种应用程序架构_第3页
嵌入式单片机三种应用程序架构_第4页
嵌入式单片机三种应用程序架构_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、在工作中经过摸索实验,总结出单片机大致应用程序的架构有三种:简单的前后台顺序执行程序,这类写法是大多数人使用的方法,不需用 思考程序的具体架构,直接通过执行顺序编写应用程序即可。时间片轮询法,此方法是介于顺序执行与操作系统之间的一种方法。操作系统,此法应该是应用程序编写的最高境界。下面就分别谈谈这三种方法的利弊和适应范围等。一、顺序执行法这种方 法,这应用程序比较简单,实时性,并行性要求不太高的情况下是不错的方法, 程序设计简单,思路比较清晰。但是当应用程序比较复杂的时候,如果没有一 个完整的流程图,恐怕别人很难看懂程序的运行状态,而且随着程序功能的增 加,编写应用程序的工程师的大脑也开始混乱

2、。即不利于升级维护,也不利于 代码优化。本人写个几个比较复杂一点的应用程序,刚开始就是使用此法,最 终虽然能够实现功能,但是自己的思维一直处于混乱状态。导致程序一直不能 让自己满意。这种方法大多数人都会采用,而且我们接受的教育也基本都是使用此法。对 于我们这些基本没有学习过数据结构,程序架构的单片机工程师来说,无疑很 难在应用程序的设计上有一个很大的提高,也导致了不同工程师编写的应用程 序很难相互利于和学习。本人建议,如果喜欢使用此法的网友,如果编写比较复杂的应用程序,一定 要先理清头脑,设计好完整的流程图再编写程序,否则后果很严重。当然应该 程序本身很简单,此法还是一个非常必须的选择。下面就

3、写一个顺序执行的程序模型,方便和下面两种方法对比: 代码/*“ 小小小小小小小小小小小小小小小小小小FunctionName : main()Description :主函数EntryParameter : NoneReturnValue : None“ 小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小“/*/int main(void)/初始化 whileuint8 keyValue; InitSys();TaskDisplayClock();keyValue = TaskKeySan();switch

4、 (keyValue)case x: TaskDispStatus(); break;default: break;二、时间片轮询法时间片轮询法,在很多书籍中有提到,而且有很多时候 都是与操作系统一起出现,也就是说很多时候是操作系统中使用了这一方法。 不过我们这里要说的这个时间片轮询法并不是挂在操作系统下,而是在前后台 程序中使用此法。也是本贴要详细说明和介绍的方法。对于时间片轮询法,虽 然有不少书籍都有介绍,但大多说得并不系统,只是提提概念而已。下面本人 将详细介绍这种模式,并参考别人的代码建立的一个时间片轮询架构程序的方 法,我想将给初学者有一定的借鉴性。在这里我们先介绍一下定时器的复用功

5、能。使用1个定时器,可以是任意 的定时器,这里不做特殊说明,下面假设有3个任务,那么我们应该做如下工 作:初始化定时器,这里假设定时器的定时中断为1ms(当然你可以改成 10ms,这个和操作系统一样,中断过于频繁效率就低,中断太长,实时性差)。定义一个数值:代码#define TASK_NUM (3)任务会使用此定时器定时。uint16 TaskCountTASK_NUM;存放定时值uint8 TaskMarkTASK_NUM;间没到,为1表示定时时间到。在定时器中断服务函数中添加:/这里定义的任务数为3,表示有三个/这里为三个任务定义三个变量来/同样对应三个标志位,为0表示时代码/*“ 小小

6、小小小小小小小小小小小小小小小小FunctionName : TimerInterrupt()Description :定时中断服务函数EntryParameter : NoneReturnValue : None“ 小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小“/*/ void TimerInterrupt(void)uint8 i;for (i=0; iTASKS_NUM; i+)if (TaskCounti)TaskCounti-;if (TaskCounti = 0)TaskMarki = 0

7、 x01;代码解释:定时中断服务函数,在中断中逐个判断,如果定时值为0 了, 表示没有使用此定时器或此定时器已经完成定时,不着处理。否则定时器减一, 知道为零时,相应标志位值1,表示此任务的定时值到了。在我们的应用程序中,在需要的应用定时的地方添加如下代码,下面就 以任务1为例: 代码TaskCount0 = 20;/ 延时 20msTaskMark0 = 0 x00; /启动此任务的定时器到此我们只需要在任务中判断TaskMark0是否为0 x01即可。其他任务添 加相同,至此一个定时器的复用问题就实现了。用需要的朋友可以试试,效果 不错。通过上面对1个定时器的复用我们可以看出,在等待一个定

8、时的到来的同 时我们可以循环判断标志位,同时也可以去执行其他函数。循环判断标志位:那么我们可以想想,如果循环判断标志位,是不是就和 上面介绍的顺序执行程序是一样的呢? 一个大循环,只是这个延时比普通的for 循环精确一些,可以实现精确延时。执行其他函数:那么如果我们在一个函数延时的时候去执行其他函数,充 分利用CPU时间,是不是和操作系统有些类似了呢?但是操作系统的任务管理 和切换是非常复杂的。下面我们就将利用此方法架构一直新的应用程序。时间 片轮询法的架构:设计一个结构体:代码任务结构typedef struct _TASK_COMPONENTSuint8 Run;/程序运行标记:0-不运行

9、,1运行uint8 Timer; / 计时器uint8 ItvTime; 任务运行间隔时间void (*TaskHook)(void); /要运行的任务函数 TASK_COMPONENTS; / 任务定义这个结构体的设计非常重要,一个用4个参数,注释说的非常详细,这里 不在描述。任务运行标志出来,此函数就相当于中断服务函数,需要在定时器的中断服 务函数中调用此函数,这里独立出来,并于移植和理解。代码/*“ 小小小小小小小小小小小小小小小小小小FunctionName : TaskRemarks()Description :任务标志处理EntryParameter : None* ReturnV

10、alue : None“ 小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小“/*/ void TaskRemarks(void)uint8 i; for (i=0; iTASKS_MAX; i+)/ 逐个任务时间处理if (TaskCompsi.Timer)时间不为 0TaskCompsi.Timer-;/ 减去一个节拍if (TaskCompsi.Timer = 0)/ 时间减完了TaskCompsi.Timer = TaskCompsi.ItvTime;恢复计时器值,从新下一次一TaskCompsi.

11、Run = 1;/ 任务可以运行大家认真对比一下次函数,和上面定时复用的函数是不是一样的呢?任务处理:代码/*“ 小小小小小小小小小小小小小小小小小小FunctionName : TaskProcess()Description :任务处理EntryParameter : NoneReturnValue : None“ 小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小小“/*/ void TaskProcess(void)uint8 i; for (i=0; i 0)OSStatInit();#endifO

12、STaskCreate(void (*) (void *) TaskLed, / 任务 1(void *) 0,/不带参数(OS_STK *) &TaskLedStkTASK_LED_STK_SIZE - 1, / 堆栈指针(INT8U ) TASK_LED_PRIO); / 优先级/ Here the task of creating yourwhile (1)OSTimeDlyHMSM(0, 0, 0, 100);不难看出,时间片轮询法优势还是比较大的,即由顺序执行法的优点,也 有操作系统的优点。结构清晰,简单,非常容易理解。延伸阅读:初学单片机时,往往都会纠结于其各个模块功能的应用,如串

13、口(232, 485)对各种功能IC的控制,电机控制PWM,中断应用,定时器应用,人机界 面应用,CAN总线等.这是一个学习过程中必需的阶段,是基本功。很庆幸, 在参加电子设计大赛赛前培训时,MCU周围的控制都训练的很扎实。经过这个 阶段后,后来接触不同的MCU就会发现,都大同小异,各有各的优势而已, 学任何一种新的MCU都很容易入手包括一些复杂的处理器。而且对MCU的编 程控制会提升一个高度概况一一就是对各种外围进行控制(如果是对复杂算法的 运算就会用DSP 了),而外围与MCU的通信方式一般也就几种时序: IIC,SPI,intel8080,M6800。这样看来MCU周围的编程就是一个很简

14、单的东西了。然而这只是嵌入式开发中的一点皮毛而已,在接触过多种MCU,接触过复 杂设计要求,跑过操作系统等等后,我们在回到单片机的裸机开发时,就不知 不觉的就会考虑到整个程序设计的架构问题;一个好的程序架构,是一个有经 验的工程师和一个初学者的分水岭。以下是我对单片机程序框架以及开发中一些常用部分的认识总结:任何对时间要求苛刻的需求都是我们的敌人,在必要的时候我们只有增加 硬件成本来消灭它;比如你要8个数码管来显示,我们在没有相关的硬件支持 的时候必须用MCU以动态扫描的方式来使其工作良好;而动态扫描将或多或 少的阻止了 MCU处理其他的事情。在MCU负担很重的场合,我会选择选用一 个类似ma

15、x8279外围ic来解决这个困扰;然而庆幸的是,有着许多不是对时间要求苛刻的事情:例如键盘的扫描,人们敲击键盘的速率是有限的,我们无需实时扫描着键 盘,甚至可以每隔几十ms才去扫描一下;然而这个几十ms的间隔,我们的 MCU还可以完成许多的事情;单片机虽然是裸机奔跑,但是往往现实的需要决定了我们必须跑出操作系 统的姿态多任务程序;比如一个常用的情况有4个任务:1、键盘扫描;2、led数码管显示;3、串口数据需要接受和处理;4、串口需要发送数据;如何来构架这个单片机的程序将是我们的重点;读书时代的我会把键盘扫描用查询的方式放在主循环中,而串口接收数据 用中断,在中断服务函数中组成相应的帧格式后置

16、位相应的标志位,在主函数 的循环中进行数据的处理,串口发送数据以及led的显示也放在主循环中; 这样整个程序就以标志变量的通信方式,相互配合的在主循环和后台中断中执 行;然而必须指出其不妥之处:每个任务的时间片可能过长,这将导致程序的 实时性能差。如果以这样的方式在多加几个任务,使得一个循环的时间过长, 可能键盘扫描将很不灵敏。所以若要建立一个良好的通用编程模型,我们必须 想办法,消去每个任务中费时间的部分以及把每个任务再次分解;下面来细谈 每个任务的具体措施:1、键盘扫描键盘扫描是单片机的常用函数,以下指出常用的键盘扫描程序中,严重阻 碍系统实时性能的地方;众所周知,一个键按下之后的波形是这

17、样的(假定低 有效):在有键按下后,数据线上的信号出现一段时间的抖动,然后为低,然 后当按键释放时,信号抖动一段时间后变高。当然,在数据线为低或者为高的 过程中,都有可能出现一些很窄的干扰信号。unsigned char kbscan(void)unsigned char sccode,recode;P2=0 xf8;if (P2&0 xf8)!=0 xf8)delay(100); /延时20ms去抖这里太费时了,很糟糕if(P2&0 xf8)!=0 xf8)sccode=0 xfe;while(sccode&0 x08)!=0)P2=sccode;if (P2&0 xf8)!=0 xf8)b

18、reak;sccode=(sccode 10)Break;即在一定得时间内,如果键盘一直按下,将作为有效键处理。这样虽然不 导致整个系统其它任务不能运行,但也很大程度上,削弱了系统的实时性能, 因为他用了延时函数;我们用两种有效的方法来解决此问题:1、在按键功能比较简单的情况下,我们仍然用上面的kbscan()函数进行扫描, 只是把其中去抖用的软件延时去了,把去抖以及判断按键的释放用一个函数来 处理,它不用软件延时,而是用定时器的计时(用一般的计时也行)来完成; 代码如下void ClearKeyFlag(void)KeyDebounceFlg = 0;KeyReleaseFlg = 0;vo

19、id ScanKey(void)+KeyDebounceCnt;/去抖计时(这个计时也可以放在后台定时器计时函数 中处理)KeyCode = kbscan();if (KeyCode != KEY_NONE)if (KeyDebounceFlg)/进入去抖状态的标志位if (KeyDebounceCnt DEBOUNCE_TIME)/ 大于了去抖规定的时间 if (KeyCode = KeyOldCode)/按键依然存在,则返回键值KeyDebounceFlg = 0;KeyReleaseFlg = 1;/释放标志return; /Here exit with keycodeClearKeyF

20、lag(); /KeyCode != KeyOldCode,只是抖动而已elseif (KeyReleaseFlg = 0)KeyOldCode = KeyCode;KeyDebounceFlg = 1;KeyDebounceCnt = 0;elseif (KeyCode != KeyOldCode)ClearKeyFlag();elseClearKeyFlag();/没有按键则清零标志KeyCode = KEY_NONE;在按键情况较复杂的情况,如有长按键,组合键,连键等一些复杂功能的 按键时候,我们跟倾向于用状态机来实现键盘的扫描;/avr单片机中4*3扫描状态机实现char read_k

21、eyboard_FUN2()static char key_state = 0, key_value, key_line,key_time;char key_return = No_key,i;switch (key_state)case 0: 最初的状态,进行3*4的键盘扫描key_line = 0b00001000;for (i=1; i=4; i+) / 扫描键盘PORTD = key_line; /输出行线电平PORTD = key_line; / 必须送 2 次! !(注 1)key_value = Key_mask & PIND; / 读列电平if (key_value = Key

22、_mask)key_line =100)/如果长时间没有释放key_time=0;key_state=3;/进入连键状态key_return= (key_line | key_value);break;case 3:/对于连键,每隔50ms就得到一次键值,windows xp系统就是这样做的PORTD = 0b00000111; /行线全部输出低电平PORTD = 0b00000111; / 重复送一次if ( (Key_mask & PIND) = Key_mask)key_state=0; /列线全部为高电平返回状态0else if(+key_time=5) /每隔 50MS 为一次连击的

23、按键key_time=0;key_return= (key_line I key_value);break;return key_return;以上用了4个状态,一般的键盘扫描只用前面3个状态就可以了,后面一个状 态是为增加“连键”功能设计的。连键一一即如果按下某个键不放,则迅速的多 次响应该键值,直到其释放。在主循环中每隔10ms让该键盘扫描函数执行 次即可;我们定其时限为10ms,当然要求并不严格。2、数码管的显示一般情况下我们用的八位一体的数码管,采用动态扫描的方法来完成显示; 非常庆幸人眼在高于50hz以上的闪烁时发现不了的。所以我们在动态扫描数码 管的间隔时间是充裕的。这里我们定其时

24、限为4ms(250HZ),用定时器定时为 2ms,在定时中断程序中进行扫描的显示,每次只显示其中的一位;当然时限也 可以弄长一些,更推荐的方法是把显示函数放入主循环中,而定时中断中置位 相应的标志位即可;/ Timer 0比较匹配中断服务,4ms定时interrupt TIM0_COMP void timer0_comp_isr(void)一display(); /调用LED扫描显示 void display(void) / 8位LED数码管动态扫描函数PORTC = 0 xff; /这里把段选都关闭是很必要的,否则数码管会产生拖影PORTA = led_7dis_buffposit;PORT

25、C = positionposit;if (+posit =8 )posit = 0;3、串口接收数据帧串口接收时用中断方式的,这无可厚非。但如果你试图在中断服务程序中完成 一帧数据的接收就麻烦大了。永远记住,中断服务函数越短越好,否则影响这 个程序的实时性能。一个数据帧一般包括若干个字节,我们需要判断一帧是否 完成,校验是否正确。在这个过程中我们不能用软件延时,更不能用死循环等 待等方式;所以我们在串口接收中断函数中,只是把数据放置于一个缓冲队列中。至于组成帧,以及检查帧的工作我们在主循环中解决,并且每次循环中我们只 处理一个数据,每个字节数据的处理间隔的弹性比较大,因为我们已经缓存在 了队

26、列里面。/*=功能:串口发送接收的时间事件说明:放在大循环中每10ms 一次输出:none输入:none=*/void UARTimeEvent(void)if (TxTimer != 0)/发送需要等待的时间递减-TxTimer;if (+RxTimer RX_FRAME_RESET) /RxCnt = 0; /如果接受超时(即不完整的帧或者接收一帧完成),把接收的不完 整帧覆盖/*=功能:串口接收中断说明:接收一个数据,存入缓存输出:none输入:none=*/interrupt USART_RXC void uart_rx_isr(void)INT8U status,data;statu

27、s = UCSRA;data = UDR;if (status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN)=0) RxBufRxBufWrIdx = data;if (+RxBufWrIdx = RX_BUFFER_SIZE) 接收数据于缓冲中RxBufWrIdx = 0;if (+RxBufCnt = RX_BUFFER_SIZE)RxBufCnt = 0;/RxBufferOvf=1;/*=功能:串口接收数据帧说明:当非0输出时,收到一帧数据放在大循环中执行输出:=0 :没有数据帧!=0:数据帧命令字输入:none=*/INT8U Chk

28、RxFrame(void)INT8U dat;INT8U cnt;INT8U sum;INT8U ret;ret = RX_NULL;if (RxBufCnt != 0)RxTimer = 0; 清接收计数时间,UARTimeEvent()中对于接收超时做了放弃整帧 数据的处理/Display();cnt = RxCnt;dat = RxBufRxBufRdIdx; / Get Charif (+RxBufRdIdx = RX_BUFFER_SIZE)RxBufRdIdx = 0;Cli();-RxBufCnt;Sei();FrameBufcnt+ = dat;if (cnt = FRAME_LEN)/ 组成一帧sum = 0;for (cnt = 0;cnt 5)Time4ms = 0;TimeEvent20ms(); 在 20ms 事件中,我们处理键盘扫描 read_keyb

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论