《ESP32-S3使用指南—MicroPython版 V1.0》第十六章 SPILCD实验

第十六章 SPILCD实验


       在本章实验中,我们将通过编写MicroPython驱动程序来实现SPILCD显示。在开发板上,我们已经预留了SPILCS模块接口,因此需要准备一个SPILCD显示模块。我们将一起点亮SPILCS,并实现字符的显示。

       16.1 SPILCD模块简介

       16.2 SPILCD C模块解析

       16.3 硬件设计

       16.4 软件设计

       16.5 下载验证


        16.1 SPILCD模块简介

       本例程仅支持两种屏幕,一种是ATK-MD0130的1.3寸显示模块,另一种是ATK-MD0240的2.4寸显示模块。这两种显示模块是由正点原子推出的高性能LCD显示模块。这两个显示模块的LCD分辨率为240*240和320*240像素,支持16位真彩色显示。模块采用ST7789V作为LCD的驱动芯片,该芯片自带RAM,无需外加驱动器或存储器。使用外接的主控芯片时,仅需使用SPI接口就可以轻松地驱动这两个显示模块。

       ATK-MD0130和ATK-MD0240 模块的各项基本参数,如下表所示。


表16.1.1 ATK-MD0130和ATK-MD0240 模块基本参数


       ATK-MD0130和ATK-MD0240模块通过2*4的排针(2.54mm 间距)同外部相连接,该模块可直接与正点原子DNESP32-S3开发板和正点原子MiniSTM32H750开发板等开发板的WIRELESS接口(SPI 接口)连接,而对于没有板载WIRELESS接口的开发板,可以通过杜邦线连接。正点原子大部分的STM32开发板,我们都提供了本模块相应的例程,用户可以直接在这些开发板上,对模块进行测试。

       ATK-MD0130模块的外观,如下图所示。


图16.1.1 ATK-MD0130模块实物图


       ATK-MD0130和ATK-MD0240模块的原理图,如下图所示。


图16.1.2 ATK-MD0130模块原理图


       ATK-MD0130和ATK-MD0240模块通过一个2*4的排针(2.54mm间距)同外部电路连接,各引脚的详细描述,如下表所示。


表16.1.2 ATK-MD0130和ATK-MD0240模块引脚说明


       16.1.1 模块SPI时序介绍

       ATK-MD0130和ATK-MD0240模块在四线SPI通讯模式下,最少仅需四根信号线(CS、SCK、SDA、WR(DC))就能够完成与这两个显示模块的通讯,四线SPI接口时序如下图所示。


图16.1.1.1 四线SPI接口时序图


       上图中各个时间参数,如下图所示。


图16.1.1.2 四线SPI接口时序时间参数


       从上图中可以看出,ATK-MD0130和ATK-MD0240模块四线SPI的写周期是非常快的(T SCYCW = 66ns),而读周期就相对慢了很多(T SCYCR = 150ns)。

       更详细的时序介绍,可以参考ST7789V的数据手册《ST7789V_SPEC_V1.4.pdf》。


       16.1.2 模块驱动说明

       ATK-MD0130和ATK-MD0240模块采用ST7789V作为LCD驱动器,LCD的显存可直接存放在ST7789V的片上RAM中,ST7789V的片上RAM有240*320*3字节,并且ST7789V会在没有外部时钟的情况下,自动将其片上RAM的数据显示至LCD上,以最小化功耗。

       在每次初始化显示模块之前,必须先通过RST引脚对显示模块进行硬件复位,硬件复位要求RST至少被拉低10微秒,拉高RST结束硬件复位后,须延时120毫秒等待复位完成后,才能够往显示模块传输数据。

       PWR引脚用于控制显示模块的LCD背光,该引脚自带下拉电阻,当PWR引脚被拉低或悬空时,ATK-MD0130模块的LCD背光都处于关闭状态,当PWR引脚被拉高时,显示模块的LCD背光才会点亮。

       ST7789V最高支持18位色深(262K色),但一般在希纳是模块上使用16位色深(65K色)的RGB565格式,这样可以在16位色深下达到最快的速度。在16位色深模式下,ST7789V采用RGB565格式传输、存储颜色数据,如下图所示。


图16.1.2.1 16位色深模式(RGB565)传输颜色数据


       如上图所示,一个像素的颜色数据需要使用16比特来传输,这16比特数据中,高5比特用于表示红色,低5比特用于表示蓝色,中间的6比特用于表示绿色。数据的数值越大,对应表示的颜色就越深。

       ST7789V支持连续读写RAM中存放的LCD上颜色对应的数据,并且连续读写的方向(LCD的扫描方向)是可以通过命令0x36进行配置的,如下图所示。


