一、前期准备
1.1 资料简介
文档中有关于2.8寸屏ILI9341的驱动手册以及网络获取的中文手册供大家参考
1.2 硬件环境
M3开发板
ST-Link调试器
ILI9341 2.8寸LCD屏
二、FSMC与LCD
2.1 8080接口
一般情况下,在嵌入式行业中常用的屏幕(OLED、TFT-LCD)会提供多个接口,其中比较常见的为SPI、IIC、8080、6800接口等,可硬件选择接口,选择方式参考官方手册,本次课程中使用屏幕硬件接口选择方式如下:
串行通讯SPI与IIC这里我们不做过多介绍,基本都是一些常规的底层硬件接口。对于屏幕显示而言,串行通信数据传输较慢,对于很多情况而言,我们更倾向于使用并行通信。并行通讯接口在屏幕中一般提供了6800与8080接口,6800总线又叫做摩托罗拉总线、8080时序也叫做英特尔总线。
在底层MCU驱动环境下,我们比较多的会使用MCU接口中的8080时序,在本次课程中我们使用了16位的8080接口,8080接口需要如下驱动管脚:
管脚名称 |
管脚功能 |
其他说明 |
CS |
片选 |
低电平有效 |
RES |
复位 |
低电平有效 |
WR |
写使能 |
低电平有效 |
RD |
读使能 |
低电平有效 |
DC |
数据命令选择端 |
低电平命令、高电平数据 |
D[15:0] |
16位并行数据线 |
无 |
8080-I/8080-II串并接口,通过D[17:0]数据引脚实现寄存器的存取
8080写数据时序图:
8080读数据时序图:
2.2 MCU中的FSMC
FSMC是嵌入式单片机MCU提供的一个接口:灵活的静态存储器控制器
FSMC可外接外部RAM到存储器上,地址空间为0X60000000~0X9FFFFFFF,共1G空间。
而这1G空间由分为了16个区,其中4个区为1个块。即一个区为64M大小,一个块为256M大小。
存储块1用于访问NOR闪存或PSRAM存储设备。
存储块2和3用于访问NAND闪存设备,每个存储块连接一个NAND闪存。
存储块4用于访问PC卡设备
根据以上内容,结合如下FSMC存储块示意图,我们可以计算出每一个区的空间首地址,此操作在此次操作中尤为重要。
比如如何计算出块1的第4个区空间首地址 addr = 0X60000000 + 3*64M 即空间首地址为基地址加上前面三个区的总空间大小 那么addr = 0X60000000 + 192M addr = 0X60000000 + 201326592字节 addr = 0X60000000 + 0XC000000 addr = 0X6C000000 |
接下来我们就需要关注如何接入将外部存储器,并且FSMC如何控制该控制块
其中HADDR是需要转换到外部存储器的内部AHB地址线。
当外部存储器的数宽为8位时,FSMC的地址线将与MCU内存空间地址线从0开始依次对应连接。
当外部存储器的数宽为16位时,FSMC的地址线将与MCU内存空间地址线从1开始依次错位连接。
此时,我们会发现FSMC的模式A与LCD屏的8080接口时序是非常相似的:
FSMC模式A读操作、读操作:
2.3 FSMC与8080时序
此时,我们可以将FSMC于8080时序在硬件接口上先做一个对比
8080时序 |
FSMC | ||
CS |
片选信号,低电平有效 |
NEx |
片选,低电平有效(x=1...4) |
WR |
写使能 |
NWR |
写使能 |
RD |
读使能 |
NOE |
输出使能 |
D[15:0] |
并行数据线 |
D[15:0] |
并行数据线 |
D/C |
数据命令选择端 |
A[25:0] |
地址线 |
而通讯的时序也可以做一个简单的对比:
经过对比我们将会发现8080接口的硬件管脚定义与FSMC接口的硬件管脚定义基本吻合,但是8080时序需要有一个数据命令选择管脚,而FSMC没有。但是FSMC接口也多出了26根地址线供我们使用,那么我们是否可以将地址线其中的一根线作为数据命令选择端与LCD屏连接呢?
此时我们必须注意D/C管脚为低电平时代表传输的信号为command,高电平是data。此时我们便发现了一个巧妙的方法使用FSMC地址线其中的一条线作为我们8080接口的D/C管脚(任意一条地址线都可以)。那么本次课程中使用的硬件设计如下所示:
LCD管脚 |
MCU IO |
FSMC功能 |
管脚功能 |
配置模式 |
BL |
PD12 |
无 |
背光板 |
通用推挽输出 |
CS |
PD7 |
FSMC_NE1 |
片选 |
复用推挽输出 |
RD |
PD4 |
FSMC_NOE |
读使能 |
复用推挽输出 |
WR |
PD5 |
FSMC_NWE |
写使能 |
复用推挽输出 |
RS(D/C) |
PD11 |
FSMC_A16 |
数据命令选择 |
复用推挽输出 |
D0 |
PD14 |
FSMC_D0 |
并行数据线0 |
复用推挽输出 |
D1 |
PD15 |
FSMC_D1 |
并行数据线1 |
复用推挽输出 |
D2 |
PD0 |
FSMC_D2 |
并行数据线2 |
复用推挽输出 |
D3 |
PD1 |
FSMC_D3 |
并行数据线3 |
复用推挽输出 |
D4 |
PE7 |
FSMC_D4 |
并行数据线4 |
复用推挽输出 |
D5 |
PE8 |
FSMC_D5 |
并行数据线5 |
复用推挽输出 |
D6 |
PE9 |
FSMC_D6 |
并行数据线6 |
复用推挽输出 |
D7 |
PE10 |
FSMC_D7 |
并行数据线7 |
复用推挽输出 |
D8 |
PE11 |
FSMC_D8 |
并行数据线8 |
复用推挽输出 |
D9 |
PE12 |
FSMC_D9 |
并行数据线9 |
复用推挽输出 |
D10 |
PE13 |
FSMC_D10 |
并行数据线10 |
复用推挽输出 |
D11 |
PE14 |
FSMC_D11 |
并行数据线11 |
复用推挽输出 |
D12 |
PE15 |
FSMC_D12 |
并行数据线12 |
复用推挽输出 |
D13 |
PD8 |
FSMC_D13 |
并行数据线13 |
复用推挽输出 |
D14 |
PD9 |
FSMC_D14 |
并行数据线14 |
复用推挽输出 |
D15 |
PD10 |
FSMC_D15 |
并行数据线15 |
复用推挽输出 |
2.4 代码实现
关于LCD屏显示原理在此我们不做过多的赘述,本文我们重点关注FSMC如何驱动LCD屏,本文我们以逐日开发板(ARM Cortex-M3内核STM32F103VET6)与ILI9341 LCD屏为硬件平台(各位使用市面上常见的开发板即可,了解原理即可灵活修改),驱动代码初始化框架为:
/************************
函数名称:LCD_Config
函数作用:LCD 初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Config(void)
{
LCD_PortConfig();
LCD_FSMCConfig();
LCD_9341Config();
LCD_Clear(WHITE);
}
在此我们对此代码按步骤分析:
- 底层接口初始化—LCD_PortConfig
初始化所有IO口,包含FSMC接口与背光板模式,并将背光板关闭。
- FSMC初始化—LCD_FSMCConfig
初始化FSMC工作模式为模式A
选择FSMC_Bank 为FSMC_Bank1_NORSRAM1,即块1的区1(因为我们CS管脚连接的PD7为FSMC_NE1)
配置数宽为16位,因为我们LCD使用16位8080并行接口
- ILI9341驱动初始化—LCD_9341Config
对于ILI9341驱动来说(其他屏幕也基本类似),驱动的初始化主要靠写命令与写数据两个函数,将我们的配置写入
LCD屏对应的配置寄存器。
那么我们在此使用了一个巧妙的方式提供了这两个基本函数:
//LCD在FSMC中寄存器操作
//使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=16 A16作为数据命令区分线
//1 1111 1111 1111 1110
//01100000 000000X1 11111111 11111110
//01100000 00000010 00000000 00000000
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE ((u32)(0x60000000 | 0x0001FFFE))
#define TFTLCD ((LCD_TypeDef *) LCD_BASE)
//LCD寄存器结构体
typedef struct
{
u16 LCD_REG;//命令寄存器
u16 LCD_RAM;
} LCD_TypeDef;
/************************
函数名称:LCD_WR_REG
函数作用:LCD 读写命令函数
函数入口:
data 写入指令
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_REG(volatile uint16_t regval)
{
regval=regval; //使用-O2优化的时候,必须插入的延时
TFTLCD->LCD_REG=regval;//写入要写的寄存器序号
}
/************************
函数名称:LCD_WR_DATA
函数作用:LCD 读写数据函数
函数入口:
data 写入数据
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_DATA(volatile uint16_t data)
{
data=data; //使用-O2优化的时候,必须插入的延时
TFTLCD->LCD_RAM=data;
//0110 1100 0000 0000 0000 1000 0000 0000
}
实现命令与数据传输的关键其实就是数据命令选择端管脚的状态,我们参照单片机内部寄存器的方式制作了一个类似于
GPIO的外设:TFTLCD
TFTLCD是一个类似于GPIO、USART1、ADC1之类的外设,其类型为结构体指针,数据为存储器空间的基地址,我们异
步一步解决这个问题。
结构体指针:
我们定义了TFTLCD的结构体指针为LCD_TypeDef类型,内含两个寄存器—LCD_REG寄存器与LCD_RAM寄存器。两个
变量都为u16类型,也就是说当我操作TFTLCD->RAM寄存器时,会在TFTLCD基地址的基础上地址偏移两个字节,这个也是我
们寄存器偏移量的原理。
所以最终我们将TFTLCD宏定义为((LCD_TypeDef *) LCD_BASE)
此处我们给出寄存器GPIOA的两张截图,大家可以对比参考,领悟其中奥妙
(PS:大家可以借此机会领悟寄存器空间的魅力^_^)
基地址:
基地址计算就要我们关注上面的一些知识了,整个FSMC的基地址为0X60000000,我们接入的是SRAM即块1。我们连接的片选是FSMC_NE1,所以我们此时基地址偏移量为0。
但是如果我们使用的是块2并且片选为NE2的话,基地址则为0X70000000+64M,得到0X74000000。
然后就是如何将DC管脚切换的问题了,在驱动LCD屏时,FSMC所有的地址线是不影响我们的操作的,所以我们将DC接入了其中任意一个地址线上,我们在硬件设计时接入的是A16。
也就是说当我使用TFTLCD->REG时,A16应该是低电平,此时地址在基地址基础上不变化
当我使用TFTLCD->RAM时,A16应该是高电平,此时地址在基地址基础上偏移两个字节
并且上文提到,当数宽为16时,HADDR与FSMC_A引脚是错位相连的,即HADDR[0]未使用
由此我们可以得到,当地址偏移两字节时A16=1,地址线数据为10 0000 0000 0000 0000(十六进制0X20000)
反推不偏移两字节时,A16=0,地址数据线为01 1111 1111 1111 1110(十六进制0X1FFFE)
所以最终我们得到最终的基地址LCD_BASE为0x60000000 | 0x0001FFFE
- 刷新屏幕—LCD_Clear
此代码大家需要去详细了解LCD屏幕的工作模式,本文我们制作测试使用。如果前面配置没有问题,那么屏幕初始化后将
变为大家想使用的颜色。
本文到此便结束了,如有不对之处,欢迎大家的批评与指正!!!
LCD源文件
#include "tft_lcd.h"
/************************
函数名称:LCD_Config
函数作用:LCD 初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Config(void)
{
LCD_PortConfig();
LCD_FSMCConfig();
LCD_9341Config();
LCD_Clear(WHITE);
}
/************************
函数名称:LCD_PortConfig
函数作用:LCD管脚初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2020.12.31
修改时间:2020.12.31
补充说明:
管脚说明
LCD_BL PD12 通用推挽输出
FSMC_NE1(CS) PD7 复用推挽输出
FSMC_NOE PD4 复用推挽输出
FSMC_NWE PD5 复用推挽输出
FSMC_A16(RS) PD11 复用推挽输出
FSMC_D0~D15 复用推挽输出
PD14 PD15 PD0 PD1 PE7 PE8 PE9 PE10
PE11 PE12 PE13 PE14 PE15 PD8 PD9 PD10
************************/
void LCD_PortConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//PORT:D E
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
//PD12背光板
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_14|GPIO_Pin_15;
GPIO_Init(GPIOD,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_Init(GPIOE,&GPIO_InitStructure);
}
/************************
函数名称:LCD_FSMCConfig
函数作用:LCD FSMC初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2020.12.31
修改时间:2020.12.31
************************/
void LCD_FSMCConfig(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStruct;
FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitWirte;
FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitRead;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE); //使能FSMC时钟
FSMC_NORSRAMTimingInitRead.FSMC_AddressSetupTime = 0x01; //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
FSMC_NORSRAMTimingInitRead.FSMC_AddressHoldTime = 0x00; //地址保持时间(ADDHLD)模式A未用到
FSMC_NORSRAMTimingInitRead.FSMC_DataSetupTime = 0x0f; // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
FSMC_NORSRAMTimingInitRead.FSMC_BusTurnAroundDuration = 0x00;
FSMC_NORSRAMTimingInitRead.FSMC_CLKDivision = 0x00;
FSMC_NORSRAMTimingInitRead.FSMC_DataLatency = 0x00;
FSMC_NORSRAMTimingInitRead.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
FSMC_NORSRAMTimingInitWirte.FSMC_AddressSetupTime = 0x00; //地址建立时间(ADDSET)为1个HCLK
FSMC_NORSRAMTimingInitWirte.FSMC_AddressHoldTime = 0x00; //地址保持时间(A
FSMC_NORSRAMTimingInitWirte.FSMC_DataSetupTime = 0x03; ////数据保存时间为4个HCLK
FSMC_NORSRAMTimingInitWirte.FSMC_BusTurnAroundDuration = 0x00;
FSMC_NORSRAMTimingInitWirte.FSMC_CLKDivision = 0x00;
FSMC_NORSRAMTimingInitWirte.FSMC_DataLatency = 0x00;
FSMC_NORSRAMTimingInitWirte.FSMC_AccessMode = FSMC_AccessMode_A; //模式A
FSMC_NORSRAMInitStruct.FSMC_Bank = FSMC_Bank1_NORSRAM1;// 这里我们使用NE1 ,也就对应BTCR[6],[7]。
FSMC_NORSRAMInitStruct.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
FSMC_NORSRAMInitStruct.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;
FSMC_NORSRAMInitStruct.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit
FSMC_NORSRAMInitStruct.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
FSMC_NORSRAMInitStruct.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
FSMC_NORSRAMInitStruct.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
FSMC_NORSRAMInitStruct.FSMC_WrapMode = FSMC_WrapMode_Disable;
FSMC_NORSRAMInitStruct.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
FSMC_NORSRAMInitStruct.FSMC_WriteOperation = FSMC_WriteOperation_Enable; // 存储器写使能
FSMC_NORSRAMInitStruct.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
FSMC_NORSRAMInitStruct.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序
FSMC_NORSRAMInitStruct.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
FSMC_NORSRAMInitStruct.FSMC_ReadWriteTimingStruct=&FSMC_NORSRAMTimingInitRead;
FSMC_NORSRAMInitStruct.FSMC_WriteTimingStruct=&FSMC_NORSRAMTimingInitWirte;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStruct);
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1,ENABLE);
}
/************************
函数名称:LCD_9341Config
函数作用:LCD 驱动初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_9341Config(void)
{
Delay_Nopnms(100);
LCD_WR_REG(0xCF);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0xC1);
LCD_WR_DATA(0X30);
LCD_WR_REG(0xED);
LCD_WR_DATA(0x64);
LCD_WR_DATA(0x03);
LCD_WR_DATA(0X12);
LCD_WR_DATA(0X81);
LCD_WR_REG(0xE8);
LCD_WR_DATA(0x85);
LCD_WR_DATA(0x10);
LCD_WR_DATA(0x7A);
LCD_WR_REG(0xCB);
LCD_WR_DATA(0x39);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x34);
LCD_WR_DATA(0x02);
LCD_WR_REG(0xF7);
LCD_WR_DATA(0x20);
LCD_WR_REG(0xEA);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_REG(0xC0); //Power control
LCD_WR_DATA(0x1B); //VRH[5:0]
LCD_WR_REG(0xC1); //Power control
LCD_WR_DATA(0x01); //SAP[2:0];BT[3:0]
LCD_WR_REG(0xC5); //VCM control
LCD_WR_DATA(0x30); //3F
LCD_WR_DATA(0x30); //3C
LCD_WR_REG(0xC7); //VCM control2
LCD_WR_DATA(0XB7);
LCD_WR_REG(0x36); // Memory Access Control
LCD_WR_DATA(0x48);
LCD_WR_REG(0x3A);
LCD_WR_DATA(0x55);
LCD_WR_REG(0xB1);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x1A);
LCD_WR_REG(0xB6); // Display Function Control
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0xA2);
LCD_WR_REG(0xF2); // 3Gamma Function Disable
LCD_WR_DATA(0x00);
LCD_WR_REG(0x26); //Gamma curve selected
LCD_WR_DATA(0x01);
LCD_WR_REG(0xE0); //Set Gamma
LCD_WR_DATA(0x0F);
LCD_WR_DATA(0x2A);
LCD_WR_DATA(0x28);
LCD_WR_DATA(0x08);
LCD_WR_DATA(0x0E);
LCD_WR_DATA(0x08);
LCD_WR_DATA(0x54);
LCD_WR_DATA(0XA9);
LCD_WR_DATA(0x43);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x0F);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_REG(0XE1); //Set Gamma
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x15);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x11);
LCD_WR_DATA(0x06);
LCD_WR_DATA(0x2B);
LCD_WR_DATA(0x56);
LCD_WR_DATA(0x3C);
LCD_WR_DATA(0x05);
LCD_WR_DATA(0x10);
LCD_WR_DATA(0x0F);
LCD_WR_DATA(0x3F);
LCD_WR_DATA(0x3F);
LCD_WR_DATA(0x0F);
LCD_WR_REG(0x2B);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x3f);
LCD_WR_REG(0x2A);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0xef);
LCD_WR_REG(0x11); //Exit Sleep
Delay_Nopnms(120);
LCD_WR_REG(0x29); //display on
LCD_WR_REG(0x36);
LCD_WR_DATA(0x08);//II9341必须这样
//整个LCD就初始化完成
//lCD有个背光灯
LCD_LED_ON;//打开LCD屏幕背光灯
//REG :写命令
//DATA:写数据
LCD_WR_REG(0x2A);
LCD_WR_DATA(0);
LCD_WR_DATA(0); //
LCD_WR_DATA(0);
LCD_WR_DATA((240-1));
//X轴是从 0开始 到240结束
LCD_WR_REG(0x2B);
LCD_WR_DATA(0);
LCD_WR_DATA(0);
LCD_WR_DATA((320-1)>>8);
LCD_WR_DATA((320-1)&0xFF);
//y轴的起始 0
//y轴的结束 320
LCD_WR_REG(0x2C);
}
/************************
函数名称:LCD_WR_REG
函数作用:LCD 读写命令函数
函数入口:
data 写入指令
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_REG(volatile uint16_t regval)
{
regval=regval; //使用-O2优化的时候,必须插入的延时
TFTLCD->LCD_REG=regval;//写入要写的寄存器序号
}
/************************
函数名称:LCD_WR_DATA
函数作用:LCD 读写数据函数
函数入口:
data 写入数据
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_DATA(volatile uint16_t data)
{
data=data; //使用-O2优化的时候,必须插入的延时
TFTLCD->LCD_RAM=data;
//0110 1100 0000 0000 0000 1000 0000 0000
}
/************************
函数名称:LCD_Clear
函数作用:LCD 读写数据函数
函数入口:
Color 清屏颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Clear(u16 Color)
{
u32 i=0;
//设置列:0~240
LCD_WR_REG(0x2A);
LCD_WR_DATA(0); //起始列高八位
LCD_WR_DATA(0); //起始列低八位
LCD_WR_DATA(0); //停止列高八位
LCD_WR_DATA((240-1)); //停止列低八位
//设置行:0~320
LCD_WR_REG(0x2B);
LCD_WR_DATA(0);
LCD_WR_DATA(0);
LCD_WR_DATA((320-1)>>8);
//0000 0001 0011 1111 >> 8 = 0000 0001
LCD_WR_DATA((320-1)&0xFF);
//0000 0001 0011 1111 & 0000 0000 1111 1111=0011 1111
//写数据
LCD_WR_REG(0x2C);
for(i=0;i<240*320;i++)
{
LCD_WR_DATA(Color);//绿色
}
}
/************************
函数名称:Set_Dispaly
函数作用:设置LCD显示窗口
函数入口:
(point_x1,point_y1) 起始坐标
(point_x1,point_y1) 停止坐标
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void Set_Dispaly(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2)
{
LCD_WR_REG(0x2A);
LCD_WR_DATA(Line_x1 >> 8);
LCD_WR_DATA(Line_x1 & 0XFF);
LCD_WR_DATA(Line_x2 >> 8);
LCD_WR_DATA(Line_x2 & 0XFF);
LCD_WR_REG(0x2B);
LCD_WR_DATA(Line_y1 >> 8);
LCD_WR_DATA(Line_y1 & 0XFF);
LCD_WR_DATA(Line_y2 >> 8);
LCD_WR_DATA(Line_y2 & 0XFF);
}
/************************
函数名称:LCD_Point
函数作用:LCD 打点函数
函数入口:
(point_x,point_y) 坐标
Color 颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Point(uint16_t point_x,uint16_t point_y,u16 Color)
{
LCD_WR_REG(0x2A);
LCD_WR_DATA(point_x >> 8);
LCD_WR_DATA(point_x & 0XFF);
LCD_WR_DATA(point_x >> 8);
LCD_WR_DATA(point_x & 0XFF);
LCD_WR_REG(0x2B);
LCD_WR_DATA(point_y >> 8);
LCD_WR_DATA(point_y & 0XFF);
LCD_WR_DATA(point_y >> 8);
LCD_WR_DATA(point_y & 0XFF);
LCD_WR_REG(0x2C);
LCD_WR_DATA(Color);
}
/************************
函数名称:LCD_Line
函数作用:LCD 画线函数
函数入口:
Line_x1,Line_y1 起始坐标
Line_x2,Line_y2 停止坐标
Wight 线宽
Color 颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Line(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Wight,u16 Color)
{
uint16_t Line_len = 0;
uint16_t i = 0,j=0;
uint16_t x=0,y=0;
uint16_t offset= 0;
float Slope = 0;
if(Line_x1 == Line_x2)//竖线
{
Set_Dispaly(Line_x1-(Wight/2),Line_y1,Line_x1-(Wight/2)+Wight-1,Line_y2);
if(Line_y1 > Line_y2)
Line_len = Line_y1-Line_y2;
else
Line_len = Line_y2-Line_y1;
LCD_WR_REG(0x2C);
for(i=0;i<Line_len*Wight;i++)
LCD_WR_DATA(Color);
}
else if(Line_y1 == Line_y2)//横线
{
Set_Dispaly(Line_x1,Line_y1-(Wight/2),Line_x2,Line_y1-(Wight/2)+Wight-1);
if(Line_x1 > Line_x2)
Line_len = Line_x1-Line_x2;
else
Line_len = Line_x2-Line_x1;
LCD_WR_REG(0x2C);
for(i=0;i<Line_len*Wight;i++)
LCD_WR_DATA(Color);
}
else//斜线
{
if(Line_x1 > Line_x2)
x = Line_x1-Line_x2;
else
x = Line_x2-Line_x1;
if(Line_y1 > Line_y2)
y = Line_y1-Line_y2;
else
y = Line_y2-Line_y1;
Slope = y/(x*1.0);
for(i=0;i<x;i++)
{
offset = (Slope*i)-(Wight/2);
for(j=0;j<Wight;j++)
{
LCD_Point(Line_x1+i,Line_y1+offset,Color);
offset++;
}
}
}
}
/************************
函数名称:LCD_Rectangle
函数作用:LCD 画矩形函数
函数入口:
Line_x1,Line_y1 起始坐标
Line_x2,Line_y2 停止坐标
Color 颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Rectangle(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Color)
{
uint16_t i=0;
uint16_t x=0,y=0;
if(Line_x1 > Line_x2)
x = Line_x1-Line_x2;
else
x = Line_x2-Line_x1;
if(Line_y1 > Line_y2)
y = Line_y1-Line_y2;
else
y = Line_y2-Line_y1;
Set_Dispaly(Line_x1,Line_y1,Line_x2,Line_y2);
LCD_WR_REG(0x2C);
for(i=0;i<x*y;i++)
LCD_WR_DATA(Color);
}
/************************
函数名称:LCD_Rectangle
函数作用:LCD 画矩形函数
函数入口:
Circ_x,Circ_y 起始坐标
Circ_r 半径
Color 颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
uint8_t Get_CircularPoint(uint16_t Circ_x,uint16_t Circ_y,uint16_t Point_x,uint16_t Point_y,uint16_t Circ_r)
{
int x=0,y=0;
x=Circ_x-Point_x;
y=Circ_y-Point_y;
if((Circ_r*Circ_r) > ((x*x)+(y*y)))
return 1;
else
return 0;
}
void LCD_Circular(uint16_t Circ_x,uint16_t Circ_y,uint16_t Circ_r,uint16_t Color)
{
uint16_t i=0,j=0;
for(i=Circ_x-Circ_r;i<Circ_x+Circ_r;i++)
{
for(j=Circ_y-Circ_r;j<Circ_y+Circ_r;j++)
{
if(Get_CircularPoint(Circ_x,Circ_y,i,j,Circ_r)==1)
LCD_Point(i,j,Color);
}
}
}
/************************
函数名称:LCD_DrawFont
函数作用:LCD 写字函数
函数入口:
start_x,start_y 起始坐标
Font_wide 字宽
Font_high 字高
Back_color 背景颜色(RGB565)
Font_color 字体颜色(RGB565)
Font_buf 字模数组指针
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_DrawFont(uint16_t start_x,uint16_t start_y,uint16_t Font_wide,uint16_t Font_high,uint16_t Back_color,uint16_t Font_color,uint8_t *Font_buf)
{
uint16_t Display_Buf[4] = {0};
uint16_t i=0,j=0;
uint16_t Buf_Count = 0;
Display_Buf[0] = start_x;
Display_Buf[1] = start_y;
Display_Buf[2] = start_x+Font_wide-1;
Display_Buf[3] = start_y+Font_high-1;
Buf_Count = Font_wide*Font_high/8;
//画LCD显示范围
Set_Dispaly(Display_Buf[0],Display_Buf[1],Display_Buf[2],Display_Buf[3]);
LCD_WR_REG(0x2C);
for(i=0;i<Buf_Count;i++)
{
for(j=0;j<8;j++)
{
//0XA5<<0 & 0X80 = 1010 0101 & 1000 0000 = 1000 0000
//0XA5<<1 & 0X80 = 0100 1010 & 0000 0000 = 0000 0000
if((Font_buf[i]<<j)&0X80)
LCD_WR_DATA(Font_color);
else
LCD_WR_DATA(Back_color);
}
}
}
/************************
函数名称:LCD_DrawString
函数作用:LCD 写字符串
函数入口:
start_x 起始x坐标
start_y 起始y坐标
Back_color 背景色
Font_color 字体色
Fontsize 字体大小(Fontsize_16、Fontsize_24、Fontsize_32)
*Font_buf 字符串
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
补充说明:
横向取模
字节不倒叙
************************/
void LCD_DrawString(uint16_t start_x,uint16_t start_y,uint16_t Back_color,uint16_t Font_color,_FontSize Fontsize,char *Font_buf)
{
uint16_t x_offset = 0,y_offset = 0;
int Font_offset = 0;
x_offset = start_x;
y_offset = start_y;
//判断字符串是否结束
while(*Font_buf != '\0')
{
//判断汉字?字符?
if(*Font_buf == 10)
{
x_offset = start_x;
start_y += 16;
goto PP1;
}
if(*Font_buf <= 127)//字符
{
Font_offset = ASCII_GetFontAddr(*Font_buf);
if(Font_offset >= 0)
{
switch(Fontsize)
{
case Fontsize_16:
if(x_offset > 232)
{
x_offset = start_x;
y_offset += 16;
}
LCD_DrawFont(x_offset,y_offset,8,16,Back_color,Font_color,&ASCII_Font16Buf[Font_offset*16]);
x_offset += 8;
break;
case Fontsize_24:
if(x_offset > 228)
{
x_offset = start_x;
y_offset += 24;
}
LCD_DrawFont(x_offset,y_offset,16,24,Back_color,Font_color,&ASCII_Font24Buf[Font_offset*48]);
x_offset += 16;//此处改为12可以让字符紧密
break;
case Fontsize_32:
if(x_offset > 226)
{
x_offset = start_x;
y_offset += 32;
}
LCD_DrawFont(x_offset,y_offset,16,32,Back_color,Font_color,&ASCII_Font32Buf[Font_offset*64]);
x_offset += 16;
break;
}
}
PP1:
Font_buf += 1;
}
else//汉字
{
switch(Fontsize)
{
case Fontsize_16:
if(x_offset > 224)
{
x_offset = start_x;
y_offset += 16;
}
Font_offset = GB2312_GetFont16Addr(*Font_buf,*(Font_buf+1));
if(Font_offset >= 0)
LCD_DrawFont(x_offset,y_offset,16,16,Back_color,Font_color,&GB2312_Font16Buf[Font_offset*32]);
x_offset += 16;
break;
case Fontsize_24:
if(x_offset > 212)
{
x_offset = start_x;
y_offset += 24;
}
Font_offset = GB2312_GetFont24Addr(*Font_buf,*(Font_buf+1));
if(Font_offset >= 0)
LCD_DrawFont(x_offset,y_offset,24,24,Back_color,Font_color,&GB2312_Font24Buf[Font_offset*72]);
x_offset += 24;
break;
case Fontsize_32:
if(x_offset > 208)
{
x_offset = start_x;
y_offset += 32;
}
Font_offset = GB2312_GetFont32Addr(*Font_buf,*(Font_buf+1));
if(Font_offset >= 0)
LCD_DrawFont(x_offset,y_offset,32,32,Back_color,Font_color,&GB2312_Font32Buf[Font_offset*128]);
x_offset += 32;
break;
}
Font_buf += 2;
}
}
}
/************************
函数名称:LCD_DrawPhoto
函数作用:LCD 画图函数
函数入口:
start_x,start_y 起始坐标
Photo_wide 图片宽
Photo_high 图片高
Photo_buf 图片数组指针
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
补充说明:
水平扫描
高位在前
************************/
void LCD_DrawPhoto(uint16_t start_x,uint16_t start_y,uint16_t Photo_wide,uint16_t Photo_high,const unsigned char *Photo_buf)
{
uint16_t Display_Buf[4] = {0};
uint32_t i=0;
uint32_t Buf_Count = 0,Color_Temp=0;
Display_Buf[0] = start_x;
Display_Buf[1] = start_y;
Display_Buf[2] = start_x+Photo_wide-1;
Display_Buf[3] = start_y+Photo_high-1;
Buf_Count = Photo_wide*Photo_high*2;
//画LCD显示范围
Set_Dispaly(Display_Buf[0],Display_Buf[1],Display_Buf[2],Display_Buf[3]);
LCD_WR_REG(0x2C);
for(i=0;i<Buf_Count;i+=2)
{
Color_Temp = (Photo_buf[i]<<8)|Photo_buf[i+1];
LCD_WR_DATA(Color_Temp);
}
}
LCD头文件
#ifndef _TFT_LCD_H_
#define _TFT_LCD_H_
#include "stm32f10x.h"
#include "..\User\API\Delay\delay.h"
#include "font.h"
//LCD在FSMC中寄存器操作
//使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=16 A16作为数据命令区分线
//1 1111 1111 1111 1110
//01100000 000000X1 11111111 11111110
//01100000 00000010 00000000 00000000
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE ((u32)(0x60000000 | 0x0001FFFE))
#define TFTLCD ((LCD_TypeDef *) LCD_BASE)
//LCD寄存器结构体
typedef struct
{
u16 LCD_REG;//命令寄存器
u16 LCD_RAM;
} LCD_TypeDef;
//背光驱动
#define LCD_LED_ON GPIO_SetBits(GPIOD,GPIO_Pin_12)
#define LCD_LED_OFF GPIO_ResetBits(GPIOD,GPIO_Pin_12)
typedef enum{
Fontsize_16 = 0,//字符8*16,汉字16*16
Fontsize_24, //字符16*24,汉字24*24
Fontsize_32 //字符16*32,汉字32*32
}_FontSize;
//RGB565部分颜色宏定义
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 //棕色
#define BRRED 0XFC07 //棕红色
#define GRAY 0X8430 //灰色
//初始化函数
void LCD_Config(void);
void LCD_PortConfig(void);
void LCD_FSMCConfig(void);
void LCD_9341Config(void);
//底层接口函数
void LCD_WR_REG(volatile uint16_t regval);
void LCD_WR_DATA(volatile uint16_t data);
void Set_Dispaly(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2);
//LCD常用函数
void LCD_Clear(u16 Color);
void LCD_Point(uint16_t point_x,uint16_t point_y,u16 Color);
void LCD_Line(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Wight,u16 Color);
void LCD_Rectangle(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Color);
void LCD_Circular(uint16_t Circ_x,uint16_t Circ_y,uint16_t Circ_r,uint16_t Color);
void LCD_DrawFont(uint16_t start_x,uint16_t start_y,uint16_t Font_wide,uint16_t Font_high,uint16_t Back_color,uint16_t Font_color,uint8_t *Font_buf);
void LCD_DrawPhoto(uint16_t start_x,uint16_t start_y,uint16_t Photo_wide,uint16_t Photo_high,const unsigned char *Photo_buf);
void LCD_DrawString(uint16_t start_x,uint16_t start_y,uint16_t Back_color,uint16_t Font_color,_FontSize Fontsize,char *Font_buf);
#endif