第二十三章 LCD1602液晶顯示

1. 導入

LCD1602(16×2 字符型液晶)使用基於HD44780控制器的命令集,支持8位或4位數據總線,常用於菜單、狀態與調試信息顯示。本章以“4位總線,RW接地(只寫)”方案實現穩定驅動,提供完整API:初始化、定位、打印、清屏、自定義字符(CGRAM)。

目標:

  • 掌握LCD1602引腳與對比度、電源接法
  • 理解4位時序與初始化流程
  • 實現常用API:定位、打印、清屏、滾動/移位
  • 擴展:顯示時鐘/温度、自定義字符“°”

2. 硬件設計

  • 供電與對比度
  • VSS→GND,VDD→+5V
  • V0(對比度)→10k電位器中點(兩端接VCC/GND)
  • 背光(如有):A→+5V,K→GND(可串電阻限流)
  • 數據/控制引腳(4位方式,推薦)
  • RW→GND(只寫,簡單可靠)
  • RS:0=命令,1=數據
  • E(EN):上升沿鎖存
  • D4~D7:數據高四位,4位模式僅用這四根

示例接線(可按需改口):

  • P2.2 → RSP2.3 → E
  • P2.4 → D4P2.5 → D5P2.6 → D6P2.7 → D7
  • RW → GND

注意:若用P0口需外接上拉;P2口免上拉更穩。


3. 指令與尋址要點

  • 主要命令(十六進制):
  • 0x01 清屏(>1.52ms)
  • 0x02 歸位(>1.52ms)
  • 0x04/0x06 輸入模式(增量/不移屏)
  • 0x08~0x0F 顯示/光標/閃爍開關(如0x0C 顯示開、無光標)
  • 0x10/0x14/0x18/0x1C 光標/整屏左/右移
  • 0x20/0x28 數據長度與行數(0x28=4位、兩行、5×8點陣)
  • 0x80 | addr 設置DDRAM地址(行列定位)
  • DDRAM行首地址(1602):
  • 第1行:0x00
  • 第2行:0x40

4. 完整驅動(4位、RW接地)

#include <reg52.h>
#include <intrins.h>

/* 引腳映射:按你的接線修改 */
sbit LCD_RS = P2^2;
sbit LCD_EN = P2^3;
sbit LCD_D4 = P2^4;
sbit LCD_D5 = P2^5;
sbit LCD_D6 = P2^6;
sbit LCD_D7 = P2^7;

/* 延時(粗略,不依賴忙標誌) */
static void delay_us_inline() { _nop_(); _nop_(); _nop_(); _nop_(); }
void delay_ms(unsigned int ms){
    unsigned int i,j;
    for(i=0;i<ms;i++) for(j=0;j<125;j++);
}

/* 低級IO */
static void lcd_write4(unsigned char nib){   // 發送高4位或低4位
    LCD_D4 = (nib & 0x01) ? 1 : 0;
    LCD_D5 = (nib & 0x02) ? 1 : 0;
    LCD_D6 = (nib & 0x04) ? 1 : 0;
    LCD_D7 = (nib & 0x08) ? 1 : 0;
    delay_us_inline();
    LCD_EN = 1; delay_us_inline();
    LCD_EN = 0; delay_us_inline();
}

/* 發送一個字節(4位總線):rs=0命令,rs=1數據 */
static void lcd_send(unsigned char value, bit rs){
    LCD_RS = rs;
    // 先發高四位
    lcd_write4((value >> 4) & 0x0F);
    // 再發低四位
    lcd_write4(value & 0x0F);

    // 大多數命令37us足夠,清屏/歸位需更長
    if (!rs && (value==0x01 || value==0x02)) {
        delay_ms(2);
    } else {
        // 約40us
        _nop_(); _nop_(); _nop_(); _nop_();
    }
}
static void lcd_cmd(unsigned char cmd){ lcd_send(cmd, 0); }
static void lcd_data(unsigned char dat){ lcd_send(dat, 1); }

/* 初始化(4位,2行,5x8,顯示開,光標關,自增) */
void lcd_init(void){
    LCD_RS = 0; LCD_EN = 0;

    delay_ms(40);          // 上電等待>30ms

    // 強制進入4位模式的固定序列(參考HD44780)
    lcd_write4(0x03); delay_ms(5);
    lcd_write4(0x03); delay_ms(5);
    lcd_write4(0x03); delay_ms(1);
    lcd_write4(0x02);      // 現在進入4位模式

    lcd_cmd(0x28);         // 功能設置:4位、2行、5x8點陣
    lcd_cmd(0x08);         // 顯示關閉
    lcd_cmd(0x01);         // 清屏
    delay_ms(2);
    lcd_cmd(0x06);         // 輸入模式:寫入後地址+1,屏不移
    lcd_cmd(0x0C);         // 顯示開,光標關,閃爍關
}

