STM32CubeMX学习笔记(38)——FSMC接口使用(TFT-LCD屏显示)

一、TFT-LCD简介

TFT-LCD(Thin Film Transistor-Liquid Crystal Display) 即薄膜晶体管液晶显示器。TFT-LCD 与无源 TN-LCD、 STN-LCD 的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。 TFT-LCD 也被叫做真彩液晶显示器。

虽然TFT-LCD被统称为LCD,不过它是种主动式矩阵LCD,被应用在电视、平面显示器及投影机上。

1.1 液晶控制原理


这个完整的显示屏由液晶显示面板、电容触摸面板以及 PCB 底板构成。图中的触摸面板带有触摸控制芯片,该芯片处理触摸信号并通过引出的信号线与外部器件通讯,触摸面板中间是透明的,它贴在液晶面板上面,一起构成屏幕的主体,触摸面板与液晶面板引出的排线连接到 PCB 底板上,根据实际需要,PCB 底板上可能会带有“液晶控制器芯片”,图中右侧的液晶屏 PCB 上带有 RA8875 液晶控制器。因为控制液晶面板需要比较多的资源,所以大部分低级微控制器都不能直接控制液晶面板,需要额外配套一个专用液晶控制器来处理显示过程,外部微控制器只要把它希望显示的数据直接交给液晶控制器即可。而不带液晶控制器的 PCB 底板,只有小部分的电源管理电路,液晶面板的信号线与外部微控制器相连,直接控制。STM32F429 系列的芯片不需要额外的液晶控制器,也就是说它把专用液晶控制器的功能集成到 STM32F429 芯片内部了,可以理解为电脑的 CPU 集成显卡,它节约了额外的控制器成本。而 STM32F1 系列的芯片由于没有集成液晶控制器到芯片内部,所以它只能驱动自带控制器的屏幕,可以理解为电脑的外置显卡。

1.2 ILI9341液晶控制器简介

本液晶屏内部包含有一个液晶控制芯片 ILI9341,它的内部结构非常复杂,该芯片最主核心部分是位于中间的 GRAM(Graphics RAM),它就是显存。GRAM 中每个存储单元都对应着液晶面板的一个像素点。它右侧的各种模块共同作用把 GRAM 存储单元的数据转化成液晶面板的控制信号,使像素点呈现特定的颜色,而像素点组合起来则成为一幅完整的图像。框图的左上角为 ILI9341 的主要控制信号线和配置引脚,根据其不同状态设置可以使芯片工作在不同的模式,如每个像素点的位数是 6、16 还是 18 位;可配置使用 SPI 接口、8080 接口还是 RGB 接口与 MCU 进行通讯。MCU 通过 SPI、8080 接口或 RGB 接口与 ILI9341 进行通讯,从而访问它的控制寄存器 (CR)、地址计数器 (AC)、及 GRAM。 在 GRAM 的左侧还有一个 LED 控制器 (LED Controller)。LCD 为非发光性的显示装置,它需要借助背光源才能达到显示功能,LED 控制器就是用来控制液晶屏中的 LED 背光源。

二、FSMC简介

FSMC(Flexible Static Memory Controller),译为灵活的静态存储控制器。STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器,它可以用于驱动包括 SRAM、NOR FLASH 以及 NAND FLSAH 类型的存储器,不能驱动如 SDRAM 这种动态的存储器而在 STM32F429 系列的控制器中,它具有 FMC 外设,支持控制 SDRAM 存储器。

由于 FSMC 外设可以用于控制扩展的外部存储器,而 MCU 对液晶屏的操作实际上就是把显示数据写入到显存中,与控制存储器非常类似,且 8080 接口的通讯时序完全可以使用 FSMC 外设产生,因而非常适合使用 FSMC 控制液晶屏。


三、引脚确定

四、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

五、FSMC

5.1 参数配置


Connectivity 中选择 FSMC 设置,并在 NOR Flash/PSRAM/SRAM/ROM/LCD 1 中选择 NE1 Chip Select 片选选择原理图中的片选引脚NE1【选择Bank1的第一区,是根据原理图的映射管脚进行选择的,这里选择不同区对应的引脚是不同的】

