单片机IO驱动LCD
2015-10-10 17:19
676 查看
用单片机IO口直接驱动段式LCD的方法
用IO口驱动段式LED(数码管)的方法相信大家比较清楚,但用IO口直接驱动段式LCD的方法相对复杂一些。在网上搜了一下单片机IO口驱动段式LCD的方法,大部分资料讲得不够清晰、具体,而且简单问题复杂化。后来查了LCD的显示原理,结合网上的相关介绍,发现IO口直接驱动段式LCD原理比较简单,用几句话就可以描述清楚:
1. LCD和LED的显示原理不一样:LED是加正向电压发光,而LCD必须交替加正、反向电压才会持续显示(可以做个实验,如果把恒定电压加到LCD的一段上,该段会显示一下,但马上不能显示,而且长时间加恒定电压,会加速LCD的老化和损坏)
2. 常听说1/2bias,1/3bias LCD,是什么意思呢?对于1/2bias LCD,假如LCD的显示电压是3V,则1/2bias是1.5V,也就是说在±3V电压作用时,LCD有显示;±1.5V及以下的电压作用时没有显示
3. 普通单片机IO口不能直接输出半高电平(1.5V),但可以用相等的上下拉电阻实现,当IO口设置为输入(高阻)时,由于上下拉电阻的分压作用,则产生一个半高电平(1.5V)
知道了以上3点后,动态驱动LCD就不是难事了,对于4*8段的LCD(4个COM,8个SEG,显示电压为3V,1/2bias),驱动方法如下:
1、 四个COM采用交替扫描的方式,每个COM在相邻两次扫描时又进行电压交变的方式。
2、 若扫描到某一个COM时,该COM输出3V(0V):
与该COM相连的SEG输出与COM相反,ΔV=±3V,则该相连点亮;
与该COM相连的SEG输出与COM相同,ΔV=0,则该相连点不亮。
3、其他没有扫描到的COM,单片机IO口为输入,从而产生1/2 bias(1.5V),不管SEG为何值,ΔV<±1.5V,故该点不亮。
本人用4*8段的LCD自制了一个数字钟表,验证了以上方法的可行性,现把制作过程罗列如下
1. 原理图
说明:由于管脚不够用,所以时钟芯片DS1302的RST和LCD的一个SEG是复用的,只要在这个SEG无效的时候去读取时间就可以了,另外,3PIN串口是ISP下载程序用的。
2. 备料
3. 焊接
4. 实验结果
5. 不足之处
通过实验结果可以发现,不显示的SEG也有阴影
原因分析:纽扣电池电压3.7V,1/2bias是1.85V,大于1.5V,所以会出现阴影。
解决办法:选择工作电压小于3V的单片机和电压等于3V的电池(如2节干电池)
6. 程序源代码
用IO口驱动段式LED(数码管)的方法相信大家比较清楚,但用IO口直接驱动段式LCD的方法相对复杂一些。在网上搜了一下单片机IO口驱动段式LCD的方法,大部分资料讲得不够清晰、具体,而且简单问题复杂化。后来查了LCD的显示原理,结合网上的相关介绍,发现IO口直接驱动段式LCD原理比较简单,用几句话就可以描述清楚:
1. LCD和LED的显示原理不一样:LED是加正向电压发光,而LCD必须交替加正、反向电压才会持续显示(可以做个实验,如果把恒定电压加到LCD的一段上,该段会显示一下,但马上不能显示,而且长时间加恒定电压,会加速LCD的老化和损坏)
2. 常听说1/2bias,1/3bias LCD,是什么意思呢?对于1/2bias LCD,假如LCD的显示电压是3V,则1/2bias是1.5V,也就是说在±3V电压作用时,LCD有显示;±1.5V及以下的电压作用时没有显示
3. 普通单片机IO口不能直接输出半高电平(1.5V),但可以用相等的上下拉电阻实现,当IO口设置为输入(高阻)时,由于上下拉电阻的分压作用,则产生一个半高电平(1.5V)
知道了以上3点后,动态驱动LCD就不是难事了,对于4*8段的LCD(4个COM,8个SEG,显示电压为3V,1/2bias),驱动方法如下:
1、 四个COM采用交替扫描的方式,每个COM在相邻两次扫描时又进行电压交变的方式。
2、 若扫描到某一个COM时,该COM输出3V(0V):
与该COM相连的SEG输出与COM相反,ΔV=±3V,则该相连点亮;
与该COM相连的SEG输出与COM相同,ΔV=0,则该相连点不亮。
3、其他没有扫描到的COM,单片机IO口为输入,从而产生1/2 bias(1.5V),不管SEG为何值,ΔV<±1.5V,故该点不亮。
本人用4*8段的LCD自制了一个数字钟表,验证了以上方法的可行性,现把制作过程罗列如下
1. 原理图
说明:由于管脚不够用,所以时钟芯片DS1302的RST和LCD的一个SEG是复用的,只要在这个SEG无效的时候去读取时间就可以了,另外,3PIN串口是ISP下载程序用的。
2. 备料
3. 焊接
4. 实验结果
5. 不足之处
通过实验结果可以发现,不显示的SEG也有阴影
原因分析:纽扣电池电压3.7V,1/2bias是1.85V,大于1.5V,所以会出现阴影。
解决办法:选择工作电压小于3V的单片机和电压等于3V的电池(如2节干电池)
6. 程序源代码
/****************************************************************** 段式LCD驱动实验 外部晶体:12MHz 作者:www.dzsj.net 邮箱:wang9601@126.com 日期:2011.08.26 *****************************************************************/ #include <reg52.h> #include <stdio.h> //管脚定义 sbit COM0=P3^5; sbit COM1=P3^4; sbit COM2=P3^3; sbit COM3=P3^2; sbit BI_4=P3^7; sbit RTC_CLK=P3^0; sbit RTC_IO=P3^1; sbit RTC_RST=P3^7; //复用 //P3口模式寄存器 sfr P3M1=0xb1; sfr P3M0=0xb2; //当前时间(BCD码):秒、分、时、日、月、星期、年 unsigned char ClockBuffer[8]={0x34,0x12,0x08,0x20,0x03,0x05,0x09}; //0~9的段码查询表 //位序 D7 D6 D5 D4 D3 D2 D1 D0 //段 A B C D E F G DOT code unsigned char seg_code[10]={~0x03,~0x9f,~0x25,~0x0d,~0x99,~0x49,~0x41,~0x1f,~0x01,~0x09}; unsigned char ScanCoun=0; //动态扫描显示位数计数器 unsigned char DisplayBuf[4]={1,2,3,4}; //4位数字对应的显示暂存 //段码缓冲区 unsigned char SegBuf[4]={0x00,0x00,0x00,0x00};//COM1、COM2、COM3、COM4的段码 bit bi_4a=0; //COM0对应的4a bit bi_4b=0; //COM1对应的4a bit bi_4c=0; //COM2对应的4a //延时 void dly(unsigned char x) {unsigned char i; for (i=0; i<x; i++); } //ds1302写1字节 void rtc_wt_byte(unsigned char sent_buf) {unsigned char i; for (i=0; i<8; i++) {RTC_CLK=0; if (sent_buf&0x01) RTC_IO=1; else RTC_IO=0; RTC_CLK=1; dly(5); sent_buf=sent_buf>>1; } RTC_CLK=0; dly(5); } //ds1302读1字节 unsigned char rtc_rd_byte(void) {unsigned char i,read_buf; RTC_IO=1; //RTC_IO置1,保证为输入状态 for (i=0; i<8; i++) {read_buf=read_buf>>1; RTC_CLK=0; dly(5); if (RTC_IO) read_buf=read_buf|0x80; else read_buf=read_buf&0x7f; RTC_CLK=1; dly(5); } RTC_CLK=0; dly(5); return read_buf; } //ds1302写入时间 void rtc_wr_time(unsigned char *p_wt_time) {unsigned char i; unsigned char tmp1; dly(30); RTC_RST=1; rtc_wt_byte(0xbe); //burst写入时间 for (i=0; i<8; i++) {tmp1=*p_wt_time++; rtc_wt_byte(tmp1); } RTC_CLK=0; RTC_RST=0; } //ds1302读出时间 void rtc_rd_time(unsigned char *p_rd_time) {unsigned char i; unsigned char tmp1; dly(30); RTC_RST=1; rtc_wt_byte(0xbf); //burst读取时间 RTC_IO=1; for (i=0; i<8; i++) {tmp1=rtc_rd_byte(); *p_rd_time++=tmp1; } RTC_CLK=0; RTC_RST=0; } //ds1302初始化 void ini_rtc() {RTC_CLK=0; RTC_RST=0; dly(30); RTC_RST=1; rtc_wt_byte(0x8e); //写CONTROL寄存器 rtc_wt_byte(0x00); //值:去掉写保护 RTC_RST=0; //复位 RTC_RST=1; //正常工作 rtc_wt_byte(0x90); //写TRICKLE CHARGER寄存器 rtc_wt_byte(0xa9); //值:使能充电,串联2个二极管,串联2k欧姆的电阻 RTC_CLK=0; RTC_RST=0; } //把4位数字的SEG放到COM1、COM2、COM3、COM4对应的段码 //LCD的管脚定义与LED不同,它不是一个COM对应一位数字,而是对应每个数字的一部分SEG // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // < 1f 1a 2f 2a 3f 3a 4f 4a > -- ---- COM0 // < 1g 1b 2g 2b 2g 3b 4g 4b > -- ---- COM1 // < 1e 1c 2e 2c : 3e 3c 4e 4c > -- ---- COM2 // 1d 1h 2d 2h 3d 3h 4d -- ---- COM3 void Seg2Seg() {unsigned char SegXX; SegBuf[0]=0; SegBuf[1]=0; SegBuf[2]=0x08; SegBuf[3]=0; bi_4a=0; bi_4b=0; bi_4c=0; SegXX=seg_code[DisplayBuf[0]]; //第1位数字 if (SegXX&0x80) SegBuf[0]|=0x40; if (SegXX&0x40) SegBuf[1]|=0x40; if (SegXX&0x20) SegBuf[2]|=0x40; if (SegXX&0x10) SegBuf[3]|=0x80; if (SegXX&0x08) SegBuf[2]|=0x80; if (SegXX&0x04) SegBuf[0]|=0x80; if (SegXX&0x02) SegBuf[1]|=0x80; if (SegXX&0x01) SegBuf[3]|=0x40; SegXX=seg_code[DisplayBuf[1]]; //第2位数字 if (SegXX&0x80) SegBuf[0]|=0x10; if (SegXX&0x40) SegBuf[1]|=0x10; if (SegXX&0x20) SegBuf[2]|=0x10; if (SegXX&0x10) SegBuf[3]|=0x20; if (SegXX&0x08) SegBuf[2]|=0x20; if (SegXX&0x04) SegBuf[0]|=0x20; if (SegXX&0x02) SegBuf[1]|=0x20; if (SegXX&0x01) SegBuf[3]|=0x10; SegXX=seg_code[DisplayBuf[2]]; //第3位数字 if (SegXX&0x80) SegBuf[0]|=0x02; if (SegXX&0x40) SegBuf[1]|=0x02; if (SegXX&0x20) SegBuf[2]|=0x02; if (SegXX&0x10) SegBuf[3]|=0x04; if (SegXX&0x08) SegBuf[2]|=0x04; if (SegXX&0x04) SegBuf[0]|=0x04; if (SegXX&0x02) SegBuf[1]|=0x04; if (SegXX&0x01) SegBuf[3]|=0x02; SegXX=seg_code[DisplayBuf[3]]; //第4位数字 if (SegXX&0x80) bi_4a=1; if (SegXX&0x40) bi_4b=1; if (SegXX&0x20) bi_4c=1; if (SegXX&0x10) SegBuf[3]|=0x01; if (SegXX&0x08) SegBuf[2]|=0x01; if (SegXX&0x04) SegBuf[0]|=0x01; if (SegXX&0x02) SegBuf[1]|=0x01; } /*一个BCD码转化成两个十进制数(如:0x79转化成0x07和0x09)*/ BcdToDec(unsigned char BcdValue,unsigned char *pDecValue) {//if (BcdValue>=0x9a||(BcdValue&0x0f)>=0x0a) return 0; *pDecValue++=(BcdValue&0xf0)>>4; *pDecValue=BcdValue&0x0f; //return 1; } //初始化MCS51内部资源 InitInterResource() {IE=0; //关全部中断 TCON=0; //清全部中断请求 IP=0; //清中断优先级 TMOD=0x01; //T0工作方式1(16位定时器) TH0=0x00; //T0定时器辅初值 TL0=0x00; TR0=1; //允许T0定时 ET0=1; //允许T0中断 EA=0; //关全局中断 RTC_RST=0; } void main() { InitInterResource(); ini_rtc(); //初始化DS1302 rtc_wr_time(ClockBuffer); //写入时间初始值 EA=1; //开全局中断 while(1) { } } //定时器0中断服务程序,5ms定时器,4位数码管动态显示驱动 void tmr0_p(void) interrupt 1 { TL0=0x78; //重新定时5ms TH0=0xec; Seg2Seg(); P3M1=0x3c; P3M0=0x00; switch(ScanCoun) //动态扫描显示 { case 0: //COM0正向驱动 P1= SegBuf[0]; BI_4= bi_4a; COM0=0; P3M1=0x1c; //除COM0输出外,其余COM设为输入 P3M0=0x00; break; case 1: //COM0反向驱动 P1= ~SegBuf[0]; BI_4= ~bi_4a; COM0=1; P3M1=0x1c; P3M0=0x00; break; case 2: //COM1正向驱动 P1= SegBuf[1]; BI_4= bi_4b; COM1=0; P3M1=0x2c; P3M0=0x00; break; case 3: //COM1反向驱动 P1= ~SegBuf[1]; BI_4= ~bi_4b; COM1=1; P3M1=0x2c; P3M0=0x00; break; case 4: //COM2正向驱动 P1= SegBuf[2]; BI_4= bi_4c; COM2=0; P3M1=0x34; P3M0=0x00; break; case 5: //COM2反向驱动 P1= ~SegBuf[2]; BI_4= ~bi_4c; COM2=1; P3M1=0x34; P3M0=0x00; break; case 6: //COM3正向驱动 P1= SegBuf[3]; COM3=0; P3M1=0x38; P3M0=0x00; RTC_RST=0; rtc_rd_time(ClockBuffer); //读时间 BcdToDec(ClockBuffer[0],DisplayBuf+2); //秒送入显示缓冲 BcdToDec(ClockBuffer[1],DisplayBuf); //分送入显示缓冲 BI_4= ~bi_4c; break; case 7: //COM3反向驱动 P1= ~SegBuf[3]; COM3=1; P3M1=0x38; P3M0=0x00; break; } ScanCoun++; //下一位 if (ScanCoun>7) ScanCoun=0; }
相关文章推荐
- Lua和C语言的交互详解
- 关于C语言中参数的传值问题
- 简要对比C语言中三个用于退出进程的函数
- 深入C++中API的问题详解
- 基于C语言string函数的详解
- C语言中fchdir()函数和rewinddir()函数的使用详解
- C语言内存对齐实例详解
- 使用C语言判断英文字符大小写的方法
- c语言实现的带通配符匹配算法
- C语言实现顺序表基本操作汇总
- C语言中计算正弦的相关函数总结
- 使用C语言详解霍夫曼树数据结构
- 探讨C语言的那些小秘密之断言
- C语言实现BMP转换JPG的方法
- 深入探讨C语言中局部变量与全局变量在内存中的存放位置
- C语言查找数组里数字重复次数的方法
- C语言泛型编程实例教程
- C语言中使用lex统计文本文件字符数
- 在C语言中转换时间的基本方法介绍
- C语言进制转换代码分享