STM32通过SPI与93C46 EEPROM通信:非8倍数时钟周期发送难题
解决STM32 SPI与93C46 EEPROM的20位命令通信问题
由于STM32 HAL库的SPI发送函数仅支持8位倍数的字节传输,而93C46的写命令需要精准的20位时钟周期,可通过以下三种实用方法实现通信:
方法一:直接操作GPIO手动生成20位时序
绕开HAL库的字节限制,直接控制SPI的NSS、SCK、MOSI引脚电平,精准生成每个时钟周期的数据位,是最可靠的方案。
步骤与代码示例
- 提前将NSS、SCK、MOSI对应的GPIO配置为推挽输出模式;
- 按93C46的高位先传规则,逐位输出数据并翻转时钟:
// 引脚宏定义(根据实际硬件连接修改) #define SPI_NSS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define SPI_NSS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define SPI_SCK_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET) #define SPI_SCK_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET) #define SPI_MOSI_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET) #define SPI_MOSI_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET) void SPI_Send_20Bits(uint32_t cmd_data) { SPI_NSS_LOW(); // 拉低片选,启动命令传输 // 从最高位(第19位)到最低位(第0位)逐位发送 for(int i = 19; i >= 0; i--) { // 设置MOSI电平 (cmd_data & (1 << i)) ? SPI_MOSI_HIGH() : SPI_MOSI_LOW(); // 生成SCK时钟上升沿(匹配93C46 CPOL=0、CPHA=0的时序) SPI_SCK_HIGH(); // 延时确保时钟宽度满足93C46要求(可根据SPI频率调整,比如用空循环替代HAL_Delay) for(uint16_t d=0; d<100; d++); SPI_SCK_LOW(); for(uint16_t d=0; d<100; d++); } SPI_NSS_HIGH(); // 拉高片选,结束命令传输 } // 使用示例:发送写命令(命令码01 + 地址0x00 + 数据0x123) uint32_t write_cmd = (0x1 << 18) | (0x00 << 12) | 0x123; SPI_Send_20Bits(write_cmd);
方法二:HAL SPI函数+手动补充剩余位
如果希望保留HAL库的部分封装,可先发送前16位(2字节),再手动补充剩余4位:
void SPI_Send_20Bits_HAL(uint32_t cmd_data) { uint8_t buf[2]; // 拆分20位数据的高16位为两个字节(高位先传) buf[0] = (cmd_data >> 12) & 0xFF; buf[1] = (cmd_data >> 4) & 0xFF; SPI_NSS_LOW(); // HAL发送前16位 HAL_SPI_Transmit(&hspi1, buf, 2, HAL_MAX_DELAY); // 手动发送剩余4位 uint8_t low4 = cmd_data & 0x0F; for(int i=3; i>=0; i--) { (low4 & (1 << i)) ? SPI_MOSI_HIGH() : SPI_MOSI_LOW(); SPI_SCK_HIGH(); for(uint16_t d=0; d<100; d++); SPI_SCK_LOW(); for(uint16_t d=0; d<100; d++); } SPI_NSS_HIGH(); }
方法三:16位SPI模式+手动补位
部分STM32的SPI支持16位数据宽度,可先配置为16位模式发送高16位,再手动补充低4位:
// 初始化时需设置SPI数据宽度为16位:hspi1.Init.DataSize = SPI_DATASIZE_16BIT; void SPI_Send_20Bits_16Mode(uint32_t cmd_data) { uint16_t high16 = (cmd_data >> 4) & 0xFFFF; SPI_NSS_LOW(); // HAL发送16位数据 HAL_SPI_Transmit(&hspi1, (uint8_t*)&high16, 1, HAL_MAX_DELAY); // 手动发送低4位 uint8_t low4 = cmd_data & 0x0F; for(int i=3; i>=0; i--) { (low4 & (1 << i)) ? SPI_MOSI_HIGH() : SPI_MOSI_LOW(); SPI_SCK_HIGH(); for(uint16_t d=0; d<100; d++); SPI_SCK_LOW(); for(uint16_t d=0; d<100; d++); } SPI_NSS_HIGH(); }
额外注意事项
- 93C46写操作前需先发送WREN写使能命令(通常为3位),同样可使用上述方法处理非8位倍数的时序;
- 时钟延时需根据93C46的最大时钟频率调整,避免因时钟过快导致数据采样错误;
- 严格控制NSS引脚的拉低/拉高时机,确保每个命令的独立性。
内容的提问来源于stack exchange,提问作者MHBagh
相关产品推荐
相关产品推荐