图16.1.2.2 命令0x36


       从上图中可以看出,命令0x36可以配置6个参数,但对于配置LCD的扫描方向,仅需关心MY、MX和MV这三个参数,如下表所示。


表16.1.2.1 命令0x36配置LCD扫描方向


       这样一来,就能够大大地提高ATK-MD0130和ATK-MD0240模块在刷屏时的效率,仅需设置一次坐标,然后连续地往ATK-MD0130和ATK-MD0240模块传输颜色数据即可。

       在往ATK-MD0130和ATK-MD0240模块写入颜色数据前,还需要设置地址,以确定随后写入的颜色数据对应LCD上的哪一个像素,通过命令0x2A和命令0x2B可以分别设置ATK-MD0130和ATK-MD0240模块显示颜色数据的列地址和行地址,命令0x2A的描述,如下图所示。


图2.3.3 命令0x2A


       命令0x2B的描述,如下图所示。


图2.3.4 命令0x2B


       以默认的LCD扫描方式(从左到右,从上到下)为例,命令0x2A的参数XS和XE和命令0x2B的参数YS和YE就在LCD上确定了一个区域,在连读读写颜色数据时,ST7789V就会按照从左到右,从上到下的扫描方式读写设个区域的颜色数据。


        16.2 SPILCD C模块解析


       16.2.1 C模块解析

       作者将简要介绍正点原子SPILCD C模块驱动。这个讲解内容会分为几个部分:SPILCD构造函数、写数据、写命令。SPILCD C模块驱动可在A盘à6,软件资料à1,软件à2,MicroPython开发工具à01-Windowsà2,正点原子MicroPython驱动àCModules_LibàLCD路径下找到。


       1,SPILCD构造函数

mp_obj_t lcd_make_new(const mp_obj_type_t *type,size_t n_args,size_t n_kw
,const mp_obj_t *all_args )
{
    /* 创建对象的参数 */
    enum
    {
        ARG_spi, ARG_reset, ARG_dc, ARG_cs,
        ARG_backlight, ARG_dir,ARG_lcd,
    };
    /* 创建对象参数的默认值 */
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_spi, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_dc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_backlight, MP_ARG_KW_ONLY|MP_ARG_OBJ,{.u_obj = MP_OBJ_NULL} },
        { MP_QSTR_dir, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
        { MP_QSTR_lcd, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
    };
        
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args,
                          MP_ARRAY_SIZE(allowed_args), allowed_args, args);
 
    /* 创建对象 */
    self = m_new_obj(lcd_obj_t);
    self->base.type = &lcd_type;
 
    /* 设置对象参数 */
mp_obj_base_t *spi_obj  = (mp_obj_base_t*)
MP_OBJ_TO_PTR(args[ARG_spi].u_obj);
    self->spi_obj           = spi_obj;
    self->dir               = args[ARG_dir].u_int;
    self->lcd               = args[ARG_lcd].u_int;
    
    /* SPI控制块是否为空 */
    if (spi_obj == MP_OBJ_NULL)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("SPI init ???"));
    }
    /* 屏幕方向只能横屏和竖屏 */
    if (self->dir != 1 && self->dir != 0)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("Not horizontal and
vertical screens"));
    }
    /* LCD类型选择 */
    if (self->lcd != 1 && self->lcd != 0)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("Only supports 320x240(0)
and 240x240(1)"));
    }
    /* DC命令/数据选择管脚 */
    if (args[ARG_dc].u_obj == MP_OBJ_NULL) {
        mp_raise_ValueError(MP_ERROR_TEXT("must specify dc pin"));
    }
    /* 背光管脚 */
    if (args[ARG_backlight].u_obj != MP_OBJ_NULL) {
        self->backlight = mp_hal_get_pin_obj(args[ARG_backlight].u_obj);
    }
    /* 复位管脚 */
    if (args[ARG_reset].u_obj != MP_OBJ_NULL) {
        self->reset = mp_hal_get_pin_obj(args[ARG_reset].u_obj);
    }
    /* 获取DC管脚号 */
    self->dc = mp_hal_get_pin_obj(args[ARG_dc].u_obj);
    /* 片选管脚 */
    if (args[ARG_cs].u_obj != MP_OBJ_NULL) {
        self->cs = mp_hal_get_pin_obj(args[ARG_cs].u_obj);
    }
    /* 执行LCD初始化序列 */
    lcd_dv_init();
    
    return MP_OBJ_FROM_PTR(self);
}

       根据上述源代码可以看出,作者首先创建了一个LCD对象控制块,然后设置了LCD对象的属性。接着,系统判断SPI控制块、LCD类型、LCD扫描方向和各个管脚是否满足相关条件。如果不满足条件,系统将不再执行下去,并在Shell交互窗口下提示错误信息。最后,系统调用了lcd_dv_init函数来初始化LCD序列,并返回LCD对象。


       2,SPILCD写命令函数

