1602LCD 是工業上常用的模塊, 在工廠交通運輸設備上經常能見到. 驅動晶元為 HD44780, 1602LCD 的字元顯示為兩行, 每行16個字元, 字元基於5×8的像素矩陣 ...
目錄
- 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU簡介
- 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode開發環境
- 普冉PY32系列(三) PY32F002A資源實測 - 這個型號不簡單
- 普冉PY32系列(四) PY32F002A/003/030的時鐘設置
- 普冉PY32系列(五) 使用JLink RTT代替串口輸出日誌
- 普冉PY32系列(六) 通過I2C介面驅動PCF8574擴展的1602LCD
1602 LCD
1602LCD 是工業上常用的模塊, 在工廠交通運輸設備上經常能見到. 驅動晶元為 HD44780, 1602LCD 的字元顯示為兩行, 每行16個字元, 字元基於5×8的像素矩陣
PIN腳功能
Pin | Name | Function |
---|---|---|
1 | Ground | 地 (0V) |
2 | Vcc | 供電 5V (4.7V – 5.3V), 註意不能用3.3V供電 |
3 | Vo / VEE | 對比度調節. 連接一個可變電阻, 過高無法分辨顯示的字元, 過低字元太淡無顯示 |
4 | RS | 寄存器選擇(Register Select), 低電平為命令寄存器, 高電平為數據寄存器 |
5 | Read/write | 讀寫選擇, 低電平寫入, 高電平讀取 |
6 | Enable | EN 閑時處於低電平, 當需要執行指令前幾個毫秒將EN拉高, 執行完再拉低 |
7~14 | DB0~DB7 | 8-bit 數據pin |
15 | Led+ | LED 背光電源 |
16 | Led- | LED 背光接地 |
RS (Register Select)
1602LCD有兩組寄存器, 命令寄存器和數據寄存器, RS用於數據和命令寄存器的切換
實際使用時, 需要配合 EN 和 R/W
- 在 EN = 1, R/W = 0, RS = 1 時, 往數據寄存器寫入的字元會用於展示.
- EN = 1, R/W = 0, RS = 0 時, 往命令寄存器寫入, 用於發送指令, 例如: 初始化, 清空屏幕, 設置游標位置, 控制顯示等
指令編碼
以下是各指令位的說明
/*-------------------------------------------------------------
* Instruction D7 D6 D5 D4 D3 D2 D1 D0
* ==============================================
* Display clear 0 0 0 0 0 0 0 1
* Cursor home 0 0 0 0 0 0 1 *
* Entry Mode Set 0 0 0 0 0 1 I/D S
* Display On/Off 0 0 0 0 1 D C B
* Curs/Disp shift 0 0 0 1 S/C R/L * *
* Function Set 0 0 1 DL N F * *
* CG RAM addr set 0 1 ---------Acg---------
* DD RAM addr set 1 -------------Add---------
*
* Meaning:
* * - nonvalid bit
* Acg - CG RAM address (CHARACTER GENERATOR)
* Add - DD RAM address (DATA DISPLAY)
* AC - adress counter
*
* I/D - 1-increment, 0-decrement
* S - 1-display shift, 0-no display shift
* D - 1-display ON, 0-display OFF
* C - 1-cursor ON, 0-cursor OFF
* B - 1-blink ON, 0-blink OFF
* S/C - 1-display shift, 0-cursor movement
* R/L - 1-right shift, 0-left shift
* DL - 1-8 bits data transfer, 0-4 bits data transfer
* N - 1-1/16 duty, 0-1/8 or 1/11 duty
* F - 1-5x10 dot matrix, 0-5x7 dot matrix
* BF - 1-internal operation in progress, 0-display ready
*
\**************************************************************/
配合 EN = 1, R/W = 0, RS = 0 時, 往命令寄存器寫入, 可以執行以下指令
No. | Hex | Binary | 命令說明 |
---|---|---|---|
1 | 01 | 0000 0001 | 清除顯示 |
2 | 02 | 0000 001x | 游標回原位 |
3 | 04 | 0000 0100 | 向左移動游標(兩個bit分別控制方向左右, 游標還是屏幕) |
4 | 06 | 0000 0110 | 向右移動游標 |
5 | 05 | 0000 0101 | 向右移顯示 |
6 | 07 | 0000 0111 | 向左移動顯示 |
7 | 08 | 0000 1000 | 顯示關閉, 游標關閉(三個bit分別控制屏幕顯示, 游標顯示, 游標閃爍) |
8 | 0A | 0000 1010 | 顯示關閉, 游標打開 |
9 | 0C | 0000 1100 | 顯示打開, 游標關閉 |
10 | 0E | 0000 1110 | 顯示打開, 游標閃爍 |
11 | 0F | 0000 1111 | 顯示打開, 游標閃爍 |
12 | 10 | 0001 00xx | 將游標位置向左移動(兩個bit分別控制游標還是屏幕, 左移還是右移) |
13 | 14 | 0001 01xx | 將游標位置向右移動 |
14 | 18 | 0001 10xx | 將整個顯示屏向左移動 |
15 | 1C | 0001 11xx | 將整個顯示屏向右移動 |
16 | 28 | 0010 10xx | 4位, 2行, 5x8矩陣(三個bit分別控制4位還是8位,一行還是兩行,5x10還是5x8) |
17 | 20 | 0010 00xx | 4位, 1行, 5x8矩陣 |
18 | 38 | 0011 10xx | 8位, 2行, 5×8 |
19 | 4x | 01xx xxxx | 設置 CGRAM 地址, 後面6個bit是地址, 在這個指令之後發送或接收數據 |
20 | 8x | 1xxx xxxx | 設置 DDRAM 地址, 後面7個bit是地址, 在這個指令之後發送或接收數據 |
21 | 80 | 1000 0000 | 將游標強制移動到開頭(第一行) |
22 | C0 | 1100 0000 | 將游標強制移動到開頭(第二行) |
顯示自定義字元
自定義字元要在 CG-RAM 中設置. CG-RAM地址從 0x40 開始, 大小為 64 byte, 可以創建8個字元, 每個字元8個byte. 在這些地址創建字元後, 就可以在LCD中顯示.
CG-RAM 地址和命令
No. | Addr. | Command |
---|---|---|
0 | 0x40 | 0 |
1 | 0x48 | 1 |
2 | 0x56 | 2 |
3 | 0x64 | 3 |
4 | 0x72 | 4 |
5 | 0x80 | 5 |
6 | 0x88 | 6 |
7 | 0x96 | 7 |
上面的表中可以看到每個字元的起始地址及其列印命令, 例如第一個字元的地址是 [0x40, 0x47], 使用命令0
可以輸出這個字元, 第二個字元的地址[0x48, 0x55], 使用命令1
輸出.
自定義字元時, 每個字元是5x8的點陣, 5是列數, 8是行數. 對應字母b
的點陣可以表示為
char b[7] = {0x10,0x10,0x16,0x19,0x11,0x11,0x1E};
將其發送到對應的地址就可以創建字元.
I2C 介面 PCF8574 擴展板
因為1602LCD本身刷新率不高, 為節省IO, 可以通過 PCF8574 擴展模塊, 將I2C協議轉為並口輸出. 1602屏直連MCU, 需要至少7個IO(RS, R/W, EN, 4位對應4根數據線)才能驅動起來, 使用 PCF8574 模塊只需要2個IO口.
PCF8574 是一個IIC協議的IO口擴展晶元, 包含一個8位準雙向口, 一個匯流排介面, 還有三條地址線. 每個IO口可以單獨的分配為輸入或者輸出. 作為輸入時, 可以用於監控中斷或者鍵盤. 作為輸出時, 可以用於驅動LED. 可以通過單獨的寄存器讀取輸入埠狀態或者配置輸出埠狀態. PCF8574 的三個地址管腳, 可以分配8個地址, 也就是同一個系統中可以最多存在8個這樣的模塊.
PCF8574 電流消耗很低, 並且輸出鎖存, 具有大電流驅動能力, 可直接驅動LED. 還帶一個中斷接線, 可以作為MCU的外部中斷. 通過中斷通知MCU 是否有數據輸入, 因此 PCF8574 也可以作為一個單被控器.
通過 PCF8574 控制 1602LCD, 可以將命令發送到 I2C 介面, PCF8574 的輸出與 1602LCD 的連接為
[tu]
VSS = Ground
VDD = Connects to VCC on the I2C header
VO = Display contrast. Connects to the potentiometer on the module
RS = P0 on PCF8574
RW = P1 on PCF8574
E = P2 on PCF8574
D0 – D3 no connects
D4 = P4 on PCF8574
D5 = P5 on PCF8574
D6 = P6 on PCF8574
D7 = P7 on PCF8574
A = Backlight Anode. Typically connects to 5V.
K = Backlight Cathode. Connects to ground
基於 PY32F0 的演示
硬體
- 已焊接 PCF8574 擴展模塊的 1602LCD
- 基於 PY32F0 系列的開發板
- USB2TTL 用於觀察地址輸出
接線
大多數 PY32F0 都有 PF1/PF0, 可以通過復用將I2C介面換成其他的pin腳.
註意 PCF8574+1602LCD 供電電壓為5V, 3.3V供電無法驅動字元顯示. 如果 PY32F0 使用 3.3V 供電, 則需要另接5V電源, 與 PY32F0 共地即可.
PY32 PCF8574 1602 LCD USB2TTL
PF1/PA9 SCL
PF0/PA10 SDA
VCC -> 5V
GND GND -> GND GND
PA2 RX
PA3 TX
軟體
以下的實現基於LL庫, 完整的示例代碼位於
https://github.com/IOsetting/py32f0-template/tree/main/Examples/LL/I2C/PCF8574_1602LCD
指令和地址的巨集定義
/* I2C address
* - 7 bit slave address, left aligned, bits 7:1 are used, LSB bit is not used
* - 0x4E or 0x7E
*/
#define LCD1602_I2C_ADDR 0x7E
/* Delay in millisecond */
#define LCD1602_DELAY 5
/* Register selection */
#define PIN_RS (1 << 0)
/* Read/Write */
#define PIN_RW (1 << 1)
/* Chip enable */
#define PIN_EN (1 << 2)
/* Back light - might not be available on some PCF8574 modules */
#define BACKLIGHT (1 << 3)
/* Clear display */
#define LCD1602_CMD_CLEAR_DISPLAY 0b00000001
/* Move cursor home */
#define LCD1602_CMD_HOME 0b00000010
// Entry Mode, Set cursor/display moving direction
#define LCD1602_CMD_DIRECTION_RIGHT 0b00000110
#define LCD1602_CMD_DIRECTION_LEFT 0b00000100
#define LCD1602_CMD_DIRECTION_RIGHT_SHIFT 0b00000111
#define LCD1602_CMD_DIRECTION_LEFT_SHIFT 0b00000101
// Display mode
#define LCD1602_CMD_MODE_OFF 0b00001000
#define LCD1602_CMD_MODE_ON_CURSOR_OFF 0b00001100
#define LCD1602_CMD_MODE_ON_CURSOR_ON 0b00001110
#define LCD1602_CMD_MODE_ON_CURSOR_BLNK 0b00001111
// Cursor/Display Shift
#define LCD1602_CMD_CURSOR_MOVE_LEFT 0b00010000
#define LCD1602_CMD_CURSOR_MOVE_RIGHT 0b00010100
#define LCD1602_CMD_DISPLAY_SHIFT_LEFT 0b00011000
#define LCD1602_CMD_DISPLAY_SHIFT_RIGHT 0b00011100
/* Function set: 4-bit, 1 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_4B_1L_5X8 0b00100000
/* Function set: 4-bit, 2 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_4B_2L_5X8 0b00101000
/* Function set: 8-bit, 1 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_8B_1L_5X8 0b00110000
/* Function set: 8-bit, 2 row, 5X8 matrix */
#define LCD1602_CMD_FUNC_8B_2L_5X8 0b00111000
/* Set/Read CGRAM address */
#define LCD1602_CMD_CGRAM_ADDR 0b01000000
/* Set/Read DDRAM address */
#define LCD1602_CMD_DDRAM_ADDR 0b10000000
/* First row address */
#define LCD1602_DDRAM_ROW0 0b10000000
/* Second row address */
#define LCD1602_DDRAM_ROW1 0b11000000
基礎方法
發送指令, 數據和文本
ErrorStatus LCD_SendInternal(uint8_t lcd_addr, uint8_t data, uint8_t flags)
{
ErrorStatus status;
for(;;)
{
status = BSP_I2C_IsDeviceReady(lcd_addr, 5000);
if(status == SUCCESS)
{
break;
}
}
uint8_t up = data & 0xF0;
uint8_t lo = (data << 4) & 0xF0;
uint8_t data_arr[4];
data_arr[0] = up|flags|BACKLIGHT|PIN_EN;
data_arr[1] = up|flags|BACKLIGHT;
data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
data_arr[3] = lo|flags|BACKLIGHT;
status = BSP_I2C_MasterTransmit(lcd_addr, data_arr, sizeof(data_arr), 5000);
LL_mDelay(LCD1602_DELAY);
return status;
}
void LCD_SendCommand(uint8_t lcd_addr, uint8_t cmd)
{
LCD_SendInternal(lcd_addr, cmd, 0);
}
void LCD_SendData(uint8_t lcd_addr, uint8_t data)
{
LCD_SendInternal(lcd_addr, data, PIN_RS);
}
void LCD_SendString(uint8_t lcd_addr, char *str)
{
while (*str)
{
LCD_SendData(lcd_addr, (uint8_t)(*str));
str++;
}
}
初始化設置
void LCD_Init(uint8_t lcd_addr)
{
// need at least 40ms after power rises above 2.7V
LL_mDelay(50);
// start in 8-bit mode, 3 commands
LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_8B_1L_5X8);
// set it to 4-bit mode, interface is still 8-bit
LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_4B_1L_5X8);
// now interface is 4-bit, set it to 2 lines and 5x8 font
LCD_SendCommand(lcd_addr, LCD1602_CMD_FUNC_4B_2L_5X8);
// display & cursor home
LCD_SendCommand(lcd_addr, LCD1602_CMD_HOME);
// display on, right shift, underline off, blink off
LCD_SendCommand(lcd_addr, LCD1602_CMD_MODE_ON_CURSOR_BLNK);
// move direction right
LCD_SendCommand(lcd_addr, LCD1602_CMD_DIRECTION_RIGHT);
// clear display (optional here)
LCD_SendCommand(lcd_addr, LCD1602_CMD_CLEAR_DISPLAY);
}
功能操作
清除屏幕
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CLEAR_DISPLAY);
移動游標, 輸出文字
// move cursor to 0,0
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW0|0);
LCD_SendString(LCD1602_I2C_ADDR, " Using 1602 LCD");
// move cursor to 1,0
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW1|0);
LCD_SendString(LCD1602_I2C_ADDR, " over I2C bus");
通過 CGRAM 設置 自定義字元
// CGRAM test
for (i = 0; i < 8; i++)
{
LCD_SetCGRAM(LCD1602_I2C_ADDR, i, &cgrom[i * 8]);
}
展示自定義字元
for (i = 0; i < 8; i++)
{
LCD_SendData(LCD1602_I2C_ADDR, i);
LL_mDelay(200);
}
顯示整體左右平移
// Shift display test
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CLEAR_DISPLAY);
LL_mDelay(500);
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW0|9);
LCD_SendString(LCD1602_I2C_ADDR, "Shift");
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_DDRAM_ROW1|8);
LCD_SendString(LCD1602_I2C_ADDR, "<<<->>>");
LL_mDelay(500);
for (i = 0; i < 8; i++)
{
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_DISPLAY_SHIFT_LEFT);
LL_mDelay(200);
}
LL_mDelay(500);
for (i = 0; i < 8; i++)
{
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_DISPLAY_SHIFT_RIGHT);
LL_mDelay(200);
}
左右移動游標
// Move cursor test
for (i = 0; i < 11; i++)
{
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CURSOR_MOVE_LEFT);
LL_mDelay(200);
}
LL_mDelay(500);
for (i = 0; i < 12; i++)
{
LCD_SendCommand(LCD1602_I2C_ADDR, LCD1602_CMD_CURSOR_MOVE_RIGHT);
LL_mDelay(200);
}
常見問題
1. 屏幕不顯示
不顯示的原因有很多, 如果確認代碼和接線無誤, 可能的原因有
- 檢查1602LCD的供電電壓是不是5V, 在3.3V下無法驅動, 只有背光沒有字元
- 檢查I2C地址是否正確. 查看串口掃描到的實際的設備I2C地址, 是否和程式中的地址一致, 通常情況下, PCF8574T 的地址是 0x4E, PCF8574AT 的地址是 0x7E
2. 字元顯示亂碼
HD44780對啟動的指令順序和延時是有要求的, 可以參考其數據手冊的P45, 如果延時不夠或指令順序不正確, 會導致屏幕未進入4-bit模式而導致顯示錯亂. 對於部分屏幕, 啟動時需要增大延時, 如果等待時間不足, 會導致輸出亂碼.
參考
- https://www.electronicsforu.com/technology-trends/learn-electronics/16x2-lcd-pinout-diagram
- https://github.com/cehberlin/bajos/blob/master/bajos/PLATFORMS/CHARON/lcd.h
- 設置CG-RAM https://github.com/h0nzZik/school/blob/master/2013_fall/pv198/examples/avr-src-pv198/avr-src-pv198/04.04.lcd/lcd.c
- https://github.com/afiskon/stm32-i2c-lcd-1602/blob/master/Src/main.c