SPI
串行外设接口,SPI 是一种高速的,全双工同步通信的一种接口,串行外设接口一般需要4根线进行通信(NSS,MISO,MOSI,SCK),但是如果打算实现单向通信(至少要用到三根线(单工通信)),就可以利用这种机制实现一对多或者一对一的通信
SPI总线采用环形结构利用主从模式(主机---->从机)进行数据传输,由于是同步通信,所以主机在发送数据的同时,从机也将发送数据
可以看到NSS/CS通过片选引脚来实现主设备和多个设备进行之间的通信,NSS可由软件控制,也可以由硬件控制
注意SPI是全双工同步通信所以引脚信号由SCK引脚进行产生,SCK只能由主设备进行控制,从设备是无法进行控制的 所以SCK输出的信号脉冲就需要时钟极性和时钟相位进行配置
时钟极性:指的是SPI在总线空闲状态下SCK的默认电平状态。SCK在空闲状态下的电平可以由SPI_CR1寄存器的CPOL位进行设置。主设备的时钟极性要根据从设备进行设置
时钟相位:指的是在传输数据时是上升沿还是下降沿来进行采集数据或者修改。该位可以由SPI_CR1寄存器的CPHA位来进行设置。主设备的时钟相位要根据采集的从设备来设置
然而采集模式有四种一般我们只用第一种和第三种第二种和第四种一般不用
第一种
CPOL=0 CPHA=0
该模式指的是SCK引脚在空闲状态下为低电平并且在第一个时钟边沿(上降沿)进行采集数据,第二个时钟边沿(下升沿)进行修改数据
第二种
CPOL=0 CPHA=1
该模式指的是SCK引脚在空闲状态下为低电平并且在第二个时钟边沿(下降沿)进行采集数据,在第一个时钟边(上升沿)进行修改数据
第三种
CPOL=1 CPHA=0
该模式指的是SCK引脚在空闲状态下是高电平并且在第一个时钟边沿(下降沿)进行采集数据,第二个时钟边沿(上升沿)进行修改数据
第四种
CPOL=1 CPHA=1
该模式指的数SCK引脚在空闲状态下四高电平并且在二个时钟边沿(上升沿)进行采集数据,第一个时钟边沿(下降沿)修改数据
在实际的开发过程中,使用最多是模式0和模式3,比如2.4G无线通信模块NRF24L01采用模式0来通信。
数据格式
主机与从机在通信传输数据时以bit为单位(串行传输),主机的数据格式必须要根据从机的数据格式进行设计(MSB/LSB)
数据通信速
可以看到SPI1外设通道的速率可以高达42Mbps,SPI2和SPI3也可以达到21Mbps,但是一些外设的通信速率最高也就是10Mbps,极少数能超过10Mbps(w25q128芯片)
使用流程
SPI外设参数数据需要根据从机进行通信配置
1) SPI_Direction 指的是SPI外设的通信模式 一般选择全双工模式
2) SPI_Mode 指的是SPI外设的操作模式 一般选择主机模式
3) SPI_DataSize 指定是SPI外设的数据位数 一般选择8bit
4) SPI_CPOL 指的是SPI外设的时钟极性 根据从机进行选择
5) SPI_CPHA 指的是SPI外设的时钟相位 在哪个边沿采集数据(根据从机设置)
6) SPI_NSS 指的是SPI外设的片选引脚 一般选择软件控
7) SPI_BaudRatePrescaler 指的是SPI外设的通信速率 要根据从机设置
8) SPI_FirstBit 指的是数据先出(MSB或者LSB) 根据从机进行设置
可以看到W25Q128Flash芯片的容量为16MB分为65536个编程页,每页可编程256个字节,擦除一次最少要擦除4kb,支持标准的SPI以及双SPI和四SPI
内存分布
工作模式
可以看到W25Q128芯片支持SPI模式(模式0和模式3),主机可选择这两种模式的一种即可
主机如何判断选择SPI的那种模式,根据CS片选引脚电平切换时SCK的电平来进行选择
所以在使用通信前必须要让主机和从机进行号通信参数的设置
数据收发
设备I
读取数据
使能写入数
读取状
禁止写入
擦除扇区
扇区写入
存储时间数据
#include "SPI.h"
#include "rtc.h"
#include "delay.h"
/*
复用miso pb4mosi pb5sck pb3
输出cs pb14*/
/* Base address of the Flash sectors */
#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6 ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8 ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9 ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10 ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11 ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */#define SPI_CS(x) (x)?GPIO_SetBits(GPIOB,GPIO_Pin_14):GPIO_ResetBits(GPIOB,GPIO_Pin_14)#define FLASH_START_ADDRESS ADDR_FLASH_SECTOR_10
#define FLASH_END_ADDRESS ADDR_FLASH_SECTOR_11static SPI_InitTypeDef SPI_InitStructure;
static GPIO_InitTypeDef GPIO_InitStructure;
//获取flash的扇区号 通过扇区地址进行获取
uint32_t GetSector(uint32_t Address)
{uint32_t sector = 0;if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)){sector = FLASH_Sector_0; }else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)){sector = FLASH_Sector_1; }else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)){sector = FLASH_Sector_2; }else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)){sector = FLASH_Sector_3; }else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)){sector = FLASH_Sector_4; }else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)){sector = FLASH_Sector_5; }else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)){sector = FLASH_Sector_6; }else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)){sector = FLASH_Sector_7; }else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)){sector = FLASH_Sector_8; }else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)){sector = FLASH_Sector_9; }else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)){sector = FLASH_Sector_10; }else/*(Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11))*/{sector = FLASH_Sector_11; }return sector; //把计算出来的扇区号返回
}void spi_config(void)
{/*!< Enable GPIO clocks */RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//打开SPI1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//配置GPIO/*!< Connect SPI pins to AF5 */ GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; /*!< SPI SCK miso mosi pin configuration */GPIO_InitStructure.GPIO_Pin =GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5 ;GPIO_Init(GPIOB, &GPIO_InitStructure);/*!< Configure sFLASH Card CS pin in output pushpull mode ********************/GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIOB, &GPIO_InitStructure);//cs引脚拉高SPI_CS(1);/*!< SPI configuration */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//8bitSPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//极性高SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个边沿采集//模式三SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//软件控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;//通信速率SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先出SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI1, &SPI_InitStructure);/*!< Enable the sFLASH_SPI */SPI_Cmd(SPI1, ENABLE);}
//发送字节
uint8_t sFLASH_SendByte(uint8_t byte)
{/*!< Loop while DR register in not emplty */while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);/*!< Send byte through the SPI1 peripheral */SPI_I2S_SendData(SPI1, byte);/*!< Wait to receive a byte */while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);/*!< Return the byte read from the SPI bus */return SPI_I2S_ReceiveData(SPI1);
}
//利用串口1发送数据
void USART1_SendString(const char *str)
{while(*str){USART_SendData(USART1,*str++);while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET ); }
}
// 读取数据
void read_data(uint32_t address, uint32_t readLen, uint8_t *recvBuf) {SPI_CS(0);sFLASH_SendByte(0x03); // Read指令// 发送地址sFLASH_SendByte((address >> 16) & 0xFF);sFLASH_SendByte((address >> 8) & 0xFF);sFLASH_SendByte(address & 0xFF);// 接收数据while (readLen--) {*recvBuf++ = sFLASH_SendByte(0xFF);}SPI_CS(1);
}// 写入数据
void pageProgram(uint32_t address, uint32_t dataLen, uint8_t *dataBuf) {writeEnable();SPI_CS(0);sFLASH_SendByte(0x02); // Page Program指令// 发送地址sFLASH_SendByte((address >> 16) & 0xFF);sFLASH_SendByte((address >> 8) & 0xFF);sFLASH_SendByte(address & 0xFF);// 发送数据while (dataLen--) {sFLASH_SendByte(*dataBuf++);}SPI_CS(1);// 等待写入完成while (readStatusRegister1() & 0x01);writeDisable();
}// 读状态寄存器1
uint8_t readStatusRegister1(void) {uint8_t status = 0;SPI_CS(0);sFLASH_SendByte(0x05); // Read Status Register指令status = sFLASH_SendByte(0xFF); // 读取状态SPI_CS(1);return status;
}// 写使能
void writeEnable(void) {SPI_CS(0);sFLASH_SendByte(0x06);SPI_CS(1);
}// 禁止写入
void writeDisable(void) {SPI_CS(0);sFLASH_SendByte(0x04);SPI_CS(1);
}// 擦除扇区
void sectorErase(uint32_t address) {writeEnable();SPI_CS(0);sFLASH_SendByte(0x20); // 扇区擦除命令sFLASH_SendByte((address >> 16) & 0xFF);sFLASH_SendByte((address >> 8) & 0xFF);sFLASH_SendByte(address & 0xFF);SPI_CS(1);// 等待擦除完成while (readStatusRegister1() & 0x01); // 等待擦除完成writeDisable();
}// 验证写入的数据是否正确
int verifyData(uint32_t address, uint8_t *data, uint32_t len) {uint8_t verifyBuf[128] = {0}; // 验证缓冲区read_data(address, len, verifyBuf); // 读取Flash中的数据for (uint32_t i = 0; i < len; i++) {if (verifyBuf[i] != data[i]) {// 输出调试信息char debugInfo[100];snprintf(debugInfo, sizeof(debugInfo), "验证失败: 读[%02X] vs 写[%02X]\r\n", verifyBuf[i], data[i]);USART1_SendString(debugInfo); // 通过串口发送错误信息return 0; // 验证失败}}return 1; // 验证成功
}// 保存100条时间记录到Flash
void save100TimeRecords(void) {uint8_t buf[26] = {0}; // 模拟26字节的时间数据uint32_t address = FLASH_START_ADDRESS;int bufSize; // 记录每条时间数据的字节大小writeEnable(); // 写使能sectorErase(address); // 擦除Flash扇区for (int i = 1; i <= 100; i++){delay_ms(2000);// 获取当前RTC时间RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure);// 将时间格式化为字符串并存储到缓冲区bufSize = snprintf((char*)buf, sizeof(buf), "Time Record %02d: %02d:%02d:%02d\r\n", i, RTC_TimeStructure.RTC_Hours, RTC_TimeStructure.RTC_Minutes, RTC_TimeStructure.RTC_Seconds);// 写入数据到FlashpageProgram(address, bufSize, buf);// 适当延时,确保数据写入完成delay_ms(50); // 验证写入数据if (!verifyData(address, buf, bufSize)) {// 如果写入失败,打印错误信息char errorBuf[50];snprintf(errorBuf, sizeof(errorBuf), "数据写入验证失败,记录编号 %d\r\n", i);USART1_SendString(errorBuf); // 通过串口发送错误信息} else {USART1_SendString("数据写入成功\r\n"); // 写入成功提示}// 更新地址,递增写入数据的长度address += bufSize;// 检查是否超出Flash地址范围if (address >= FLASH_END_ADDRESS) {USART1_SendString("写入超出Flash地址范围\r\n");break;}}writeDisable(); // 禁止写入
}
// 读取并打印保存的时间记录
void readTimeRecords(void)
{uint8_t readBuf[26] = {0}; // 用于存储从Flash中读取的数据uint32_t address = FLASH_START_ADDRESS;int recordSize = 26; // 每条时间记录的大小for (int i = 1; i <= 100; i++) {// 从Flash中读取一条时间记录read_data(address, recordSize, readBuf);// 通过串口打印读取到的数据char debugInfo[50];snprintf(debugInfo, sizeof(debugInfo), "记录编号 %d: %s\r\n", i, (char*)readBuf);USART1_SendString(debugInfo);// 更新地址,移动到下一条记录的位置address += recordSize;if (address >= FLASH_END_ADDRESS) {USART1_SendString("读取超出Flash地址范围");break;}}
}//-----------------.----
//---------------------------
// SPI任务块定义
OS_TCB SPI_Task_Tcb; // SPI任务控制块
CPU_STK SPI_Task_STK[128]; // SPI任务的堆栈空间,大小为128// SPI任务函数声明
void SPI_Task(void *parg);// SPI任务函数定义
void SPI_Task(void *parg) {OS_ERR err; // 定义错误变量,用于存储任务延时中的错误信息// 只执行一次的写入操作save100TimeRecords(); // 调用函数,保存100条时间记录到Flash中readTimeRecords(); // 延时2秒,避免过度频繁执行OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TIME_HMSM_STRICT, &err);// 假设任务只需要执行一次,可以在任务完成后终止任务OSTaskDel(NULL, &err); // 删除当前任务,释放资源
}