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线的电平信号CPOL0代表SCK为低电平反之则反之。3.3.2 CPHA时钟相位时钟相位CPHA是用于数据采样时刻CPHA0时那两根数据线会在SCK时钟线上的奇数边沿采样CPHA1时会在偶数边缘采样。 原因(实际上CPHA1,数据都在偶数边沿采样因为 这里所说的“偶数边沿”是指相对于每个完整的时钟周期而言而不是指具体是上升沿还是下降沿。因为CPOL可以配置SCK的空闲状态所以对于CPHA1来说无论是上升沿还是下降沿都是偶数边沿 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 i0; for(i8; i0; i--) { if(Data0x80) LCD_SDA_SET; //输出数据 else LCD_SDA_CLR; LCD_SCL_CLR; LCD_SCL_SET; Data1; } } /************************************************************************** ** -------------------------------------------------------------------- ** ** 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(data8); 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.AlternateGPIO_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.InstanceSPI2; //选择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.NSSSPI_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,data8); SPI_WriteByte(SPI_InitStructure,data); LCD_CS_SET; }八、实验示例我们将LCD连接到我们单片机上通过宏定义来切换软硬SPI进行实验可见如下能够正常显示。九、总结通过以上学习我们弄懂了SPI的原理在实际使用中可以搭配DMA将数据快速迁移显著提高传输效率减少CPU负载使得系统响应更加迅速和稳定。总之细致的配置和有效的调试技巧是实现稳定可靠SPI通信的基础。最后点个赞再走吧~