FSMC_NE 是用于控制存储器芯片的片选控制信号线,STM32 具有 FSMC_NE1/2/3/4 号引脚,不同的引脚对应 STM32 内部不同的地址区域。例如,当 STM32 访问 0x68000000-0x6BFFFFFF 地址空间时,FSMC_NE3 引脚会自动设置为低电平,由于它一般连接到外部存储器的片选引脚且低电平有效,所以外部存储器的片选被使能,而访问 0x60000000-
0x63FFFFFF 地址时,FSMC_NE1 会输出低电平。当使用不同的 FSMC_NE 引脚连接外部存储器时,STM32 访问外部存储的地址不一样,从而达到控制多个外部存储器芯片的目的。

  • Memory type(设置要控制的存储器类型): 选择 LCD Interface LCD接口
  • LCD Register Select(RS引脚): 选择 A16 ,RS脚也就是命令/数据选择位,同样是根据原理图得知这里应该选择A16
  • Data(设置要控制的存储器的数据宽度): 选择 16 bits 很明显从原理图看出有16个数据引脚,这里选择16bits就好

NOR/PSRAM 1 进行具体参数配置。

NOR/PSRAM control:

  • Write operation(设置是否写使能): 选择 Enabled ,禁止写使能的话 FSMC 只能从存储器中读取数据,不能写入。
  • Extended mode(设置是否使用扩展模式): 选择 Enabled ,在非扩展模式下,对存储器读写的时序都只使用 FSMC_BCR 寄存器中的配;在扩展模式下,对存储器的读写时序可以分开配置,读时序使用 FSMC_BCR 寄存器,写时序使用 FSMC_BWTR 寄存器的配置。

5.1.1 FSMC读时序配置

这里引入一个基本概念:

HCLK周期:
按STM32F103的默认配置,HCLK的时钟频率为72MHz,即一个 THCLK 为 1/72us=0.0138us=13.8us。

NOR/PSRAM timing(FSMC读时序配置):

  • Address setup time in HCLK clock cycles(地址建立时间):0
  • Data setup time in HCLK clock cycles(数据建立时间):26

根据ILI9341时序配置FSMC读时序

NEx片选后,NOE要保持一段时间的高电平,这个时间就是ADDSET地址建立时间(通过寄存器FMC_BTRx可配置)
之后NOE变为低电平,读使能。低电平保持的时间由DATAST数据建立时间(通过寄存器FMC_BTRx可配置)决定。


  • tast:

    tast 表示地址建立时间,最小为0ns
    由时序图可以知道,FSMC在ADDSET周期之后,进入DATAST周期之后将会进行数据采样。

    所以我们设置(ADDSET)HCLK的时间要大于等于tast地址建立时间。
    (ADDSET)HCLK >= 0ns,(0)·13.8 = 0ns,所以ADDSET可以设置为0就可保证满足最小tast地址建立时间。

  • trdlfm:
    trdlfm 表示读取数据低电平的时间,最小为355ns
    ILI9341时序图没有给出ILI9341操作数据线传输被读取的数据时的相关信息,我们最好做到满足其读取数据低电平的最小时间。
    当然不做到也行,影响不大,只要在FSMC在DATAST的这个周期的数据采样中获取所有的要访问的数据就行。

    (DATAST)HCLK >355ns,(26) * 13.8 = 358.8ns>355ns,所以DATAST设置为26。

  • Bus turn around time in HCLK clock cycles(总线转换周期):0 ,仅适用于总线复用模式的NOR Flash操作,所以这里设0。
  • Access mode(存储器访问模式):A ,LCD控制器使用 Mode A ,该模式用来控制SRAM/PSRAM且OE会翻转。控制异步 NOR FLASH 时使用 B 模式。

5.1.2 FSMC写时序配置

NOR/PSRAM timing for write accesses(FSMC写时序配置):

  • Extended address setup time(地址建立时间):0
  • Extended data setup time(数据建立时间):1

根据ILI9341时序配置FSMC写时序

