TPAFE驱动闭环E-44/E-45/E-58取数契约,固化E-59门禁,adc_task挂进Keil工程

This commit is contained in:
2026-10-10 03:46:50 +08:00
parent 66b6cdf425
commit 5c978465c9
12 changed files with 247 additions and 127 deletions
+8 -4
View File
@@ -11,15 +11,19 @@
## 硬件接口
- **并行 16 位**:PG0–PG15 数据总线 / **PD3 RD**(读选通)/ **PD4 CONVST**(转换启动)/ **PD7 BUSY**(转换中)
- **并行 16 位**:PG0–PG15 数据总线 / **PC8 RD**(读选通)/ **PC9 CONVST**(转换启动)/ **PC7 BUSY**(转换中)/ **PC6 FRSTDATA**(首通道标记,板上 R24 4.7k 上拉)/ **PA12·PA11·PA8 = OS0·OS1·OS2**(过采样选择)——V0.1 网表口径(V0.0 曾为 PD3/PD4/PD7,已作废)
- 部分引脚为 GPIO 模拟时序(非硬件 SPI/FSMC)
## 关键点
- **首次转换在 `app_main_init` 末尾由 `tpafe5160_start_conv_irq` 启动**(中断驱动)
- 采样数据由 `App/task/adc_task.c` 消费处理
- **首次转换在 `app_main_init` 末尾由 `tpafe5160_start_conv_irq` 启动**(中断驱动);**无周期重触发** → 现只采到一帧(登记簿 P1-1)
- **取数只用 `tpafe5160_snapshot(int16_t *dst, uint8_t count)`**(**仅任务上下文**,内部临界区原子拷贝;返回 1=新帧 / 0=无新帧 / -1=参数无效)。配套的 `tpafe5160_frame_seq()` 给帧序号、`tpafe5160_frstdata_err()` 给"FRSTDATA 无跳变"帧计数
- ⚠️ **不要用缓冲区指针取数**:旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已于 2026-10-10 **删除**(审计 E-45 / E-58)——`get_buf` 只给指针、任务再逐通道拷,中间被 BUSY EXTI 抢占就拷回**两帧混合**数据;`s_ready` 电平标志在 `start_conv_irq` 里被清 0,等于**用重触发去丢帧**。现由**帧序号**(只增不清)+ **临界区快照**取代
- **`tpafe5160_init()` 做首帧有效性自检**(E-44):完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `TPAFE5160_FRSTDATA_ERR`(-3) 并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片
- 采样数据的消费者 `App/task/adc_task.c` **仍是空桩**(已挂进 `MDK-ARM/*.uvprojx`,尚未在 `Src/freertos.c` 建任务)
- CONVST→BUSY→RD 三段时序对**建立/保持时间敏感**,改动需对照芯片手册
- ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`)
- ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`);接消费者前的强制门禁见根 `AGENTS.md`「接 ADC / RS-485 消费者前的强制门禁」
- ⚠️ **通道语义不是通道号**:`buf[i]` 对应哪个电气量必须查 `docs/硬件平台/ADC通道映射说明.md`(`buf[2]=IA`、`buf[5]=UN`、`buf[6]=UA`);V0.1 上 8 路电气量分属两片转换器属硬件缺陷(N-18)→ **禁止**跨转换器做相位/功率/功率因数类运算
## 已知问题
+123 -46
View File
@@ -8,12 +8,20 @@
* 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册
* 2026-07-17 王建锋 增加 EXTI 中断读取模式
* 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变)
* 2026-10-10 王建锋 消费者接入前置修复(E-44 / E-45 / E-58),详见 tpafe5160.h
*/
/* 头文件包含区 */
#include <string.h> /* memcpy:原子快照 */
#include "tpafe5160.h"
#include "main.h"
/* taskENTER_CRITICAL:把"选缓冲 + 拷数据"收进一个不可分割动作(E-58)。
* 与 App/log/console_log.c、Drivers/BSP/NET/net_socket.c 同一写法,仅任务上下文可用 */
#include "FreeRTOS.h"
#include "task.h"
/*
* 调试输出配置
*/
@@ -53,18 +61,20 @@
/* ======================== 私有函数声明 ======================== */
static void tpafe5160_read_channels(uint8_t count, int16_t *buf);
static int tpafe5160_read_channels(uint8_t count, int16_t *buf);
/* ======================== 公共函数定义 ======================== */
/*
* 函数功能:初始化 TPAFE5160(设置过采样、等待就绪)
* 函数功能:初始化 TPAFE5160(设置过采样、等待就绪、首帧有效性自检)
* 入口参数:无
* 返回值:0 - 成功,-2 - BUSY 超时
* 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(芯片未驱动数据总线)
* 限定条件:CubeMX 已完成 GPIO 初始化
* 函数说明:1. 设置过采样为无过采样 (000)
* 2. 确保 RD 为高、CONVST 为低
* 3. 等待 BUSY 释放(转换空闲)
* 4. 采一帧并校验 FRSTDATA 有跳变(E-44:旧实现只看 BUSY,芯片死亡
* 时照样报 Init OK,此后所有读数都是上拉/总线残值——静默死亡)
*/
int tpafe5160_init(void) {
/* 设置默认过采样:无过采样 (OS[2:0] = 000) */
@@ -78,9 +88,24 @@ int tpafe5160_init(void) {
int ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS);
if (ret != TPAFE5160_OK) {
DBG_ERROR("Init wait BUSY timeout");
} else {
DBG_INFO("Init OK");
return ret;
}
/* 首帧有效性自检(E-44):一次完整转换 + 读取,确认芯片真的在响应 RD */
int16_t probe[TPAFE5160_CH_NUM];
tpafe5160_start_conv();
ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS);
if (ret != TPAFE5160_OK) {
DBG_ERROR("Init probe BUSY timeout");
return ret;
}
if (tpafe5160_read_channels(TPAFE5160_CH_NUM, probe) != TPAFE5160_OK) {
ret = TPAFE5160_FRSTDATA_ERR;
DBG_ERROR("Init FRSTDATA no-toggle (chip not driving DB bus)");
return ret;
}
DBG_INFO("Init OK");
return ret;
}
@@ -142,15 +167,19 @@ uint8_t tpafe5160_is_busy(void) {
/*
* 函数功能:读取全部8通道转换结果
* 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL
* 返回值:0 - 成功,-2 - BUSY 超时
* 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信)
* 限定条件:GPIO 已初始化
* 函数说明:1. 启动转换并等待 BUSY 释放
* 2. 连续8次拉低RD读取各通道数据
* 3. 通过 FRSTDATA 验证第一通道
* 3. 校验 FRSTDATA 在这一帧内发生跳变(E-44)
*/
int tpafe5160_read_all(int16_t *buf) {
int ret = 0;
if (buf == NULL) {
return TPAFE5160_ERROR;
}
/* 启动转换 */
tpafe5160_start_conv();
@@ -161,8 +190,12 @@ int tpafe5160_read_all(int16_t *buf) {
return ret;
}
/* 连续读取8个通道 */
tpafe5160_read_channels(TPAFE5160_CH_NUM, buf);
/* 连续读取8个通道(内含 FRSTDATA 有效性校验) */
ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, buf);
if (ret != TPAFE5160_OK) {
DBG_ERROR("read_all FRSTDATA no-toggle (data not trustworthy)");
return TPAFE5160_FRSTDATA_ERR;
}
return TPAFE5160_OK;
}
@@ -171,7 +204,7 @@ int tpafe5160_read_all(int16_t *buf) {
* 函数功能:读取指定通道的转换结果
* 入口参数:channel - 通道号 uint8_t 0 - 7
* value - 输出指针 int16_t* 不为 NULL
* 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时
* 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时,-3 - FRSTDATA 无跳变
* 限定条件:GPIO 已初始化
* 函数说明:启动转换并等待完成后,连续读取至指定通道
*/
@@ -220,10 +253,10 @@ uint16_t tpafe5160_read_bus(void) {
/* ======================== 私有函数定义 ======================== */
/*
* 函数功能:通过 RD 脉冲连续读取多个通道数据
* 函数功能:通过 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 寄存器一次读取
@@ -232,9 +265,19 @@ uint16_t tpafe5160_read_bus(void) {
* GPIO 写操作 ~6ns + 端口延迟 ~18ns → RD 引脚实际翻转约 24ns 后
* t14 数据建立 ≤ 21ns → 数据在 RD 下降沿后 21ns 内有效
* 5 个 NOP (~30ns) 覆盖建立时间
*
* E-44(数据有效性):FRSTDATA 由芯片主动驱动,板上 R24 4.7k 上拉(V0.1 网表)。
* 手册 Parallel Data Read:FRSTDATA 随首个 RD 下降沿变高标记通道 1 可读,t26/t27
* 给出"RD 下降沿 → FRSTDATA 高/低"两个跳变。判据刻意**不锁死具体是哪个 RD 边沿
* 拉高/拉低**(本板未实测确认边沿次序),只要求"一帧读下来必须发生跳变":
* 芯片死亡 / 不响应 RD 时该脚被上拉钉死为高 → 全程无跳变 → 判失败。
* 旧实现完全不看这根脚(宏 TPAFE5160_FRSTDATA_READ 有定义、代码零引用),
* 却在注释里承诺"通过 FRSTDATA 验证第一通道",芯片死亡时返回 OK + 常量垃圾。
*/
static void tpafe5160_read_channels(uint8_t count, int16_t *buf) {
static int 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] */
@@ -245,16 +288,30 @@ static void tpafe5160_read_channels(uint8_t count, int16_t *buf) {
/* RD 上升沿:准备下一通道 */
TPAFE5160_RD_HIGH_DLY();
/* FRSTDATA 跳变观测(每次 RD 之后采一次) */
if (((TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u) != lvl0) {
toggled = 1;
}
}
return toggled ? TPAFE5160_OK : TPAFE5160_FRSTDATA_ERR;
}
/* ======================== 中断模式实现 ======================== */
/* 双缓冲区:ISR 写 s_buf_b,主循环读 s_buf_a */
/* 双缓冲区:ISR 写非活动缓冲,消费者在临界区内拷活动缓冲 */
static int16_t s_buf_a[TPAFE5160_CH_NUM];
static int16_t s_buf_b[TPAFE5160_CH_NUM];
static volatile uint8_t s_ready = 0;
static volatile uint8_t s_buf_sel = 0; /* 0 = 写 B / 读 A, 1 = 写 A / 读 B */
/* E-45:帧序号取代"就绪电平标志"。旧 s_ready 有三个写者,其中 start_conv_irq
* 在触发前清 0 —— 重触发这个动作本身就会抹掉一帧还没被消费的数据(高速采样
* 系统性丢样,且无从发现)。序号只增不清,消费者比较序号即可 */
static volatile uint32_t s_frame_seq = 0;
/* E-44:FRSTDATA 无跳变帧计数。中断里只加计数(不打日志),由消费者/自检侧上报 */
static volatile uint16_t s_frstdata_err = 0;
/* 消费者游标:只在任务上下文读写(snapshot 内部),中断不碰 */
static uint32_t s_consumer_seq = 0;
/*
* 函数功能:使能 BUSY EXTI 中断(运行时重使能用)
@@ -283,44 +340,64 @@ void tpafe5160_irq_disable(void) {
* 入口参数:无
* 返回值:无
* 限定条件:已调用 tpafe5160_irq_enable()
* 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据
* 函数说明:产生 CONVST 脉冲,转换完成后由 EXTI 中断自动读取 8 通道数据。
* E-45:这里**不再清就绪标志**——旧实现"触发前置 0"等于用重触发去丢帧
*/
void tpafe5160_start_conv_irq(void) {
s_ready = 0;
TPAFE5160_CONVST_PULSE();
}
/*
* 函数功能:检查是否有新的转换数据
* 函数功能:查询已完成的帧序号
* 入口参数:无
* 返回值:1 - 数据就绪,0 - 无新数据
* 返回值:uint32_t 帧序号(EXTI 每读完一帧 +1)
* 限定条件:中断模式已启用
* 函数说明:在 EXTI 回调中置位,主循环读取后需调用 tpafe5160_clear_ready() 清除
* 函数说明:见 tpafe5160.h 同名声明的契约说明(E-45)
*/
uint8_t tpafe5160_data_ready(void) {
return s_ready;
uint32_t tpafe5160_frame_seq(void) {
return s_frame_seq;
}
/*
* 函数功能:清除数据就绪标志
* 函数功能:FRSTDATA 无跳变帧的累计次数
* 入口参数:无
* 返回值:无
* 返回值:uint16_t 计数
* 限定条件:中断模式已启用
* 函数说明:主循环处理完数据后调用
* 函数说明:非 0 = 至少一帧的 8 个值不可信(E-44),消费者据此上报自检
*/
void tpafe5160_clear_ready(void) {
s_ready = 0;
uint16_t tpafe5160_frstdata_err(void) {
return s_frstdata_err;
}
/*
* 函数功能:获取数据缓冲区指针
* 入口参数:无
* 返回值:int16_t[8] 数据缓冲区的 const 指针
* 限定条件:tpafe5160_data_ready() 返回 1 时调用
* 函数说明:双缓冲切换,ISR 写另一个缓冲区,主循环安全读取当前缓冲区
* 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势)
* 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL
* count - 拷贝通道数 uint8_t 1 - 8
* 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效
* 限定条件:**任务上下文**(内部 taskENTER_CRITICAL,中断里不可调用)
* 函数说明:E-58——把"选缓冲 + 拷数据 + 记游标"收进一个临界区。旧 get_buf() 只给
* 指针、任务再逐通道拷,两步之间被 BUSY EXTI 抢占并跨过一帧周期就拷回
* 两帧混合的数据(8 通道失去同一时刻性)且无任何错误指示
*/
const int16_t* tpafe5160_get_buf(void) {
return (s_buf_sel == 0) ? s_buf_a : s_buf_b;
int tpafe5160_snapshot(int16_t *dst, uint8_t count) {
uint32_t seq;
if (dst == NULL || count == 0u || count > TPAFE5160_CH_NUM) {
return TPAFE5160_ERROR;
}
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;
}
/*
@@ -339,18 +416,18 @@ void tpafe5160_exti_handler(uint16_t GPIO_Pin) {
return;
}
/* 双缓冲切换:ISR 写与主循环读不同的缓冲区 */
int16_t *p_wr = (s_buf_sel == 0) ? s_buf_b : s_buf_a;
/* 双缓冲切换:ISR 写与消费者读不同的缓冲区 */
int16_t *p_wr = (s_buf_sel == 0u) ? s_buf_b : s_buf_a;
/* 连续读取8个通道,约 0.5µs @ 168MHz */
uint8_t i;
for (i = 0; i < TPAFE5160_CH_NUM; i++) {
TPAFE5160_RD_LOW_DLY();
p_wr[i] = (int16_t)TPAFE5160_READ_BUS();
TPAFE5160_RD_HIGH_DLY();
/* 连续读取8个通道(内含 FRSTDATA 有效性校验),约 0.5µs @ 168MHz */
int ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, p_wr);
/* 切换缓冲区并推进帧序号(E-45:序号只增不清,重触发不会抹掉未消费帧) */
s_buf_sel ^= 1u;
s_frame_seq++;
/* E-44:中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */
if (ret != TPAFE5160_OK && s_frstdata_err < 0xFFFFu) {
s_frstdata_err++;
}
/* 切换缓冲区并标记就绪 */
s_buf_sel ^= 1;
s_ready = 1;
}
+30 -17
View File
@@ -11,6 +11,9 @@
* 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册
* 2026-07-17 王建锋 增加 EXTI 中断读取模式
* 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变)
* 2026-10-10 王建锋 消费者接入前置修复(错误码传播审计 E-44 / E-45 / E-58):
* FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 +
* 原子快照 API;移除会撕裂的 get_buf/data_ready/clear_ready
*/
#ifdef __cplusplus
@@ -69,6 +72,9 @@ typedef enum {
#define TPAFE5160_OK 0
#define TPAFE5160_ERROR -1
#define TPAFE5160_BUSY_TIMEOUT -2
/* 整帧读完 FRSTDATA 无跳变 = 芯片没在驱动数据线(死亡 / 未响应 RD),
* 此时读回的是上拉/总线残值而非转换结果(E-44,见 read_channels 注释) */
#define TPAFE5160_FRSTDATA_ERR -3
/* 默认超时时间 (ms) */
#define TPAFE5160_CONV_TIMEOUT_MS 10
@@ -125,11 +131,11 @@ uint8_t tpafe5160_is_busy(void);
/*
* 函数功能:读取全部8通道转换结果
* 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL
* 返回值:0 - 成功,-2 - BUSY 超时
* 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信)
* 限定条件:GPIO 已初始化
* 函数说明:1. 启动转换并等待 BUSY 释放
* 2. 连续8次拉低RD读取各通道数据
* 3. 通过 FRSTDATA 验证第一通道
* 3. 校验 FRSTDATA 在这一帧内发生过跳变(= 芯片确实在驱动数据线,E-44)
*/
int tpafe5160_read_all(int16_t *buf);
@@ -192,31 +198,38 @@ void tpafe5160_irq_disable(void);
void tpafe5160_start_conv_irq(void);
/*
* 函数功能:检查是否有新的转换数据
* 函数功能:查询已完成的帧序号(消费者判断"有没有新帧"的唯一依据)
* 入口参数:无
* 返回值:1 - 数据就绪,0 - 无新数据
* 返回值:uint32_t 帧序号,EXTI 每读完一帧 +1
* 限定条件:中断模式已启用
* 函数说明:在 EXTI 回调中置位,主循环读取后需调用 tpafe5160_clear_ready() 清除
* 函数说明:取代旧的 data_ready/clear_ready **电平标志**(E-45)——电平标志有三个
* 写者,其中 start_conv_irq 在触发前清 0,于是"重触发"本身就会抹掉一帧
* 还没被消费的数据(高速采样系统性丢样且无从发现)。序号只增不清,
* 消费者比较 seq 即可,重触发不会丢
*/
uint8_t tpafe5160_data_ready(void);
uint32_t tpafe5160_frame_seq(void);
/*
* 函数功能:清除数据就绪标志
* 入口参数:无
* 返回值:无
* 限定条件:中断模式已启用
* 函数说明:主循环处理完数据后调用
* 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势)
* 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL
* count - 拷贝通道数 uint8_t 1 - 8
* 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效
* 限定条件:**任务上下文**调用(内部用 taskENTER_CRITICAL,不可在中断里调)
* 函数说明:E-58——旧 get_buf() 只返回缓冲区指针,任务再逐通道拷;两步之间被 BUSY
* EXTI 抢占并跨过一帧周期,拷回的就是**两帧混合**的 8 通道数据(同一时刻
* 的 8 路值互相错位),且没有任何错误指示。本函数把"选缓冲 + 拷数据 +
* 记游标"收进一个临界区(只盖 16 字节拷贝),撕裂在设计上消除
*/
void tpafe5160_clear_ready(void);
int tpafe5160_snapshot(int16_t *dst, uint8_t count);
/*
* 函数功能:获取数据缓冲区指针
* 函数功能:读取到"FRSTDATA 无跳变"帧的累计次数(芯片死亡 / 总线异常的留痕)
* 入口参数:无
* 返回值:int16_t[8] 数据缓冲区的 const 指针
* 限定条件:tpafe5160_data_ready() 返回 1 时调用
* 函数说明:缓冲区由 EXTI 回调写入,主循环只读
* 返回值:uint16_t 计数(中断里只加计数不打日志)
* 限定条件:中断模式已启用
* 函数说明:E-44——非 0 表示至少有一帧的 8 个值不可信;消费者据此上报自检
*/
const int16_t* tpafe5160_get_buf(void);
uint16_t tpafe5160_frstdata_err(void);
/*
* 函数功能:EXTI 中断处理(由 HAL_GPIO_EXTI_Callback 调用)