零、目录
零、目录
一、硬件和参考对象
二、STC32G 的 LCM
三、简要的初始化流程
四、代码的移植和修改
五、引脚接线
六、运行结果
七、总结和注意事项
一、硬件和参考对象
- 本次将使用 STC32G12K128 芯片的 LCM 对 8080 接口的液晶屏(该液晶屏为野火指南者STM32F103开发板的 3.2寸 TFTLCD)进行驱动,目的是成功点亮并在液晶屏中输出英文字符串和绘图。
- 硬件接线、液晶屏驱动和测试程序均来自野火指南者STM32F103开发板 ,有关的资料可以在野火官网下载。
- 用到的参考图文为 STC32G 的库函数手册、STC32G 芯片手册、野火液晶屏原理图。
二、STC32G 的 LCM
STC32G 系列单片机内部集成了一个 LCM 接口控制器,可驱动 I8080 接口和 M6800 接口,支持 8位 和 16位 数据宽度。
本文将使用到 STC 官方提供的库函数来控制 LCM,对于 LCM 的初始化,将使用库文件: STC32G_LCM.H;LCM 的功能引脚切换,将使用库文件:STC32G_Switch().H。
STC32G 的库函数可以在 STC32G 官网下载。对于 LCM,STC32G_LCM.H 的主要内容如下:
/* 引脚定义 */
sbit LCD_RS = P4^5; // 数据/命令切换
sbit LCD_RD = P4^4; // 读控制
sbit LCD_WR = P4^2; // 写控制sbit LCD_CS = P4^0; // 片选
sbit LCD_RESET = P4^3; // 复位
sbit LCD_BK = P4^1; // 背光控制/* 数据/指令触发宏定义 */
#define LCM_WRITE_CMD() LCMIFCR = ((LCMIFCR & ~0x07) | 0x84)
#define LCM_WRITE_DAT() LCMIFCR = ((LCMIFCR & ~0x07) | 0x85)
#define LCM_READ_CMD() LCMIFCR = ((LCMIFCR & ~0x07) | 0x86)
#define LCM_READ_DAT() LCMIFCR = ((LCMIFCR & ~0x07) | 0x87)/* 接口类型和数据宽度类型 */
#define MODE_I8080 0 //I8080模式
#define MODE_M6800 1 //M6800模式#define BIT_WIDE_8 0 //8位数据宽度
#define BIT_WIDE_16 2 //16位数据宽度/* 初始化结构体 */
typedef struct
{u8 LCM_Enable; //LCM接口使能 ENABLE,DISABLEu8 LCM_Mode; //LCM接口模式 MODE_I8080,MODE_M6800u8 LCM_Bit_Wide; //LCM数据宽度 BIT_WIDE_8,BIT_WIDE_16u8 LCM_Setup_Time; //LCM通信数据建立时间 0~7u8 LCM_Hold_Time; //LCM通信数据保持时间 0~3
} LCM_InitTypeDef;/* 接口函数 */
void LCM_Inilize(LCM_InitTypeDef *LCM);
LCD_RS、LCD_RD、LCD_WR 的引脚定义受到 LCM 寄存器的控制,具体设置可参考官方手册,如下图所示。本文使用 LCMIFCPS[1:0] = 00 时的功能引脚。
LCD_CS、LCD_RESET、LCD_BK 可任意定义,主要用于 LCD 的片选、复位和背光控制。
触发数据和指令的宏定义用于触发 LCM 模块将数据寄存器中的数据通过端口发送给 LCD,仅用于触发,数据需要在触发前写入数据寄存器。
接口类型和数据宽度类型将在 LCM 的初始化结构体里被使用,本次使用的 LCD 的类型是 I8080 接口 和 16位数据宽度。16位 LCM 接口数据脚选择参考官方手册,如下图所示。本文使用的是 LCMIFDPS[1:0] = 11 时的引脚。
LCM 结构体公有 5个成员函数,STC 在注释中给出了可设置的范围。结构体配置完成后通过函数 LCM_Inilize() 对 LCM 进行初始化。
功能引脚切换使用 STC32G_Switch() 中提供的宏定义:
#define LCM_CTRL_SW(Pin) LCMIFCFG2 = (LCMIFCFG2 & ~0x60) | (Pin << 5)
#define LCM_DATA_SW(Pin) LCMIFCFG = (LCMIFCFG & ~0x0C) | (Pin << 2)
三、简要的初始化流程
- 野火程序的简单分析。下图为笔者对野火液晶屏参考示例的程序运行简单摘要(如有错误或理解有误可以在评论区指出)。
- STM32 与 STC32G 的异同点比较
STM32 STC32G GPIO 都是对需要的 GPIO 进行初始化 驱动 LCD 的外设
采用 FSMC 控制 采用 LCM 控制 液晶屏配置 对于液晶屏的配置函数,只要基本的读写函数能够实现,基本不需要做修改 读写函数 基于 FSMC 的读写触发 基于 LCM 的读写触发 LCD 控制引脚 通过 FSMC ,自动控制的对应的引脚实现控制 需要用户来手动控制引脚的高低电平
四、代码的移植和修改
提前准备好需要的库函数和驱动程序,本文的中断函数将使用 STC32G 库函数自带 ISR 函数。具体使用到的有关文件请参考下表:
STC32G 库函数 | STM32 参考程序 |
STC32G_Delay STC32G_GPIO STC32G_LCM STC32G_NVIC STC32G_SWITCH STC32G_UART | bsp_ili9341_lcd font |
创建 KEIL C251 项目,先将 STC32G 的文件导入到项目中,并实现通过串口发送 Hello World 的程序,以此确保 STC32G 的库函数移植无误。
接下来添加 STM32 的液晶屏驱动程序和字模 到工程项目中。在 bsp_ili9341_lcd.h 中删除有关 FSMC 的所有定义,并针对 bsp_ili9341_lcd.c 的需要,补充基本的类型定义,并包含下表所需要的头文件:
头文件 | 作用 |
STC32G_GPIO | 对引脚进行初始化 |
STC32G_LCM | 提供对 LCM 的控制 |
STC32G_NVIC | 中断嵌套配置,用于配置外设中断 |
STC32G_Delay | 延时函数 |
STC32G_Switch | 提供功能引脚切换 |
接下来对需要修改的函数进行重定义。
需要重定义的函数 | 函数功能 |
ILI9341_Write_Cmd() | 向 ILI9341 写指令 |
ILI9341_Write_Data() | 向 ILI9341 写数据 |
ILI9341_Read_Data() | 向 ILI9341 读数据 |
ILI9341_Delay() | 延时函数 |
ILI9341_GPIO_Config() | GPIO 初始化 |
ILI9341_FSMC_Config() | LCM 初始化 |
ILI9341_Init() | 初始化外设并对液晶屏进行基本配置 |
ILI9341_BackLed_Control() | 背光控制 |
ILI9341_Rst() | 复位控制 |
首先对 ILI9341_Write_Cmd()、ILI9341_Write_Data()、ILI9341_Read_Data() 和 ILI9341_Delay() 进行重定义,如下:
/*** @brief 向ILI9341写入命令* @param usCmd :要写入的命令(表寄存器地址)* @retval 无*/
void ILI9341_Write_Cmd(uint16_t usCmd)
{//* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_CMD ) = usCmd;LCD_RS = 0;LCMIFDATL = usCmd & 0x00ff;LCMIFDATH = (usCmd >> 8) & 0x00ff;LCM_WRITE_CMD();
}/*** @brief 向ILI9341写入数据* @param usData :要写入的数据* @retval 无*/
void ILI9341_Write_Data(uint16_t usData)
{//* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) = usData;LCD_RS = 0;LCMIFDATL = usData & 0x00ff;LCMIFDATH = (usData >> 8) & 0x00ff;LCM_WRITE_DAT();
}/*** @brief 从ILI9341读取数据* @param 无* @retval 读取到的数据*/
uint16_t ILI9341_Read_Data(void)
{// return ( * ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) );LCD_RS = 1;LCM_READ_DAT();return (LCMIFDATH << 8) | LCMIFDATL;
}/*** @brief 用于 ILI9341 简单延时函数* @param nCount :延时计数值* @retval 无*/
static void ILI9341_Delay(uint16_t ms)
{delay_ms(ms);
}
重定义 GPIO 初始化函数,具体的初始化端口依据实际用到的端口来决定。
/*** @brief 初始化ILI9341的IO引脚* @param 无* @retval 无*/
static void ILI9341_GPIO_Config(void)
{GPIO_InitTypeDef gpio;// 选用 P7 P6 作为数据/指令发送信号线,16线制gpio.Mode = GPIO_PullUp;gpio.Pin = GPIO_Pin_All;GPIO_Inilize(GPIO_P6, &gpio);GPIO_Inilize(GPIO_P7, &gpio);// LCD 控制信号线配置gpio.Pin = GPIO_Pin_All;GPIO_Inilize(GPIO_P4, &gpio);
}
重定义 LCM 初始化函数,此处函数名没有修改。
/*** @brief LCD FSMC 模式配置* @param 无* @retval 无*/
static void ILI9341_FSMC_Config(void)
{LCM_InitTypeDef lcm;lcm.LCM_Bit_Wide = BIT_WIDE_16;lcm.LCM_Enable = ENABLE;lcm.LCM_Hold_Time = 1;lcm.LCM_Setup_Time = 1;lcm.LCM_Mode = MODE_I8080;LCM_Inilize(&lcm);NVIC_LCM_Init(ENABLE, Priority_1);LCM_CTRL_SW(LCM_CTRL_P45_P44_P42);LCM_DATA_SW(LCM_D16_P6_P7);
}
重定义 ILI9341 复位函数。
/*** @brief ILI9341G 软件复位* @param 无* @retval 无*/
void ILI9341_Rst(void)
{// digitalL( GPIOE,GPIO_PIN_1); //低电平复位LCD_RESET = 0;ILI9341_Delay(50);// digitalH( GPIOE,GPIO_PIN_1);LCD_RESET = 1;ILI9341_Delay(50);
}
重定义 ILI9341 背光控制函数。
/*** @brief ILI9341G背光LED控制* @param enumState :决定是否使能背光LED* 该参数为以下值之一:* @arg ENABLE :使能背光LED* @arg DISABLE :禁用背光LED* @retval 无*/
void ILI9341_BackLed_Control(FunctionalState enumState)
{if (enumState){// digitalL( GPIOD, GPIO_PIN_12);LCD_BK = 0; // 用的 PNP 型三极管做开关,因此低电平点亮}else{// digitalH( GPIOD, GPIO_PIN_12);LCD_BK = 1;}
}
在 ILI9341 初始化函数中补上 LCD 片选控制。
/*** @brief ILI9341初始化函数,如果要用到lcd,一定要调用这个函数* @param 无* @retval 无*/
void ILI9341_Init(void)
{ILI9341_GPIO_Config();ILI9341_FSMC_Config();LCD_CS = 0;ILI9341_BackLed_Control(ENABLE); // 点亮LCD背光灯ILI9341_Rst();ILI9341_REG_Config();// 设置默认扫描方向,其中 6 模式为大部分液晶例程的默认显示方向ILI9341_GramScan(LCD_SCAN_MODE);ILI9341_Clear(0, 0, ILI9341_LESS_PIXEL, ILI9341_MORE_PIXEL);
}
将 bsp_ili9341_lcd.h 包含到 main.c 中。编写测试程序。其中 gpio_init() 和 uart_init() 是针对串口输出做的初始化。
void main(void)
{__STC32G_DRIVER_INIT();gpio_init();uart_init();ILI9341_Init();LCD_SetTextColor(BLACK);ILI9341_DrawRectangle(0, 0, ILI9341_LESS_PIXEL, ILI9341_MORE_PIXEL, 1);LCD_SetFont(&Font8x16);LCD_SetColors(YELLOW, BLACK);EA = 1;while(1){printf("Hello world\r\n");LCD_SetColors(YELLOW, BLACK);ILI9341_DispStringLine_EN(LINE(0), "Hello LCM I8080!");delay_ms(1000);// 画线LCD_ClearLine(LINE(4)); /* 清除单行文字 */LCD_SetColors(YELLOW, BLACK);ILI9341_DispStringLine_EN(LINE(4), "Draw line:");LCD_SetColors(RED, BLACK);ILI9341_DrawLine(50, 170, 210, 230);ILI9341_DrawLine(50, 200, 210, 240);LCD_SetColors(GREEN, BLACK);ILI9341_DrawLine(100, 170, 200, 230);ILI9341_DrawLine(200, 200, 220, 240);LCD_SetColors(BLUE, BLACK);ILI9341_DrawLine(110, 170, 110, 230);ILI9341_DrawLine(130, 200, 220, 240);delay_ms(2500);ILI9341_Clear(0, 16 * 8, LCD_X_LENGTH, LCD_Y_LENGTH - 16 * 8); /* 清屏,显示全黑 */// 画矩形LCD_ClearLine(LINE(4)); /* 清除单行文字 */LCD_SetColors(GREEN, BLACK);ILI9341_DispStringLine_EN(LINE(4), "Draw Rectangle:");LCD_SetColors(RED, BLACK);ILI9341_DrawRectangle(50, 200, 100, 30, 1);LCD_SetColors(GREEN, BLACK);ILI9341_DrawRectangle(160, 200, 20, 40, 0);LCD_SetColors(BLUE, BLACK);ILI9341_DrawRectangle(170, 200, 50, 20, 1);delay_ms(2500);ILI9341_Clear(0, 16 * 8, LCD_X_LENGTH, LCD_Y_LENGTH - 16 * 8); /* 清屏,显示全黑 */// 画圆LCD_ClearLine(LINE(4)); /* 清除单行文字 */LCD_SetColors(GREEN, BLACK);ILI9341_DispStringLine_EN(LINE(4), "Draw Circle:");LCD_SetColors(RED, BLACK);ILI9341_DrawCircle(100, 200, 20, 0);LCD_SetColors(GREEN, BLACK);ILI9341_DrawCircle(100, 200, 10, 1);LCD_SetColors(BLUE, BLACK);ILI9341_DrawCircle(140, 200, 20, 0);delay_ms(2500);ILI9341_Clear(0, 0, LCD_X_LENGTH, LCD_Y_LENGTH); /* 清屏,显示全黑 */}
}
编译项目,若无报错则说明文件移植没有语法错误。
五、引脚接线
已经接线对照表如下。
实物接线图如下。
六、运行结果
烧录后,液晶屏正常点亮并显示测试程序:首先显示字符串 “Hello LCM I8080”,然后显示字符串“Draw Line”,绘制测试直线,之后显示字符串“Draw Rectangle”,绘制测试矩形,最后显示字符串“Draw Circle”,绘制测试圆形。运行结果如下图所示。
七、总结
1. BUG 汇总
问题 | 可能的原因 | 解决方法 |
程序卡死 | 未开启外设或中断 | 1. 使能外设 2. 开启中断,STC32G 的库函数开启外设中断的方式是使用 NVIC 提供的接口 |
未定义正确的中断函数或未清空标志位 | 本文使用 STC32G 库函数提供的默认中断函数,可用于参考 | |
使用 printf 时未正确指定串口 | 确定 printf 使用的串口是连接到电脑端的串口 | |
未正确初始化相关外设 | 确认 GPIO、LCM、UART 等外设初始化正确 | |
串口有输出,屏幕没有点亮 | LCB_BK 接线不正确或电平值不正确 | 检查 LCB_BK 电平值是否正确,或检查引脚是否接线正确 |
可能是因为片选引脚 LCB_CS 电平不对 | LCB_CS 处于低电平时 LCD 被选中 | |
GPIO 未正确初始化 | 检查 GPIO 的引脚配置是否正确 | |
屏幕点亮,没有显示字符串和图像 | 接线错误或未正确指定功能引脚 | 检查接线是否正常,检查是否进行了正确的功能引脚切换 |
驱动程序未正确重定义 | 检查需要重定义的函数是否都正确重定义 | |
数据建立时间和数据保存时间数值不正确 | 初始化 LCM 时,依据实际情况赋值进行测试,笔者的两个值都为 1 | |
清屏函数仅清屏部分区域 | ILI9341_FillColor() 的传参不正确 | ILI9341_FillColor() 第一个参数是 uint32_t 类型,调用该函数时,需要将参数进行强制类型转换到 uint32_t,建议将 bsp_ili9341_lcd.c 中所有用到 ILI9341_FillColor() 的地方都手动补上强制类型转换的语句。 |
屏幕输出字符串像素点不完整 | 数据建立时间和数据保存时间数值不正确 | 初始化 LCM 时,依据实际情况赋值进行测试,笔者的两个值都为 1 |
填充区块时出现白屏闪烁问题 | 数据建立时间和数据保存时间数值不正确 | 初始化 LCM 时,依据实际情况赋值进行测试,笔者的两个值都为 1 |
调用了 ILI9341_FillColor() | 该问题目前暂未解决,除了黑色填充外,其他颜色进行填充时都会导致白色屏闪的情况,推测可能和 STC32G 的执行速度有关系。 |
2. LCM 使用感受
STC32G 提供的 LCM 对驱动 TFTLCD 提供了便利的帮助,官方库函数的提供让使用这一外设变得更加简单。STC8A8K64D4 也具有 LCM 功能,通用官方也提供了相应的库函数,而 STC8A8K64D4 的时钟频率能调节到 45MHz,比 STC32G 在 ISP 上能调节的范围更广,也许用起来会比 STC32G 更好吧(可能)。
可惜适配 51 的 GUI 太少了,虽然 STC 完成了 FreeRTOS 的移植(据说可以尝试 uC/GUI,没试过不清楚)。