本帖最后由 7788281 于 2012-10-27 22:39 编辑
1.自制单片机之一……想法与计划
学习了一段时间的51单片机,也买了学习板,但总觉得在学习板上操作对元件引脚认识不那么深刻。学习板上的许多功能块元件密集,相互又牵扯,造成电路认识上的不清。在接扩展板时引线也不能集中成一组排线引出。商家也对他的原理图遮遮掩掩说一半留一半的。因此我打算拿起电烙铁,在通用板上先搭建起最小系统,再做出ISP下载线,一步步的与愿和我一同上路的菜鸟们踏入C51的世界。
昨天下午才汇了钱买洞洞板,没想到今天中午快递公司的人就把货送到我手上了。快的有点让我吃惊。等元件也到货后我便可以动手并上图与大家一同学习讨论了。
今天刚申请的空间,也试试发贴和上图。
![]()
2.自制单片机之二------AT89S51最小系统制做废话这里就不多说了,干活吧!
C51最小系统电路在网上一搜一大把,大同小异。我略做改动后如图:
加一个11.0592MHZ的晶振是为了以后做串口通信时和PC有相同的波特率。可用短路帽切换。
说说板子的布局:网上卖的最小系统都是把板子做的非常小,如果仅仅只学习最小系统,那是可以的。我以后是要做扩展的,所以不能做的太小。因此该有的应留着,不该有的都去掉。很重要的是以后在做其它扩展功能块时的连接线就用一组排线而不能再有其它飞线。因此做一个好的布局是非常有必要的。
在板上除了最小系统外还有键盘输入、数码管、LCD、I2C存储器。它们的数据接口和电源接口也是完全独立的。其它的功能都准备在以后通过上方的接口另外用板子扩展。
图上方的弯脚针就是向外扩展的接口。我在弯脚下方又安排了一组直插针。它是和弯脚插针并行连接的。这样在扩展功能块时我先在AT89S51板上将要输出的引线(包括数据线、控制线、电源线等)跳至直插针上。这样通过弯脚插针引到扩展板上的线就只有一组排线。不用再零乱地接上几组连接线了。这也是我用买的学习板常常垢病而最终弃而自制的主要原因之一。因为当时我扩展LCD12864,我既要连上数据线,又要连电源线,LCD模块上还拖着背光电源限流电阻线。还有液晶驱动电压调节电位器。所以感觉拖的到处是线,很不方便。
实施:通过检查各元件的好坏后便进行焊接工作。焊接进行的还是比较顺利。见下图:
![]() 完成了。在检查了线路的正确性后,用万用表测了是否有短路。一切正常!
之前我的AT89S51里已写好了个最小系统测试程序,就是让P1.0上的红色发光管闪烁。
# include
void main(void)
{ unsigned char i,j,k;
while(1)
{
for(i=0;i<100;i++)
{
for(j=0;j<255;j++)
for(k=0;k<255;k++);
}
P1_0= !P1_0;
}
}
我把它先插在我买的学习板上运行,一切正常。但插在我做的的最小系统上却没反应。就那么点线路查来查去还是没问题呀!是晶振不起振?在学习板上用手拿着改锥分别碰18脚(XTAL2)和19脚(XTAL1)。发现当人体碰到19脚时程序会乱掉,就是说可能会停振。于是又把我的系统板上晶振位置的电路重焊一遍,尽量缩短了19脚处线的长度。C4、C5又分别换了15PF、20PF、30PF的电容试试。故障依然,没办法,得静一静,看书! 在看到介绍第31脚(EA/VPP)时猛然想到31脚没有接到电源正啊!起来立即把31脚接到电源正,红色发光管立即闪烁起来。此时的高兴真不可名状啊!
总结:除了在焊接时要仔细外,先画好电路图也是很重要的。像上面的电路图在89S51的31脚上就只写了Vcc的字样,因此焊接时就被忽视了。应该先画出一张完整的图才行。
检查分几个部分:1.晶振部分、2.复位部分、3.电源部分(最不可以忘掉EA接V+5v),4.还有就是P1_0发光管最基本的一个应用部分。
其实这个板子上的晶振电路是非常容易起振的。19脚上的引线也没那么敏感。但处理19脚时可能的话还是要尽量短些的,毕竟它比18脚要对外界干扰敏感的多。手摸上去就停振了。
还有就是学习单片机怎么着也得先买块最最简单的板子。否则你焊好你的板子后有故障到底是程序问题还是线路问题就无从查起,查错查得还不疯掉呀! 程序也没法写进片子里去。你可能会说再做一个ISP下载线呀。做下载线更加不稳定,下一篇我会专门讲做ISP下载线的问题。再来个特写鼓励一下自己。
![]()
3.自制单片机之三-----AT89S51ISP下载线的制做最小系统板做好了,接下来就是做根ISP下载线了。否则程序怎么写到AT89S51芯片里呢?
先来认识一下AT89S51上ISP(在线编程)功能脚的定义
![]()
看上图的左边AT89S51引脚图的P1.5、P1.6、P1.7的第二功能分别为MOSI(主机发送从机接收)、MISO(主机接收从机发送)、SCK(时钟脉冲信号由主机发送)。那什么时候才能启用第二功能呢?就是当复位脚RST接高电平一直处于复位状态时就可用第二功能了,所以在ISP下载板上有一条线接至AT89S51的第9脚(RST)上,就是在写程序前先发一个高电平将S51的RST脚设为复位态,然后就可通过MOSI、MISO向S51内写程序了。
有网有问我的板子上ISP线是如何定义的,我上图的右边就是我这个板子上的接法。 ISP十针接口的定义如下图
![]()
看见上图的实物接口边缘上的三角标记了吗?这就是第1脚的标记,它的定义如上图的右边示意图。
在网上查了一下,ISP下载线的种类主要取决于PC端下载程序的种类。有并口的,有串口的,也有USB的。串口和USB的介绍较少而并口的介绍的很多,也比较简单。易于自己制做。并口的在网上也分为几类,原理都一样。主要是根据下载程序的不同。
1.这是Easy Isp-2 的配合软件为Easy 51Pro v2.0宇宙版
这是他的简化版:
在网上的制做思路几乎都是把74HC373放在并口头的小盒子内见下图:
但由于我的台式机放在桌子的下面,把74HC373放在接头盒内在电脑机箱后面怎么调试呢。因此我没将它放在接头盒内而是另用个洞洞板做的,前面是一米的并行线,后面是约50cm的连接线。线路的焊接没什么问题,比较顺利。见下图:(因旧的已拆了,现在只是示意一下)
Easy 51Pro 2.0的工作界面:
连上我的最小系统后,发现不能稳定工作。但可以读出89S51的特征字,说明线路是好的
反复试验,不断在电源间加去偶电容,没什么效果,后来发现把连接排线握成一团握在手心里,就能有80%的机率正确写入程序,跟并口线那边关系却不大。莫非我要在排线上挂块肉,就像以前黑白电视的天线?
结论:读写不可靠。放弃!
因为这个下载程序不支持win98,我的笔记本是98的不能用。因此这次我用了官方的下载线方案。配合软件为ISP-30a.
线路的原理还是差不多,焊接也没什么问题,这次可以支持笔记本,我省掉了并口线,将并口头直接焊在板子上。完工后的样子见下图。
现在它的并口端没有线了,直接在了我的笔记本的并口上,启动程序,ISP-30A界面见下图
我一不做二不休,狠狠剪短了排线,就剩下这么一点点引线,见下图:
再接上笔记本一试,你猜怎么着,正确读写率100%,成功了!
结论:各种方案都差不多,但在布线上,原来总是把元件装在并口盒内,而留一段连线到AT89S51板上的想法是错误的。应尽量把从HC373到AT89S51板上的连线做短,最好HC373就做在板子上,因为HC373是三态输出,停用时OE端是高电平,输出是高阻抗的,对系统板的独立性是没有影响的。而留的线应该是从PC机到HC373的并口线,这样的下载线作为自制来说才能有点实用价值。
第二天我把那段并口线又再连上后,接上笔记本再试,读写正确率仍为100%。下载线见图
样子虽不好看,但它是个皮实且易于自制的方案,在论坛里有人跟我说,把下载板上元件做好布线和抗干扰也能把下载板至AT89S51板的引线做到1.5米。我非常相信。但我没有示波器及其它测量设备,只有一块万用表。有简单易行的方案为什么不用呢
4.自制单片机之四----数码管电路的制做与驱动数码管的使用方法与发光二极管没什么区别,只是把七或八只发光二极管组合在一个模件上组成了个8字和小数点,用以显示数字。为了减少管脚,把各个发光管的其中同一个极接在一起作为共用点,因此就产生了共阳极和共阴极数码之说。共阳管就是把各个发光管的正极接在一起,而共阴管就刚好相反。见下图:
一般来说大部分的逻辑IC的吸收电流要强于输出电流。因此,大家都爱使用共阴极的数码管,因为可选的IC多些。很可惜,我的这组数码管是共阳的,因此公共端我打算用三级管来驱动。 我的最小系统板: 我用最常用的S9012,首先我得计划好电路方式,就采用最常用的动态扫描显示。先搭建最简电路,调试出需采用元件的参数。 先不接上图的R2和74HC244,将数码管一个段直接接地。调节R1,测得S9012基极电流为0.21mA时集电极也就是数码管上已有40mA,说明放大倍数足够了。这时接上R2和74HC244,调节R2使数码管电流控制在15mA,这样当8个段一起点亮时三极管上得通过120mA的电流。而基极上需要0.63mA,为了减小三极管的负荷应使三极管过饱和,,调节R1使基极电流为2mA,此时测得集电极和漏极之间的电压约0.1V。好!此时R1为2K。R2为240欧姆。确定。 接下来就是确定电路。电路的接口与AT89S51间有三组接口:段码、位码和电源。为了让AT89S51独立出来这三级接口都采用插针做接口,用排线自由连接到AT89S51的P1-P3口,电源用短路帽连接,完成后的板子见下图 反面: 说明: 然后就是写程序。先写个查询方式的吧!
//六位管码管在以0.3秒的间隔在闪烁,这是采用查询方式的,比较占CUP资源
/********************************************************************
定义管脚:P2_0-------上横 a P3_0-------个位
P2_1-----右上竖 b P3_1-------十位
P2_2-----右下竖 c P3_2-------百位
P2_3----- 下横 d P3_3-------千位
P2_4-----左下竖 e P3_4-------万位
P2_5-----左上竖 f P3_5-------十万位
P2_6-----中间横 g
P2_7-----小数点 H
*********************************************************************/
# include
typedef unsigned char uchar;
uchar code bit_num[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf};//位码值表:0,1,2,3,4,5
uchar code meg_val[]={0x03,0x9f,0x25,0x0d,0x99,0x49};//段码值表:0,1,2,3,4,5
uchar code hello[]={0x03,0xe3,0xe3,0x61,0x91,0xff}; //HELLO
uchar code beybey[]={0x89,0x61,0xc1,0x89,0x61,0xc1};//beybey
uchar code ab6789[]={0xc1,0x11,0x09,0x01,0x1f,0x41};//ab6789
void delay(int n);
void main(void)
{
uchar i,m;
P2=0xff; //先将段码关闭
P3=0xff; //将位码关闭
delay(20);//等待一会
while(1)
{
for (m=30;m>0;m--) //显示30次约0.3秒
{
for(i=0;i<=5;i++)
{
P2=0xff;
P3=bit_num; //输出位码到P3口
P2=ab6789; //输出段码到P2口
delay(5);
}
}
P2=0xff; //关闭段码
P3=0xff; //关闭位码
delay(1000); //等待0.3秒
}
}void delay(int n) //子程序
{
int j;
uchar k;
for(j=0;j
{
for(k=255;k>0;k--);
}
}
======================================
当我插把程序写入片子,插上电运行时,是乱码。你猜怎么回事?
原来那个P2口方向是反的,您注意过没有,在AT89S51管脚排列上,P0--P1和P3都是上方为PX_0。而唯独P2口管脚排列是下方为P2_0。方向则好是反的。既然反了,我就把段码表重写一下。再试,一切正常。
在这里我说一下段码的排列,好多人问数码管段码是如何排列的,我也在网上查了,好像没有什么标准的排法,随自己的接法而定,这也是导致为什么在网上下载的一些数码管程序在自己的板子上不能正常显示的原因。就普遍而言我最上面的那张图示的标法最多,在上面程序里原打算也是P2_0对应段码a(也就是上面的横)。一直到P2_7对应段为h(就是小数点)。结果哪知道P2口刚好是反的。这样一来也就是倒过来了,P2_0对应段h(小数点了)。例如我原先定义的数码管显示“2”段码为10100100B的,一接反了就不再是“2”了。而要想再显示“2”那就把段码的高低位倒过来。改为00100101B就OK了。 下面再写个用中断来显示的://这是采用中断方式的,也是带闪烁的。
/********************************************************************
定义管脚:P2_0------小数点 P3_0------个位
P2_1------中横 P3_1------十位
P2_2------左上竖 P3_2------百位
P2_3------左下竖 P3_3------千位
P2_4------下横 P3_4------万位
P2_5------右下竖 P3_5------十万位
P2_6------右上竖
P2_7------上横
*********************************************************************/
# include
typedef unsigned char uchar;
uchar code bit_num[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf};//位码:0,1,2,3,4,5
uchar code meg_val[]={0x49,0x99,0x0d,0x25,0x9f,0x03};//段码:0,1,2,3,4,5
uchar i,aa; //定义全局变量
bit fg; //定义一个亮起和熄灭标志void timer0(void) interrupt 1 using 1 //中断程序
{
if (fg) //当fg为1时点亮6位数码管
{ P2=0xff;
if (i>=6)
{
i=0;
}
else
{
P3=bit_num; //输出位码到P3口
P2=meg_val; //输出段码到P2口
i++;
}
}
else //当fg为0时熄灭数码管
{
if(aa==0)
{
P3=0xff;
P2=0xff;
}
}
aa++;
if (aa>=254) //当aa值累加至254时fg标志翻转。
{
fg=~fg;
aa=0;
}
TH0=0xf8; //重装定时器初值,2ms,值为65536-2000
TL0=0x30;
}
void main(void)
{
P2=0xff; //先将段码关闭
P3=0xff; //将位码关闭
TMOD=0x01;//设置T0为模式1
TH0=0xf8; //装入计数初值高位
TL0=0x30; //装入计数初值低位
EA=1; //总充许
ET0=1; //T0充许
fg=1; //将亮、灭标志设置为亮
TR0=1; //启动中断
while(1);
}OK!
5.自制单片机之五(1)……LCD1602的驱动LCD1602已很普遍了,具体介绍我就不多说了,市面上字符液晶绝大多数是基于HD44780液晶芯片的,控制原理是完全相同的,因此HD44780写的控制程序可以很方便地应用于市面上大部分的字符型液晶。字符型LCD通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线VCC(15脚)和地线GND(16脚),其控制原理与14脚的LCD完全一样,定义如下表所示:
字符型LCD的引脚定义
HD44780内置了DDRAM、CGROM和CGRAM。
DDRAM就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,其地址和屏幕的对应关系如下表:
也就是说想要在LCD1602屏幕的第一行第一列显示一个"A"字,就要向DDRAM的00H地址写入“A”字的代码就行了。但具体的写入是要按LCD模块的指令格式来进行的,后面我会说到的。那么一行可有40个地址呀?是的,在1602中我们就用前16个就行了。第二行也一样用前16个地址。对应如下:
DDRAM地址与显示位置的对应关系
我们知道文本文件中每一个字符都是用一个字节的代码记录的。一个汉字是用两个字节的代码记录。在PC上我们只要打开文本文件就能在屏幕上看到对应的字符是因为在操作系统里和BIOS里都固化有字符字模。什么是字模?就代表了是在点阵屏幕上点亮和熄灭的信息数据。例如“A”
字的字模:
01110 ○■■■○
10001 ■○○○■
10001 ■○○○■
10001 ■○○○■
11111 ■■■■■
10001 ■○○○■
10001 ■○○○■
上图左边的数据就是字模数据,右边就是将左边数据用“○”代表0,用“■”代表1。看出是个“A”字了吗?在文本文件中“A”字的代码是41H,PC收到41H的代码后就去字模文件中将代表A字的这一组数据送到显卡去点亮屏幕上相应的点,你就看到“A”这个字了。
刚才我说了想要在LCD1602屏幕的第一行第一列显示一个"A"字,就要向DDRAM的00H地址写入“A”字的代码41H就行了,可41H这一个字节的代码如何才能让LCD模块在屏幕的阵点上显示“A”字呢?同样,在LCD模块上也固化了字模存储器,这就是CGROM和CGRAM。HD44780内置了192个常用字符的字模,存于字符产生器CGROM(Character Generator ROM)中,另外还有8个允许用户自定义的字符产生RAM,称为CGRAM(Character Generator RAM)。下图说明了CGROM和CGRAM与字符的对应关系。
从上图可以看出,“A”字的对应上面高位代码为0100,对应左边低位代码为0001,合起来就是01000001,也就是41H。可见它的代码与我们PC中的字符代码是基本一致的。因此我们在向DDRAM写C51字符代码程序时甚至可以直接用P1='A'这样的方法。PC在编译时就把“A”先转为41H代码了。
字符代码0x00~0x0F为用户自定义的字符图形RAM(对于5X8点阵的字符,可以存放8组,5X10点阵的字符,存放4组),就是CGRAM了。后面我会详细说的。
0x20~0x7F为标准的ASCII码,0xA0~0xFF为日文字符和希腊文字符,其余字符码(0x10~0x1F及0x80~0x9F)没有定义。
那么如何对DDRAM的内容和地址进行具体操作呢,下面先说说HD44780的指令集及其设置说明,请浏览该指令集,并找出对DDRAM的内容和地址进行操作的指令。
共11条指令:
1.清屏指令
功能:<1> 清除液晶显示器,即将DDRAM的内容全部填入"空白"的ASCII码20H;
<2> 光标归位,即将光标撤回液晶显示屏的左上方;
<3> 将地址计数器(AC)的值设为0。
2.光标归位指令
功能:<1> 把光标撤回到显示器的左上方;
<2> 把地址计数器(AC)的值设置为0;
<3> 保持DDRAM的内容不变。
3.进入模式设置指令
功能:设定每次定入1位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。参数设定的
情况如下所示:
位名 设置
I/D 0=写入新数据后光标左移 1=写入新数据后光标右移
S 0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移1个字符
4.显示开关控制指令
功能:控制显示器开/关、光标显示/关闭以及光标是否闪烁。参数设定的情况如下:
位名 设置
D 0=显示功能关 1=显示功能开
C 0=无光标 1=有光标
B 0=光标闪烁 1=光标不闪烁
5.设定显示屏或光标移动方向指令
功能:使光标移位或使整个显示屏幕移位。参数设定的情况如下:
S/C R/L 设定情况
0 0 光标左移1格,且AC值减1
0 1 光标右移1格,且AC值加1
1 0 显示器上字符全部左移一格,但光标不动
1 1 显示器上字符全部右移一格,但光标不动
6.功能设定指令
功能:设定数据总线位数、显示的行数及字型。参数设定的情况如下:
位名 设置
DL 0=数据总线为4位 1=数据总线为8位
N 0=显示1行 1=显示2行
F 0=5×7点阵/每字符 1=5×10点阵/每字符
7.设定CGRAM地址指令
功能:设定下一个要存入数据的CGRAM的地址。
8.设定DDRAM地址指令
![]()
功能:设定下一个要存入数据的CGRAM的地址。
9.读取忙信号或AC地址指令
功能:<1> 读取忙碌信号BF的内容,BF=1表示液晶显示器忙,暂时无法接收单片机送来的数据或指令;
当BF=0时,液晶显示器可以接收单片机送来的数据或指令;
<2> 读取地址计数器(AC)的内容。
10.数据写入DDRAM或CGRAM指令一览
功能:<1> 将字符码写入DDRAM,以使液晶显示屏显示出相对应的字符;
<2> 将使用者自己设计的图形存入CGRAM。
11.从CGRAM或DDRAM读出数据的指令一览
功能:读取DDRAM或CGRAM中的内容。
基本操作时序:
读状态 输入:RS=L,RW=H,E=H 输出:DB0~DB7=状态字
写指令 输入:RS=L,RW=L,E=下降沿脉冲,DB0~DB7=指令码 输出:无
读数据 输入:RS=H,RW=H,E=H 输出:DB0~DB7=数据
写数据 输入:RS=H,RW=L,E=下降沿脉冲,DB0~DB7=数据 输出:无
看了那么多是不是有些晕?我也是啊,不过慢慢理解还是没问题的。
实际上面说了那么多具体怎么操作我还是没会啊?好!咱就简单点。
举个实例,就在LCD1602屏幕上第一行第一列显示个“A”字。
1.先初始化。(老大!好像上面没初始化这条指令啊!)
先别拿东西扔我,说明书上是这么说的。也就先写入些指令。
//先定义接口
# include
/*****************************************
P1------DB0~DB7 P2.0------RS
P2.1------RW
P2.2------E
*****************************************/
# define LCD_DB P1
***it LCD_RS=P2^0;
***it LCD_RW=P2^1;
***it LCD_E=P2^2;
/******定义函数****************/
# define uchar unsigned char
# define uint unsigned int
void LCD_init(void);//初始化函数
void LCD_write_command(uchar command);//写指令函数
void LCD_write_data(uchar dat);//写数据函数
void LCD_disp_char(uchar x,uchar y,uchar dat);//在某个屏幕位置上显示一个字符,X(0-16),y(1-2)
//void LCD_check_busy(void);//检查忙函数。我没用到此函数,因为通过率极低。
void delay_n40us(uint n);//延时函数
//********************************
//*******初始化函数***************
void LCD_init(void)
{
LCD_write_command(0x38);//设置8位格式,2行,5x7
LCD_write_command(0x0c);//整体显示,关光标,不闪烁
LCD_write_command(0x06);//设定输入方式,增量不移位
LCD_write_command(0x01);//清除屏幕显示
delay_n40us(100);//实践证明,我的LCD1602上,用for循环200次就能可靠完成清屏指令。
}
//********************************
//********写指令函数************
void LCD_write_command(uchar dat)
{
LCD_DB=dat;
LCD_RS=0;//指令
LCD_RW=0;//写入
LCD_E=1;//允许
LCD_E=0;
delay_n40us(1);//实践证明,我的LCD1602上,用for循环1次就能完成普通写指令。
}
//*******************************
//********写数据函数*************
void LCD_write_data(uchar dat)
{
LCD_DB=dat;
LCD_RS=1;//数据
LCD_RW=0;//写入
LCD_E=1;//允许
LCD_E=0;
delay_n40us(1);
}
//********************************
//*******显示一个字符函数*********
void LCD_disp_char(uchar x,uchar y,uchar dat)
{
uchar address;
if(y==1)
address=0x80+x;
else
address=0xc0+x;
LCD_write_command(address);
LCD_write_data(dat);
}
//********************************
/*******检查忙函数*************
void LCD_check_busy() //实践证明,在我的LCD1602上,检查忙指令通过率极低,以
{ //至于不能正常使用LCD。因此我没有再用检查忙函数。而使
do //用了延时的方法,延时还是非常好用的。我试了一下,用
{ LCD_E=0; //for循环作延时,普通指令只要1次循就可完成。清屏指令
LCD_RS=0; //要用200次循环便能完成。
LCD_RW=1;
LCD_DB=0xff;
LCD_E=1;
}while(LCD_DB^7==1);
}
******************************/
//********延时函数***************
void delay_n40us(uint n)
{ uint i;
uchar j;
for(i=n;i>0;i--)
for(j=0;j<2;j++); //在这个延时循环函数中我只做了2次循环,
} //实践证明我的LCD1602上普通的指令只需1次循环就能可靠完成。
//*******************************
//*********主函数*****************
void main(void)
{
LCD_init();
LCD_disp_char(0,1,"A");
while(1);
}
//*******************************
具体电路的制作是很简单的,就接了两个电阻,一个是10欧姆的背光限流电阻,另一个是2K的LCD极板电压调节电阻。这两个电阻的阻值怎么定呢?背光比较简单,它就相当于在后面接了几个发光二极管,任何时候你只要在15、16脚串上个100欧的电位器接上电源,调节电位器,觉得亮度合适。此时的阻值便可。LCD液晶极板驱动电压调节电阻的确定就稍微麻烦一点。在各数据线,控制线接好关通上电源的前提下在第3脚(VEE)和地之间接一个10K的电位器。调节电位器。当3脚电压高时为全亮,电压为0时为全暗(液晶全显示为黑块)。你用电位器把屏幕从全暗刚好调到变亮。这时便可调试程序。待屏幕能正确显示后再细调电位器,使对比度合适。这时的阻值便可确定,然后换成等值的固定电阻焊上便可。
我们接着上次的系统板制做:
新买的1602LCD,20元,贵不? 反面: 组装后: 具体电路图: 接口说明: 运行: 用户自定义字符的应用:我们从CGROM表上可以看到,在表的最左边是一列可以允许用户自定义的CGRAM,从上往下看着是16个,实际只有8个字节可用。它的字符码是00000000-00000111这8个地址,表的下面还有8个字节,但因为这个CGRAM的字符码规定0-2位为地址,3位无效,4-7全为零。因此CGRAM的字符码只有最后三位能用也就是8个字节了。等效为0000X111,X为无效位,最后三位为000-111共8个。
如果我们要想显示这8个用户自定义的字符,操作方法和显示CGROM的一样,先设置DDRAM位置,再向DDRAM写入字符码,例如“A”就是41H。现在我们要显示CGRAM的第一个自定义字符,就向DDRAM写入00000000B(00H),如果要显示第8个就写入00000111(08H),简单吧!
好!现在我们来看怎么向这八个自定义字符写入字模。有个设置CGRAM地址的指令大家还记得吗?赶快再找出来看看。![]()
从这个指令可以看出指令数据的高2位已固定是01,只有后面的6位是地址数据,而这6位中的高3位就表示这八个自定义字符,最后的3位就是字模数据的八个地址了。例如第一个自定义字符的字模地址为01000000-01000111八个地址。我们向这8个字节写入字模数据,让它能显示出“℃”
地址:01000000 数据:00010000 图示:○○○■○○○○
01000001 00000110 ○○○○○■■○
01000010 00001001 ○○○○■○○■
01000011 00001000 ○○○○■○○○
01000100 00001000 ○○○○■○○○
01000101 00001001 ○○○○■○○■
01000110 00000110 ○○○○○■■○
01000111 00000000 ○○○○○○○○
下面我们写一段程序让这8个自定义字符显示出一个心的图案:
# include
unsigned char table1[]={0x03,0x07,0x0f,0x1f,0x1f,0x1f,0x1f,0x1f,
0x18,0x1E,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
0x07,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
0x10,0x18,0x1c,0x1E,0x1E,0x1E,0x1E,0x1E,
0x0f,0x07,0x03,0x01,0x00,0x00,0x00,0x00,
0x1f,0x1f,0x1f,0x1f,0x1f,0x0f,0x07,0x01,
0x1f,0x1f,0x1f,0x1f,0x1f,0x1c,0x18,0x00,
0x1c,0x18,0x10,0x00,0x00,0x00,0x00,0x00};//心图案
unsigned char table[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00};//字符℃ #define CLEARSCREEN LCD_write_command(0x01)/**************定义接口************************/#define LCDIO P2
***it LCD1602_RS=P3^0;
***it LCD1602_RW=P3^1;
***it LCD1602_EN=P3^2; /**************定义函数************************/
void LCD_write_command(unsigned char command);//写入指令函数
void LCD_write_dat(unsigned char dat);//写入数据函数
void LCD_set_xy( unsigned char x, unsigned char y );//设置显示位置函数
void LCD_dsp_char( unsigned x,unsigned char y,unsigned char dat);//显示一个字符函数
void LCD_dsp_string(unsigned char X,unsigned char Y,unsigned char *s);//显示字符串函数
void LCD_init(void);//初始化函数
void delay_nms(unsigned int n);//延时函数
/********************************************//************初始化函数****************/
void LCD_init(void)
{
CLEARSCREEN;//clear screen
LCD_write_command(0x38);//set 8 bit data transmission mode
LCD_write_command(0x0c);//open display (enable lcd display)
LCD_write_command(0x80);//set lcd first display address
CLEARSCREEN;//clear screen
}
/****************************************************//**************写指令函数********************************/
void LCD_write_command(unsigned char command)
{
LCDIO=command;
LCD1602_RS=0;
LCD1602_RW=0;
LCD1602_EN=0;
LCD1602_EN=1;
delay_nms(10);
}
/***************************************************/
/****************写数据函数************************/
void LCD_write_dat(unsigned char dat)
{
LCDIO=dat;
LCD1602_RS=1;
LCD1602_RW=0;
LCD1602_EN=0;
delay_nms(1);
LCD1602_EN=1;
}
/****************************************************//***************设置显示位置**************************/
void LCD_set_xy( unsigned char x, unsigned char y )
{
unsigned char address;
if (y == 1)
address = 0x80 + x;
else
address =0xc0+ x;
LCD_write_command(address);
}
/***************************************************//****************显示一个字符**********************/
void LCD_dsp_char( unsigned x,unsigned char y,unsigned char dat)
{
LCD_set_xy( x, y );
LCD_write_dat(dat);
}
/**********************************************//***************显示字符串函数***************/
void LCD_dsp_string(unsigned char X,unsigned char Y,unsigned char *s)
{
LCD_set_xy( X, Y );
while (*s)
{
LCD_write_dat(*s);
s ++;
}
}
/***********************************************//********** 延时**********************/
void delay_nms(unsigned int n)
{
unsigned int i=0,j=0;
for (i=n;i>0;i--)
for (j=0;j<10;j++);
}
/**************************************//***********主函数**************/
void main(void)
{
unsigned char i,j,k,tmp;
LCD_init();
delay_nms(100);
tmp=0x40;//设置CGRAM地址的格式字
k=0;
for(j=0;j<8;j++)
{
for(i=0;i<8;i++)
{
LCD_write_command(tmp+i); // 设置自定义字符的 CGRAM 地址
delay_nms(2);
LCD_write_dat(table1[k]); // 向CGRAM写入自定义字符表的数据
k++;
delay_nms(2);
}
tmp=tmp+8;
}
LCD_dsp_string(1,1,"LCD TEST ");//在第一行第一列显示“LCD TEST”
LCD_dsp_string(1,2,"SUCCESSFUL ");//在第二行第一列显示“SUCCESSFUL”
for (i=0;i<4;i++)
{
LCD_dsp_char( 12+i,1,i);//在第一行第12列位置显示心图案的上半部
delay_nms(1);
}
for (i=4;i<8;i++)
{
LCD_dsp_char( 12+i-4,2,i);在第二行第12列位置显示心图案的下半部
delay_nms(1);
}
while (1);
}
/********************************************************************/实际效果如图:![]()
6.自制单片机之六……LCD12864的驱动LCD12864的驱动LCD12864在市面上主要分为两种,一种是采用st7920控制器的,它一般带有中文字库字模,价格略高一点。另一种是采用KS0108控制器,它只是点阵模式,不带字库。很可惜,我的这块就是KS0108控制器不带汉字库的,不过不打算用它专门显示文本,也就无所谓了。 LCD12864模块的20个引脚定义如下:1。Vss 逻辑电源地
2。VDD 逻辑电源正 5v
3。V0 LCD驱动电压
4。RS 数据/指令选择:高电平为数据,低电平为指令
5。R/W 读/写选择:高电平为读数据,低电平为写数据
6。E 读写使能,高电平有效,下降沿锁定数据
7。DB0 数据输入输出引脚
8。DB1 数据输入输出引脚
9。DB2 数据输入输出引脚
10。DB3 数据输入输出引脚
11。DB4 数据输入输出引脚
12。DB5 数据输入输出引脚
13。DB6 数据输入输出引脚
14。DB7 数据输入输出引脚
15。CS1 片选择号,低电平时选择前64列
16。CS2 片选择号,低电平时选择后64列
17。RET 复位信号,低电平有效。
18。VEE 输出-15v电源给V0提供驱动电源
19。A 背光电源LED正极
20。K 背光电源LED负极具体电路图如下: 制做如下: 接口说明: 装上12864 具体的电路还是两个电阻。一个背光限流电阻。一个液晶驱动电压调节电阻。背光电阻还是任何时候在19、20脚与电源之间串上个100欧电位器接上电源。调节电位器到合适亮度。具体值最好是到调试完程序能够正常显示后再将阻值确定换成固定电阻。液晶驱动电压的调整在数据线、电源线接好的前提下是在Vee(-15v)和地之间接一个电位器。中间接V0,通过调节电位器来调节V0上的电压。当V0上为-15V时为全暗(液晶显示为全黑)。当V0为0V时为全亮。调节电位器使屏幕从全暗刚好变到亮时,便可进行程序的调试。待屏幕显示正常后,进行对比度的细调,然后测量这两边的阻值在地和V0之间、V0和Vee之间换成两个固定电阻焊上就好了。注意在V0的电压是在一个很小的范围有效。我的就是在-2.2——-2.5这个范围。仔细调节V0和地之间的电阻使V0上的电压在2.3V。 更换为固定电阻后的装配图: 下面说说具体的驱动:先来了解一下LCD12864的内部控制结构:见图 可以看出12864屏是分为左、右两块控制的。所有对屏幕的操作要受片选CS1、CS2来控制。
我们再来看一看对屏幕操作数据与屏幕点阵的排布关系:见下图。![]()
从上图可以看出数据按字节在屏幕上是竖向排列的。上方为低位,下方为高位。因此在横向上(也就是Y)就一共是128列数据。分为CS1和CS2两个64列来写入。在竖方向上(也就是X)一字节数据显示8个点,竖向64个点分为8个字节,称做8页(X=0-7)。了解这些后我们就知道要满屏显示一张图就要从y=0…127、X=0…7一共写128×8=1024个字节的数据。同样在AT89S51中存一张图就要1024个字节的空间。
好!下面我们来了解对LCD12864进行操作的一些指令。 下面对上图的指作解释:
1.显示开关控制(DISPLAY ON/OFF) D=1:开显示(DISPLAY ON) 意即显示器可以进行各种显示操作
D=0:关显示(DISPLAY OFF) 意即不能对显示器进行各种显示操作 2.设置显示起始行(DISPLAY START LINE) 前面在Z地址计数器一节已经描述了显示起始行是由Z地址计数器控制的。A5~A0 6位地址自动送入Z地址计数器,起始行的地址可以是0~63的任意一行。
例如: 选择A5~A0是62,则起始行与DDRAM行的对应关系如下:
DDRAM 行:62 63 0 1 2 3 ·················28 29
屏幕显示行: 1 2 3 4 5 6················· 31 32 3.设置页地址(SET PAGE “X ADDRESS”) 所谓页地址就是DDRAM的行地址,8行为一页,模块共64行即8页,A2~A0表示0~7页。读写数据对地址没有影响,页地址由本指令或RST信号改变复位后页地址为0。页地址与DDRAM的对应关系见DDRAM地址表。 4.设置Y地址(SET Y ADDRESS) 此指令的作用是将A5~A0送入Y地址计数器,作为DDRAM的Y地址指针。在对DDRA M进行读写操作后,Y地址指针自动加1,指向下一个DDRAM单元。
5.读状态(STATUS READ) 当R/W=1 D/I=0时,在E信号为“H”的作用下,状态分别输出到数据总线(DB7~DB0)的相应位。 BF: 前面已叙述过(见BF标志位一节)。 ON/OFF: 表示DFF触发器的状态(见DFF触发器一节)。 RST: RST=1表示内部正在初始化,此时组件不接受任何指令和数据。6.写显示数据(WRITE DISPLAY DATE) D7~D0为显示数据,此指令把D7~D0写入相应的DDRAM单元,Y地址指针自动加1。7.读显示数据(READ DISPLAY DATE) 此指令把DDRAM的内容D7~D0读到数据总线DB7~DB0,Y地址指针自动加1。 再帖一下接口时序图1.写操作时序
2.读操作时序 ![]()
时序参数表: 又帖了这么多指令呀时序图什么的,看了就头晕。我也和你一样不爱看这些枯燥的东西。下面实际写些程序让屏幕亮起来。
运行:![]()
不要走开哦!我会不断补全的。。。
边学边秀单片机1:https://bbs.elecfans.com/jishu_286355_1_1.html
边学边秀单片机2:https://bbs.elecfans.com/jishu_286362_1_1.html
边学边秀单片机3:https://bbs.elecfans.com/jishu_286363_1_1.html
边学边秀单片机4:https://bbs.elecfans.com/jishu_286367_1_1.html
边学边秀单片机5:https://bbs.elecfans.com/jishu_286368_1_1.html
边学边秀单片机6:https://bbs.elecfans.com/jishu_286369_1_1.html
边学边秀单片机7:https://bbs.elecfans.com/jishu_286370_1_1.html
边学边秀单片机8:https://bbs.elecfans.com/jishu_286371_1_1.html
|