赞
踩
当我们发现STM32上的硬件SPI引脚已被占用时,为了实现SPI通信功能,我们可以转而采用软件SPI作为替代方案。然而,需要注意的是,相较于硬件SPI,软件SPI的性能可能会受到一定程度的影响,因为软件实现需要更多的CPU周期和资源来模拟硬件的行为。尽管如此,在硬件资源受限或特定应用需求下,软件SPI仍然是一个可行的选择。
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种由Motorola(摩托罗拉)公司推出并发展的同步串行接口技术。它主要应用于嵌入式系统中的短距离通信,是一种高速的、全双工、同步的通信总线。
SPI通信协议具有高速传输、简单灵活、可靠稳定等特点,在各种嵌入式系统中得到广泛应用。SPI通信协议由四根信号线组成,包括时钟线(SCK)、主端输出从端输入线(MOSI)、主端输入从端输出线(MISO)和片选线(SS)。其中,时钟线由主设备控制,用于同步数据传输;MOSI和MISO分别用于主设备向从设备发送数据和从设备向主设备发送数据;片选线用于选择从设备,可以有多个从设备,通过片选线来选择具体的从设备进行通信。
SPI设备之间采用全双工模式通信,是一个主机和一个或者多个从机的主从模式。主机负责初始化帧,这个数据传输帧可以用于读与写两种操作,片选线可以从多个从机选择一个来响应主机的请求。
然而,SPI也有其缺点,比如没有指定的流控制,没有应答机制确认是否接收到数据,因此在数据传输的可靠性上存在一些缺陷。
总的来说,SPI是一种非常实用的串行通信协议,尤其适用于连接微控制器和外围设备,如存储器芯片、传感器、显示屏等。
我这里进行一个简单的描述,具体的原理有人写的比我更好
我这里使用的是STM32CubeMX来生成代码,使用的芯片是STM32F103RCT6,但是未使用CubeMX来配置引脚,所以我这里将给出代码。引脚分配如下(我这里的应用是驱动LCD,所以默认使用主机模式):
SCL=SCLK | SDA=MOSI | RST | CS |
---|---|---|---|
PB4 | PB5 | PB6 | PB8 |
代码如下(这里我的应用是驱动LCD,所以命名的就是LCD):
#define LCD_SCL_PORT GPIOB #define LCD_SCL_PIN GPIO_PIN_4 #define LCD_SDA_PORT GPIOB #define LCD_SDA_PIN GPIO_PIN_5 #define LCD_RST_PORT GPIOB #define LCD_RST_PIN GPIO_PIN_6 #define LCD_DC_PORT GPIOB #define LCD_DC_PIN GPIO_PIN_7 #define LCD_CS_PORT GPIOB #define LCD_CS_PIN GPIO_PIN_8 #define LCD_SCL_H HAL_GPIO_WritePin(LCD_SCL_PORT,LCD_SCL_PIN,GPIO_PIN_SET); #define LCD_SCL_L HAL_GPIO_WritePin(LCD_SCL_PORT,LCD_SCL_PIN,GPIO_PIN_RESET); #define LCD_SDA_H HAL_GPIO_WritePin(OLED_SDA_PORT,LCD_SDA_PIN,GPIO_PIN_SET); #define LCD_SDA_L HAL_GPIO_WritePin(OLED_SDA_PORT,LCD_SDA_PIN,GPIO_PIN_RESET); #define LCD_RST_H HAL_GPIO_WritePin(LCD_RST_PORT,LCD_RST_PIN,GPIO_PIN_SET); #define LCD_RST_L HAL_GPIO_WritePin(LCD_RST_PORT,LCD_RST_PIN,GPIO_PIN_RESET); #define LCD_DC_H HAL_GPIO_WritePin(LCD_DC_PORT,LCD_DC_PIN,GPIO_PIN_SET); #define LCD_DC_L HAL_GPIO_WritePin(LCD_DC_PORT,LCD_DC_PIN,GPIO_PIN_RESET); #define LCD_CS_H HAL_GPIO_WritePin(LCD_CS_PORT,LCD_CS_PIN,GPIO_PIN_SET); #define LCD_CS_L HAL_GPIO_WritePin(LCD_CS_PORT,LCD_CS_PIN,GPIO_PIN_RESET); #define LCD_BL_H HAL_GPIO_WritePin(LCD_BL_PORT,LCD_BL_PIN,GPIO_PIN_SET); #define LCD_BL_L HAL_GPIO_WritePin(LCD_BL_PORT,LCD_BL_PIN,GPIO_PIN_RESET);
void LCD_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = LCD_SCL_PIN | LCD_SDA_PIN | LCD_BL_PIN | LCD_CS_PIN | LCD_RST_PIN | LCD_DC_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
对引脚的初始化完毕之后,那就是实现基本的函数,接收和发送函数
发送函数
void SPI_Transmit(uint8_t byte) {
uint16_t counter;
LCD_CS_L;
for (counter = 0; counter < 8; counter++) {
LCD_SCL_L;
if ((byte & 0x80) == 0) {
LCD_SDA_L;
} else
LCD_SDA_H;
byte = byte << 1;
LCD_SCL_H;
}
LCD_CS_H;
}
接收函数(使用接收函数,需要将SDA引脚模式改为输入模式)
uint8_t SPI_ReceiveByte(){
uint8_t i;
uint8_t byte = 0;
for(i = 7;i >= 0; i--){
LCD_SCL_L;
if(HAL_GPIO_ReadPin(LCD_SDA_PORT,LCD_SDA_PIN)){
byte |= (1 << i);
}
LCD_SCL_H;
}
return byte;
}
以上就是实现SPI发送和接受的基本功能函数。
直接复制代码会显得文章特别长,所以我这里使用了代码小抄来分享我的代码,这里需要说明的是我LCD屏幕是1.4寸的,它的驱动芯片为ST7735S。
LCD.c->https://codecopy.cn/post/gfio1s
LCD.h->https://codecopy.cn/post/ezgqo8
lcdfont.h->https://codecopy.cn/post/ky0cam
虽然软件SPI相比于硬件SPI的性能比较弱,但是可以为我们解决引脚占用的问题,它的灵活性也比较高,可以不限特定的硬件引脚。但是它的缺点也比较明显,就是它的传输速度较低,无法满足高速的传输需求,将会增加处理的负载,影响系统的整体性能。所以可以使用硬件SPI的情况下,则还是推荐使用硬件SPI为宜。
如果有不对的地方,欢迎指正!
本人博客:https://www.lfengsir.cn
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。