如何编程用eeprom记录单片机流水灯程序的运行状态呢,断电后再上电流水灯还能接着原来的那个亮

从单片机初学者迈向单片机工程师作者:tangwei039 来自:西安 整理:by 主题讨论周第一章----写在前面学习单片机也已经有几年了,藉此机会和大家聊一下我学习过程中的一些经历和想法吧。也感谢一线工人提供了这个机会。希望大家有什么好的想法和建议都直接跟帖说出来。毕竟只有交流才能够碰撞出火花来^_^。。“卖弄”也好,“吹嘘”也罢,我只是想认真的写写我这一路走来历经的总总,把其中值得注意,以及经验的地方写出来,权当是我对自己的一个总结吧。而作为看官的你,如果看到了我的错误,还请一定指正,这样对我以及其它读者都有帮助,而至于你如果从中能够收获到些许,那便是我最大的欣慰了。姑妄言之,姑妄听之。如果有啥好的想法和建议一定要说出来。?几年前,和众多初学者一样,我接触到了单片机,立刻被其神奇的功能所吸引,从此不能自拔。很多个日夜就这样陪伴着它度过了。期间也遇到过非常多的问题,也一度被这些问题所困惑……等到回过头来,看到自己曾经走过的路,唏嘘不已。经常混迹于论坛里,也看到了很多初学者发的求助帖子,看到他们走在自己曾走过的弯路上,忽然想到了自己的那段日子,心里竟然莫名的冲动,凡此总总,我总是尽自己所能去回帖。很多时候,都想写一点什么东西出来,希望对广大的初学者有一点点帮助。但总是不知从何处写起。今天借一线工人的台,唱一唱我的戏一路学习过来的过程中,帮助最大之一无疑来自于网络了。很多时候,通过网络,我们都可以获取到所需要的学习资料。但是,随着我们学习的深入,我们会慢慢发现,网络提供的东西是有限度的,好像大部分的资料都差不多,或者说是适合大部分的初学者所需,而当我们想更进一步提高时,却发现能够获取到的资料越来越少,相信各位也会有同感,铺天盖地的单片机资料中大部分不是流水灯就是LED,液晶,而且也只是仅仅作功能性的演示。于是有些人选择了放弃,或者是转移到其他兴趣上面去了,而只有少部分人选择了继续摸索下去,结合市面上的书籍,然后在网络上锲而不舍的搜集资料,再从牛人的只言片语中去体会,不断动手实践,慢慢的,也摸索出来了自己的一条路子。当然这个过程必然是艰辛的,而他学会了之后也不会在网络上轻易分享自己的学习成果。如此恶性循环下去,也就不难理解为什么初级的学习资料满天飞,而深入一点的学习资料却很少的原因了。相较于其他领域,单片机技术的封锁更加容易。尽管已经问世了很多年了,有价值的资料还是相当的欠缺,大部分的资料都是止于入门阶段或者是简单的演示实验。但是在实际工程应用中却是另外一回事。有能力的高手无暇或者是不愿公开自己的学习经验。很多时候,我也很困惑,看到国外爱好者毫不保留的在网络上发布自己的作品,我忽然感觉到一丝丝的悲哀。也许,我们真的该转变一下思路了,帮助别人,其实也是在帮助自己。啰啰嗦嗦的说了这么多,相信大家能够明白说的是什么意思。在接下来的一段日子里,我将会结合电子工程师之家举办的主题周活动写一点自己的想法。尽可能从实用的角度去讲述。希望能够帮助更多的初学者更上一层楼。而关于这个主题周的最大主题我想了这样的一个名字“从单片机初学者迈向单片机工程师”。名字挺大挺响亮,给我的压力也挺大的,但我会努力,争取使这样的一系列文章能够带给大家一点帮助,而不是看后大跌眼镜。这样的一系列文章主要的对象是初学者,以及想从初学者更进一步提高的读者。而至于老手,以及那些牛XX的人,希望能够给我们这些初学者更多的一些指点哈~@_@~.二、LED 主题讨论周第二章----学会释放CPU从这一章开始,我们开始迈入单片机的世界。在我们开始这一章具体的学习之前,有必要给大家先说明一下。在以后的系列文章中,我们将以51 内核的单片机为载体,C 语言为编程语言,开发环境为KEIL uv3。至于为什么选用C 语言开发,好处不言而喻,开发速度快,效率高,代码可复用率高,结构清晰,尤其是在大型的程序中,而且随着编译器的不断升级,其编译后的代码大小与汇编语言的差距越来越小。而关于C 语言和汇编之争,就像那个啥,每隔一段时间总会有人挑起这个话题,如果你感兴趣,可以到网上搜索相关的帖子自行阅读。不是说汇编不重要,在很多对时序要求非常高的场合,需要利用汇编语言和C 语言的同时,也还需要利用闲余的时间去学习了解汇编语言。-------------------------- 4一般的LED 的正常发光电流为10~20MA 而低电流LED 的工作电流在2mA 以下(亮度与普通发光管相同)。在上图中我们可知,当Q1~Q8 引脚上面的电平为低电平时,LED 发光。通过LED 的电流约为(VCC- Vd)/ RA2 。其中Vd 为LED 导通后的压降,约为-------------------------- 5P0 口电平的变化即Q 端的电平的变化,进而引起LED 的亮灭变化。由于单片机的驱动能力有限,在此,74HC573 的主要作用就是起一个输出驱动的作用。需要注意的是,通过74HC573 的最大电流是有限制的,否则可能会烧坏74HC573 这个芯片。上面这个图是从74HC573 的DATASHEET 中截取出来的,从上可以看出,每个引脚允许通过的最大电流为35mA 整个芯片允许通过的最大电流为75mA。在我们设计相应的驱动电路时候,这些参数是相当重要的,而且是最容易被初学者所忽略的地方。同时在设计的时候,要留出一定量的余量出来,不能说单个引脚允许通过的电流为35mA,你就设计为35mA,这个时候你应该把设计的上限值定在20mA 左右才能保证能够稳定的工作。(设计相应驱动电路时候,应该仔细阅读芯片的数据手册,了解每个引脚的驱动能力,以及整个芯片的驱动能力)了解了相应的硬件后,我们再来编写驱动程序。首先定义LED 的接口#define LED 熄灭下面到了重点了,究竟该如何释放CPU,避免其做延时空等待这样的事情呢。很简单,我们为系统产生一个1MS 的时标。假定LED 需要亮500MS,熄灭500MS,那么我们可以对这个1MS 的时标进行计数,当这个计数值达到500 时候,清零该计数值,同时把LED ;}}}上面有一个变量没有提到,就是g_bSystemTime1Ms 。这个变量可以定义为位变量或者是其它变量,在我们的定时器中断函数中对其置位,其它函数使用该变量后,应该对其复位(清0) ;}}因为LED 的亮或者灭依赖于LED 状态变量(g_u8LedState)的改变,而状态变量的改变,又依赖于LED 计数器的计数值(g_u16LedTimeCount ,只有计数值达到一定后,状态变量才改变)所以,两个函数都没有堵塞CPU 的地方。让我们来从头到尾分析一遍整个程序的流程。程序首先执行LedProcess() ;函数因为g_u8LedState 的初始值为0 (见定义,对于全局变量,在定义的时候最好给其一个确定的值)所以LED被点亮,然后退出LedStateChange()函数,执行下一个函数LedStateChange()在函数LedStateChange()内部首先判断1MS 的系统时标是否到了,如果没有到就直接退出函数,如果到了,就把时标清0 以便下一个时标消息的到来,同时对LED 计数器加一,然后再判断LED 计数器是否到达我们预先想要的值500,如果没有,则退出函数,如果有,对计数器清0,以便下次重新计数,同时把LED 状态变量取反,然后退出函数。由上面整个流程可以知道,CPU 所做的事情,就是对一些计数器加一,然后根据条件改变状态,再根据这个状态来决定是否点亮LED。这些函数执行所花的时间都是相当短的,如果主程序中还有其它函数,则CPU 会顺次往下执行下去。对于其它的函数(如果有的话)也要采取同样的措施,保证其不堵塞CPU,如果全部基于这种方法设计,那么对于不是非常庞大的系统,我们的系统依旧可以保证多个任务(多个函数)同时执行。系统的实时性得到了一定的保证,从宏观上看来,就是多个任务并发执行。好了,这一章就到此为止,让我们总结一下,究竟有哪些需要注意的吧。(1) 白白浪费等待,尤其是延时(超过1MS)这样的地方。(3) 设计相应驱动电路时候,应该仔细阅读芯片的数据手册,了解每个引脚的驱动能力,以及整个芯片的驱动能力(4) 最重要的是,如何去释放CPU(参考本章的例子),这是写出合格程序的基础。附完整程序代码(基于电子工程师之家的单片机开发板)#include<-------------------------- ,到此一个简单的工程模板就建立起来了,以后我们再新建源文件和头文件的时候,就可以直接保存到src 关闭水阀,那么流出的水相比刚才20MS 全开肯定要小的多。同样的道理,我们可以通过控制20MS 时间里水阀开启的时间的长短来控制流过的水的多少。那么在1S 内平均流出的水流量也就可以被控制了。当我们调整PWM 的占空比时,就会引起电压或者电流的改变,LED 的明暗状态就会随之发生相应的变化,听起来好像可以通过这种方法来实现我们想要的渐明渐暗的效果。让我们来试一下吧。大家都知道人眼有一个临界频率,当LED 的闪烁频率达到一定的时候,人眼就分辨不出LED 是否在闪烁了。就像我们平常看电视一样,看起来画面是连续的,实质不是这个样子,所有连续动作都是一帧帧静止的画面在1S 的时间里快速播放出来,譬如每秒24 帧的速度播放,由于人眼的视觉暂留效应,看起来画面就是连续的了。同样的道理,为了让我们的LED 在变化的过程中,我们感觉不到其在闪烁,可以将其闪烁的频率定在50Hz 以上。同时为了看起来明暗过渡的效果更加明显,我们在这里定义其变化范围为0~99(100等分).即最亮的时候其灰度等级为99,为0 的时候最暗,也就是熄灭了。于是乎我们定义PWM 的占空比上限为99, 的亮灭状态时,应该是在200us 整数倍时刻时。在这里我们用单片机的定时器产生200us 的中断,同时每20MS 调整一次LED 的占空比。这样在20MS * 100 = 2S 的时间内LED 可以从暗逐渐变亮,在下一个2S 内可以从亮逐渐变暗,然后不断循环。由于大部分的内容都可以在中断中完成,因此,我们的大部分代码都在-------------------------- 28TMOD |= 0x01 ; //定时器0 工作方式1TH0 = 技术在我们实际生活中应用的非常多。比较典型的应用就是控制电机的转速,控制充电电流的大小,等等。而随着技术的发展,也出现了其他类型的PWM 技术,如相电压PWM,线电压PWM,SPWM等等,如果有兴趣可以到网上去获取相应资料学习。关于渐明渐暗的灯就简单的讲到这里。五、LED 主题讨论周第五章----多任务环境下的数码管编程设计[post]数码管在实际应用中非常广泛,尤其是在某些对成本有限制的场合。编写一个好用的LED 程序并不是那么的简单。曾经有人这样说过,如果用数码管和按键,做一个简易的可以调整的时钟出来,那么你的单片机就算入门了60%了。此话我深信不疑。我遇到过很多单片机的爱好者,他们问我说单片机我已经掌握了,该如何进一步的学习下去呢?我并不急于回答他们的问题,而是问他们:会编写数码管的驱动程序了吧?“嗯”。会编写按键程序了吧?“嗯”。好,我给你出一个小题目,你做一下。用按键和数码管以及单片机定时器实现一个简易的可以调整的时钟,要求如下:8 位数码管显示,显示格式如下时-分-秒XX-XX-XX要求:系统有四个按键,功能分别是调整,加,减,确定。在按下调整键时候,显示时的两位数码管以1 Hz 频率闪烁。如果再次按下调整键,则分开始闪烁,时恢复正常显示,依次循环,直到按下确定键,恢复正常的显示。在数码管闪烁的时候,按下加或者减键可以调整相应的显示内容。按键支持短按,和长按,即短按时,修改的内容每次增加一或者减小一,长按时候以一定速率连续增加或者减少。结果很多人,很多爱好者一下子都理不清楚思路。其实问题的根源在于没有以工程化的角度去思考程序的编写。很多人在学习数码管编程的时候,都是照着书上或者网上的例子来进行试验。殊不知,这些例子代码仅仅只是具有一个演示性的作用,拿到实际中是很难用的。举一个简单的例子。下面这段程序是在网上随便搜索到的:while(1){for(num=0;num<9;num++){P0=table[num];--------------------------cn----------------------------------------------------------------------------- ;}}看出什么问题来了没有,如果没有看出来请仔细想一下,如果还没有想出来,请回过头去,认真再看一遍“学会释放CPU”这一章的内容。这个程序作为演示程序是没有什么问题的,但是实际应用的时候,数码管显示的内容经常变化,而且还有很多其它任务需要执行,因此这样的程序在实际中是根本就无法用的,更何况,它这里也调用了delayms(2)这个函数来延时2 这更是令我们深恶痛绝本章的内容正是探讨如何解决多任务环境下(不带OS)的数码管程序设计的编写问题。理解了其中的思想,无论要求我们显示的形式怎么变化(如数码管闪烁,移位等),我们都可以很方便的解决问题。数码管的显示分为动态显示和静态显示两种。静态显示是每一位数码管都用一片独立的驱动芯片进行驱动。比较常见的有74LS164,74HC595 等。利用这类芯片的好处就是可以级联,留给单片机的接口只需要时钟线,数据线,因此比较节省I/O 口。如下图所示:利用74LS164 级联驱动8 个单独的数码管静态显示的优点是程序编写简单。但是由于涉及到的驱动芯片数量比较多,同时考虑到PCB的布线等等因素,在低成本要求的开发环境下,单纯的静态驱动并不合适。这个时候就可以考虑到动态驱动了。动态驱动的图如下所示(以EE21 驱动。同时每一个数码管的公共端连接在另外一个74HC573 的输出上。当送出第一位数码管的段码内容时候,同时选通第一位数码管的位选,此时,第一位数码管就显示出相应的内容了。一段时间之后,送出第二位数码管段码的内容,选通第二位数码管的位选,这时显示的内容就变成第二位数码管的内容了……依次循环下去,就可以看到了所有数码管同时显示了。事实上,任意时刻,只有一位数码管是被点亮的。由于人眼的视觉暂留效应以及数码管的余辉效应,当数码管扫描的频率非常快的时候,人眼已经无法分辨出数码管的变化了,看起来就是同时点亮的。我们假设数码管的扫描频率为50 ;}}}函数内部定义一个静态的变量s_LedDisPos,用来表示扫描数码管的位置。每当我们执行该函数一次的时候,s_LedDisPos 的值会自加1,表示下次扫描下一个数码管。然后判断g_bSystemTime2Ms 时标消息是否到了。如果到了,就开始执行相关扫描,否则就直接跳出函数。SendLedBitData(8) ;的作用是消隐。因为我们的系统的段选和位选是共用P0 口的。在送段码之前,必须先关掉位选,否则,因为上次位选是选通的,在送段码的时候会造成相应数码管的点亮,尽管这个时间很短暂。但是因为我们的数码管是不断扫描的,所以看起来还是会有些微微亮。为了消除这种影响,就有必要再送段码数据之前关掉位选。if(pBuffer[s_LedDisPos] == '-') //显示'-'号这行语句是为了显示’-’符号特意加上去的,大家可以看到在定义数码管的段码表的时候,我多加了一个字节的代码0xbf:code uint8 。否则,没有任何一个数码管被选中。这也是为什么我们可以用SendLedBitData(8) ; //消隐,只需要设置位选不为0~7 即可对数码管进行消隐操作的原因。下面我们来编写相应的主函数,并实现数码管上面类似时钟的效果,如显示10-20-30即10 点20 36在前面的基础之上,添加一点相应的代码,让我们这个时钟走动起来。我们知道,之前我们以及设置了一个扫描数码管用到的2 ms 时标。如果我们再对这个时标进行计数,当计数值达到500,即500 * 2 = 1000 ms 时候,即表示已经逝去了1 S 的时间。我们再根据这个1 S 的时间更新显示缓冲区即可。听起来很简单,让我们实现它吧。首先在-------------------------- ;}}}}}}}}这个函数的作用就是对每个数码管缓冲位的值进行判断,判断的标准就是我们熟知的24 小时制。如秒的个位到了10 就清0,同时秒的十位加1….诸如此类,我就不一一详述了。同时,我们再编写一个时钟初始值设置函数,这样,可以很方便的在主程序开始的时候修改时钟初始值。void SetClock(uint8 nHour, uint8 nMinute, uint8 38至此,本章所诉就告一段落了。至于如何完成数码管的闪烁显示,就像本章开头所说的那个数码管时钟的功能,就作为一个思考的问题留给大家思考吧。同时整个LED 篇就到此结束了,在以后的文章中,我们将开始学习如何编写实用的按键扫描程序。[/post本章所附例程在EE21 学习板上调试通过,拥有板子的朋友可以直接下载附件对照学习六、KEY 主题讨论第一章——按键程序编写的基础从这一章开始,我们步入按键程序设计的殿堂。在基于单片机为核心构成的应用系统中,用户输入是必不可少的一部分。输入可以分很多种情况,譬如有的系统支持PS2 键盘的接口,有的系统输入是基于编码器,有的系统输入是基于串口或者USB 或者其它输入通道等等。在各种输入途径中,更常见的是,基于单个按键或者由单个键盘按照一定排列构成的矩阵键盘(行列键盘)。我们这一篇章主要讨论的对象就是基于单个按键的程序设计,以及矩阵键盘的程序编写。◎按键检测的原理常见的独立按键的外观如下,相信大家并不陌生,各种常见的开发板学习板上随处可以看到他们的身影。总共有四个引脚,一般情况下,处于同一边的两个引脚内部是连接在一起的,如何分辨两个引脚是否处在同一边呢?可以将按键翻转过来,处于同一边的两个引脚,有一条突起的线将他们连接一起,以标示它们俩是相连的。如果无法观察得到,用数字万用表的二极管挡位检测一下即可。搞清楚这点非常重要,对于我们画PCB 39对于单片机I/O 内部有上拉电阻的微控制器而言,还可以省掉外部的那个上拉电阻。简单分析一下按键检测的原理。当按键没有按下的时候,单片机I/O 通过上拉电阻R 接到VCC,我们在程序中读取该I/O 的电平的时候,其值为1(高电平); 当按键S 按下的时候,该I/O被短接到GND,在程序中读取该I/O 的电平的时候,其值为0(低电平) 。这样,按键的按下与否,就和与该按键相连的I/O 的电平的变化相对应起来。结论:我们在程序中通过检测到该I/O 口电平的变化与否,即可以知道按键是否被按下,从而做出相应的响应。一切看起来很美好,是这样的吗?◎现实并非理想在我们通过上面的按键检测原理得出上述的结论的时候,其实忽略了一个重要的问题,那就是现实中按键按下时候的电平变化状态。我们的结论是基于理想的情况得出来的,就如同下面这幅按键按下时候对应电平变化的波形图一样:--------------------------cn----------------------------------------------------------------------------- 40而实际中,由于按键的弹片接触的时候,并不是一接触就紧紧的闭合,它还存在一定的抖动,尽管这个时间非常的短暂,但是对于我们执行时间以us 为计算单位的微控制器来说,它太漫长了。因而,实际的波形图应该如下面这幅示意图一样。这样便存在这样一个问题。假设我们的系统有这样功能需求:在检测到按键按下的时候,将某个I/O 的状态取反。由于这种抖动的存在,使得我们的微控制器误以为是多次按键的按下,从而将某个I/O 的状态不断取反,这并不是我们想要的效果,假如该I/O 控制着系统中某个重要的执行的部件,那结果更不是我们所期待的。于是乎有人便提出了软件消除抖动的思想,道理很简单:抖动的时间长度是一定的,只要我们避开这段抖动时期,检测稳定的时候的电平不久可以了吗?听起来确实不错,而且实际应用起来效果也还可以。于是,各种各样的书籍中,在提到按键检测的时候,总也不忘说道软件消抖。就像下面的伪代码所描述的一样。(假设按键按下时候,低电平有效)If(0 //等待按键释放}乍看上去,确实挺不错,实际中呢?在实际的系统中,一般是不允许这么样做的。为什么呢?首先,这里的Delayms(20) , 让微控制器在这里白白等待了20 ms 的时间,啥也没干,考虑我在《学会释放CPU》一章中所提及的几点,这是不可取的。其次while(0 == io_KeyEnter)所以合理的分配好微控制的处理时间,是编写按键程序的基础。?;更是程序设计中的大忌(极少的特殊情况例外)。任何非极端情况下,都不要使用这样语句来堵塞微控制器的执行进程。原本是等待按键释放,结果CPU 就一直死死的盯住该按键,其它事情都不管了,那其它事情不干了吗?你同意别人可不会同意◎消除抖动有必要吗?的确,软件上的消抖确实可以保证按键的有效检测。但是,这种消抖确实有必要吗?有人提出了这样的疑问。抖动是按键按下的过程中产生的,如果按键没有按下,抖动会产生吗?如果没有按键按下,抖动也会在I/O 上出现,我会立刻把这个微控制器锤了,永远不用这样一款微控制器。所以抖动的出现即意味着按键已经按下,尽管这个电平还没有稳定。所以只要我们检测到按键按下,即可以返回键值,问题的关键是,在你执行完其它任务的时候,再次执行我们的按键任务的时候,抖动过程还没有结束,这样便有可能造成重复检测。所以,如何在返回键值后,避免重复检测,或者在按键一按下就执行功能函数,当功能函数的执行时间小于抖动时间时候,如何避免再次执行功能函数,就成为我们要考虑的问题了。这是一个仁者见仁,智者见智的问题,就留给大家去思考吧。所以消除抖动的目的是:防止按键一次按下,多次响应。七、KEY 主题讨论第二章——基于状态转移的独立按键程序设计本章所描述的按键程序要达到的目的:检测按键按下,短按,长按,释放。即通过按键的返回值我们可以获取到如下的信息:按键按下(短按),按键长按,按键连发,按键释放。不知道大家还记得小时候玩过的电子钟没有,就是外形类似于CALL 机(CALL 42呢,今天就让我们来剖析它的原理吧。?机,好像是很古老的东西了状态在生活中随处可见。譬如早上的时候,闹钟把你叫醒了,这个时候,你便处于清醒的状态,马上你就穿衣起床洗漱吃早餐,这一系列事情就是你在这个状态做的事情。做完这些后你会去等车或者开车去上班,这个时候你就处在上班途中的状态…..中午下班时间到了,你就处于中午下班的状态,诸如此类等等,在每一个状态我们都会做一些不同的事情,而总会有外界条件促使我们转换到另外一种状态,譬如闹钟叫醒我们了,下班时间到了等等。对于状态的定义出发点不同,考虑的方向不同,或者会有些许细节上面的差异,但是大的状态总是相同的。生活中的事物同样遵循同样的规律,譬如,用一个智能充电器给你的手机电池充电,刚开始,它是处于快速充电状态,随着电量的增加,电压的升高,当达到规定的电压时候,它会转换到恒压充电。总而言之,细心观察,你会发现生活中的总总都可以归结为一个个的状态,而状态的变换或者转移总是由某些条件引起同时伴随着一些动作的发生。我们的按键亦遵循同样的规律,下面让我们来简单的描绘一下它的状态流程转移图。下面对上面的流程图进行简要的分析。首先按键程序进入初始状态S1,在这个状态下,检测按键是否按下,如果有按下,则进入按键消抖状态2,在下一次执行按键程序时候,直接由按键消抖状态进入按键按下状态3,在此状态下检测按键是否按下,如果没有按键按下,则返回初始状态S1,如果有则可以返回键值,同时进入长按状态S4,在长按状态下每次进入按键程序时候对按键时间计数,当计数值超过设定阈值时候,则表明长按事件发生,同时进入按键连发状态S5。如果按键键--------------------------cn----------------------------------------------------------------------------- 43值为空键,则返回按键释放状态S6,否则继续停留在本状态。在按键连发状态下,如果按键键值为空键则返回按键释放状态S6,如果按键时间计数超过连发阈值,则返回连发按键值,清零时间计数后继续停留在本状态。看了这么多,也许你已经有一个模糊的概念了,下面让我们趁热打铁,一起来动手编写按键驱动程序吧。下面是我使用的硬件的连接图。硬件连接很简单,四个独立按键分别接在P3^0------P3^3 ;KeyScan()作为底层按键的驱动程序,为上层按键扫描提供一个接口,这样我们编写的上层按键扫描函数可以几乎不用修改就可以拿到我们的其它程序中去使用,使得程序复用性大大提高。同时,通过有意识的将与底层硬件连接紧密的程序和与硬件无关的代码分开写,使得程序结构层次清晰,可移植性也更好。对于单片机类的程序而言,能够做到函数级别的代码重用已经足够了。在编写我们的上层按键扫描函数之前,需要先完成一些宏定义。//定义长按键的TICK 47}关于这个函数内部的细节我并不打算花过多笔墨去讲解。对照着按键状态流程转移图,然后去看程序代码,你会发现其实思路非常清晰。最能让人理解透彻的,莫非就是将整个程序自己看懂,然后想象为什么这个地方要这样写,抱着思考的态度去阅读程序,你会发现自己的程序水平会慢慢的提高。所以我更希望的是你能够认认真真的看完,然后思考。也许你会收获更多。不管怎么样,这样的一个程序已经完成了本章开始时候要求的功能:按下,长按,连按,释放。事实上,如果掌握了这种基于状态转移的思想,你会发现要求实现其它按键功能,譬如,多键按下,功能键等等,亦相当简单,在下一章,我们就去实现它。在主程序中我编写了这样的一段代码,来演示我实现的按键功能。void 亮,第一个熄灭,表示长按事件发生。再过500 ms第5~8 个LED 闪烁,表示连按事件发生。当释放按键时候,P0 口所接的LED 的状态为:灭亮灭亮亮灭亮灭,这也正是P0 = 0xa5 48八、综合应用之一——如何设计复杂的多任务程序我们在入门阶段,一般面对的设计都是单一的简单的任务,流程图可以如图1 所示,通常会用踏步循环延时来满足任务需要。面对多任务,稍微复杂的程序设计,沿用图1 的思想,我们会做出如图2 所示的程序,在大循环体中不断增加任务,通常还要用延时来满足特定任务节拍,这种程序设计思想它有明显的不足,主要是各个任务之间相互影响,增加新的任何之后,以前很好的运行的任务有可能不正常,例如数码管动态扫描,本来显示效果很好的驱动函数,在增加新的任务后出现闪烁,显示效果变差了。(原文件名:-------------------------- 49引用图片图1 单一任务简单流程图图2 多任务简单流程图很明显,初学者在设计程序时,需要从程序构架思想上下功夫,在做了大量基本模块练习之后,需要总结提炼自己的程序设计思路(程序架构思想)。首先我们来理解“任务”,所谓任务,就是需要CPU 周期“关照”的事件,绝大多数任务不需要CPU 一直“关照” ,例如启动ADC 的启动读取。甚至有些任务“害怕”CPU 一直“关照”例如LCD 的刷新,因为LCD 是显示给人看的,并不需要高速刷新,即便是显示的内容在高速变化,也不需要高速刷新,道理是一样的。这样看来,让CPU 做简单任务一定浪费,事实也是如此,绝大多数简单任务,CPU 都是在“空转” (循环踏步延时) 。对任务结还可以知道,很多任务需要CPU 不断“关照” ,其实这种“不断”也是有极限的,比如数码管动态扫描,能够做到40Hz 就可以了,又如键盘扫描,能够做到20Hz(经验值),基本也就不会丢有效按键键值了,再如LCD 刷新,我觉得做到10Hz 就可以了,等等。看来,大多数任务都是工作在低速频度。而我们的CPU 一旦运行起来,速度又很快,CPU 本身就靠很快的速度执行很简单的指令来胜任复杂的任务(逻辑)的。如果有办法把“快”的CPU分成多个慢的CPU,然后给不同的任务分配不同速度的CPU,这种设想是不是很好呢!确很好,下面就看如何将“快”的CPU 划分成多个“慢”的CPU。根据这种想法,我们需要合理分配CPU 资源来“关照”不同的任务,最好能够根据任务本身合理占用CPU 资源,首先看如图3 所示的流程图,各个任务流程独立,各任务通过全变量来交互信息,在流程中有一个重要的模块“任务切换”,就是任务切换模块实现CPU 52杂。在充分考虑单片机中断特性(在哪里中断就返回到哪里)后,实际可行的任务切换如图4B 所示,定时中断可能发生在任务调度,随机任务执行的任何时候,图中最大的框框所示,不管中断在何时发生,它都会正常返回,定时中断所产生的影响只在任务调度模块起作用,即依次让不同的任务按不同的节拍就绪。任务调度会按一定的优先级执行就绪任务。总结不同的任务需要CPU 关照的频度,选择最快的那个频度来设定定时器中断的节拍,一般选择200Hz,或者100Hz 都可以。另外再给每个任务设定一个节拍控制计数器C,也就是定时器每中断多少次后执行任务一次。例如取定时中断节拍为200Hz,给任务设定的C=10,则任务执行频度为200/10=20Hz,如果是数码管扫描,按40Hz 不闪烁规律,则任务节拍控计数器C=5 即可。在程序设计中,C 代表着任务运行的节拍控制参数,我们习惯用delay 来描述,不同的任务用task0,task1……来描述。明天继续写如何用代码实现!下面我们来用代码实现以上多任务程序设计思想。首先是任务切换while(1){if(task_delay[0]==0) 就绪,……}很显然,执行任务的条件是任务延时量task_delay=0,那么任务延时量谁来控制呢?定时器啊!定时器中断对任务延时量减一直到归零,标志任务就绪。当没有任务就绪时,任务切换本身就是一个Idle 任务。void 的中断节拍为200Hz,task0_delay 初值为10,则task0()执行频度为200/10=20Hz。有了以上基础,我们来设计一个简单多任务程序,进一步深入理解这种程序设计思想。任务要求:用单片机不同IO 脚输出1Hz,5Hz,10Hz,20Hz 56引用图片图6 CPU 运行情况示意图CPU 运行情况如图6 所示,黑色区域表示CPU 进程,系统启动后, CPU 将无休止的运行,CPU 资源将如何分配呢?程序首先进入“任务切换”进程,如果当前没有任务就绪,就在任务切换进程循环(也可以理解为空闲进程),定时中断将CPU 当前进程打断,在定时中断进程可能让某些任务就绪,中断返回任务切换进程,很快会进入就绪任务0,CPU“关照”完任务0,再次回到任务切换进程,如果还有其它任务就绪,还会再次进入其它任务,没有任务就循环等待,定时中断会不断让新的任务就绪,CPU 也会不断进入任务“关照” 。这样不同的任务就会获得不同的CPU 资源,每一个任务都像是拥有一个独立的CPU 为之服务。从这种进程切换我们可以看出,在定时中断和任务切换过程中,额外的占用了一些CPU资源, 这就是定时中断频度不宜太快, 否则将大大降低CPU 的有效资源率, 当然太慢也不行。另外就是CPU 每次关照任务的时间不能太长,如果超过一个中断周期,就会影响到其它任的实时性。所谓的实时性就是按定时中断设定的节拍,准时得到CPU 关照。这样,每一个任务就必须简单,每次“关照”时间最好不要超过定时中断节拍周期(5ms 或10ms,初学者要对ms 有一个概念,机器周期为us 级的单片机,1ms 可以执行上千条指令,对于像数码管扫描,键盘扫描,LCD 显示等常规任务都是绰绰有余的,只是遇到大型计算,数据排序就得短了)关于任务优先级的问题:一个复杂系统,多个任务之间总有“轻重缓急”之区别,那些需要严格实时的任务通常用中断实现,中断能够保证第一时间相应,我们这里讨论的不是那种实时概念,是指在最大允许时差内能够得到CPU“关照” ,例如键盘扫描,为了保证较好的操作效果,快的/慢的/长的/短的(不同人按键不一样)都能够正确识别,这就要保证足够的扫描速度,这种扫描速度对不同的按键最好均等,如果我们按50Hz 来设计,那么就要保证键盘扫描速度在任何情况下都能够做到50Hz 扫描频度,不会因为某个新任务的开启而被破坏,如果确实有新的任务有可能破坏这个50Hz 57时,最先执行哪个的问题,任务调度时要优先执行级别高的任务。关于“长”任务的问题:有些任务虽然很独立,但完成一次任务执行需要很长时间,例如DS18B20,从复位初始化到读回温度值,最长接近1s,这主要是DS18B20 温度传感器完成一次温度转换需要500 到750ms,这个时间对CPU 而言,简直是太长了,就像一件事情要我们人等待10 年一样,显然这样的任务是其它任务所耽搁不起的。像类似DS18B20 这样的器件(不少ADC 也是这样) ,怎么设计任务体解决“长”的问题。进一步研究这些器件发现,真正需要CPU“关照”它们的时间并不长,关键是等待结果要很长时间。解决的办法就是把类似的器件驱动分成多个段:初始化段、启动段、读结果段,而在需要花长时间等待时间段,不要CPU 关照,允许CPU 去关照其它任务。将一个任务分成若干段,确保每段需要CPU 关照时长小于定时器中断节拍长,这样CPU 在处理这些长任务时,就不会影响到其它任务的执行。Easy51RTOS正是基于以上程序设计思想,总结完善后提出一种耗费资源特别少并且不使用堆栈的多线程操作系统,这个操作系统以纯C 语言实现,无硬件依赖性,需要单片机的资源极少。名为Easy51RTOS,特别适合初学者学习使用。有任务优先级,通过技巧可以任务间延时,缺点是高优先级任务不具有抢占功能,一个具有抢占功能的操作系统,一定要涉及到现场保护与恢复,需要更多的RAM 资源,涉及到堆栈知识,文件系统将很复杂,初学者学习难度大。为了便于初学者学习,将代码文件压缩至4 个文件。-------------------------- 58extern void 开始,就意味着优先级高的任务就绪会先执行。这样task0 具有最高优先级,task1、task2、task3 优先级依次降低。特别是void task3(void)用switch(state)状态机实现了任务分段,这也是任务内系统延时的一种方法。我会继续更新的。。。。。。。。。。。九、综合应用之二——DS1320/DS18B20 应用好几天没有更新了,呵呵~~今天我把咱们常用的传感器DS1320 DS18B20 给大家介绍下。对于市面上的大多数51 单片机开发板来说。ds1302 和ds18b20 应该是比较常见的两种外围芯片。ds1302 是具有SPI 总线接口的时钟芯片。ds18b20 则是具有单总线接口的数字温度传感器。下面让我们分别来认识并学会应用这两种芯片。首先依旧是看DS1302 64引用图片由于对于这些器件的操作基本上按照数据手册上面提供的时序图和相关命令字来进行操作就可以了。因此在我们应用这些器件的时候一定要对照着手册上面的要求来进行操作。如果觉得还不够放心的话。可以到网上下载一些参考程序。对着手册看别人的程序,看别人的思路是怎么样的。DS1302 和单片机的连接很简单。只需一根复位线,一根时钟线,一根数据线即可。同时它本身还需要接一个-------------------------- 69构体中。至于如何把时间数据在数码管或者是液晶屏上显示出来我相信大家应该都会了吧^_^.看看显示效果如何~~(原文件名:-------------------------- 71引用图片RAM:DS18B20 的内部暂存器共9 个字节。其中第一个和第二个字节存放转换后的温度值。第二个和第三个字节分别存放高温和低温告警值。(可以用RAM 指令将其拷贝到EEPROM中)第四个字节为配置寄存器。第5~7 个字节保留。第9 个字节为前8 个字节的CRC 码。DS18B20 的温度存放如上图所示。其中S 位符号位。当温度值为负值时,S = 1 ,反之则S = 0 。我们把得到的温度数据乘上对应的分辨率即可以得到转换后的温度值。DS18B20 的通讯协议:在对DS18B20 进行读写编程时,必须严格保证读写的时序。否则将无法读取测温结果。根据DS18B20 的通讯协议,主机控制DS18B20 完成温度转换必须经过3 个步骤:每一次读写之前都要对DS18B20 进行复位,复位成功后发送一条ROM 指令,最后发送RAM 指令。这样才能对DS18B20 进行预定的操作。复位要求主机将数据线下拉500us,然后释放,DS18B20 收到信号后等待16~160us 然后发出60~240us

