离线
网上找到一份手册, 含原理图: stm3210b-eval-manual.pdf
IAR 源码下载: EKSTM3210B_v5.7z
STC IO驱动 段码液晶
链接: http://www.stcisp.com/STCMCU_IO_DIRECT_DRIVER_LCD-V3.html
源码: LCD_2014_6_19.7z
最近编辑记录 天边那朵白云 (2019-03-18 16:50:20)
离线
转载圈圈的文章: https://www.veryarm.com/138135.html (这个也是网上转来的)
LCD的驱动不像LED那样,加上电压(LED实际上是电流驱动)就可以长期显示的。LCD驱动必须使用交流电压驱动才能保持稳定的显示,如果在LCD上加上稳定的直流电压,不但不能正常显示,时间久了还会损坏LCD。一段LCD由背电极和段电极组成,需要显示时,在背电极和段电极之间加上合适的交流电压(通常使用方波)。为了调节对比度,可以调节方波中每半个周期中显示的时间(即占空比)来实现。
通常,为了节约驱动口,将多个背电极连在一起,形成公共背电极端:COM。另外,再将属于不同COM的段电极连接在一起,形成公共段电极端:SEG。当在某个COM和某个SEG之间加了足够的交流电压之后,就会将对应的段点亮(实际上是变黑)。
像万利的板子上使用的这种LCD,有4个COM,还有16个SEG。要想某一SEG显示时,需要在对应的SEG和COM之间加上足够的交流电压。在万利的板子上,COM驱动使用了两个电阻分压,输出电压为1/2Vcc,当不想让某位显示时,就将它的电压设置为1/2Vcc(通过设置IO口为高阻态来完成),这样加在对应的SEG和COM之间的电压只有1/2Vcc,不足以点亮对应的SEG。需要显示的,就将COM电压设置为0或者1,这样SEG电压跟COM电压相反的段就被点亮了(变黑),因为它们之间的电压为Vcc。通过定期扫描每个COM,即可稳定的在LCD上显示需要的图形了。需要显示字符或者数字时,自己先将对应的图案设计好,在显示时,发送到相应的SEG和COM上即可。但是如果使用100%的时间都驱动的话,会造成对比度太高,甚至出现不该显示的地方也显示了。因此在显示一段时间后,就将COM和SEG都设置为低,以关闭它的显示,降低对比度。通过调节关闭时间的长短(PWM),可以调节对比度。在下面的测试程序中,为了简化程序,使用了50%固定的占空比。
为了方便描述,我们把COM为低电平时点亮叫做正亮,COM为高电平时点亮叫做负亮。扫描每个COM分成4个阶段:正亮,关闭,负亮,关闭。因此对于本板子上的LCD驱动,总共有16个状态,每个COM都有上面所说的4个状态。我们每隔2ms就切换一次状态,这样整个扫描周期就是2*16=32ms,基本上感觉不到闪烁。
但是需要注意的是,这个LCD中的每个COM并不是刚好对应着显示图案中的一个字符的位置。每个COM都对应着每个显示字符中的相同4段!换句话说,要显示第一个字符位置的字符,每个COM都要被用到。因此,要改变某个字符位置的显示,就需要改变每次COM输出时对应的SEG中的4段。为此,建立一个缓冲区,当需要修改显示字符时,就修改缓冲区中的内容。这个缓冲区有4行,每行中有16个SEG,对应着一个COM。需要修改显示时,把每行中对应的4个SEG设置为需要的值,这样就实现了某个显示位置图案的修改。
为了显示字符,需要事先把需要显示的字符按照SEG和COM的分布,制作成数据保存起来,需要显示时,就把它复制到显示缓冲区中对应的位置去。另外,由于输入的参数是字符的ASCII码,因此还需要将ASCII码转换为对应的字符图案的索引值。使用一个专门的函数来完成这些转换和填充缓冲区,在需要修改显示数据时,就调用该函数。
为了方便大家对这个LCD的驱动方式和编程,下面简单的画一下驱动的波形图。
这里只画出2个SEG波形图,实际有16个SEG,只要你理解了2个SEG的,那么16个的也是一样的意思。如图所示,所有偶数阶段都是关闭显示阶段,这时COM和SEG都是0,将不会有段被点亮,通过调节关闭显示阶段所占的时间百分比,即可调节总体显示的对比度。SEG和COM之间电平相差1格的显示不出来或者浓度不够,而SEG和COM之间电平相差2格的则可以显示出来或者浓度较深。例如第一阶段中的SEG1和COM1之间相差2格,第三阶段中COM1和SEG1相差2格,因而SEG1和COM1之间的交叉点(即点1)被显示。又如第九阶段的SEG1和COM3之间相差2格,第十一阶段中的COM3和SEG1之间相差2格,因而SEG1和COM3之间的交叉点(即点5)被显示出来。其它点以此类推。
最后,再来看看万利板子上的LCD的COM和SEG之间的关系图,如下图所示。
图中显示,S0、S1、S2、S3属于第一个字符,在显示第一个字符时,只要在对应的COM选中时,将需要显示的SEG放在上面即可。其余几个字符类似。例如要显示一个数字3,则应该将A段、B段、C段、D段、G段、K段显示。某段显示,用1表示,不显示用0表示,得到的各段值如下:
X=0 I="0" A="1" DP="0"
F=0 H="0" J="0" B="1"
E=0 G="1" K="1" C="1"
L=0 M="0" N="0" D="1"
注意是低位在先的,把每行用十六进制来表示(高位在先),就是0x4,0x8,0xE,0x8。它们分别对应着COM1~COM3选中时S3~S0的输出值。为了方便管理,将这4个十六进制值合并为一个2字节的值0x48E8保存。其它各字符的构造方式相同。显示时,分别取出各段的值写入到对应的缓冲区去。
扫描LCD的程序流程如下:
①、COM1设置为低电平,其余COM为1/2高电平,设置PE口为需要的电平(16个段码),延时2ms;
②、4个COM、PE口均设置为低电平,关闭显示,延时2ms;
③、COM1设置为高电平,其余COM为1/2高电平,设置PE口为需要的电平(第一步16个段码的取反),延时2ms。
④、4个COM、PE口均设置为低电平,关闭显示,延时2ms;
然后对剩下的3个COM重复前面4个步骤,这样一个完整的扫描就完成了。
具体的实现代码比较长,这里就不再贴出了,在工程包中的LCD.C中可以查看。该测试工程包使用软件延时的方法来实现LCD的扫描,实际使用时,可以将扫描代码放在定时器中断中处理。该测试程序使用一个时钟功能来演示LCD的显示,显示为xx分xx秒。从这里下载整个测试的工程包:
离线
我也刚找出了一块
好有缘 ... 哈
离线
给板子洗了一个澡, 看起来漂亮多了, 自带的 stlink 好像不能用, 用个 jlink ob 怼上去.
段码液晶屏能用, 但是好些段不能显示.
离线
离线
调了一下分压电阻, 把 COM0/1/2/3 电压提到了 1.5V, 现在终于正常了!
GPIO 驱动段码液晶, 差不多成功了.
离线
COM1 - COM4 按照万利板子原来的方法接
SEG0 - SEG15 也是
IAR 工程源码下载: Segment_MyTestProject_20190329.7z
/******************************************************************
本程序只供学习使用,未经作者许可,不得用于其它任何用途
欢迎访问我的USB专区:http://group.ednchina.com/93/
欢迎访问我的blog: http://www.ednchina.com/blog/computer00
http://computer00.21ic.org
LCD.c file
作者:Computer-lov
建立日期: 2008.07.30
修改日期: 2008.07.31
版本:V1.2
版权所有,盗版必究。
Copyright(C) Computer-lov 2008-2018
All rights reserved
*******************************************************************/
#include "gpio.h"
unsigned short int LcdDispBuf[4]; //用来保存显示的段码缓冲
void LcdInit(void)
{
//4个COM口都设置为高阻输入状态
//COM4
GPIOC_MODE8=0; //输入模式
GPIOC_CNF8=1; //高阻输入模式
//COM3
GPIOC_MODE9=0; //输入模式
GPIOC_CNF9=1; //高阻输入模式
//COM2
GPIOC_MODE10=0; //输入模式
GPIOC_CNF10=1; //高阻输入模式
//COM1
GPIOC_MODE11=0; //输入模式
GPIOC_CNF11=1; //高阻输入模式
//PE口设置为50MHz推挽输出
GPIOE_CRL=0x33333333;
GPIOE_CRH=0x33333333;
//先初始化为无显示
LcdDispBuf[0]=0;
LcdDispBuf[1]=0;
LcdDispBuf[2]=0;
LcdDispBuf[3]=0;
}
//像万利的板子上使用的这种LCD,有4个COM,还有有16个SEG。
//要想某一SEG显示时,需要在对应的SEG和COM之间加上足够的电压,
//但是LCD它不像LED那样,有电压就一直亮的,它只能维持一段
//时间,然后内容就消失了。此时需要将电压反转再加到相应的
//SEG和COM之间。在万利的板子上,COM驱动使用了两个电阻分压,
//输出电压为1/2VCC,当不想让某位显示时,就将它的电压设置
//为1/2VCC(通过设置IO口为高阻态来完成),这样加在对应的
//SEG和COM之间的电压只有1/2VCC,不足以点亮对应的SEG。需要显示
//的,就将COM电压设置为0或者1,这样SEG电压跟COM电压相反的
//段就被点亮了(变黑)。通过定期扫描每个COM,即可稳定的在LCD
//上显示需要的图形了。需要显示字符或者数字时,需要自己将
//对应的图案设计好,需要显示时,发送到相应的SEG和COM上即可。
/*the varitronix LCD digit is:
A
-- ----------
X \/ |\ |I /|
F| H | J |B
| \ | / |
--G-- --K--
| /| \ |
E | L | N |C
| / |M \| _
----------- | |DP
D -
PE0 PE1 PE2 PE3 ...................................................... PE15
----------------------------------------------------------------------------------------
| | S0 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 | S9 | S10| S11| S12| S13| S14| S15|
----------------------------------------------------------------------------------------
| COM1 | 1X | 1I | 1A | 1DP| 2X | 2I | 2A | 2DP| 3X | 3I | 3A | 3DP| 4X | 4I | 4A | 4DP|
----------------------------------------------------------------------------------------
| COM2 | 1F | 1H | 1J | 1B | 2F | 2H | 2J | 2B | 3F | 3H | 3J | 3B | 4F | 4H | 4J | 4B |
----------------------------------------------------------------------------------------
| COM3 | 1E | 1G | 1K | 1C | 2E | 2G | 2K | 2C | 3E | 3G | 3K | 3C | 4E | 4G | 4K | 4C |
----------------------------------------------------------------------------------------
| COM4 | 1L | 1M | 1N | 1D | 2L | 2M | 2N | 2D | 3L | 3M | 3N | 3D | 4L | 4M | 4N | 4D |
----------------------------------------------------------------------------------------
A LCD character coding is based on the following matrix:
{ X , F , E , L }
{ I , H , G , M }
{ A , J , K , N }
{ DP, B , C , D }
The characher A for example is:
{ 0 , 1 , 1 , 0 }
{ 0 , 0 , 1 , 0 }
{ 1 , 0 , 1 , 0 }
{ 0 , 1 , 1 , 0 }
-------------------
= 4 9 F 0 hex
=> 'A' = 0x49F0 */
#if 0
const unsigned short int Letter[26]={0x49F0,0x01F8,0x4118,0x08F8,0x4178,0x4170,0x41D8,0x09F0,0x600A,
0x0888,0x0534,0x0118,0x0F90,0x0B94,0x4998,0x4970,0x499C,0x4974,
0x41E8,0x6002,0x0998,0x0511,0x299A,0x0605,0x0601,0x4409};
const unsigned short int Number[10]={0x4998,0x0880,0x4878,0x48E8,0x09E0,0x41E8,0x41F8,0x4880,0x49F8,0x49E8};
const unsigned short int Arrow[2]={0x0005,0x0600}; // {Upstair,Downstair}
#endif
const unsigned short int Letter[26]={0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, };
const unsigned short int Number[10]={0x00003, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, };
const unsigned short int Arrow[2]={0x0000,0x0000}; // {Upstair,Downstair}
//根据LCD需要显示的字符设置缓冲区的内容。
//入口参数c为需要显示的字符。
//入口参数p为需要显示的位置,从左往右依次为0,1,2,3。
//入口参数dot为是否需要显示小数点和单引号,1只显示小数点,
//2只显示单引号,3为小数点和单引号同时显示
void LcdSetChar(unsigned char c, unsigned char p, unsigned char dot)
{
//定义一个Temp变量来保存查表的结果
unsigned short int Temp;
unsigned int tmp_num_display_buf[4];
switch(c)
{
case '0':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x03;
tmp_num_display_buf[2] = 0x01;
tmp_num_display_buf[3] = 0x03;
break;
case '1':
tmp_num_display_buf[0] = 0x00;
tmp_num_display_buf[1] = 0x02;
tmp_num_display_buf[2] = 0x00;
tmp_num_display_buf[3] = 0x02;
break;
case '2':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x01;
tmp_num_display_buf[2] = 0x02;
tmp_num_display_buf[3] = 0x03;
break;
case '3':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x02;
tmp_num_display_buf[2] = 0x02;
tmp_num_display_buf[3] = 0x03;
break;
case '4':
tmp_num_display_buf[0] = 0x00;
tmp_num_display_buf[1] = 0x02;
tmp_num_display_buf[2] = 0x03;
tmp_num_display_buf[3] = 0x02;
break;
case '5':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x02;
tmp_num_display_buf[2] = 0x03;
tmp_num_display_buf[3] = 0x01;
break;
case '6':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x03;
tmp_num_display_buf[2] = 0x03;
tmp_num_display_buf[3] = 0x01;
break;
case '7':
tmp_num_display_buf[0] = 0x00; //点D
tmp_num_display_buf[1] = 0x02; //C E
tmp_num_display_buf[2] = 0x00; //G F
tmp_num_display_buf[3] = 0x03; //B A
break;
case '8':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x03;
tmp_num_display_buf[2] = 0x03;
tmp_num_display_buf[3] = 0x03;
break;
case '9':
tmp_num_display_buf[0] = 0x01;
tmp_num_display_buf[1] = 0x02;
tmp_num_display_buf[2] = 0x03;
tmp_num_display_buf[3] = 0x03;
break;
}
if((p >= 0) && (p <= 6))
{
LcdDispBuf[0] |= tmp_num_display_buf[0] << (p*2);
LcdDispBuf[1] |= tmp_num_display_buf[1] << (p*2);
LcdDispBuf[2] |= tmp_num_display_buf[2] << (p*2);
LcdDispBuf[3] |= tmp_num_display_buf[3] << (p*2);
}
else
{
}
}
//每隔一段时间要调用一次该函数,例如2ms
//可以在一个定时器中调用该函数来定期刷新
//这里为了演示程序,使用软件延时的方法
void LcdScan(void)
{
static unsigned int i=1;
static unsigned int Off=0;
//一个完整的驱动周期,需要在COM和SEG之间正负
//电压交替一次。我们把COM输出低时定义为前半
//个周期,COM输出为高时定义为后半个周期。
//为了能够调节显示的对比度,需要在每个半周期
//中控制显示时间的长短。这里为了程序简单,
//使用了50%的占空比,即在每个半周期的前半段
//正常显示,后半段不显示,将COM和SEG都设置为
//低电平。该过程由Off标志来决定,当Off标志
//为1时,则是后半段的关闭过程,将COM和SEG置0
//后,就返回。当Off标志为0时,则处于前半段,
//就选中某个COM显示。这样,对于一个COM,完整
//的扫描周期有4个阶段:COM负亮,关,COM正亮,关。
//由于有4个COM,因此整个扫描周期就有16个阶段。
if(Off==1)
{
Off=0;
//先将4个COM都设置为高阻状态
GPIOC_MODE8=0; //输入模式
GPIOC_CNF8=1; //高阻输入模式
GPIOC_MODE9=0; //输入模式
GPIOC_CNF9=1; //高阻输入模式
GPIOC_MODE10=0; //输入模式
GPIOC_CNF10=1; //高阻输入模式
GPIOC_MODE11=0; //输入模式
GPIOC_CNF11=1; //高阻输入模式
//然后将所有SEG设置为低电平
GPIOE_ODR=0;
//再将所有COM设置为低电平
GPIOC_CLR=(1<<8)|(1<<9)|(1<<10)|(1<<11);
GPIOC_MODE8=3; //50M输出模式
GPIOC_CNF8=0; //推挽模式
GPIOC_MODE9=3; //50M输出模式
GPIOC_CNF9=0; //推挽模式
GPIOC_MODE10=3; //50M输出模式
GPIOC_CNF10=0; //推挽模式
GPIOC_MODE11=3; //50M输出模式
GPIOC_CNF11=0; //推挽模式
return;
}
else
{
Off=1;
//先将4个COM都设置为高阻状态
GPIOC_MODE8=0; //输入模式
GPIOC_CNF8=1; //高阻输入模式
GPIOC_MODE9=0; //输入模式
GPIOC_CNF9=1; //高阻输入模式
GPIOC_MODE10=0; //输入模式
GPIOC_CNF10=1; //高阻输入模式
GPIOC_MODE11=0; //输入模式
GPIOC_CNF11=1; //高阻输入模式
}
switch(i)
{
case 1:
//COM4的前半个周期
GPIOE_ODR=LcdDispBuf[3]; //将SEG码输出
GPIOC_CLR=1<<8; //COM4输出低电平
GPIOC_CNF8=0; //推挽输出
GPIOC_MODE8=3; //50MHz输出模式
break;
case 2:
//COM4的后半个周期
GPIOE_ODR=~LcdDispBuf[3]; //将SEG码取反
GPIOC_SET=1<<8; //COM4输出高电平
GPIOC_CNF8=0; //推挽输出
GPIOC_MODE8=3; //50MHz输出模式
break;
case 3:
//COM3的前半个周期
GPIOE_ODR=LcdDispBuf[2]; //将SEG码输出
GPIOC_CLR=1<<9; //COM3输出低电平
GPIOC_CNF9=0; //推挽输出
GPIOC_MODE9=3; //50MHz输出模式
break;
case 4:
//COM3的后半个周期
GPIOE_ODR=~LcdDispBuf[2]; //将SEG码取反
GPIOC_SET=1<<9; //COM3输出高电平
GPIOC_CNF9=0; //推挽输出
GPIOC_MODE9=3; //50MHz输出模式
break;
case 5:
//COM2的前半个周期
GPIOE_ODR=LcdDispBuf[1]; //将SEG码输出
GPIOC_CLR=1<<10; //COM2输出低电平
GPIOC_CNF10=0; //推挽输出
GPIOC_MODE10=3; //50MHz输出模式
break;
case 6:
//COM2的后半个周期
GPIOE_ODR=~LcdDispBuf[1]; //将SEG码取反
GPIOC_SET=1<<10; //COM2输出高电平
GPIOC_CNF10=0; //推挽输出
GPIOC_MODE10=3; //50MHz输出模式
break;
case 7:
//COM1的前半个周期
GPIOE_ODR=LcdDispBuf[0]; //将SEG码输出
GPIOC_CLR=1<<11; //COM1输出低电平
GPIOC_CNF11=0; //推挽输出
GPIOC_MODE11=3; //50MHz输出模式
break;
case 8:
//COM1的后半个周期
GPIOE_ODR=~LcdDispBuf[0]; //将SEG码取反
GPIOC_SET=1<<11; //COM1输出高电平
GPIOC_CNF11=0; //推挽输出
GPIOC_MODE11=3; //50MHz输出模式
i=0;
break;
default :
i=0;
}
//切换到下一状态
i++;
}
最近编辑记录 天边那朵白云 (2019-03-29 15:16:33)
离线