[应用方案] 用单片机IO口直接驱动段式LCD的方法

[复制链接]
2682|2
 楼主| 734774645 发表于 2018-10-29 16:19 | 显示全部楼层 |阅读模式
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.       原理图
20160909094240876.jpg
说明:由于管脚不够用,所以时钟芯片DS1302的RST和LCD的一个SEG是复用的,只要在这个SEG无效的时候去读取时间就可以了,另外,3PIN串口是ISP下载程序用的。
2.       备料
20160909094240467.jpg

3.       焊接
20160909094241239.jpg
4.       实验结果
20160909094241336.jpg
5.       不足之处
通过实验结果可以发现,不显示的SEG也有阴影
原因分析:纽扣电池电压3.7V,1/2bias是1.85V,大于1.5V,所以会出现阴影。
解决办法:选择工作电压小于3V的单片机和电压等于3V的电池(如2节干电池)


 楼主| 734774645 发表于 2018-10-29 16:20 | 显示全部楼层
6. 程序源代码
  1. /******************************************************************
  2. 段式LCD驱动实验            
  3. 外部晶体:12MHz
  4. *****************************************************************/

  5. #include <reg52.h>
  6. #include <stdio.h>

  7. //管脚定义
  8. sbit COM0=P3^5;
  9. sbit COM1=P3^4;
  10. sbit COM2=P3^3;
  11. sbit COM3=P3^2;
  12. sbit BI_4=P3^7;
  13. sbit RTC_CLK=P3^0;
  14. sbit RTC_IO=P3^1;
  15. sbit RTC_RST=P3^7;   //复用

  16. //P3口模式寄存器
  17. sfr P3M1=0xb1;
  18. sfr P3M0=0xb2;

  19. //当前时间(BCD码):秒、分、时、日、月、星期、年
  20. unsigned char ClockBuffer[8]={0x34,0x12,0x08,0x20,0x03,0x05,0x09};

  21. //0~9的段码查询表
  22. //位序 D7 D6 D5 D4 D3 D2 D1 D0
  23. //段   A  B  C  D  E  F  G  DOT
  24. code unsigned char seg_code[10]={~0x03,~0x9f,~0x25,~0x0d,~0x99,~0x49,~0x41,~0x1f,~0x01,~0x09};

  25. unsigned char ScanCoun=0;                  //动态扫描显示位数计数器
  26. unsigned char DisplayBuf[4]={1,2,3,4};               //4位数字对应的显示暂存

  27. //段码缓冲区
  28. unsigned char SegBuf[4]={0x00,0x00,0x00,0x00};//COM1、COM2、COM3、COM4的段码
  29. bit bi_4a=0; //COM0对应的4a
  30. bit bi_4b=0; //COM1对应的4a
  31. bit bi_4c=0; //COM2对应的4a

  32. //延时
  33. void dly(unsigned char x)
  34.    {unsigned char i;
  35.     for (i=0; i<x; i++);
  36.     }

  37. //ds1302写1字节
  38. void rtc_wt_byte(unsigned char sent_buf)
  39.          {unsigned char i;
  40.           for (i=0; i<8; i++)
  41.               {RTC_CLK=0;
  42.                if (sent_buf&0x01) RTC_IO=1;
  43.                else RTC_IO=0;
  44.                RTC_CLK=1;
  45.                dly(5);
  46.                sent_buf=sent_buf>>1;
  47.                }
  48.               RTC_CLK=0;
  49.               dly(5);
  50.            }

  51. //ds1302读1字节
  52. unsigned char rtc_rd_byte(void)
  53.           {unsigned char i,read_buf;
  54.            RTC_IO=1;         //RTC_IO置1,保证为输入状态
  55.            for (i=0; i<8; i++)
  56.                {read_buf=read_buf>>1;
  57.                        RTC_CLK=0;
  58.                        dly(5);
  59.                 if (RTC_IO) read_buf=read_buf|0x80;
  60.                 else read_buf=read_buf&0x7f;
  61.                 RTC_CLK=1;
  62.                 dly(5);
  63.                }
  64.            RTC_CLK=0;
  65.            dly(5);
  66.            return read_buf;
  67.           }

  68. //ds1302写入时间
  69. void rtc_wr_time(unsigned char *p_wt_time)
  70.            {unsigned char i;
  71.             unsigned char tmp1;
  72.             dly(30);
  73.             RTC_RST=1;
  74.             rtc_wt_byte(0xbe);         //burst写入时间
  75.             for (i=0; i<8; i++)
  76.                   {tmp1=*p_wt_time++;
  77.                    rtc_wt_byte(tmp1);
  78.                    }
  79.             RTC_CLK=0;
  80.             RTC_RST=0;
  81.            }

  82. //ds1302读出时间
  83. void rtc_rd_time(unsigned char *p_rd_time)
  84.            {unsigned char i;
  85.             unsigned char tmp1;
  86.             dly(30);
  87.             RTC_RST=1;
  88.             rtc_wt_byte(0xbf);        //burst读取时间

  89.             RTC_IO=1;
  90.             for (i=0; i<8; i++)
  91.                   {tmp1=rtc_rd_byte();
  92.                    *p_rd_time++=tmp1;
  93.                   }

  94.             RTC_CLK=0;
  95.             RTC_RST=0;
  96.            }


  97. //ds1302初始化
  98. void ini_rtc()
  99.          {RTC_CLK=0;
  100.           RTC_RST=0;
  101.           dly(30);
  102.          
  103.           RTC_RST=1;               
  104.           rtc_wt_byte(0x8e);        //写CONTROL寄存器
  105.           rtc_wt_byte(0x00);        //值:去掉写保护
  106.           RTC_RST=0;                //复位

  107.           RTC_RST=1;                //正常工作
  108.           rtc_wt_byte(0x90);        //写TRICKLE CHARGER寄存器
  109.           rtc_wt_byte(0xa9);        //值:使能充电,串联2个二极管,串联2k欧姆的电阻

  110.           RTC_CLK=0;
  111.           RTC_RST=0;
  112.          }


  113. //把4位数字的SEG放到COM1、COM2、COM3、COM4对应的段码
  114. //LCD的管脚定义与LED不同,它不是一个COM对应一位数字,而是对应每个数字的一部分SEG
  115. // 1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
  116. // <  1f  1a  2f  2a      3f  3a  4f  4a   >              --   ---- COM0
  117. // <  1g  1b  2g  2b      2g  3b  4g  4b   >          --       ---- COM1
  118. // <  1e  1c  2e  2c   :  3e  3c  4e  4c   >      --           ---- COM2
  119. //    1d  1h  2d  2h      3d  3h  4d          --               ---- COM3
  120. void Seg2Seg()
  121. {unsigned char SegXX;

  122. SegBuf[0]=0;
  123. SegBuf[1]=0;
  124. SegBuf[2]=0x08;
  125. SegBuf[3]=0;
  126. bi_4a=0;
  127. bi_4b=0;
  128. bi_4c=0;
  129.        
  130. SegXX=seg_code[DisplayBuf[0]];      //第1位数字
  131. if (SegXX&0x80) SegBuf[0]|=0x40;
  132. if (SegXX&0x40) SegBuf[1]|=0x40;
  133. if (SegXX&0x20) SegBuf[2]|=0x40;
  134. if (SegXX&0x10) SegBuf[3]|=0x80;
  135. if (SegXX&0x08) SegBuf[2]|=0x80;
  136. if (SegXX&0x04) SegBuf[0]|=0x80;
  137. if (SegXX&0x02) SegBuf[1]|=0x80;
  138. if (SegXX&0x01) SegBuf[3]|=0x40;

  139.   SegXX=seg_code[DisplayBuf[1]];    //第2位数字
  140. if (SegXX&0x80) SegBuf[0]|=0x10;
  141. if (SegXX&0x40) SegBuf[1]|=0x10;
  142. if (SegXX&0x20) SegBuf[2]|=0x10;
  143. if (SegXX&0x10) SegBuf[3]|=0x20;
  144. if (SegXX&0x08) SegBuf[2]|=0x20;
  145. if (SegXX&0x04) SegBuf[0]|=0x20;
  146. if (SegXX&0x02) SegBuf[1]|=0x20;
  147. if (SegXX&0x01) SegBuf[3]|=0x10;


  148.   SegXX=seg_code[DisplayBuf[2]];   //第3位数字
  149. if (SegXX&0x80) SegBuf[0]|=0x02;
  150. if (SegXX&0x40) SegBuf[1]|=0x02;
  151. if (SegXX&0x20) SegBuf[2]|=0x02;
  152. if (SegXX&0x10) SegBuf[3]|=0x04;
  153. if (SegXX&0x08) SegBuf[2]|=0x04;
  154. if (SegXX&0x04) SegBuf[0]|=0x04;
  155. if (SegXX&0x02) SegBuf[1]|=0x04;
  156. if (SegXX&0x01) SegBuf[3]|=0x02;

  157.   SegXX=seg_code[DisplayBuf[3]];   //第4位数字
  158. if (SegXX&0x80) bi_4a=1;
  159. if (SegXX&0x40) bi_4b=1;
  160. if (SegXX&0x20) bi_4c=1;
  161. if (SegXX&0x10) SegBuf[3]|=0x01;
  162. if (SegXX&0x08) SegBuf[2]|=0x01;
  163. if (SegXX&0x04) SegBuf[0]|=0x01;
  164. if (SegXX&0x02) SegBuf[1]|=0x01;

  165. }


  166. /*一个BCD码转化成两个十进制数(如:0x79转化成0x07和0x09)*/
  167. BcdToDec(unsigned char BcdValue,unsigned char *pDecValue)
  168.        {//if (BcdValue>=0x9a||(BcdValue&0x0f)>=0x0a) return 0;
  169.                *pDecValue++=(BcdValue&0xf0)>>4;
  170.         *pDecValue=BcdValue&0x0f;
  171.         //return 1;
  172.        }

  173. //初始化MCS51内部资源
  174. InitInterResource()
  175.        {IE=0;       //关全部中断
  176.         TCON=0;     //清全部中断请求
  177.         IP=0;       //清中断优先级   

  178.         TMOD=0x01;  //T0工作方式1(16位定时器)
  179.         TH0=0x00;   //T0定时器辅初值
  180.         TL0=0x00;
  181.         TR0=1;      //允许T0定时
  182.         ET0=1;      //允许T0中断
  183.         EA=0;       //关全局中断
  184.         
  185.         RTC_RST=0;
  186.        }




  187. void main()
  188.     {   
  189.      InitInterResource();
  190.          
  191.      ini_rtc();                    //初始化DS1302
  192.      rtc_wr_time(ClockBuffer);     //写入时间初始值

  193.      EA=1;         //开全局中断
  194.      while(1)
  195.         {         
  196.         }
  197.     }


  198. //定时器0中断服务程序,5ms定时器,4位数码管动态显示驱动
  199. void tmr0_p(void) interrupt 1
  200.     {
  201.      TL0=0x78;     //重新定时5ms
  202.      TH0=0xec;
  203.      Seg2Seg();
  204.      P3M1=0x3c;
  205.      P3M0=0x00;
  206.           switch(ScanCoun)                //动态扫描显示
  207.          {
  208.           case 0:                        //COM0正向驱动
  209.           P1= SegBuf[0];
  210.           BI_4= bi_4a;
  211.           COM0=0;           
  212.           P3M1=0x1c;                      //除COM0输出外,其余COM设为输入
  213.           P3M0=0x00;
  214.           break;
  215.          
  216.           case 1:                       //COM0反向驱动
  217.           P1= ~SegBuf[0];
  218.           BI_4= ~bi_4a;
  219.           COM0=1;              
  220.           P3M1=0x1c;
  221.           P3M0=0x00;
  222.           break;
  223.          
  224.          
  225.           case 2:                       //COM1正向驱动
  226.           P1= SegBuf[1];
  227.           BI_4= bi_4b;
  228.           COM1=0;            
  229.           P3M1=0x2c;
  230.           P3M0=0x00;
  231.           break;
  232.          
  233.           case 3:                       //COM1反向驱动
  234.           P1= ~SegBuf[1];
  235.           BI_4= ~bi_4b;
  236.           COM1=1;                  
  237.           P3M1=0x2c;
  238.           P3M0=0x00;
  239.           break;
  240.          
  241.          
  242.           case 4:                       //COM2正向驱动
  243.           P1= SegBuf[2];
  244.           BI_4= bi_4c;
  245.           COM2=0;                  
  246.           P3M1=0x34;
  247.           P3M0=0x00;
  248.           break;
  249.          
  250.           case 5:                       //COM2反向驱动
  251.           P1= ~SegBuf[2];
  252.           BI_4= ~bi_4c;
  253.           COM2=1;                  
  254.           P3M1=0x34;
  255.           P3M0=0x00;
  256.           break;
  257.          
  258.           case 6:                       //COM3正向驱动
  259.           P1= SegBuf[3];
  260.           COM3=0;                  
  261.           P3M1=0x38;
  262.           P3M0=0x00;
  263.          
  264.           RTC_RST=0;
  265.           rtc_rd_time(ClockBuffer);    //读时间
  266.           BcdToDec(ClockBuffer[0],DisplayBuf+2);     //秒送入显示缓冲
  267.           BcdToDec(ClockBuffer[1],DisplayBuf);       //分送入显示缓冲
  268.           BI_4= ~bi_4c;
  269.           break;
  270.          
  271.           case 7:                       //COM3反向驱动
  272.           P1= ~SegBuf[3];
  273.           COM3=1;                  
  274.           P3M1=0x38;
  275.           P3M0=0x00;
  276.          
  277.           break;
  278.          
  279.          }

  280.      ScanCoun++;       //下一位
  281.      if (ScanCoun>7) ScanCoun=0;      
  282.      }


 楼主| 734774645 发表于 2018-10-29 16:20 | 显示全部楼层
我看有人问普通单片机IO怎么驱动LCD,我也不会,我网上找了个,转来给需要的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

211

主题

3588

帖子

15

粉丝
快速回复 在线客服 返回列表 返回顶部