今天我们的主题是单片机,说道单片机你可能最先想到的是51单片机,然后通过外接的数字电路和模拟电路来控制外围的硬件,比如PWM,ADC,I2C等等。那么今天我们所说的是什么那?其实你把Atmega16单片机当成51单片机来使用即可,只不过是Atmega16单片机的外围接口更丰富,那么它都有什么外设资源那?

ATmega16是基于增强的AVR RISC结构的低功耗8 位CMOS微控制器,由于其先进的指令集以及单时钟周期指令执行时间,ATmega16 的数据吞吐率高达1 MIPS/MHz。ATmega16常用片上资源有定时器、PWM、外部中断、SPI总线、两线串行接口TWI、EEPROM、USART、模拟比较器、ADC转换、IEEE(JTAG)边界扫描等。如何去学习Atmega16单片机那?我们从硬件和软件两方面分析。

2. Atmega16单片机硬件原理图(最小系统)

从上述的最小系统板子上们可以看到它分为如下几个部分:

复位电路:上电复位(电容),手动按键复位。

晶振电路:Atmega16单片机内部自带RC震荡电路,但是自带的震荡电路不够精确,所以这里就外接了一个8MHZ的晶振。

下载程序接口ISP:ISP串行的下载程序的接口

AVR单片机的编译软件有GCC AVR和ICC AVR等,通常使用较多的是ICC AVR,也比较好用,本书以ICC AVR6.31版本为例介绍编译软件的使用。启动ICC AVR,界面如下图所示。如何使用avr单片机控制最小系统上的led灯那?代码如下:

PORTA=0X00; /*点亮led灯,由最小系统的外部电路决定单片机输出

低电平时流水灯点亮*/

//由于其他端口没有接外围设备先不用定义

我要回帖

更多关于 单片机流水灯程序 的文章

 

随机推荐