本帖最后由 正点原子运营 于 2024-4-13 17:57 编辑
1)实验平台:正点原子 M144Z-M3 STM32F103最小系统板
2) 章节摘自【正点原子】M144Z-M3最小系统板使用指南——STM32F103版
6)正点原子STM32技术交流QQ群:725095144
本章将介绍使用STM32F103驱动TFTLCD(MCU屏)进行显示。通过本章的学习,读者将学习到FSMC的使用。 本章分为如下几个小节: 25.1硬件设计 25.2 程序设计 25.3 下载验证
25.1 硬件设计 25.1.1 例程功能 1. TFTLCD上显示实验信息,并不断刷新底色 2. LED0闪烁,提示程序正在运行
25.1.2 硬件资源 1. LED LED0 - PB5 2. 正点原子2.8/3.5/4.3/7/10寸TFTLCD模块
25.1.3 原理图 本章实验使用了正点原子的TFTLCD模块(兼容正点原子2.8/3.5/4.3/7/10寸的TFTLCD模块),该模块需通过LCD转接板与板载的TFTLCD接口进行连接,该接口与板载MCU的连接原理图,如下图所示: 图25.1.3.1 TFTLCD模块与MCU的连接原理图 从上图中可以看到,TFTLCD模块连接至FSMC的存储块1的区域块1,并且使用了地址线A19作为TFTLCD模块命令或数据的选择信号,可计算出配置好FSMC后,TFTLCD模块的命令访问地址被映射到0x600FFFFE,TFTLCD模块的数据访问地址被映射到0x60100000。
25.2 程序设计 25.2.1 HAL库的FSMC驱动 本章实验通过FSMC驱动8080并口的TFTLCD模块,通过FSMC可以将TFTLCD模块的命令和数据寄存器映射为两个地址,往这两个地址写入或读取数据就可直接与TFTLCD模块进行通讯,因此需要对FSMC做相应的配置,具体的步骤如下: ①:配置FSMC存储块1的区域块1 在HAL库中对应的驱动函数如下: ①:配置FSMC存储块1的区域块 该函数用于配置FSMC存储块1的区域块,其函数原型如下所示: - HAL_StatusTypeDef HAL_SRAM_Init( SRAM_HandleTypeDef *hsram,
- FMC_NORSRAM_TimingTypeDef*Timing,
- FMC_NORSRAM_TimingTypeDef*ExtTiming);
复制代码该函数的形参描述,如下表所示: 表25.2.1.1 函数HAL_SRAM_Init()形参描述 该函数的返回值描述,如下表所示: 表25.2.1.2 函数HAL_SRAM_Init()返回值描述 该函数使用FMC_NORSRAM_TimingTypeDef类型的结构体变量传入FSMC存储块1区域块的读写时序配置参数,该结构体的定义如下所示: - typedef struct
- {
- uint32_t AddressSetupTime; /* 地址建立时间 */
- uint32_t AddressHoldTime; /* 地址保持时间 */
- uint32_t DataSetupTime; /* 数据建立时间 */
- uint32_tBusTurnAroundDuration; /* 总线转换时间 */
- uint32_t CLKDivision; /* 时钟分频系数 */
- uint32_t DataLatency; /* 数据延时 */
- uint32_t AccessMode; /* 访问模式 */
- }FSMC_NORSRAM_TimingTypeDef;
复制代码该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- SRAM_HandleTypeDef sram_handle = {0};
- FSMC_NORSRAM_TimingTypeDef read_timing = {0};
- FSMC_NORSRAM_TimingTypeDef write_timing = {0};
-
- /* 填充读数据结构体 */
- read_timing.AddressSetupTime = 0xFF;
- read_timing.AddressHoldTime = 0xFF;
- read_timing.DataSetupTime = 0xFF;
- read_timing.AccessMode = FSMC_ACCESS_MODE_A;
-
- /* 填充写时序结构体 */
- write_timing.AddressSetupTime = 0xFF;
- write_timing.AddressHoldTime = 0xFF;
- write_timing.DataSetupTime = 0xFF;
- write_timing.AccessMode = FSMC_ACCESS_MODE_A;
-
- /* 配置FSMC存储块1的区域块 */
- sram_handle.Instance =FSMC_NORSRAM_DEVICE;
- sram_handle.Extended =FSMC_NORSRAM_EXTENDED_DEVICE;
- sram_handle.Init.NSBank = FSMC_NORSRAM_BANK1;
- sram_handle.Init.DataAddressMux=FSMC_DATA_ADDRESS_MUX_DISABLE;
- sram_handle.Init.MemoryType =FSMC_MEMORY_TYPE_SRAM;
- sram_handle.Init.MemoryDataWidth=FSMC_NORSRAM_MEM_BUS_WIDTH_16;
- sram_handle.Init.BurstAccessMode=FSMC_BURST_ACCESS_MODE_DISABLE;
- sram_handle.Init.WaitSignalPolarity=FSMC_WAIT_SIGNAL_POLARITY_LOW;
- sram_handle.Init.WrapMode =FSMC_WRAP_MODE_DISABLE;
- sram_handle.Init.WaitSignalActive=FSMC_WAIT_TIMING_BEFORE_WS;
- sram_handle.Init.WriteOperation=FSMC_WRITE_OPERATION_ENABLE;
- sram_handle.Init.WaitSignal =FSMC_WAIT_SIGNAL_DISABLE;
- sram_handle.Init.ExtendedMode =FSMC_EXTENDED_MODE_ENABLE;
- sram_handle.Init.AsynchronousWait=FSMC_ASYNCHRONOUS_WAIT_DISABLE;
- sram_handle.Init.WriteBurst =FSMC_WRITE_BURST_DISABLE;
- sram_handle.Init.PageSize =FSMC_PAGE_SIZE_NONE;
- HAL_SRAM_Init(&sram_handle, &read_timing, &write_timing);
- }
复制代码
25.2.2 TFTLCD驱动 本章实验的TFTLCD驱动主要负责向应用层提供TFTLCD的初始化和各种TFTLCD显示的操作函数。本章实验中,TFTLCD的驱动代码包括lcd.c、lcd_ex.c、lcd.h和字体文件lcdfont.h四个文件。 由于TFTLCD模块需要使用到大量的GPIO引脚,因此对于GPIO的相关定义,请读者自行查看lcd.c和lcd.h这两个文件。 TFTLCD驱动中,TFTLCD的初始化函数,如下所示: - /**
- *@brief 初始化LCD
- *@note 该初始化函数可以初始化各种型号的LCD(详见本.c文件最前面的描述)
- *
- *@param 无
- *@retval 无
- */
- void lcd_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct;
- FSMC_NORSRAM_TimingTypeDef fsmc_read_handle;
- FSMC_NORSRAM_TimingTypeDef fsmc_write_handle;
- LCD_CS_GPIO_CLK_ENABLE(); /* LCD_CS脚时钟使能 */
- LCD_WR_GPIO_CLK_ENABLE(); /* LCD_WR脚时钟使能 */
- LCD_RD_GPIO_CLK_ENABLE(); /* LCD_RD脚时钟使能 */
- LCD_RS_GPIO_CLK_ENABLE(); /* LCD_RS脚时钟使能 */
- LCD_BL_GPIO_CLK_ENABLE(); /* LCD_BL脚时钟使能 */
-
- gpio_init_struct.Pin = LCD_CS_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 推挽复用 */
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
- HAL_GPIO_Init(LCD_CS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_CS引脚 */
- gpio_init_struct.Pin = LCD_WR_GPIO_PIN;
- HAL_GPIO_Init(LCD_WR_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_WR引脚 */
- gpio_init_struct.Pin = LCD_RD_GPIO_PIN;
- HAL_GPIO_Init(LCD_RD_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RD引脚 */
- gpio_init_struct.Pin = LCD_RS_GPIO_PIN;
- HAL_GPIO_Init(LCD_RS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RS引脚 */
- gpio_init_struct.Pin = LCD_BL_GPIO_PIN;
- gpio_init_struct.Mode =GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
- /* LCD_BL引脚模式设置(推挽输出) */
- HAL_GPIO_Init(LCD_BL_GPIO_PORT, &gpio_init_struct);
- g_sram_handle.Instance =FSMC_NORSRAM_DEVICE;
- g_sram_handle.Extended =FSMC_NORSRAM_EXTENDED_DEVICE;
-
- g_sram_handle.Init.NSBank = FSMC_NORSRAM_BANK4; /* 使用NE4 */
- /* 地址/数据线不复用 */
- g_sram_handle.Init.DataAddressMux=FSMC_DATA_ADDRESS_MUX_DISABLE;
- /* 16位数据宽度 */
- g_sram_handle.Init.MemoryDataWidth=FSMC_NORSRAM_MEM_BUS_WIDTH_16;
- /* 是否使能突发访问,仅对同步突发存储器有效,此处未用到 */
- g_sram_handle.Init.BurstAccessMode=FSMC_BURST_ACCESS_MODE_DISABLE;
- /* 等待信号的极性,仅在突发模式访问下有用 */
- g_sram_handle.Init.WaitSignalPolarity=FSMC_WAIT_SIGNAL_POLARITY_LOW;
- /* 存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT */
- g_sram_handle.Init.WaitSignalActive=FSMC_WAIT_TIMING_BEFORE_WS;
- /* 存储器写使能 */
- g_sram_handle.Init.WriteOperation=FSMC_WRITE_OPERATION_ENABLE;
- /* 等待使能位,此处未用到 */
- g_sram_handle.Init.WaitSignal =FSMC_WAIT_SIGNAL_DISABLE;
- /* 读写使用不同的时序 */
- g_sram_handle.Init.ExtendedMode =FSMC_EXTENDED_MODE_ENABLE;
- /* 是否使能同步传输模式下的等待信号,此处未用到 */
- g_sram_handle.Init.AsynchronousWait=FSMC_ASYNCHRONOUS_WAIT_DISABLE;
- /* 禁止突发写 */
- g_sram_handle.Init.WriteBurst =FSMC_WRITE_BURST_DISABLE;
- /* 地址建立时间(ADDSET)为1个HCLK 1/72M = 13.9ns (实际 > 200ns) */
- fsmc_read_handle.AddressSetupTime = 0;
- /* 地址保持时间(ADDHLD) 模式A是没有用到 */
- fsmc_read_handle.AddressHoldTime = 0;
- /* 数据保存时间(DATAST)为16个HCLK = 13.9 * 16 = 222.4ns */
- fsmc_read_handle.DataSetupTime = 15;
- fsmc_read_handle.AccessMode = FSMC_ACCESS_MODE_A; /* 模式A */
-
- /* FSMC写时序控制寄存器 */
- /* 地址建立时间(ADDSET)为1个HCLK =13.9ns */
- fsmc_write_handle.AddressSetupTime = 0;
- /* 地址保持时间(ADDHLD) 模式A是没有用到 */
- fsmc_write_handle.AddressHoldTime = 0;
- /* 数据保存时间(DATAST)为2个HCLK = 13.9* 2 = 27.8ns (实际 > 200ns)*/
- fsmc_write_handle.DataSetupTime = 1;
- /* 某些液晶驱动IC的写信号脉宽,最少也得50ns。 */
- fsmc_write_handle.AccessMode = FSMC_ACCESS_MODE_A; /* 模式A */
- HAL_SRAM_Init(&g_sram_handle, &fsmc_read_handle, &fsmc_write_handle);
- delay_ms(50); /* 初始化FSMC后,必须等待一定时间才能开始初始化 */
- /* 尝试9341 ID的读取 */
- lcd_wr_regno(0XD3);
- lcddev.id = lcd_rd_data(); /* dummy read */
- lcddev.id = lcd_rd_data(); /* 读到0X00 */
- lcddev.id = lcd_rd_data(); /* 读取0X93 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 读取0X41 */
- if (lcddev.id != 0X9341) /* 不是 9341 , 尝试看看是不是 ST7789 */
- {
- lcd_wr_regno(0X04);
- lcddev.id = lcd_rd_data(); /* dummy read */
- lcddev.id = lcd_rd_data(); /* 读到0X85 */
- lcddev.id = lcd_rd_data(); /* 读取0X85 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 读取0X52 */
-
- if (lcddev.id == 0X8552) /* 将8552的ID转换成7789 */
- {
- lcddev.id = 0x7789;
- }
- if (lcddev.id != 0x7789) /* 也不是ST7789, 尝试是不是 NT35310 */
- {
- lcd_wr_regno(0xD4);
- lcddev.id = lcd_rd_data(); /* dummyread */
- lcddev.id = lcd_rd_data(); /* 读回0x01 */
- lcddev.id = lcd_rd_data(); /* 读回0x53 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 这里读回0x10 */
- if (lcddev.id != 0x5310) /* 也不是NT35310,尝试看看是不是ST7796 */
- {
- lcd_wr_regno(0XD3);
- lcddev.id = lcd_rd_data(); /* dummy read */
- lcddev.id = lcd_rd_data(); /* 读到0X00 */
- lcddev.id = lcd_rd_data(); /* 读取0X77 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 读取0X96 */
-
- if (lcddev.id != 0x7796) /* 也不是ST7796,尝试看看是不是NT35510 */
- {
- /* 发送密钥(厂家提供) */
- lcd_write_reg(0xF000, 0x0055);
- lcd_write_reg(0xF001, 0x00AA);
- lcd_write_reg(0xF002, 0x0052);
- lcd_write_reg(0xF003, 0x0008);
- lcd_write_reg(0xF004, 0x0001);
- lcd_wr_regno(0xC500); /* 读取ID低八位 */
- lcddev.id = lcd_rd_data();/* 读回0x55 */
- lcddev.id <<= 8;
- lcd_wr_regno(0xC501); /* 读取ID高八位 */
- lcddev.id |= lcd_rd_data();/* 读回0x10 */
-
- delay_ms(5);
- if (lcddev.id != 0x5510) /* 也不是NT5510,尝试看看是不是ILI9806 */
- {
- lcd_wr_regno(0XD3);
- lcddev.id = lcd_rd_data(); /* dummyread */
- lcddev.id = lcd_rd_data(); /* 读回0X00 */
- lcddev.id = lcd_rd_data(); /* 读回0X98 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 读回0X06 */
- /* 也不是ILI9806,尝试看看是不是SSD1963 */
- if (lcddev.id != 0x9806)
- {
- lcd_wr_regno(0xA1);
- lcddev.id = lcd_rd_data();
- lcddev.id = lcd_rd_data(); /* 读回0x57 */
- lcddev.id <<= 8;
- lcddev.id |= lcd_rd_data(); /* 读回0x61 */
- /* SSD1963读回的ID是5761H,为方便区分,我们强制设置为1963 */
- if (lcddev.id == 0x5761) lcddev.id = 0x1963;
- }
- }
- }
- }
- }
- }
- /* 特别注意, 如果在main函数里面屏蔽串口1初始化, 则会卡死在printf
- *里面(卡死在f_putc函数), 所以, 必须初始化串口1, 或者屏蔽掉下面
- *这行 printf 语句 !!!!!!!
- */
- printf("LCDID:%x\r\n", lcddev.id); /* 打印LCD ID */
- if (lcddev.id == 0X7789)
- {
- lcd_ex_st7789_reginit(); /* 执行ST7789初始化 */
- }
- else if (lcddev.id == 0X9341)
- {
- lcd_ex_ili9341_reginit(); /* 执行ILI9341初始化 */
- }
- else if (lcddev.id == 0x5310)
- {
- lcd_ex_nt35310_reginit(); /* 执行NT35310初始化 */
- }
- else if (lcddev.id == 0x7796)
- {
- lcd_ex_st7796_reginit(); /* 执行ST7796初始化 */
- }
- else if (lcddev.id == 0x5510)
- {
- lcd_ex_nt35510_reginit(); /* 执行NT35510初始化 */
- }
- else if (lcddev.id == 0x9806)
- {
- lcd_ex_ili9806_reginit(); /* 执行ILI9806初始化 */
- }
- else if (lcddev.id == 0x1963)
- {
- lcd_ex_ssd1963_reginit(); /* 执行SSD1963初始化 */
- lcd_ssd_backlight_set(100); /* 背光设置为最亮 */
- }
- lcd_display_dir(0); /* 默认为竖屏 */
- LCD_BL(1); /* 点亮背光 */
- lcd_clear(WHITE);
- }
复制代码 从上的代码中可以看出,本章实验的TFTLCD驱动是兼容了正点原子的多款TFTLCD模块的,因此在初始化完FSMC后,会与TFTLCD进行通讯,确定TFTLCD的型号,然后根据型号针对性地对TFTLCD模块进行配置。 TFTLCD驱动中与TFTLCD模块通讯的函数,如下所示: - /**
- *@brief LCD写数据
- *@param data: 要写入的数据
- *@retval 无
- */
- void lcd_wr_data(volatile uint16_t data)
- {
- data = data;
- LCD->LCD_RAM = data;
- }
- /**
- *@brief LCD写寄存器编号或地址
- *@param regno: 寄存器编号或地址
- *@retval 无
- */
- void lcd_wr_regno(volatile uint16_t regno)
- {
- regno = regno;
- LCD->LCD_REG = regno;
- }
- /**
- *@brief LCD写寄存器
- *@param regno: 寄存器编号
- *@param data : 要写入的数据
- *@retval 无
- */
- voidlcd_write_reg(uint16_t regno, uint16_t data)
- {
- LCD->LCD_REG = regno;
- LCD->LCD_RAM = data;
- }
- /**
- *@brief LCD读数据
- *@param 无
- *@retval 读取到的数据
- */
- static uint16_t lcd_rd_data(void)
- {
- volatile uint16_t ram;
-
- ram= LCD->LCD_RAM;
-
- return ram;
- }
复制代码从上面的代码中可以看出,与TFTLCD的通讯都是通过LCD这一结构体对象来完成的,对于LCD结构体的相关定义,如下所示: - typedef struct
- {
- volatile uint16_t LCD_REG;
- volatile uint16_t LCD_RAM;
- } LCD_TypeDef;
- #define LCD_FSMC_NEX 1
- #define LCD_FSMC_AX 19
- #define LCD_BASE \
- (uint32_t)((0x60000000+(0x4000000*(LCD_SMC_NEX-1)))|(((1<<LCD_SMC_AX)*2)-2))
- #define LCD ((LCD_TypeDef*)LCD_BASE)
复制代码从LCD结构体的相关定义中可以看出,与TFTLCD模块的通讯地址是与TFTLCD连接的FSMC存储块1的区域块和TFTLCD模块命令、数据选择信号所连接的FSMC地址线是有关的。通过上面宏定义的计算,可以算出LCD_BASE宏定义的值为0x600FFFFE,因此访问LCD->LCD_REG就是访问0x600FFFFE这一地址,访问LCD->LCD_RAM就是访问0x60100000这一地址,这两个地址也就是通过FSMC映射的TFTLCD命令和数据寄存器的访问地址。 通过上面介绍的驱动函数就能够与TFTLCD模块进行通讯了,而在TFTLCD模块的显示屏上显示出特定的图案或字符或设置TFTLCD模块的显示方向等等的操作都是能够通过TFTLCD模块规定的特定命令来完成的,想深究的读者可以产看正点原子TFTLCD模块的用户手册或查看实际使用的TFTLCD模块的相关文档。
25.2.3 实验应用代码 本章实验的应用代码,如下所示: - int main(void)
- {
- uint8_t x = 0;
- uint8_t lcd_id[13];
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(RCC_PLL_MUL9); /* 配置时钟,72MHz */
- delay_init(72); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
-
- sprintf((char *)lcd_id, "LCD ID:%04X", lcddev.id);
- 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(LGRAY);
- break;
- }
- case 11:
- {
- lcd_clear(BROWN);
- break;
- }
- }
-
- lcd_show_string(10, 40, 240, 32, 32, "STM32", RED);
- lcd_show_string(10, 80, 240, 24, 24, "TFTLCDTEST", RED);
- lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(10, 130, 240, 16, 16, (char *)lcd_id, RED);
-
- if (++x == 12)
- {
- x = 0;
- }
-
- LED0_TOGGLE();
- delay_ms(1000);
- }
- }
复制代码从上面的代码中可以看出,在初始化完LCD后,便在LCD上显示一些本实验的相关信息,随后便每间隔1000毫秒就更换一次LCD屏幕显示的背景色。
25.3 下载验证 在完成编译和烧录操作后,先将开发板断电,随后将TFTLCD模块通过LCD转接板与开发板进行连接,最后再给开发板供电(在插拔开发板上的接插件和模块时,要求必须断电操作,否则容易烧毁硬件)。程序运行后,可以看到LCD上显示了本实验的相关信息,同时LCD显示的背景色被间隔1000毫秒就切换一次。 |