自学单片机-13

第13章 1602液晶与串口的应用实例

		理论上的内容要想逐步消化掌握,必须通过大量的实践进行巩固,否则时间一长,极容易忘掉。尤其是一些编程相关的技巧,就是靠不停地写程序,不停地参考别人的程序慢慢积累成长起来的。本章学习1602的例程和实际开发中比较实用的串口通信程序。

13.1 通信时序解析

	随着对通信技术的深入学习,大家要逐渐在头脑中建立起时序这种概念。所谓“时序”从字面意义上来理解,一是“时间问题”,二是“顺序问题”。先说“顺序问题”,这个相对简单一些。在学UART串口通信的时候,先1位起始位,再8位数据位,最后1位停止位,这个先后顺序不能错。在学1602液晶的时候,比如写指令8位数据位,最后1位停止位,这个先后顺序不能错。在学1602液晶的时候,比如写指令RS=L, R/W=L,D0~D7=指令码,这三者的顺序是无所谓的,但是最终的E=高脉冲,必须是在这三条程序之后,这个顺序一旦错误,写的数据也会出错。“时间问题”内容相对复杂。比如UART通信,每一位的时间宽度是1/baud。读者在初中就学过一个概念,世界上没有绝对的准确。那么每一位的时间宽度1/baud要求精确到什么范围内呢?前边提到过,单片机读取UART的RXD引脚数据的时候,一位数据被单片机平均分成了16份,取其中的7、8、9三次读到的结果,这三次中有2次是高电平那这一位就是1,有2次是低电平,那这一次就是0。如果波特率稍微有些偏差,只要累计下来到最后一位停止位,这7、8、9还在范围内即可,如图13-1所示。

