第一次用STM32F407的DMA+SPI驱动M95512 EEPROM时,我在硬件连接上就踩了坑。这个512Kbit的SPI接口存储芯片,引脚看似简单但暗藏玄机。最容易被忽略的是WP(写保护)引脚必须接高电平,否则所有写操作都会失败。我当时用杜邦线随便接了GND,结果调试了一整天才发现是这个低级错误。
CubeMX配置SPI3时,时钟源要选PLLCLK才能达到最高42MHz。具体参数设置有个关键细节:CPOL和CPHA必须与EEPROM规格书一致。M95512默认是模式0(CPOL=0,CPHA=0),但实际测试发现模式3(CPOL=1,CPHA=1)更稳定。这里有个技巧:用逻辑分析仪抓取EEPROM开发板的波形,直接对照着配置最保险。
DMA通道配置容易出错的地方是数据流选择。以SPI3_TX为例,查参考手册可知要选DMA1 Stream5 Channel0。我最初错选了Stream7,导致数据传输时触发硬件错误。建议配置时打开"Show Advanced"选项,仔细核对每个参数:
调试时遇到最诡异的问题是SPI完全没有波形输出。用示波器检查所有引脚,发现SCK、MOSI都是死寂的直线。查遍CubeMX配置没发现问题,最后发现是GPIO速度设置不当——初始配置为低速模式,而SPI3时钟设成了21MHz。
教训:GPIO速度必须与SPI时钟匹配。当SPI时钟超过10MHz时,GPIO必须配置为Very High Speed。修改方法是在MX_GPIO_Init函数里增加这行:
c复制GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
这个坑让我浪费了两天时间,因为CubeMX默认生成的代码是低速模式,而手册里又没明确说明这个关联性。后来发现STM32的GPIO速度实际影响的是输出驱动电路的响应时间,速度不够会导致信号边沿达不到SPI要求的斜率。
用HAL_SPI_Transmit_DMA()发送数据时,发现DR寄存器始终显示0xFF。这个问题困扰了我很久,最终发现是DMA和SPI的启动顺序有问题。正确的操作流程应该是:
另一个关键点是DMA中断配置。如果传输完成后需要处理回调,要在CubeMX里启用DMA全局中断,并实现HAL_SPI_TxCpltCallback()函数。但注意不要在中断里做耗时操作,我遇到过因为打印调试信息导致数据丢失的情况。
对于大数据量传输,推荐使用双缓冲模式。示例代码如下:
c复制// 在main.c中定义双缓冲区
uint8_t txBuffer1[256], txBuffer2[256];
// DMA传输完成回调
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(hspi->Instance == SPI3)
{
// 切换缓冲区继续传输
HAL_SPI_Transmit_DMA(hspi, txBuffer2, 256);
}
}
M95512的页写功能看似简单,但有三个致命细节:
c复制uint16_t pageRemain = EEPROM_PAGESIZE - (addr % EEPROM_PAGESIZE);
if(len > pageRemain){
// 分两次写入
}
c复制do {
sEE_ReadStatusRegister(&status);
} while(status & EEPROM_WIP_FLAG);
经过多次迭代,我总结出几个提升可靠性的技巧:
实测性能数据对比:
| 操作方式 | 写入1KB耗时 | CPU占用率 |
|---|---|---|
| 轮询模式 | 12.8ms | 100% |
| 中断模式 | 11.2ms | 85% |
| DMA模式 | 3.5ms | <5% |
最后分享一个调试技巧:当SPI通信异常时,可以先用GPIO模拟SPI验证硬件连接是否正确。我写了个简化的模拟函数,帮助快速定位物理层问题:
c复制void Software_SPI_Write(uint8_t data)
{
for(int i=0; i<8; i++){
HAL_GPIO_WritePin(SPI_SCK_GPIO_Port, SPI_SCK_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SPI_MOSI_GPIO_Port, SPI_MOSI_Pin, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
delay_us(1);
HAL_GPIO_WritePin(SPI_SCK_GPIO_Port, SPI_SCK_Pin, GPIO_PIN_SET);
delay_us(1);
data <<= 1;
}
}
在项目后期,我将所有EEPROM操作封装成了通用驱动库,主要包含以下功能模块:
硬件抽象层:
核心功能层:
应用接口层:
关键数据结构设计:
c复制typedef struct {
SPI_HandleTypeDef *hspi;
GPIO_TypeDef *cs_port;
uint16_t cs_pin;
uint32_t timeout;
uint8_t status;
} EEPROM_HandleTypeDef;
使用示例:
c复制EEPROM_HandleTypeDef heeprom;
heeprom.hspi = &hspi3;
heeprom.cs_port = GPIOA;
heeprom.cs_pin = GPIO_PIN_15;
uint8_t buf[256];
eeprom_read(&heeprom, 0x0000, buf, sizeof(buf));
这个架构的优点是硬件无关性,更换SPI接口或EEPROM型号时只需修改硬件抽象层。我在两个项目中复用这套代码,开发效率提升了60%以上。