Agent-Reach:AI智能体生产落地的运行时桥接层
2026/10/8 14:05:57
SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信协议,由摩托罗拉公司设计,广泛应用于嵌入式系统中。它只需要四根信号线就能实现主从设备之间的数据交换,非常适合连接Flash存储器、传感器等外设。
SPI的四根信号线分别是:
在STM32中配置SPI外设时,我们需要重点关注几个关键参数:
以STM32F1系列为例,硬件SPI1的引脚通常对应:
W25Q64是Winbond公司推出的64M-bit串行Flash存储器,采用SPI接口通信,具有以下特点:
实际项目中我发现,W25Q64对时序要求比较严格。有一次调试时发现写入失败,最后发现是SPI时钟相位配置错误。正确的配置应该是:
下面是一个完整的SPI初始化代码示例,采用STM32标准外设库:
void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 2. 配置GPIO // SCK和MOSI配置为复用推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // MISO配置为上拉输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct); // SS配置为普通推挽输出 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_SetBits(GPIOA, GPIO_Pin_4); // 默认不选中 // 3. 配置SPI参数 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low; // 模式0 SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge; // 模式0 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 18MHz @72MHz PCLK SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStruct.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStruct); // 4. 使能SPI SPI_Cmd(SPI1, ENABLE); }调试时有个小技巧:如果SPI通信不正常,可以先用逻辑分析仪抓取SCK、MOSI和MISO的波形,确认时序是否符合预期。我曾经遇到过因为GPIO速度配置过低导致波形畸变的问题,将GPIO_Speed提高到50MHz后解决。
首先需要封装几个基础函数:
// 片选控制 void W25Q64_CS(uint8_t state) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)state); } // 发送一个字节并接收返回值 uint8_t SPI1_ReadWriteByte(uint8_t data) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, data); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); } // 读取设备ID(用于检测芯片是否正常) uint32_t W25Q64_ReadID(void) { uint32_t id = 0; W25Q64_CS(0); SPI1_ReadWriteByte(0x9F); // JEDEC ID指令 id |= SPI1_ReadWriteByte(0xFF) << 16; id |= SPI1_ReadWriteByte(0xFF) << 8; id |= SPI1_ReadWriteByte(0xFF); W25Q64_CS(1); return id; }W25Q64的操作都需要通过特定指令实现,常见指令如下:
#define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_PageProgram 0x02 #define W25X_SectorErase 0x20实现写使能和等待写入完成函数:
void W25Q64_WriteEnable(void) { W25Q64_CS(0); SPI1_ReadWriteByte(W25X_WriteEnable); W25Q64_CS(1); } void W25Q64_WaitBusy(void) { W25Q64_CS(0); SPI1_ReadWriteByte(W25X_ReadStatusReg); while((SPI1_ReadWriteByte(0xFF) & 0x01) == 0x01); W25Q64_CS(1); }Flash存储器必须先擦除才能写入,擦除的最小单位是扇区(4KB):
void W25Q64_SectorErase(uint32_t addr) { W25Q64_WriteEnable(); W25Q64_CS(0); SPI1_ReadWriteByte(W25X_SectorErase); SPI1_ReadWriteByte((addr >> 16) & 0xFF); SPI1_ReadWriteByte((addr >> 8) & 0xFF); SPI1_ReadWriteByte(addr & 0xFF); W25Q64_CS(1); W25Q64_WaitBusy(); }页编程函数(最多写入256字节):
void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { W25Q64_WriteEnable(); W25Q64_CS(0); SPI1_ReadWriteByte(W25X_PageProgram); SPI1_ReadWriteByte((addr >> 16) & 0xFF); SPI1_ReadWriteByte((addr >> 8) & 0xFF); SPI1_ReadWriteByte(addr & 0xFF); for(uint16_t i=0; i<len; i++) { SPI1_ReadWriteByte(data[i]); } W25Q64_CS(1); W25Q64_WaitBusy(); }一个完整的读写流程示例:
uint8_t buf[256], rbuf[256]; memset(buf, 0xAA, 256); // 擦除第一个扇区 W25Q64_SectorErase(0x000000); // 写入数据 W25Q64_PageProgram(0x000000, buf, 256); // 读取验证 W25Q64_ReadData(0x000000, rbuf, 256); if(memcmp(buf, rbuf, 256) == 0) { printf("Verify OK!\n"); }在项目开发中,建议将W25Q64的操作封装成独立的驱动模块,便于移植和维护。对于需要频繁读写的场景,可以考虑实现缓存机制来减少擦写次数,延长Flash寿命。