在这里插入图片描述
用三个箭头来表示7、8、9这三次的采集位置,大家可以注意到,当采集到D7的时候,已经有一次采集偏出去了,但是采集到的数据还是不会错,因为有2次采集正确。至于这个偏差允许多大,读者可以详细算一下。实际上UART通信的波特率是允许一定范围内误差存在的,但是不能过大,否则就会采集错误。大家在计算波特率的时候,发现没有整除,有小数部分的时候,就要特别小心了,因为小数部分是一概被舍掉的,于是计算误差就产生了。用11.0592M晶振计算的过程中,11059200/12/32/9600得到的是一个整数,如果用12M晶振计算12000000/12/32/9600就会得到一个小数,读者可以算一下误差多少,是否在误差范围内。
1602液晶的时序问题,大家要学会通过LCD1602的数据手册提供的时序图和时序参数表来进行研究,而且看懂时序图是学习单片机所必须掌握的一项技能,如图13-2所示。
在这里插入图片描述
大家看到这种图的时候,不要感觉害怕。说句不过分的话,单片机这些逻辑上的问题,只要小学毕业就可以理解,很多时候是因为大家把问题想象得太难才学不下去的。
先来看一下读操作时序的RS引脚和R/W引脚,这两个引脚先进行变化,因为是读操作,所以R/W引脚首先要置为高电平,而不管它原来是什么。读指令还是读数据,都是读操作,而且都有可能,所以RS引脚既有可能是置为高电平,也有可能是置为低电平,大家注意图上的画法。而RS和R/W变化了经过tsp1p这么长时间后,使能引脚E才能从低电平到高电平发生变化。
而使能引脚E拉高经过了tD这么长时间后,LCD1602输出DB的数据就是有效数据了,就可以来读取DB的数据了。读完之后,要先把使能E拉低,经过一段时间后RS、R/W和DB才可以变化继续为下一次读写做准备了。
而写操作时序和读操作时序的差别,就是写操作时序中,DB的改变是由单片机来完成的,因此要放到使能引脚E的变化之前进行操作,其他区别大家可以自行对比一下。
细心的读者会发现,这个时序图上还有很多时间标签。比如E的上升时间tR,下降时间时间tF, 使能引脚E从一个上升沿到下一个上升沿之间的长度同期tc,使能引脚E下降沿后,R/W和RS变化时间间隔tHD1等很多时间要求,这些要求怎么看呢?放心,只要是正规的数据手册,都会把这些时间要求给大家标记出来的,如表13-1所示。

		表13-1		1602时序参数![在这里插入图片描述](https://img-blog.csdnimg.cn/46518ebc5ccd4467b17d5451b66b8253.png#pic_center)

在这里插入图片描述
大家要善于把手册中的这个表和时序图结合起来看。表13-1中的数据都是时序参数,本章所有时序参数都一点点给大家讲出来,以后遇到同类时序图就不再讲了,只是提一下,但是大家务必要学会自己看时序图,这个很重要,此外,看以下解释需要结合图13-2。
.tc:指的是使能引脚E从本次上升沿到下次上升沿的最短时间是400ms,而单片机因为速度较慢,一个机器周期就是1us多,而一条C语言指令肯定是一个或者几个机器周期的,所以这个条件完全满足。
tpw:指的是使能引脚E高电平的持续时间最短是150ms,同样由于单片机比较慢,这个条件也完全满足。
tR, tF:指的是使能引脚E的上升沿时间和下降沿时间,不能超过25ns,别看这个数很小,其实这个时间限值很宽裕,实际用示波器测了一下开发板的这个引脚上升沿和下降沿时间大概是10~15ns之间,完全满足。
tsp1:指的是RS和R/W引脚使能后至少保持30ns,使能引脚E才可以变成高电平,这个条件同样也完全满足。
tHD1:指的是使能引脚E变成低电平后,至少保持10ns之后,RS和R/W才能进行变化,这个条件也完全满足。
tD: 指的是使能引脚E变成高电平后 ,最多100ns后,1602就把数据送出来了,就可以正常去读取状态或者数据了。
tHD2: 指的是读操作过程中,使能引脚E变成低电平后,至少保持20ns, DB数据总线才可以进行变化, 这个条件也完全满足。
tsp2:指的是DB数据总线准备好后,至少保持40ns,使能引脚E才可以从低到高进行使能变化,这个条件也完全满足。
tHD2:指的是写操作过程中,要引脚E变成低电平后,至少保持10ns,DB数据总线才可以变化,这个条件也完全满足。
表13-1中LCD1602的时序参数表已经解析完成了,看完之后,是不是感觉比想象的要简单。大家也得慢慢学会看这种时序图和表,在今后的学习中,这方面的能力尤为重要。如果以后换用了其他型号的单片机,那么就根据单片机的执行速度来评估你的程序是否满足时序要求,整体上来说器件都是有一个最快速度的限制,而没有最慢限制,所以当换用高速的单片机后通常都是靠在各步骤间插入软件延时来满足较慢的时序要求。

13.2 1602整屏移动

第7章学点阵LED的时候可知, LED可以实现上下移动、左右移动等。而对于1602液晶来说,也可以进行屏幕移动,实现想要的一些效果,下面用一个例程实现字符串在1602液晶上的左移。每个人都不要只瞪着眼看,一定要认真抄下来,甚至抄几遍,边抄边理解。

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
通过这个程序,大家首先要学会for语句在数组中的灵活应用,这个其实在数码管显示有效位的例程中已经有所体现了。其次,随着后边程序量的增大,大家得学会多个函数之间相互调用的灵活应用,体会其中的奥妙。

13.3 多.c文件的初步认识

	13.2节写液晶滚屏移动程序,大概有160行左右。随着硬件模块使用的增多,程序量的增大,往往要把程序写到多个文件里,方便代码的编写、维护和移植。比如这个液晶滚屏程序,就可以把1602底层的功能函数专门写到一个.c文件内,如LcdWaitReady、LcdWriteCmd、LcdWriteDat、LcdShowStr、LcdSetCursor、InitLcd1602这些函数,都是属于液晶底层驱动的程序代码,要使用液晶功能的时候,只有两个函数对实际功能实现部分有用,一个是InitLcd1602,因为需要先初始化液晶,另外一个就是LcdShowStr,只需把要显示的内容通过参数传递给这个函数,函数就可以实现我们想要的显示效果,所以把这几个底层的液晶驱动程序都放到另外一个Lcd1602.c文件中,而把相实现的一些比如滚动实现、中断等上层功能程序全部放到main.c中,但是main.c文件如何调用Lcd1602.c文件中的函数呢?c语言中,有一个extern关键字,它有两个基本作用。(1)当一个变量的声明不在文件的开头,在它声明之前的函数想要引用,则应该用extern进行“外部变量”声明,代码如下。# include sbit LED = P0^0;void main(){extern unsigned int i;while(1){LED = 0;									//点亮小灯for(i=0; i<30000; i++);				//延时LED = 1;									//熄灭小灯for(i=0; i<30000; i++);         //延时}}unsigned int i = 0;....变量的作用域,是从声明这个变量开始往后所有的程序,如果调用在前,声明在后,那么就是这么用。但是实际开发过程中一般都不会这样做,所以仅仅是表达一下extern的这个用法,但它并不实用。(2)在一个工程中,为了方便管理和维护代码,用了多个.c源文件,如果其中一个main.c文件要调用Lcd1602.c文件里的变量或者函数的时候,就必须得在main.c里边进行外部声明,告诉编译器这个变量或者函数是在其他文件中定义的,可以直接在侬个文件中进行调用。多.c文件的编程方式,大家不要想象得太复杂。首先新建一个工程,一个工程代表一个完整的单片机程序,只能生成一个hex,但是一个工程可以有很多个.c源文件组成共同参与 编译。工程建立好之后,新建文件并且保存取名为main.c文件,再新建一个文件并且保存取名为Lcd1602.c文件,下面就可以在两个不同文件中分别编写代码了。当然,在编写程序的过程中,不是说要先把main.c的文件全部写完,再进行1602.c程序的编写,而往往是交互的。比如先写Lcd1602.c文件中部分Lcd1602液晶的底层函数LcdWaitReady、LcdWriteCmd、LcdWriteDat、InitLcd1602,然后编写main.c文件中的功能程序,在编写main.c文件中程序时,又有对Lcd1602.c底层程序的综合调用,这个时候需要Lcd1602.c文件提供一个被调用的函数比如LcdShowStr,就可以再到Lcd1602.c中把这个函数完成。当然了,这仅仅是一个说明例子而已,顺序完全没有一个标准,实际应用中如果对程序逻辑需求了解透彻,根据自己的理解去写程序即可。把1602整屏移动的程序改造成为多文件的程序,代码如下。/******************************************Lcd1602.c文件程序源代码*************************************************/![在这里插入图片描述](https://img-blog.csdnimg.cn/457f40a7c6cb4eb1995753f912939fb6.png#pic_center)

在这里插入图片描述
在这里插入图片描述/main.c文件程序源代码*************************/
在这里插入图片描述
在这里插入图片描述

在main.c中要调用Lcd1602.c文件中的InitLcd1602()和LcdShowStr这两个函数,只需要在main.c中进行extern声明即可。大家用Keil软件编程试试,真正地感觉一下多.c源文件的好处。如果这个程序给你的感觉还不深刻,下面来做一个稍微大点的程序来体会一下。

13.4 计算器实例

	按键和液晶可以组成最简易的计算器。下面来写一个简易整数计算器提供给大家学习。为了使程序不过于复杂,这个计算器不考虑连加、连减等连续计算,不考虑小数情况。加减乘除分别用上下左右来替代,回车表示等于,ESC表示归0。程序共分为三部分,一部分是1602液晶显示, 一部分是按键动作和扫描,一部分是主函数功能,代码如下:

/**************Lcd1602.c文件程序源代码/
在这里插入图片描述
在这里插入图片描述
Lcd1602.c文件中根据上层应用的需要增加了两个清屏函数:区域清屏-----------LcdAreaClear,整屏清屏-----LcdFullClear。

/keyboard.c文件程序源代码******/
在这里插入图片描述
在这里插入图片描述keyboard.c是对之前已经用过多次的矩阵按键驱动的封装,具体到某个按键要执行的动作函数都放到上层的main.c中实现,在这个按键驱动文件中只负责调用上层实现的按键动作函数即可,代码如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
main.c文件实现所有应用层的操作函数,即计算器功能所需要信息显示、按键动作响应等,另外还包括主循环和定时中断的调度。
通过这样一个程序,大家一方面学习如何进行多个.c文件的编程,另外一个方面学会多个函数之间的灵活调用。可以把这个程序看成是一个简单的小项目,学习一下项目编程都是如何进行和布局的。不要把项目想象得太难,再复杂的项目也是这种简单程序的组合和扩展而已。

13.5 串口通信机制和实用的串口例程

		前边学习串口通信的时候,比较注重的是串口底层时序上的操作过程,所以例程都是简单地收发字符或者字符串。在实际应用中,往往串口还要和计算机上的上位机软件进行交互,实现计算机软件发送不同的指令,单片机对应执行不同操作功能,这就要求组织一个比较合理的通信机制和逻辑关系,用来实现想要的结果。本节所提供程序的功能是,通过计算机串口调试助手下发三个不同的命令,第一条指令:buzz on可以让蜂鸣器响;第二条指令:buzz off可以让蜂鸣器不响;第三条指令:showstr,这个命令空格后边,可以添加任何字符串,让后边的字符串在1602液晶上显示出来,同时不管发送什么命令,单片机收到后把命令原封不动地再通过串口发送给计算机,以表示“我收到了......你可以检查一下对不对”。这样的感觉是不是更像是一个小项目了呢?对于串口通信部分来说,单片机给计算机发字符串好说,有多大的数组就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收起始头在哪里,结束在哪里?这些在接收到数据前都是无从得知的,那怎么办呢?编程思路是基于这样一种通常的事实:当需要发送一帧(多个字节)数据时,这些数据都是连续不断地发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间。于是建立这样一种程序机制:设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程中是30ms)后,就可以认定一帧完整的数据已经传输完毕了,于是告诉其他程序可以来处理数据了,本次的数据处理完后就恢复初始状态,再 准备下一次的接收。那么这个用于判定一帧结束的空闲时间取多少合适呢?它取决于多个条件,并没有一个固定值,这里介绍几个需要考虑的原则:第一,这个时间必须大于一个字节传输时间很明显单片机接收中断产生是在一个字节接收完毕,也就是一个时刻点,而其接收过程程序是无从知晓的,因此在至少一个字节传输时间内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统 延时,因为不是所有的发送方都能让数据严格无间隔地发送,因为软件响应、关中断、系统临界区等操作都会引起延时,所以还得再附加几毫秒到十几毫秒的时间。选取30ms是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC或其他单片机系统)情况。先把这个程序最重要的Uart.c文件中的程序贴出来,代码如下。一点点给大家解析,这个是实际项目开发常用的用法,大家一定要认真弄明白。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

	大家可以对照注释和前面的讲解分析一下这个Uart.c文件, 在这里指出其中的两个要点希望大家多注意。(1)接收数据的处理。在串口中断中,将接收到的字节都存入缓冲区bufRxd中,同时利用另外的定时器中断通过间隔调用UartRxMonitor来监控一帧数据是否接收完毕,判定的原则就是前面介绍的空闲时间。当判定一帧数据接收完毕时,设置flagFrame标志,主循环中可以通过调用UartDriver来检测该标志,并处理接收到的数据。当要处理接收到的数据时,先通过串口读取函数UartRead把接收缓冲区bufRxd中的数据读取出来,然后再对里面用不就行了嘛,何必还得再复制到另一个地方去呢?我们设计这种双缓冲的机制,主要是为了提高串口接收到响应效率:首先,如果你在bufRxd中处理数据,那么这时就不能再接收任何数据,因为新接收的数据会破坏原来的数据,造成其不完整和混乱;其次,这个处理过程可能会耗费较长的时间,比如说上位机现在就给你发来一个延时显示的命令,那么在这个延时的过程中都无法去接收新的命令,在上位机看来就是暂时失去响应了。而使用这种双缓冲机制就可以大大改善这个问题,因为数据复制所需的时间是相当短的,只要复制出去后,bufRxd就可以马上准备去接收新数据了。(2)串口数据写入函数UartWrite,它把数据指针buf指向的数据块连续地由串口发送出去。虽然串口程序启用了中断,但这里的发送功能却没有在中断中完成,而是仍然靠查询发送中断标志flagTxd(因中断函数内必须清零TI,否则中断会重复进入执行,所以另置了一个flagTxd来代替TI)来完成,当然也可以采用先把发送数据复制到一个缓冲区中,然后再在中断中把缓冲区数据发送出去的方式,但这样一是要耗费额外的内存,二是使程序更复杂。这里也还是想告诉大家,简单方式可以解决的问题就不要搞得更复杂。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

