LCD在日常生活中应用广泛,本章节直接介绍如何利用FMSC模拟8080时序去点亮LCD。本章用到野火的霸道开发板。
信号线 |
ILI9341对应的信号线 |
说明 |
LCD_DB[15:0] |
D[15:0] |
数据信号 |
LCD_RD |
RDX |
读数据信号,低电平有效 |
LCD_RS |
D/CX |
数据/命令信号,高电平时,D[15:0]表示的是数据(RGB像素数据或命令数据),低电平时D[15:0]表示控制命令 |
LCD_RESET |
RESX |
复位信号,低电平有效 |
LCD_WR |
WRX |
写数据信号,低电平有效 |
LCD_CS |
CSX |
片选信号,低电平有效 |
LCD_BK |
- |
背光信号,低电平点亮 |
GPIO[5:1] |
- |
触摸屏的控制信号线,下一章再介绍 |
这些引出的信号线即8080通讯接口,带X的表示低电平有效,STM32通过该接口与ILI9341芯片进行通讯,实现对液晶屏的控制。通讯的内容主要包括命令和显存数据,显存数据即各个像素点的RGB565内容;命令是指对ILI9341的控制指令,MCU可通过8080接口发送命令编码控制ILI9341的工作方式,例如复位指令、设置光标指令、睡眠模式指令等等,具体的指令在《ILI9341.pdf》数据手册均有详细说明。
命令时序由片选信号CSX拉低开始,对数据/命令选择信号线D/CX也置低电平表示写入的是命令地址(可理解为命令编码,如软件复位命令:0x01),以写信号WRX为低,读信号RDX为高表示数据传输方向为写入,同时,在数据线D[17:0](或D[15:0])输出命令地址,在第二个传输阶段传送的是命令的参数,所以D/CX要置高电平,表示写入的是命令数据,命令数据是某些指令带有的参数,如复位指令编码为0x01,它后面可以带一个参数,该参数表示多少秒后复位(实际的复位命令不含参数,此处只是为了讲解指令编码与参数的区别)。 当需要把像素数据写入GRAM时,过程很类似,把片选信号CSX拉低后,再把数据/命令选择信号线D/CX置为高电平,这时由D[17:0]传输的数据则会被ILI9341保存至它的GRAM中。
ILI9341的8080通讯接口时序可以由STM32使用普通I/O接口进行模拟,但这样效率太低,STM32提供了一种特别的控制方法——使用FSMC接口实现8080时序。
控制LCD时,适合使用FSMC的NOR\PSRAM模式,如下:
FSMC信号名称 |
信号方向 |
功能 |
CLK |
输出 |
时钟(同步突发模式使用) |
A[25:0] |
输出 |
地址总线 |
D[15:0] |
输入/输出 |
双向数据总线 |
NE[x] |
输出 |
片选,x = 1...4 |
NOE |
输出 |
输出使能 |
NWE |
输出 |
写使能 |
NWAIT |
输入 |
NOR闪存要求FSMC等待的信号 |
NADV |
输出 |
地址、数据线复用时作锁存信号 |
在控制LCD时,使用的是类似异步、地址与数据线独立的NOR FLASH控制方式,所以实际上CLK、NWAIT、NADV引脚并没有使用到。
FSMC NOR/PSRAM中的模式B的写时序如下图:
相对比8080写时序来看:
对比FSMC NOR/PSRAM中的模式B时序与ILI9341液晶控制器芯片使用的8080时序可发现,这两个时序是十分相似的(除了FSMC的地址线A和8080的D/CX线,可以说是完全一样)。 所以我们可以FSMC上的地址线的电平高低来模拟命令(电平为低)和数据(电平为高)
对于FSMC和8080接口,前四种信号线都是完全一样的,仅仅是FSMC的地址信号线A[25:0]与8080的数据/命令选择线D/CX有区别。而对于D/CX线,它为高电平的时候表示数值,为低电平的时候表示命令,如果能使用FSMC的A地址线根据不同的情况产生对应的电平,那么就完全可以使用FSMC来产生8080接口需要的时序了。
FSMC-NOR信号线
功能
8080信号线
功能
NEx
片选信号
CSX
片选信号
NWR
写使能
WRX
写使能
NOE
读使能
RDX
读使能
D[15:0]
数据信号
D[15:0]
数据信号
A[25:0]
地址信号
D/CX
数据/命令选择
为了模拟出8080时序,我们可以把FSMC的A0地址线(也可以使用其它A1/A2等地址线)与ILI9341芯片8080接口的D/CX信号线连接,那么当A0为高电平时(即D/CX为高电平),数据线D[15:0]的信号会被ILI9341理解为数值,若A0为低电平时(即D/CX为低电平),传输的信号则会被理解为命令。
main主函数
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main ( void )
{
ILI9341_Init (); //LCD 初始化
USART_Config();
printf("\r\n ********** 液晶屏英文显示程序*********** \r\n");
//其中0、3、5、6 模式适合从左至右显示文字,
//不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果
//其中 6 模式为大部分液晶例程的默认显示方向
ILI9341_GramScan ( 6 );
while ( 1 )
{
LCD_Test();
}
}
/*用于测试各种液晶的函数*/
void LCD_Test(void)
{
/*演示显示变量*/
static uint8_t testCNT = 0;
char dispBuff[100];
testCNT++;
LCD_SetFont(&Font8x16);
LCD_SetColors(RED,BLACK);
ILI9341_Clear(0,0,LCD_X_LENGTH,LCD_Y_LENGTH); /* 清屏,显示全黑 */
/********显示字符串示例*******/
ILI9341_DispStringLine_EN(LINE(0),"BH 3.2 inch LCD para:");
ILI9341_DispStringLine_EN(LINE(1),"Image resolution:240x320 px");
ILI9341_DispStringLine_EN(LINE(2),"ILI9341 LCD driver");
ILI9341_DispStringLine_EN(LINE(3),"XPT2046 Touch Pad driver");
/********显示变量示例*******/
LCD_SetFont(&Font16x24);
LCD_SetTextColor(GREEN);
/*使用c标准库把变量转化成字符串*/
sprintf(dispBuff,"Count : %d ",testCNT);
LCD_ClearLine(LINE(4)); /* 清除单行文字 */
/*然后显示该字符串即可,其它变量也是这样处理*/
ILI9341_DispStringLine_EN(LINE(4),dispBuff);
ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8); /* 清屏,显示全黑 */
}
ILI9341初始化
/**
* @brief ILI9341初始化函数,如果要用到lcd,一定要调用这个函数
* @param 无
* @retval 无
*/
void ILI9341_Init ( void )
{
ILI9341_GPIO_Config ();
ILI9341_FSMC_Config ();
ILI9341_BackLed_Control ( ENABLE ); //点亮LCD背光灯
ILI9341_Rst ();
ILI9341_REG_Config ();
//设置默认扫描方向,其中 6 模式为大部分液晶例程的默认显示方向
ILI9341_GramScan(LCD_SCAN_MODE);
}
显示字符串函数
/**
* @brief 在 ILI9341 显示器上显示英文字符串
* @param line :在特定扫描方向下字符串的起始Y坐标
* 本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
* 宏LINE(x)会根据当前选择的字体来计算Y坐标值。
* 显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
* @param pStr :要显示的英文字符串的首地址
* @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
* @retval 无
*/
void ILI9341_DispStringLine_EN ( uint16_t line, char * pStr )
{
uint16_t usX = 0;
while ( * pStr != '\0' )
{
if ( ( usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width ) > LCD_X_LENGTH )
{
usX = ILI9341_DispWindow_X_Star;
line += LCD_Currentfonts->Height;
}
if ( ( line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height ) > LCD_Y_LENGTH )
{
usX = ILI9341_DispWindow_X_Star;
line = ILI9341_DispWindow_Y_Star;
}
ILI9341_DispChar_EN ( usX, line, * pStr);
pStr ++;
usX += LCD_Currentfonts->Width;
}
}
/**
* @brief 在 ILI9341 显示器上显示一个英文字符
* @param usX :在特定扫描方向下字符的起始X坐标
* @param usY :在特定扫描方向下该点的起始Y坐标
* @param cChar :要显示的英文字符
* @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
* @retval 无
*/
void ILI9341_DispChar_EN ( uint16_t usX, uint16_t usY, const char cChar )
{
uint8_t byteCount, bitCount,fontLength;
uint16_t ucRelativePositon;
uint8_t *Pfont;
//对ascii码表偏移(字模表不包含ASCII表的前32个非图形符号)
ucRelativePositon = cChar - ' ';
//每个字模的字节数
fontLength = (LCD_Currentfonts->Width*LCD_Currentfonts->Height)/8;
//字模首地址
/*ascii码表偏移值乘以每个字模的字节数,求出字模的偏移位置*/
Pfont = (uint8_t *)&LCD_Currentfonts->table[ucRelativePositon * fontLength];
//设置显示窗口
ILI9341_OpenWindow ( usX, usY, LCD_Currentfonts->Width, LCD_Currentfonts->Height);
ILI9341_Write_Cmd ( CMD_SetPixel );
//按字节读取字模数据
//由于前面直接设置了显示窗口,显示数据会自动换行
for ( byteCount = 0; byteCount < fontLength; byteCount++ )
{
//一位一位处理要显示的颜色
for ( bitCount = 0; bitCount < 8; bitCount++ )
{
if ( Pfont[byteCount] & (0x80>>bitCount) )
ILI9341_Write_Data ( CurrentTextColor );
else
ILI9341_Write_Data ( CurrentBackColor );
}
}
}
以上代码仅供思路参考,需要完整代码的小伙伴可以私信我拿,谢谢