STM32上不用硬件SPI手把手教你用GPIO模拟SPI驱动ADS8688采集电压附完整代码在嵌入式开发中硬件SPI资源常常捉襟见肘。当你的STM32项目需要同时连接多个SPI设备或者硬件SPI引脚被其他功能占用时GPIO模拟SPI就成为了一个极具价值的替代方案。本文将带你从零开始用GPIO位带操作实现软件SPI并成功驱动高精度ADC芯片ADS8688进行多通道电压采集。1. 为什么需要软件SPI硬件SPI虽然高效但在实际项目中常常面临三个痛点引脚资源冲突当多个外设都需要SPI接口时硬件SPI数量往往不够布线限制硬件SPI的固定引脚位置可能不符合PCB布局需求时序灵活性某些特殊设备需要非标准的SPI时序软件SPI通过GPIO模拟解决了这些问题它最大的优势在于引脚可任意配置不受硬件SPI固定引脚的限制可同时支持多个设备每个设备可以独占一组GPIO时序完全可控可以精确调整时钟极性和相位提示软件SPI的缺点是速度较慢通常只能达到硬件SPI的1/10到1/5速度。但对于ADS8688这类低速高精度ADC来说完全够用。2. ADS8688芯片关键特性ADS8688是TI推出的16位、8通道SAR ADC具有以下核心优势特性参数说明分辨率16位高精度测量采样率500kSPS适合中速应用输入范围±12.288V可编程设置通道数8路单端支持自动扫描接口SPI兼容3.3V电平特别值得注意的是它的自动扫描模式可以按预设顺序自动切换采集通道大大简化了多通道采集的软件设计。3. 软件SPI实现关键点3.1 GPIO位带操作位带(bit-band)是Cortex-M内核提供的一个独特功能它允许对单个比特进行原子操作。我们首先定义位带操作宏// 位带操作宏定义 #define BITBAND(addr, bitnum) ((addr 0xF0000000)0x2000000((addr 0xFFFFF)5)(bitnum2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum)) // GPIO端口地址映射 #define GPIOA_ODR_Addr (GPIOA_BASE12) #define GPIOB_ODR_Addr (GPIOB_BASE12) #define GPIOC_ODR_Addr (GPIOC_BASE12) // 引脚操作宏 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n)3.2 SPI时序模拟ADS8688使用标准SPI模式0CPOL0CPHA0即时钟空闲时为低电平数据在上升沿采样MSB先传输对应的读写函数实现如下void SPI_ReadWriteByte(u8 Tx_Data, u8 *MISO1, u8 *MISO2, u8 *MISO3) { u8 i; for(i 0; i 8; i) { ADS8688_SCK 0; // 时钟低电平 // 准备MOSI数据 if(Tx_Data (1 (7 - i))) { ADS8688_MOSI 1; } else { ADS8688_MOSI 0; } // 产生上升沿 ADS8688_SCK 1; // 下降沿读取数据 *MISO1 1; if(ADS8688_MISO1) *MISO1 | 1; *MISO2 1; if(ADS8688_MISO2) *MISO2 | 1; *MISO3 1; if(ADS8688_MISO3) *MISO3 | 1; ADS8688_SCK 0; // 时钟回到低电平 } }4. ADS8688驱动实现4.1 初始化配置ADS8688的初始化需要配置以下几个关键参数输入电压范围±10.24V、±5.12V等自动扫描通道使能通道上电状态void ADS8688_Init(void) { SPI_GPIO_Init(); // 初始化GPIO // 设置所有通道输入范围为±10.24V Set_CH_Range_Select(Channel_0_Input_Range, VREF_B_25); Set_CH_Range_Select(Channel_1_Input_Range, VREF_B_25); // ...其他通道类似设置 // 通道0-7全部上电 Set_Channel_Power_Down(0x00); // 启用自动扫描通道0-7 Set_Auto_Scan_Sequence(0xFF); // 进入自动扫描模式 Enter_AUTO_RST_Mode(); }4.2 数据采集流程在自动扫描模式下ADS8688会按预设顺序自动切换通道我们只需要连续读取数据void Get_AUTO_RST_Mode_Data(u8 chn) { u16 i; for(i0; ichn; i) { ADS8688_CS 0; // 发送4个NOP命令读取数据 SPI_ReadWriteByte(0x00, Rxh, Rxh1, Rxh2); SPI_ReadWriteByte(0x00, Rxl, Rxl1, Rxl2); SPI_ReadWriteByte(0xFF, Rxh, Rxh1, Rxh2); SPI_ReadWriteByte(0xFF, Rxl, Rxl1, Rxl2); ADS8688_CS 1; // 组合高低字节 ADC_Data[i] (Rxh[0] 8) | Rxl[0]; } }5. 实际应用技巧5.1 时序调试方法软件SPI最常见的问题是时序不符合设备要求。调试时可以用逻辑分析仪抓取实际波形检查时钟频率是否在设备允许范围内确认CS信号的建立和保持时间验证数据在正确时钟边沿采样5.2 性能优化建议虽然软件SPI速度较慢但通过以下方法可以提升性能使用寄存器直接操作代替库函数合理设置GPIO速度50MHz减少函数调用层级使用DMA传输数据如果支持5.3 多设备扩展软件SPI的一个优势是可以轻松支持多个设备。只需为每个设备定义独立的CS引脚#define DEV1_CS PAout(4) #define DEV2_CS PAout(8) #define DEV3_CS PBout(1) // 选择设备1 DEV1_CS 0; DEV2_CS 1; DEV3_CS 1; // 进行SPI通信...6. 完整代码实现以下是驱动ADS8688的核心代码框架#include stm32f10x.h // 位带操作和引脚定义见上文 // 全局变量 u8 Rxh[3], Rxl[3]; u16 ADC_Data[8]; // 函数声明 void SPI_GPIO_Init(void); void SPI_ReadWriteByte(u8 Tx_Data, u8 *MISO); void ADS8688_WriteCmdReg(u16 cmd); void ADS8688_Init(void); void Get_AUTO_RST_Mode_Data(u8 chn); int main(void) { SystemInit(); ADS8688_Init(); while(1) { Get_AUTO_RST_Mode_Data(8); // 读取8个通道 // 处理ADC_Data中的数据... Delay_ms(10); } }在实际项目中我发现ADS8688的输入阻抗较高前端需要添加缓冲电路才能保证测量精度。另外软件SPI的时钟抖动会比硬件SPI大在高速应用时需要特别注意。