ARTICLE DETAIL

资讯详情

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

STM32驱动AD7606的SPI模式实现:接线、DMA读取与校准

STM32驱动AD7606的SPI模式实现:接线、DMA读取与校准 简介基于STM32F103微控制器的AD7606与AD7608驱动工程包面向嵌入式软硬件开发及单片机数据采集场景旨在解决多通道高精度同步模数转换器的SPI通信配置与迁移问题。压缩包共包含567个文件约2.06MB除了配套的超文本说明文档和电路图图片外还含有C与H源文件、汇编启动文件、集成开发环境工程文件、PDF芯片手册、可直接烧录的十六进制文件以及批量烧录与清理脚本兼顾文档查阅、引脚连线查看、编译调试与固件烧写。例程围绕SPI工作模式展开完整覆盖串行时钟与引脚功能配置、片选信号控制、数据收发、转换完成中断以及电源管理等关键环节同时保留定时器、复位时钟、模数转换等标准外设驱动文件整体目录结构与真实产品工程接近适合二次开发或逐模块拆解学习。目前已有1307人学习下载其核心价值在于提供可直接运行和移植的十六位八通道同步采样参考设计帮助开发者快速验证数据采集链路、缩短调试周期。1. 为什么AD7606驱动选SPI模式而不是并行总线拿到一个“V4-120b_AD76068通道16位同步ADCspi模式例程”的工程第一反应通常是AD7606明明有并行总线为什么偏要用SPI并行模式一次读16根数据线速度上限更高但在STM32F103这种LQFP48、LQFP64封装上16根数据线加控制线基本把GPIO耗尽电机驱动、视觉定位这类还要同时管编码器、通信和传感器的板子根本腾不出这么多脚。SPI模式用4根线把8个通道的转换结果依次移出来读取时间变长但在200kSPS采样率下完全够用PCB布线也干净得多。真正难的不是接线而是片选时序、转换完成标志和连续采样的配合。这篇博文就按“硬件规划 → SPI初始化 → 轮询读取 → DMA提速 → 校准验证”的顺序把例程里能直接抄的部分拆开讲。2. 从引脚电平到GPIO分配AD7606串行模式接线与F103引脚规划2.1 用PAR/SER/BYTE引脚把AD7606切进串行模式AD7606的接口模式不是软件寄存器配置的而是靠PAR、SER、BYTE三个引脚的电平组合决定。这三个引脚在芯片内部都有下拉电阻上电默认是低电平。要把AD7606切到SPI串行模式需要把SER拉高PAR和BYTE保持低电平。常见做法是在SER引脚上直接接一个10kΩ上拉到3.3VPAR和BYTE通过0Ω电阻接到GND这样即使程序没来得及配置GPIO芯片上电后就处于正确的串行模式。模式PARSERBYTE说明16位并行100数据线D0~D15全部占用8位并行001分高低字节两次读取SPI串行010本文使用的模式4线读取和并行模式相比SPI模式最直观的优势是IO占用从20个左右降到8个以内而且SCLK由F103主动产生不存在并行总线那种“地址线和数据线建立时间不够”的问题。代价是读8个通道需要串行移出128个bitSCLK频率相同的情况下读取耗时是并行的16倍但这个工程里F103跑72MHzSPI1配置到9MHz时8通道读取也就14μs左右远小于200kSPS对应的5μs单个采样周期要求——注意这里说的是“读取耗时”和“采样周期”的关系。真正制约连续采样率的是转换时间典型3μs内加上读取时间实际跑100kSPS以内很从容。2.2 接线表和GPIO初始化代码SPI接口选择上我习惯用SPI1而不是SPI2原因是SPI1挂在APB2总线时钟源72MHz分频后能拿到更细腻的SCLK档位SPI2挂在APB1最高36MHz分频比选择少。下面的接线表是一个在最小系统板上可直接落地的分配方式。信号方向F103引脚GPIO配置SCLK输出PA5复用推挽输出50MHzMISO输入PA6浮空输入MOSI输出PA7复用推挽输出50MHzCS输出PA4通用推挽输出50MHzCONVST输出PC0通用推挽输出BUSY输入PB1浮空输入后续做EXTIRESET输出PC1通用推挽输出上电复位用RANGE输出PC2通用推挽输出选择±5V或±10VPA4做软件片选是默认选择因为SPI1的硬件NSS默认在PA4但这里我们不用硬件NSS自动控制而是GPIO手动拉低拉高。BUSY放在PB1是为了接EXTI1方便后续第4章做中断方式读取。GPIO初始化用标准外设库写出来是这样GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); /* SPI1_SCK 和 SPI1_MOSI 配置为复用推挽输出 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* SPI1_MISO 配置为浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); /* 软件片选 CS使用 PA4 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* 控制引脚 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure);MISO配置成浮空输入是因为AD7606的MISO输出由VDRIVE电平决定F103的输入侧不需要内部上拉浮空输入反而避免上拉电阻影响信号边沿。SCLK和MOSI配置成复用推挽是因为这两个引脚由SPI外设控制不是普通IO口翻转。CS和CONVST这些控制脚是纯软件操作用通用推挽输出就够了。GPIO_Speed选50MHz是给信号边沿留余量SPI时钟到9MHz时如果边沿太缓长线传输容易判错电平。2.3 电源和参考电压的处理AD7606的模拟供电AVCC建议5V数字接口供电VDRIVE接3.3V两路电源都要加0.1μF和10μF去耦电容且电容要尽量靠近芯片电源引脚。VDRIVE很关键它决定SPI接口输出高电平的电压VDRIVE接3.3V时MISO输出就是3.3V电平F103的引脚可以直接读取。反过来如果VDRIVE接了5V而F103是3.3V供电PA6作为输入虽然多数情况下能容忍5V但这依赖引脚的FT属性不是一个应该依赖的设计。REF SELECT引脚选择基准源接高电平用内部2.5V基准接低电平用外部基准。内部基准精度对于一般数据采集够用如果要做精密测量REF SELECT拉低后在REF引脚外部加高精度基准源比如ADR421或REF192成本不高但对零漂改善明显。上电后最好对RESET引脚做一次低脉冲复位避免芯片处于不确定状态。3. 标准外设库下的SPI初始化与8通道数据读取实现3.1 SPI1主模式参数与库函数配置SPI的初始化参数决定了通信是否稳定先看代码再逐个解释SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);SPI_DataSize选16b而不是8b是因为AD7606单个通道转换结果是16位串行模式下每个通道输出16bit16位帧一次读一个通道正好和DMA的HalfWord传输对齐。SPI_CPOL_Low表示SCLK空闲时低电平AD7606串行读时序中SCLK空闲就是低电平SPI_CPHA_2Edge表示数据在第二个边沿采样和AD7606手册里串行模式的读数时序匹配。如果读取结果每位都错位通常就是CPHA反了把2Edge改成1Edge再看数据。波特率分频这里用8SPI1时钟源72MHz除以8得到9MHz的SCLK。AD7606串行模式下SCLK最高典型值在16MHz以上9MHz留了足够裕量同时保证8通道128bit的读取时间在15μs左右。SPI_NSS必须用Soft用Hardware的话每传输一帧NSS都会被硬件自动拉高拉低而AD7606要求整个8通道读取过程CS保持低电平硬件NSS没法满足这个时序。习惯用CubeMX生成工程的读者在SPI1配置页里对应选择Master、16 Bits、CPOLLow、CPHA2 Edge、Software NSS生成的HAL代码底层参数和上面完全一致。3.2 触发转换并按通道顺序读取AD7606的转换流程是CONVST引脚产生一个由低到高的脉冲芯片检测到上升沿后开始对8个通道同时采样并转换转换期间BUSY引脚拉高转换结束后BUSY回到低电平。软件上必须先等BUSY变低再发起SPI读取否则读到的可能是上一轮数据或者是转换中间的不稳定值。完整读取函数如下#define AD7606_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define AD7606_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) #define AD7606_SET_CONVST() do { GPIO_SetBits(GPIOC, GPIO_Pin_0); \ GPIO_ResetBits(GPIOC, GPIO_Pin_0); } while(0) #define AD7606_BUSY_LEVEL() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) int16_t adc_data[8]; void AD7606_ReadAll(int16_t *buf) { uint8_t i; AD7606_SET_CONVST(); /* CONVST 上升沿触发转换 */ while (AD7606_BUSY_LEVEL() 1); /* 等待转换完成 */ AD7606_CS_LOW(); /* CS 保持低开始读出 */ for (i 0; i 8; i) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, 0x0000); /* 主模式发哑数据产生SCLK */ while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); buf[i] (int16_t)SPI_I2S_ReceiveData(SPI1); } AD7606_CS_HIGH(); }CONVST这里用宏简化成一组SetBits和ResetBits实际上GPIO翻转速度很快这个脉冲宽度已经满足AD7606对CONVST高电平最小脉宽的要求。如果放在while循环里反复调用建议在SetBits后加几条空指令或直接操作BSRR寄存器确保上升沿干净。等待BUSY的部分在轮询模式里是死等简单但会阻塞主循环这个限制到第4章用EXTI解决。SPI主模式下接收数据必须有发送动作才能产生SCLK所以每次读之前先发送0x0000哑数据。发送完成后TXE标志置位芯片内部移位寄存器开始输出SCLK同时RXNE标志在接收到16bit后置位这时读DR寄存器就是该通道的转换结果。整个读取过程中CS必须从第1个通道的SCLK之前拉低一直到第8个通道读完后才能拉高中间任何一次SCLK跳变时CS为高都会让AD7606的串行输出中止或移位错乱。buf[i]强制转换成int16_t是因为AD7606输出的是二进制补码最高位是符号位直接用uint16_t接收后面换算电压还要多做一步符号扩展。3.3 16位补码与±5V量程换算AD7606的RANGE引脚决定模拟输入量程RANGE为低时输入范围±5V为高时±10V。内部基准2.5V时对应关系是0V输入读到0x0000正满量程读到0x7FFF负满量程读到0x8000。换算公式为float adc_voltage(int16_t raw, float full_scale) { return (float)raw / 32768.0f * full_scale; }full_scale取5.0还是10.0取决于RANGE引脚电平。这个公式用32768作分母是因为16位补码的正向最大值是32767满量程定义在±FSR。实际测量中零点附近会有几个LSB的偏移这是正常现象精度要求高的场合需要做第5章的两点校正。压缩包里如果翻到stm32f10x_adc.c和stm32f10x_rcc.c可以看出例程是基于STM32F103标准外设库3.5版本搭的cstart_thumb2.asm是启动文件CopyHex_Flash.bat这类脚本只是把编译产物拷贝到烧录工具目录核心逻辑都在应用层代码里移植时把SPI引脚按自己的板子改一下即可。4. 连续采样提速SPI DMA搬运与BUSY中断衔接4.1 BUSY与EXTI结合避免轮询卡死主循环上一章的读取函数在等待BUSY时是死等如果主循环里还有其他任务比如通信协议处理、显示刷新、电机控制每次转换加读取要占掉十几微秒到几十微秒连续高采样率下主循环基本跑不动。常见做法是把BUSY接到EXTI输入用下降沿中断通知CPU“转换完成可以读数了”。EXTI_InitTypeDef EXTI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); EXTI_InitStructure.EXTI_Line EXTI_Line1; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);EXTI_Trigger用下降沿因为BUSY高电平表示转换中回到低电平才是转换完成。中断优先级这里给了2如果系统中还有定时器触发采样优先级分配要考虑清楚定时器优先级高于EXTI保证采样间隔准确EXTI高于SPI读取完成中断保证转换完成能及时处理。进入EXIT1_IRQHandler后在主循环里置一个标志或者直接在中断里做轻量级处理但不要在中断里做耗时运算。4.2 用DMA把8个半字搬进内存单通道读取时每读一个16bit都要检查TXE和RXNE标志8个通道就是16次标志检查CPU开销不小。改成DMA后读取过程变成启动发送DMA和接收DMAF103自动产生SCLK并把接收到的数据写入内存数组全部完成后触发DMA中断。SPI1的接收DMA通道是DMA1_Channel2发送DMA是DMA1_Channel3。DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* 接收DMASPI1-DR 到 adc_data */ DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_data; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 8; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel2, DMA_InitStructure);DMA_DIR是外设到内存PeripheralBaseAddr指向SPI1-DR而不是某个变量地址因为SPI数据寄存器地址固定且每次读取后硬件自动清RXNE。BufferSize等于8表示一次传输8个HalfWord对应8个通道。发送侧需要准备一个静态哑数据数组同样配置DMA1_Channel3做内存到外设传输方向和接收相反。传输完成中断里把CS拉高同时置一个数据处理标志void DMA1_Channel2_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC2)) { DMA_ClearITPendingBit(DMA1_IT_TC2); AD7606_CS_HIGH(); /* 8通道读完释放片选 */ adc_ready_flag 1; /* 主循环处理数据 */ } }DMA_Mode这里用Normal而非Circular是因为每次转换完成后CS要拉高下一轮转换重新触发DMA需要重新配置BufferSize才能再次启动。如果系统追求极致吞吐率可以把DMA配成Circular模式并配合定时器周期性触发CONVST但要注意Circular模式不会自然停下来CS控制时序要做额外处理复杂度上升不少普通数据采集场景Normal模式更可控。4.3 连续采样场景下容易踩的四个坑第一个坑是SPI硬件NSS。初始化里SPI_NSS如果配成HardwareSPI外设会在每帧传输前后自动控制NSS引脚AD7606的CS会每16bit跳动一次读出来的数据完全错乱。一定要配SPI_NSS_SoftCS完全用GPIO控制。第二个坑是F103的SPI2挂在APB1时钟源只有36MHz如果用的是SPI2且分频比照搬SPI1的8分频实际SCLK只有4.5MHz读取时间翻倍。所以代码里要区分SPI1还是SPI2计算分频系数时先确认外设时钟源是72MHz还是36MHz。第三个坑是16位帧的字节序F103 SPI默认MSB先行AD7606串行输出也是从DB15开始方向一致不用调整如果换到其他MCU平台要先确认硬件SPI的高位在前配置否则高低字节会交换。第四个坑是JTAG引脚冲突PB3、PB4默认是JTAG功能如果CS或片选复用到了这两个引脚程序里要调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)释放引脚否则配了GPIO也不起作用。5. 时序验证与两点校准把AD7606原始码值变成可信电压5.1 用逻辑分析仪检查CS和SCLK关系程序跑通后先别急着算电压用逻辑分析仪挂CS、SCLK、BUSY三根线采样率设到50MHz以上看四个关键点CS拉低后到第一个SCLK上升沿有没有足够的建立时间整个读取过程CS是否持续为低SCLK总共是不是恰好128个边沿BUSY下降沿到CS拉低之间是否有间隙。如果SCLK边沿数不对多半是SPI数据位配置成了8位如果CS在读取过程中有抖动检查是不是NSS_Soft没配成功。调试串口每轮打印帧头能辅助判断数据是否对齐。5.2 零点与增益两点校正AD7606本身没有可编程增益寄存器零点和增益误差靠外部校准。把8个通道输入全部接地各读100次取平均得到零点偏移值zero[i]。再将通道输入接到稳定的2.5V基准源读100次取平均计算增益系数gain[i] 2.5f / (float)(avg_raw_2v5[i] - zero[i]); /* 校正后电压 */ float v_cal (float)(raw - zero[i]) * gain[i];AVCC用5V开关电源供电时零点偏移通常在±10 LSB以内增益误差在±0.5%左右。如果校正后数值跳动超过几十个LSB优先检查AVCC纹波和REF引脚去耦电容而不是继续调软件。没有精密基准源时可以用一个低温漂电阻分压从5V取2.5V精度要求不高的场合勉强可用。5.3 通道间串扰自查多通道同步采样的典型问题是相邻通道串扰。把信号只接到CH1其余通道接地观察CH2读数是否有明显跟随CH1变化。串扰来源通常是模拟输入走线平行过长、去耦电容离芯片太远、地平面不连续。Layout上CH1和CH2之间保持地线隔离V1~V8输入串联的RC滤波电阻电容尽量靠近AD7606引脚AGND和DGND在芯片下方单点连接。几次测试下来如果相邻通道串扰幅度小于1~2 LSB这套驱动方案就可以直接进量产。本文还有配套的精品资源点击获取
返回列表