/* 常用API */
void lcd_clear(void){ lcd_cmd(0x01); delay_ms(2); }
void lcd_home(void){ lcd_cmd(0x02); delay_ms(2); }

/* 設置光標:行row=0/1,列col=0..15 */
void lcd_set_cursor(unsigned char row, unsigned char col){
    unsigned char addr = (row ? 0x40 : 0x00) + (col & 0x0F);
    lcd_cmd(0x80 | addr);
}

/* 打印字符串(以 '\0' 結束) */
void lcd_print(const char* s){
    while(*s) lcd_data((unsigned char)*s++);
}

/* 指定位置打印 */
void lcd_print_at(unsigned char row, unsigned char col, const char* s){
    lcd_set_cursor(row, col);
    lcd_print(s);
}

/* 自定義字符(CGRAM)。loc:0~7,對應字符碼0..7;pattern[8]每行低5位有效 */
void lcd_define_char(unsigned char loc, const unsigned char pattern[8]){
    unsigned char i;
    loc &= 0x07;
    lcd_cmd(0x40 | (loc << 3));   // 設置CGRAM地址
    for(i=0;i<8;i++) lcd_data(pattern[i] & 0x1F);
    lcd_cmd(0x80);                // 返回DDRAM(可選)
}

/* 示例:主程序 */
void main(void){
    unsigned int cnt = 0;
    const unsigned char deg_sym[8] = {
        0x04,0x0A,0x04,0x00,0x00,0x00,0x00,0x00  // 簡易“°”
    };

    lcd_init();
    lcd_define_char(0, deg_sym); // 自定義字符0號為“°”

    lcd_print_at(0, 0, "Hello, LCD1602!");
    lcd_print_at(1, 0, "Cnt: ");

    while(1){
        char buf[6];
        unsigned int v = cnt;

        // 將數字轉字符串
        buf[0] = (v/10000)%10 + '0';
        buf[1] = (v/1000)%10 + '0';
        buf[2] = (v/100)%10 + '0';
        buf[3] = (v/10)%10 + '0';
        buf[4] = (v%10) + '0';
        buf[5] = '\0';

        lcd_print_at(1, 5, buf);      // 在第2行第6列更新計數
        // 示範自定義符號(在末尾顯示“°C”)
        lcd_set_cursor(1, 11);
        lcd_data(0);                   // 打印自定義“°”
        lcd_data('C');

        cnt++;
        delay_ms(500);
    }
}

要點:

  • 初始化中四次lcd_write4(...)的固定序列是切入4位模式的關鍵。
  • RW接地避開忙標誌讀取,使用“足夠延時”保證可靠。
  • 清屏/歸位需要>1.52ms,其餘指令約37μs,代碼已分別處理。

5. 常用拓展

  • 光標與移屏
  • lcd_cmd(0x0E):顯示開、有光標
  • lcd_cmd(0x0F):顯示開、光標閃爍
  • lcd_cmd(0x18):整屏左移;0x1C:右移
  • 快速覆蓋行尾空白
  • 打印後用空格填滿剩餘列,避免殘留字符
  • 數值/時間顯示(與前章結合)
  • DS1302時間:lcd_print_at(0,0,"HH:MM:SS");
  • DS18B20温度:使用lcd_define_char自定義“°”,顯示“23.5°C”

6. 故障排查

  • 無顯示/黑方塊一行:對比度V0未調;初始化時序錯誤;E腳未正確翻轉。
  • 亂碼:4位高低半字節順序錯;數據線接錯;延時不足。
  • 顯示位置錯亂:行首地址理解錯誤;lcd_set_cursor計算錯誤。
  • 閃爍嚴重:頻繁清屏;改用覆蓋更新局部字符。

7. 進階(可選)

  • 8位總線:將D0~D7全部接入,初始化用0x38(8位、2行、5×8),寫入一次8位即可(速度快,線多)。
  • 讀忙標誌:RW接MCU,切換D口方向,讀BF(D7=1忙);但硬件與代碼更復雜,通常延時法已足夠。
  • I²C轉接模塊:很多1602帶PCF8574 I/O擴展,I²C僅佔兩線(與第17章I²C兼容),命令需映射到PCF8574位序