单片机学习中的常见问题
在单片机学习中,我们常常遇到这样那样的零星问题,这里我将我遇到的问题总结如下,都是个人见解,如果不对,万望指出。(持续更新中)
问题0001:51单片机下载失败常见原因
1.查看单片机是否插反了;
2.下载软件选择正确的单片机型号(注意STC89C52和STC89C52RC是不同的) ;
3.检测是否连接了晶振 ,时候有晶振接触不良的现象 ;
4.查看电脑com是否有多个,是否选错了com口 (是否安装了相应的驱动);
5.单片机开发板是否重新上电了(下载单片机程序需要冷启动过程) ;
6.注意是否有短路(比如开发板下面最好铺一张纸,不要被短路) ;
7.下载软件用的什么版本,旧版本可能需要设置最低、最高波特率,并且还会出现很多奇怪的现象,比如第一次烧写用一个波特率,第二次就不行了,但是有时候却一直可以(个人经验)。新版本不用设置,并且不用等待软件提醒上电才可以上电,点击下载后,直接重新上电即可,建议尽量使用新版 ;
8.如果使用的是CH340/CH341或者PL2302,检测RXD/TXD是否连接反了。(注意PL2302,驱动,新版的驱动只支持正版芯片,这个一定要注意,很多人会沿用老版本驱动,不建议用PL2302)
9.换一个单片机试试看,验证单片机是否已损坏。
10.一部分人修改上一次工程代码,然后直接编译,下载无效果。可能是新的代码编译有错(编译有三个按钮,请注意区别差异),导致目标文件未生成,hex文件未更新,所以下载的还是上一次的程序。这里要注意。
11.STC-ISP软件选择hex文件后,要勾“每次下载前都重新装置文件”,这样有三大好处:(1)免去每次编译都重新手动装在hex文件(2)避免因忘记手动重新装载hex文件,而导致下载的hex文件还是上一次的hex文件,代码下载后现象和上一次一样,误以为还是程序问题(3)方便快速调试代码。
12.连接线可以出现问题,比如接触不良,线头脱断等。
13.芯片“锁死”。STC-ISP软件设置问题,上一次下载程序的时候,在STC-ISP可能勾选了“下次冷启动时,P1.0 / P1.1为0/0才可以下载程序” , 这种情况,试着将此两个IO拉低,下载程序试试看。
(其他原因后续更新中-------2016年2月2日最后更新此问题)
问题0002:学习单片机我们都需要什么软件
最近看到群里面有很多人为了学习单片机,下载了STC-ISP,然后又下载了串口软件、单片机小精灵、定时器计算神器之类的,可谓五花八门,但是实际我们并不需要那么多的软件。
大家注意到STC-ISP软件已经附带了很多功能了,学会使用这些功能可以让我们事半功倍。另外,STC手册上有C语言和汇编语言程序,值得参考。下面在分析几款常用软件给大家:
keil4 : 点击打开链接 (附带我下载的两个仿真插件,可以做很多仿真)
STC-ISP 下载方式:进入www.stcmcu.comwww.stcmcu.com 然后CTRL+F搜索STC-ISP即可。
BCompare : 点击打开链接 (快速比较文件差异,便于对比程序)
Proteus7.5 仿真:点击打开链接 (文件中包含我录制的破解过程,目前我的系统为W8.1暂时未发现使用问题)
Multisim 仿真 :点击打开链接
Source Insight : 点击打开链接(适合编写代码,非常方便)
SecureCRT7 :点击打开链接 (免破解)
Altium Designer6.9 : 点击打开链接
Altium Designer14.2.3 : 点击打开链接
屏幕录制专家: 点击打开链接 (有时候交流沟通,录制视频更加简单直接)
另外,单片机采用C语言编程的学生,有两本书必须要度:
华为技术有限公司c语言编程规范.pdf :点击打开链接
高质量C编程指南—林锐.pdf : 点击打开链接
问题0003:51类单片机需要冷启动,但是一定要手动重启开关一次吗
51类单片机下载程序时必须冷启动,这是大家都知道的。昨天,一个学生告诉我他是用的普中科技开发板,他用普中科技的下载软件去下载程序,发现不是很好用。我告诉他STC-ISP下载软件非常好用,他说STC-ISP软件必须经过手动重启开关完成下载,而普中科技的开发板,可以完成全自动下载,只需点击“下载”按钮即可,无需手动重启开关,并且普中科技技术支持告诉他,只能使用他们的软件才能够完成全自动下载(我对此只能呵呵)。本人也是采用普中科技开发板,普中科技的自动下载功能确实存在,但是我使用过发现并不是很好用,而且需要结合它的开发板硬件才行。这里介绍一下我使用过的两种“全自动”下载(可能方法还有很多,需要广大网友的补充)
(1)硬件法:利用STC自动下载器,点击下载后,模块会自动重新上电,完成下载任务,需要淘宝购买,约9~13元。
(2)软件法:利用串口结合IAP功能,点击下载,模块自动完成下载,无需手动重启。(版本古老的芯片无此功能)此方法可参考我的笔记:stc单片机“全自动下载”(程序版) http://blog.csdn.NET/yagnruinihao/article/details/21739665
问题0004:变量的定义与程序的执行
为了简化问题,这里把程序简化为2行(先不要看变量i是否有意义)
在C语言方面,这是可以的(尽管这样不是很规范),但是在keil4中,却出现了问题,网上百度,说可能是有中文字符,但是我将程序删除后,编译没有错误,切换输入法,再次输入图片2的内容,出错,将int i=0 ; 剪切到第一行,错误消失。还有一个可能是软件出错(经过测试,排除可能性)。
所以,在单片机编程中,最好将变量的定义放在执行语句的前面。
问题0005:程序中的“死循环等待”正确吗?
在单片机程序中,有时候会需要“死循环等待”,也就是等到某一条件我们才需要执行下面的操作。这里以温度传感器DS18B20为例,主机首先需要发送一个存在检测脉冲,如果DS18B20存在,则它会以60us至240us的电平来回应主机,这里就会需要一个“死循环等待”,我们必须等到DS18B20“回应完成”,这时候主机才能继续发送指令到DS18B20中去,这时候很多人会采用这样的方式:(首先假设sbit ds18b20_io_bit = P3^7 ;)
则,while( !ds18b20_io_bit) ;
这样的代码其实是有问题的,在未连接DS18B20或者接触不良时,DS18B20并不能回应主机,这样的代码就会有问题,尤其在大工程中,所以,为了安全,我通常的做法是:
第一步:
while( (!ds18b20_io_bit) ;
P1=0x01 ; //led
如果,P1口的led有变化,则表示我们“程序在逻辑上和时序上没有问题”
第二步:
将上面的代码修改为:
unsigned long i=0
while( ( (!ds18b20_io_bit) && (i++)) ;
然后打印 i ,
因为i一般都很小,所以这时候我一般将unsigned long 修改为unsigned char 类型,然后最后修改为这样
while( (!ds18b20_io_bit)&& (i++ <250)) ;
注意这里的250不是随意写的,是经过测试的,经过测试,我读出的i值远小于250,我才这样做的,如果在其他类似的地方,打印出来i是其他较大的值,那就重新定义i的类型为unsigned int 或者unsigned long ,根据具体情况而定。
上面的修改似乎意义不大,但是在大工程中,这样不会因为DS18B20的“不存在”而导致整个程序“死循环等待”。
问题0006:指针能否指向特殊功能寄存器
特殊功能寄存器的寻址方式只有直接寻址和位寻址。没有间接寻址方式。而指针却是间接寻址方式,不能通过指向特殊功能寄存器的指针来操作特殊功能寄存器。
例:利用指向0x90的指针来操作P1口的LCD灯(高电平时LCD点亮)(P1口的地址是0x90,但是属于特殊功能寄存器区)
#include
#include "./stc/stc12c5a60s2.h"
void main(void)
{
unsigned char *p=0x90;
while(1)
{
*p=0x55;/*从这里看,似乎应该是LCD每隔一个被点亮,但是事实却并非这样*/
}
}
问题0007:“判忙” 函数重要吗
在某些芯片操作时,会有一些引脚或者寄存器或地址的数据被用来表明当前芯片处于的状态为忙碌状态或者空闲状态,如果忙碌,单片机需要用轮询的方式等待直到芯片空闲下来才继续发送指令或数据。这里以1602液晶屏为例:
一个朋友用ST89C52操作1602液晶屏,操作成功,后来换了芯片STC12C5A60S2,同样的程序,显示出来的却是“乱码”,后来经过测试,程序部门似乎没有问题,于是,我在LCD1602的“写命令”函数和“写数据”函数中
加入了延时(只是为了快速测试),下载后,两个芯片都运行正常。其实对于LCD1602,一般都不需要“判忙”,它运行时比较快的,但是如果将一个没有“判忙”的1602液晶屏模块封装起来,在调试一个大工程时,这时候
因为换了芯片,导致整体效果错误,这就是增加了调试难度。比如在利用LCD1602学习使用时钟芯片DS1302时,如果显示乱码,这时候,因为你的LCD1602是实验的基础环节,肯定是你之前就已经封装好了的,这时候
你肯定会怀疑DS1302环节操作出错了,这就增加了调试的难度和时间。所以,“判忙”函数必不可少。这里,又有一个技巧,就是防止程序“卡死”,含义与本文中的问题0002类似。
起初LCD1602的核心操作为:
[cpp] view plain copy
do{
;
}while( (lcd1602ReadStatus()) & 0x80));
后来我变为
[cpp] view plain copy
unsigned long i=0 ;
do{
i++;
}while( (lcd1602ReadStatus()) & 0x80);
UartSendValue("i = " ,i) ;/*利用串口打印出i的值*/
打印出i的值为 “1”(基于芯片STC12C5A60S1,11.0592MHZ,其他型号的单品机可能打印出来的效果不同,甚至同样的型号单片机、同样的晶振、同样型号的1602 操作打印出来的值也是不一样的),又变化为:
[cpp] view plain copy
unsigned char i=0 ;/*同时,需要注意这里,修改了类型*/
do{
i++;
}while( ((lcd1602ReadStatus()) & 0x80) && (i<10)); /*经过验证,10已经可以满足足够的时间了*/
这样做才是安全的。(尤其在大系统中,不会因为1602的”不存在“导致整个系统的运行失败,经典案例如下)
(在2015年2月7日,我帮助一个大四学生调试毕设时,也发现了同样的问题,此同学在1602液晶屏的写命令、写数据函数里面加入了判忙函数,判忙核心代码如下:
[cpp] view plain copy
do{
;
}while( (lcd1602ReadStatus()) & 0x80));
,最后因在系统设计时,暂时用了1602显示,后来为了做上位机,就用了串口通信代替1602, 拔掉了1602液晶屏,但是没有屏蔽掉主函数中关于1602的操作,导致了程序卡死在判忙函数中,因为如果没有硬件连接,通过数据口读取到的1602状态数据是0xff,然后0xff&0x80,这样得到的结果永远是1(除非再次插上液晶屏或者手动将数据最高bit对应的IO口拉低),导致程序卡死在这里,而且这些功能都是封装好的,所以就更加加大了难度,导致一周没有调试出来,后来,我给他指出两种方法(1)在主函数中屏蔽所有关于1602液晶屏的函数,实验后,系统运行正常(2)修改判忙函数,因为一劳永逸,试验后,运行正常,即使主函数中没有屏蔽1602相关函数,1602硬件暂时取下,也是可以成功运行的)
另外,还有很多同学运用了延时的方法去操作,似乎大部分人都得到了满意的结果,那么,这里有出现了两个问题:(1)延时的结果有三种:延时太小、延时正好、延时太大,对于大家来说,延时正好、延时太大都是可以完成系统的设计的,但是,你又如何保证你的延时不会小呢?大部分人想,这非常简单,把延时设计的非常大就好了,那么,就引出了问题(2)延时太大,不利于系统整体设计,导致了效率太低 。 何去何从,大家可以想一想。
问题0008:调试程序之“穷途末路”
有时候,在调试程序时,明明都是严格按照datasheet的说明和时序图编写的代码,就是没有效果,然后借鉴别人的代码,代码相同,但还是没有效果 。 这时候,先要怀疑硬件,如果硬件没有问题,有可能还会有一个原因,那就是单片机的问题(或者是keil软件的问题,这个不得而知了)。我之前遇到过编写一个代码,没有效果,最后把全部代码屏蔽掉,只在main函数中,点亮了LED,但还是失败了,后来,复制代码,重新新建工程,粘贴代码,就好了。(这里不是喷STC,其实STC在国内做的较好,但是总会有一些奇葩的时候),这里分享一下,仅个人经历。
问题0009:如何一步步测试编写代码
单片机程序测试调试似乎对于刚入门的同学有一定难度,但是入门之后我们快速调试程序呢?很多人写代码一气呵成,然后在main函数中调用10几个子函数,然后直接通过液晶屏或者其他的手段观察效果是正确,这样的方法对吗 ?以下是常用方法:
1.借助于网上的视频讲解或源代码,(对于32位的MCU尚可理解,但是51、PIC.MSP430单片机使用者如果入门后还是同样的方法,你就OUT了),但只适用于部分网上资料较多的芯片。
2.借助"逻辑分析仪"仪器去分析(这种神器我也是通过网上资料介绍,有所了解,但是并没有使用过,不过好像非常好用的),通过逻辑分析仪可以分析我们的时序是否存在问题,但是个人感觉比较适用于菜鸟和破解单片机程序的高手,对于大多数用户,没有必须花钱购买
3.利用程序本身去验证程序,编写一个函数,测试一个函数,操作简单,屡试不爽,这里以大家熟知的51类单片机STC12C5A60S2和数字温度传感器DS18B20为例(单总线协议通信)。
下图为DS18B20的“复位”时序,因为每次操作都需要MCU发送一个复位信号,所以,“复位”函数相当重要。
我们知道,当DS18B20存在时,红色部分的时间内,总线将会被DS15B20拉低,呈现低电平 ; 如果DS15B20不存在,则在单片机使用总线(蓝色)后,红色部分仍然是高电平。
假设我们有以下定义:
[cpp] view plain copy
typedef unsigned char UB8 ;
typedef unsigned short int UW16 ;
typedef unsigned long UL32 ;
typedef char SB8;
typedef short int SW16 ;
typedef long SL32 ;
#define HIGH_LEVEL 1
#define LOW_LEVEL 0
sbit ds18b20_io_bit = P1^7 ;/*根据硬件选择*/
#define DS18B20_EXISTENCE 0 /*存在DS18B20*/
#define DS18B20_NOT_EXISTENCE -1/*不存在DS18B20*/
然后编写“复位函数”
[cpp] view plain copy
/******************************************************
Function :ds18b20Init
Input :N/A
Output :N/A
Return :DS18B20_EXISTENCE或者DS18B20_NOT_EXISTENCE
Description :N/A
Note :经过测试在11.0592MHZ和12.00MHZ条件下都可以
检测到ds18b20的存在。
******************************************************/
SB8 ds18b20Init(void)
{
SB8 existenceFlag ;
UB8 i=0 ;
ds18b20_io_bit = HIGH_LEVEL ;
_nop_() ;
_nop_() ;
/*master transmit the reset pulse */
ds18b20_io_bit = LOW_LEVEL ;
delay720usForDs18b20();
/*rising edge*/
ds18b20_io_bit = HIGH_LEVEL ;
/*ds18b20 wait 15us to 60us then transmit a presence pulse by
pulling the 1-wire low for 60us to 240 us */
delay15usForDs18b20();
delay15usForDs18b20();
delay15usForDs18b20();
delay15usForDs18b20();
if( ! ds18b20_io_bit)
{/*ds18b20 eixstence*/
existenceFlag = DS18B20_EXISTENCE;
}
else
{/*no ds18b20*/
existenceFlag = DS18B20_NOT_EXISTENCE;
}
while((!ds18b20_io_bit) && (i<250))
{
i++;/*wait*//*暂时,具体见调试*/
/*经过测试,这里的250对于11.0592MHZ和12MHZ都远远足够了*/
}
return existenceFlag ;
}
然后,我们验证此函数是否可以成功完成“复位功能”:(时序不再详细分析)
[cpp] view plain copy
#include
#include "ds18b20.h"
void main(void)
{
SB8 flag = -3 ;
P1=0x00;//连接leds,高电平点亮
flag = ds18b20Init();
if( flag == DS18B20_EXISTENCE) //检测结果:存在,0
{
P1 = 0x01 ;
}
else if(flag == DS18B20_NOT_EXISTENCE)//检测结果:不存在,-1
{
P1=0x02;
}
else
{
P1 = 0x04 ;
}
while(1) ;
}
这样,我们通过LED就可以验证函数了:
(1)正常连接DS18B20,结果如果是P1的第一个灯亮了(P1=0x01),则表示单片机可以检测到DS18B20的存在,说明函数可能对了(可以检测到DS18B20的存在);
(2)拔掉DS18B20,结果如果是P1的第二个灯亮了(P1=0x02),则表示单片机未检测到DS18B20的存在,说明函数完全正确。(可以同时检测到DS18B20的存在和不存在)
注意:只有当(1)(2)都正确后,才表示此“复位”函数编写正确了。然后继续进行下面的函数编写。
同样的,对于很多芯片,我们使用51单片机驱动时,都会涉及到“读操作”和“写操作”,在编写读写函数时,通常严格按照时序编写“写”函数和“读”函数,然后向芯片的内部RAM写入一个数据(这个数据最好不是0xff,因为当芯片未连接时,单片机读出的数据就是0xff,这时候如果我们写0xff,然后读取,因为结果肯定是0xff,所以并不能证明我们的读写函数书写正确),然后读出该地址的数据,验证数据是否相同,如果相同,表明读写操作正确,但是这里同时需要注意一个问题:读/写操作周期为多少?即需要隔多长时间MCU才可以继续读/写此芯片,因为对于芯片,我们读写一次后,可能需要过一段时间才可以继续读写,如果没有注意这个问题,就可能导致:编写的代码好像没错,但是在后期我们连续对芯片进行读/写操作时,没有注意这个延时等待啊,就会出现很奇怪的问题(通常这种错误导致的结果是随机的),这时候反过来调试过程可能就繁琐了,如果连续操作间隔较长,安全的做法是:在每一次读/写操作后,都加一个延时,这样就保证肯定OK了。
问题0010:蓝牙串口模块使用的正确步骤
很多人现在都开始使用蓝口模块,利用手机蓝牙或者PC蓝牙进行控制单片机系统。但是很多人一上来就直接把蓝牙模块和单片机相连接,然后编写程序,效果实现不了,就不知该如何了。蓝牙模块很多都是结合了串口通信的,通常拿到模块,需要经过一下步骤(自己总结,可做参考):
(1)首先,需要蓝牙模块和PC的硬件连接,这里有两种方法:
把你的开发板的单片机取下来,然后从开发板引出电源线VCC和GND,还有P30、P31接口,连接蓝牙模块的四个端口(注意RXD和TXD不要反接了)
如果你有USB-TTL模块,可以直接连接蓝牙模块,无需单片机开发板转接过去。
(2)通过PC的串口软件(比如STC-ISP软件自带的串口),设置好软件的波特率和通信数据格式,通过串口软件发送AT命令(通常购买时卖家提供了),看是否有返回数据,如果没有返回,有可能是P30、P31接口需要换过来,如果发送命令,有返回信息且不是乱码,表示蓝牙模块和PC可以串口通信了。
(3)手机下载软件“蓝牙串口”,通过此软件连接蓝牙模块(蓝牙的名称和配对密码可通过 AT命令设置),然后给蓝牙模块供电,PC打开串口软件,手机发送任意字符到蓝牙模块,会发现PC串口软件接收到了信息,这表示蓝牙可以正常工作。
(4)编写单片机程序,此时需要分为两步:
先不连接蓝牙模块,编写程序,单片机接收到串口数据,做出相应动作,写好后下载,然后打开PC的串口软件,发送命令,检验现象是否正确,如果正确,开始第二步
把蓝牙模块连接到单片机,手机蓝牙连接模块,手机发送命令,此时只要步骤现象经过反复验证没有问题,第二步肯定没问题。(如果第二步不对,那就返回去做第一步)
(5)其他。如果需要用PC的蓝牙进行控制,还需要购买蓝牙适配器,也就是模块发送信息到电脑时,需要一个接收器。如果不是很必要,就用手机蓝牙软件就好了,省钱省事。
问题0011:不知道遥控器的编码协议,如何解码
不同的遥控器,其协议也不同.
要看你的目的是什么 , 分两种情况:
(1)你只是想单纯的解码出来而已 :可以查看一下你用的遥控器类型,然后百度一下试试看 , 然后结合示波器观看。
(2)你想解码出来后,用到其他设计上去 :这里有两种方法
较为严格 : 就是参考(1)的思路去实验,但是花时太多。
<2>不是很严格,但是很实用 : 先在网上看一个红外遥控器的协议,然后根据它的协议去解码,然后,查看解码的是否正确(通过原码和反码初步验证) , 然后把按下每一个按键,去记录下他的编码,然后下一设计要用到它的时候,你就把上次记录的编码,做一个数组,然后自己去定义每一个编码对应的功能即可。(因为实际上很多类型的编码,无非是引导码的时间不同,当然我们可以不关心引导码 ,还有就是0和1的定义不同,再或者就是先传输的是高位还是低位 , 就是以上三点不同---->个人理解,可能有所偏差,但是对于我们,我们可以不关心解码得到的是对的,还是不对的--->这里的“不对”是指是否和其真实的编码相同,而不是解码失败。我们只需要关注,接收到的信息是否和数组里面的一样,如果一样,就完成某种功能。 当然,这里有更加简单的方法,就是无需每次记录,用一个薄码开关,去选择学习模式和工作模式,这时候定义一个数组,然后先进入学习模式下,将接收到的信息放在数组中,然后进入工作模式,工作模式下只是对比此次接受的编码和数组当中的编码,如果相同,则完成一定的任务就可以了)
问题0012:Proteus仿真的那点事
越来越多的人,问我以下问题
(1)为什么我的程序在Proteus中可以运行,但在实物上却运行失败。
(2)为什么我的程序在实物上可以运行,但是Proteus中却运行失败。
现在在这里做统一解答 : (1)Proteus仿真和实物有差异,在Proteus中成功的代码不一定可以用于实物,反过来,实物上已经成功的代码也不一定可以用于Proteus。如果出现了一方成功,一方失败,那么很大的就是延时。(当然不排除其他可能性,但这个延时问题可能最大)
(2)如果软件仿真和硬件实验,只有一方可以成功,而另一方失败。 那么表明你的逻辑上没有错误。参照第一条,稍做修改即可 。
问题0013:程序的严格性
程序的严格性似乎总是被很多人遗忘,在编写程序时,一定要把隐患消灭掉,否则后患无穷。这里以1602液晶屏为例,希望可以作为一个切入点,但是要通过问题表象,看到问题本质,而不仅仅局限于1602液晶屏,思想更加重要。大家都知道1602液晶屏的数据口是8位数据口,但是实际上还有一种模式,就是4位数据口,实质上很简单,就是利用4位数据口传送数据,传送两次而已,其他无差别,但这不是这里讨论的关键。下面看一下网友的代码(只放关键部分):
[cpp] view plain copy
#define DataPort P0
void LCD_Write_Data(unsigned char Data)
{
//while(LCD_Check_Busy()); //忙则等待
DelayMs(5);
RS_SET;
RW_CLR;
EN_SET;
DataPort= Data;
_nop_();
EN_CLR;
EN_SET;
DataPort= Data<<4;
_nop_();
EN_CLR;
}
其中,我们之关心两句话,就是DataPort= Data; 和DataPort= Data<<4; 这两句话什么意思呢 ? 就是通过P0端口的高4个Bit,把数据传送两次,使用的是P04,P05,P06,P07。这里的实验网友成功了,他认为这个代码没有问题,但是这里的隐患确实非常明显的,如果不该掉,可能导致这样的情况发生:在1602的液晶屏4位数据口模式的独立实验中成功了,但是在一个复杂的系统设计中,却出现了很多意料之外的情况。实际上,问题较为突出,比如第一句DataPort= Data ; 从上面我们看出,对于1602而言,他只关心和他的4位数据口连接的 P04,P05,P06,P07的状态,但是上面的复制,却影响到了P00,P01,P02,P03,这里明显不行的,比如P00,P01,P02,P03连接了LED或者其他设备,这时候会影响到他们的运行。所以我的建议是,修改为:
[cpp] view plain copy
第一次传送:
DataPort &= 0x0f ; /*P0高4位清零,准备传送数据*/
DataPort |= (temp & 0xf0) ;/*P0传送数据temp的高4位,P0的低4位不受影响*/
第二次传送:
DataPort &= 0x0f ; /*P0高4位清零,准备传送数据*/
DataPort |= ((temp & 0x0f)<<4) ;/*P0传送数据temp的低4位,P0的低4位不受影响*/
这里,希望新手可以通过这表象,看到问题的本质,将隐患扼杀在摇篮,否则简单的1602的4位数据口模式,会成为你调试复杂系统调试的噩梦。
问题0014:液晶如何显示小数并且小数自动四舍五入
液晶显示数据的小小技巧:(主要针对液晶显示浮点型数据)
我们在使用液晶屏的时候,无论是1602/12864、NOKIA、J2004A还是其他的,常常遇到需要显示一个变量(尤其是小数),那么显示变量又有一些小小的技巧,这里把我的一些使用方法,给新手介绍一下,欢迎新人学习,更加欢迎高手拍砖(但不能总是拍,得让我知道为什么被拍)
(1)首先,我们需要先做一些预备知识:如何进行四舍五入:
[cpp] view plain copy
float temp = 2.376 ;
我们需要保留两位小数,也就是2.38(四舍五入后的结果)
在程序中,我们只需要一句话:temp = (unsigned int)((temp*100)+0.5) / 100 ;就完成了这个功能,得到的结果就是2.38(不信可以试试看额)
(2)液晶如何显示小数
使用以下代码:
[cpp] view plain copy
#include
#include
#include "lcd1602.h"
void main(void)
{
float a = 182.376 ;
lcd1602Init();
lcd1602WriteCommand(0X0C);
/*显示原来的数字,非重点*/
lcd1602AddressWriteByte(LCD1602_ROW0,0,((unsigned long)a)%1000/100+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,1,((unsigned long)a)%100/10+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,2,((unsigned long)a)%10+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,3,'.') ;
lcd1602AddressWriteByte(LCD1602_ROW0,4,(unsigned long)(a*10)%10+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,5,(unsigned long )(a*100)%10+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,6,(unsigned long )(a*1000)%10+'0') ;
lcd1602AddressWriteByte(LCD1602_ROW0,8,0x7e) ;
/*重点部分*/
/*显示,百位这里我一直最高是百位,所以没有判断更加位,实际操作时,应该添加其他机制,使其更加智能*/
lcd1602AddressWriteByte(LCD1602_ROW0,10,((unsigned long)a)%1000/100+'0') ;
/*显示十位*/
lcd1602AddressWriteByte(LCD1602_ROW0,11,((unsigned long)a)%100/10+'0') ;
/*显示各位*/
lcd1602AddressWriteByte(LCD1602_ROW0,12,((unsigned long)a)%10+'0') ;
/*显示小数点*/
lcd1602AddressWriteByte(LCD1602_ROW0,13,'.') ;
/*显示小数点后第一位*/
lcd1602AddressWriteByte(LCD1602_ROW0,14,(unsigned long)(a*10)%10+'0') ;
/*显示小数点后第二位,此时需要四舍五入计算*/
lcd1602AddressWriteByte(LCD1602_ROW0,15,(unsigned long )(a*100+0.5)%10+'0') ;
lcd1602AddressWriteString(LCD1602_ROW1,0,"QQ:279729201") ;
while(1);
}
下载程序后效果如下图所示:
如果此时修改数据temp为182.372,则显示为下图:
发现液晶不仅显示了数据,而且有了四舍五入的心结果。实际上,这里为了实验效果,部分代码并没有做很好的处理,主要是为了凸显出重点,网友们可以在我的基础上不断修改,希望大家可以通过点,看到面,不要局限于这一个方式。
编辑:admin 最后修改时间:2018-05-18