NEx片选后,NWE要保持一段时间的高电平,这个时间就是ADDSET地址建立时间(通过寄存器FMC_BTRx可配置)。
之后NWE变为低电平,写使能。低电平保持的时间由DATAST数据建立时间(通过寄存器FMC_BTRx可配置)决定。


  • tast:

    tast 表示地址建立时间,最小为0ns
    由时序图可以知道,FSMC在ADDSET周期之后,进入DATAST周期之后将会进行数据采样。

    所以我们设置(ADDSET)HCLK的时间要大于等于tast地址建立时间。
    (ADDSET)HCLK >= 0ns,(0)·13.8 = 0ns,所以ADDSET可以设置为0就可保证满足最小tast地址建立时间。

  • tdst、tdht:

    tdst:数据设置时间最小是10ns,在这个周期内WRX线处于低电平。
    tdht:数据保持时间,与 twrh写控制高电平的最小时间相同,是10ns,在这个周期内WRX线处于高电平。
    观察时序图,我们设置 tdst数据设置时间 为1HCLK(13.8>10)就能满足数据设置最小时间的要求,我们不需要考虑tdht数据保持时间(看上面模式B时序图,NWE变成高电平后,会持续1HCLK=13.8ns,默认满足tdht了)。

    故我们只需考虑数据建立周期 DATAST 要大于10ns就行。
    (DATAST)HCLK > 10ns,13.8>10 ,故DATAST 至少设置为1

  • Extended bus turn around time(总线转换周期):0
  • Extended access mode(存储器访问模式):A

六、设置背光和复位引脚

System Core 中选择 GPIO 设置。


在右边图中找到 LCD 背光和复位对应引脚,选择 GPIO_Output

GPIO output level 中选择 Low 输出低电平点亮,可以添加自定义标签(这样生成代码也会根据标签设置引脚的宏定义)。

七、UART串口打印

查看 STM32CubeMX学习笔记(6)——USART串口使用

八、生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

九、修改代码优化级别

STM32CubeMX生成的代码默认优化级别为Level 3,在此优化级别下编译无误下载到开发板后,LCD屏不能正常运行;将优化级别调整到Level 0编译下载后,LCD屏能够正常运行读取到ID。

KEIL5中C/C++优化等级介绍:
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。
-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。

十、添加LCD驱动文件

链接:https://pan.baidu.com/s/1W2PmfaDmv94yjvSeWvO0fw?pwd=o6cg 提取码:o6cg

加入野火的LCD驱动文件,屏蔽 ILI9341_Init() 中 GPIO 初始化 ILI9341_GPIO_Config() 和 FSMC配置 ILI9341_FSMC_Config(),因为 STM32CubeMX 工程在 main.c 里已经配置了。

十一、修改main.c

加入 ILI9341_Init() LCD屏驱动初始化后,进行 LCD_Test() 测试。

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "bsp_ili9341_lcd.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

SRAM_HandleTypeDef hsram1;

