/****************************************************************************** * @file app_disp.c * @brief CBR3 数码管显示实现 * @author cyWu <1917507415@qq.com> * @date 2026.09.02 * @version V2.0.0(规格书 V3.0.0):闪烁按第 8.2 节改——有**有效联动配置** * 就按是否落在 [min,max] 闭区间闪(温度/湿度管独立 500ms 亮灭), * 出范围就闪、与风扇是否在转、联动是否被手动作废无关;无有效 * 联动不因温湿度闪;fault 时 "--" 优先。闪烁源由 app_linkage * (500ms 任务上下文)喂 app_disp_set_temp/humi_alarm。 * * @note ⚠ CT1642 内部只有单个 12 位锁存器(4 位选 + 8 段码),无按位显示 RAM、 * 不自动扫描,一次只点亮当前锁存的那 1 位。要让 4 位都亮且不闪,必须由 MCU * 持续轮询:本模块用 app_disp_run()(20ms)算好 4 位段码放进 s_disp.buf, * 用 app_disp_scan()(BOARD_DISP_SCAN_MS)每次点亮 1 位、4 位轮询。 * 告警闪烁(规格书 8.2):比较成立侧 500ms 亮/500ms 灭,与吹风解耦; * 传感器故障(连续 3 次失败)优先显示 "--" 不闪。 ******************************************************************************/ #include "app_disp.h" #include "app_th.h" #include "bsp_ct1642.h" #include "board_pin.h" #define LOG_TAG_CONST APP #define LOG_TAG "[APP_DISP]" #define LOG_INFO_ENABLE #include "debug.h" /** 显示运行时:段码缓冲 + 扫描指针 + 8.2 告警闪烁 */ typedef struct { uint8_t inited; uint8_t buf[4]; /* 段码,下标 = com 顺序:0 温度十 / 1 温度个 / 2 湿度十 / 3 湿度个 */ uint8_t scan_idx; /* 动态扫描轮询指针 0..3 */ uint8_t temp_alarm; /* 1=温度超范围(app_linkage 喂) */ uint8_t humi_alarm; /* 1=湿度超范围(app_linkage 喂) */ uint8_t blink_phase; /* 0=亮半周期,1=灭半周期 */ uint16_t blink_ms; /* 半周期内累计毫秒 */ } AppDispCtx_t; static AppDispCtx_t s_disp; static const uint8_t s_com_tbl[4] = { BOARD_DISP_COM_TEMP_TEN, BOARD_DISP_COM_TEMP_ONE, BOARD_DISP_COM_HUMI_TEN, BOARD_DISP_COM_HUMI_ONE }; /* 段码索引:bsp_ct1642_seg(0~9)=数字,10='-',11=灭 */ #define SEG_MINUS 10 #define SEG_OFF 11 /** * @brief 温度值 → 两位段码(0~99;<0 显示 -9~-1;<-9 钳位 -9;>99 钳位 99) * @param temp_x10 温度 ×10 * @param out_t1 十位段码索引 * @param out_t0 个位段码索引 * @return 无 */ static void disp_split_temp(int16_t temp_x10, uint8_t *out_t1, uint8_t *out_t0) { int16_t t = temp_x10 / 10; /* 取整数 ℃(P0 不显示小数) */ uint8_t tens; uint8_t ones; if (t < 0) { if (t < -9) { t = -9; /* 钳位 */ } *out_t1 = SEG_MINUS; *out_t0 = (uint8_t)(-t % 10); return; } if (t > 99) { t = 99; } tens = (uint8_t)(t / 10); ones = (uint8_t)(t % 10); *out_t1 = tens; *out_t0 = ones; } /** * @brief 湿度值 → 两位段码(0~99;>99 钳位 99) * @param humi 湿度 % * @param out_h1 十位段码索引 * @param out_h0 个位段码索引 * @return 无 */ static void disp_split_humi(uint8_t humi, uint8_t *out_h1, uint8_t *out_h0) { uint8_t h = humi; if (h > 99) { h = 99; } *out_h1 = (uint8_t)(h / 10); *out_h0 = (uint8_t)(h % 10); } /** * @brief 初始化显示模块;注册快节拍动态扫描定时器 * @return 无 */ void app_disp_init(void) { #if !BOARD_DISP_ENABLE log_info("disp disabled\n"); return; #endif if (s_disp.inited) { return; } s_disp.inited = 1; s_disp.scan_idx = 0; bsp_ct1642_init(); /* PG0(CLK)/PG1(DATA) 推挽输出 */ /* 默认写死 25 / 60,供 P0 台上扫段验证(规格书 P0 验收) */ s_disp.buf[0] = bsp_ct1642_seg(2); s_disp.buf[1] = bsp_ct1642_seg(5); s_disp.buf[2] = bsp_ct1642_seg(6); s_disp.buf[3] = bsp_ct1642_seg(0); log_info("disp init, scan %ums\n", (unsigned)BOARD_DISP_SCAN_MS); /* 注意:快节拍动态扫描定时器在 usr_jb_init 里注册(usr_timer_add 声明在那) */ } /** * @brief 业务节拍(20ms):计算 4 位要显示什么,写入段码缓冲(不直接打管) * @note 软关机由 app_disp_scan 处理。本函数只更新 s_disp.buf。 * @return 无 */ void app_disp_run(uint16_t dt) { #if !BOARD_DISP_ENABLE (void)dt; return; #endif if (!s_disp.inited) { return; } /* 闪烁相位推进:满 BOARD_DISP_BLINK_MS 切换亮/灭半周期 */ s_disp.blink_ms = (uint16_t)(s_disp.blink_ms + dt); if (s_disp.blink_ms >= BOARD_DISP_BLINK_MS) { s_disp.blink_ms = 0; s_disp.blink_phase ^= 1; } #if BOARD_DISP_ALARM_TEST == 1 s_disp.temp_alarm = 1; /* P1 调试:强制温度管闪 */ #elif BOARD_DISP_ALARM_TEST == 2 s_disp.humi_alarm = 1; /* P1 调试:强制湿度管闪 */ #endif #if BOARD_DISP_SCAN_TEST == 1 /* 段扫描:4 位同时点亮同一段,每 600ms 切 A→B→...→G→灭→全亮,定性段码位序 */ { static uint32_t tick; static uint8_t step; static const uint8_t seg_seq[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x7F }; if (++tick >= 30) { /* 30*20ms=600ms */ tick = 0; step = (uint8_t)((step + 1) % (sizeof(seg_seq) / sizeof(seg_seq[0]))); } uint8_t s = seg_seq[step]; s_disp.buf[0] = s; s_disp.buf[1] = s; s_disp.buf[2] = s; s_disp.buf[3] = s; return; } #elif BOARD_DISP_SCAN_TEST == 2 /* 位扫描:4 位分别显示 1/2/3/4,定性 C1~C4 与物理管位对应 */ s_disp.buf[0] = bsp_ct1642_seg(1); s_disp.buf[1] = bsp_ct1642_seg(2); s_disp.buf[2] = bsp_ct1642_seg(3); s_disp.buf[3] = bsp_ct1642_seg(4); return; #endif if (app_th_is_fault()) { /* 连续失败 3 次:双管 "--"(规格书 8.1;故障优先于闪烁,8.2) */ uint8_t m = bsp_ct1642_seg(SEG_MINUS); s_disp.buf[0] = m; s_disp.buf[1] = m; s_disp.buf[2] = m; s_disp.buf[3] = m; } else if (app_th_get_valid()) { /* 有有效采样:显示真实值;比较成立且当前在灭半周期 → 该管熄灭 */ uint8_t t1, t0, h1, h0; disp_split_temp(app_th_get_temp_x10(), &t1, &t0); disp_split_humi(app_th_get_humi(), &h1, &h0); if (s_disp.temp_alarm && s_disp.blink_phase) { t1 = SEG_OFF; t0 = SEG_OFF; } if (s_disp.humi_alarm && s_disp.blink_phase) { h1 = SEG_OFF; h0 = SEG_OFF; } s_disp.buf[0] = bsp_ct1642_seg(t1); s_disp.buf[1] = bsp_ct1642_seg(t0); s_disp.buf[2] = bsp_ct1642_seg(h1); s_disp.buf[3] = bsp_ct1642_seg(h0); } else { /* 尚未有有效采样:写死 25 / 60,供 P0 台上扫段验证(规格书 P0 验收) */ s_disp.buf[0] = bsp_ct1642_seg(2); s_disp.buf[1] = bsp_ct1642_seg(5); s_disp.buf[2] = bsp_ct1642_seg(6); s_disp.buf[3] = bsp_ct1642_seg(0); } } /** * @brief 显示动态扫描(BOARD_DISP_SCAN_MS 快节拍定时器回调):点亮 1 位、4 位轮询 * @note 必须短、不可阻塞。每次只发 1 帧,靠高频轮询让 4 位视觉同亮不闪。 * IO 分离后 CT1642 独占 PG0/PG1,与 AHT20 的 PC4/PC5 无总线竞争, * 扫描回调无需锁、无跳过。 * @return 无 */ void app_disp_scan(void) { #if !BOARD_DISP_ENABLE return; #endif if (!s_disp.inited) { return; } #if BOARD_DISP_SCAN_TEST == 1 /* 段扫描:4 位同显一段。⭐ 值变化才刷(无鬼影),com=0xF 四位全选同显。 */ static uint8_t seg = 0; if (seg == s_disp.buf[0]) { return; } seg = s_disp.buf[0]; bsp_ct1642_send_frame(s_disp.buf[s_disp.scan_idx], 0xF); s_disp.scan_idx = (uint8_t)((s_disp.scan_idx + 1) % 4); #else /* 正常显示(0) / 位选验证(2):4 位分显不同数字,必须分时轮询。 * 鬼影(切位杂段)靠 ct1642_delay 8→2 提速缩短杂段窗口来减轻。 */ bsp_ct1642_send_frame(s_disp.buf[s_disp.scan_idx], s_com_tbl[s_disp.scan_idx]); s_disp.scan_idx = (uint8_t)((s_disp.scan_idx + 1) % 4); #endif } /** * @brief 设置温度超范围标志:超范围时温度管 500ms 闪烁(app_linkage 喂, * 规格书 8.2:T>Tmax 或 THmax 或 H