/**
 * @brief       发送命令到LCD,使用轮询方式阻塞等待传输完成(由于数据传输量很少,
因此在轮询方式处理可提高速度。使用中断方式的开销要超过轮询方式)
 * @param       cmd 传输的8位命令数据
 * @retval      无
 */
STATIC void lcd_write_cmd(const uint8_t cmd)
{
    CS_LOW();
    DC_LOW();
    mp_obj_base_t *s = (mp_obj_base_t *)MP_OBJ_TO_PTR(lcd_self->spi_obj);
mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)
MP_OBJ_TYPE_GET_SLOT(s->type, protocol);
    spi_p->transfer(s, 1, &cmd, NULL);
    CS_HIGH();
}

       上述源码中,作者首先拉低DC数据/命令和CS片选管脚的电平,接着根据传入的SPI控制块,调用了SPI收发函数来发送SPILCD命令。发送完成后,拉高CS片选管脚的电平。


       3,SPILCD写数据函数

/**
 * @brief       发送数据到LCD,使用轮询方式阻塞等待传输完成(由于数据传输量很少,
因此在轮询方式处理可提高速度。使用中断方式的开销要超过轮询方式)
 * @param       data 传输的8位数据
 * @retval      无
 */
STATIC void lcd_write_data(const uint8_t *data, int len){
    CS_LOW();
    DC_HIGH();
    mp_obj_base_t *s = (mp_obj_base_t *)MP_OBJ_TO_PTR(lcd_self->spi_obj);
mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t *)
MP_OBJ_TYPE_GET_SLOT(s->type, protocol);
    spi_p->transfer(s, len, data, NULL);
    CS_HIGH();
}

       上述源码中,作者首先拉高DC数据/命令和拉低CS片选管脚的电平,接着,调用了SPI收发函数来发送SPILCD数据。发送完成后,拉高CS片选管脚的电平。其他函数,如画线、画点等,请参考lcd.c/.h文件。


       16.2.2 C模块构造与类的方法


       1,atk_lcd类与SPI构造函数

       Lcd和SPI的构造对象方法如下:

class atk_lcd.init(spi,reset,dc,cs,backlight,dir,lcd)
使用示例:
spi=SPI(2,baudrate=80000000, sck = Pin(12), mosi = Pin(11), miso = Pin(13))
lcd=atk_lcd.init(spi,dc=Pin(40,Pin.OUT),cs=Pin(21,Pin.OUT,value=1),dir=1,lcd=0)

       该构造方法的参数描述,如下表所示。


表16.2.2.1 atk_lcd.init构造函数参数描述


       返回值:LCD对象。


       2,lcd类的方法


       ①:打开LCD背光。

       其函数原型如下:

lcd.on()


       ②:关闭LCD背光。

       其函数原型如下:

lcd.off()


       ③:LCD清屏。

       其函数原型如下:

lcd.clear(color)

        该函数的参数描述,如下表所示。


表16.2.2.2 lcd.clear函数参数描述


       ④:LCD画点。

       其函数原型如下:

lcd.pixel(x,y,colot)

        该函数的参数描述,如下表所示。


表16.2.2.3 lcd.clear函数参数描述


       ⑤:LCD画线。

       其函数原型如下:

lcd.line(x1,y1,x2,y2,color)

        该函数的参数描述,如下表所示。


表16.2.2.4 lcd.line函数参数描述


       ⑥:LCD画一个矩形。

       其函数原型如下:

lcd.rectangle (x0,y0,x1,y1,color)

        该函数的参数描述,如下表所示。


表16.2.2.5 lcd.rectangle函数参数描述


       ⑦:LCD画一个圆。

       其函数原型如下:

lcd.circle (x0,y0,x1,y1,color)

        该函数的参数描述,如下表所示。


表16.2.2.6 lcd.circle函数参数描述


       ⑧:LCD显示字符。

       其函数原型如下:

lcd.char(x,y,chr,size,mode,color)

        该函数的参数描述,如下表所示。


表16.2.2.7 lcd.char函数参数描述


       ⑨:LCD显示len个数字。

       其函数原型如下:

lcd.num(x,y,num,len,size,color)

        该函数的参数描述,如下表所示。


表16.2.2.7 lcd.num函数参数描述


       ⑩:LCD显示字符串。

       其函数原型如下:

lcd.string(x,y,width,heught,size,p,color)

        该函数的参数描述,如下表所示。


表16.2.2.8 lcd.string函数参数描述


       11:LCD填充区域。

       其函数原型如下:

lcd.fill(x0,y0,x1,y1,color)

        该函数的参数描述,如下表所示。


表16.2.2.9 lcd.fill函数参数描述


       12,RGB888转RGB565,返回RGB565颜色数值。

       其函数原型如下:

atk_lcd.color565(r,g,b)

        该函数的参数描述,如下表所示。


表16.2.2.10 atk_lcd.color565函数参数描述


        16.3 硬件设计


       1. 例程功能

       本章实验功能简介:使用开发板的SPI接口连接正点原子 SPILCD模块(仅限SPI显示模块),实现SPILCD模块的显示。通过把LCD模块插入底板上的WIRELESS接口(SPI 接口),按下复位之后,就可以看到SPILCD模块不停的显示一些信息并不断切换底色。LED闪烁用于提示程序正在运行。


       2. 硬件资源


       1)LED灯

              LED-IO1


       2)XL9555

              IIC_INT-IO0(需在P5连接IO0)

              IIC_SDA-IO41

              IIC_SCL-IO42


       3)SPILCD

              CS-IO21

              SCK-IO12

              SDA-IO11

              DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)

              PWR- IO1_3(XL9555)

              RST- IO1_2(XL9555)


       3. 原理图

       SPILCD硬件部分的原理图,如下图所示。


图16.3.1 SPILCD接口原理图


        16.4 软件设计


       16.3.1 程序流程图

       程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图。


图16.3.1.1 程序流程图


       16.3.2 程序解析

       本书籍的代码都在main.py脚本下编写的,读者可在光盘资料下找到对应的源码。SPILCD实验main.py源码如下:

from machine import Pin,SPI,I2C
import atk_xl9555 as io_ex
import atk_lcd as lcd
import time
"""
 * @brief       程序入口
 * @param       无
 * @retval      无
"""
if __name__ == '__main__':
    x = 0
    # IIC初始化
    i2c0 = I2C(0, scl = Pin(42), sda = Pin(41), freq = 400000)
    # XL9555初始化
    xl9555 = io_ex.init(i2c0)
    # 复位LCD
    xl9555.write_bit(io_ex.SLCD_RST,0)
    time.sleep_ms(100)
    xl9555.write_bit(io_ex.SLCD_RST,1)
    time.sleep_ms(100)
    # 初始化SPI
    spi=SPI(2,baudrate=80000000, sck = Pin(12), mosi = Pin(11), miso = Pin(13))
    # 初始化LCD,lcd = 0为正点原子2.4寸屏幕;lcd = 1为正点原子1.3寸SPILCD屏幕;
    display=lcd.init(spi,dc=Pin(40,Pin.OUT),cs=Pin(21, Pin.OUT),dir = 1,lcd = 0)
    # 打开背光
    xl9555.write_bit(io_ex.SLCD_PWR,1)
    time.sleep_ms(100)
    while True:
        #创建字典
        seasondict = {
        0: lcd.BLACK,
        1: lcd.BLUE,
        2: lcd.RED,
        3: lcd.GREEN,
        4: lcd.CYAN,
        5: lcd.MAGENTA,
        6: lcd.YELLOW}
        #刷新颜色
        display.clear(seasondict[x])
        #显示字体
        display.string(0, 5, 240, 32, 32, "ESP32-S3",lcd.RED)
        display.string(0, 34, 240, 16, 16, "SPI LCD Test",lcd.RED)
        display.string(0, 50, 240, 16, 16, "ATOM@ALIENTEK",lcd.RED)
        x += 1
        if x == 7:
            x = 0
        time.sleep(1)

       这示例代码使用SPI和I2C接口与外部设备进行通信,并通过特定的库函数控制2.4寸LCD显示屏的显示内容。具体来说,代码中初始化了I2C和SPI接口,然后使用这些接口与XL9555芯片通信,以控制LCD显示屏的复位和背光。接着,它通过调用特定库(atk_lcd)提供的函数在LCD屏幕上显示文本和颜色。在循环中,它使用一个变量x来循环显示不同的颜色,并在屏幕上显示固定的文本。当变量x的值达到7时,它会被重置为0,从而循环显示。


        16.5 下载验证

       下载代码后,LED不停的闪烁,提示程序已经在运行了。同时可以看到SPILCD模块的显示实验信息,并且背景色不停切换,如下图所示。 


图16.5.1 SPILCD显示效果图


请使用浏览器的分享功能分享到微信等