/* USER CODE BEGIN PV */
extern uint16_t lcdid;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_FSMC_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/*用于测试各种液晶的函敿*/
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");
    if(lcdid == LCDID_ILI9341)
    {
        ILI9341_DispStringLine_EN(LINE(2),"ILI9341 LCD driver");
    }
    else if(lcdid == LCDID_ST7789V)
    {
        ILI9341_DispStringLine_EN(LINE(2),"ST7789V 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);

	/*******显示图形示例******/
	LCD_SetFont(&Font24x32);
    /* 画直线 */
  
    LCD_ClearLine(LINE(4));/* 清除单行文字 */
	LCD_SetTextColor(BLUE);

    ILI9341_DispStringLine_EN(LINE(4),"Draw line:");
  
	LCD_SetTextColor(RED);
    ILI9341_DrawLine(50,170,210,230);  
    ILI9341_DrawLine(50,200,210,240);
  
	LCD_SetTextColor(GREEN);
    ILI9341_DrawLine(100,170,200,230);  
    ILI9341_DrawLine(200,200,220,240);
	
	LCD_SetTextColor(BLUE);
    ILI9341_DrawLine(110,170,110,230);  
    ILI9341_DrawLine(130,200,220,240);
  
    HAL_Delay(1000);
  
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */
   
    /*画矩彿*/

    LCD_ClearLine(LINE(4));	/* 清除单行文字 */
	LCD_SetTextColor(BLUE);

    ILI9341_DispStringLine_EN(LINE(4),"Draw Rect:");

	LCD_SetTextColor(RED);
    ILI9341_DrawRectangle(50,200,100,30,1);
	
	LCD_SetTextColor(GREEN);
    ILI9341_DrawRectangle(160,200,20,40,0);
	
	LCD_SetTextColor(BLUE);
    ILI9341_DrawRectangle(170,200,50,20,1);
   
    HAL_Delay(1000);
	
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */

    /* 画圆 */
    LCD_ClearLine(LINE(4));	/* 清除单行文字 */
	LCD_SetTextColor(BLUE);
	
    ILI9341_DispStringLine_EN(LINE(4),"Draw Cir:");

	LCD_SetTextColor(RED);
    ILI9341_DrawCircle(100,200,20,0);
	
	LCD_SetTextColor(GREEN);
    ILI9341_DrawCircle(100,200,10,1);
	
	LCD_SetTextColor(BLUE);
	ILI9341_DrawCircle(140,200,20,0);

    HAL_Delay(1000);
  
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_FSMC_Init();
  /* USER CODE BEGIN 2 */
  ILI9341_Init();

  uint16_t test = ILI9341_ReadID();
  printf("t%d", test);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    LCD_Test();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : LCD_BL_Pin */
  GPIO_InitStruct.Pin = LCD_BL_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LCD_BL_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LCD_RST_Pin */
  GPIO_InitStruct.Pin = LCD_RST_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LCD_RST_GPIO_Port, &GPIO_InitStruct);

}

