ARTICLE DETAIL

资讯详情

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

基于RT-Thread的SGM58031驱动开发实战:兼容ADS1115的16位ADC

基于RT-Thread的SGM58031驱动开发实战:兼容ADS1115的16位ADC 简介面向GD32F407与RT-thread开发者这份SGM58031驱动资源聚焦多通道高精度AD采样场景帮助解决模拟I2C时序控制、多设备扩展及实时系统下的驱动移植难题。压缩包整体仅3KB共3个文件包含C源文件、头文件与SConscript构建脚本代码精简便于按RT-thread设备框架直接集成。资源已有334人学习兼具实用性与参考价值。内容深入解析通过两路模拟I2C挂接四个SGM58031、实现16路AD采样的驱动写法覆盖GPIO模拟时序配置、设备初始化、多通道数据读取与中断完成处理等关键环节并针对I2C总线仲裁、数据同步等常见问题给出了设计考量。此外还提供了与STM32等平台的移植思路有助于读者快速理解驱动结构并复用到其他项目适合正在学习RT-thread设备驱动模型或需要快速落地多路AD采样功能的嵌入式工程师与高校学生。 做了几块基于GD32F407的采集板之后我越来越觉得SGM58031这颗芯片是个被低估的好东西。简单说它就是一颗16位、I2C接口的ADC硬件上完全兼容ADS1115价格却便宜不少在项目成本敏感的批次里优势很明显。配合RT-Thread这种国产RTOS驱动写起来算是比较顺手的活。这篇文章把我在项目里调通SGM58031驱动的完整过程整理出来从芯片内部结构、硬件接线到RT-Thread框架下的驱动实现再到实际采集时遇到的各种坑一次性讲透。1. 为什么是SGM58031这颗ADC的定位与选型逻辑1.1 和ADS1115的那点血缘关系第一次拿到SGM58031的手册第一反应是这玩意儿怎么这么眼熟。仔细对照寄存器映射表之后发现无论是转换寄存器、配置寄存器还是阈值寄存器几乎和TI的ADS1115一模一样。这意味着原本给ADS1115写的驱动代码改一改寄存器地址和位定义就能用不需要重新研究协议逻辑。这个表格是我整理的对比情况项目SGM58031ADS1115分辨率16 bit16 bit接口I2CI2C通道数1路差分/1路单端实际为多路MUX输入4路单端/2路差分可编程增益PGA支持FSR范围±0.256V~±6.144V支持范围相同数据速率8~860 SPS8~860 SPS内部基准有约2.048V有约2.048V兼容性寄存器级兼容ADS1115-实际项目里如果遇到ADS1115缺货用SGM58031做备选是很自然的方案。反过来也是一样SGM58031的供货紧张时换ADS1115几乎不用改板子。这种双源设计在工业产品里是非常重要的抗风险手段。1.2 精度够用功耗还低SGM58031的核心卖点是16位无失码分辨率在4.096V满量程、PGA1的情况下LSB大小是125uV。对大多数传感器信号的采集来说这个精度完全够用。如果需要更小的量程把PGA调大到±0.256VLSB可以降到7.8125uV。功耗方面也要提一下。芯片内部集成了低漂移电压基准和振荡器不需要外部基准源。连续转换模式下电流大约150uA单次模式下更低。我之前做过一个电池供电的手持检测设备把ADC配置成8 SPS单次转换模式对整个系统的功耗预算影响非常小。1.3 FT引脚和I2C地址逻辑SGM58031的ADDR引脚决定I2C从机地址。手册上给出的地址表是ADDR接地、接VDD、接SDA、接SCL对应4种不同地址。这和ADS1115结论一致。在设计时需要注意的是如果一颗总线上挂了多颗SGM58031用电阻分压接到ADDR脚的办法也行但最稳的还是直接硬连到电源或地避免焊接不良导致地址漂移导致设备找不到。这颗芯片还有FT引脚实际是SCL/SDA引脚的5V容忍能力可以直接挂在3.3V的GD32F407 I2C总线上不需要额外电平转换。但这里有个细节虽然引脚是5V容忍I2C总线的上拉电阻仍然需要按3.3V供电来选否则可能会把漏电流搞大。2. 硬件设计GD32F407和SGM58031的接线与注意事项2.1 最小硬件连接图GD32F407的I2C外设资源比较丰富I2C0、I2C1、I2C2共三路。我这里用的是I2C1因为PB10/PB11这两个引脚在板子上刚好空出来。如果你在别的板子上用记得查数据手册里的Alternate Function映射表别接错了。接线很简单GD32F407引脚功能连接目标PB10I2C1_SCLSGM58031 SCLPB11I2C1_SDASGM58031 SDA3.3V电源SGM58031 VDDGND地SGM58031 GND-地址选择ADDR接GNDSCL和SDA两条线上各接一个4.7kΩ上拉电阻到3.3V。这是I2C总线的标准做法。如果总线上设备多、走线长可以换成2.2kΩ。上拉电阻太小会导致信号边沿太陡EMI变差太大则信号上升沿太慢在400kHz下可能无法满足时序要求。2.2 模拟输入的滤波设计有人直接把传感器输出接到ADC引脚就跑结果采集到的数据跳得厉害然后怪芯片不行。实际上大部分情况是前端滤波没做好。SGM58031是过采样型ADCΔ-Σ架构它对输入端的阻抗和噪声有一定要求。我的习惯是每路模拟输入对地接一个100nF的滤波电容再根据信号带宽串联一个RC低通。比如采集温度信号时用1kΩ和1uF构成约159Hz的低通滤波器效果立竿见影。另外还有个反直觉的点AIN引脚的绝对输入范围不能超出电源轨太多。虽然PGA可以配置成±6.144V量程但输入电压必须限制在GND-0.3V到VDD0.3V之间。我第一次用的时候想测一个0~10V的电压信号直接在±6.144V增益下把10V接进去结果芯片直接烧了。后来老老实实在前面加了电阻分压。2.3 电源去耦的细节VDD引脚旁边必须放一个0.1uF的陶瓷电容而且要尽量靠近引脚放置。如果电源质量不好还可以加一个10uF的钽电容做低频滤波。之所以这么强调是因为ADC内部的基准电压源对电源噪声比较敏感电源纹波会直接耦合进转换结果。这里有个容易被忽略的地方SGM58031的VDD同时充当了参考电压的供电脚如果VDD上有高频毛刺基准源很难完全抑制掉最终表现为转换结果上叠加了周期性噪声。特别是板上同时有DCDC开关电源时布局上要把模拟部分和数字部分的地分开单点汇聚。3. RT-Thread环境下的驱动软件架构设计3.1 为什么用RT-Thread的I2C设备驱动框架RT-Thread的设备驱动框架把I2C控制器封装成了标准设备上层通过rt_device_find和rt_i2c_transfer来读写不直接操作寄存器。这样带来的好处是应用层代码与具体的MCU硬件解耦以后换平台、换I2C控制器驱动不用重写。用的是标准I2C设备接口需要先在rtconfig.h里使能#define RT_USING_I2C #define RT_USING_I2C_BITOPS #define BSP_USING_I2C1其中RT_USING_I2C_BITOPS表示使用软件模拟I2C。GD32F407上如果你觉得硬件I2C用起来麻烦可以依靠RT-Thread的bit ops直接模拟时序稳定性反而更好。我这次驱动用的是硬件I2C1因为板子上引脚刚好够用且硬件I2C的时序更规整。3.2 驱动文件的结构规划完整的驱动代码我拆成了几个部分sgm58031.h寄存器地址、位定义、设备结构体声明sgm58031.c核心读写逻辑、配置函数、数据读取函数sgm58031_sample.c测试示例配置成单次转换模式并读取通道0数据应用层只需要包含头文件、调用初始化函数然后周期性读取电压值不需要关心底层的I2C时序细节。设备结构体的设计参考了RT-Thread的ADC设备模型但简化了些typedef struct { struct rt_i2c_bus_device *i2c_bus; rt_uint8_t slave_addr; rt_uint8_t pga_gain; rt_uint8_t data_rate; rt_uint8_t mux_channel; rt_uint8_t continuous_mode; } sgm58031_dev_t;这个结构体把SGM58031的配置项全部集中起来切换PGA增益或者数据速率时只需要改这一个结构体实例。3.3 I2C消息的组装原则RT-Thread的rt_i2c_transfer函数接收rt_i2c_msg数组支持在一条事务里先写后读。这里最关键的注意点是消息的flags标志位struct rt_i2c_msg msgs[2]; msgs[0].addr dev-slave_addr; msgs[0].flags RT_I2C_WR; msgs[0].buf config_cmd; msgs[0].len 3; msgs[1].addr dev-slave_addr; msgs[1].flags RT_I2C_RD; msgs[1].buf data_buf; msgs[1].len 2;这几个字段看起来简单但实际踩过坑如果flags没有正确设置或者len和实际数据长度不一致I2C总线上会出现跑飞或者读错字节的情况。另外rt_i2c_transfer返回的是成功传输的消息数不是字节数一定要判断返回值和期望的消息数量是否相等否则就是通信异常。4. 驱动代码逐段拆解从寄存器配置到数据读取4.1 初始化查找总线和设置默认参数初始化函数要做的事情有这几件从设备管理器找到I2C总线设备、设置从机地址、配置默认PGA增益和数据速率、写入初始配置寄存器。rt_err_t sgm58031_init(sgm58031_dev_t *dev, const char *i2c_bus_name, rt_uint8_t slave_addr) { RT_ASSERT(dev); RT_ASSERT(i2c_bus_name); dev-i2c_bus (struct rt_i2c_bus_device *)rt_device_find(i2c_bus_name); if (dev-i2c_bus RT_NULL) { rt_kprintf(I2C bus %s not found!\n, i2c_bus_name); return -RT_ERROR; } dev-slave_addr slave_addr; dev-pga_gain SGM58031_PGA_4P096V; dev-data_rate SGM58031_DR_128SPS; dev-mux_channel SGM58031_MUX_AIN0_GND; dev-continuous_mode 0; if (sgm58031_write_config(dev) ! RT_EOK) { rt_kprintf(SGM58031 config write failed!\n); return -RT_ERROR; } return RT_EOK; }首次上电后芯片的默认配置也能工作但我仍然建议显式写一次完整的配置寄存器算是给自己一个明确的状态基准。排查问题的时候可以先确认配置寄存器回读值判断I2C通信是否正常。4.2 配置寄存器的位操作封装SGM58031的配置寄存器是16位的需要拆成两个字节写入。我用一个函数封装这个操作传入配置字先写高位后写低位。rt_err_t sgm58031_write_config(sgm58031_dev_t *dev) { rt_uint8_t buf[3]; rt_uint16_t config 0; /* OS: 写1触发单次转换 */ if (dev-continuous_mode) config | (0 15); else config | (1 15); /* MUX: 输入通道选择 */ config | ((dev-mux_channel 0x07) 12); /* PGA: 可编程增益 */ config | ((dev-pga_gain 0x07) 9); /* MODE: 0连续转换, 1单次 */ if (dev-continuous_mode) config | (0 8); else config | (1 8); /* DR: 数据速率 */ config | ((dev-data_rate 0x07) 5); /* COMP_QUE: 禁用比较器 */ config | 0x03; buf[0] SGM58031_REG_CONFIG; buf[1] (config 8) 0xFF; buf[2] config 0xFF; struct rt_i2c_msg msg {0}; msg.addr dev-slave_addr; msg.flags RT_I2C_WR; msg.buf buf; msg.len 3; if (rt_i2c_transfer(dev-i2c_bus, msg, 1) ! 1) return -RT_ERROR; return RT_EOK; }MUX字段映射关系需要对着数据手册确认一遍000是AIN0-AIN1差分101是AIN0对GND单端110是AIN1对GND单端以此类推。我封装的时候把“通道号”和“物理引脚”的对应关系直接写在宏定义里这样调用时语义清楚。4.3 单次转换模式下的完整读取流程单次转换模式的逻辑是写配置寄存器时把OS位置1然后等待转换完成最后读取转换寄存器。等待转换完成有两种方式读配置寄存器轮询OS位或者直接延时一个固定时间。轮询方式更可靠但需要额外一次I2C读操作。延时方式简单但需要精确计算转换时间。我采用的是轮询方式rt_err_t sgm58031_read_raw(sgm58031_dev_t *dev, rt_int16_t *raw) { rt_uint8_t buf[2]; rt_uint16_t config 0; rt_uint16_t timeout 1000; /* 触发单次转换 */ sgm58031_read_reg(dev, SGM58031_REG_CONFIG, config); config | (1 15); sgm58031_write_reg(dev, SGM58031_REG_CONFIG, config); /* 等待OS位清零 */ while (timeout--) { sgm58031_read_reg(dev, SGM58031_REG_CONFIG, config); if ((config (1 15)) 0) break; rt_thread_mdelay(1); } if (timeout 0) return -RT_ETIMEOUT; /* 读取转换结果 */ sgm58031_read_regs(dev, SGM58031_REG_CONVERSION, buf, 2); *raw (rt_int16_t)((buf[0] 8) | buf[1]); return RT_EOK; }这里有一个特别重要的细节操作OS位时必须“先读后写”也就是先读出当前配置寄存器的值把OS位改为1之后再整体写回去。只写一个OS位会导致其他配置被重置为默认值这是个非常隐蔽的bug。4.4 连续模式的配置与FIFO处理思路连续模式下ADC会按照设定的数据速率不断采样转换结果自动更新。这时候配置寄存器的MODE位要清0OS位写0。读取时直接读转换寄存器即可读到的总是最新一次转换结果。连续模式的读数据代码更短rt_err_t sgm58031_read_continuous(sgm58031_dev_t *dev, rt_int16_t *raw) { return sgm58031_read_regs(dev, SGM58031_REG_CONVERSION, (rt_uint8_t *)raw, 2); }但要注意读回来的字节序是大端模式buf[0]是最高有效字节。如果直接强转成int16_t在little-endian的Cortex-M4上会得到错误结果。很多新手在这里翻车所以我上面特意手动做了一个移位拼接而不是简单强转。5. 数据校准与电压换算从原始值到物理量5.1 LSB和增益的关系SGM58031的转换结果是16位有符号数对应关系是满量程范围内1个LSB等于FSR除以32768。FSR由PGA配置决定。换算公式很简单float sgm58031_convert_to_voltage(rt_int16_t raw, float fsr_voltage) { return (float)raw * fsr_voltage / 32768.0f; }这里的fsr_voltage指的是你配置的PGA对应的满量程电压。比如配置成±4.096V时调用时传4.096f注意正负满量程一共是8.192V跨度但公式里对应的是单边量程因为raw本身是带符号的。这个逻辑比较容易晕建议直接写成宏或者查表。常用PGA配置对应表PGA设置满量程范围LSB大小±6.144V6.144V187.5uV±4.096V4.096V125uV±2.048V2.048V62.5uV±1.024V1.024V31.25uV±0.512V0.512V15.625uV±0.256V0.256V7.8125uV5.2 负电压的表示问题SGM58031支持测量AIN引脚对GND的负电压吗严格来说AIN输入不能低于GND-0.3V。这意味着差分模式下AIN0可以低于AIN1但两者都不能超出电源轨。我在实际项目里测过一个双向电流检测电路传感器输出范围是-2.5V到2.5V但芯片电源只有3.3V直接接一定会出问题。最后的方案是用运放把-2.5V~2.5V平移成0.15V~2.65V再送给SGM58031采集程序里反向平移回来。这种信号调理电路的设计和对ADC本身特性的理解同等重要。5.3 数据的软件滤波SGM58031在128 SPS数据速率下原始adc值会有些随机噪声。按我的经验128 SPS时峰峰值噪声大约在1~3个LSB对16位分辨率来说主要受内部基准和PCB布局影响。如果不需要那么快的数据速率干脆降到16 SPS噪声显著减小。需要更平滑的数据时我用一个简单的滑动平均滤波#define FILTER_SIZE 8 rt_int32_t adc_filter(rt_int16_t new_value) { static rt_int16_t buffer[FILTER_SIZE]; static rt_uint8_t index 0; static rt_uint8_t count 0; rt_int32_t sum 0; rt_uint8_t i; buffer[index] new_value; index (index 1) % FILTER_SIZE; if (count FILTER_SIZE) count; for (i 0; i count; i) sum buffer[i]; return sum / count; }这种滤波适合缓慢变化的信号比如温度或压力。如果是快速变化的动态信号建议改成加权移动平均或者直接提高数据速率后做快速傅里叶变换从频域分析噪声来源。6. 踩坑实录驱动调试过程中遇到的问题6.1 问题一I2C总线找不到设备启动时日志显示I2C bus i2c1 not found!先查rtconfig.h里是否启用了I2C1再看board.c里BSP初始化代码是否正确调用了rt_i2c_bit_add_bus或硬件I2C初始化函数。我在一次移植过程中发现RT-Thread Studio生成的工程默认只使能了I2C0需要在CubeMX类似配置界面里打开I2C1。另一个原因是引脚复用没配置好。GD32F407的PB10/PB11需要被设置为AF4复用模式如果初始化代码里把引脚设置成了GPIO_MODE_OUTPUT那么I2C外设根本没法工作。6.2 问题二读数全是0xFFFF或0x7FFF读数异常先别急着怀疑芯片用示波器抓一下I2C波形。如果波形正常那问题多半出在用读到的值强转符号上。0xFFFF在int16_t里是-1这在电压上对应负满量程不是“读不出来”。还有个常见情况是地址配置错误。SGM58031的7位地址比如0x48但rt_i2c_transfer里要传的addr是按7位地址还是8位地址不同驱动框架处理方式不同。RT-Thread的rt_i2c_msg里addr使用7位地址不需要左移一位。如果你按STM32标准库的习惯传0x90那肯定找不到设备。6.3 问题三单次转换模式下读到的数据一直不变这个问题我在调试时折腾了很久。现象是第一次读到的数据是正常的之后无论怎么触发转换读回来的始终是第一次的值。根因是在等待转换完成的循环里如果OS位一直没有清零就会返回超时但函数前面已经触发了转换如果触发之后使用固定延时就没有问题而我用的是轮询轮询出错时把config寄存器读出来的同时OS位本来已经被硬件清掉了问题是触发转换之后没过多久就去读配置寄存器此时硬件可能还没开始转换OS位尚未被清导致误判超时超时后就退出但转换其实仍在继续下次读转换寄存器时读的是上一次完成的数据。解决方法是触发转换后先延时1ms再轮询等待或者直接把超时时间放宽。更稳妥的方案是不用OS位轮询直接根据数据速率算出转换时间比如128 SPS时单次转换大约8ms延时完成后读取。6.4 问题四供电电压变化导致测量值偏移这个坑和SGM58031本身关系不大但是很难排查。现象是用锂电池供电时随着电池电量下降ADC读到的电压值逐渐偏低。排查后发现问题出在传感器的参考电压也用了电池电压ADC的基准是内部的2.048V而传感器的基准是电源电压两者没有共地参考关系。解决办法是给传感器加一个独立的低压差稳压器或者改用比例测量的方案让ADC的参考源与传感器的激励源共用同一个电压源。比例测量在电桥类传感器中很常见能直接抵消电源波动。7. 性能优化与扩展让驱动更实用7.1 区分快速读取和精确读取驱动里我增加了两个读取接口一个适合快速连续采样每次只触发转换并读取结果不做额外处理另一个适合对精度要求高的场景内部会连续读取多次并取平均。这两个接口切换起来很方便应用层按需调用。在实时性要求高的场景下可以把读取函数放到中断上下文或高优先级线程里配合RT-Thread的信号量机制转换完成后通过中断通知读取线程避免阻塞式的轮询浪费CPU。SGM58031没有硬件中断引脚但软件方式完全够用了。7.2 用RT-Thread的finSH命令调试ADC调试阶段我一直用RT-Thread的finSH控制台来查看ADC数据比反复烧录固件高效得多。写了一个简单的shell命令static void adc_read(int argc, char **argv) { rt_int16_t raw; float voltage; sgm58031_read_raw(sgm_dev, raw); voltage sgm58031_convert_to_voltage(raw, 4.096f); rt_kprintf(raw%d, voltage%.4fV\n, raw, voltage); } MSH_CMD_EXPORT(adc_read, read sgm58031 adc value);这样在终端敲一个adc_read就能看到实时数据。批量采集还可以加一个次数参数配合波形显示工具快速判断信号稳定性。7.3 双通道或多芯片扩展思路SGM58031本身是单芯片多路MUX输入通过配置寄存器选择不同的输入通道。要做多通道采集可以轮流切换MUX字段读取不同通道但要注意每次切换后第一次读取的数据需要丢弃因为模拟前端有一个建立时间。如果是多颗芯片并联在同一根I2C总线上依靠不同的ADDR引脚地址来区分。我之前做过一根总线上挂4颗SGM58031的设计地址分别为0x48~0x4B每颗采集不同的信号实测400kHz速率下总线通信稳定每颗芯片依次读取总耗时约30ms对采集任务来说完全能接受。唯一需要注意的是总线上拉电阻需要相应减小因为多颗芯片的输入电容并联后会增加总线的容性负载。7.4 低功耗场景下的驱动策略如果是电池供电建议每次读取前才上电初始化读取完成后把SGM58031的两个引脚配置成模拟输入关闭I2C外设时钟可以节省一部分功耗。SGM58031本身没有硬件关断引脚但通过TTL电平把VDD断掉也行。我在一个低功耗产品里用了一颗负载开关控制VDD平时完全断电需要采样时再上电等待约10ms稳定后触发转换整体功耗能降到uA级别。8. 总结与作业SGM58031驱动开发的经验回溯回看整个SGM58031驱动开发过程从芯片选型到驱动程序跑通最耗时间的不是写代码本身而是排查各种硬件和软件边界条件。寄存器映射本身就那么几个但每个位在不同模式下的行为差异、I2C通信的不确定性、电源和布局的影响叠加在一起才导致问题难以定位。我给正在做类似项目的开发者几个建议第一拿到芯片后先回读配置寄存器确认I2C通信正常再做任何数据读取第二初次调试时始终用单次转换模式一次转换读一次结果逻辑清晰容易排查第三PCB布局时尽量缩短SGM58031到传感器的距离并加好RC滤波能减少后续大量的软件补偿工作第四别忘了在驱动里做好错误处理RT-Thread的I2C传输可能会因为总线繁忙或超时失败代码必须有对应的返回值和日志否则现场问题排查会非常痛苦。RT-Thread的设备驱动框架帮我们屏蔽了很多底层差异但底层I2C的时序基础还是得懂。如果你也在用GD32或者STM32做类似的ADC采集方案建议先从一颗芯片的驱动写起把单点打透再扩展到多通道多芯片的复杂系统。这个思路适用于所有外围传感器的驱动开发尤其是眼下国产芯片生态越来越丰富的大环境下掌握这种“照着数据手册写驱动”的能力会让你换芯片时心里有底。本文还有配套的精品资源点击获取
返回列表