简介:针对STM32开发者,这份资源围绕TLC3578模数转换芯片,提供基于模拟SPI的驱动方案,解决8通道±10V信号采集与单片机通信问题。资源共152个文件,整体4.35MB,包含H/C源文件、Keil工程文件、PDF说明文档、编译生成的HEX与AXF文件等。其中C驱动代码覆盖初始化、通道切换、读数解析等关键逻辑,可直接移植;PDF文档详细讲解模拟SPI时序与寄存器配置,适合需要多路高电压ADC采样的工业控制、数据采集系统开发者。目前已有324人学习下载。内含完整Keil工程与编译输出文件,便于结合硬件调试,快速验证±10V输入下的采样精度,减少底层时序摸索成本。对于希望理解GPIO模拟SPI实现方式的读者,驱动代码也给出了可参考的时序控制写法,有助于移植到其他ADC芯片或平台,提升项目复用性。
1. TLC3578 在 STM32 上做 8 路 ±10V 采集,为什么绕不开模拟 SPI
TLC3578 最吸引人的地方,是它把工业现场最常见的 ±10V 信号直接接进芯片输入,而不是让你在前端运放电路上折腾半天。8 路通道、14 位分辨率、SPI 串行接口,这三个条件凑齐之后,剩下的问题就变成:STM32 怎么把这颗芯片读干净。很多工程师第一反应是挂硬件 SPI,但实际项目里 SPI1 被 Flash 占用、SPI2 接了屏的例子太常见,为了一颗采样率并不高的 ADC 专门腾出硬外设,反而让 PCB 走线和固件解耦都变扭。模拟 SPI 用四根普通 GPIO 复制出时序,几百 kbit/s 的速度对 TLC3578 绰绰有余,而且时序完全受控,出问题可以直接拿逻辑分析仪量 GPIO。这篇就把驱动写法和 ±10V 前端的计算按工程顺序讲清楚。
2. TLC3578 的 SAR 采样周期、预充电输入与 SPI 帧结构
2.1 14 位 SAR 内核决定采样周期,软件 SPI 只管沿
TLC3578 属于 SAR,即逐次逼近寄存器型 ADC,内部由采样保持电容、比较器和电容 DAC 组成,一次转换要经过 14 次逐位逼近。SAR 型 ADC 与积分型、Sigma-Delta 型最大的区别是采样周期短、功耗低,没有复杂的数字滤波器,但输入端的建立时间必须由前级电路保证。对 STM32 来说,软件模拟 SPI 不必关心内部逼近过程,只需要保证两件事:写命令时沿对齐正确,读取时在 SDO 稳定之后采样。TLC3578 的 SCLK 由主机给出,所以 SPI 速率完全由你翻 GPIO 的速度决定,和芯片内部时钟无关。
模拟 SPI 的采样周期上限通常不取决于 GPIO,而取决于转换时间和预充电电容。GPIO 翻一个沿大约 2 到 3 条指令,四根线挪完一个字节不过十几微秒,ADC 本身转换还要几微秒,整体瓶颈永远在信号源阻抗和前端电容上。这个结论在 4.2 节会拿具体参数算一遍。
2.2 预充电输入怎么把 ±10V 收进来
TLC3578 一个容易被忽略的设计是预充电输入拓扑。芯片内部不是直接把多路开关接到采样电容,而是先通过外部电容把输入信号预充到内部电容上。引脚上有多个 CAP 引脚,8 路通道分成 4 组。这个设计的好处是输入等效电容很小,信号源不用推动很大的容性负载,而且多路开关切换时不会把上一通道的电荷留给下一通道。代价是外部电容取值敏感:太小则预充电不足导致读值偏小,太大会拉长建立时间。
±10V 能进芯片,依赖的是输入路径上没有低压限幅,真正把电平限制在 ADC 内核量程是靠外部电阻网络。也就是说,芯片引脚耐压可以,但内核量程仍然跟着基准走。工程上通常采用分压加偏置把 ±10V 线性映射到 0 到 5V,这个网络的设计放到第 4 章讲。这里先记住一个原则:源阻抗和预充电电容会构成 RC,建立时间近似按 10 倍时间常数估算,源阻抗超过 10kΩ 时要格外小心。
2.3 8 位命令字和 16 位结果帧的位结构
TLC3578 的串行帧由两段组成:首先通过 SDIN 移入 8 位命令字,随后从 SDOUT 移出 16 位转换结果。命令字高位在前,通道地址占高 3 位,接着是模式位,最后是范围与过采样控制位。建议映射如下表,实际使用前务必和具体型号数据手册核对。
| 位段 | 名称 | 值含义 |
|---|---|---|
| bit7~bit5 | A2~A0 | 通道地址,000=CH0,001=CH1,直到 111=CH7 |
| bit4~bit3 | M1~M0 | 00 待机,01 单次转换,10 自动轮询模式 |
| bit2 | 范围位 | 0 正常量程,1 扩展量程 |
| bit1 | 过采样使能 | 置 1 启用片内过采样或均值 |
| bit0 | 保留位 | 必须写 0 |
结果帧是 16 位:前 2 位是状态或前导位,后面 14 位是转换结果。读取时要把两次字节交换拼成 uint16_t,再对 0x3FFF 取掩码。初学最常漏的就是这一步:直接拿整个 16 位当结果,读出来的数值会偏大,而且看起来完全没有规律。
3. STM32 模拟 SPI 驱动 TLC3578:寄存器级核心代码
3.1 引脚定义与寄存器操作宏
模拟 SPI 的第一步是分配四个引脚:CS、SCLK、SDI、SDO。SDI 是 STM32 输出到 TLC3578 的数据线,SDO 是 TLC3578 输出到 STM32 的数据线,名字不要搞反。下面以 STM32F103 的 GPIOA 为例。
/* 引脚分配,按实际板子修改 */ #define TLC_CS_PORT GPIOA #define TLC_CS_PIN GPIO_PIN_4 #define TLC_SCLK_PORT GPIOA #define TLC_SCLK_PIN GPIO_PIN_5 #define TLC_SDI_PORT GPIOA #define TLC_SDI_PIN GPIO_PIN_6 #define TLC_SDO_PORT GPIOA #define TLC_SDO_PIN GPIO_PIN_7 /* 用 BSRR/IDR 直接操作,时序稳定 */ #define TLC_CS_L() (TLC_CS_PORT->BSRR = TLC_CS_PIN << 16) #define TLC_CS_H() (TLC_CS_PORT->BSRR = TLC_CS_PIN) #define TLC_SCLK_L() (TLC_SCLK_PORT->BSRR = TLC_SCLK_PIN << 16) #define TLC_SCLK_H() (TLC_SCLK_PORT->BSRR = TLC_SCLK_PIN) #define TLC_SDI_L() (TLC_SDI_PORT->BSRR = TLC_SDI_PIN << 16) #define TLC_SDI_H() (TLC_SDI_PORT->BSRR = TLC_SDI_PIN) #define TLC_SDO_GET()((TLC_SDO_PORT->IDR & TLC_SDO_PIN) != 0)为什么不用 HAL_GPIO_WritePin?HAL 在每个引脚操作里都要做参数检查加读改写,连续翻电平会产生几十个周期的抖动,SCLK 高电平宽度忽大忽小。模拟 SPI 不是不能用 HAL,但不稳定时第一步先把 GPIO 操作换成寄存器宏。CS 和 SCLK 必须配置为推挽输出,SDI 也是推挽输出,SDO 配置为浮空输入或上拉输入。上拉会引入微弱电流,对高阻前端有影响,一般用浮空输入更干净。
3.2 字节交换函数和 SDO 采样时点
TLC3578 的逻辑是:数据在 SCLK 上升沿被芯片锁存,SDOUT 在下降沿之后改变。主机读取应该在 SCLK 高电平期间采样,而不是在上升沿瞬间采样,否则可能读到上一位的残留电平。
static inline void tlc_delay_half(void) { __NOP(); __NOP(); __NOP(); /* 主频变化时调整 */ } static uint8_t tlc_swap_byte(uint8_t tx) { uint8_t rx = 0; for (uint8_t i = 8; i; i--) { if (tx & 0x80) { TLC_SDI_H(); } else { TLC_SDI_L(); } TLC_SCLK_H(); tlc_delay_half(); rx <<= 1; if (TLC_SDO_GET()) { rx |= 0x01; } TLC_SCLK_L(); tlc_delay_half(); tx <<= 1; } return rx; }逻辑说明:在 SCLK 上升沿前就把 SDI 摆到目标电平,保证芯片能够可靠锁存命令位;SCLK 拉高后等半个周期再读 SDO,这时数据线上已经是最新的输出位。rx 先左移再置位,读到的数据始终落在最低位。如果读出来的通道号错乱或者数值像被拆散,先怀疑字节序反了:把接收处的移位方向反过来,命令字发送则保持高位在前即可。
延时不是越短越好。TLC3578 数据手册对 SCLK 高电平最小脉宽有明确要求,GPIO 实现保证几百纳秒脉宽足够稳定。延时过大会拖慢整轮采集:单通道一次完整帧是 24 个时钟位,按 500kHz SCLK 算约 48 微秒,8 通道轮询一轮不到 400 微秒,对工控采集完全够用。
3.3 单次转换函数与首次读数无效帧
#define TLC3578_MODE_SINGLE (1u << 3) uint16_t tlc3578_read_once(uint8_t ch) { uint8_t cmd = (ch << 5) | TLC3578_MODE_SINGLE; uint8_t hi, lo; uint16_t raw; TLC_CS_L(); tlc_swap_byte(cmd); /* 写入 8 位命令 */ hi = tlc_swap_byte(0); /* 读结果高 8 位 */ lo = tlc_swap_byte(0); /* 读结果低 8 位 */ TLC_CS_H(); raw = ((uint16_t)hi << 8) | lo; return raw & 0x3FFF; /* 丢弃前导 2 位 */ }CS 拉低期间连续送出 24 个时钟,前 8 个时钟把命令移入芯片,后 16 个时钟把转换结果一位位移出来。tlc_swap_byte(0) 表面看是发送 0,实际功能是利用时钟边沿读取 SDO;主机 SDI 同时写 0,不会影响已经锁存的命令。
第一次调用返回的是上电随机值,正确做法是开机后空跑一次,或者连续读两次丢弃第一帧。很多现象表现为“第一次读总是异常,后面正常”,并不是芯片坏,而是帧对齐还没建立。
3.4 8 路轮询与滑动均值
工业采集里单次读回来的原始码不能直接用,一是电源纹波会耦合进 ADC 结果,二是多路开关切换瞬间有残压。常用做法是每通道连续读多次取平均,下面这个函数把滤波和轮询合在一起。
#define TLC_CH_COUNT 8 #define TLC_FILTER_N 16 uint32_t tlc3578_read_filtered(uint8_t ch) { uint32_t sum = 0; for (uint8_t i = 0; i < TLC_FILTER_N; i++) { sum += tlc3578_read_once(ch); } return sum / TLC_FILTER_N; } void tlc3578_scan_all(uint16_t out[TLC_CH_COUNT]) { for (uint8_t ch = 0; ch < TLC_CH_COUNT; ch++) { out[ch] = (uint16_t)tlc3578_read_filtered(ch); } }16 次平均后,单通道耗时约 16 乘 48 微秒,接近 0.8 毫秒,8 通道一轮约 6 毫秒。如果上位机只需要每秒刷新几百点,这个速度完全够。均值滤波是成本最低的抗 ADC 数据漂移手段,比任何数字滤波算法都可靠,缺点是拉长了等效采样周期,快速变化信号不能这么干。
4. ±10V 前端电平缩放与 8 路通道的参数选择
4.1 分压偏置网络的计算
TLC3578 的 ADC 内核量程按基准电压走,假设基准为 5V,输入引脚接受 0 到 5V。要处理 ±10V 工业信号,需要一个能把 -10V 映射到 0V、+10V 映射到 5V 的电平转换网络。常见做法是三个电阻:输入串联电阻 Rs 接到 ADC 节点,节点再通过 Rb 接 +5V 基准,通过 Rg 接地。
节点电压公式为:
Vadc = (Vin / Rs + 5V / Rb) / (1/Rs + 1/Rb + 1/Rg)令 Vin = -10V 时 Vadc=0,Vin=0V 时 Vadc=2.5V,Vin=+10V 时 Vadc=5V,解这个方程组得到一组比例:
| 电阻 | 推荐值 | 作用 |
|---|---|---|
| Rs | 10kΩ | 串联限流,与信号源隔离 |
| Rb | 5kΩ | 接 +5V,提供偏置 |
| Rg | 10kΩ | 接 GND,完成电平映射 |
按这个网络,输入输出关系如下:
| Vin | Vadc | 14 位 ADC 码 |
|---|---|---|
| -10V | 0V | 0 |
| 0V | 2.5V | 约 8192 |
| +10V | 5V | 16383 |
电阻值不是唯一解,比例必须保持。把 Rs 增大到 100kΩ,网络功耗更小,但等效源阻抗会急剧上升,预充电电容的建立时间变长,最终导致采样值偏小。Rs 取 10kΩ 是工业采集的常规起点。
4.2 运放缓冲、ESD 保护与抗混叠滤波
只靠电阻分压最大的坑是等效源阻抗太高。上面的 Rs、Rb、Rg 网络等效输出阻抗约 2.5kΩ,如果预充电电容是 10nF,时间常数就是 25 微秒,乘以 10 倍建立时间需要 250 微秒。也就是说,每次切换通道后必须等 250 微秒再启动读取,否则读到的电压还没稳定。
解决办法是加一级运放跟随。运放输出阻抗只有几十欧,和 10nF 组成的建立时间降到微秒级。运放选型要求不高,工业低频采集用轨到轨输出、带宽 1MHz 左右即可,反而要注意输入偏置电流:普通 JFET 运放偏置电流在 pA 级,对分压网络影响可以忽略。
ESD 钳位要放在分压网络之前的输入端子处,用 BAT54S 或类似双二极管钳到 ±15V 电源轨。不要把钳位二极管直接并在 ADC 引脚上,二极管漏电流随温度变化明显,直接并在高阻节点上会让 ADC 数据漂移。
如果输入端有较强的高频干扰,在运放和 ADC 之间加一个 RC 低通,截止频率按采样周期的五分之一到十分之一设计。例如采样周期 100 微秒,低通截止频率取 2kHz,R 取 1kΩ,C 取 100nF 左右。这个滤波电容会和 TLC3578 的预充电电容形成电荷分享,取值过大会导致读数偏低。
4.3 基准源和电源去耦的边界
TLC3578 的基准源直接决定测量精度,不要从 STM32 的 3.3V LDO 取基准。模拟电源 AVDD 和基准引脚各放一组 10V 搭配 100nF 去耦电容,如果板上有开关电源,模拟电源走磁珠再进 AVDD。数字地和模拟地单点连接,SDO 走线不要太长,避免数字翻转噪声耦合进模拟输入。
判断基准和去耦是否到位的办法很简单:把某一通道输入端直接短接到 GND,连续读 100 次看码分布。正常情况抖动应该小于正负 2 个 LSB,如果抖动达到几十个 LSB,先查基准源噪声,再查接地方式。这个测试排错顺序比盲目改滤波参数有效得多。
5. 调试对拍、三点校准和自动轮询的验证顺序
5.1 用万用表对拍原始码
拿到驱动后不要直接接 ±10V,先用稳压源给通道灌一个精确电压。例如输入 2.50V,14 位 ADC 的理论码是 2.5 除以 5.0 乘 16383,约 8192。用串口打印出来,代码在 8185 到 8200 之间说明链路基本正常。
printf("ch0 code = %d\r\n", tlc3578_read_once(0));如果偏差超过 20 个 LSB,先检查分压电阻精度和基准电压实际值,不要急着改软件。用万用表量 ADC 引脚上的实际电压,再和代码算出来的电压对比,能快速定位是前端问题还是 SPI 时序问题。
5.2 三点校准与线性插值
电阻精度和基准误差会导致整体增益和偏移偏差,工业上常用三点校准修正。校准点取 -10V、0V、+10V,分别记录码值,然后分段线性插值。
typedef struct { uint16_t code; int32_t volt_mv; /* 单位 mV */ } cal_point_t; static cal_point_t cal_table[3]; int32_t adc_code_to_mv(uint16_t code) { int32_t result; if (code <= cal_table[0].code) { result = cal_table[0].volt_mv; } else if (code >= cal_table[2].code) { result = cal_table[2].volt_mv; } else { for (uint8_t i = 0; i < 2; i++) { if (code >= cal_table[i].code && code <= cal_table[i + 1].code) { result = cal_table[i].volt_mv + (int32_t)(code - cal_table[i].code) * (cal_table[i + 1].volt_mv - cal_table[i].volt_mv) / (cal_table[i + 1].code - cal_table[i].code); break; } } } return result; }校准完成后,把 0V 点以外两个点的误差打印出来,观察插值结果的线性度。三点校准只能校正增益和偏移,不能校正严重的非线性,如果中间点误差仍然很大,问题大概率在基准源或者预充电电容上,而不是校准系数不够多。
5.3 自动轮询模式验证流程
单次模式跑通后,再启用自动轮询模式。自动轮询让 TLC3578 自己循环扫描 8 个通道并把结果存入片内 FIFO,STM32 只需要按固定间隔读取 FIFO,减少 GPIO 翻转次数。启动命令把命令字模式位设为自动轮询,之后连续读取 16 位帧即可。
#define TLC3578_MODE_AUTOSCAN (2u << 3) void tlc3578_start_autoscan(void) { uint8_t cmd = TLC3578_MODE_AUTOSCAN; TLC_CS_L(); tlc_swap_byte(cmd); TLC_CS_H(); } uint16_t tlc3578_read_fifo(void) { uint8_t hi, lo; uint16_t raw; TLC_CS_L(); hi = tlc_swap_byte(0); lo = tlc_swap_byte(0); TLC_CS_H(); raw = ((uint16_t)hi << 8) | lo; return raw & 0x3FFF; }验证自动轮询是否正确,建议先只接一路标准电压,其他通道悬空或接地,按启动命令后连续读 16 次 FIFO,观察通道电压是否按固定顺序出现。如果通道错位,检查命令字 A2~A0 的设置和 FIFO 的组织是否匹配。确认顺序后,再接入全部 8 路信号做完整扫描,同时用示波器观察 SDO 线上的帧间隔是否均匀。FIFO 读空时会返回旧数据,判断依据是连续两次读取结果完全相同,这时应停止读取等待下一轮填充完成。
本文还有配套的精品资源,点击获取