PCF8576静态驱动段码LCD

硬件原理图

PCF8576原理图如图所示,与STM32F2接口为I2C,其中段码屏EDC190为静态动屏,PIN1与PIN40共用COM引脚,S0~S31共32段SEG。驱动芯片PC8576采用5V供电VDD,中间连接电位器来调节VLCD使其达到段码屏的额定供电电压,OSC接地表示使用内部时钟,A0~A2接地表示I2C地址为0x70,SA0接地表示唯一的从机地址为0x70。
Schematic
这样的原理图有个明显的问题,段码屏的SEG没有按照PCF8576的ram存储方式进行排序,编程起来会比较麻烦。

程序设计

宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "stm32f2xx.h"
/*************************************************************************************************
connection PCF8576 to EDC190
A
-------- -------- COL -------- --------
| | | | |-| | | | |
F | 1 | B | 2 | |-| | 3 | | 4 |
| G | | | | | | |
|--------| |--------| |-| |--------| |--------|
E | | C | | |-| | | | |
| | | | | | | |
| | |-| | | |-| | | |-| | |
-------- |-| -------- |-| -------- |-| --------
D DP1 DP2 DP3

**************************************************************************************************/

#define CONTINUECMD 0x80
#define LASTCMD 0x00
#define PCF8576_CMD_MODE_SET 0x49 //0b0100 1001 //Normal-Power,enbale display status,static drive mode
#define PCF8576_CMD_CLEAR_LCD 0x41 //0b0100 0001 //Normal-Power,enbale display status,static drive mode
#define PCF8576_CMD_LOAD_DATA 0x00
#define PCF8576_CMD_DEVICE_SEL 0x60 //0b0110 0000 //subaddr = 000
#define PCF8576_CMD_BANK_SEL 0x78 //0b0111 1000
#define PCF8576_CMD_BINK 0x70 //0b0111 0000 //normal blinking,frequency off

#define PCF8576_DEFAULT_SLAVE_ADDRESS 0x70
#define PCF8576_CSubAddr 0xE0
#define PCF8576_LSubAddr 0x00
#define PCF8576_I2C_SPEED 80000 //ÒªÇóËÙÂÊ<125k

#define OFFSETNUM 39

#define LCD_DP1REG2 0x10
#define LCD_DP2REG2 0x01
#define LCD_DP3REG3 0x10
#define LCD_COLREG0 0x01

正常应该按照LCD屏段码的A~G加上一个点DP为一个字节,但一开始做原理图以及PCB的时候没考虑这么多,所以是按照走线方便的原则随意布线的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
1A 1B 1C 1D 1E 1F 1G DP1
S14 S15 S18 S17 S16 S13 S12 S19

2A 2B 2C 2D 2E 2F 2G DP2
S9 S8 S22 S21 S20 S10 S11 S23

3A 3B 3C 3D 3E 3F 3G DP3
S4 S3 S26 S25 S24 S5 S6 S27

4A 4B 4C 4D 4E 4F 4G COL
S0 S31 S30 S29 S28 S1 S2 S7

7 6 5 4 3 2 1 0 BYTE1
15 14 13 12 11 10 9 8 BYTE2
23 22 21 20 19 18 17 16 BYTE3
31 30 29 28 27 26 25 24 BYTE4*/
const uint8_t LcdSegTab[4][8][2] = {
{{1,6},{1,7},{2,2},{2,1},{2,0},{1,5},{1,4},{2,3}},//1A 1B 1C 1D 1E 1F 1G DP1
{{1,1},{1,0},{2,6},{2,5},{2,4},{1,2},{1,3},{2,7}},//2A 2B 2C 2D 2E 2F 2G DP2
{{0,4},{0,3},{3,2},{3,1},{3,0},{0,5},{0,6},{3,3}},//3A 3B 3C 3D 3E 3F 3G DP3
{{0,0},{3,7},{3,6},{3,5},{3,4},{0,1},{0,2},{0,7}}//4A 4B 4C 4D 4E 4F 4G COL
};
const uint8_t LcdAscTab[] = {
//0b dp g f e d c b a
0x3f, //0b0011 1111 `0`
0x06, //0b0000 0110 `1`
0x5b, //0b0101 1011 `2`
0x4f, //0b0100 1111 `3`
0x66, //0b0110 0110 `4`
0x6d, //0b0110 1101 `5`
0x7d, //0b0111 1101 `6`
0x07, //0b0000 0111 `7`
0x7f, //0b0111 1111 `8`
0x6f, //0b0110 1111 `9`
0x77, //0b0111 0111 `A`
0x7c, //0b0111 1100 `B`
0x39, //0b0011 1001 `C`
0x5e, //0b0101 1110 `D`
0x79, //0b0111 1001 `E`//0x45 0x52 0x52 0x4f
0x79, //0b0111 1001 `F`
0x6f, //0b0110 1111 `G`
0x76, //0b0111 0110 `H`
0x76, //0b0111 0110 `I`//
0x00, //0b0111 0110 `J`//
0x00, //0b0111 0110 `K`//
0x38, //0b0011 1000 `L`
0x00, //0b0011 1000 `M`//
0x37, //0b0011 0111 `N`//
0x3f, //0b0011 1111 `O`
0x73, //0b0111 0011 `P`
0x67, //0b0110 0111 `Q`
0x77, //0b0111 0111 `R`
0x6d, //0b0110 1101 `S`
0x00, //0b0110 1101 `T`
0x3e, //0b0011 1110 `U`
0x39, //0b0011 1001 `[`
0x0f, //0b0000 1111 `]`
};

