第十六章 LCD显示实验
本章将介绍初步介绍Kendryte K210使用官方SDK驱动的LCD。通过本章的学习,读者将学习到板载LCD的简单使用。 本章分为如下几个小节: 16.1 LCD模块介绍 16.2 硬件设计 16.3 程序设计 16.4 运行验证
16.1 LCD介绍 DNK210板载一块2.4寸(此处的寸是代表英寸,下同)的TFT-LCD显示模块,显示分辨率为320*240,颜色数量高达262K。 本章将介绍Kendryte K210使用8线SPI驱动开发板板载的2.4寸TFT-LCD显示屏。 16.1.1 SPI简介 SPI,Serial Peripheral interface,顾名思义,就是串行外围设备接口,是由原摩托罗拉公司在其MC68HCXX系列处理器上定义的。SPI是一种高速的全双工、同步、串行的通信总线,已经广泛应用在众多MCU、存储芯片、AD转换器和LCD之间。 SPI通信跟IIC通信一样,通信总线上允许挂载一个主设备和一个或者多个从设备。为了跟从设备进行通信,一个主设备至少需要4跟数据线,分别为: MOSI(Master Out / Slave In):主数据输出,从数据输入,用于主机向从机发送数据。 MISO(Master In / Slave Out):主数据输入,从数据输出,用于从机向主机发送数据。 SCLK(Serial Clock):时钟信号,由主设备产生,决定通信的速率。 CS(Chip Select):从设备片选信号,由主设备产生,低电平时选中从设备。 多从机SPI通信网络连接如下图所示。 图16.1.1.1 多从机SPI通信网络图 从上图可以知道,MOSI、MISO、SCLK引脚连接SPI总线上每一个设备,如果CS引脚为低电平,则从设备只侦听主机并与主机通信。SPI主设备一次只能和一个从设备进行通信。如果主设备要和另外一个从设备通信,必须先终止和当前从设备通信,否则不能通信。 SPI通信有4种不同的模式,不同的从机可能在出厂时就配置为某种模式,这是不能改变的。通信双方必须工作在同一模式下,才能正常进行通信,所以可以对主机的SPI模式进行配置。SPI通信模式是通过配置CPOL(时钟极性)和CPHA(时钟相位)来选择的。 CPOL,详称Clock Polarity,就是时钟极性,当主从机没有数据传输的时候即空闲状态,SCL线的电平状态,假如空闲状态是高电平,CPOL=1;若空闲状态时低电平,那么CPOL = 0。 CPHA,详称Clock Phase,就是时钟相位,实质指的是数据的采样时刻。CPHA = 0表示数据的采样是从第1个边沿信号上即奇数边沿,具体是上升沿还是下降沿的问题,是由CPOL决定的。CPHA=1表示数据采样是从第2个边沿即偶数边沿。 SPI的4种模式对比图,如下图所示。 图16.1.1.2 SPI的4种模式对比图 l 模式0,CPOL=0,CPHA=0;空闲时,SCL处于低电平,数据采样在第1个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。 l 模式1,CPOL=0,CPHA=1;空闲时,SCL处于低电平,数据采样在第2个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。 l 模式2,CPOL=1,CPHA=0;空闲时,SCL处于高电平,数据采样在第1个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。 l 模式3,CPOL=1,CPHA=1;空闲时,SCL处于高电平,数据采样在第2个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。 Kendryte K210的SPI功能非常强大,最高支持8线全双工模式,串行外设接口有4组SPI接口,其中SPI0、SPI1、SPI3只能工作在MASTER模式,SPI2只能工作在SLAVE模式,他们有如下特性: 1. 支持1/2/4/8线全双工模式 2. SPI0、SPI1、SPI2可支持25MHz时钟 3. SPI3最高可支持80MHz时钟 4. 支持32位宽、32BYTE深的FIFO 5. 独立屏蔽中断-主机冲突,发送FIFO溢出,发送FIFO空,接收FIFO满,接收FIFO下溢,接收FIFO溢出中断都可以被屏蔽独立 6. 支持DMA功能 7. 支持双沿的DDR传输模式 我们选择使用SPI0外设接口,SPI时钟配置为15MHz,使用8线SPI模式,这使我们屏幕具有较快的刷新速度和显示效果,能够适用于摄像头显示、视频播放等场景。 Kendryte K210官方SDK提供了多个操作SPI接口的函数,这里我们只讲述本实验用到的函数,这些函数介绍如下: 1, spi_init函数 该函数主要用于SPI的配置初始化,该函数原型及参数描述如下代码所示: void spi_init(spi_device_num_t spi_num, spi_work_mode_t work_mode, spi_frame_format_t frame_format,size_t data_bit_length, uint32_t endian); /* SPI配总线号配置参数 */ typedef enum _spi_device_num { SPI_DEVICE_0, SPI_DEVICE_1, SPI_DEVICE_2, SPI_DEVICE_3, SPI_DEVICE_MAX, } spi_device_num_t; /* SPI工作模式配置参数 */ typedef enum _spi_work_mode { SPI_WORK_MODE_0, SPI_WORK_MODE_1, SPI_WORK_MODE_2, SPI_WORK_MODE_3, } spi_work_mode_t; /* SPI帧格式配置参数 */ typedef enum _spi_frame_format { SPI_FF_STANDARD, SPI_FF_DUAL, SPI_FF_QUAD, SPI_FF_OCTAL } spi_frame_format_t; 该函数共有五个配置参数,第一个为总线号,第二个为SPI工作模式,第三个是配置帧格式,第四个为数据长度,第五个是选择大小端模式,大端模式配置为1,小端模式配置为0。我们依次配置为:设备0、SPI工作模式0、8线模式、数据长度为8、小端模式,关于大小端模式的内容,大家可自行百度了解。 2,spi_set_clk_rate t函数 该函数用于设置SPI总线时钟,如下代码所示: uint32_t spi_set_clk_rate(spi_device_num_t spi_num, uint32_t spi_clk); 第一个参数是选择需要配置的总线号,第二个参数为设置的频率,我们配置SPI0时钟为15MHz即可。Kendryte K210的SPI0、SPI1、SPI2可支持25MHz时钟,SPI3支持80MHz时钟,这是在设备支持的理想情况下的最高时钟频率配置。但实际上我们要根据自己的设备情况而定,并不是越高越好。 3,spi_init_non_standard函数 该函数用来初始化SPI传输,该函数原型及参数描述如下所示: void spi_init_non_standard(spi_device_num_t spi_num, uint32_t instruction_length, uint32_t address_length, uint32_t wait_cycles, spi_instruction_address_trans_mode_t instruction_address_trans_mode); /* SPI数据传送模式配置参数 */ typedef enum _spi_instruction_address_trans_mode { SPI_AITM_STANDARD, SPI_AITM_ADDR_STANDARD, SPI_AITM_AS_FRAME_FORMAT } spi_instruction_address_trans_mode_t; 函数共有5个参数,分别配置SPI总线号、指令长度、数据长度、等待周期和传输模式,这里我们需要根据自己的需要配置不同的参数。 4,spi_send_data_standard_dma函数 该函数是SPI用DMA通道发送数据,该函数原型及参数描述如下所示: void spi_send_data_standard_dma(dmac_channel_number_t channel_num, spi_device_num_t spi_num, spi_chip_select_t chip_select, const uint8_t *cmd_buff, size_t cmd_len, const uint8_t *tx_buff, size_t tx_len); /* SPI数据传送模式配置参数 */ typedef enum _dmac_channel_number { DMAC_CHANNEL0 = 0, DMAC_CHANNEL1 = 1, DMAC_CHANNEL2 = 2, DMAC_CHANNEL3 = 3, DMAC_CHANNEL4 = 4, DMAC_CHANNEL5 = 5, DMAC_CHANNEL_MAX } dmac_channel_number_t; 通过DMA发送数据可以有效节省CPU的资源,得到更好的传输效果,函数共有7个参数,分别配置DMA通道号、SPI总线号、芯片片选引脚、传输的数据地址、数据长度和数据位宽。 16.1.2 LCD模块介绍 液晶显示器,即Liquid Crystal Display,利用了液晶导电后透光性可变的特性,配合显示器光源、彩色滤光片和电压控制等工艺,最终可以在液晶阵列上显示彩色的图像。目前液晶显示技术以TN、STN、TFT三种技术为主,TFT-LCD即采用了TFT(Thin Film Transistor)技术的液晶显示器,也叫薄膜晶体管液晶显示器。 TFT-LCD与无源TN-LCD、STN-LCD的简单矩阵不同的是,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。TFT式显示器具有很多优点:高响应度,高亮度,高对比度等等。TFT式屏幕的显示效果非常出色,广泛应用于手机屏幕、笔记本电脑和台式机显示器上。 由于液晶本身不会发光,加上液晶本身的特性等原因,使得液晶屏的成像角受限,我们从屏幕的的一侧可能无法看清液晶的显示内容。液晶显示器的成像角的大小也是评估一个液晶显示器优劣的指标,目前,规格较好的液晶显示器成像角一般在120°~160°之间。 16.2 硬件设计 16.2.1 例程功能 1. 使用lcd模块初始化LCD,在LCD上显示一段字符串,并不断更换背景颜色 16.2.2 硬件资源 1. LCD LCD_RD - IO34 LCD_BL - IO35 LCD_CS - IO36 LCD_RST - IO37 LCD_RS - IO38 LCD_WR - IO39 LCD_D0~LCD_D7 - SPI0_D0~SPI0_D7 16.2.3 原理图 本章实验内容,需要使用到板载的LCD显示屏,正点原子DNK210开发板上的LCD显示屏连接原理图,如下图所示: 图16.2.3.1 LCD显示屏连接原理图 16.3 程序设计 16.3.1 lcd驱动代码 LCD驱动源码包括三个文件:lcd.c、lcd.h和lcdfont.h,因为涉及内容较多,我们只挑选部分讲解,我们先看lcd.h文件的内容。 lcd.h主要就是一些管脚的宏定义和SPI配置的宏,还有RGB565颜色常量的定义,如下表所示: 表16.3.1.1 lcd模块提供的颜色常量 lcd.h文件内容就介绍到这里了,接下来我们看lcd.c文件。 /** * @param 无 * @retval 无 */ void lcd_init(void) { uint8_t data; /* Initialize DCX pin */ fpioa_set_function(LCD_DCX_PIN, FUNC_GPIOHS0 + LCD_DCX_GPIOHS_NUM); gpiohs_set_drive_mode(LCD_DCX_GPIOHS_NUM, GPIO_DM_OUTPUT); gpiohs_set_pin(LCD_DCX_GPIOHS_NUM, GPIO_PV_HIGH); /* Initialize RESET pin */ fpioa_set_function(LCD_RST_PIN, FUNC_GPIOHS0 + LCD_RST_GPIOHS_NUM); gpiohs_set_drive_mode(LCD_RST_GPIOHS_NUM, GPIO_DM_OUTPUT); gpiohs_set_pin(LCD_RST_GPIOHS_NUM, GPIO_PV_HIGH); /* Initialize CSX pin */ fpioa_set_function(LCD_CSX_PIN, FUNC_SPI0_SS0 + LCD_SPI_CS_NUM); /* Initialize WRX pin */ fpioa_set_function(LCD_WRX_PIN, FUNC_SPI0_SCLK); /* Initialize SPI interface */ spi_init(LCD_SPI, SPI_WORK_MODE_0, SPI_FF_OCTAL, 8, 0); spi_set_clk_rate(LCD_SPI, LCD_SPI_CLK_RATE); /* Hardware reset */ gpiohs_set_pin(LCD_RST_GPIOHS_NUM, GPIO_PV_LOW); msleep(50); gpiohs_set_pin(LCD_RST_GPIOHS_NUM, GPIO_PV_HIGH); msleep(50); /* Software reset */ lcd_write_command(0x01); msleep(50); /* Exit sleep */ lcd_write_command(0x11); msleep(120); /* Set pixel format */ lcd_write_command(0x3A); data = 0x55; lcd_write_data_8b(&data, 1); msleep(10); /* Set direction */ lcd_write_command(0x36); data = 0xA0; lcd_write_data_8b(&data, 1); /* Inversion display */ lcd_write_command(0x21); msleep(10); /* Display on */ lcd_write_command(0x13); msleep(10); lcd_write_command(0x29); /* Clear display */ lcd_clear(0xFFFF); } 首先介绍的是lcd初始化函数,函数的内容是配置SPI相关引脚,然后初始化SPI,设置SPI时钟,最后是用指令配置LCD。 DNK210板载的TFT-LCD显示器使用的是ST7789驱动IC芯片,详细的ST7789初始化寄存器对应功能的指令可以参考:ST7789_DS.pdf这个文档 ,存放路径是:A盘à硬件资料à液晶资料。 /** * @brief SPI写命令 * @param cmd:7789指令 * @retval 无 */ static void lcd_write_command(uint8_t cmd) { gpiohs_set_pin(LCD_DCX_GPIOHS_NUM, GPIO_PV_LOW); spi_init(LCD_SPI, SPI_WORK_MODE_0, SPI_FF_OCTAL, 8, 0); spi_init_non_standard(LCD_SPI, 8, 0, 0, SPI_AITM_AS_FRAME_FORMAT); spi_send_data_normal_dma(LCD_SPI_DMA_CH, LCD_SPI, LCD_SPI_CS_NUM, &cmd, 1, SPI_TRANS_CHAR); } 该函数用于写命令操作,我们先拉低RS引脚,选择命令,然后重新初始化SPI,这里我们需要配置为8位的发送指令模式。 /** * @brief SPI写数据(uint16_t类型) * @param dat :数据地址 * @param len :数据长度 * @retval 无 */ static void lcd_write_data_16b(uint16_t *dat, uint32_t len) { gpiohs_set_pin(LCD_DCX_GPIOHS_NUM, GPIO_PV_HIGH); spi_init(LCD_SPI, SPI_WORK_MODE_0, SPI_FF_OCTAL, 16, 0); spi_init_non_standard(LCD_SPI, 16, 0, 0, SPI_AITM_AS_FRAME_FORMAT); spi_send_data_normal_dma(LCD_SPI_DMA_CH, LCD_SPI, LCD_SPI_CS_NUM, dat, len, SPI_TRANS_SHORT); } 该函数用于写数据(uint16_t类型),TFT-LCD是以RGB565的格式显示,每个像素点占16位bit,为了方便使用所以我们常用此函数来写数据,函数执行过程和写指令差不多,不同的是写指令要将SPI的RS引脚拉高,然后SPI初始化为16bit的数据宽度。 /** * @brief 设置显示区域 * @param x1,y1 :起点坐标 * @param x2,y2 :终点坐标 * @retval 无 */ void lcd_set_area(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { uint8_t data[4] = {0}; data[0] = (uint8_t)(x1 >> 8); data[1] = (uint8_t)(x1); data[2] = (uint8_t)(x2 >> 8); data[3] = (uint8_t)(x2); lcd_write_command(0x2A); lcd_write_data_8b(data, 4); data[0] = (uint8_t)(y1 >> 8); data[1] = (uint8_t)(y1); data[2] = (uint8_t)(y2 >> 8); data[3] = (uint8_t)(y2); lcd_write_command(0x2B); lcd_write_data_8b(data, 4); lcd_write_command(0x2C); } 该函数作用类似于重新设置一个显示窗口,用于定位到要写数据的位置,0x2A和0x2B这两个指令用于配置列地址和行地址,通过这两个指令可以找到要写的内存地址,用0x2C指令可以启用内存写功能,后面用写数据函数即可将数据写入内存中。 为防止篇幅过长,功能函数这里就不再叙述,因为底层操作都是基于上面讲述的几个函数,源码有详细的功能介绍和参数的作用,大家可以直接参考代码源码进行学习。 lcdfont.h文件主要存放着LCD字符集点阵数组,用于LCD显示各种字符串,目前的例程仅支持16*16大小的字符显示,如需显示其他大小的字符可自行扩展。 16.3.2 main.c代码 main.c中的代码如下所示: #define LCD_SPI_CLK_RATE 15000000 int main(void) { uint8_t x = 0; sysctl_pll_set_freq(SYSCTL_PLL0, 800000000); sysctl_pll_set_freq(SYSCTL_PLL1, 400000000); sysctl_pll_set_freq(SYSCTL_PLL2, 45158400); sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18); sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18); sysctl_set_spi0_dvp_data(1); lcd_init(); lcd_set_direction(DIR_YX_LRUD); while(1) { switch(x) { case 0:lcd_clear(WHITE);break; case 1:lcd_clear(BLACK);break; case 2:lcd_clear(BLUE);break; case 3:lcd_clear(RED);break; case 4:lcd_clear(MAGENTA);break; case 5:lcd_clear(GREEN);break; case 6:lcd_clear(CYAN);break; case 7:lcd_clear(YELLOW);break; case 8:lcd_clear(BRRED);break; case 9:lcd_clear(GRAY);break; case 10:lcd_clear(LIGHTGREY);break; case 11:lcd_clear(BROWN);break; } lcd_draw_string(10, 10, "ATK-DNK210", RED); lcd_draw_string(10, 30, "LCD", RED); lcd_draw_string(10, 50, "ATOM@ALIENTEK", RED); if(++x == 12) { x=0; } sleep(1); } } 可以看到首先是配置系统时钟,然后使用lcd_init函数对LCD进行初始化,然后重新设置显示扫描方向,最好在一个循环中不断改变LCD的背景颜色,并在LCD上显示一段字符串。 16.4 运行验证 将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到LCD上显示了一段字符串,并不断地更换背景颜色,如下图所示: 图16.4.1 LCD显示(白色背景) 图16.4.2 LCD显示(蓝色背景)
|