打印
[其他ST产品]

【正点原子K210连载】第十六章 LCD显示实验《DNK210使用指南-SDK版》

[复制链接]
70|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
第十六章 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.4TFT-LCD显示屏。
16.1.1 SPI简介
SPISerial Peripheral interface,顾名思义,就是串行外围设备接口,是由原摩托罗拉公司在其MC68HCXX系列处理器上定义的。SPI是一种高速的全双工、同步、串行的通信总线,已经广泛应用在众多MCU、存储芯片、AD转换器和LCD之间。
SPI通信跟IIC通信一样,通信总线上允许挂载一个主设备和一个或者多个从设备。为了跟从设备进行通信,一个主设备至少需要4跟数据线,分别为:
MOSIMaster Out / Slave In):主数据输出,从数据输入,用于主机向从机发送数据。
MISOMaster In / Slave Out):主数据输入,从数据输出,用于从机向主机发送数据。
SCLKSerial Clock):时钟信号,由主设备产生,决定通信的速率。
CSChip Select):从设备片选信号,由主设备产生,低电平时选中从设备。
多从机SPI通信网络连接如下图所示。
图16.1.1.1 多从机SPI通信网络图
        从上图可以知道,MOSIMISOSCLK引脚连接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个边沿即偶数边沿。
SPI4种模式对比图,如下图所示。
图16.1.1.2 SPI4种模式对比图
l 模式0CPOL=0CPHA=0;空闲时,SCL处于低电平,数据采样在第1个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。
l 模式1CPOL=0CPHA=1;空闲时,SCL处于低电平,数据采样在第2个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。
l 模式2CPOL=1CPHA=0;空闲时,SCL处于高电平,数据采样在第1个边沿,即SCL由高电平到低电平的跳变,数据采样在下升沿,数据发送在上降沿。
l 模式3CPOL=1,CPHA=1;空闲时,SCL处于高电平,数据采样在第2个边沿,即SCL由低电平到高电平的跳变,数据采样在上升沿,数据发送在下降沿。
Kendryte K210SPI功能非常强大,最高支持8线全双工模式,串行外设接口有4SPI接口,其中SPI0SPI1SPI3只能工作在MASTER模式,SPI2只能工作在SLAVE模式,他们有如下特性:
1. 支持1/2/4/8线全双工模式
2. SPI0SPI1SPI2可支持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。我们依次配置为:设备0SPI工作模式08线模式、数据长度为8、小端模式,关于大小端模式的内容,大家可自行百度了解。
2spi_set_clk_rate t函数
该函数用于设置SPI总线时钟,如下代码所示:
uint32_t spi_set_clk_rate(spi_device_num_t spi_num, uint32_t spi_clk);
第一个参数是选择需要配置的总线号,第二个参数为设置的频率,我们配置SPI0时钟为15MHz即可。Kendryte K210SPI0SPI1SPI2可支持25MHz时钟,SPI3支持80MHz时钟,这是在设备支持的理想情况下的最高时钟频率配置。但实际上我们要根据自己的设备情况而定,并不是越高越好。
3spi_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总线号、指令长度、数据长度、等待周期和传输模式,这里我们需要根据自己的需要配置不同的参数。
4spi_send_data_standard_dma函数
该函数是SPIDMA通道发送数据,该函数原型及参数描述如下所示:
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,利用了液晶导电后透光性可变的特性,配合显示器光源、彩色滤光片和电压控制等工艺,最终可以在液晶阵列上显示彩色的图像。目前液晶显示技术以TNSTNTFT三种技术为主,TFT-LCD即采用了TFTThin Film Transistor)技术的液晶显示器,也叫薄膜晶体管液晶显示器
TFT-LCD与无源TN-LCDSTN-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.clcd.hlcdfont.h,因为涉及内容较多,我们只挑选部分讲解,我们先看lcd.h文件的内容。
lcd.h主要就是一些管脚的宏定义和SPI配置的宏,还有RGB565颜色常量的定义,如下表所示:
常量
颜色
0x0000
Black
0x000F
Navy
0x03E0
Dark green
0x03EF
Dark cyan
0x7800
Maroon
0x780F
Purle
0x7BE0
Olive
0xC618
Light grey
0x7BEF
Dark grey
0x001F
Blue
0x07E0
Green
0x07FF
Cyan
0xF800
Red
0xF81F
Magenta
0xFFE0
Yellow
0xFFFF
White
0xFD20
Orange
0xAFE5
Green yellow
0xF81F
Pink
0xFC07
Brred
0xBC40
Brown
0x8430
Gray
16.3.1.1 lcd模块提供的颜色常量
lcd.h文件内容就介绍到这里了,接下来我们看lcd.c文件。
/**
* @brief       LCD初始化
* @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       cmd7789指令
* @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的格式显示,每个像素点占16bit,为了方便使用所以我们常用此函数来写数据,函数执行过程和写指令差不多,不同的是写指令要将SPIRS引脚拉高,然后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);
}
该函数作用类似于重新设置一个显示窗口,用于定位到要写数据的位置,0x2A0x2B这两个指令用于配置列地址和行地址,通过这两个指令可以找到要写的内存地址,用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显示(蓝色背景)

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

83

主题

84

帖子

1

粉丝