news 2026/7/18 10:30:53

STM32驱动1.8寸TFT_LCD基于 “软/硬SPI协议”

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动1.8寸TFT_LCD基于 “软/硬SPI协议”

一、简介

本章讲解模拟SPI和硬件SPI驱动方式,模拟SPI可以使用任意GPIO进行模拟,比较灵活,但是速率和稳定性不如硬件SPI,硬件SPI由单片机硬件自主完成时序,在资源充足情况下推荐使用硬件SPI。

二、TFT_LCD模块介绍

2.1 简介:

这是一块常见的65K全彩的LCD屏幕采用SPI接口,驱动器IC使用ST7735,工作电压3.3V;不带触摸,本次我们通过它来进行软硬SPI实验。

三、SPI协议原理

3.1 简介

SPI是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合,它速度传输理论没有上限,主要是看硬件能接受的速度,但是我们STM32上的SPI受限于CLK/2,相比I2C半双工通信快得多;数据帧支持8位/16位长度。

3.2 SPI物理层

3.2.1SCK时钟信号线:

用于通讯同步,速率由硬件设备决定,两个不同速率的设备通讯时,通讯速率受限于低速设备。

3.2.2 MOSI主设备输出/从设备输入引脚:

即这条线上数据的方向为主机到从机。

3.2.3 MISO主设备输入/从设备输出引脚:

即在这条线上数据的方向为从机到主机。

3.2.4 片选线SSx:

一般叫片选信号线,也称NSS或CS,这个类似于I2C寻址的,低电平为选中,高电平为结束。

3.3 SPI协议层

把以下SPI的时序图理解完就懂得了他的模式配置,以及数据的读写。

3.3.1 SCK(CPOL)时钟极性:

时钟极性CPOL代表着SPI通讯设备空闲(NSS为高电平)状态时的SCK线的电平信号,CPOL=0代表SCK为低电平;反之则反之。

3.3.2 CPHA时钟相位:

时钟相位CPHA是用于数据采样时刻,CPHA=0时那两根数据线会在SCK时钟线上的奇数边沿采样,CPHA=1时会在偶数边缘采样。 原因:(实际上CPHA=1,数据都在偶数边沿采样,因为 这里所说的“偶数边沿”是指相对于每个完整的时钟周期而言,而不是指具体是上升沿还是下降沿。因为CPOL可以配置SCK的空闲状态,所以对于CPHA=1来说,无论是上升沿还是下降沿都是偶数边沿, CPOL只能控制到底是在上升沿采样还是下降沿采样)。

3.3.3 MOSI/MIOS数据线:

我们可见看到它的输入与输出是在同一个周期内被执行,所以说这就是全双工,在读的时候就可以写,在写的时候可以读,所以我们当要读时就算不需要写内容也要写一个无效数据过去然后同时进行数据接收,反之写时候它也会回复无效的数据不携带任何有意义的信息。

3.3.4 SPI模式:

由于CPOL和CPHA的不同状态,SPI分成了四种组合,主机与从机要在相同模式下才可以正常通讯,用最多就是0、3模式了。

六、软件SPI代码

