Files
balance-board/src/ads1256.c
T
pNexus ec1ba14e46 fix(ads1256): 修复 SPI 无波形问题,添加 PDWN 引脚控制
PDWN 浮空导致 ADS1256 进入 power-down 模式,DRDY 无响应且 SPI 总线
无输出。同时将 CS 管理从手动 GPIO 改为 SPIM 驱动自动控制,消除引脚
所有权冲突;读寄存器和读数据改用 spi_transceive 保证单次 CS 内完成。
2026-05-21 15:01:26 +08:00

367 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ads1256.h"
#include <stdint.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ads1256, LOG_LEVEL_INF);
/* --- ADS1256 命令 --- */
#define CMD_WAKEUP 0x00
#define CMD_RDATA 0x01
#define CMD_RREG 0x10
#define CMD_WREG 0x50
#define CMD_SELFCAL 0xF0
#define CMD_SYNC 0xFC
#define CMD_SDATAC 0x0F
/* --- ADS1256 寄存器地址 --- */
#define REG_STATUS 0x00
#define REG_MUX 0x01
#define REG_ADCON 0x02
#define REG_DRATE 0x03
#define REG_IO 0x04
#define REG_OFC0 0x05
#define REG_OFC1 0x06
#define REG_OFC2 0x07
#define REG_FSC0 0x08
#define REG_FSC1 0x09
#define REG_FSC2 0x0A
#define SPI_OP (SPI_OP_MODE_MASTER | SPI_MODE_CPHA | SPI_WORD_SET(8) | SPI_LINES_SINGLE)
/* --- 硬件资源 --- */
static struct spi_config spi_cfg;
static const struct device *spi_dev;
static const struct gpio_dt_spec cs_spec = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(ads1256));
static const struct gpio_dt_spec drdy_spec = GPIO_DT_SPEC_GET(DT_ALIAS(ads_drdy), gpios);
static const struct gpio_dt_spec reset_spec = GPIO_DT_SPEC_GET(DT_ALIAS(ads_reset), gpios);
static const struct gpio_dt_spec pdwn_spec = GPIO_DT_SPEC_GET(DT_ALIAS(ads_pdwn), gpios);
struct ads1256_reg_snapshot {
uint8_t status;
uint8_t mux;
uint8_t adcon;
uint8_t drate;
uint8_t io;
uint8_t ofc0;
uint8_t ofc1;
uint8_t ofc2;
uint8_t fsc0;
uint8_t fsc1;
uint8_t fsc2;
};
/**
* @brief 等待 DRDY 拉低(转换结果可读),带超时保护。
*
* @param timeout_ms 超时时间(毫秒)。
*
* @retval 0 DRDY 在超时前变为可读状态。
* @retval -ETIMEDOUT 超时后仍未等到本次转换结果。
*/
int ads1256_wait_drdy(uint16_t timeout_ms) {
/* drdy_spec 配了 GPIO_ACTIVE_LOW,逻辑 1 表示 DRDY 有效(物理拉低) */
int64_t deadline = k_uptime_get() + timeout_ms;
while (gpio_pin_get_dt(&drdy_spec) == 0) {
k_usleep(10);
if (k_uptime_get() >= deadline) {
LOG_WRN("DRDY timeout (%u ms)", timeout_ms);
return -ETIMEDOUT;
}
}
return 0;
}
/**
* @brief 向 ADS1256 写单个寄存器。
*
* @param reg 目标寄存器地址。
* @param val 要写入的寄存器值。
*/
void ads1256_write_reg(uint8_t reg, uint8_t val) {
ads1256_wait_drdy(50);
uint8_t tx_buf[3] = { CMD_WREG | reg, 0x00, val };
struct spi_buf tx = { .buf = tx_buf, .len = 3 };
struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };
spi_write(spi_dev, &spi_cfg, &tx_set);
/* t11: WREG 后至少 4 × tCLKIN ≈ 0.5µs */
k_busy_wait(2);
}
/**
* @brief 从 ADS1256 读单个寄存器。
*
* ADS1256 RREG 时序要求:命令+数据在同一次 CS 拉低内完成,
* 中间需 t6 延时。这里用单次 transceive 保证 CS 不释放。
*
* @param reg 目标寄存器地址。
*
* @return 读取到的寄存器值。
*/
uint8_t ads1256_read_reg(uint8_t reg) {
ads1256_wait_drdy(50);
/* tx: [RREG|reg, 0x00, dummy_for_t6, dummy_read]
* rx: [x, x, x, data] — 前 3 字节是命令+延时期间的垃圾 */
uint8_t tx_buf[4] = { CMD_RREG | reg, 0x00, 0xFF, 0xFF };
uint8_t rx_buf[4] = { 0 };
struct spi_buf tx = { .buf = tx_buf, .len = 4 };
struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };
struct spi_buf rx = { .buf = rx_buf, .len = 4 };
struct spi_buf_set rx_set = { .buffers = &rx, .count = 1 };
spi_transceive(spi_dev, &spi_cfg, &tx_set, &rx_set);
return rx_buf[3];
}
/**
* @brief 连续读取 ADS1256 关键寄存器,形成一次诊断快照。
*
* @param snapshot 输出快照对象,不能为空。
*/
static void ads1256_read_snapshot(struct ads1256_reg_snapshot *snapshot) {
snapshot->status = ads1256_read_reg(REG_STATUS);
snapshot->mux = ads1256_read_reg(REG_MUX);
snapshot->adcon = ads1256_read_reg(REG_ADCON);
snapshot->drate = ads1256_read_reg(REG_DRATE);
snapshot->io = ads1256_read_reg(REG_IO);
snapshot->ofc0 = ads1256_read_reg(REG_OFC0);
snapshot->ofc1 = ads1256_read_reg(REG_OFC1);
snapshot->ofc2 = ads1256_read_reg(REG_OFC2);
snapshot->fsc0 = ads1256_read_reg(REG_FSC0);
snapshot->fsc1 = ads1256_read_reg(REG_FSC1);
snapshot->fsc2 = ads1256_read_reg(REG_FSC2);
}
/**
* @brief 仅打印恢复前后发生变化的寄存器。
*
* @param tag 日志标签,用于区分恢复成功与失败后的变化。
* @param before 恢复前寄存器快照。
* @param after 恢复后寄存器快照。
*/
static void ads1256_log_snapshot_delta(
const char *tag,
const struct ads1256_reg_snapshot *before,
const struct ads1256_reg_snapshot *after) {
if (before->status != after->status) {
LOG_WRN("%s STATUS: 0x%02X -> 0x%02X", tag, before->status, after->status);
}
if (before->mux != after->mux) {
LOG_WRN("%s MUX: 0x%02X -> 0x%02X", tag, before->mux, after->mux);
}
if (before->adcon != after->adcon) {
LOG_WRN("%s ADCON: 0x%02X -> 0x%02X", tag, before->adcon, after->adcon);
}
if (before->drate != after->drate) {
LOG_WRN("%s DRATE: 0x%02X -> 0x%02X", tag, before->drate, after->drate);
}
if (before->io != after->io) {
LOG_WRN("%s IO: 0x%02X -> 0x%02X", tag, before->io, after->io);
}
if (before->ofc0 != after->ofc0) {
LOG_WRN("%s OFC0: 0x%02X -> 0x%02X", tag, before->ofc0, after->ofc0);
}
if (before->ofc1 != after->ofc1) {
LOG_WRN("%s OFC1: 0x%02X -> 0x%02X", tag, before->ofc1, after->ofc1);
}
if (before->ofc2 != after->ofc2) {
LOG_WRN("%s OFC2: 0x%02X -> 0x%02X", tag, before->ofc2, after->ofc2);
}
if (before->fsc0 != after->fsc0) {
LOG_WRN("%s FSC0: 0x%02X -> 0x%02X", tag, before->fsc0, after->fsc0);
}
if (before->fsc1 != after->fsc1) {
LOG_WRN("%s FSC1: 0x%02X -> 0x%02X", tag, before->fsc1, after->fsc1);
}
if (before->fsc2 != after->fsc2) {
LOG_WRN("%s FSC2: 0x%02X -> 0x%02X", tag, before->fsc2, after->fsc2);
}
}
/**
* @brief 发送单字节命令到 ADS1256。
*
* @param cmd 命令字。
*/
void ads1256_write_cmd(uint8_t cmd) {
ads1256_wait_drdy(50);
struct spi_buf tx = { .buf = &cmd, .len = 1 };
struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };
spi_write(spi_dev, &spi_cfg, &tx_set);
}
/**
* @brief 发送 SYNC + WAKEUP 命令,触发一次同步采样。
*
* SYNC 和 WAKEUP 必须在同一次 CS 内完成,用单次 transceive 保持 CS。
* 中间插入 dummy 字节满足 t11 延时 (24 × tCLKIN ≈ 3.1µs)。
*/
void ads1256_sync_wakeup(void) {
/* [SYNC, dummy(延时), WAKEUP] — 500kHz 下每字节 16µs1 字节 dummy 远超 3.1µs */
uint8_t tx_buf[3] = { CMD_SYNC, 0xFF, CMD_WAKEUP };
struct spi_buf tx = { .buf = tx_buf, .len = 3 };
struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };
spi_write(spi_dev, &spi_cfg, &tx_set);
}
/**
* @brief 硬件复位 ADS1256(通过 RESET 引脚),等待复位后自校准完成。
*
* @retval 0 芯片完成复位并重新进入可通信状态。
* @retval -ETIMEDOUT 复位后等待 DRDY 超时,芯片未按预期响应。
*/
int ads1256_hwreset(void) {
/* reset_spec 配了 GPIO_ACTIVE_LOW:逻辑 1 = 物理 LOW = 断言复位 */
gpio_pin_set_dt(&reset_spec, 1);
/* t16: 最小复位脉宽 4 × tCLKIN ≈ 0.5µs,取 1ms 余量 */
k_msleep(10);
gpio_pin_set_dt(&reset_spec, 0);
/* 复位释放后芯片执行自校准,等待完成 */
k_msleep(10);
return ads1256_wait_drdy(500);
}
/**
* @brief 读取当前 24 位转换结果,符号扩展为 int32_t。
*
* RDATA 命令后需 t6 延时再读 3 字节数据,全部在同一次 CS 内完成。
*
* @return 当前 ADC 转换原始值。
*/
int32_t ads1256_read_data(void) {
/* tx: [RDATA, dummy(t6延时), MSB, MID, LSB]
* rx: [x, x, MSB, MID, LSB] */
uint8_t tx_buf[5] = { CMD_RDATA, 0xFF, 0xFF, 0xFF, 0xFF };
uint8_t rx_buf[5] = { 0 };
struct spi_buf tx = { .buf = tx_buf, .len = 5 };
struct spi_buf_set tx_set = { .buffers = &tx, .count = 1 };
struct spi_buf rx = { .buf = rx_buf, .len = 5 };
struct spi_buf_set rx_set = { .buffers = &rx, .count = 1 };
spi_transceive(spi_dev, &spi_cfg, &tx_set, &rx_set);
int32_t val = ((int32_t)rx_buf[2] << 16) | ((int32_t)rx_buf[3] << 8) | rx_buf[4];
if (val & 0x800000) val |= 0xFF000000;
return -val;
}
/**
* @brief 初始化 ADS1256:配置 GPIO/SPI,复位芯片,写入寄存器,自校准。
*
* @retval 0 初始化成功,芯片参数已经写入并校验通过。
* @retval -ENODEV SPI 控制器未就绪,无法访问 ADS1256。
* @retval -ETIMEDOUT 复位或校准等待阶段未收到芯片响应。
* @retval -EIO 寄存器回读校验失败,SPI 通信结果不可信。
*/
int ads1256_init(void) {
/* GPIO */
/* PDWN 低有效:配为 INACTIVE(物理高电平) 保持芯片正常运行 */
gpio_pin_configure_dt(&pdwn_spec, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&reset_spec, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&drdy_spec, GPIO_INPUT);
/* SPI — CS 由 SPIM 驱动自动管理 */
spi_dev = DEVICE_DT_GET(DT_NODELABEL(spi1));
spi_cfg.operation = SPI_OP;
spi_cfg.frequency = 500000;
spi_cfg.slave = 0;
spi_cfg.cs.gpio = cs_spec;
spi_cfg.cs.delay = 0;
if (!device_is_ready(spi_dev)) {
LOG_ERR("SPI device not ready");
return -ENODEV;
}
int ret = ads1256_hwreset();
if (ret) {
LOG_ERR("ADS1256 not detected (DRDY no response after reset)");
return ret;
}
/* 关闭连续输出模式,进入命令模式 */
ads1256_write_cmd(CMD_SDATAC);
k_busy_wait(100);
/*
* STATUS = 0x06: ORDER=MSB, ACAL=1(自动校准), BUFEN=1(开启输入缓冲)
* GML670 50kg 传感器输出阻抗 1kΩ,Buffer OFF + PGA=64 时输入阻抗仅 ~8kΩ
* (datasheet Table 10),导致 ~11% 负载误差。开 Buffer 后输入阻抗 >10MΩ。
*/
ads1256_write_reg(REG_STATUS, 0x06);
ads1256_write_reg(REG_MUX, 0x01);
/* ADCON: CLK_OUT=OFF, SDCS=OFF, PGA=64 */
ads1256_write_reg(REG_ADCON, 0x07);
/*
* DRATE: 3750 SPS (0xC0)
* 50Hz 循环 = 20ms 预算。4 通道各采 10 次均值:
* 单通道 = t18(0.44ms) + 9 × 1/3750(0.27ms) = 2.87ms
* 4 通道 = 11.5ms,余量 8.5ms
* 相比 30kSPS: ENOB 从 16.5 → 17.4 bit (Buffer On, PGA=64)
*/
ads1256_write_reg(REG_DRATE, 0xC0);
/* 显式自校准:Buffer/PGA 变更后必须重新校准 (datasheet p25) */
ads1256_write_cmd(CMD_SELFCAL);
/* SELFCAL 完成后 DRDY 拉低 (datasheet: "Do not send additional commands
after issuing this command until DRDY goes low") */
ads1256_wait_drdy(500);
/* 回读关键寄存器验证 SPI 通信正确性 */
uint8_t status = ads1256_read_reg(REG_STATUS);
uint8_t adcon = ads1256_read_reg(REG_ADCON);
uint8_t drate = ads1256_read_reg(REG_DRATE);
LOG_INF("ADS1256 regs: STATUS=0x%02X ADCON=0x%02X DRATE=0x%02X", status, adcon, drate);
if (drate != 0xC0) {
if (status == 0x00 && adcon == 0x00 && drate == 0x00) {
LOG_ERR("ADS1256 SPI read failure (all regs 0x00) — check MISO wiring");
} else if (status == 0xFF && adcon == 0xFF && drate == 0xFF) {
LOG_ERR("ADS1256 SPI read failure (all regs 0xFF) — MISO floating or not connected");
} else {
LOG_ERR("ADS1256 register verify failed (DRATE=0x%02X, expected 0xC0)", drate);
}
return -EIO;
}
LOG_INF("ADS1256 initialized (BUFEN=1, PGA=64, 3750SPS)");
return 0;
}
/**
* @brief EMI 恢复:重写关键寄存器并验证,用于马达干扰后自恢复。
*
* @param max_retries 最大重试次数,每次间隔 20ms。
*
* @retval 0 恢复成功。
* @retval -EIO 达到最大重试次数仍未恢复。
*/
int ads1256_recover(int max_retries) {
struct ads1256_reg_snapshot before_snapshot;
ads1256_read_snapshot(&before_snapshot);
for (int i = 0; i < max_retries; i++) {
ads1256_write_reg(REG_STATUS, 0x06);
ads1256_write_reg(REG_ADCON, 0x07);
ads1256_write_reg(REG_DRATE, 0xC0);
ads1256_sync_wakeup();
k_msleep(20);
uint8_t adcon = ads1256_read_reg(REG_ADCON);
uint8_t drate = ads1256_read_reg(REG_DRATE);
if (adcon == 0x07 && drate == 0xC0) {
struct ads1256_reg_snapshot after_snapshot;
ads1256_read_snapshot(&after_snapshot);
ads1256_log_snapshot_delta("ADS1256 recover reg change", &before_snapshot, &after_snapshot);
LOG_WRN("ADS1256 recovered after %d retries", i + 1);
return 0;
}
}
struct ads1256_reg_snapshot failed_snapshot;
ads1256_read_snapshot(&failed_snapshot);
ads1256_log_snapshot_delta(
"ADS1256 recover reg change after retries exhausted", &before_snapshot, &failed_snapshot);
LOG_ERR("ADS1256 recovery failed after %d retries", max_retries);
return -EIO;
}