/* * 模块名称:TPAFE5160 16位8通道同步采样ADC驱动 * 模块功能:提供 TPAFE5160 并行接口模式下的初始化、过采样设置、转换启动、数据读取接口 * 适用平台:STM32F407ZGT6,并行16位数据总线接 GPIOG[15:0] * 作者:王建锋 * 创建日期:2026-07-17 * 修改记录: * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) * 2026-10-10 王建锋 消费者接入前置修复,详见 tpafe5160.h */ /* 头文件包含区 */ #include /* memcpy:原子快照 */ #include "tpafe5160.h" #include "main.h" /* taskENTER_CRITICAL:把"选缓冲 + 拷数据"收进一个不可分割动作。 * 与 App/log/console_log.c、Drivers/BSP/NET/net_socket.c 同一写法,仅任务上下文可用 */ #include "FreeRTOS.h" #include "task.h" /* * 调试输出配置 */ #define DBG_TAG "[ADC]" #include "dbg_log.h" /* ======================== 私有宏定义 ======================== */ /* * 并行读取时序延时 (168MHz 主频,1 NOP ≈ 5.95ns) * * TPAFE5160 并行时序要求 (VDRIVE > 2.7V): * t10 (RD 低脉宽) ≥ 22ns * t11 (RD 高脉宽) ≥ 10ns * t14 (数据访问时间) ≤ 21ns (从 RD 下降沿算起) * t15 (数据保持时间) ≥ 6ns (从 RD 下降沿算起) * * GPIO 写操作本身约 1 个 AHB 周期 (~6ns), * 加上端口延迟 (~12-18ns),实际 RD 引脚翻转滞后约 18-24ns。 * 因此 NOP 延时只需覆盖数据建立时间即可。 */ #define TPAFE5160_NOP_5() __NOP(); __NOP(); __NOP(); __NOP(); __NOP() /* RD 低脉宽延时:GPIO写(~6ns) + 5NOP(~30ns) > 22ns */ #define TPAFE5160_RD_LOW_DLY() do { TPAFE5160_RD_LOW(); TPAFE5160_NOP_5(); } while (0) /* RD 高脉宽延时:GPIO写(~6ns) + 3NOP(~18ns) > 10ns */ #define TPAFE5160_RD_HIGH_DLY() do { TPAFE5160_RD_HIGH(); __NOP(); __NOP(); __NOP(); } while (0) /* CONVST 脉冲延时:上升沿前需保证低电平,5NOP 覆盖 t5 约20ns */ #define TPAFE5160_CONVST_PULSE() do { \ TPAFE5160_CONVST_LOW(); \ TPAFE5160_NOP_5(); \ TPAFE5160_CONVST_HIGH(); \ TPAFE5160_NOP_5(); \ } while (0) /* ======================== 私有函数声明 ======================== */ static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf); /* ======================== 公共函数定义 ======================== */ /* * 函数功能:初始化 TPAFE5160(设置过采样、等待就绪、首帧有效性自检) * 入口参数:无 * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(芯片未驱动数据总线) * 限定条件:CubeMX 已完成 GPIO 初始化 * 函数说明:1. 设置过采样为无过采样 (000) * 2. 确保 RD 为高、CONVST 为低 * 3. 等待 BUSY 释放(转换空闲) * 4. 采一帧并校验 FRSTDATA 有跳变(旧实现只看 BUSY,芯片死亡 * 时照样报 Init OK,此后所有读数都是上拉/总线残值——静默死亡) */ bsp_err_t tpafe5160_init(void) { /* 设置默认过采样:无过采样 (OS[2:0] = 000) */ tpafe5160_set_os(TPAFE5160_OS_NONE); /* 确保控制引脚处于空闲状态 */ TPAFE5160_RD_HIGH(); TPAFE5160_CONVST_LOW(); /* 等待 BUSY 释放,确保上电后无残留转换 */ int ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); if (ret != BSP_OK) { DBG_ERROR("Init wait BUSY timeout"); return ret; } /* 首帧有效性自检:一次完整转换 + 读取,确认芯片真的在响应 RD */ int16_t probe[TPAFE5160_CH_NUM]; tpafe5160_start_conv(); ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); if (ret != BSP_OK) { DBG_ERROR("Init probe BUSY timeout"); return ret; } if (tpafe5160_read_channels(TPAFE5160_CH_NUM, probe) != BSP_OK) { ret = BSP_ERR_NO_DEVICE; DBG_ERROR("Init FRSTDATA no-toggle (chip not driving DB bus)"); return ret; } DBG_INFO("Init OK"); return ret; } /* * 函数功能:设置过采样率 * 入口参数:os - 过采样率枚举值 tpafe5160_os_t * 返回值:无 * 限定条件:GPIO 已初始化 * 函数说明:通过 OS[2:0] 引脚设置过采样率,在下一次 BUSY 下降沿锁存生效 */ void tpafe5160_set_os(tpafe5160_os_t os) { TPAFE5160_OS0(os & 0x01); /* OS0 = bit0 */ TPAFE5160_OS1((os >> 1) & 0x01); /* OS1 = bit1 */ TPAFE5160_OS2((os >> 2) & 0x01); /* OS2 = bit2 */ } /* * 函数功能:启动一次转换(CONVST 上升沿触发) * 入口参数:无 * 返回值:无 * 限定条件:GPIO 已初始化 * 函数说明:产生 CONVST 脉冲上升沿,启动全部8通道同步采样与转换 */ void tpafe5160_start_conv(void) { TPAFE5160_CONVST_PULSE(); } /* * 函数功能:等待转换完成 * 入口参数:timeout_ms - 超时时间 uint32_t > 0 * 返回值:0 - 转换完成,-2 - 超时 * 限定条件:已调用 tpafe5160_start_conv() * 函数说明:轮询 BUSY 引脚等待下降沿 */ bsp_err_t tpafe5160_wait_busy(uint32_t timeout_ms) { uint32_t tick_start = HAL_GetTick(); /* 等待 BUSY 释放(低电平表示空闲) */ while (TPAFE5160_BUSY_READ() == GPIO_PIN_SET) { if ((HAL_GetTick() - tick_start) >= timeout_ms) { return BSP_ERR_TIMEOUT; } } return BSP_OK; } /* * 函数功能:查询当前是否正在转换 * 入口参数:无 * 返回值:1 - 正在转换,0 - 空闲 * 限定条件:GPIO 已初始化 * 函数说明:读取 BUSY 引脚电平 */ uint8_t tpafe5160_is_busy(void) { return (TPAFE5160_BUSY_READ() == GPIO_PIN_SET) ? 1 : 0; } /* * 函数功能:读取全部8通道转换结果 * 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信) * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 * 3. 校验 FRSTDATA 在这一帧内发生跳变 */ bsp_err_t tpafe5160_read_all(int16_t *buf) { bsp_err_t ret = BSP_OK; if (buf == NULL) { return BSP_ERR_PARAM; } /* 启动转换 */ tpafe5160_start_conv(); /* 等待转换完成 */ ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); if (ret != BSP_OK) { DBG_ERROR("read_all BUSY timeout"); return ret; } /* 连续读取8个通道(内含 FRSTDATA 有效性校验) */ ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, buf); if (ret != BSP_OK) { DBG_ERROR("read_all FRSTDATA no-toggle (data not trustworthy)"); return BSP_ERR_NO_DEVICE; } return BSP_OK; } /* * 函数功能:读取指定通道的转换结果 * 入口参数:channel - 通道号 uint8_t 0 - 7 * value - 输出指针 int16_t* 不为 NULL * 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时,-3 - FRSTDATA 无跳变 * 限定条件:GPIO 已初始化 * 函数说明:启动转换并等待完成后,连续读取至指定通道 */ bsp_err_t tpafe5160_read_channel(uint8_t channel, int16_t *value) { bsp_err_t ret = BSP_OK; int16_t buf[TPAFE5160_CH_NUM] = {0}; if (channel >= TPAFE5160_CH_NUM || value == NULL) { return BSP_ERR_PARAM; } ret = tpafe5160_read_all(buf); if (ret != BSP_OK) { return ret; } *value = buf[channel]; return BSP_OK; } /* * 函数功能:原始ADC值转电压值 * 入口参数:raw - ADC原始值 int16_t 有符号补码 * 返回值:电压值 float 单位 V * 限定条件:无 * 函数说明:±5V量程时 LSB = 10V / 65536 ≈ 152.59μV * ±10V量程时 LSB = 20V / 65536 ≈ 305.18μV * 硬件 RANGE 引脚接 GND,默认 ±5V 量程 */ float tpafe5160_to_voltage(int16_t raw) { /* RANGE 接 GND → ±5V 量程,满量程 10V */ return (float)raw * (10.0f / 65536.0f); } /* * 函数功能:直接读取并行数据总线(不启动转换) * 入口参数:无 * 返回值:16位原始数据 uint16_t * 限定条件:RD 为低或 CS 与 RD 已拉低 * 函数说明:读取 GPIOG->IDR 低16位,对应 DB[15:0] */ uint16_t tpafe5160_read_bus(void) { return TPAFE5160_READ_BUS(); } /* ======================== 私有函数定义 ======================== */ /* * 函数功能:通过 RD 脉冲连续读取多个通道数据(含 FRSTDATA 有效性校验) * 入口参数:count - 要读取的通道数 uint8_t 1 - 8 * buf - 输出缓冲区 int16_t* 不为 NULL * 返回值:0 - 成功,-3 - 整帧读完 FRSTDATA 无跳变(读回值不可信) * 限定条件:转换已完成(BUSY 为低),RD 初始为高 * 函数说明:每次 RD 下降沿输出一个通道数据,按通道1~8顺序输出 * DB[15:0] 直接接 GPIOG[15:0],通过 IDR 寄存器一次读取 * * 时序关键点 (VDRIVE > 2.7V, 168MHz 主频): * GPIO 写操作 ~6ns + 端口延迟 ~18ns → RD 引脚实际翻转约 24ns 后 * t14 数据建立 ≤ 21ns → 数据在 RD 下降沿后 21ns 内有效 * 5 个 NOP (~30ns) 覆盖建立时间 * * 数据有效性:FRSTDATA 由芯片主动驱动,板上 R24 4.7k 上拉(V0.1 网表)。 * 手册 Parallel Data Read:FRSTDATA 随首个 RD 下降沿变高标记通道 1 可读,t26/t27 * 给出"RD 下降沿 → FRSTDATA 高/低"两个跳变。判据刻意**不锁死具体是哪个 RD 边沿 * 拉高/拉低**(本板未实测确认边沿次序),只要求"一帧读下来必须发生跳变": * 芯片死亡 / 不响应 RD 时该脚被上拉钉死为高 → 全程无跳变 → 判失败。 * 旧实现完全不看这根脚(宏 TPAFE5160_FRSTDATA_READ 有定义、代码零引用), * 却在注释里承诺"通过 FRSTDATA 验证第一通道",芯片死亡时返回 OK + 常量垃圾。 */ static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf) { uint8_t i; uint8_t lvl0 = (TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u; uint8_t toggled = 0; for (i = 0; i < count; i++) { /* RD 下降沿:ADC 输出当前通道数据到 DB[15:0] */ TPAFE5160_RD_LOW_DLY(); /* 读取16位并行数据 */ buf[i] = (int16_t)TPAFE5160_READ_BUS(); /* RD 上升沿:准备下一通道 */ TPAFE5160_RD_HIGH_DLY(); /* FRSTDATA 跳变观测(每次 RD 之后采一次) */ if (((TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u) != lvl0) { toggled = 1; } } return toggled ? BSP_OK : BSP_ERR_NO_DEVICE; } /* ======================== 中断模式实现 ======================== */ /* 双缓冲区:ISR 写非活动缓冲,消费者在临界区内拷活动缓冲 */ static int16_t s_buf_a[TPAFE5160_CH_NUM]; static int16_t s_buf_b[TPAFE5160_CH_NUM]; static volatile uint8_t s_buf_sel = 0; /* 0 = 写 B / 读 A, 1 = 写 A / 读 B */ /* 帧序号取代"就绪电平标志"。旧 s_ready 有三个写者,其中 start_conv_irq * 在触发前清 0 —— 重触发这个动作本身就会抹掉一帧还没被消费的数据(高速采样 * 系统性丢样,且无从发现)。序号只增不清,消费者比较序号即可 */ static volatile uint32_t s_frame_seq = 0; /* FRSTDATA 无跳变帧计数。中断里只加计数(不打日志),由消费者/自检侧上报 */ static volatile uint16_t s_frstdata_err = 0; /* 消费者游标:只在任务上下文读写(snapshot 内部),中断不碰 */ static uint32_t s_consumer_seq = 0; /* * 函数功能:使能 BUSY EXTI 中断(运行时重使能用) * 入口参数:无 * 返回值:无 * 限定条件:CubeMX 已完成 GPIO 和 NVIC 配置 * 函数说明:正常启动流程无需调用,仅在 irq_disable() 后需要重新使能时使用 */ void tpafe5160_irq_enable(void) { HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); } /* * 函数功能:关闭 BUSY EXTI 中断 * 入口参数:无 * 返回值:无 * 限定条件:已调用 tpafe5160_irq_enable() * 函数说明:仅禁用 NVIC 中断,GPIO 配置保持 CubeMX 设定 */ void tpafe5160_irq_disable(void) { HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); } /* * 函数功能:启动转换(中断模式) * 入口参数:无 * 返回值:无 * 限定条件:已调用 tpafe5160_irq_enable() * 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据。 * 这里**不再清就绪标志**——旧实现"触发前置 0"等于用重触发去丢帧 */ void tpafe5160_start_conv_irq(void) { TPAFE5160_CONVST_PULSE(); } /* * 函数功能:查询已完成的帧序号 * 入口参数:无 * 返回值:uint32_t 帧序号(EXTI 每读完一帧 +1) * 限定条件:中断模式已启用 * 函数说明:见 tpafe5160.h 同名声明的契约说明 */ uint32_t tpafe5160_frame_seq(void) { return s_frame_seq; } /* * 函数功能:FRSTDATA 无跳变帧的累计次数 * 入口参数:无 * 返回值:uint16_t 计数 * 限定条件:中断模式已启用 * 函数说明:非 0 = 至少一帧的 8 个值不可信,消费者据此上报自检 */ uint16_t tpafe5160_frstdata_err(void) { return s_frstdata_err; } /* * 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势) * 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL * count - 拷贝通道数 uint8_t 1 - 8 * 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效 * 限定条件:**任务上下文**(内部 taskENTER_CRITICAL,中断里不可调用) * 函数说明:把"选缓冲 + 拷数据 + 记游标"收进一个临界区。旧 get_buf() 只给 * 指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回 * 两帧混合的数据(8 通道失去同一时刻性)且无任何错误指示 */ int tpafe5160_snapshot(int16_t *dst, uint8_t count) { uint32_t seq; if (dst == NULL || count == 0u || count > TPAFE5160_CH_NUM) { return BSP_ERR_PARAM; } seq = s_frame_seq; if (seq == s_consumer_seq) { return 0; /* 无新帧 */ } taskENTER_CRITICAL(); (void)memcpy(dst, (s_buf_sel == 0u) ? s_buf_a : s_buf_b, (size_t)count * sizeof(int16_t)); s_consumer_seq = seq; taskEXIT_CRITICAL(); return 1; } /* * 函数功能:BUSY 下降沿 EXTI 处理(由 HAL_GPIO_EXTI_Callback 调用) * 入口参数:GPIO_Pin - 触发中断的引脚号 * 返回值:无 * 限定条件:BUSY EXTI 已使能 * 函数说明:转换完成后自动读取 8 通道数据到缓冲区,耗时约 0.5µs (168MHz) */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { tpafe5160_exti_handler(GPIO_Pin); } void tpafe5160_exti_handler(uint16_t GPIO_Pin) { if (GPIO_Pin != TP_BUSY_Pin) { return; } /* 双缓冲切换:ISR 写与消费者读不同的缓冲区 */ int16_t *p_wr = (s_buf_sel == 0u) ? s_buf_b : s_buf_a; /* 连续读取8个通道(内含 FRSTDATA 有效性校验),约 0.5µs @ 168MHz */ int ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, p_wr); /* 切换缓冲区并推进帧序号(序号只增不清,重触发不会抹掉未消费帧) */ s_buf_sel ^= 1u; s_frame_seq++; /* 中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */ if (ret != BSP_OK && s_frstdata_err < 0xFFFFu) { s_frstdata_err++; } }