/* FSMC initialization function */
static void MX_FSMC_Init(void)
{

  /* USER CODE BEGIN FSMC_Init 0 */

  /* USER CODE END FSMC_Init 0 */

  FSMC_NORSRAM_TimingTypeDef Timing = {0};
  FSMC_NORSRAM_TimingTypeDef ExtTiming = {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_BANK1;
  hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = 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_ENABLE;
  hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
  /* Timing */
  Timing.AddressSetupTime = 0;
  Timing.AddressHoldTime = 15;
  Timing.DataSetupTime = 26;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_A;
  /* ExtTiming */
  ExtTiming.AddressSetupTime = 0;
  ExtTiming.AddressHoldTime = 15;
  ExtTiming.DataSetupTime = 1;
  ExtTiming.BusTurnAroundDuration = 0;
  ExtTiming.CLKDivision = 16;
  ExtTiming.DataLatency = 17;
  ExtTiming.AccessMode = FSMC_ACCESS_MODE_A;

  if (HAL_SRAM_Init(&hsram1, &Timing, &ExtTiming) != HAL_OK)
  {
    Error_Handler( );
  }

  /** Disconnect NADV
  */

  __HAL_AFIO_FSMCNADV_DISCONNECTED();

  /* USER CODE BEGIN FSMC_Init 2 */

  /* USER CODE END FSMC_Init 2 */
}

/* USER CODE BEGIN 4 */
/**
  * @brief 重定向c库函数printf到USARTx
  * @retval None
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * @brief 重定向c库函数getchar,scanf到USARTx
  * @retval None
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

查看效果:

十二、工程代码

链接:https://pan.baidu.com/s/1Zb8qI-yZ795eyjGyXpgocQ?pwd=fnro 提取码:fnro

十三、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。


• 由 Leung 写于 2022 年 1 月 21 日

• 参考:stm32学习笔记 -根据外接存储器时序初始化FSMC结构体
    STM32CubeMX实战教程(七)——TFT_LCD液晶显示(附驱动代码)
    STM32CubeMX | 35-使用硬件FSMC驱动TFT-LCD屏幕(MCU屏,NT35510控制器)
    STM32CubeMX系列|TFTLCD显示

版权声明:本文为CSDN博主「Leung_ManWah」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36347513/article/details/122624920

一、TFT-LCD简介

TFT-LCD(Thin Film Transistor-Liquid Crystal Display) 即薄膜晶体管液晶显示器。TFT-LCD 与无源 TN-LCD、 STN-LCD 的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。 TFT-LCD 也被叫做真彩液晶显示器。

虽然TFT-LCD被统称为LCD,不过它是种主动式矩阵LCD,被应用在电视、平面显示器及投影机上。

1.1 液晶控制原理


这个完整的显示屏由液晶显示面板、电容触摸面板以及 PCB 底板构成。图中的触摸面板带有触摸控制芯片,该芯片处理触摸信号并通过引出的信号线与外部器件通讯,触摸面板中间是透明的,它贴在液晶面板上面,一起构成屏幕的主体,触摸面板与液晶面板引出的排线连接到 PCB 底板上,根据实际需要,PCB 底板上可能会带有“液晶控制器芯片”,图中右侧的液晶屏 PCB 上带有 RA8875 液晶控制器。因为控制液晶面板需要比较多的资源,所以大部分低级微控制器都不能直接控制液晶面板,需要额外配套一个专用液晶控制器来处理显示过程,外部微控制器只要把它希望显示的数据直接交给液晶控制器即可。而不带液晶控制器的 PCB 底板,只有小部分的电源管理电路,液晶面板的信号线与外部微控制器相连,直接控制。STM32F429 系列的芯片不需要额外的液晶控制器,也就是说它把专用液晶控制器的功能集成到 STM32F429 芯片内部了,可以理解为电脑的 CPU 集成显卡,它节约了额外的控制器成本。而 STM32F1 系列的芯片由于没有集成液晶控制器到芯片内部,所以它只能驱动自带控制器的屏幕,可以理解为电脑的外置显卡。

1.2 ILI9341液晶控制器简介

本液晶屏内部包含有一个液晶控制芯片 ILI9341,它的内部结构非常复杂,该芯片最主核心部分是位于中间的 GRAM(Graphics RAM),它就是显存。GRAM 中每个存储单元都对应着液晶面板的一个像素点。它右侧的各种模块共同作用把 GRAM 存储单元的数据转化成液晶面板的控制信号,使像素点呈现特定的颜色,而像素点组合起来则成为一幅完整的图像。框图的左上角为 ILI9341 的主要控制信号线和配置引脚,根据其不同状态设置可以使芯片工作在不同的模式,如每个像素点的位数是 6、16 还是 18 位;可配置使用 SPI 接口、8080 接口还是 RGB 接口与 MCU 进行通讯。MCU 通过 SPI、8080 接口或 RGB 接口与 ILI9341 进行通讯,从而访问它的控制寄存器 (CR)、地址计数器 (AC)、及 GRAM。 在 GRAM 的左侧还有一个 LED 控制器 (LED Controller)。LCD 为非发光性的显示装置,它需要借助背光源才能达到显示功能,LED 控制器就是用来控制液晶屏中的 LED 背光源。

二、FSMC简介

FSMC(Flexible Static Memory Controller),译为灵活的静态存储控制器。STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器,它可以用于驱动包括 SRAM、NOR FLASH 以及 NAND FLSAH 类型的存储器,不能驱动如 SDRAM 这种动态的存储器而在 STM32F429 系列的控制器中,它具有 FMC 外设,支持控制 SDRAM 存储器。

由于 FSMC 外设可以用于控制扩展的外部存储器,而 MCU 对液晶屏的操作实际上就是把显示数据写入到显存中,与控制存储器非常类似,且 8080 接口的通讯时序完全可以使用 FSMC 外设产生,因而非常适合使用 FSMC 控制液晶屏。


三、引脚确定

四、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

五、FSMC

5.1 参数配置


Connectivity 中选择 FSMC 设置,并在 NOR Flash/PSRAM/SRAM/ROM/LCD 1 中选择 NE1 Chip Select 片选选择原理图中的片选引脚NE1【选择Bank1的第一区,是根据原理图的映射管脚进行选择的,这里选择不同区对应的引脚是不同的】

FSMC_NE 是用于控制存储器芯片的片选控制信号线,STM32 具有 FSMC_NE1/2/3/4 号引脚,不同的引脚对应 STM32 内部不同的地址区域。例如,当 STM32 访问 0x68000000-0x6BFFFFFF 地址空间时,FSMC_NE3 引脚会自动设置为低电平,由于它一般连接到外部存储器的片选引脚且低电平有效,所以外部存储器的片选被使能,而访问 0x60000000-
0x63FFFFFF 地址时,FSMC_NE1 会输出低电平。当使用不同的 FSMC_NE 引脚连接外部存储器时,STM32 访问外部存储的地址不一样,从而达到控制多个外部存储器芯片的目的。

  • Memory type(设置要控制的存储器类型): 选择 LCD Interface LCD接口
  • LCD Register Select(RS引脚): 选择 A16 ,RS脚也就是命令/数据选择位,同样是根据原理图得知这里应该选择A16
  • Data(设置要控制的存储器的数据宽度): 选择 16 bits 很明显从原理图看出有16个数据引脚,这里选择16bits就好

NOR/PSRAM 1 进行具体参数配置。

NOR/PSRAM control:

  • Write operation(设置是否写使能): 选择 Enabled ,禁止写使能的话 FSMC 只能从存储器中读取数据,不能写入。
  • Extended mode(设置是否使用扩展模式): 选择 Enabled ,在非扩展模式下,对存储器读写的时序都只使用 FSMC_BCR 寄存器中的配;在扩展模式下,对存储器的读写时序可以分开配置,读时序使用 FSMC_BCR 寄存器,写时序使用 FSMC_BWTR 寄存器的配置。

5.1.1 FSMC读时序配置

这里引入一个基本概念:

HCLK周期:
按STM32F103的默认配置,HCLK的时钟频率为72MHz,即一个 THCLK 为 1/72us=0.0138us=13.8us。

NOR/PSRAM timing(FSMC读时序配置):

  • Address setup time in HCLK clock cycles(地址建立时间):0
  • Data setup time in HCLK clock cycles(数据建立时间):26

根据ILI9341时序配置FSMC读时序

NEx片选后,NOE要保持一段时间的高电平,这个时间就是ADDSET地址建立时间(通过寄存器FMC_BTRx可配置)
之后NOE变为低电平,读使能。低电平保持的时间由DATAST数据建立时间(通过寄存器FMC_BTRx可配置)决定。


  • tast:

    tast 表示地址建立时间,最小为0ns
    由时序图可以知道,FSMC在ADDSET周期之后,进入DATAST周期之后将会进行数据采样。

    所以我们设置(ADDSET)HCLK的时间要大于等于tast地址建立时间。
    (ADDSET)HCLK >= 0ns,(0)·13.8 = 0ns,所以ADDSET可以设置为0就可保证满足最小tast地址建立时间。

  • trdlfm:
    trdlfm 表示读取数据低电平的时间,最小为355ns
    ILI9341时序图没有给出ILI9341操作数据线传输被读取的数据时的相关信息,我们最好做到满足其读取数据低电平的最小时间。
    当然不做到也行,影响不大,只要在FSMC在DATAST的这个周期的数据采样中获取所有的要访问的数据就行。

    (DATAST)HCLK >355ns,(26) * 13.8 = 358.8ns>355ns,所以DATAST设置为26。

  • Bus turn around time in HCLK clock cycles(总线转换周期):0 ,仅适用于总线复用模式的NOR Flash操作,所以这里设0。
  • Access mode(存储器访问模式):A ,LCD控制器使用 Mode A ,该模式用来控制SRAM/PSRAM且OE会翻转。控制异步 NOR FLASH 时使用 B 模式。

5.1.2 FSMC写时序配置

NOR/PSRAM timing for write accesses(FSMC写时序配置):

  • Extended address setup time(地址建立时间):0
  • Extended data setup time(数据建立时间):1

根据ILI9341时序配置FSMC写时序

NEx片选后,NWE要保持一段时间的高电平,这个时间就是ADDSET地址建立时间(通过寄存器FMC_BTRx可配置)。
之后NWE变为低电平,写使能。低电平保持的时间由DATAST数据建立时间(通过寄存器FMC_BTRx可配置)决定。


  • tast:

    tast 表示地址建立时间,最小为0ns
    由时序图可以知道,FSMC在ADDSET周期之后,进入DATAST周期之后将会进行数据采样。

    所以我们设置(ADDSET)HCLK的时间要大于等于tast地址建立时间。
    (ADDSET)HCLK >= 0ns,(0)·13.8 = 0ns,所以ADDSET可以设置为0就可保证满足最小tast地址建立时间。

  • tdst、tdht:

    tdst:数据设置时间最小是10ns,在这个周期内WRX线处于低电平。
    tdht:数据保持时间,与 twrh写控制高电平的最小时间相同,是10ns,在这个周期内WRX线处于高电平。
    观察时序图,我们设置 tdst数据设置时间 为1HCLK(13.8>10)就能满足数据设置最小时间的要求,我们不需要考虑tdht数据保持时间(看上面模式B时序图,NWE变成高电平后,会持续1HCLK=13.8ns,默认满足tdht了)。

    故我们只需考虑数据建立周期 DATAST 要大于10ns就行。
    (DATAST)HCLK > 10ns,13.8>10 ,故DATAST 至少设置为1

  • Extended bus turn around time(总线转换周期):0
  • Extended access mode(存储器访问模式):A

六、设置背光和复位引脚

System Core 中选择 GPIO 设置。


在右边图中找到 LCD 背光和复位对应引脚,选择 GPIO_Output

GPIO output level 中选择 Low 输出低电平点亮,可以添加自定义标签(这样生成代码也会根据标签设置引脚的宏定义)。

七、UART串口打印

查看 STM32CubeMX学习笔记(6)——USART串口使用

八、生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

九、修改代码优化级别

STM32CubeMX生成的代码默认优化级别为Level 3,在此优化级别下编译无误下载到开发板后,LCD屏不能正常运行;将优化级别调整到Level 0编译下载后,LCD屏能够正常运行读取到ID。

KEIL5中C/C++优化等级介绍:
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。
-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。

十、添加LCD驱动文件

链接:https://pan.baidu.com/s/1W2PmfaDmv94yjvSeWvO0fw?pwd=o6cg 提取码:o6cg

加入野火的LCD驱动文件,屏蔽 ILI9341_Init() 中 GPIO 初始化 ILI9341_GPIO_Config() 和 FSMC配置 ILI9341_FSMC_Config(),因为 STM32CubeMX 工程在 main.c 里已经配置了。

十一、修改main.c

加入 ILI9341_Init() LCD屏驱动初始化后,进行 LCD_Test() 测试。

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "bsp_ili9341_lcd.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;

SRAM_HandleTypeDef hsram1;

/* USER CODE BEGIN PV */
extern uint16_t lcdid;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_FSMC_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/*用于测试各种液晶的函敿*/
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");
    if(lcdid == LCDID_ILI9341)
    {
        ILI9341_DispStringLine_EN(LINE(2),"ILI9341 LCD driver");
    }
    else if(lcdid == LCDID_ST7789V)
    {
        ILI9341_DispStringLine_EN(LINE(2),"ST7789V 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);

	/*******显示图形示例******/
	LCD_SetFont(&Font24x32);
    /* 画直线 */
  
    LCD_ClearLine(LINE(4));/* 清除单行文字 */
	LCD_SetTextColor(BLUE);

    ILI9341_DispStringLine_EN(LINE(4),"Draw line:");
  
	LCD_SetTextColor(RED);
    ILI9341_DrawLine(50,170,210,230);  
    ILI9341_DrawLine(50,200,210,240);
  
	LCD_SetTextColor(GREEN);
    ILI9341_DrawLine(100,170,200,230);  
    ILI9341_DrawLine(200,200,220,240);
	
	LCD_SetTextColor(BLUE);
    ILI9341_DrawLine(110,170,110,230);  
    ILI9341_DrawLine(130,200,220,240);
  
    HAL_Delay(1000);
  
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */
   
    /*画矩彿*/

    LCD_ClearLine(LINE(4));	/* 清除单行文字 */
	LCD_SetTextColor(BLUE);

    ILI9341_DispStringLine_EN(LINE(4),"Draw Rect:");

	LCD_SetTextColor(RED);
    ILI9341_DrawRectangle(50,200,100,30,1);
	
	LCD_SetTextColor(GREEN);
    ILI9341_DrawRectangle(160,200,20,40,0);
	
	LCD_SetTextColor(BLUE);
    ILI9341_DrawRectangle(170,200,50,20,1);
   
    HAL_Delay(1000);
	
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */

    /* 画圆 */
    LCD_ClearLine(LINE(4));	/* 清除单行文字 */
	LCD_SetTextColor(BLUE);
	
    ILI9341_DispStringLine_EN(LINE(4),"Draw Cir:");

	LCD_SetTextColor(RED);
    ILI9341_DrawCircle(100,200,20,0);
	
	LCD_SetTextColor(GREEN);
    ILI9341_DrawCircle(100,200,10,1);
	
	LCD_SetTextColor(BLUE);
	ILI9341_DrawCircle(140,200,20,0);

    HAL_Delay(1000);
  
    ILI9341_Clear(0,16*8,LCD_X_LENGTH,LCD_Y_LENGTH-16*8);	/* 清屏,显示全黿 */
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_FSMC_Init();
  /* USER CODE BEGIN 2 */
  ILI9341_Init();

  uint16_t test = ILI9341_ReadID();
  printf("t%d", test);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    LCD_Test();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel4_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  /* DMA1_Channel5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : LCD_BL_Pin */
  GPIO_InitStruct.Pin = LCD_BL_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LCD_BL_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LCD_RST_Pin */
  GPIO_InitStruct.Pin = LCD_RST_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LCD_RST_GPIO_Port, &GPIO_InitStruct);

}

/* FSMC initialization function */
static void MX_FSMC_Init(void)
{

  /* USER CODE BEGIN FSMC_Init 0 */

  /* USER CODE END FSMC_Init 0 */

  FSMC_NORSRAM_TimingTypeDef Timing = {0};
  FSMC_NORSRAM_TimingTypeDef ExtTiming = {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_BANK1;
  hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = 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_ENABLE;
  hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
  /* Timing */
  Timing.AddressSetupTime = 0;
  Timing.AddressHoldTime = 15;
  Timing.DataSetupTime = 26;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_A;
  /* ExtTiming */
  ExtTiming.AddressSetupTime = 0;
  ExtTiming.AddressHoldTime = 15;
  ExtTiming.DataSetupTime = 1;
  ExtTiming.BusTurnAroundDuration = 0;
  ExtTiming.CLKDivision = 16;
  ExtTiming.DataLatency = 17;
  ExtTiming.AccessMode = FSMC_ACCESS_MODE_A;

  if (HAL_SRAM_Init(&hsram1, &Timing, &ExtTiming) != HAL_OK)
  {
    Error_Handler( );
  }

  /** Disconnect NADV
  */

  __HAL_AFIO_FSMCNADV_DISCONNECTED();

  /* USER CODE BEGIN FSMC_Init 2 */

  /* USER CODE END FSMC_Init 2 */
}

/* USER CODE BEGIN 4 */
/**
  * @brief 重定向c库函数printf到USARTx
  * @retval None
  */
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
 
/**
  * @brief 重定向c库函数getchar,scanf到USARTx
  * @retval None
  */
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

查看效果:

十二、工程代码

链接:https://pan.baidu.com/s/1Zb8qI-yZ795eyjGyXpgocQ?pwd=fnro 提取码:fnro

十三、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。


• 由 Leung 写于 2022 年 1 月 21 日

• 参考:stm32学习笔记 -根据外接存储器时序初始化FSMC结构体
    STM32CubeMX实战教程(七)——TFT_LCD液晶显示(附驱动代码)
    STM32CubeMX | 35-使用硬件FSMC驱动TFT-LCD屏幕(MCU屏,NT35510控制器)
    STM32CubeMX系列|TFTLCD显示

版权声明:本文为CSDN博主「Leung_ManWah」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36347513/article/details/122624920

生成海报
点赞 0

Leung_ManWah

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