通讯协议学习之路(实践部分):SPI开发实践
- 人工智能
- 2025-08-13 03:00:02

通讯协议之路主要分为两部分,第一部分从理论上面讲解各类协议的通讯原理以及通讯格式,第二部分从具体运用上讲解各类通讯协议的具体应用方法。
后续文章会同时发表在个人博客(jason1016.club)、CSDN;视频会发布在bilibili(UID:399951374)
本文前缀:
通讯协议专栏:通讯协议_JASON丶LI的博客-CSDN博客
UART理论部分:
一、具体实践方案选择同样的对于SPI也具有软件模拟和硬件外设配置的两种方案,此外也同样可以采用DMA转运数据、中断处理数据、轮询处理数据这三种方案。
软件SPI和硬件SPI之间的关系是,软件SPI是对硬件SPI的一种软件实现。软件SPI可以在没有硬件SPI模块的情况下实现SPI通信,但由于软件实现的限制,软件SPI的速度和可靠性可能不如硬件SPI。在一些资源受限的系统中,软件SPI是一种常用的替代方案。
软件模拟按照SPI传输的时序与模式,通过对SCK、SS、MOSI、MISO这四个进行高低电平的时序配置是实现SPI通讯协议的模拟。
硬件模式硬件模式直接配置单片机的SPI外设,使用其封装的库进行协议通信,不需要像软件一样一步步配置其时序,硬件SPI的工作状态主要通过读其SPI内部寄存器进行判断。其信息读取的4种模式按照参考下表。
NSS管脚与片选NSS管脚及我们熟知的片选信号,作为主设备NSS管脚为高电平,从设备NSS管脚为低电平。当NSS管脚为低电平时,该spi设备被选中,可以和主设备进行通信。在stm32中,每个spi控制器的NSS信号引脚都具有两种功能,即输入和输出。所谓的输入就是NSS管脚的信号给自己。所谓的输出就是将NSS的信号送出去,给从机。
对于NSS的输入,又分为软件输入和硬件输入。
软件输入:
NSS分为内部管脚和外部管脚,通过设置spi_cr1寄存器的ssm位和ssi位都为1可以设置NSS管脚为软件输入模式且内部管脚提供的电平为高电平,其中SSM位为使能软件输入位。SSI位为设置内部管脚电平位。同理通过设置SSM和SSI位1和0则此时的NSS管脚为软件输入模式但内部管脚提供的电平为0。若从设备是一个其他的带有spi接口的芯片,并不能选择NSS管脚的方式,则可以有两种办法,一种是将NSS管脚直接接低电平。另一种就是通过主设备的任何一个gpio口去输出低电平选中从设备。
硬件输入:
主机接高电平,从机接低电平。
二、开发实践 标准库 软件SPI SPI_Software.c #include "stm32f10x.h" // Device header #include "SPI_Software.h" void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } void MySPI_W_SCK(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); } void MySPI_W_MOSI(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); } uint8_t MySPI_R_MISO(void) { return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); } void MySPI_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); MySPI_W_SS(1); MySPI_W_SCK(0); } void MySPI_Start(void) { MySPI_W_SS(0); } void MySPI_Stop(void) { MySPI_W_SS(1); } uint8_t MySPI_SwapByte(uint8_t ByteSend) { uint8_t i, ByteReceive = 0x00; for (i = 0; i < 8; i ++) { MySPI_W_MOSI(ByteSend & (0x80 >> i)); MySPI_W_SCK(1); if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} MySPI_W_SCK(0); } return ByteReceive; } //SPI写应该Byte函数 void SPI_WriteByte(uint8_t Byte) { uint8_t i; for(i = 0;i < 8;i++) { //SCK从低电平到高电平(上升沿)时传输数据 MySPI_W_SCK(0); if(Byte & 0x80) //取出最高为,每次只能传输一个bit的数据 { MySPI_W_MOSI(1); } else { MySPI_W_MOSI(0); } Byte <<= 1; MySPI_W_SCK(1); } MySPI_W_SCK(0); } //SPI读一个Byte函数 uint8_t SPI_ReadByte(void) { uint8_t i,Byte; MySPI_W_SCK(0); for(i = 0;i < 8;i++) { MySPI_W_SCK(1); Byte <<= 1; if(MySPI_R_MISO()) { Byte ++; } MySPI_W_SCK(0); } return Byte; } SPI_Software.h #ifndef __SPISOFTWARE_H #define __SPISOFTWARE_H #include "stm32f10x.h" // Device header void MySPI_W_SS(uint8_t BitValue); void MySPI_W_SCK(uint8_t BitValue); void MySPI_W_MOSI(uint8_t BitValue); uint8_t MySPI_R_MISO(void); void MySPI_Init(void); void SPI_WriteByte(uint8_t Byte); uint8_t SPI_ReadByte(void); #endif SPI_Control.c #include "SPI_Control.h" #include "SPI_Software.h" //设备为:25AA010A //EEPROM开启写使能函数 void EEPROM_Write_ENABLE(void) { //MySPI_W_SS(1); MySPI_W_SS(0); SPI_WriteByte(EEPROM_Address_ENABLE); MySPI_W_SS(1); } //EEPROM关闭写使能函数 void EEPROM_Write_DISABLE(void) { //MySPI_W_SS(1); MySPI_W_SS(0); SPI_WriteByte(EEPROM_Address_DISABLE); MySPI_W_SS(1); } //从EEPROM中读取数据 uint8_t EEPROM_Read(uint8_t HW_Address,uint8_t SW_Address) { uint8_t date = 0; //MySPI_W_SS(1); MySPI_W_SS(0); SPI_WriteByte(HW_Address); SPI_WriteByte(SW_Address); date = SPI_ReadByte(); MySPI_W_SS(1); return date; } //往EEPROM中写数据函数 void EEPROM_Write(uint8_t HW_Address,uint8_t SW_Address,uint8_t date) { //HW_Address:EEPROM硬件地址 //SW_Address: EEPROM的软件地址,即写出内存的地址 uint8_t status = 0x01; EEPROM_Write_ENABLE(); //开启写使能 //MySPI_W_SS(1); MySPI_W_SS(0); SPI_WriteByte(HW_Address); SPI_WriteByte(SW_Address); SPI_WriteByte(date); MySPI_W_SS(1); //读取EEPROM状态寄存器的最低为,当状态寄存器的最低位为1表示还未写完 while(1) { //MySPI_W_SS(1); MySPI_W_SS(0); SPI_WriteByte(EEPROM_Address_REGISTER); status = SPI_ReadByte(); if((status & 0x01) == 0) { break; } MySPI_W_SS(1); } EEPROM_Write_DISABLE(); //关闭写使能 } SPI_Control.h #ifndef __SPICONTROL_H #define __SPICONTROL_H #include "stm32f10x.h" // Device header #define EEPROM_Address_W 0x02 //从指定地址开始写 #define EEPROM_Address_R 0X03 //从指定地址开始读 #define EEPROM_Address_ENABLE 0x06 //开启写使能命令 #define EEPROM_Address_DISABLE 0x04 //关闭写使能命令 #define EEPROM_Address_REGISTER 0x05 //读取寄存器的状态(状态寄存器的值) void EEPROM_Write_ENABLE(void); void EEPROM_Write_DISABLE(void); uint8_t EEPROM_Read(uint8_t HW_Address,uint8_t SW_Address); void EEPROM_Write(uint8_t HW_Address,uint8_t SW_Address,uint8_t date); #endif main.c #include "stm32f10x.h" // Device header #include <string.h> #include "delay.h" #include "sys.h" #include "led.h" #include "OLED.h" #include "key.h" #include "SPI_Control.h" #include "SPI_Software.h" uint8_t RxData; extern uint8_t num; int main(void) { led_Init(); Key_Init(); OLED_Init(); MySPI_Init(); // num = EEPROM_Read(EEPROM_Address_R,0x00); while(1) { OLED_ShowNum(1,1,num,2); EEPROM_Write(EEPROM_Address_W,0x00,num); led_turn(GPIOB, GPIO_Pin_0); Delay_ms(1000); } } 硬件SPI HAL库模式设置:
有主机模式全双工/半双工——Full-Duplex Master从机模式全双工/半双工——Ful-Duplex Slave只接收主机模式/只接收从机模式——Half-Duplex Master只发送主机模式——Half-Duplex SlaveSPI发送和接收轮询、中断、DMA三种模式操作函数
/* IO operation functions ****************************************************/ /******* Blocking mode: Polling */ HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout); /******* Non-Blocking mode: Interrupt */ HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Master_Seq_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Seq_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c); HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c); HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress); /******* Non-Blocking mode: DMA */ HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Master_Seq_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Seq_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); HAL_StatusTypeDef HAL_I2C_Slave_Seq_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); 硬件模式:SPI的proteus硬件模式仍在调试,后续会持续更新
通讯协议学习之路(实践部分):SPI开发实践由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“通讯协议学习之路(实践部分):SPI开发实践”
上一篇
Linux--gcc/g++
下一篇
某XX自考小程序的AES加密分析