zoukankan      html  css  js  c++  java
  • 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为低电平的读取操作来获取其状态,并等待直到状态为空闲。

    软件

    开发板的库不仅包装了这些硬件操作,还添加了对字符串的支持,包括对 等不可打印字符的处理。

    下面我们用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] = '
    ', [5] = ''};        // 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什么区别?

  • 相关阅读:
    浅谈MySQL字符集
    思维导图_Python知识点
    思维导图_Linux文件系统及常用监控命令
    思维导图_Linux中的软件安装命令
    思维导图_Linux中的重要日志文件
    思维导图_Linux开机启动流程
    CentOS7.6中mysql实践
    搭建集群架构
    LNMP架构
    Day004_Linux基础_基础命令之tar打包解包
  • 原文地址:https://www.cnblogs.com/jerry-fuyi/p/12174393.html
Copyright © 2011-2022 走看看