ec1ba14e46
PDWN 浮空导致 ADS1256 进入 power-down 模式,DRDY 无响应且 SPI 总线 无输出。同时将 CS 管理从手动 GPIO 改为 SPIM 驱动自动控制,消除引脚 所有权冲突;读寄存器和读数据改用 spi_transceive 保证单次 CS 内完成。
367 lines
13 KiB
C
367 lines
13 KiB
C
#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µs,1 字节 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;
|
||
}
|