第十六章 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显示效果图