fix(rf): true single-edge P3.6 trigger + pull-down, restore edge decode
Root cause found via datasheet: PxIM0/PxIM1 has no dual-edge mode, only falling/rising/low-level/high-level. (IM1=1,IM0=1) was actually high-level interrupt, refiring continuously for the whole high-pulse duration (confirmed by scope: no real HF noise, and edge_count=0 when P3.6 grounded). Fix: start in rising-edge mode, software-toggle to the opposite edge on every trigger (ping-pong) to emulate true dual-edge triggering, feeding exact edge direction into the incremental decode state machine (no glitch filter needed). Also switch P3.6 idle bias from pull-up to pull-down, since floating-high under the old high-level mode caused the interrupt-storm boot issue; removed the now-conflicting pull-up restore in Wakeup_Restore().
This commit is contained in:
11
App/main.c
11
App/main.c
@@ -352,10 +352,11 @@ void Port2_Isr(void) interrupt 15
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
||||||
* @details P3.6 (RF_RX_DATA) 双边沿中断现在改为常态开启 (见 RF_Init()),身兼两职:
|
* @details P3.6 (RF_RX_DATA) 单边沿触发中断现在改为常态开启 (见 RF_Init()),身兼两职:
|
||||||
* 1. 休眠期间作为唤醒源,记录 p3_wakeup_flag 供 Wakeup_Restore() 判定唤醒来源;
|
* 1. 休眠期间作为唤醒源,记录 p3_wakeup_flag 供 Wakeup_Restore() 判定唤醒来源
|
||||||
* 2. DEBUG 对照实验期间,RF_HandleEdgeInterrupt() 只做纯边沿计数,不参与解码
|
* (上升沿/下降沿均支持掉电唤醒);
|
||||||
* (真正的解码已改回 EV1527_Decode() 里的阻塞轮询实现,用于对照验证)。
|
* 2. 正常运行期间驱动 RF_HandleEdgeInterrupt() 增量解码 EV1527 信号 (内部会在
|
||||||
|
* 每次触发后软件翻转到相反边沿,模拟双边沿触发)。
|
||||||
* 不再像旧版那样在首次触发后自行关闭 P3INTE,否则第 1 项唤醒功能会在第一次边沿后失效。
|
* 不再像旧版那样在首次触发后自行关闭 P3INTE,否则第 1 项唤醒功能会在第一次边沿后失效。
|
||||||
*/
|
*/
|
||||||
void Port3_Isr(void) interrupt 16
|
void Port3_Isr(void) interrupt 16
|
||||||
@@ -363,5 +364,5 @@ void Port3_Isr(void) interrupt 16
|
|||||||
EAXFR = 1; // 使能访问扩展寄存器
|
EAXFR = 1; // 使能访问扩展寄存器
|
||||||
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
|
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
|
||||||
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
|
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
|
||||||
RF_HandleEdgeInterrupt(); // DEBUG: 纯边沿计数,不参与解码
|
RF_HandleEdgeInterrupt(); // 驱动 EV1527 边沿增量解码状态机
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,9 +390,8 @@ void Wakeup_Restore(void)
|
|||||||
P0INTE = 0x00;
|
P0INTE = 0x00;
|
||||||
P2INTE = 0x00;
|
P2INTE = 0x00;
|
||||||
P3INTE = 0x40;
|
P3INTE = 0x40;
|
||||||
// 恢复休眠前被 Enter_Low_Power_Sleep() 关闭的 P3.6 内部上拉:LR690L 在 SHUT=1 期间数据脚
|
// P3.6 的悬空钳位已改用内部下拉 (P3PD,在 RF_Init() 里一次性永久开启,Power Down 期间
|
||||||
// 高阻悬空,配合常态开启的 P3.6 中断会引发中断风暴,唤醒后必须立刻用上拉钳住电平
|
// SFR 状态保持不变,无需在这里重新配置);此处不再恢复上拉,避免上拉与下拉同时使能相互打架
|
||||||
P3PU |= 0x40;
|
|
||||||
|
|
||||||
P0WKUE = 0x00;
|
P0WKUE = 0x00;
|
||||||
P2WKUE = 0x00;
|
P2WKUE = 0x00;
|
||||||
|
|||||||
388
Drivers/rf.c
388
Drivers/rf.c
@@ -26,8 +26,9 @@ extern volatile u16 ms_tick; // 全局毫秒滴答 (0~999 循环),用于半成
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与状态
|
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与状态
|
||||||
* @details 同时在此完成 Timer0 自由运行时间戳基准与 P3.6 (RF_RX_DATA) 常态双边沿中断的
|
* @details 同时在此完成 Timer0 自由运行时间戳基准与 P3.6 (RF_RX_DATA) 常态单边沿触发中断的
|
||||||
* 一次性开机配置,供 RF_HandleEdgeInterrupt() 增量解码状态机全程使用。
|
* 一次性开机配置 (软件乒乓切换模拟双边沿,见 RF_HandleEdgeInterrupt()),
|
||||||
|
* 供增量解码状态机全程使用。
|
||||||
*/
|
*/
|
||||||
void RF_Init(void)
|
void RF_Init(void)
|
||||||
{
|
{
|
||||||
@@ -45,9 +46,11 @@ void RF_Init(void)
|
|||||||
P3M0 &= ~(1 << 6);
|
P3M0 &= ~(1 << 6);
|
||||||
P3M0 |= ((1 << 5) | (1 << 7));
|
P3M0 |= ((1 << 5) | (1 << 7));
|
||||||
|
|
||||||
// DEBUG: 临时去掉 P3.6 内部上拉,测试是否是它干扰了真实信号接收 (isr_jump.asm 向量重定向
|
// P3.6 内部下拉:SHUT=1(芯片关断)期间 DATA 脚高阻悬空,下拉把悬空状态钳在低电平。
|
||||||
// 修好后,即使没有上拉、芯片关闭时引脚悬空乱跳也只是空耗 CPU,不会再导致重启,可以安全去掉测试)
|
// 之前误用上拉,配合当年错误的"高电平中断"配置,悬空高电平会导致中断风暴——
|
||||||
// P3PU |= 0x40;
|
// 现在确认端口中断硬件只有下降沿/上升沿/低电平/高电平四选一,没有真正的"双边沿"模式,
|
||||||
|
// 已改为下面的软件乒乓单边沿切换方案,配下拉才是让悬空态落在"不触发"一侧的正确选择。
|
||||||
|
P3PD |= 0x40;
|
||||||
|
|
||||||
// 3. 设置系统默认的初始电平
|
// 3. 设置系统默认的初始电平
|
||||||
RF_TX = 0; // 断开发射天线通路
|
RF_TX = 0; // 断开发射天线通路
|
||||||
@@ -58,10 +61,13 @@ void RF_Init(void)
|
|||||||
// 4. 启动 Timer0 常驻自由运行,作为边沿中断增量解码的时间戳基准 (实现见 Drivers/timer.c)
|
// 4. 启动 Timer0 常驻自由运行,作为边沿中断增量解码的时间戳基准 (实现见 Drivers/timer.c)
|
||||||
Timer0_Init();
|
Timer0_Init();
|
||||||
|
|
||||||
// 5. P3.6 (RF_RX_DATA) 双边沿中断常态开启 (原来只在 Enter_Low_Power_Sleep 里临时开启,
|
// 5. P3.6 (RF_RX_DATA) 中断常态开启。硬件 PxIM1/PxIM0 只支持下降沿/上升沿/低电平/高电平
|
||||||
// 现在改为开机后永久开启,休眠时复用同一路中断做唤醒源,不再需要单独切换)
|
// 四选一,没有"双边沿都触发"的模式;(IM1=1,IM0=1) 实际是"高电平中断",会在整个高电平
|
||||||
P3IM1 |= 0x40;
|
// 期间不断重复触发,这正是之前边沿计数异常偏高(远超协议理论值)的真正原因。
|
||||||
P3IM0 |= 0x40; // 双边沿触发
|
// 现改为初始配置成"上升沿触发",配合 RF_HandleEdgeInterrupt() 里的软件乒乓切换
|
||||||
|
// (每次触发后立即翻转到相反边沿) 来模拟真正的双边沿触发,每次真实翻转只精确响应一次。
|
||||||
|
P3IM1 &= ~0x40; // IM1=0
|
||||||
|
P3IM0 |= 0x40; // IM0=1 => (0,1) 上升沿触发,先等待同步头/数据位高电平的起始
|
||||||
P3INTF = 0x00; // 清除挂起的中断标志
|
P3INTF = 0x00; // 清除挂起的中断标志
|
||||||
P3INTE |= 0x40; // 开启 P3.6 中断允许 (全局 EA 由 Timer1_Init() 统一开启)
|
P3INTE |= 0x40; // 开启 P3.6 中断允许 (全局 EA 由 Timer1_Init() 统一开启)
|
||||||
}
|
}
|
||||||
@@ -106,168 +112,250 @@ void RF_Delay_us(u16 us)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* DEBUG 实验:验证方案 —— 解码逻辑临时切回已验证工作的轮询版本,
|
* EV1527 边沿中断增量解码状态机 (真正的单边沿触发版本)
|
||||||
* P3.6 边沿中断只做纯计数,不参与解码。轮询每次解码成功时,把"解码开始
|
* @details 硬件 PxIM1/PxIM0 只有下降沿/上升沿/低电平/高电平四选一,没有"双边沿都触发"
|
||||||
* 前"和"解码成功后"的中断边沿计数快照打印出来,跟 EV1527 协议理论边沿数
|
* 的模式,之前 (IM1=1,IM0=1) 其实是"高电平中断",会在一整段高电平期间反复
|
||||||
* (同步头 2 个 + 24 位数据 × 每位 2 个 = 50 个) 对比,用来验证:"轮询解码
|
* 重触发——这才是边沿计数远超协议理论值 50 的真正原因,与信号噪声无关(示波器已
|
||||||
* 成功的这段时间里,真实硬件边沿次数是否稳定、是否符合协议"。
|
* 确认信号干净)。现在改为 RF_Init() 里先配置成单一边沿(上升沿)触发,每次触发后
|
||||||
|
* 在本函数里立即软件翻转到相反边沿(乒乓切换),从而让每一次真实的电平翻转都精确
|
||||||
|
* 触发一次中断,不多不少。由于翻转前的 P3IM0 状态本身就明确告诉了我们"刚刚发生的
|
||||||
|
* 是哪种边沿",不再需要像旧版那样读取电平猜测方向,也不需要毛刺过滤器。
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
|
|
||||||
// 纯计数用的边沿计数器 (由 RF_HandleEdgeInterrupt 递增,不做任何解码状态机处理)
|
// 计时阈值 (单位: tick,0.5us/tick),与原阻塞轮询版 EV1527_Decode 的判定区间保持一致
|
||||||
static volatile u32 dbg_isr_edge_count = 0;
|
#define RF_SYNC_HIGH_MIN_TICKS (50UL * 2UL) // 50us
|
||||||
|
#define RF_SYNC_HIGH_MAX_TICKS (2500UL * 2UL) // 2500us
|
||||||
|
#define RF_SYNC_LOW_MIN_TICKS (1500UL * 2UL) // 1500us
|
||||||
|
#define RF_SYNC_LOW_MAX_TICKS (60000UL* 2UL) // 60000us
|
||||||
|
#define RF_T_MIN_TICKS (100UL * 2UL) // 100us
|
||||||
|
#define RF_T_MAX_TICKS (1200UL * 2UL) // 1200us
|
||||||
|
|
||||||
|
#define RF_ISR_STATE_IDLE 0 // 搜索同步头候选高电平
|
||||||
|
#define RF_ISR_STATE_SYNC_SEEN 1 // 已见候选同步高电平,等待其后的低电平确认
|
||||||
|
#define RF_ISR_STATE_COLLECTING 2 // 正在逐位收集 24 位数据
|
||||||
|
|
||||||
|
#define RF_DECODE_TIMEOUT_MS 200 // 半成品帧看门狗超时 (远大于一帧最坏情况约 95ms 的总耗时)
|
||||||
|
|
||||||
|
static volatile u8 rf_isr_state = RF_ISR_STATE_IDLE;
|
||||||
|
static volatile u32 rf_isr_last_edge_ts = 0;
|
||||||
|
static volatile u32 rf_isr_pending_high = 0; // 候选同步头高电平宽度 / 收集阶段暂存的本位高电平宽度
|
||||||
|
static volatile u32 rf_isr_bit_T = 0; // 本帧的基准单位时间 T
|
||||||
|
static volatile u8 rf_isr_bit_index = 0;
|
||||||
|
static volatile u32 rf_isr_addr_acc = 0;
|
||||||
|
static volatile u8 rf_isr_dat_acc = 0;
|
||||||
|
static volatile u16 rf_isr_state_enter_ms = 0;
|
||||||
|
|
||||||
|
// 中断解码出的结果,供 EV1527_Decode() 非阻塞取用
|
||||||
|
static volatile bit rf_decoded_ready = 0;
|
||||||
|
static volatile u32 rf_decoded_addr = 0;
|
||||||
|
static volatile u8 rf_decoded_data = 0;
|
||||||
|
|
||||||
|
// 简易运行计数器:解码成功帧数 / 因校验不通过而放弃的候选帧数,供 'e' 调试指令查看
|
||||||
|
static volatile u32 dbg_frame_count = 0;
|
||||||
|
static volatile u32 dbg_abort_count = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief DEBUG: P3.6 边沿中断处理 —— 本实验阶段只做计数,不参与任何解码逻辑
|
* @brief DEBUG: 打印边沿解码状态机的简易运行计数器
|
||||||
*/
|
|
||||||
void RF_HandleEdgeInterrupt(void)
|
|
||||||
{
|
|
||||||
dbg_isr_edge_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief DEBUG: 打印 P3.6 边沿中断纯计数快照 (本实验阶段唯一保留的排查计数器)
|
|
||||||
*/
|
*/
|
||||||
void RF_PrintDebugCounters(void)
|
void RF_PrintDebugCounters(void)
|
||||||
{
|
{
|
||||||
Uart_SendString("[RF-DBG] isr_edge_count=");
|
Uart_SendString("[RF-DBG] state=");
|
||||||
Uart_SendHex32(dbg_isr_edge_count);
|
Uart_SendHex8(rf_isr_state);
|
||||||
|
Uart_SendString(" bit_idx=");
|
||||||
|
Uart_SendHex8(rf_isr_bit_index);
|
||||||
|
Uart_SendString(" frames=");
|
||||||
|
Uart_SendHex32(dbg_frame_count);
|
||||||
|
Uart_SendString(" aborts=");
|
||||||
|
Uart_SendHex32(dbg_abort_count);
|
||||||
Uart_SendString("\r\n");
|
Uart_SendString("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void RF_ResetDecodeState(void)
|
||||||
* @brief 获取特定引脚电平状态的持续微秒时间
|
|
||||||
* @details 解码算法/阈值恢复自 commit d8df5d4 已验证工作的轮询版本,但测量方式改为对
|
|
||||||
* 常驻自由运行的 Timer0 (Drivers/timer.c) 做快照差值读取,而不是像原始实现那样
|
|
||||||
* 直接停表/清零/重启 TR0——因为现在 Timer0 已经是 RF_Delay_us()/TX 发射计时共用的
|
|
||||||
* 自由运行时间戳源,若在这里停表清零,会导致解码尝试之后紧接的 TX 发射 RF_Delay_us()
|
|
||||||
* 永远等不到时间戳前进而卡死。快照差值法不触碰 TR0/TL0/TH0,与自由运行架构兼容,
|
|
||||||
* 且不影响本次实验要验证的解码算法本身。
|
|
||||||
*/
|
|
||||||
u16 GetPulseDuration(u8 state, u16 timeout_us)
|
|
||||||
{
|
{
|
||||||
u32 start = Timer0_GetTimestamp();
|
rf_isr_state = RF_ISR_STATE_IDLE;
|
||||||
u32 timeout_ticks = (u32)timeout_us * 2UL; // 0.5us/tick
|
rf_isr_bit_index = 0;
|
||||||
|
rf_isr_addr_acc = 0;
|
||||||
while (RF_RX_DATA == state)
|
rf_isr_dat_acc = 0;
|
||||||
{
|
|
||||||
if ((u32)(Timer0_GetTimestamp() - start) > timeout_ticks)
|
|
||||||
{
|
|
||||||
return 0; // 判定超时,返回 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (u16)((Timer0_GetTimestamp() - start) / 2UL); // tick -> us
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 阻塞式检测并解码一个合法的 EV1527 射频信号数据帧
|
* @brief 处理"刚刚结束的一段高电平"(即将开始新的低电平)
|
||||||
* @details DEBUG 对照实验:恢复自 commit d8df5d4 已验证工作的轮询版本实现,
|
* @param duration 刚结束的高电平宽度 (tick)
|
||||||
* 解码成功时额外打印解码前后 P3.6 中断边沿计数快照,用于验证边沿中断
|
*/
|
||||||
* 在一次成功解码期间的实际触发次数是否稳定、是否符合 EV1527 协议理论值 (50)。
|
static void RF_HandleHighSegmentEnd(u32 duration)
|
||||||
|
{
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE)
|
||||||
|
{
|
||||||
|
// 空闲搜索阶段:只关心刚结束的高电平脉宽是否落在同步头候选区间
|
||||||
|
if (duration >= RF_SYNC_HIGH_MIN_TICKS && duration <= RF_SYNC_HIGH_MAX_TICKS)
|
||||||
|
{
|
||||||
|
rf_isr_pending_high = duration;
|
||||||
|
rf_isr_state = RF_ISR_STATE_SYNC_SEEN;
|
||||||
|
rf_isr_state_enter_ms = ms_tick;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_COLLECTING)
|
||||||
|
{
|
||||||
|
// 本位的高电平段先暂存,等对应的低电平段结束后一起判定这一位
|
||||||
|
rf_isr_pending_high = duration;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SYNC_SEEN 阶段理应只等待低电平段 (乒乓切换保证了 高/低 严格交替出现),
|
||||||
|
// 不会在这里收到高电平结束事件;防御性地放弃当前候选,避免状态卡死
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
dbg_abort_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理"刚刚结束的一段低电平"(即将开始新的高电平)
|
||||||
|
* @param duration 刚结束的低电平宽度 (tick)
|
||||||
|
*/
|
||||||
|
static void RF_HandleLowSegmentEnd(u32 duration)
|
||||||
|
{
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return; // 同步头候选还没确立前,空闲期低电平时长任意,不关心
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_SYNC_SEEN)
|
||||||
|
{
|
||||||
|
// 期望这段低电平是同步头的 31T 低电平
|
||||||
|
if (duration >= RF_SYNC_LOW_MIN_TICKS && duration <= RF_SYNC_LOW_MAX_TICKS &&
|
||||||
|
rf_isr_pending_high >= RF_T_MIN_TICKS && rf_isr_pending_high <= RF_T_MAX_TICKS)
|
||||||
|
{
|
||||||
|
rf_isr_bit_T = rf_isr_pending_high; // 基准单位时间 T 取自同步头高电平宽度
|
||||||
|
rf_isr_bit_index = 0;
|
||||||
|
rf_isr_addr_acc = 0;
|
||||||
|
rf_isr_dat_acc = 0;
|
||||||
|
rf_isr_state = RF_ISR_STATE_COLLECTING;
|
||||||
|
rf_isr_state_enter_ms = ms_tick;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 不满足同步条件,放弃候选,回到空闲搜索
|
||||||
|
dbg_abort_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_COLLECTING)
|
||||||
|
{
|
||||||
|
u32 total = rf_isr_pending_high + duration;
|
||||||
|
if (total < rf_isr_bit_T * 3UL || total > rf_isr_bit_T * 6UL)
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 单个数据位周期超出 3T~6T 范围,判定畸变,放弃本帧
|
||||||
|
dbg_abort_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_pending_high > duration * 2UL)
|
||||||
|
{
|
||||||
|
// 高电平明显长于低电平:逻辑 "1"
|
||||||
|
if (rf_isr_bit_index < 20) rf_isr_addr_acc = (rf_isr_addr_acc << 1) | 1;
|
||||||
|
else rf_isr_dat_acc = (rf_isr_dat_acc << 1) | 1;
|
||||||
|
}
|
||||||
|
else if (duration > rf_isr_pending_high * 2UL)
|
||||||
|
{
|
||||||
|
// 低电平明显长于高电平:逻辑 "0"
|
||||||
|
if (rf_isr_bit_index < 20) rf_isr_addr_acc = (rf_isr_addr_acc << 1);
|
||||||
|
else rf_isr_dat_acc = (rf_isr_dat_acc << 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 电平脉冲比值不合规,放弃本帧
|
||||||
|
dbg_abort_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_isr_bit_index++;
|
||||||
|
if (rf_isr_bit_index >= 24)
|
||||||
|
{
|
||||||
|
// 24 位全部收齐,输出解码结果供主循环取用
|
||||||
|
rf_decoded_addr = rf_isr_addr_acc;
|
||||||
|
rf_decoded_data = rf_isr_dat_acc;
|
||||||
|
rf_decoded_ready = 1;
|
||||||
|
dbg_frame_count++;
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief P3.6 (RF_RX_DATA) 边沿中断处理 (由 main.c 的 Port3_Isr 调用)
|
||||||
|
* @details 每次触发时,P3IM0 的当前值就是"触发前配置的是哪种边沿"——因为软件每次都会
|
||||||
|
* 在处理完当前事件后立即翻转到相反边沿,所以这个值精确对应"刚刚发生的是上升沿
|
||||||
|
* 还是下降沿",不需要额外读取电平判断方向。翻转完成后再把 (方向, 距上次边沿的
|
||||||
|
* 时间差) 喂给增量解码状态机。
|
||||||
|
*/
|
||||||
|
void RF_HandleEdgeInterrupt(void)
|
||||||
|
{
|
||||||
|
u32 now;
|
||||||
|
u32 duration;
|
||||||
|
bit was_rising;
|
||||||
|
|
||||||
|
now = Timer0_GetTimestamp();
|
||||||
|
was_rising = (P3IM0 & 0x40) ? 1 : 0;
|
||||||
|
duration = now - rf_isr_last_edge_ts;
|
||||||
|
rf_isr_last_edge_ts = now;
|
||||||
|
|
||||||
|
// 乒乓切换触发边沿,为下一次真实翻转做准备 (模拟双边沿触发)
|
||||||
|
if (was_rising) {
|
||||||
|
P3IM0 &= ~0x40; // 现在已进入高电平,下一次要抓它结束 (改为下降沿触发)
|
||||||
|
} else {
|
||||||
|
P3IM0 |= 0x40; // 现在已进入低电平,下一次要抓它结束 (改为上升沿触发)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_rising) {
|
||||||
|
RF_HandleLowSegmentEnd(duration); // duration = 刚结束的低电平宽度
|
||||||
|
} else {
|
||||||
|
RF_HandleHighSegmentEnd(duration); // duration = 刚结束的高电平宽度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 半成品帧看门狗:若中断状态机卡在同步头确认/数据收集阶段太久 (信号中途被干扰打断),
|
||||||
|
* 超时后强制复位回空闲搜索,防止后续真实信号被误当作"半成品帧的延续"而永远解不出来。
|
||||||
|
*/
|
||||||
|
static void RF_CheckDecodeWatchdog(void)
|
||||||
|
{
|
||||||
|
u16 elapsed;
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ms_tick >= rf_isr_state_enter_ms) {
|
||||||
|
elapsed = ms_tick - rf_isr_state_enter_ms;
|
||||||
|
} else {
|
||||||
|
elapsed = 1000 + ms_tick - rf_isr_state_enter_ms;
|
||||||
|
}
|
||||||
|
if (elapsed > RF_DECODE_TIMEOUT_MS) {
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 非阻塞地检测并取出一个已解码完成的 EV1527 射频信号数据帧
|
||||||
|
* @details 真正的解码工作已经在 P3.6 边沿中断 (RF_HandleEdgeInterrupt) 中增量完成;
|
||||||
|
* 本函数只是原子地检查并取出中断解码结果,可在主循环中高频调用而不会造成阻塞。
|
||||||
*/
|
*/
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
||||||
{
|
{
|
||||||
u16 high_time, low_time;
|
bit ready;
|
||||||
u8 i;
|
|
||||||
u32 addr = 0;
|
|
||||||
u8 dat = 0;
|
|
||||||
u16 T;
|
|
||||||
u16 idata raw_h[24];
|
|
||||||
u16 idata raw_l[24];
|
|
||||||
u32 edge_before;
|
|
||||||
|
|
||||||
edge_before = dbg_isr_edge_count; // DEBUG: 解码尝试开始前的边沿计数快照
|
RF_CheckDecodeWatchdog();
|
||||||
|
|
||||||
// ====== 1. 同步头引导脉冲捕获 ======
|
EA = 0; // 与中断互斥,保证 32 位地址与结果标志的原子性读取
|
||||||
{
|
ready = rf_decoded_ready;
|
||||||
u16 wait_cnt = 0;
|
if (ready) {
|
||||||
while (RF_RX_DATA == 1)
|
*out_addr = rf_decoded_addr;
|
||||||
{
|
*out_data = rf_decoded_data;
|
||||||
Delay_us(5);
|
rf_decoded_ready = 0;
|
||||||
wait_cnt++;
|
|
||||||
if (wait_cnt > 2000) // 10 毫秒超时保护
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
EA = 1;
|
||||||
|
|
||||||
wait_cnt = 0;
|
return ready;
|
||||||
while (RF_RX_DATA == 0)
|
|
||||||
{
|
|
||||||
Delay_us(5);
|
|
||||||
wait_cnt++;
|
|
||||||
if (wait_cnt > 4000) // 20 毫秒超时保护
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 捕获引导头高电平时间
|
|
||||||
high_time = GetPulseDuration(1, 2500);
|
|
||||||
if (high_time < 50 || high_time > 2500) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 捕获引导头后续的 31T 低电平时间
|
|
||||||
low_time = GetPulseDuration(0, 60000);
|
|
||||||
if (low_time < 1500 || low_time > 60000) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步引导脉冲的基准时间 T 计算 (引导脉冲高电平宽度即为 1T)
|
|
||||||
T = high_time;
|
|
||||||
if (T < 100 || T > 800) return 0; // 自适应范围限幅: 100us ~ 800us
|
|
||||||
|
|
||||||
// ====== 2. 依次解析 24 个数据位脉宽 ======
|
|
||||||
for (i = 0; i < 24; i++)
|
|
||||||
{
|
|
||||||
raw_h[i] = GetPulseDuration(1, 2000);
|
|
||||||
if (raw_h[i] == 0) return 0;
|
|
||||||
|
|
||||||
raw_l[i] = GetPulseDuration(0, 2000);
|
|
||||||
if (raw_l[i] == 0) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== 3. 双端比值解调判定数据内容 ======
|
|
||||||
for (i = 0; i < 24; i++)
|
|
||||||
{
|
|
||||||
u16 total = raw_h[i] + raw_l[i];
|
|
||||||
if (total < T * 3 || total > T * 6) return 0; // 单个数据位周期必须在 3T~6T 之间
|
|
||||||
|
|
||||||
if (raw_h[i] > raw_l[i] * 2)
|
|
||||||
{
|
|
||||||
// 高电平明显长于低电平,代表逻辑 "1"
|
|
||||||
if (i < 20)
|
|
||||||
addr = (addr << 1) | 1;
|
|
||||||
else
|
|
||||||
dat = (dat << 1) | 1;
|
|
||||||
}
|
|
||||||
else if (raw_l[i] > raw_h[i] * 2)
|
|
||||||
{
|
|
||||||
// 低电平明显长于高电平,代表逻辑 "0"
|
|
||||||
if (i < 20)
|
|
||||||
addr = (addr << 1);
|
|
||||||
else
|
|
||||||
dat = (dat << 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0; // 电平脉冲比值不合规,判定数据帧畸变,解码作废
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验无误,输出解调数据,并打印本次解码期间的边沿计数对照
|
|
||||||
{
|
|
||||||
u32 edge_after = dbg_isr_edge_count;
|
|
||||||
Uart_SendString("[RF-ISR-CHECK] edges_before=");
|
|
||||||
Uart_SendHex32(edge_before);
|
|
||||||
Uart_SendString(" edges_after=");
|
|
||||||
Uart_SendHex32(edge_after);
|
|
||||||
Uart_SendString(" delta=");
|
|
||||||
Uart_SendHex32(edge_after - edge_before);
|
|
||||||
Uart_SendString(" (EV1527 理论值=50)\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_addr = addr;
|
|
||||||
*out_data = dat;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
20
Drivers/rf.h
20
Drivers/rf.h
@@ -40,20 +40,21 @@ void EV1527_TxFrame(u32 addr, u8 dat);
|
|||||||
void EV1527_Transmit(u32 addr, u8 dat);
|
void EV1527_Transmit(u32 addr, u8 dat);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief DEBUG 对照实验:P3.6 (RF_RX_DATA) 边沿中断处理,由 main.c 的 Port3_Isr 在每次电平跳变时调用
|
* @brief P3.6 (RF_RX_DATA) 单边沿触发中断处理,由 main.c 的 Port3_Isr 在每次电平跳变时调用
|
||||||
* @details 本实验阶段只做纯边沿计数 (dbg_isr_edge_count++),不参与任何解码逻辑;真正的解码
|
* @details 硬件 PxIM1/PxIM0 只支持下降沿/上升沿/低电平/高电平四选一,没有真正的"双边沿"模式;
|
||||||
* 已改回 EV1527_Decode() 内的阻塞轮询实现。用于验证:一次成功轮询解码期间,
|
* 本函数在每次触发后都会立即把 P3IM0 翻转到相反边沿(乒乓切换),模拟出双边沿触发的效果,
|
||||||
* 真实硬件触发的边沿中断次数是否稳定、是否符合 EV1527 协议理论值 (50)。
|
* 并据此驱动内部的三态增量解码状态机 (空闲搜索 -> 同步头确认 -> 逐位收集),凑齐 24 位后
|
||||||
|
* 把结果写入内部标志供 EV1527_Decode() 非阻塞取用。全程不做任何阻塞等待。
|
||||||
*/
|
*/
|
||||||
void RF_HandleEdgeInterrupt(void);
|
void RF_HandleEdgeInterrupt(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 阻塞式检测并解码一个合法的 EV1527 射频信号数据帧 (DEBUG 对照实验:恢复自 commit d8df5d4 的轮询实现)
|
* @brief 非阻塞地检查一次 EV1527 射频信号是否已解码完成
|
||||||
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
|
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
|
||||||
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
|
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
|
||||||
* @return bit 1-本次调用解码出一帧有效数据;0-未检测到合法数据帧 (含超时/校验失败)
|
* @return bit 1-本次调用取到了一帧新解码出的有效数据;0-当前尚无新结果
|
||||||
* @note 解码成功时会额外通过串口打印本次解码期间 RF_HandleEdgeInterrupt 的边沿计数快照
|
* @note 真正的解码工作已经在 P3.6 边沿中断 (RF_HandleEdgeInterrupt) 中增量完成,
|
||||||
* (解码前/解码后/差值),用于对照验证。
|
* 本函数只是原子地检查并取出中断解码结果,可在主循环中高频调用而不会造成任何阻塞。
|
||||||
*/
|
*/
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
||||||
|
|
||||||
@@ -68,8 +69,7 @@ void RF_DiagnosticMode(char choice);
|
|||||||
void Loopback_Test(void);
|
void Loopback_Test(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief DEBUG: 打印 P3.6 边沿中断纯计数快照 (本实验阶段唯一保留的排查计数器)
|
* @brief DEBUG: 打印边沿解码状态机的简易运行计数器 (当前状态/位索引/成功帧数/放弃次数)
|
||||||
* @note 排查用,定位问题完成后可以连同 rf.c 里的计数器一起删除
|
|
||||||
*/
|
*/
|
||||||
void RF_PrintDebugCounters(void);
|
void RF_PrintDebugCounters(void);
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,33 @@
|
|||||||
|
|
||||||
状态:代码已完成,待实机烧录,触发一次真实发射并观察 `[RF-ISR-CHECK]` 日志的 `delta` 是否稳定等于 50。
|
状态:代码已完成,待实机烧录,触发一次真实发射并观察 `[RF-ISR-CHECK]` 日志的 `delta` 是否稳定等于 50。
|
||||||
|
|
||||||
|
**实测结果**:连续 4 次独立发射,`delta` 稳定在 6963~7043 之间(约理论值 50 的 140 倍,四次误差仅约 1%),远超"偶发毛刺"能解释的量级,且波动极小,不像随机噪声,更像由代码本身某个确定性行为决定。用户随后用示波器直接实测 P3.6 波形,**确认没有对应的高频噪声/振铃**,彻底排除了 2.6/2.7 节以来"信号有毛刺、需要软件去抖"的整个分析方向。
|
||||||
|
|
||||||
|
### 2.9 真正根因定位:`PxIM0/PxIM1` 没有"双边沿触发"模式,`(1,1)` 实际是"高电平中断"
|
||||||
|
|
||||||
|
在排除毛刺假设后,用户质疑"会不会中断接错了引脚"。核对 `App/isr_jump.asm`/`P3IM0`/`P3IM1`/`P3INTE` 全仓库唯一赋值点、原理图 Pin19=P3.6=RF_RX_DATA 网络标签,确认引脚本身没有接错。随后查阅《STC32G 系列技术手册》"15.1.3 端口中断模式配置寄存器 (PxIM0,PxIM1)"真值表,发现关键事实:
|
||||||
|
|
||||||
|
| PnIM1.x | PnIM0.x | 中断模式 | 掉电唤醒支持 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 | 0 | 下降沿中断 | 支持 |
|
||||||
|
| 0 | 1 | 上升沿中断 | 支持 |
|
||||||
|
| 1 | 0 | 低电平中断 | 不支持 |
|
||||||
|
| 1 | 1 | 高电平中断 | 不支持 |
|
||||||
|
|
||||||
|
这颗芯片的端口中断硬件**只有下降沿/上升沿/低电平/高电平四选一,根本没有"双边沿都触发"这个模式**。而 `RF_Init()` 里一直写的是 `P3IM1|=0x40; P3IM0|=0x40;`——对照表格,这实际配置的是**高电平中断**,跟代码注释"双边沿触发"完全对不上:只要 P3.6 处于高电平,中断就会在整段高电平期间被硬件持续、大量地重新触发,这才是边沿计数远超协议理论值(50)、且四次测量高度稳定(由代码执行速度决定,而非随机噪声)的真正原因,从一开始就与信号质量无关。用户随后做了接地实验直接验证:P3.6 直接短接到地,`isr_edge_count` 恒为 0(满足不了"高电平"这个触发条件);正常收码时该值持续变化——与"高电平中断"模型完全吻合。
|
||||||
|
|
||||||
|
同时发现一个关联问题:`P3.6` 还兼职做掉电唤醒源,而上表显示"高电平中断"**不支持掉电唤醒**,说明之前 `P3.6` 唤醒功能大概率也没有真正按预期工作。
|
||||||
|
|
||||||
|
### 2.10 修复:软件乒乓单边沿切换模拟双边沿触发 + P3.6 内部下拉
|
||||||
|
|
||||||
|
由于硬件不提供真正的双边沿模式,改为标准的软件模拟方案:`RF_Init()` 里初始配置成单一边沿(上升沿)触发;`RF_HandleEdgeInterrupt()` 每次触发后,先根据触发前的 `P3IM0` 值判断刚刚发生的是上升沿还是下降沿(乒乓切换保证了高/低电平段严格交替出现,不需要再读引脚电平猜测方向),再立即把 `P3IM0` 翻转到相反边沿,为下一次真实翻转做准备。这样每次真实电平翻转都会精确触发一次中断,不多不少,不再需要任何毛刺过滤器。
|
||||||
|
|
||||||
|
顺带修正一个关联的悬空钳位问题:`P3.6` 内部上拉 (`P3PU`) 改为内部下拉 (`P3PD`),在 `RF_Init()` 里一次性永久开启——`SHUT=1`(芯片关断)时 `DATA` 脚高阻悬空,配合"高电平中断"误配置,之前的上拉方案正好会把悬空态钳在会触发中断的那一侧,是当年开机中断风暴/背光闪烁问题的更深层成因;改用下拉后悬空态落在"不触发"一侧。同时清理了 `App/system.c` 里 `Wakeup_Restore()` 遗留的、每次唤醒都重新打开 P3.6 上拉的代码——留着会导致上拉和下拉同时使能、相互打架,现已移除(下拉在 `RF_Init()` 一次性配置好,Power Down 期间 SFR 状态不丢失,休眠/唤醒不需要重新配置)。
|
||||||
|
|
||||||
|
改动范围:`Drivers/rf.c`(重写 `RF_Init` 引脚上下拉与初始边沿配置、重写 `RF_HandleEdgeInterrupt` 为乒乓单边沿版本、恢复三态增量解码状态机 `RF_HandleHighSegmentEnd`/`RF_HandleLowSegmentEnd`、`RF_CheckDecodeWatchdog`、非阻塞 `EV1527_Decode()`,移除本次调试实验用的 `GetPulseDuration`/阻塞轮询版 `EV1527_Decode`)、`Drivers/rf.h`(同步更新函数注释)、`App/main.c`(`Port3_Isr` 注释同步)、`App/system.c`(`Wakeup_Restore()` 移除上拉恢复)。
|
||||||
|
|
||||||
|
状态:代码已完成,待实机烧录验证——重点关注解码是否能稳定成功,以及 `RF_PrintDebugCounters()` 里 `aborts` 计数是否合理(偶发因真实干扰打断可以理解,持续大量 abort 则说明状态机或阈值仍有问题)。
|
||||||
|
|
||||||
## 六、阶段 1 实现备注
|
## 六、阶段 1 实现备注
|
||||||
|
|
||||||
* 语言字节与传感器数据库共用同一 Flash 扇区(偏移地址为 `sizeof(sensor_list)`,当前为 352,扇区剩余空间足够),复用现有 `Load_Database`/`Save_Database`,未新增独立扇区。
|
* 语言字节与传感器数据库共用同一 Flash 扇区(偏移地址为 `sizeof(sensor_list)`,当前为 352,扇区剩余空间足够),复用现有 `Load_Database`/`Save_Database`,未新增独立扇区。
|
||||||
|
|||||||
Reference in New Issue
Block a user