ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32模拟SPI驱动TLC3578多通道ADC采集与校准实现

STM32模拟SPI驱动TLC3578多通道ADC采集与校准实现 简介针对TLC3578 8通道12位ADC芯片在±10V输入下的STM32模拟SPI驱动问题这份资料提供了完整可用的工程代码与说明文档。资料面向具备一定STM32基础、需要多路模拟量采集的嵌入式开发者涵盖GPIO模拟SPI时序、命令配置、数据读取及工程移植方法。压缩包共152个文件以C源码、H头文件、编译生成的O目标文件及D调试信息为主同时包含PDF说明文档、Keil工程文件uvprojx、HEX烧录文件等整体大小仅4.35MB便于快速下载与参照调试。已有324人学习使用适合作为工业控制、数据采集等场景的ADC驱动参考方案。资料中的TLC3578驱动程序可直接引用结合ADC采集PDF可帮助理解芯片工作时序有效缩短从硬件连接到底层驱动的开发周期。1. TLC3578 的模拟 SPI 驱动是在没有硬件 SPI 资源时最稳的方案TLC3578 是一片 8 通道、12 位分辨率、支持 ±10V 输入的 ADC虽然不像新出的 24 位 Sigma-Delta 器件那么亮眼但在工业控制、多路仪表采集这类对通道数和绝对精度有要求的场合它仍是低成本可拿到的成熟方案。这次拆解的例程用的是 STM32F103 标准外设库代码里没有启用 SPI1/SPI2 硬件外设而是用 PA4~PA7 四个普通 GPIO 模拟 SPI 去驱动 TLC3578。这种做法的价值在于当板子上 SPI 资源被 Flash、显示模块占用或者你只是想快速验证 ADC 时序时模拟 SPI 可以完全按数据手册自定义帧格式不依赖芯片的 SPI 外设配置。适合正在做多路模拟量采集、又不想动 PCB 的工程师参考。2. TLC3578 的 SPI 时序与模拟 SPI 的 GPIO 实现2.1 为什么不用 STM32 内置 SPI 而选择模拟TLC3578 的串口帧和普通 SPI 外设的“一字节一字节”模型不完全一致。命令字加上随后读回的数据至少要在一个片选周期里连续跑 32 个时钟中途所有数据 bit 的时间空隙都要由软件控制而不能依赖 SPI 外设的自动字节间隔。STM32 内置 SPI 也能做到连续传输但要小心 CPOL/CPHA 相位设置、数据帧格式、NSS 管理和模拟 SPI 相比反而容易出错。尤其是当 SPI2 同时挂在外部 Flash 上改一次相位配置就可能影响另一个器件在旧项目里为了不引入回归问题很多驱动会直接保留 GPIO 模拟 SPI。TLC3578 是数据转换器件不是简单的寄存器配置。I2C 那种带地址应答的半双工方式并不适合它SPI 的全双工边发边收才是更自然的模型。这里提一下 iic 和 spi 的区别I2C 靠地址和 ACK 管理多个从机速度上限低适合读写配置寄存器TLC3578 这种按通道连续输出转换结果的 ADC用 SPI 可以每来一个时钟就推出一位结果时序确定性强。模拟 SPI 不是倒退而是因为你需要控制的不是波特率而是命令字边界和采样点位置。2.2 引脚映射和 GPIO 宏封装以一块典型的 STM32F103 板子为例我习惯把四个引脚放在同一个 GPIOA 上方便统一配置时钟和检查状态。下表是参考映射不是 TLC3578 的强制要求。信号STM32 GPIO方向说明SCLKPA5输出模拟 SPI 时钟SDINPA7输出命令字输入MOSISDOUTPA6输入转换结果输出MISOCSPA4输出片选低有效把电平操作封装成宏是模拟 SPI 驱动里第一个值得养成的习惯。后续写业务代码时不必频繁查找 GPIO 定义硬件改版时也只需替换宏定义不用改采集逻辑。#define TLC3578_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define TLC3578_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) #define TLC3578_SCLK_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define TLC3578_SCLK_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_5) #define TLC3578_SDIN_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_7) #define TLC3578_SDIN_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_7) #define TLC3578_SDOUT_READ() GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)这里把 CS/SCLK/SDIN 配置成输出SDOUT 配置成输入。宏定义要和控制逻辑放在同一个模块里不要分散在多个 C 文件不然改板时容易漏改。还有硬件片选与软件片选的区别硬件 NSS 常被外设自动拉低软件片选则完全由你决定拉低时机。TLC3578 这类需要按通道切换的 ADC用软件片选更直观CS 低电平窗口把一次命令和数据读周期包住不会因为外设自动控制 NSS 而出错。2.3 模拟 SPI 的时钟边沿和数据采样点模拟 SPI 的核心是确定 SCLK 上升沿和下降沿分别做什么。通常做法是SCLK 上升沿前把 SDIN 设置好SCLK 上升沿让 TLC3578 锁存命令SCLK 下降沿后、下一个上升沿前先等待一段时间再读取 SDOUT保证 SDOUT 已经稳定。static void TLC3578_Delay(void) { volatile uint32_t i; for (i 0; i 20; i) { __NOP(); } }这个 Delay 函数没有精确到微秒它只是提供一个可软件调整的半周期延时。72MHz 主频下循环 20 次大约是百纳秒量级和 1MHz SPI 时钟需求差不多。实际应该用示波器测 SCLK 频率如果 SCLK 高电平宽度太窄就把循环次数调大如果整个 8 通道一轮采样太慢就调小。注意__NOP()是内核空操作编译时不能优化掉所以要配合volatile保证循环存在。模拟 SPI 最怕 Delay 被中断打断导致 SCLK 高低电平不均匀。我一般会在传输期间关闭可屏蔽中断或者至少保证 Delay 函数不会被频繁抢占。否则逻辑分析仪上会看到某一拍特别长ADC 虽然大概率还能读但采样时刻的一致性就变差了。2.4 模拟 SPI 和硬件 SPI 的边界怎么划硬件 SPI 不是不能用而是在什么情况下更合适。假如系统里有多片支持标准 SPI 的设备且 TLC3578 的数据手册时序和 STM32 硬件 SPI 完全匹配使用硬件 SPI 加 DMA 当然能节省 CPU。但 TLC3578 在 16 位命令后紧接着读 16 位数据如果用硬件 SPI你往往要配置成 16 位数据帧、关闭软件 NSS手动维护 FIFO。只要一个参数不对读回来的数据就会少一拍或错半字节。模拟 SPI 的优势是每一拍都可见逻辑分析仪抓到什么就是什么所以调试初期先用模拟 SPI 打通再决定是否移植到硬件 SPI这个顺序能省下大量排查时间。3. STM32 驱动 TLC3578 的完整采集流程3.1 GPIO 初始化初始化代码放在标准外设库的工程里关键是先把涉及到的外设时钟打开再分别设置输出和输入引脚。void TLC3578_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; 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_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); TLC3578_CS_HIGH(); TLC3578_SCLK_LOW(); }PA4、PA5、PA7 设置成推挽输出PA6 设置成带上拉的输入是为了避免 SDOUT 浮空时读到随机电平。CS 默认拉高、SCLK 默认拉低确保 TLC3578 上电后不会因为引脚电平不确定而进入错误状态。换成 HAL 库时只需要把GPIO_Mode_Out_PP改成GPIO_MODE_OUTPUT_PPGPIO_Mode_IPU改成GPIO_MODE_INPUT并设置上拉初始化逻辑完全一样。3.2 命令字组帧与 16 位传输TLC3578 的通道选择在命令字的高位。我习惯在例程里这样组帧实际位域以你拿到的手册 Revision 为准bit15 作为启动转换标志bit14:12 放通道号低 12 位补 0。这样看起来直观也方便用宏展开。bit15bit14:12bit11:01启动转换CH2~CH0 通道号保留置 0先在头文件里把位操作定义好。#define TLC3578_CMD_START (1 15) #define TLC3578_CMD_CH(ch) (((ch) 0x07) 12)发送和接收合在一起因为 SPI 是同时进行的。下面的函数一次传输 16 位发送命令的同时把 SDOUT 上返回的位读进 rx。uint16_t TLC3578_Transfer(uint16_t cmd) { uint16_t rx 0; uint8_t i; for (i 0; i 16; i) { if (cmd (1 (15 - i))) { TLC3578_SDIN_HIGH(); } else { TLC3578_SDIN_LOW(); } TLC3578_SCLK_HIGH(); TLC3578_Delay(); rx 1; if (TLC3578_SDOUT_READ()) { rx | 1; } TLC3578_SCLK_LOW(); TLC3578_Delay(); } return rx; }逻辑说明发送位从 bit15 开始依次到 bit0高位先出符合大多数 SPI ADC 的 MSB-first 约定。每个 bit 位准备好 SDIN 后把 SCLK 拉高等待延时再读 SDOUT读到的结果从低位移入 rx。返回的 rx 既包含命令也包含 SDOUT 在当前帧输出的数据。有效数据是哪几个 bit要看这次传输是片选拉低后的第几个 16 位窗口。3.3 等待转换并读取结果TLC3578 在收到启动命令后需要一段转换时间不同供电和基准配置下不太一样。例程里先做一个阻塞延时再发起第二次 Transfer 读取数据这是最通用也最容易排除问题的写法。void TLC3578_DelayMs(uint32_t ms) { volatile uint32_t i, j; for (j 0; j ms; j) { for (i 0; i 7200; i) { __NOP(); } } } uint16_t TLC3578_ReadChannel(uint8_t ch) { uint16_t cmd TLC3578_CMD_START | TLC3578_CMD_CH(ch); uint16_t data; if (ch 7) { ch 7; } TLC3578_CS_LOW(); TLC3578_Transfer(cmd); TLC3578_DelayMs(1); data TLC3578_Transfer(0x0000); TLC3578_CS_HIGH(); return data 0x0FFF; }这里把 CS 低电平期间分成两段第一段发送启动转换命令第二段送一个空命令并同步读回结果。TLC3578_DelayMs(1)是保守的等待时间不是最优值。如果数据手册标的是 2us就可以把这个延时缩短为几次半周期延时8 路采样周期会明显快很多。return data 0x0FFF表示只取低 12 位有效值因为 TLC3578 是 12 位 ADC高 4 位在本例程中没有参与电压计算。如果数据手册里结果是右对齐这个写法就是对的如果实际是左对齐需要改成data 4。不确定时先用 0V 输入验证0V 对应码值应该接近 2048而不是 0 或 32768。3.4 8 路轮询与采样周期估算8 路轮流采集就是循环调用TLC3578_ReadChannel。void TLC3578_ScanAll(uint16_t raw_value[8]) { uint8_t ch; for (ch 0; ch 8; ch) { raw_value[ch] TLC3578_ReadChannel(ch); } }这个循环的问题是每通道都有 1ms 延时8 路全跑完约 8ms。如果只要 100Hz 更新率完全够用如果要做工频同步采样就要把延时降到几十微秒或者改用多通道同时采样。采样周期的估算公式是每通道两次 16bit 传输共 32 个 SCLK加上片选切换和转换等待时间。假设模拟 SCLK 为 1MHz一次传输约 32us8 路固定开销约 0.3ms主要时间都花在等待转换和函数调用上。所以想提升采样率优先改延时而不是去优化移位循环。4. ±10V 输入范围下的数据换算、校准与漂移排查4.1 从 12 位码值换算到真实电压TLC3578 的 ±10V 输入范围对应 0~4095 码值零输入对应 2048 附近。如果把 4096 个码均匀映射到 20V 区间每个码约 4.88mV。换算函数可以写成float TLC3578_RawToVoltage(uint16_t raw) { return (raw * 20.0f / 4095.0f) - 10.0f; }这里除以 4095 而不是 4096是因为满量程码 4095 对应 10V。若基准源或前级运放有增益偏差这个线性系数还要通过校准修正。注意输入电压千万不要超过手册规定的极限范围否则片内保护二极管会长期承受电流精度和寿命都会受影响。码值理论输入电压0-10.00 V1024-5.00 V20480.00 V30725.00 V409510.00 V4.2 外部 ADC 三点校准怎么落到代码里12 位 ADC 的典型误差是零点偏移和增益误差这两项用两点校准就能修但实际工程中我还加了一个中间点用来观察线性度是否满足要求。三点校准的做法是用高精度信号源分别给 -10V、0V、10V记录每个通道读数然后做分段线性查表。校准结果保存到结构体里运行时在采集线程中调用。typedef struct { int32_t code[3]; float voltage[3]; } TLC3578_CalTable; float TLC3578_CalibratedVoltage(TLC3578_CalTable *cal, uint16_t raw) { uint8_t i; if (raw cal-code[0]) { return cal-voltage[0]; } for (i 1; i 3; i) { if (raw cal-code[i]) { float k (cal-voltage[i] - cal-voltage[i - 1]) / (cal-code[i] - cal-code[i - 1]); return cal-voltage[i - 1] k * (raw - cal-code[i - 1]); } } return cal-voltage[2]; }校准的是 ADC 链路的整体增益误差包括分压电阻、运放偏置和 ADC 自身。校准表和实际输入范围有关如果前端改过电阻表要重新标定。三点校准不是万能的它修不了 12 位以下的噪声如果码值跳变超过正负 3 LSB先解决噪声再校准。对于多通道系统每个通道都要单独做一遍校准不能用一个通道的系数去补偿另一个通道。4.3 采集值漂移、毛刺和 SPI 干扰的处理外部 ADC 和 MCU 共地时容易出现地环路表现为读数随负载电流波动。模拟地、数字地单点连接是最常见做法如果板子已经定型可以在软件上做中值滤波。下面的函数取三次采样的中值能滤掉大部分窄脉冲干扰又不影响响应速度。uint16_t TLC3578_MedianFilter(uint16_t a, uint16_t b, uint16_t c) { uint16_t tmp; if (a b) { tmp a; a b; b tmp; } if (b c) { tmp b; b c; c tmp; } if (a b) { tmp a; a b; b tmp; } return b; }这段代码就是经典三数排序求中值三次比较后 b 是中位数。使用场景是某个通道读数偶尔跳到满量程大概率是 SPI 线耦合进脉冲或 MCU 中断把总线时序拉偏加中值滤波比均值滤波更合适。如果漂移是缓慢变化的比如开机半小时后读数慢慢往上走优先怀疑基准芯片温漂软件滤波只是掩盖问题。还有一类采样值异常来自 ADC 输入采样电容没有充分建立。如果某个通道前一次接高电压下一次接低电压等待时间短就会看到值向高偏移。解决方案是每个通道多读一两次并丢弃第一次或者把片选和 SCLK 之间的延时放大。工频干扰引起的读数波动则可以用整周期采样结合滑动平均来处理不要在 50Hz 设备的布线上直接做高阻抗长走线。5. 用逻辑分析仪验证 TLC3578 模拟 SPI 时序并定位通道错位验证模拟 SPI 驱动我第一件事不是看电压而是用逻辑分析仪同时抓 CS、SCLK、SDIN、SDOUT 四路信号。逻辑分析仪采样率设到 25MHz 以上触发条件配成 CS 下降沿然后只读一个通道。正常波形里CS 拉低后先出现 16 个 SCLK 脉冲SDIN 上依次出现命令字的高位到低位随后经过转换延时再出现 16 个 SCLK 脉冲SDOUT 在这段时间里逐位输出转换结果。如果看到 CS 低电平期间的 SCLK 数量不是 16 的整数倍检查发送函数里有没有被别的代码打断。模拟 SPI 的 Delay 是基于循环的中断一进来 SCLK 高电平时间就会被拉长但不会少脉冲少脉冲多半是循环变量被编译优化掉了或者 while 条件写错。若 SDOUT 不变化先确认 PA6 输入模式有没有配置正确再用杜邦线把 SDOUT 直接拉高或拉低看读回来是否全 1 或全 0。这样能迅速区分是引脚接错还是芯片没有输出。通道错位也是常见问题。把所有通道短接到 0V正常读数应该在 2048 附近。如果 CH0 读数等于 CH1说明命令字里的通道号位移错了位。例程里用TLC3578_CMD_CH(ch)左移 12 位如果实际手册要求左移 11 位或 13 位就会出现错位。另一个检查方法是只给某一通道接 5V其他通道悬空或接 0V连续打印 8 个通道的值观察高电平出现在哪个位置。出现高位数据串位时不要急着改硬件先算一下当前左移位数和手册要求差几位再回头改宏定义。除了抓时序我还习惯在代码里加一个专门的自检通道。把 TLC3578 的某一输入引脚通过 1k 电阻连接到一块精密参考电压上上电后周期读这个通道并与参考值做差。如果差值超出设定阈值就认为模拟电路或 SPI 时序出了问题。这种检查不需要额外仪器现场维护时比较有用。等自检通过后再关掉校准代码剩下的就是根据实际采样周期去调整延时参数和滤波强度。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表