main函数和主循环的结构已经做过很多了,这里重点把串口接收数据的具体解析方法给大家分析一下,这种用法具有很强的普通性,掌握并灵活运用它可以使将来的开发工作事半功倍。
首先来看CmpMemory函数,这个函数很简单,就是比较两段内存数据,通常都是数组中的数据,函数接收两段数据的指针,然后逐个字节比较-------if(* ptrl++ != *ptr2++),这行代码既完成了两个指针指向的数据的比较,又在比较完成后把两个指针都各自+1,从这里是不是也能领略到一点C语言的简洁高效的魅力呢。这个函数的用处自然就是用来比较接收到的数据和事先放在程序里的命令字符串是否相同,从而找出相符的命令。
接下来是UartAction函数对接收数据的解析和处理方法,先把接收的数据与所支持的命令字符串逐条比较,这个比较中首先要确保接收的长度大于命令字符串的长度,然后再用上述的CmpMemory函数逐字节比较,如果比较相同就立即退出循环,不同则继续对比下一条命令。当找到相符的命令字符串时,最终i的值就是该命令在其列表中的索引位置,当遍历完命令列表都没有找到相符的命令时,最终i的值将等于命令总数,那么接下来就用switch语句根据i的值来执行具体的动作,这个就不需要再详细说明了。
在这里插入图片描述
在这里插入图片描述

	液晶文件与上一个例子的液晶文件基本是一样的,唯一的区别是删掉了一个本例中用不到的全屏清屏函数,其实留着这个函数也没有关系,只是Keil会提示一个警告,告诉你有未被调用的函数,可以不理会它。经过这几个多文件工程的练习,大家是否发现,在采用多文件模块化编程后,不光是某些函数,甚至整个c文件,如有需要都可以直接复制到其他的新工程中使用,非常方便功能程序的移植,这样随着实践积累的增加,工作效率就会越来越高。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部