AVR單片機教程——LCD1602


本文隸屬於AVR單片機教程系列。

 

顯示屏

開發板套件里有兩塊屏幕,大的是LCD(液晶顯示),小的是OLED(有機發光二極管)。正與你所想的相反,短小精悍的比較貴,而本講的主題——LCD1602——功能較少,使用起來也簡單很多。

這塊屏幕的顯示是以字符為單位的。每個字符都是8像素高,5像素寬。1602這個名字,來源於顯示字符的數量,共2行,每行16個字符。出售1602的商家提供了一份文檔:提取碼8c1u

硬件

一個典型的1602顯示屏有16個引腳(還有些模塊是用串行總線驅動的):

名稱 功能 連接
VSS 電源地 GND
VDD 正電源 VCC(5V)
VO 對比度調整 左側的電位器,其左端接GND,右端接VCC
RS 數據/指令選擇 PB0
R/W 讀/選擇 PB1
E 使能 PB2
D0~D7 雙向並行數據線 74HC595的輸出,通過exout_write輸出
A 背光LED正極 NPN三極管的發射極,其集電極接VCC,基極接BAK
K 背光LED負極 GND

很復雜吧?好在開發板已經處理好了這些,我們只需要關注2組線:RSR/WE這3根控制線,通過DDRBPORTB來操控,和D0~D7這8跟數據線,通過exout_write來以字節為單位輸出(在這第一期的最后一篇中,我終於成功地將“盡量減少接線”的原則在第一期中貫徹到底了)。其他的還有對比度CON接口,可以不外接,還有背光BAK接口,可以接5V或3.3V以開啟背光。當然,如果你是非主流,你可以把CON接到DAC上,把BAK接到PWM上。

此外,還需要把擴展輸出的最高位(標注Ext Out 7)接到一個單片機引腳上。關於為什么會有這種詭異的接法,這是設計時的失誤(也可能是不得已吧,畢竟單片機的32個IO已經占滿了),參見:一個低電平引發的思考

協議

1602與單片機之間是通過並行總線通信的。AVR單片機硬件上不支持並行總線,需要通過軟件模擬時序來實現。

寫操作的時序如下:

進行一個寫操作,需要先讓RS根據寫的類型設置電平,R/W輸出低電平,D0~D7輸出要發送的數據,然后在E的上升沿數據被對方讀取,並保持R/WD0~D7電平不變,直到E的下降沿之后。兩次E的上升沿之間至少需要400us時間間隔。

1602共有8條指令,都是一字節長度的。從高位到低位,每一條指令都由若干個0、一個1和有效指令組成,使得沒有兩條指令會有相同的二進制表示。這種編碼方式成為前綴碼,UTF-8也用了類似的編碼。

前導0個數 功能
0 設置DDRAM(顯示存儲器)地址
1 設置CGRAM(字符發生存儲器)地址
2 設置總線寬度、字符行數、字符大小
3 設置滾動對象(光標或畫面)、滾動方向
4 設置畫面、光標、閃爍的開關
5 設置AC(地址計數器)是否變化與變化方向
6 將AC清零
7 清屏

1602插上開發板后只有第一行有黑色背景。我們先把它設置為2行輸出,這是最簡單的有視覺效果的指令。

#include <avr/io.h>
#include <ee1/lcd.h>

inline void short_delay()
{
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
}

int main()
{
    DDRB |= 0b111; // PB2:0 for LCD control: E, R/W, RS
    exout_init();  // Ext Out 7:0 for LCD instructions/data
#define LCD_CONTROL(x) (PORTB = (PORTB & ~0b111) | (x))
    LCD_CONTROL(0b000);
    short_delay();
    LCD_CONTROL(0b100);
    short_delay();
    exout_write(0b00111000); // 8-bit, 2 lines, 5x7 font
    short_delay();
    LCD_CONTROL(0b000);
}

在這段代碼中,asm("nop")是一句匯編語句nop,代表這個CPU周期內不執行操作,可以用來短時間而精確地延時。在控制信號與數據信號之間加入短暫的延時,使時序符合1602的要求。

其他指令見數據手冊,這份數據手冊本身就是很好的教程。需要提醒的是,顯示屏內的控制器執行指令需要一定時間,程序在發送下一指令之前,應通過RS為低電平的讀取操作來獲取其狀態,並等待直到狀態為空閑。

軟件

開發板的庫不僅包裝了這些硬件操作,還添加了對字符串的支持,包括對\b\t\n\r等不可打印字符的處理。

下面我們用1602顯示屏來完成一個小項目,在之前用按鍵控制蜂鳴器的基礎上,加入LCD顯示與背光動態效果。

#include <string.h>
#include <ee1/lcd.h>
#include <ee1/pwm.h>
#include <ee1/button.h>
#include <ee1/tone.h>
#include <ee1/delay.h>

int main()
{
    lcd_init(PIN_3);
    lcd_set_status(true, false, false);
    button_init(PIN_6, PIN_7);
    wave_mode(WAVE_0, WAVE_MODE_PWM);
    wave_mode(WAVE_1, WAVE_MODE_TONE);
    char display[6] = {[0] = '\r', [5] = '\0'};        // 1. 為什么要開6字節字符數組?
    char* notes = display + 1;                         // 2. display數組是如何使用的?
    uint8_t brightness;                                // 3. 這一行有什么問題?
    uint16_t frequency[] = {262, 330, 392, 523};
    uint16_t buzzer = 0, tone;
    while (1)
    {
        memset(notes, ' ', 4);                         // 4. memset是什么函數?
        tone = 0;
        for (uint8_t i = 0; i != 4; ++i)
            if (button_down(i))
            {
                notes[i] = 0xFF;                       // 5. 0xFF是什么字符?
                brightness = 255;
                tone = frequency[i];                   // 6. 如果有多個按鍵按下,蜂鳴器會響什么音?
            }
        lcd_print_string(display);
        pwm_set(WAVE_0, brightness * brightness >> 8); // 7. 為什么第二個參數不直接寫brightness?
        if (tone != buzzer)                            // 8. 為什么必須引入buzzer變量並在這里作判斷?
        {
            buzzer = tone;
            tone_set(WAVE_1, buzzer);
        }
        if (brightness)
            brightness -= 5;
        delay(10);
    }
}

在這AVR單片機教程第一期最后一講中,我在代碼中留了8個問題。如果你能全部答上來,說明你對C語言已經有一些了解,熟悉了單片機開發中的部分細節,並具備初步的工程素養了。

作業

思考題:

  1. 明明可以選擇4位總線來節省IO,為什么開發板上還是8位連接?

  2. 實現讀取1602空閑狀態。

  3. 在1602上畫一顆心。

  4. 修改例程,使1602以滾動的橫線指示按下的按鍵與發出的音符(類似於你玩過或看過的音游)。

擴展閱讀:

  1. How LCDs Work

  2. OLED和LCD什么區別?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM