前言
将标准库下的ILI9341驱动移植到使用CubeMX生成的HAL库环境,并成功运行。
一、STM32CubeMX生成框架
(一)配置RCC、SYS和时钟树
参见常规配置。
(二)配置FSMC
1、原理图引脚定义
LCD8080接口使用的引脚主要分为3类:
(一)对应关系由STM32内部硬件原理定义,参见STM32F103xE 数据手册 表5 引脚定义
PD14 ------> FSMC_D0
PD15 ------> FSMC_D1
PD0 ------> FSMC_D2
PD1 ------> FSMC_D3
PE7 ------> FSMC_D4
PE8 ------> FSMC_D5
PE9 ------> FSMC_D6
PE10 ------> FSMC_D7
PE11 ------> FSMC_D8
PE12 ------> FSMC_D9
PE13 ------> FSMC_D10
PE14 ------> FSMC_D11
PE15 ------> FSMC_D12
PD8 ------> FSMC_D13
PD9 ------> FSMC_D14
PD10 ------> FSMC_D15
(二)
PE2 ------> FSMC_A23 地址输入(重要DC)(不同板子可能不同)
PD4 ------> FSMC_NOE RD读使能
PD5 ------> FSMC_NWE WR写使能
PG12 ------> FSMC_NE4 片选(重要)(不同板子可能不同)
(三)
PG11 ------> LCD_RST 复位
PG6 ------> LCD_BK 背光
1)16个数据引脚;由《STM32F103xCDE_数据手册》中的引脚定义决定;
2)4个FSMC功能引脚:片选(CS/NE)、数据/命令选择(RS或D/CX)、读使能(RD/NOW),写使能(WR/NWE)
3)2个LCD功能引脚:背光控制引脚(LCD_BL),复位引脚(RESET)
根据原理图选择NE4(PG12),后续FSMC_NORSRAM_BANK4
#define ILI9341_CMD_ADDR (__IO uint16_t*)(0x6c000000)
#define ILI9341_DATA_ADDR (__IO uint16_t*)(0x6d000000)
根据原理图选择地址线选择A23(PE2)。
2、CubeMX配置
注:本处可能只用到address setup time 和 data setup time ,一般采用十六进制设置为0x01和0x04或0x02和0x05。
3、控制液晶屏时使用的地址(重要)
注:上图来源于《STM32 参考手册》中对 HADDR 与 FSMC 地址线的说明
在本工程中使用的是 16 位的数据访问方式,所以 HADDR 与 FSMC_A 的地址线连接关系会左移一位,如 HADDR1 与 FSMC_A0 对应、HADDR2 与 FSMC_A1 对应。因 此,当 FSMC_A0 地址线为 1 时,实际上内部地址的第 1 位为 1 , FSMC_A1 地址线为1 时,实际上内部地址的第 2 位为 1 。同样地,当希望 FSMC_A23 地址输出高电平或低电平时,需要重新调整计算公式要使 FSMC_A23 地址线为高电平,实质是访问内部 HADDR 地址的第 (23+1) 位为 1 即可,使用 0X6C00 0000~0X6FFF FFFF 内的任意地址,作如下运算:使 FSMC_A23 地址线为高电平:0X6C00 0000 |= (1«(23+1)) = 0x6D00 0000要使 FSMC_A23 地址线为低电平,实质是访问内部 HADDR 地址的第 (23+1) 位为 0 即可,使用 0X6C00 0000~0X6FFF FFFF 内的任意地址,作如下运算:使 FSMC_A23 地址线为低电平:0X6C00 0000 &= ~ (1«(23+1)) = 0x6C00 0000
#define ILI9341_CMD_ADDR (__IO uint16_t*)(0x6c000000)
#define ILI9341_DATA_ADDR (__IO uint16_t*)(0x6d000000)
(三)配置相关GPIO和UART
PG11为LCD复位,PG6为LCD背光控制,因此必须配置;
PB0、1、5为 板子的LED,不同板子可能不同,根据实际配置。
使能UART1,即PA9(TX)和PA10(RX),用于串口打印调试。
最后在Project&Configuration中生成代码。
二、修改相关代码
(一)FSMC部分
其中fsmc.c文件中的执行顺序:
HAL_FSMC_MspInit==>HAL_SRAM_MspInit==>MX_FSMC_Init ==>HAL_SRAM_Init
1)HAL_FSMC_MspInit()
完成FSMC时钟使能,FSMC直接使用到的GPIO配置和初始化
static void HAL_FSMC_MspInit(void){
/* USER CODE BEGIN FSMC_MspInit 0 */
/* USER CODE END FSMC_MspInit 0 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (FSMC_Initialized) {
return;
}
FSMC_Initialized = 1;
/* Peripheral clock enable */
__HAL_RCC_FSMC_CLK_ENABLE();
/** FSMC GPIO Configuration
PE2 ------> FSMC_A23
PE7 ------> FSMC_D4
PE8 ------> FSMC_D5
PE9 ------> FSMC_D6
PE10 ------> FSMC_D7
PE11 ------> FSMC_D8
PE12 ------> FSMC_D9
PE13 ------> FSMC_D10
PE14 ------> FSMC_D11
PE15 ------> FSMC_D12
PD8 ------> FSMC_D13
PD9 ------> FSMC_D14
PD10 ------> FSMC_D15
PD14 ------> FSMC_D0
PD15 ------> FSMC_D1
PD0 ------> FSMC_D2
PD1 ------> FSMC_D3
PD4 ------> FSMC_NOE
PD5 ------> FSMC_NWE
PG12 ------> FSMC_NE4
*/
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_2|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_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_14
|GPIO_PIN_15|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4
|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* GPIO_InitStruct */
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
/* USER CODE BEGIN FSMC_MspInit 1 */
printf("\n================HAL_FSMC_MspInit is OK=================\n");
/* USER CODE END FSMC_MspInit 1 */
}
2)MX_FSMC_Init()
完成FSMC功能配置和时序结构体配置
void MX_FSMC_Init(void)
{
/* USER CODE BEGIN FSMC_Init 0 */
/* USER CODE END FSMC_Init 0 */
FSMC_NORSRAM_TimingTypeDef Timing = {0};
/* USER CODE BEGIN FSMC_Init 1 */
/* USER CODE END FSMC_Init 1 */
/** Perform the SRAM1 memory initialization sequence
*/
hsram1.Instance = FSMC_NORSRAM_DEVICE;
hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram1.Init */
hsram1.Init.NSBank = FSMC_NORSRAM_BANK4;
hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;//FSMC_MEMORY_TYPE_NOR //FSMC_MEMORY_TYPE_SRAM
hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
//hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
/* Timing */
Timing.AddressSetupTime = 0x01;
Timing.AddressHoldTime = 0x00;
Timing.DataSetupTime = 0x04;//0x08
Timing.BusTurnAroundDuration = 0x0;
Timing.CLKDivision = 0x0;
Timing.DataLatency = 0x0;
Timing.AccessMode = FSMC_ACCESS_MODE_A;//FSMC_ACCESS_MODE_B // FSMC_ACCESS_MODE_A
/* ExtTiming */
if (HAL_SRAM_Init(&hsram1, &Timing, &Timing) == HAL_OK)
{
printf("\n================MX_FSMC_Init is OK=================\n");
//Error_Handler( );
}
else
Error_Handler( );
/** Disconnect NADV
*/
__HAL_AFIO_FSMCNADV_DISCONNECTED();
/* USER CODE BEGIN FSMC_Init 2 */
/* USER CODE END FSMC_Init 2 */
}
(二)UART部分
串口重定向
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口DEBUG_USART */
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return (ch);
}
(三)ILI9341初始化
1、CMD/ADDR定义和读写函数封装
注:CMD和DATA地址计算见上。
#define ILI9341_CMD_ADDR (__IO uint16_t*)(0x6c000000)
#define ILI9341_DATA_ADDR (__IO uint16_t*)(0x6d000000)
__inline void ILI9341_Write_Cmd ( uint16_t usCmd )
{
*ILI9341_CMD_ADDR = usCmd;
// * ( __IO uint16_t * ) ( ILI9341_CMD_ADDR ) = usCmd;
}
/**
* @brief 向ILI9341写入数据
* @param usData :要写入的数据
* @retval 无
*/
__inline void ILI9341_Write_Data ( uint16_t usData )
{
* ILI9341_DATA_ADDR = usData;
// * ( __IO uint16_t * ) ( ILI9341_DATA_ADDR ) = usData;
}
/**
* @brief 从ILI9341读取数据
* @param 无
* @retval 读取到的数据
*/
uint16_t ILI9341_Read_Data ( void )
{
return ( *ILI9341_DATA_ADDR );
// return ( * ( __IO uint16_t * ) ( ILI9341_DATA_ADDR ) );
}
2、ILI9341初始化
void ILI9341_Init(void)
{
//ILI9341_GPIO_Config();
//ILI9341_FSMC_Config();
ILI9341_BackLed_Control(ENABLE);
ILI9341_Rst();
ILI9341_REG_Config();
ILI9341_GramScan(LCD_SCAN_MODE);
printf("\n================ILI9341_Init is OK=================\n");
}
三、GUI绘制
(一)编写液晶屏的绘制像素点函数
/**
* @brief 在 ILI9341 显示器上以某一颜色填充像素点
* @param ulAmout_Point :要填充颜色的像素点的总数目
* @param usColor :颜色
* @retval 无
*/
static __inline void ILI9341_FillColor ( uint32_t ulAmout_Point, uint16_t usColor )
{
uint32_t i = 0;
/* memory write */
ILI9341_Write_Cmd ( CMD_SetPixel );
for ( i = 0; i < ulAmout_Point; i ++ )
ILI9341_Write_Data ( usColor );
}
static uint16_t CurrentTextColor = BLACK;//前景色
static uint16_t CurrentBackColor = WHITE;//背景色
/**
* @brief 设定 ILI9341 的光标坐标
* @param usX :在特定扫描方向下光标的 X 坐标
* @param usY :在特定扫描方向下光标的 Y 坐标
* @retval 无
*/
static void ILI9341_SetCursor ( uint16_t usX, uint16_t usY )
{
ILI9341_OpenWindow ( usX, usY, 1, 1 );
}
/**
* @brief 对 ILI9341 显示器的某一点以某种颜色进行填充
* @param usX :在特定扫描方向下该点的 X 坐标
* @param usY :在特定扫描方向下该点的 Y 坐标
* @note 可使用 LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors 函数设置颜色
* @retval 无
*/
void ILI9341_SetPointPixel ( uint16_t usX, uint16_t usY )
{
if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ) ) {
ILI9341_SetCursor ( usX, usY );
ILI9341_FillColor ( 1, CurrentTextColor );
}
}
(二)利用描点函数制作各种不同的液晶显示应用
/**
* @brief 在 ILI9341 显示器上画一个矩形
* @param usX_Start :在特定扫描方向下矩形的起始点 X 坐标
* @param usY_Start :在特定扫描方向下矩形的起始点 Y 坐标
* @param usWidth:矩形的宽度(单位:像素)
* @param usHeight:矩形的高度(单位:像素)
* @param ucFilled :选择是否填充该矩形
* 该参数为以下值之一:
* @arg 0 : 空心矩形
* @arg 1 : 实心矩形
* @note 可使用 LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors 函数设置颜色
* @retval 无
*/
void ILI9341_DrawRectangle ( uint16_t usX_Start, uint16_t usY_Start,
uint16_t usWidth, uint16_t usHeight, uint8_t ucFilled )
{
if ( ucFilled )
{
ILI9341_OpenWindow ( usX_Start, usY_Start, usWidth, usHeight );
ILI9341_FillColor ( usWidth * usHeight ,CurrentTextColor);
}
else
{
ILI9341_DrawLine ( usX_Start, usY_Start,
usX_Start + usWidth - 1, usY_Start );
ILI9341_DrawLine ( usX_Start, usY_Start + usHeight - 1,
usX_Start + usWidth - 1, usY_Start + usHeight - 1 );
ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start,
usY_Start + usHeight - 1 );
ILI9341_DrawLine ( usX_Start + usWidth - 1, usY_Start,
usX_Start + usWidth - 1, usY_Start + usHeight - 1 );
}
}
后续如设置扫描方向,LCD初始化,main函数等可参考相关资料,在此不再赘述。