前言

LCD 可以说是最常见的人机交互设备,买个手机,手环都带个屏幕,本文为实操性的MCU技术文章,便不赘述LCD的基本原理与技术细节,有需要的小伙伴可以去B站搜BV1Wz411B7Tf观看。

LCD驱动原理

单片机上LCD驱动方式,分为两种:
一、 GPIO模拟时序
二、 FSMC模拟时序

GPIO大家都很熟悉,点亮数码管、LED,还是侦测按键状态使用的都是GPIO。
那什么是FSMC,FSMC全称Flexible Static Memory Controller(可变静态存储控制器),是一种存储器扩展技术。
大容量(内部flash256K以上)后缀为xC、xD、xE的stm32才会搭载。

FSMC

  1. 支持多种静态存储器类型。STM32 通过 FSMC 可以与 SRAM、ROM、PSRAM、NOR Flash 和 NANDFlash 存储器的引脚直接相连。
  2. 支持代码从 FSMC 扩展的外部存储器中直接运行,而不需要首先调入内部 SRAM。

不同的是TFTLCD有RS信号,但没有地址线。因此可以通过模拟LCD时序实现对LCD的驱动。
由于是直接操作外部存储设备,相比于普通GPIO,速度会快很多。因此条件允许的情况下尽量选择FSMC驱动LCD。

cubeMX配置

时钟树配置

串口配置

配置串口是为了打印LCD的id,检测是否正确读取到其值

FSMC配置

将TFTLCD当作SRAM使用的原理
大抵是,FSMC把整个EXternal RAM 存储区域分成4个Bank区域,并分配了地址范围,适用的存储类型,每个bank内部又分为4块,每一块有相应的引脚控制片选信号。
如下图,当stm32访问0x6000 0000 ~ 0xFFF FFFF地址空间,bank1就会被访问,相应的FSMC_NE1信号输出。

详见正点原子 - 战舰 V3 第十八章 TFTLCD 显示实验
我们配置FSMC位置bank1,按正点原子的官方例程区间选择NE4,存储类型选择LCD接口,LCD register select 选择 A10,数据按照LCD手册的选择我的是16Bit
按照下图配置即好

配置GPIO

PB0是TFTLCD的背光板所以需要单独配置。

代码移植

正点原子官方提供了lcd的驱动,但官方的HAL是自己手动写的,我的则是生成的,需要手动修改。
注释掉 lcd.c文件中 void HAL_SRAM_MspInit(SRAM_HandleTypeDef *hsram) 函数。
注释掉 lcd.c文件中 void LCD_Init(void)函数中关于FSMC初始化部分。
注释掉 lcd.c文件中 变量 SRAM_HandleTypeDef TFTSRAM_Handler; //SRAM¾ä±ú(ÓÃÓÚ¿ØÖÆLCD)
注释掉 lcd.h文件中 关于上述变量的声明

在lcd.c中加入延时函数,

void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD;
ticks=nus*72;
told=SysTick->VAL;
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow;
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break;
}
}
}

删掉所有没有必要的头文件。
在 lcd,h中 加入类型声明

typedef uint16_t u16;
typedef uint8_t u8;
typedef uint32_t u32;
typedef volatile uint32_t vu32;
typedef volatile uint16_t vu16;
typedef volatile uint8_t vu8;

测试程序

/* USER CODE BEGIN 2 */
LCD_Init();
sprintf((char*)lcd_id,"LCD ID:%04x",lcddev.id);
printf("lcd: %04x\r\n",lcddev.id);
printf("lcd height:%u\r\n",lcddev.height);
printf("lcd width: %u\r\n",lcddev.width);
LCD_Clear(WHITE);
POINT_COLOR=RED;
LCD_ShowString(30,40,210,24,24,"WarShip STM32 ^_^");
LCD_ShowString(30,70,200,16,16,"TFTLCD TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,lcd_id); //显示LCD ID
LCD_ShowString(30,130,200,12,12,"2017/5/27");
/* USER CODE END 2 */

通过串口打印LCD_ID,若ID为0000则将编译器优化等级设置为零,
或将结构体_lcd_dev成员用volatile修饰。