STM32CubeMX | 35-使用硬件FSMC驱动TFT-LCD屏幕(MCU屏,NT35510控制器)
本篇详细的记录了如何使用STM32CubeMX配置 STM32f407ZGT6 的硬件FSMC外设驱动TFT-LCD屏幕。
1. 准备工作
硬件准备
-
开发板
首先需要准备一个开发板,这里我准备的是STM32F407ZGT6的开发板。 -
TFT-LCD
开发板底板接正点原子4.3寸TFT-LCD。
2. STM32 FSMC外设概述
2.1. 什么是FSMC
FSMC全称 Flexible static memory controller,灵活的静态内存控制器,顾名思义,其主要作用是:负责向外部扩展的存储类设备提供控制信号。
FSMC内存控制器支持的存储设备有:
- Nor Flash、SRAM、PSRAM
- Nand Flash
- 类SRAM设备
2.2. FSMC外设的功能框图
2.3. 外部设备的地址映射(重点)
从FSMC的角度来看,外部的存储设备被分为几个固定大小的Bank,每个bank 256 MB。
整个FSMC外设映射地址的划分如图:
2.3.1. Bank1
Bank1的地址空间为:0x6000 0000 - 0x6FFF FFFF
,支持外接Nor Flash、PSRAM、SRAM等设备,还可以外接DM9000等类存储设备。
整个Bank1的地址空间被划分为四个子bank,每个子bank的大小为64MB,刚好对应FSMC外设的地址总线(FSMC_A[0:25])有26条(2^26=64MB)。
FSMC还有两条内部总线ADDR[27:26],用这两路控制片选信号,如下表:
BANK1控制时序模型
接下来讲述BANK1控制外部存储器的时序模式,BANK1又称为Nor Flash/SRAM/PSRAM控制器,后续暂且叫它SRAM控制器。
SRAM控制器支持两种控制模式:
- 同步模式
- 异步模式
对于异步模式,FSMC主要设置三个时序参数:
- 地址建立时间:ADDSET
- 数据建立时间:DATASET
- 地址保持时间:ADDHLD
根据SRAM、PSRAM、Nor Flash的综合特点,FMC定义了四种不同的异步时序模型,如下表:
本文中控制TFT-LCD使用的就是异步ModeA时序模型。
异步ModeA时序模型
模式A时序模型的优势在于:支持独立的读写时序控制。这一点对于控制TFT-LCD来说,非常符合。因为TFT-LCD在读的时候,一般比较慢,而在写入的时候一般比较快。
模式A的读操作时序如图:
模式A的写操作时序如图:
图中ADDSET和DATASET两个时序的值,后续配置的时候会详细讲述。
2.3.2. Bank2、3/4
只能外接Nand Flash设备和PC Card设备:
3. 使用STM32CubeMX生成工程
选择芯片型号
打开STM32CubeMX,打开MCU选择器:
搜索并选中芯片STM32F407ZGT6
:
配置时钟源
- 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
- 如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
调试选项配置
默认没有配置下载引脚,烧录之后下载器将无法再检测到,这里我使用ST-Link,所以配置为SW选项:
配置串口
开发板板载了一个CH340换串口,连接到USART1,但是引脚不是默认引脚,需要手动修改。
接下来开始配置USART1
:
配置FSMC外设
本文所使用的开发板中,将TFT-LCD当做SRAM来操作,连接在FSMC的BANK1的第4个区域。
知识点:为什么TFT-LCD可以当做SRAM来控制?
因为TFT-LCD和SRAM相比,同样需要D0-D15数据线,WR、RD、CS控制线,唯一不同的就是TFT-LCD需要一条RS信号线(用于控制传输的是命令还是数据),而SRAM则需要一堆地址线,所以可以巧妙的使用任意一条地址线来当做RS信号。
FSMC配置
开发板上 TFT-LCD 的原理图如下:
通过原理图可以看出:
- LCD D0-D15:使用了16bit:FSMC D0 - FSMC D15;
- LCD_RS:使用FSMC A6来控制向LCD写入数据还是命令(0-命令,1-数据);
- LCD_BL:背光控制,对应PB5;
- LCD_CS:LCD片选信号,FMC_NE4,表示使用Bank1的Bank4子区域
- LCD_WR :LCD写使能,FSMC_NWE;
- LCD_RD:LCD读使能,FSMC_NOE;
- RESET:LCD复位信号,直接与单片机复位信号接在一起;
根据这些信息,在STM32CubeMX中先配置SRAM4的基本设置:
此处如果选择LCD接口类型和SRAM类型的区别在于:
LCD接口类型只会配置用到的那一个地址引脚,而SRAM类型则会配置所有的地址引脚。
SRAM基本参数配置
首先设置基本的参数,允许读与写使用不同的模式:
SRAM时序参数配置
其中主要的时序参数配置方法如下:
读时序配置
① HCLK
时序参数都是以HCLK的周期为单位的,在本文中HCLK=168Mhz,所以一个周期为5.95ns。
② 地址建立时间:Address setup time(ADDSET)
该时序的最大值的15个HCLK,也就是15x5=75ns,一般设为最大值就好。
③ 数据持续时间:Data setup time(DATASET)
读时序比较慢,该时序的最大值为255个HCLK,这里有个小技巧,刚开始测试时可以设置为255个HCLK,先确保可以正常读取屏幕驱动ID,可以刷屏,之后再调小。
经实际测试,值为60时依然正常,也就是 5.95 * 60 = 357ns。
写时序配置
① HCLK
时序参数都是以HCLK的周期为单位的,在本文中HCLK=168Mhz,所以一个周期为5.95ns。
② 地址建立时间:Address setup time(ADDSET)
该时序的最大值的15个HCLK,也就是15x5=75ns,一般设为最大值就好。
③ 数据持续时间:Data setup time(DATASET)
写时序比较快,该时序的最大值为255个HCLK,经实际测试,值为15时依然正常,也就是 5.95 * 15 = 89.25ns。
综合上述计算,配置情况如下:
配置背光引脚
配置时钟树
STM32F407ZGT6的最高主频到168M,使HCLK = 168Mhz
即可:
生成工程设置
代码生成设置
最后设置生成独立的初始化文件:
生成代码
点击GENERATE CODE
即可生成MDK-V5工程:
4. 编写TFT-LCD驱动(测试是否可以正常读写ID)
特别提醒:STM32CubeMX生成的工程默认开启了-O3优化,编写的驱动太菜了,会出问题,所以遇到玄学Bug请改为-O0优化!
封装底层发送/读取函数
LCD的底层无非就是两个API:发送命令、发送数据,(有的还需要从屏幕读取数据),接下来封装出这两(三)个底层API。
之前查看原理图的时候,表示命令或者数据的LCD_RS控制引脚接在FMC_A6上,也就是说地址数据的第6位,所以在头文件lcd-fsmc.h
中先定义:
/* 通过地址线控制RS引脚 */
#define LCD_CMD_ADDR 0x6c00007E
#define LCD_DAT_ADDR 0x6c000080
接着开始封装两个(三个)底层操作函数:
① 发送命令函数:
/**
* @brief 向LCD写入命令
* @param cmd 待写入命令
* @retval none
*/
static void lcd_write_cmd(__IO uint16_t cmd)
{
*(uint16_t *)(LCD_CMD_ADDR) = cmd;
}
② 发送数据函数:
/**
* @brief 向LCD写入数据
* @param data 待写入数据
* @retval none
*/
static void lcd_write_data(__IO uint16_t data)
{
*(uint16_t *)(LCD_DAT_ADDR) = data;
}
③ 读取数据函数:
/**
* @brief 从LCD读取数据
* @param none
* @retval 读取到的数据
*/
static uint16_t lcd_read_data(void)
{
__IO uint16_t data;
data = *(uint16_t *)(LCD_DAT_ADDR);
return data;
}
基于这三个底层API,还可以封装出读写LCD内部寄存器的函数:
/**
* @brief 写LCD中的寄存器
* @param reg 寄存器序号
* @param data 要写入寄存器的值
* @retval none
*/
static void lcd_write_reg(__IO uint16_t reg, __IO uint16_t data)
{
lcd_write_cmd(reg);
lcd_write_data(data);
}
LCD控制参数结构体
为了方便驱动不同的IC,保存不同的控制参数,在lcd_fmc.h
中封装如下数据类型:
/**
* @brief 保存LCD屏幕参数
* @param lcd_width LCD屏幕宽度
* @param lcd_height LCD屏幕高度
* @param lcd_id LCD 驱动IC ID
* @param lcd_direction LCD横屏显示还是竖屏显示,0-竖屏,1-横屏
* @param wram_cmd 开始写gram指令
* @param set_x_cmd 设置x坐标指令
* @param set_y_cmd 设置y坐标指令
*/
typedef struct lcd_params_st {
uint16_t lcd_width;
uint16_t lcd_height;
uint16_t lcd_id;
uint8_t lcd_direction;
uint16_t wram_cmd;
uint16_t set_x_cmd;
uint16_t set_y_cmd;
} lcd_params_t;
然后在头文件中声明外部变量定义,方便其他程序访问:
extern lcd_params_t lcd_params;
在lcd_fsmc.c
中定义此变量为全局变量:
lcd_params_t lcd_params;
LCD驱动打印日志的处理
为了方便程序开发,难免要打印一些日志,但是如果printf没有被重定向,则会导致LCD驱动卡死。为了避免这个问题,我们使用宏开关的方式来控制是否打印。
在lcd_fsmc.h
中定义此宏开关:
/* 使能此驱动是否打印调试日志(需要printf支持) */
#define LCD_LOG_ENABLE 1
接着可以定义一个日志打印函数:
#if LCD_LOG_ENABLE
#include <stdio.h>
#define LCD_LOG printf
#else
#define LCD_LOG(format,...)
#endif
之后所以需要打印的地方使用LCD_LOG
代替printf即可。
编写LCD控制器ID读取函数
通过主动读取此控制器ID,可以自动检测出是哪种类型的控制器,然后执行不同的驱动代码:
static int lcd_read_id(void)
{
/* 尝试执行ILI9341控制器ID的读取流程 */
lcd_write_cmd(0XD3);
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id <<= 8;
lcd_params.lcd_id |= lcd_read_data();
/* 如果正常读到,则返回成功 */
if (lcd_params.lcd_id == 0x9341) {
return 0;
}
/* 尝试执行NT35310控制器ID的读取流程 */
lcd_write_cmd(0XD4);
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id <<= 8;
lcd_params.lcd_id |= lcd_read_data();
/* 如果正常读到,则返回成功 */
if (lcd_params.lcd_id == 0x5310) {
return 0;
}
/* 尝试执行NT35510控制器ID的读取流程 */
lcd_write_cmd(0XDA00);
lcd_params.lcd_id = lcd_read_data();
lcd_write_cmd(0XDB00);
lcd_params.lcd_id = lcd_read_data();
lcd_params.lcd_id <<= 8;
lcd_write_cmd(0XDC00);
lcd_params.lcd_id |= lcd_read_data();
/* 如果正常读到,则返回成功 */
if (lcd_params.lcd_id == 0x8000) {
lcd_params.lcd_id = 0x5510;
return 0;
}
/* 驱动IC不支持 */
lcd_params.lcd_id = 0;
return -1;
}
编写LCD初始化函数
LCD初始化需要发送大量的命令和数据,本文限于篇幅,只给出读LCD 控制IC的ID的部分,用来测试LCD是否能正常读写足矣。
void lcd_init(void)
{
/* 初始化FMC接口 */
//MX_FSMC_Init();
/* 开启背光 */
HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);
HAL_Delay(50);
/* 读取LCD控制器IC */
if (lcd_read_id() == -1) {
LCD_LOG("Not Support LCD IC!\r\n");
return;
} else {
LCD_LOG("LCD IC ID is:%#x\r\n", lcd_params.lcd_id);
}
return;
}
在lcd_fsmc.h
中声明该函数:
void lcd_init(void);
测试是否可以正常操作LCD
在main.c
中包含进来头文件:
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "lcd_fsmc.h"
/* USER CODE END Includes */
然后在man函数中调用:
/* USER CODE BEGIN 2 */
printf("4.3' TFT-LCD Test By Mculover666\r\n");
lcd_init();
/* USER CODE END 2 */
编译,下载,在串口助手中查看结果:
5. 编写TFT-LCD驱动(初始化、刷屏测试)
可以正常读取ID之后,接下来的工作是:
- 发送一堆一堆的命令,初始化屏幕;
- 设置坐标
- 清屏
- 刷屏测试
① LCD开显示、关显示、LCD设置扫描方向、 LCD设置显示方向、LCD设置光标位置这些函数代码不多,需要的话请查看源码。
② 清屏函数:
static void lcd_write_ram_start(void)
{
lcd_write_cmd(lcd_params.wram_cmd);
}
static void lcd_write_ram(uint16_t rgb_color)
{
lcd_write_data(rgb_color);
}
void lcd_clear(uint16_t color)
{
uint32_t index = 0;
uint32_t totalpoint = lcd_params.lcd_width;
/* 计算得到总点数 */
totalpoint *= lcd_params.lcd_height;
/* 设置光标位置 */
lcd_set_cursor(0x00,0x0000);
/* 开始写入GRAM */
lcd_write_ram_start();
/* 写入数据到GRAM */
for (index = 0; index < totalpoint; index++) {
lcd_write_ram(color);
}
}
③ 初始化函数:代码过长,请查看源码。
这三类函数实现完之后,就可以编写一个如下的刷屏测试函数:
void lcd_auto_clear(uint16_t period_ms)
{
lcd_clear(BLACK);
HAL_Delay(period_ms);
lcd_clear(BLUE);
HAL_Delay(period_ms);
lcd_clear(GREEN);
HAL_Delay(period_ms);
lcd_clear(GBLUE);
HAL_Delay(period_ms);
lcd_clear(CYAN);
HAL_Delay(period_ms);
lcd_clear(GRAY);
HAL_Delay(period_ms);
lcd_clear(BROWN);
HAL_Delay(period_ms);
lcd_clear(RED);
HAL_Delay(period_ms);
lcd_clear(BRED);
HAL_Delay(period_ms);
lcd_clear(BRRED);
HAL_Delay(period_ms);
lcd_clear(YELLOW);
HAL_Delay(period_ms);
lcd_clear(WHITE);
HAL_Delay(period_ms);
}
在main函数中调用此函数,分别给予不同的刷新频率,测试刷屏速度和效果。
6. 实现打点、画线、填充函数(重点)
更多精彩文章及资源,请关注我的微信公众号:『mculover666』。
上一篇: 基于FPGA的SDRAM控制器设计(2)
下一篇: STM32cubemx配置ADC
推荐阅读
-
STM32cubemx配置ADC
-
【STM32CubeMX】5,STM32之ADC
-
使用IDEA搭建Spring Boot开发环境
-
STM32CubeMX系列|待机唤醒
-
新手入门gulp必备以及gulp常用插件在项目中的使用(windows下操作)
-
【瞎捣鼓】Mac下使用Spring tool suite创建spring mvc项目时使用自带Maven问题
-
【重要总结】使用gulp来解决浏览器缓存的问题。还有遇到的bug。missing script: gulp和Task function must be specified
-
STM32CubeMX | 35-使用硬件FSMC驱动TFT-LCD屏幕(MCU屏,NT35510控制器)
-
基于FPGA的SDRAM控制器设计(2)
-
基于FPGA的SDRAM控制器设计(3)