ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32与AD7606 SPI接口实战:硬件连接、CubeMX配置与数据采集

STM32与AD7606 SPI接口实战:硬件连接、CubeMX配置与数据采集 1. 先搞清楚AD7606到底该怎么接STM321.1 为什么不用并行接口而选SPIAD7606这颗芯片做电力监测、振动采集的人应该都不陌生8通道同步采样、16位分辨率、200kSPS吞吐率算是中高精度采集场景里的常客。它本身支持并行、串行两种主机接口并行模式下8根数据线加一堆控制线STM32F103这种48脚或64脚的封装根本不够分。我最早用并行模式接过一次光数据线就吃掉8个GPIO再加上CS、RD、RESET、CONVST、BUSY一个端口基本被占满后面想加个LCD或者SD卡都没地方。SPI模式就舒服多了只需要SCLK、MISO、CS三根线MOSI其实用不上因为AD7606是只读从机加上CONVST和BUSY两根控制线总共5根就能跑起来。代价是读取速度会慢一些因为8个通道的数据要串行移出来每个通道16位一共128个时钟周期。但STM32F103的SPI跑到18MHz完全没问题128个周期也就7微秒左右对于大多数工业采集场景采样率1k到10k绰绰有余。这里有个容易搞混的点AD7606的SPI不是标准四线SPI它没有MOSI也不需要发送命令字。它的工作方式是——你拉低CS之后每个SCLK下降沿它就从SDO吐出一位数据从DB15一直吐到DB0然后自动切换到下一个通道。所以STM32这边配置SPI的时候MOSI引脚可以不接但CubeMX里SPI模式还是要选全双工或者只接收模式不能选成只发送。1.2 硬件连接里最容易翻车的三个地方第一个是CONVST的脉冲宽度。AD7606要求CONVST高电平至少保持25ns听起来很短对吧但如果你用STM32的GPIO直接翻转中间夹着HAL库函数调用实际脉宽可能到几百纳秒甚至微秒级。这倒不是问题问题是有些人用PWM去触发CONVST频率设得太高导致转换还没完成下一次转换又来了。我的建议是老老实实用GPIO翻转转换间隔根据你需要的采样率来定别贪快。第二个是BUSY信号的等待方式。AD7606转换开始后BUSY拉高转换完成拉低。很多人图省事用while循环死等这在单任务裸机里勉强能用但如果你后面要加串口输出或者LCD刷新死等就会把整个系统卡住。正确做法是用EXTI外部中断BUSY下降沿触发中断在中断里置标志位主循环检测标志位再去读SPI。这样CPU利用率高也不会丢数据。第三个是基准电压和模拟输入范围。AD7606内部有2.5V基准也可以通过REFIN/REFOUT引脚外接基准。如果你用内部基准量程就是±5V或±10V由RANGE引脚决定。这里有个坑很多人忘了接RANGE引脚悬空状态下电平不确定导致读出来的数据忽大忽小。一定要把RANGE明确拉到GND或VCC对应±5V和±10V量程。引脚方向连接要点CONVSTSTM32输出GPIO推挽输出脉冲≥25nsBUSYSTM32输入建议接EXTI下降沿触发CSSTM32输出SPI硬件NSS或普通GPIOSCLKSTM32输出SPI时钟建议≤18MHzMISOSTM32输入接AD7606的SDORANGE硬件配置拉高±10V拉低±5VRESETSTM32输出上电后给一个高脉冲复位1.3 CubeMX里SPI参数怎么填才不踩坑打开CubeMX选好芯片型号之后在Connectivity里找到SPI。以STM32F103C8T6为例SPI1的引脚是PA5(SCK)、PA6(MISO)、PA7(MOSI)。MOSI虽然用不上但CubeMX默认会分配你可以不管它实际不接线就行。关键参数这么设ModeFull-Duplex Master虽然只收不发但全双工模式最省事Data Size8 Bits注意AD7606是16位数据但SPI按8位一字节读读两次拼起来CLK PolarityHighCPOL1CLK Phase2 EdgeCPHA1NSSSoftware用普通GPIO控制CS比硬件NSS灵活Prescaler8分频72MHz/89MHz先跑通再提速First BitMSB First这里重点说CPOL和CPHA。AD7606的SPI时序是SCLK空闲为高数据在下降沿输出、上升沿采样。对应到STM32就是CPOL1、CPHA1。如果你设成CPOL0、CPHA0读出来的数据会整体偏移一位表现为数值翻倍或者完全乱码。我见过有人调了两天以为是硬件问题最后发现就是这两个参数反了。还有NSS选Software的原因AD7606的CS需要在连续读8个通道期间一直保持低电平如果用硬件NSSSTM32在每次SPI传输结束后会自动拉高CS导致通道切换错乱。用软件NSS手动控制GPIO读完8个通道再拉高时序就对了。2. 用CubeMX生成代码后必须手动补的部分2.1 初始化顺序里藏着的隐患CubeMX生成的MX_SPI1_Init()只配置了SPI外设本身但AD7606需要额外的GPIO控制和复位时序。很多人直接把MX_SPI1_Init()放在main开头就完事结果发现读出来的全是0或者全是0xFFFF。问题出在AD7606上电后需要至少一个RESET脉冲才能进入正常工作状态而且CONVST和BUSY引脚也要初始化。正确的初始化顺序应该是HAL_Init()和SystemClock_Config()CubeMX自动生成MX_GPIO_Init()CubeMX自动生成但你要在里面加上CONVST、CS、RESET的初始电平设置MX_SPI1_Init()手动执行AD7606复位RESET拉高至少50ns然后拉低等待至少25ms让AD7606内部电路稳定配置EXTI中断如果BUSY接了中断引脚我一般会写一个AD7606_Init()函数把这些步骤包起来放在MX_SPI1_Init()之后调用。注意复位脉冲宽度数据手册写的是最小50ns但实际用HAL_Delay(1)给1ms也没问题AD7606不会因为复位脉冲太长而损坏。2.2 GPIO初始电平的细节CubeMX里配置GPIO输出时有一个GPIO output level选项默认是Low。对于CS引脚初始电平应该是High不选中否则上电瞬间AD7606可能误以为被选中。对于CONVST初始应该是Low不触发转换。RESET初始应该是Low不复位。这些细节CubeMX不会帮你自动设对需要你在GPIO配置界面一个个改。我建议把CS、CONVST、RESET都设成推挽输出、无上下拉、高速模式。速度选High就行AD7606的时序要求不高不需要Very High。另外BUSY引脚配置成GPIO_Input如果要接EXTI就选GPIO_EXTI模式触发边沿选Falling Edge。这里有个小坑STM32F103的EXTI线是共享的PA0和PB0共用EXTI0如果你BUSY接在PA0那PB0就不能再用中断了。选引脚的时候留意一下。2.3 SPI读写函数的封装HAL库提供的HAL_SPI_Receive()和HAL_SPI_TransmitReceive()都可以用但直接调用会有问题每次调用HAL_SPI_Receive()HAL库内部会检查SPI状态如果上一次传输还没完成会返回HAL_BUSY。在连续读8个通道的场景下这种检查反而添乱。我的做法是直接用寄存器操作绕过HAL的状态机uint8_t AD7606_ReadByte(void) { while(!(SPI1-SR SPI_SR_TXE)); SPI1-DR 0xFF; // 发送哑数据产生时钟 while(!(SPI1-SR SPI_SR_RXNE)); return SPI1-DR; } uint16_t AD7606_ReadChannel(void) { uint8_t hi AD7606_ReadByte(); uint8_t lo AD7606_ReadByte(); return ((uint16_t)hi 8) | lo; }这样写的好处是时序完全可控不会被HAL库的内部状态干扰。注意发送的数据是0xFF还是0x00都行因为AD7606根本不看MOSI它只管在SCLK下降沿输出数据。但发0xFF有个好处如果MISO断线了读回来全是0xFF你能立刻发现异常。读8个通道的完整流程void AD7606_ReadAll(int16_t *buf) { CS_LOW(); for(int i 0; i 8; i) { buf[i] (int16_t)AD7606_ReadChannel(); } CS_HIGH(); }注意buf用int16_t而不是uint16_t因为AD7606输出的是二进制补码±5V量程下0x8000对应-5V0x7FFF对应4.9998V。用有符号数存后面换算电压方便。3. 从原始数据到真实电压的换算逻辑3.1 二进制补码的坑AD7606输出的是16位二进制补码量程±5V时理想情况下0x8000-32768对应 -5V0x0000 对应 0V0x7FFF32767对应 4.99985V换算公式是电压 原始值 / 32768.0 * 5.0但这里有个精度陷阱如果你用原始值 / 32768 * 5整数除法会先把小数部分丢掉。必须写成原始值 * 5.0 / 32768.0让编译器按浮点运算。或者更高效的做法是用定点数电压mV (int32_t)原始值 * 5000 / 32768这样全程整数运算在STM32F103这种没有FPU的芯片上快很多。±10V量程同理把5.0换成10.05000换成10000。3.2 基准电压误差的校准AD7606内部基准标称2.5V但实际可能在2.49到2.51之间对应到满量程就有±0.4%的误差。如果你做的是电表或者精密测量这个误差不能忽略。校准方法很简单找一个已知的精密电压源比如AD584或者干脆用高精度万用表测过的电池输入到一个通道读回原始值反推实际基准。比如你输入1.0000V读回原始值6553那实际基准 1.0000 * 32768 / 6553 5.0008V。把这个值存到Flash里每次换算用它代替5.0。我一般会在出厂前用一个通道做校准把校准系数存在STM32的Flash最后一页。运行时从Flash读出来用。注意Flash读写要解锁而且擦除是按页擦的别把程序区擦了。3.3 过采样与数字滤波AD7606本身支持过采样通过OS0/OS1/OS2三个引脚配置可以设置2倍到64倍过采样。过采样会降低吞吐率但提高有效分辨率。比如你设64倍过采样200kSPS会降到3.125kSPS但噪声会显著降低。如果你不想动硬件引脚也可以在STM32软件里做滑动平均。比如连续读16次去掉最大最小值再平均能有效抑制工频干扰。但注意软件滤波会引入延迟如果你做的是闭环控制延迟太大会导致振荡。我的经验是电力监测类应用50Hz工频用软件滑动平均就够了窗口取20ms的整数倍比如20个点对应1kHz采样率下的20ms能完美抑制50Hz纹波。振动采集类应用建议用AD7606硬件过采样因为振动信号频率高软件滤波容易把有用信号也滤掉。4. 实测中遇到的时序问题和解决方案4.1 BUSY信号抖动导致的误触发第一次调通之后我发现偶尔会读到全0的数据。用逻辑分析仪抓波形发现BUSY信号在下降沿附近有抖动导致EXTI中断触发了两次。第一次中断时转换确实完成了第二次中断时数据已经被读走SPI移位寄存器里是空的读回来就是0。解决方案有两个一是在EXTI中断里加软件消抖检测到下降沿后延时1微秒再确认BUSY还是低二是改用轮询方式在主循环里检测BUSY引脚电平低电平才去读。我最后选了轮询因为采集任务本来就是主循环的核心没必要用中断增加复杂度。轮询的写法void AD7606_StartConvert(void) { CONVST_HIGH(); __NOP(); __NOP(); __NOP(); // 约25ns CONVST_LOW(); } int AD7606_WaitReady(uint32_t timeout) { while(BUSY_READ()) { if(--timeout 0) return -1; } return 0; }timeout用循环次数而不是时间因为不同主频下时间不一样。72MHz下大概10000次循环对应几毫秒足够AD7606完成转换。4.2 SPI时钟太快导致数据错位把Prescaler从8改成4之后18MHz发现读出来的数据偶尔会跳变。用示波器看SCLK波形上升沿有振铃幅度大概0.5V。AD7606的SCLK输入是高阻抗CMOS振铃导致它在上升沿附近误判。解决办法在SCLK线上串一个33欧姆的电阻靠近STM32端放。这个电阻和线路寄生电容形成低通把振铃压下去。同时把SPI时钟降到9MHz数据就稳了。如果你非要用18MHzPCB走线要短最好包地。还有一个可能是MISO的建立时间不够。AD7606在SCLK下降沿输出数据STM32在上升沿采样中间只有半个时钟周期。18MHz下半个周期是27nsAD7606的输出延迟最大25ns余量只有2ns非常紧张。降到9MHz后余量变成55ns就安全了。4.3 多通道串扰的排查8个通道同时采集时如果某个通道输入悬空相邻通道的读数会受影响。这是因为AD7606内部是多路复用加ADC架构通道切换时会有电荷注入。数据手册建议未用通道接地不要悬空。我实测发现即使所有通道都接了信号源如果信号源内阻差异大比如一个通道接50欧姆另一个接10k欧姆高内阻通道的读数会偏低。这是因为AD7606的输入阻抗是1M欧姆和信号源内阻分压。解决办法是在高内阻信号源后面加一个电压跟随器比如OPA333把输出阻抗降下来。问题现象可能原因排查方法解决方案读数全0CS未拉低或SPI模式错逻辑分析仪看CS和SCLK检查CPOL/CPHA和NSS配置读数全0xFFFFMISO断线或AD7606未复位万用表测MISO通断补复位脉冲检查焊接数据跳变SCLK振铃或建立时间不足示波器看SCLK波形串33Ω电阻降时钟通道间串扰未用通道悬空逐个通道接地测试未用通道接GND数值整体偏移基准电压不准输入已知电压反推软件校准基准系数5. 把采集系统跑稳的几条实战经验5.1 电源和地去耦不能省AD7606的AVCC和DVCC要分开供电AVCC用5VDVCC用3.3V和STM32一致。每个电源引脚旁边放一个100nF陶瓷电容加一个10uF钽电容越近越好。我见过有人为了省事把AVCC和DVCC接在一起用5V结果SPI通信时好时坏因为DVCC超过STM32的IO耐压值长期运行会损坏引脚。模拟地和数字地要单点连接通常在AD7606的GND引脚下方用0欧姆电阻或者磁珠连接。如果整个板子铺地至少要把模拟区域和数字区域分开铺中间用一条细线连接。5.2 采集任务的调度策略如果你的系统还要做串口输出、LCD显示、按键扫描采集任务不能一直占着CPU。我的做法是用定时器触发采集比如TIM2每1ms产生一次中断在中断里启动CONVST然后主循环里检测BUSY标志去读数据。这样采集节奏由硬件定时器保证不受其他任务影响。注意中断里不要做SPI读取因为SPI读取要几十微秒放在中断里会阻塞其他中断。中断里只做CONVST触发和置标志位读取放在主循环。void TIM2_IRQHandler(void) { if(TIM2-SR TIM_SR_UIF) { TIM2-SR ~TIM_SR_UIF; AD7606_StartConvert(); conv_flag 1; } } int main(void) { // 初始化... while(1) { if(conv_flag !BUSY_READ()) { AD7606_ReadAll(adc_buf); conv_flag 0; // 处理数据... } } }5.3 数据对齐和缓冲区管理如果你用DMA搬运SPI数据注意AD7606是16位数据但SPI按8位传输DMA缓冲区要定义成uint8_t数组长度是通道数乘以2。读完之后再拼成int16_t。DMA的好处是不占CPU但配置起来麻烦而且STM32F103的SPI1_RX只有DMA1通道2SPI2_RX是DMA1通道4别搞混了。我一般不用DMA因为8个通道128个字节用寄存器轮询也就几十微秒对大多数应用足够了。DMA留给串口或者ADC这种数据量大的外设。缓冲区建议用双缓冲一个缓冲区在采集时另一个缓冲区在处理。处理完交换指针。这样不会因为处理数据而丢失下一帧采集。双缓冲在裸机里用两个数组加一个索引变量就能实现不需要RTOS。5.4 上电自检和异常恢复工业现场环境恶劣AD7606偶尔会因为电源波动或者静电导致工作异常。我一般会在上电时做一次自检读一个已知电压比如内部基准输出的2.5V如果读数在合理范围内就继续否则重新复位AD7606。运行过程中如果连续多次读到全0或全0xFFFF也触发一次复位。复位就是拉高RESET引脚1ms再拉低然后重新初始化SPI。这个逻辑放在主循环的空闲时间做不影响正常采集。最后说一个我踩过的坑AD7606的RESET引脚不能一直拉高否则芯片一直处于复位状态。有些人初始化时把RESET拉高忘了拉低结果怎么读都是0。记住RESET是高电平复位正常工作时必须是低电平。
返回列表