/************************************************************************** ** -------------------------------------------------------------------- ** ** @name : LCD_GPIOInit ** @brief : 液晶屏模拟SPI IO初始化,液晶初始化前要调用此函数 ** @param : None ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void LCD_GPIOInit(void) { GPIO_InitTypeDef GPIO_InitStructure; __HAL_RCC_GPIOB_CLK_ENABLE(); //打开引脚时钟 __HAL_RCC_GPIOE_CLK_ENABLE(); //打开背光引脚 //配置管脚 GPIO_InitStructure.Pin = LCD_SCL | LCD_SDO| LCD_SDA|LCD_CS; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //配置数据/命令选择引脚 GPIO_InitStructure.Pin = LCD_RS; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //配置背光控制引脚 GPIO_InitStructure.Pin = LCD_BLK; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); LCD_CS_CLR;//片选信号为低 LCD_RS_CLR; //信号为低 LCD_BLK_SET; //设置背光--开 LCD_BLK_CLR; //设置背光--关 LCD_BLK_SET; //设置背光--开 } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : SPIv_WriteData ** @brief : STM32_模拟SPI写一个字节数据底层函数 ** @param : uint8_t Data 需要写入的数据 ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void SPIv_WriteData(uint8_t Data) { unsigned char i=0; for(i=8; i>0; i--) { if(Data&0x80) LCD_SDA_SET; //输出数据 else LCD_SDA_CLR; LCD_SCL_CLR; LCD_SCL_SET; Data<<=1; } } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : LCD_RESET ** @brief : 这是一个复位LCD的函数 ** @param : None ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void LCD_RESET(void) { LCD_SDO_CLR; delay_ms(15); LCD_SDO_SET; //设置SDO为高 delay_ms(15); } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : LCD_RESET ** @brief : 这是一个写入LCD一个16位数据 ** @param : uint16_t data 写入的数据 ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void LCD_WR_DATA_16Bit(uint16_t data) { LCD_CS_CLR; LCD_RS_SET; SPIv_WriteData(data>>8); SPIv_WriteData(data); LCD_CS_SET; }

七、硬件SPI代码

/************************************************************************** ** -------------------------------------------------------------------- ** ** @name : SPI2_Init ** @brief : 这是一个初始化硬件SPI的函数 ** @param : None ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; __HAL_RCC_GPIOB_CLK_ENABLE(); //打开SPI 硬件引脚 __HAL_RCC_GPIOE_CLK_ENABLE(); //打开背光引脚 //配置SPI2管脚 GPIO_InitStructure.Pin = LCD_SCL | LCD_SDO| LCD_SDA; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; GPIO_InitStructure.Alternate=GPIO_AF5_SPI2; //复用成SPI功能 HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //配置数据/命令选择引脚 GPIO_InitStructure.Pin = LCD_RS|LCD_CS; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); //配置背光控制引脚 GPIO_InitStructure.Pin = LCD_BLK; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStructure.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); LCD_CS_CLR;//片选信号为低 LCD_RS_CLR; //信号为低 LCD_BLK_SET; //设置背光--开 LCD_BLK_CLR; //设置背光--关 LCD_BLK_SET; //设置背光--开 //SPI2配置选项 __HAL_RCC_SPI2_CLK_ENABLE(); //打开SPI外设时钟 SPI_InitStructure.Instance=SPI2; //选择SPI片上外设 SPI_InitStructure.Init.Direction = SPI_DIRECTION_2LINES; //打开方向为双向 SPI_InitStructure.Init.Mode = SPI_MODE_MASTER; //配置模式为主模式 SPI_InitStructure.Init.DataSize = SPI_DATASIZE_8BIT;//数据大小为8bit SPI_InitStructure.Init.CLKPolarity = SPI_POLARITY_LOW; //时钟极性为低 SPI_InitStructure.Init.CLKPhase = SPI_PHASE_1EDGE; //指定捕获边沿奇 SPI_InitStructure.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//是否使用CRC校验 SPI_InitStructure.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//波特率预分频 SPI_InitStructure.Init.FirstBit = SPI_FIRSTBIT_MSB;//先位 SPI_InitStructure.Init.NSS=SPI_NSS_SOFT; //软件使能 SPI_InitStructure.Init.CRCPolynomial = 7;//crc计算多项式 HAL_SPI_Init(&SPI_InitStructure); //初始化SPI //使能SPI2 __HAL_SPI_ENABLE(&SPI_InitStructure); //使能SPI } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : SPI_WriteByte ** @brief : STM32_硬件SPI读写一个字节数据底层函数 ** @param : PI_HandleTypeDef* SPIx 句柄 uint8_t Data 需要写入的数据 ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : STM32_硬件SPI读写一个字节数据底层函数 ** -------------------------------------------------------------------- ** **************************************************************************/ uint8_t SPI_WriteByte(SPI_HandleTypeDef* SPIx,uint8_t Byte) { while(__HAL_SPI_GET_FLAG(SPIx, SPI_FLAG_TXE)==RESET) { ;//等待发送区空 } WRITE_REG(SPIx->Instance->DR, Byte); while(__HAL_SPI_GET_FLAG(SPIx,SPI_FLAG_RXNE)==RESET);//等待接收完一个byte return READ_REG(SPIx->Instance->DR); //返回收到的数据 } /** ****************************************************************************** * @brief : 设置硬件SPI的速度 * @param[in] : SPIx: 指向SPI_HandleTypeDef结构体的指针 * @param[in] : SpeedSet: 速度设置标志, 1表示高速, 0表示低速 * @retval : None * @author data : 轩哥 2024-09-21 * @note : 修改SPI的速度配置需要根据实际需求调整预分频系数。 ****************************************************************************** */ void SPI_SetSpeed(SPI_HandleTypeDef* SPIx, uint8_t SpeedSet) { // 关闭SPI外设以修改配置 if (HAL_SPI_GetState(SPIx) == HAL_SPI_STATE_READY) { __HAL_SPI_DISABLE(SPIx); // 使用宏关闭SPI // 根据SpeedSet参数选择合适的预分频值 if (SpeedSet) { SPIx->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // 高速配置 } else { SPIx->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 低速配置 } // 重新初始化SPI以应用新的配置 if (HAL_SPI_Init(SPIx) != HAL_OK) { printf("速度配置错误\r\n"); } // 重新使能SPI外设 __HAL_SPI_ENABLE(SPIx); } else { printf("SPI未准备好进行配置\r\n"); } } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : LCD_RESET ** @brief : 这是一个复位LCD的函数 ** @param : None ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void LCD_RESET(void) { LCD_SDO_CLR; delay_ms(15); LCD_SDO_SET; //设置SDO为高 delay_ms(15); } /************************************************************************** ** -------------------------------------------------------------------- ** ** @name : LCD_RESET ** @brief : 这是一个写入LCD一个16位数据 ** @param : uint16_t data 写入的数据 ** @retval : None ** @author data : 轩哥 2024-09-21 ** @attention : None ** -------------------------------------------------------------------- ** **************************************************************************/ void LCD_WR_DATA_16Bit(uint16_t data) { LCD_CS_CLR; LCD_RS_SET; SPI_WriteByte(&SPI_InitStructure,data>>8); SPI_WriteByte(&SPI_InitStructure,data); LCD_CS_SET; }

八、实验示例

我们将LCD连接到我们单片机上,通过宏定义来切换软硬SPI进行实验,可见如下能够正常显示。

九、总结

通过以上学习我们弄懂了SPI的原理,在实际使用中可以搭配DMA将数据快速迁移,显著提高传输效率,减少CPU负载,使得系统响应更加迅速和稳定。总之,细致的配置和有效的调试技巧是实现稳定可靠SPI通信的基础。,最后点个赞再走吧~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 10:30:23

终极指南:yuzu模拟器带你解锁Switch游戏PC体验的完整奥秘

终极指南&#xff1a;yuzu模拟器带你解锁Switch游戏PC体验的完整奥秘 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu 你是否曾梦想过在电脑上畅玩任天堂Switch的精彩游戏&#xff1f;yuzu模拟器正是实现这一梦想的…

作者头像 李华
网站建设 2026/7/18 10:28:15

Vue File Agent性能优化技巧:处理大文件上传的6个关键方法

Vue File Agent性能优化技巧&#xff1a;处理大文件上传的6个关键方法 【免费下载链接】vue-file-agent The most beautiful and full featured file upload component for Vue JS 项目地址: https://gitcode.com/gh_mirrors/vu/vue-file-agent Vue File Agent是Vue.js生…

作者头像 李华
网站建设 2026/7/18 10:28:12

连接模块中的BLE方式GATT协议分析

GATT协议简介在蓝牙的世界里&#xff0c;GATT&#xff08;通用属性配置文件&#xff09;就是这套“交接规则”。它专门负责在两个蓝牙设备连接之后&#xff0c;数据该如何组织和传输。&#x1f4ca; GATT的核心概念GATT把数据组织得像一个严格的表格&#xff0c;你可以把它想象…

作者头像 李华
网站建设 2026/7/18 10:23:36

IE8linter完全指南:如何检测HTML5标签和CSS属性在IE8中的兼容性

IE8linter完全指南&#xff1a;如何检测HTML5标签和CSS属性在IE8中的兼容性 【免费下载链接】ie8linter A little tool to lint websites for IE8 compatibility, with warnings for possible pitfalls 项目地址: https://gitcode.com/gh_mirrors/ie/ie8linter IE8linte…

作者头像 李华
网站建设 2026/7/18 10:23:31

filebeat+elasticsearch+kibana日志分析

1 默认配置 1.1 filebeat filebeat-7.17.yml,从网关中下载k8s的配置&#xff0c;指定es和kibana的配置 通过kibana查询可以查询到日志了&#xff0c;但此时还不知道具体怎么用。 1.2 kibana 在Discover中创建索引格式&#xff1a;filebeat-*&#xff0c;得到如下图&#xf…

作者头像 李华
网站建设 2026/7/18 10:22:55

poissonsearch-py错误处理:Elasticsearch异常捕获与调试技巧

poissonsearch-py错误处理&#xff1a;Elasticsearch异常捕获与调试技巧 【免费下载链接】poissonsearch-py Official Python client for Elasticsearch.The original name is elasticsearch-py, and the name is changed to poissonsearch-py for self-maintenance. 项目地址…

作者头像 李华