新增 usr_app 产品逻辑:手动>定时>条件仲裁、条件控温湿、定时任务、数码管显示、 按键/LED、风扇档位、NVM 掉电恢复、配对与开关机,上电按规格恢复上次状态。 新增 usr_periph:AHT20、CT1642、板级引脚与硬件封装;见宝协议 cond_config 改为 7 字节编解码,并接入产品 DP。 补充《温湿度风扇_固件需求规格书》,工程加入对应源文件。 .gitignore 忽略 WorkBuddy/Cursor 本地会话
279 lines
9.3 KiB
C
279 lines
9.3 KiB
C
/******************************************************************************
|
||
* @file app_disp.c
|
||
* @brief CBR3 数码管显示实现
|
||
* @author cyWu <1917507415@qq.com>
|
||
* @date 2026.08.29
|
||
* @version V1.1.0
|
||
* @history
|
||
* - V1.0.0, 2026.08.29, cyWu, 首次发布
|
||
* - V1.0.1, 2026.08.29, cyWu, 改动态扫描:20ms 节拍只算数据写缓冲,
|
||
* 另起 BOARD_DISP_SCAN_MS 快节拍定时器轮询点亮 1 位,解决闪烁
|
||
* - V1.1.0, 2026.08.31, cyWu, P1 加告警闪烁:温度/湿度管独立 500ms 亮灭,
|
||
* BOARD_DISP_ALARM_TEST 宏可强制模拟超限;fault 时 "--" 优先
|
||
*
|
||
* @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 灭,与吹风解耦;
|
||
* 闪烁触发源由 app_disp_set_temp/humi_alarm 喂入(P2 条件引擎),
|
||
* 传感器故障(连续 3 次失败)优先显示 "--" 不闪。
|
||
******************************************************************************/
|
||
|
||
#include "app_disp.h"
|
||
#include "app_th.h"
|
||
#include "app_power.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"
|
||
|
||
static uint8_t s_inited;
|
||
|
||
/* 4 位段码缓冲,下标顺序 = com 顺序:0 温度十 / 1 温度个 / 2 湿度十 / 3 湿度个 */
|
||
static uint8_t s_disp_buf[4];
|
||
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
|
||
};
|
||
static uint8_t s_scan_idx; /* 动态扫描轮询指针 0..3 */
|
||
|
||
/* 告警闪烁(P1,规格书 8.2):比较成立侧 500ms 亮/500ms 灭 */
|
||
static uint8_t s_temp_alarm; /* 1=温度比较成立(P2 条件引擎喂) */
|
||
static uint8_t s_humi_alarm; /* 1=湿度比较成立(P2 条件引擎喂) */
|
||
static uint8_t s_blink_phase; /* 0=亮半周期,1=灭半周期 */
|
||
static uint16_t s_blink_ms; /* 半周期内累计毫秒 */
|
||
|
||
/* 段码索引: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_inited) {
|
||
return;
|
||
}
|
||
s_inited = 1;
|
||
s_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_inited) {
|
||
return;
|
||
}
|
||
|
||
/* 闪烁相位推进:满 BOARD_DISP_BLINK_MS 切换亮/灭半周期 */
|
||
s_blink_ms = (uint16_t)(s_blink_ms + dt);
|
||
if (s_blink_ms >= BOARD_DISP_BLINK_MS) {
|
||
s_blink_ms = 0;
|
||
s_blink_phase ^= 1;
|
||
}
|
||
|
||
#if BOARD_DISP_ALARM_TEST == 1
|
||
s_temp_alarm = 1; /* P1 调试:强制温度管闪 */
|
||
#elif BOARD_DISP_ALARM_TEST == 2
|
||
s_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_temp_alarm && s_blink_phase) {
|
||
t1 = SEG_OFF;
|
||
t0 = SEG_OFF;
|
||
}
|
||
if (s_humi_alarm && s_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 无总线竞争,
|
||
* 扫描回调无需锁、无跳过。
|
||
* 软关机流程:4 位全灭(com=0x00 → 所有 Cx 高,位选全关)。
|
||
* @return 无
|
||
*/
|
||
void app_disp_scan(void)
|
||
{
|
||
#if !BOARD_DISP_ENABLE
|
||
return;
|
||
#endif
|
||
if (!s_inited) {
|
||
return;
|
||
}
|
||
|
||
/* 软关机流程:数码管锁存灭(规格书 5.1:风扇 0、数码管锁存灭、LED 灭) */
|
||
if (app_power_poweroff_pending()) {
|
||
bsp_ct1642_send_frame(0x00, 0x00); /* com=0x00 → 4 位全灭 */
|
||
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_scan_idx], 0xF);
|
||
s_scan_idx = (uint8_t)((s_scan_idx + 1) % 4);
|
||
#else
|
||
/* 正常显示(0) / 位选验证(2):4 位分显不同数字,必须分时轮询。
|
||
* 鬼影(切位杂段)靠 ct1642_delay 8→2 提速缩短杂段窗口来减轻。 */
|
||
bsp_ct1642_send_frame(s_disp_buf[s_scan_idx], s_com_tbl[s_scan_idx]);
|
||
s_scan_idx = (uint8_t)((s_scan_idx + 1) % 4);
|
||
#endif
|
||
}
|
||
|
||
/**
|
||
* @brief 设置温度比较成立标志:成立时温度管 500ms 闪烁(P2 条件引擎喂)
|
||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||
* @return 无
|
||
*/
|
||
void app_disp_set_temp_alarm(uint8_t en)
|
||
{
|
||
s_temp_alarm = en ? 1 : 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 设置湿度比较成立标志:成立时湿度管 500ms 闪烁(P2 条件引擎喂)
|
||
* @param en 1=成立(闪),0=不成立(常亮数值)
|
||
* @return 无
|
||
*/
|
||
void app_disp_set_humi_alarm(uint8_t en)
|
||
{
|
||
s_humi_alarm = en ? 1 : 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 温度或湿度管是否处于告警闪烁
|
||
* @return 1=任一侧告警
|
||
*/
|
||
uint8_t app_disp_is_alarm(void)
|
||
{
|
||
return (s_temp_alarm || s_humi_alarm) ? 1 : 0;
|
||
}
|