STM32CubeMX实战:SPI+DMA驱动ST7735S屏幕全流程解析
在嵌入式开发领域,图形化界面的快速实现一直是开发者关注的焦点。ST7735S作为一款常见的TFT液晶驱动芯片,广泛应用于各种嵌入式显示场景。本文将详细介绍如何利用STM32CubeMX工具和HAL库,快速构建基于SPI+DMA的高效显示驱动方案。
1. 开发环境搭建与工程创建
首先需要准备以下硬件和软件环境:
硬件准备:
- STM32F103系列开发板(本文以STM32F103C8T6为例)
- ST7735S驱动的1.44寸TFT屏幕
- 杜邦线若干
- USB转TTL串口模块(用于调试)
软件准备:
- STM32CubeMX 6.x或更高版本
- Keil MDK-ARM或STM32CubeIDE
- STM32F1xx HAL库
在CubeMX中新建工程时,选择对应的STM32型号,然后按照以下步骤配置:
/* 系统时钟配置示例(72MHz) */ RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; HAL_RCC_OscConfig(&RCC_OscInitStruct); 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; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);2. SPI外设与DMA配置详解
在CubeMX中配置SPI接口时,需要注意以下关键参数:
- SPI模式:选择全双工主模式
- 时钟极性(CPOL):Low
- 时钟相位(CPHA):1 Edge
- 数据大小:8位
- NSS信号:软件控制
- 波特率预分频:根据屏幕规格选择(建议初始设置为8分频)
DMA配置表格:
| 参数 | 配置值 | 说明 |
|---|---|---|
| Direction | Memory to Peripheral | 内存到外设传输 |
| Increment Address | Memory | 内存地址自动递增 |
| Data Width | Byte | 传输数据单位为字节 |
| Mode | Normal | 非循环模式 |
| Priority | High | DMA通道优先级 |
对应的CubeMX配置代码如下:
/* SPI1 init function */ void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } }3. GPIO引脚配置与屏幕控制信号
ST7735S屏幕除了SPI接口外,还需要控制以下几个关键信号:
- RESET:硬件复位引脚
- DC:数据/命令选择引脚
- CS:片选信号(可选,如果只有一个SPI设备可固定使能)
在CubeMX中配置这些GPIO时,建议:
- 所有控制引脚设置为输出推挽模式
- 初始电平根据屏幕规格设置
- 输出速度设置为高速(50MHz)
典型引脚配置表:
| 引脚功能 | GPIO引脚 | 初始状态 | 备注 |
|---|---|---|---|
| SPI_SCK | PA5 | - | SPI时钟线 |
| SPI_MOSI | PA7 | - | SPI数据线 |
| RESET | PB0 | High | 复位信号 |
| DC | PB1 | High | 数据/命令选择 |
| CS | PA4 | Low | 片选信号 |
对应的初始化代码:
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(hspi->Instance==SPI1) { __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // 控制引脚初始化 __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // DMA控制器时钟使能 __HAL_RCC_DMA1_CLK_ENABLE(); // SPI1 TX DMA配置 hdma_spi1_tx.Instance = DMA1_Channel3; hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPHERAL; hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_spi1_tx.Init.Mode = DMA_NORMAL; hdma_spi1_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_spi1_tx); __HAL_LINKDMA(hspi,hdmatx,hdma_spi1_tx); } }4. ST7735S驱动实现与优化技巧
ST7735S的驱动实现主要包括初始化序列配置和显示数据发送两部分。以下是关键操作函数:
屏幕初始化序列:
void ST7735_Init(void) { // 硬件复位 HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET); HAL_Delay(120); HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET); HAL_Delay(120); // 发送初始化命令序列 ST7735_WriteCommand(ST7735_SLPOUT); // 退出睡眠模式 HAL_Delay(120); ST7735_WriteCommand(ST7735_FRMCTR1); // 帧率控制 ST7735_WriteData(0x01); ST7735_WriteData(0x2C); ST7735_WriteData(0x2D); // ... 其他初始化命令 ST7735_WriteCommand(ST7735_DISPON); // 开启显示 }DMA传输优化:
对于需要频繁刷新的场景,可以使用双缓冲技术进一步提升性能:
// 定义双缓冲 uint8_t buffer1[BUFFER_SIZE]; uint8_t buffer2[BUFFER_SIZE]; uint8_t* currentBuffer = buffer1; void ST7735_Refresh_DMA(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t* buffer) { // 设置显示区域 ST7735_SetAddressWindow(x1, y1, x2, y2); // 启动DMA传输 HAL_SPI_Transmit_DMA(&hspi1, buffer, BUFFER_SIZE); // 切换缓冲区 currentBuffer = (currentBuffer == buffer1) ? buffer2 : buffer1; } // 在DMA传输完成中断中处理 void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { // 可以在这里准备下一帧数据 }5. 性能测试与常见问题解决
在实际应用中,我们通过以下方法测试驱动性能:
帧率测试:
- 实现简单的动画效果(如移动方块)
- 使用定时器计算每秒刷新帧数
- 典型结果:SPI+DMA可达50-60FPS(取决于SPI时钟和屏幕分辨率)
常见问题及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 屏幕无显示 | 电源或信号连接错误 | 检查所有连线,确认电压正常 |
| 显示花屏 | 初始化序列不正确 | 核对ST7735S规格书,调整初始化参数 |
| 刷新慢 | SPI时钟配置过低 | 提高SPI时钟频率,优化DMA配置 |
| DMA传输不完整 | 缓冲区对齐问题 | 确保缓冲区地址和大小符合DMA要求 |
性能优化建议:
- 根据实际需求调整SPI时钟分频
- 使用内存中的显示缓冲区,减少实时计算
- 合理规划屏幕刷新区域,避免全屏刷新
- 利用STM32的硬件特性(如CRC校验)提高数据传输可靠性
6. 工程源码结构与扩展应用
完整的工程源码应包含以下模块:
/Drivers /STM32F1xx_HAL_Driver // HAL库文件 /CMSIS // 内核支持文件 /Inc stm32f1xx_hal_conf.h // HAL库配置 st7735.h // 屏幕驱动头文件 main.h /Src main.c // 主程序 stm32f1xx_it.c // 中断处理 st7735.c // 屏幕驱动实现 system_stm32f1xx.c // 系统初始化扩展应用示例:
- 实现GUI框架集成
- 添加触摸屏支持
- 开发动画效果
- 构建菜单系统
提示:在实际项目中,建议将屏幕驱动封装为独立的硬件抽象层(HAL),便于移植和维护。同时,对于需要更高性能的场景,可以考虑使用LTDC接口的屏幕或更强大的STM32系列芯片。