F103ZET6使用FSMC和HAL点亮ILI9341

news/2024/10/22 11:06:33/

前言

将标准库下的ILI9341驱动移植到使用CubeMX生成的HAL库环境,并成功运行。

一、STM32CubeMX生成框架

(一)配置RCC、SYS和时钟树

参见常规配置。

(二)配置FSMC

1、原理图引脚定义

 LCD8080接口使用的引脚主要分为3类:

(一)对应关系由STM32内部硬件原理定义,参见STM32F103xE 数据手册 表5 引脚定义PD14   ------> FSMC_D0PD15   ------> FSMC_D1PD0    ------> FSMC_D2PD1    ------> FSMC_D3PE7    ------> FSMC_D4PE8    ------> FSMC_D5PE9    ------> FSMC_D6PE10   ------> FSMC_D7PE11   ------> FSMC_D8PE12   ------> FSMC_D9PE13   ------> FSMC_D10PE14   ------> FSMC_D11PE15   ------> FSMC_D12PD8    ------> FSMC_D13PD9    ------> FSMC_D14PD10   ------> 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、控制液晶屏时使用的地址(重要)

计算地址的过程如下:
(1) 由于 本工程的硬件设计中使用的是 FSMC_NE4 作为 8080_CS 片选信号【见原理图】
所以首先可以确认地址范围,当访 问 0X6C00 0000 ~ 0X6FFF FFFF 地址时, FSMC 均会对外产生片选有效的访问时序;

 

(2) 本工程中使用 FSMC_A23 地址线作为命令 / 数据选择线 RS 信号【见原理图】
所以在以上地址范围内, 再选择出使得 FSMC_A23 输出高电平的地址,即可控制表示数据,选择出使得 FSMC_A23 输出低电平的地址,即可控制表示命令
要使 FSMC_A23 地址线为高电平,实质是 输出地址信号的第 23 位为 1 即可 ,使用 0X6C00
0000~0X6FFF FFFF 内的任意地址,作如下运算:
设置地址的第 23 位为 1 0X6C00 0000 |= (1«23) = 0x6C80 0000
要使 FSMC_A23 地址线为低电平,实质是 输出地址信号的第 23 位为 0 即可 ,使用 0X6C00
0000~0X6FFF FFFF 内的任意地址,作如下运算:
设置地址的第 23 位为 0 0X6C00 0000 &= ~ (1«23) = 0x6C00 0000
(3) 但是,以上方法计算的地址还不完全正确,根据《 STM32 参考手册》对 FSMC 访问 NOR
FLASH 的说明,见下图 STM32 内部访问地址时使用的是内部 HADDR 总线,它是
要转换到外部存储器的内部 AHB 地址线,它是字节地址 (8 ) ,而存储器访问不都是按字
节访问,因此接到存储器的地址线依存储器的数据宽度有所不同。

注:上图来源于《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
根据最终的计算结果,总结如下:
STM32 访问内部的 0x6D00 0000 地址时, FSMC 自动输出 时序,且使得与液晶屏的数据/ 命令选择线 RS( D/CX) 相连的 FSMC_A23 输出高电平,使得液 晶屏会把传输过程理解为数据传输;类似地,当 STM32 访问内部的 0X6C00 0000 地址时, FSMC 自动输出时序,且使得与液晶屏的数据/ 命令选择线 RS( D/CX) 相连的 FSMC_A23 输出低电平, 使得液晶屏会把传输过程理解为命令传输,因此定义如下:
	#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 ConfigurationPE2   ------> FSMC_A23PE7   ------> FSMC_D4PE8   ------> FSMC_D5PE9   ------> FSMC_D6PE10   ------> FSMC_D7PE11   ------> FSMC_D8PE12   ------> FSMC_D9PE13   ------> FSMC_D10PE14   ------> FSMC_D11PE15   ------> FSMC_D12PD8   ------> FSMC_D13PD9   ------> FSMC_D14PD10   ------> FSMC_D15PD14   ------> FSMC_D0PD15   ------> FSMC_D1PD0   ------> FSMC_D2PD1   ------> FSMC_D3PD4   ------> FSMC_NOEPD5   ------> FSMC_NWEPG12   ------> 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_SRAMhsram1.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;//0x08Timing.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( );}elseError_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函数等可参考相关资料,在此不再赘述。


http://www.ppmy.cn/news/75215.html

相关文章

java.util.concurrent.atomic

java.util.concurrent.atomic是Java中的一个包&#xff0c;提供了一组原子操作类&#xff0c;用于在多线程环境下进行原子性操作。这些类可用于实现线程安全的、无锁的操作&#xff0c;保证操作的原子性&#xff0c;避免数据竞争和线程冲突。以下是java.util.concurrent.atomic…

bitset优化

bitset优化背包 d p i , k ∣ d p i − 1. k − j ∗ j dp_{i,k}|dp_{i-1.k-j*j} dpi,k​∣dpi−1.k−j∗j​ 其中 i i i代表到第 i i i个区间&#xff0c;选完数得到的平方和为 k k k 这种情况是否存在 j j j表示第 i i i次选数为 j j j 枚举 i , j , k i,j,k i,j,k时间复…

使用Ceph对象存储的Amazon S3接口(基于nautilus版本)

使用Ceph对象存储的Amazon S3接口&#xff08;基于nautilus版本&#xff09; Ceph是一个分布式存储系统&#xff0c;提供了多种数据存储方式&#xff0c;包括对象存储。Amazon S3是一个流行的对象存储服务&#xff0c;Ceph提供了Amazon S3接口的兼容性&#xff0c;使得Ceph可以…

Chrome 的骑士盾,谷歌 Security Princess 访谈

童话故事里的公主都有一种需要被保护的感觉&#xff0c;就像马里奥大叔在这么多年来都要在库巴手上拯救出碧姬公主一样。不过在谷歌的这位 Security Princess 却手执盾牌&#xff0c;守护着大家的 Chrome 浏览器免受恶意程序攻击。小编这次就乘着世界网络安全日的机会&#xff…

使用AWS S3 为同一账户拥有的源桶和目标桶配置复制

复制是在相同或跨不同 AWS 区域 的桶自动、异步地复制对象。复制操作会将源桶中新创建的对象和对象更新复制到目标桶 配置复制时&#xff0c;需要向源桶添加复制规则。复制规则定义要复制的源桶对象和存储已复制对象的目标桶。您可以创建一条规则&#xff0c;以复制桶中的所有…

Ubuntu crontab定时任务

1. crontab 相关的命令&#xff1a; 安装&#xff1a;apt-get install cron 启动&#xff1a;service cron start 重启&#xff1a;service cron restart 停止&#xff1a;service cron stop 检查状态&#xff1a;service cron status 查询cron可用的命令&#xff1a;service …

Java基础-面向对象总结(3)

本篇文章主要讲解Java面向对象的知识点 面向对象的三大特性类的扩展(抽象类,接口,内部类,枚举) 目录 面向对象和面向过程的区别? 面向对象的五大基本原则 面向对象三大特性 继承 怎么理解继承 ? 继承和聚合的区别&#xff1f; 封装 多态 什么是多态 什么是运行时多…

David Silver Lecture 9:Exploration and Exploitation

1 Introduction 1.1 Outline 1.1.1 Exploration vs. Exploitation Dilemma 1.1.2 examples 1.1.3 principles Naive Exploration 在前面的章节主要使用的是naive exploration的方法Optimistic Initialisation 这种方法的思想是&#xff0c;我们对每个动作的奖励给出一个乐观的…