LcdSegTab定义为三维数组,第一维对应四个段码,第二维对应0~7共8段,第三维包含两个元素,{x,y},其中x代表0~3共4个代表SEG所在RAM的地址位置(byte1~byte4),y代表0~7表示所在byte-x位置的第几位。
LcdAscTab定义为一维数组,表示字符asc码对应的段码十六进制数,按照DP-G-F-E-D-C-B-A由高位到低位表示,例如"0"点亮时,其A-B-C-D-E-F位为1,其余为0,换算成十六进制为0x3f,以此类推。其中未表示方便,有些无用的字符也定义出来方便编程。

函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uint8_t lcd_write_reg(uint8_t addr,uint8_t data)
{
lcd_initpara();

I2C_GenerateSTART(I2C2, ENABLE);//产生起始位
if(LCD_I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT) == 0) return 0;

I2C_Send7bitAddress(I2C2, PCF8576_DEFAULT_SLAVE_ADDRESS, I2C_Direction_Transmitter);//发送器件地址0x70
if(LCD_I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == 0) return 0;

I2C_SendData(I2C2,PCF8576_CMD_MODE_SET | CONTINUECMD);//发送mode设置0x49,开启显示
if(LCD_I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) == 0) return 0;

I2C_SendData(I2C2,addr);//发送地址
if(LCD_I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) == 0) return 0;
I2C_SendData(I2C2,data);//发送数据
if(LCD_I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) == 0) return 0;

I2C_GenerateSTOP(I2C2, ENABLE);//产生停止信号
return 1;
}
按照驱动芯片PCF8576的I2C协议,需要MCU在一个发送周期内根据具体要求发送具体命令和数据来与芯片通讯,根据STM32F2系列的I2C库函数很容易实现。
下面主要讲一下具体显示字符的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define BIT(n)  (1 << (7 - n))  
uint8_t lcdreg[4]={0,0,0,0};

void lcd_displays_string(char *pstr)
{
uint8_t ch;
uint8_t i,j;
for(i = 0;i < 4;i++)
{
if(pstr[i] < 0x40)
{
ch = pstr[i] - 0x30;
}
else
{
ch = pstr[i] - 0x37;
}
for(j = 0;j < 8;j++)
{
//if(LcdAscTab[ch] & (0x80 >> j))
if(LcdAscTab[ch] & (0x01 << j))
{
//lcdreg[LcdSegTab[i][7-j][0]] |= BIT( LcdSegTab[i][7-j][1]);
lcdreg[LcdSegTab[i][j][0]] |= BIT(LcdSegTab[i][j][1]);
}
else
{
//lcdreg[LcdSegTab[i][7-j][0]] &= ~BIT( LcdSegTab[i][7-j][1]);
lcdreg[LcdSegTab[i][j][0]] &= ~BIT(LcdSegTab[i][j][1]);
}
}
}
lcd_write_reg(0,lcdreg[0]);
lcd_write_reg(8,lcdreg[1]);
lcd_write_reg(16,lcdreg[2]);
lcd_write_reg(24,lcdreg[3]);
}
首先需要明确一个小函数 BIT(n),操作时 |=BIT(n)表示第n位置1, &=~BIT(n)表示第n位置0.明白了这个函数再看具体的display函数就清楚多了,由于字符0的十六进制为0x30,为了让LcdAscTab从0开始,一开始判断如果字符属于0~9则将其减去0x30.大循环里依次填充0~3共4个段的寄存器内容,根据不同的字符显示填充不同的置0还是置1位,小循环则根据LcdAscTab表内置1的位来操作lcdreg,lcdreg共4字节,代表第0地址开始1个字节,第8地址开始的第2个字节,第16地址开始的第3个字节,第24地址开始的第3个字节,共32位数据。依次填充到lcdreg中。并利用lcd_write_reg函数写到对应RAM起始地址的内存里。
其中要注意BIT(n)定义为 (1 << (7-n)),该处经过单步调试发现原本定义的(1<<n)有问题而更改。其中也可以用注释里的形式来表示,看哪一种比较好理解了。
以上就是具体的驱动函数实现。

显示效果

最终的现实效果如图:
Display