接入温湿度风扇产品固件(仲裁/条件/定时/显示),并补齐规格书与外设驱动

新增 usr_app 产品逻辑:手动>定时>条件仲裁、条件控温湿、定时任务、数码管显示、
按键/LED、风扇档位、NVM 掉电恢复、配对与开关机,上电按规格恢复上次状态。
新增 usr_periph:AHT20、CT1642、板级引脚与硬件封装;见宝协议 cond_config
改为 7 字节编解码,并接入产品 DP。
补充《温湿度风扇_固件需求规格书》,工程加入对应源文件。
.gitignore 忽略 WorkBuddy/Cursor 本地会话
This commit is contained in:
2026-08-31 15:17:27 +08:00
parent 4cb7418a02
commit 5cc7e9dbd6
53 changed files with 7028 additions and 513 deletions
+156
View File
@@ -0,0 +1,156 @@
/******************************************************************************
* @file board_pin.h
* @brief CBR3 温湿度风扇板级引脚、电平与时序(换板只改本文件)
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布(P0 Bring-up
*
* @note 产品 GPIO 全部集中到本文件;板级 board_jl701n_demo_cfg.h 只保留
* UART0_TX=PG6 一项改动。CT1642 用 PG0(CLK)/PG1(DATA)AHT20 用
* PC4(SCL)/PC5(SDA),两路 IO 独立、无总线共享(2026-08-29 改板飞线分离,
* 原 PC4/PC5 共线 + bsp_bus 分时机制整体删除)。
******************************************************************************/
#ifndef __BOARD_PIN_H__
#define __BOARD_PIN_H__
#include "asm/gpio.h"
/*============================================================================*/
/* 功能开关:某路硬件异常时可单独关掉,不影响配网 */
/* 0 时对应模块 init/run 直接 returnGPIO 不配,日志打一次 xxx disabled */
/*============================================================================*/
#define BOARD_LED_ENABLE 1 /**< 1=状态 LED PC2(低亮) */
#define BOARD_KEY_ENABLE 1 /**< 1=按键 PB1(低按下) */
#define BOARD_FAN_ENABLE 1 /**< 1=风扇 MCPWM PC3(高导通) */
#define BOARD_DISP_ENABLE 1 /**< 1=CT1642 数码管(PG0/PG1 独立) */
#define BOARD_TH_ENABLE 1 /**< 1=AHT20 温湿度(PC4/PC5 独立) */
#define BOARD_MODE_ENABLE 1 /* 通风/强力状态机;P1 开 */
#define BOARD_COND_ENABLE 1 /* 条件引擎;P2 开 */
#define BOARD_TIMER_ENABLE 1 /* 定时引擎;P2 开 */
#define BOARD_NVM_ENABLE 1 /* VM 持久化;P1 开 */
/*============================================================================*/
/* GPIO */
/*============================================================================*/
#define BOARD_UART_TX_PIN IO_PORTG_06 /**< 调试 UART TX(只发不占 PC5 */
#define BOARD_DISP_CLK_PIN IO_PORTG_00 /**< CT1642 CLK(独立,飞线 G0 */
#define BOARD_DISP_DATA_PIN IO_PORTG_01 /**< CT1642 DATA(独立,飞线 G1 */
#define BOARD_TH_SCL_PIN IO_PORTC_04 /**< AHT20 SCLK(独立,推挽驱动) */
#define BOARD_TH_SDA_PIN IO_PORTC_05 /**< AHT20 SDA(开漏+内部上拉,外部 4.7~10k 双保险) */
#define BOARD_FAN_PIN IO_PORTC_03 /**< 风扇 PWM,高电平导通,MCPWM */
#define BOARD_LED_PIN IO_PORTC_02 /**< 状态 LED,低电平亮 */
#define BOARD_KEY_PIN IO_PORTB_01 /**< 按键,低电平按下 */
#define BOARD_LED_ON_LEVEL 0 /**< LED 点亮电平:0=低亮 */
#define BOARD_FAN_ON_LEVEL 1 /**< 风扇有效电平:1=高导通 */
#define BOARD_KEY_ACTIVE_LEVEL 0 /**< 按键有效电平:0=按下为低 */
/*============================================================================*/
/* 按键 / 开机 */
/*============================================================================*/
#define BOARD_KEY_CLICK_MS 1000 /**< 开机后:按下 <1s 松开为短按切档 */
#define BOARD_KEY_POWER_MS 3000 /**< 关机态:按住 3s 开机;开机态:按住 3s 关机 */
#define BOARD_KEY_PAIR_MS 5000 /**< 关机态:按住 5s 开机并进配对 */
#define BOARD_KEY_OTA_MS 12000 /**< 关机态:按住 12s 开机并进 OTA */
#define BOARD_KEY_DEBOUNCE_MS 20 /**< 按键消抖 */
#define BOARD_PAIR_TIMEOUT_MS (120u * 1000u) /**< 配对超时 120s,失败后保持开机 */
/*============================================================================*/
/* LED(单灯 PC2,事件表见 led_manager.c */
/*============================================================================*/
#define BOARD_LED_FAST_ON_MS 100 /**< 配对快闪:亮 */
#define BOARD_LED_FAST_OFF_MS 100 /**< 配对快闪:灭 */
#define BOARD_LED_OTA_ON_MS 200 /**< OTA 闪:亮 */
#define BOARD_LED_OTA_OFF_MS 200 /**< OTA 闪:灭 */
#define BOARD_LED_PAIR_OK_MS 3000 /**< 配对成功常亮时长(允许直接常亮) */
#define BOARD_LED_PAIR_FAIL_MS 3000 /**< 配对失败 200ms 闪提示时长 */
#define BOARD_LED_STANDBY_ON 1 /**< 1=开机常亮 */
/*============================================================================*/
/* 风扇 PWM */
/*============================================================================*/
#define BOARD_FAN_PWM_HZ 20000 /**< MCPWM 频率(Hz)20k 避开可听啸叫 */
#define BOARD_FAN_DUTY_GEAR0 0 /**< 0% */
#define BOARD_FAN_DUTY_GEAR1 5000 /**< 50%(用户拍板,2026-08-31 */
#define BOARD_FAN_DUTY_GEAR2 7500 /**< 75%(用户拍板,2026-08-31 */
#define BOARD_FAN_DUTY_GEAR3 10000 /**< 100% */
/*============================================================================*/
/* 显示(CT1642 */
/*============================================================================*/
/**
* CT1642 动态扫描节拍(ms)。
* ⚠ CT1642 内部只有单个 12 位锁存器(4 位选 + 8 段码),无按位显示 RAM、不自动扫描,
* 一次只点亮当前锁存的那 1 位。必须 MCU 持续轮询 4 位才能 4 位同亮、不闪。
* 本节拍每 BOARD_DISP_SCAN_MS 点亮 1 位、4 位轮询 → 每位 4*BOARD_DISP_SCAN_MS 一轮。
* 4ms:每位 16ms/62.5Hz、25% 占空,均匀不闪,且不饿死 priority=1 扫描中断里的
* 主循环喂狗(实测 2ms 节拍会导致上电硬复位,禁止)。
* 注:SCAN_TEST=1 段扫描用"值变化才刷"机制,本节拍不敏感;正常显示(0)与
* 位选验证(2)用分时轮询,必须 ≤4ms 否则肉眼可见闪烁。
*/
#define BOARD_DISP_SCAN_MS 4
#define BOARD_DISP_BLINK_MS 500 /**< 告警闪烁半周期(P1 用) */
/**
* 告警闪烁调试宏(P1 台上验证用,正常固件保持 0):
* 0 = 关:闪烁由 app_disp_set_temp/humi_alarm 驱动(P2 条件引擎喂)
* 1 = 强制温度管闪(模拟温度比较成立)
* 2 = 强制湿度管闪(模拟湿度比较成立)
*/
#define BOARD_DISP_ALARM_TEST 0
/**
* CT1642 协议(18bit/帧,CLK 上升沿采样,DATA 仅在 CLK=低时改变):
* [0:3] C1-C4 位选(低有效,入参 com 取反后先发 C1)
* [4:9] 6 位保留位(固定填 1)
* [10:17] Q2-Q9 段码(LSB 先,seg 的 bit0 → Q2 引脚)
* 发完 18bitCLK=高 时 DATA 0→1 = 锁存;CLK=低 时 DATA 0→1 = 输出刷新。
* 刷新一位数码管 = 发一帧;4 位连发 4 帧(每帧仅一个 Cx 位选生效)。
*
* ⚠ P0 扫段:段码/位选与硬件物理映射为"待实测"值,台上用 SCAN_TEST 扫清后修正。
* seg_tbl 段码位定义:bit7=DP bit6=G bit5=F bit4=E bit3=D bit2=C bit1=B bit0=A
* (bit0 先发 → 落到 Q2 引脚;PCB 上 Q2 接哪段即"软件 A 段"对应的物理段)
* COM 位选 → 物理管位映射(2026-08-29 SCAN_TEST=2 实测定型):
* C1=湿度十位 C2=湿度个位 C3=温度十位 C4=温度个位(默认假定温/湿相反,已对调)
*/
#define BOARD_DISP_COM_TEMP_TEN 0x04 /**< C3 = 温度十位(实测定型) */
#define BOARD_DISP_COM_TEMP_ONE 0x08 /**< C4 = 温度个位(实测定型) */
#define BOARD_DISP_COM_HUMI_TEN 0x01 /**< C1 = 湿度十位(实测定型) */
#define BOARD_DISP_COM_HUMI_ONE 0x02 /**< C2 = 湿度个位(实测定型) */
/**
* 扫段调试模式(P0 台上定型用,2026-08-29 已全部定型,正常固件保持 0):
* 0 = 正常:无采样时写死 25/60(分时轮询 + 提速移位)
* 1 = 段扫描:4 位同显一段,值变化才刷(无鬼影);段序已实测确认 A..G 顺序正确
* 2 = 位扫描:4 位分显 1/2/3/4(分时轮询);位序已实测确认 C1=湿十/C2=湿个/C3=温十/C4=温个
*/
#define BOARD_DISP_SCAN_TEST 0
/*============================================================================*/
/* 传感器(AHT20 */
/*============================================================================*/
#define BOARD_TH_PERIOD_MS 2000 /**< 采集周期 2s(手册要求 >1s,规格书第 2.3/18 章) */
#define BOARD_TH_FAIL_MAX 3 /**< 连续失败 3 次判传感器故障 */
#define BOARD_AHT20_WAIT_MS 80 /**< 测量等待时长 */
#define BOARD_AHT20_I2C_HZ 100000 /**< 软件 I2C 时钟 */
/*============================================================================*/
/* 模式(P1 起用,P0 关闭) */
/*============================================================================*/
#define BOARD_MODE_VENT_ON_MS (25u * 60u * 1000u) /**< 通风开段 25min */
#define BOARD_MODE_VENT_OFF_MS (5u * 60u * 1000u) /**< 通风停段 5min */
#define BOARD_MODE_POWER_ON_MS (15u * 60u * 1000u) /**< 强力一轮 15min */
#endif /* __BOARD_PIN_H__ */
+436
View File
@@ -0,0 +1,436 @@
/******************************************************************************
* @file bsp_aht20.c
* @brief CBR3 AHT20 软件 I2C 驱动实现
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.3
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布
* - V1.0.1, 2026.08.29, cyWu, 修复 SCL 高电平期间推挽输出 SDA 导致的假 STOP
* (读数据必 CRC fail 的根因);补 AHT20 0x71 校准检查 + 0xBE 初始化
* - V1.0.2, 2026.08.29, cyWu, 排障结束:回退诊断代码(电平自检/地址扫描/IN 直读),
* 保留时序修复、内部上拉依赖、总线恢复、0x71/0xBE 初始化流程
* - V1.0.3, 2026.08.29, cyWu, IO 分离:AHT20 独占 PC4(SCL)/PC5(SDA),删除
* bsp_bus 共线锁与 measure_start/finish 拆分锁,采集恢复一次完成;
* PC4/PC5 上拉配置从 bsp_bus_init 移入本文件 init
******************************************************************************/
#include "bsp_aht20.h"
#include "board_pin.h"
#include "asm/gpio.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[AHT20]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
#define AHT20_ADDR_W 0x70 /**< 写地址(7bit 0x38 << 1 */
#define AHT20_ADDR_R 0x71 /**< 读地址 */
#define AHT20_CMD_MEASURE 0xAC
#define AHT20_CMD_INIT 0xBE /**< 初始化校准寄存器命令 */
#define AHT20_CMD_SOFTRESET 0xBA /**< 软复位命令(备用) */
#define AHT20_READ_LEN 7 /**< status + 20bit humi + 20bit temp + crc */
static uint8_t s_inited;
/* 软件 I2C:PC5=SDA(三态开漏,内部上拉 + 外部 4.7~10k 双保险),PC4=SCL(推挽)
*
* ⚠ 时序铁律:SDA 的方向切换 / 推挽输出只能在 SCL=0 时进行!
* 若在 SCL=1 期间把 SDA 推挽输出为高,而从机正拉低 SDA(数据位 0 或 ACK),
* 会在 SCL 高电平期间制造 SDA 上升沿 = 从机误判 STOP,传输中止 →
* 之后读到的全是上拉的垃圾字节 → CRC failV1.0.0 实测 bug)。
*
* ⚠ IO 分离(V1.0.3):CT1642 已移到 PG0/PG1,本驱动独占 PC4/PC5
* 无总线锁、无模式切换,可直接调用。
*/
/**
* @brief ~2us 延时(24MHz,约 48 条指令),对应 I2C ≤250kHz
* @return 无
*/
static void i2c_delay(void)
{
volatile uint32_t i;
for (i = 0; i < 16; i++) {
/* nop */
}
}
/**
* @brief SCL 拉高/拉低(SCL 推挽输出,主控独占驱动,无方向切换)
* @param level 1=高,0=低
* @return 无
*/
static void i2c_scl(uint8_t level)
{
gpio_direction_output(BOARD_TH_SCL_PIN, level ? 1 : 0);
i2c_delay();
}
/**
* @brief 设置 SDA 输出电平(内部含"输出高=推 1 后切高阻"
* @note ⚠ 仅允许在 SCL=0 时调用(START/STOP 条件除外,那两处从机不驱动总线)
* @param level 1=高(推 1 后切高阻,靠外部上拉保持),0=低(推挽拉低)
* @return 无
*/
static void i2c_sda_out(uint8_t level)
{
if (level) {
gpio_direction_output(BOARD_TH_SDA_PIN, 1);
gpio_set_direction(BOARD_TH_SDA_PIN, 1); /* 高阻,外部上拉拉高 */
} else {
gpio_direction_output(BOARD_TH_SDA_PIN, 0);
}
i2c_delay();
}
/**
* @brief 释放 SDA 为输入(高阻)
* @note ⚠ 仅允许在 SCL=0 时调用;释放后用 i2c_sda_read() 采样
* @return 无
*/
static void i2c_sda_release(void)
{
gpio_direction_output(BOARD_TH_SDA_PIN, 1);
gpio_set_direction(BOARD_TH_SDA_PIN, 1);
i2c_delay();
}
/**
* @brief 采样 SDA 电平(调用时 SDA 必处于输入态)
* @return 1=高,0=低
*/
static uint8_t i2c_sda_read(void)
{
return (uint8_t)(gpio_read(BOARD_TH_SDA_PIN) ? 1 : 0);
}
/**
* @brief I2C STARTSCL 高时 SDA 高→低
* @note 起始时 SCL 必为低(上一帧收尾),先释放 SDA 再抬 SCL
* @return 无
*/
static void i2c_start(void)
{
i2c_scl(0);
i2c_sda_out(1);
i2c_scl(1);
i2c_sda_out(0); /* SCL 高时 1→0 = START */
i2c_scl(0);
}
/**
* @brief I2C STOPSCL 高时 SDA 低→高
* @return 无
*/
static void i2c_stop(void)
{
i2c_scl(0);
i2c_sda_out(0);
i2c_scl(1);
i2c_sda_out(1); /* SCL 高时 0→1 = STOP;此时从机已释放总线,推 1 无冲突 */
}
/**
* @brief 写一个字节(MSB 先;每位 SDA 都在 SCL 低电平期间变化)
* @param byte 数据
* @return 无
*/
static void i2c_write_byte(uint8_t byte)
{
uint8_t i;
for (i = 0; i < 8; i++) {
i2c_sda_out((uint8_t)((byte & 0x80u) ? 1 : 0));
byte <<= 1;
i2c_scl(1);
i2c_scl(0);
}
}
/**
* @brief 等待从机 ACK(第 9 个时钟;SDA 方向切换只在 SCL=0 时做)
* @return 0=收到 ACK1=NACK
*/
static uint8_t i2c_wait_ack(void)
{
uint8_t ack;
i2c_scl(0);
i2c_sda_release(); /* SCL 低时切输入,绝不污染 SCL 高电平区间 */
i2c_scl(1);
ack = i2c_sda_read();
i2c_scl(0);
return ack; /* 0=ACK(从机拉低),1=NACK */
}
/**
* @brief 读一个字节(先在 SCL=0 时释放 SDA,循环内只采样不切方向)
* @param ack 1=收完发 ACK(继续读),0=发 NACK(最后一字节)
* @return 读到的字节
*/
static uint8_t i2c_read_byte(uint8_t ack)
{
uint8_t i;
uint8_t byte = 0;
i2c_sda_release();
for (i = 0; i < 8; i++) {
i2c_scl(1);
byte = (uint8_t)((byte << 1) | i2c_sda_read());
i2c_scl(0);
}
i2c_sda_out(ack ? 0 : 1); /* 第 9 位:ACK 拉低 / NACK 释放(SCL 低时设置) */
i2c_scl(1);
i2c_scl(0);
i2c_sda_out(1);
return byte;
}
/**
* @brief CRC8poly 0x31init 0xFF
* @param data 数据指针
* @param len 长度
* @return CRC 值
*/
static uint8_t aht20_crc8(const uint8_t *data, uint8_t len)
{
uint8_t crc = 0xFF;
uint8_t i, b;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (b = 0; b < 8; b++) {
crc = (uint8_t)((crc & 0x80u) ? ((crc << 1) ^ 0x31u) : (crc << 1));
}
}
return crc;
}
/**
* @brief I2C 总线恢复:发 9 个 SCL 时钟 + STOP,解开"从机拉死 SDA"的卡死态
* @note 卡死场景:之前某次传输中途被复位/打断,从机停在"还有 N 位没发完"
* 持续拉低 SDA,此后所有 START/ACK 全废。标准 I2C 恢复手段。
* @return 无
*/
static void i2c_bus_recover(void)
{
uint8_t i;
i2c_scl(0);
i2c_sda_release(); /* 释放 SDASCL=低时切输入) */
for (i = 0; i < 9; i++) {
i2c_scl(1);
if (i2c_sda_read()) {
break; /* SDA 回到高:从机已释放 */
}
i2c_scl(0);
}
i2c_stop(); /* 补一个 STOP 收尾 */
}
/**
* @brief 读状态寄存器(0x71):检查校准使能位,未校准则发 0xBE 0x08 0x00
* @note IO 分离后 AHT20 独占 PC4/PC5,无需总线锁,直接调用
* @return 0=就绪(已校准),-1=通讯失败
*/
static int8_t aht20_check_and_calib(void)
{
uint8_t status;
/* 写 0x71 读状态命令 */
i2c_start();
i2c_write_byte(AHT20_ADDR_W);
if (i2c_wait_ack()) {
goto fail;
}
i2c_write_byte(0x71);
if (i2c_wait_ack()) {
goto fail;
}
i2c_stop();
/* 读 1 字节状态 */
i2c_start();
i2c_write_byte(AHT20_ADDR_R);
if (i2c_wait_ack()) {
goto fail;
}
status = i2c_read_byte(0);
i2c_stop();
if (status & 0x08u) {
return 0; /* Bit3 Calibration Enabled:出厂默认已校准 */
}
/* 未校准:发初始化命令 0xBE 0x08 0x00 */
log_error("aht20 not calib(0x%02x), init 0xBE\n", status);
i2c_start();
i2c_write_byte(AHT20_ADDR_W);
if (i2c_wait_ack()) {
goto fail;
}
i2c_write_byte(AHT20_CMD_INIT);
if (i2c_wait_ack()) {
goto fail;
}
i2c_write_byte(0x08);
if (i2c_wait_ack()) {
goto fail;
}
i2c_write_byte(0x00);
if (i2c_wait_ack()) {
goto fail;
}
i2c_stop();
return 0;
fail:
i2c_stop();
return -1;
}
/**
* @brief 初始化:配置 PC4/PC5 + 软件 I2C 总线复位 + 0x71 校准检查 + 0xBE 初始化
* @return 无
*/
void bsp_aht20_init(void)
{
if (s_inited) {
return;
}
#if BOARD_TH_ENABLE
/* 上电后等待 ≥100ms 由 app 启动节奏保证,此处直接操作 */
/* PC5(SDA) 开内部上拉:I2C 释放 SDA 后必须有上拉,否则浮空
* (实测无上拉时:init 读状态=0xFF 假 init ok;等 80ms 后漏电读全 0 → crc fail)。
* 板上有外部 4.7~10k 上拉时双保险。PC4(SCL) 推挽驱动,上拉无害防浮空。 */
gpio_set_pull_up(BOARD_TH_SDA_PIN, 1);
gpio_set_pull_down(BOARD_TH_SDA_PIN, 0);
gpio_set_die(BOARD_TH_SDA_PIN, 1);
gpio_set_pull_up(BOARD_TH_SCL_PIN, 1);
gpio_set_pull_down(BOARD_TH_SCL_PIN, 0);
gpio_set_die(BOARD_TH_SCL_PIN, 1);
/* 总线复位:先 9 clock + STOP 解开可能的从机卡死,再补一次 STOP */
i2c_bus_recover();
i2c_stop();
if (aht20_check_and_calib() == 0) {
log_info("aht20 init ok\n");
} else {
/* 校准检查失败不阻塞测量(多数 AHT20 出厂已校准),后续采样重试;
* 顺带做一次总线恢复,解开可能的从机卡死 */
log_error("aht20 calib check fail\n");
i2c_bus_recover();
}
s_inited = 1;
#else
log_info("aht20 disabled\n");
#endif
}
/**
* @brief 发起一次测量:发 0xAC 0x33 0x00
* @note 调用后必须等 ≥80ms 再 bsp_aht20_measure_finish。等待期间 I2C 总线
* 空闲(AHT20 内部测量),CT1642 在 PG0/PG1 独立刷新,互不影响。
* @return 0=命令已发出,非 0=失败
*/
int8_t bsp_aht20_measure_start(void)
{
#if !BOARD_TH_ENABLE
return -1;
#else
if (!s_inited) {
bsp_aht20_init(); /* init 失败时此处补做 */
if (!s_inited) {
return -1;
}
}
i2c_start();
i2c_write_byte(AHT20_ADDR_W);
if (i2c_wait_ack()) {
log_error("addr nack\n");
goto fail;
}
i2c_write_byte(AHT20_CMD_MEASURE);
if (i2c_wait_ack()) {
log_error("cmd nack\n");
goto fail;
}
i2c_write_byte(0x33);
if (i2c_wait_ack()) {
log_error("param1 nack\n");
goto fail;
}
i2c_write_byte(0x00);
if (i2c_wait_ack()) {
log_error("param2 nack\n");
goto fail;
}
i2c_stop();
return 0;
fail:
i2c_stop();
return -1;
#endif
}
/**
* @brief 读取测量结果:读 7 字节 + CRC8 校验 + 换算
* @param temp_x10 输出温度 ×10(如 25.3℃ → 253
* @param humi_pct 输出湿度 0~100 整数 %
* @return 0=成功;-1=通讯失败(CRC 错/超时/忙)
*/
int8_t bsp_aht20_measure_finish(int16_t *temp_x10, uint8_t *humi_pct)
{
#if !BOARD_TH_ENABLE
return -1;
#else
uint8_t buf[AHT20_READ_LEN];
uint8_t i;
uint32_t humi_raw;
uint32_t temp_raw;
int32_t temp_10;
int8_t ret = -1;
i2c_start();
i2c_write_byte(AHT20_ADDR_R);
if (i2c_wait_ack()) {
log_error("read addr nack\n");
goto out;
}
for (i = 0; i < AHT20_READ_LEN; i++) {
buf[i] = i2c_read_byte((uint8_t)((i < (AHT20_READ_LEN - 1)) ? 1 : 0));
}
i2c_stop();
if (aht20_crc8(buf, AHT20_READ_LEN - 1) != buf[AHT20_READ_LEN - 1]) {
log_error("crc fail: %02X %02X %02X %02X %02X %02X crc=%02X\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]);
goto out;
}
if (buf[0] & 0x80u) { /* busy 位 */
log_error("sensor busy\n");
goto out;
}
humi_raw = ((uint32_t)buf[1] << 12u) | ((uint32_t)buf[2] << 4u) | ((uint32_t)buf[3] >> 4u);
temp_raw = (((uint32_t)buf[3] & 0x0Fu) << 16u) | ((uint32_t)buf[4] << 8u) | (uint32_t)buf[5];
/* T = raw * 200 / 2^20 - 50(℃);×10 输出。整数运算:raw*2000 最大 ~2e7int32 安全 */
temp_10 = (int32_t)((temp_raw * 2000u) / 1048576u) - 500;
*temp_x10 = (int16_t)temp_10;
*humi_pct = (uint8_t)((humi_raw * 100u) / 1048576u);
if (*humi_pct > 100) {
*humi_pct = 100;
}
ret = 0;
out:
return ret;
#endif
}
+48
View File
@@ -0,0 +1,48 @@
/******************************************************************************
* @file bsp_aht20.h
* @brief CBR3 AHT20 温湿度传感器:软件 I2C + 测量 + CRC8
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.3
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布
* - V1.0.1, 2026.08.29, cyWu, 修复 SCL 高电平期间推挽输出 SDA 导致的假 STOP
* - V1.0.2, 2026.08.29, cyWu, 回退诊断代码,保留时序修复/上拉/总线恢复/0x71 校准
* - V1.0.3, 2026.08.29, cyWu, IO 分离:AHT20 独占 PC4(SCL)/PC5(SDA),删除
* bsp_bus 共线锁与 measure_start/finish 拆分锁,恢复一次完成采集
*
* @note I2C 7 位地址 0x38(写 0x70 / 读 0x71)。测量命令 0xAC 0x33 0x00
* 等 ≥80ms 后读 7 字节:Status + 20bit 湿度 + 20bit 温度 + CRC8。
* CRC8 多项式 0x31,初值 0xFF,覆盖前 6 字节。
* PC4=SCL(推挽)/ PC5=SDA(开漏+内部上拉),与 CT1642 的 PG0/PG1
* 完全独立,无总线竞争(2026-08-29 改板分离)。
******************************************************************************/
#ifndef __BSP_AHT20_H__
#define __BSP_AHT20_H__
#include <stdint.h>
/**
* @brief 初始化:配置 PC4/PC5(上拉+die+ 软件 I2C 总线复位 + 0x71 校准检查
* @return 无
*/
void bsp_aht20_init(void);
/**
* @brief 发起一次测量:发 0xAC 0x33 0x00
* @note 调用后必须等 ≥80ms 再 bsp_aht20_measure_finish。
* 等待期间 I2C 总线空闲(AHT20 内部测量),CT1642 显示不受影响。
* @return 0=命令已发出,非 0=失败(未初始化/通讯失败)
*/
int8_t bsp_aht20_measure_start(void);
/**
* @brief 读取测量结果:读 7 字节 + CRC8 校验 + 换算
* @param temp_x10 输出温度 ×10(如 25.3℃ → 253
* @param humi_pct 输出湿度 0~100 整数 %
* @return 0=成功,非 0=失败(CRC 错/时序错,本次采样视为失败)
*/
int8_t bsp_aht20_measure_finish(int16_t *temp_x10, uint8_t *humi_pct);
#endif /* __BSP_AHT20_H__ */
+153
View File
@@ -0,0 +1,153 @@
/******************************************************************************
* @file bsp_ct1642.c
* @brief CBR3 CT1642 类数码管驱动实现(软件移位)
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.1
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布
* - V1.0.1, 2026.08.29, cyWu, IO 分离:CT1642 独立 PG0=CLK/PG1=DATA,删除
* bsp_bus 共线机制;删调试遗留 latch/show4/scan_seg/scan_com
* 新增 bsp_ct1642_init()
*
* @note 驱动协议严格参照 CT1642 手册(与 CSDN yitx 一致):
* 一帧 = 18bitCLK 上升沿采样 DATADATA 只在 CLK=低时改变):
* [0:3] C1-C4 位选(低有效,入参 com 取反后发送,先发 C1)
* [4:9] 6 位保留位(固定填 1)
* [10:17] Q2-Q9 段码(LSB 先,seg 的 bit0 → Q2 引脚)
* 发完 18bit 后:CLK=高时 DATA 0→1 = 锁存;CLK=低时 DATA 0→1 = 输出。
* 段码/位选物理映射由 PCB 决定,SCAN_TEST 扫段/扫位已定型(见 board_pin.h)。
* 本驱动独立使用 PG0/PG1,与 AHT20 的 PC4/PC5 互不干扰。
******************************************************************************/
#include "bsp_ct1642.h"
#include "board_pin.h"
#include "asm/gpio.h"
/* 软件延时:1 次空循环(24MHz 下 ~0.1us 量级,gpio 调用本身开销另计)。
* ⚠ 提速历程:8→2(鬼影明显→一点点)→1(继续压残余微亮)。
* 若出现显示乱码/错位(低于 CT1642 最小脉宽),回调到 2。
* 软件压到极限后残余的微亮 = 硬件层(Q2-Q9 段线加 1~10k 下拉可根除)。 */
static void ct1642_delay(void)
{
volatile uint32_t i;
for (i = 0; i < 1; i++) {
/* nop */
}
}
/**
* @brief 初始化 PG0(CLK)/PG1(DATA) 为推挽输出,初始 0
* @note ⚠ 必须在第一次发帧前调用(app_disp_init 内)。
* CT1642 为纯输入从机,两根线全程推挽驱动即可,无需上拉/开漏。
* @return 无
*/
void bsp_ct1642_init(void)
{
gpio_set_pull_up(BOARD_DISP_CLK_PIN, 0);
gpio_set_pull_down(BOARD_DISP_CLK_PIN, 0);
gpio_set_die(BOARD_DISP_CLK_PIN, 1);
gpio_direction_output(BOARD_DISP_CLK_PIN, 0);
gpio_set_pull_up(BOARD_DISP_DATA_PIN, 0);
gpio_set_pull_down(BOARD_DISP_DATA_PIN, 0);
gpio_set_die(BOARD_DISP_DATA_PIN, 1);
gpio_direction_output(BOARD_DISP_DATA_PIN, 0);
}
static void ct1642_clk(uint8_t level)
{
gpio_direction_output(BOARD_DISP_CLK_PIN, level);
ct1642_delay();
}
static void ct1642_data(uint8_t level)
{
gpio_direction_output(BOARD_DISP_DATA_PIN, level);
ct1642_delay();
}
/**
* @brief 7 段数码管数字转段码(共阴,A=bit0 ... G=bit6DP=bit7
* @param digit 0~910='-'G 段);11=全灭
* @return 段码
*/
uint8_t bsp_ct1642_seg(uint8_t digit)
{
static const uint8_t seg_tbl[12] = {
0x3F, /* 0: abcdef */
0x06, /* 1: bc */
0x5B, /* 2: abdeg */
0x4F, /* 3: abcdg */
0x66, /* 4: bcfg */
0x6D, /* 5: acdfg */
0x7D, /* 6: acdefg */
0x07, /* 7: abc */
0x7F, /* 8: abcdefg */
0x6F, /* 9: abcdfg */
0x40, /* '-': g */
0x00, /* 灭 */
};
if (digit > 11) {
digit = 11;
}
return seg_tbl[digit];
}
/**
* @brief 发一帧 18bit(不含消影):C1-C4 → 6保留 → Q2-Q9,再锁存+输出
* @note 内部函数;com/seg 的消影由 bsp_ct1642_send_frame 负责。
*/
static void ct1642_xfer(uint8_t seg, uint8_t com)
{
uint8_t i;
uint8_t posn = (uint8_t)(~com); /* C1-C4 低有效:位0=选中位 */
/* 起始空闲:CLK=低,DATA=高(规格“上电 CLK 低、DATA 高”) */
ct1642_data(1);
ct1642_clk(0);
/* 1. 移位 18bitC1-C4(4) → 6位保留(填1) → Q2-Q9(8LSB=Q2=seg bit0) */
for (i = 0; i < 4; i++) { /* C1-C4,先发 C1 */
ct1642_data((uint8_t)(posn & 0x01u));
ct1642_clk(0); ct1642_clk(1); /* 下降沿改完,上升沿采样 */
posn >>= 1;
}
for (i = 0; i < 6; i++) { /* 6 位保留,固定填 1 */
ct1642_data(1);
ct1642_clk(0); ct1642_clk(1);
}
for (i = 0; i < 8; i++) { /* Q2-Q9 段码,先发 Q2(=seg bit0) */
ct1642_data((uint8_t)(seg & 0x01u));
ct1642_clk(0); ct1642_clk(1);
seg >>= 1;
}
/* 2. 锁存:CLK 保持高,DATA 给 0→1 上升沿 */
ct1642_clk(1);
ct1642_data(0);
ct1642_data(1);
/* 3. 输出:CLK 拉低,DATA 给 0→1 上升沿(刷新该位显示) */
ct1642_clk(0);
ct1642_data(0);
ct1642_data(1);
}
/**
* @brief 发一帧刷新一位数码管(CT1642 18bit 协议)
* @param seg 段码(bit7=DP ... bit0=A,与 seg_tbl 一致;先发 bit0→Q2
* @param com COM 位选掩码(bit3=C4 ... bit0=C1,仅 1 位置位;硬件低有效内部取反)
* @note ⚠ 不做任何消影预帧:CT1642 移位过程实时输出,“持续刷新”才是鬼影根因
* (切位瞬间杂段),预帧/全灭帧都无法根除,且每帧翻倍会饿死 ISR 喂狗导致
* 上电复位(实测)。鬼影的正确解法在应用层:数字没变就不刷新(值变化才
* 刷一次,其余时间锁存静止显示)。本函数保持单帧、极短,适配 ISR 上下文。
* @return 无
*/
void bsp_ct1642_send_frame(uint8_t seg, uint8_t com)
{
// ct1642_xfer(0, 0);
ct1642_xfer(seg, com); /* 单帧:18bit 移位 + 锁存 + 输出,无消影预帧 */
}
+46
View File
@@ -0,0 +1,46 @@
/******************************************************************************
* @file bsp_ct1642.h
* @brief CBR3 CT1642 类数码管驱动:18bit 移位、锁存、送出
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.1
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布
* - V1.0.1, 2026.08.29, cyWu, IO 分离:CT1642 独立 PG0=CLK/PG1=DATA(飞线),
* AHT20 独占 PC4/PC5,删除 bsp_bus 共线机制;删调试遗留
* latch/show4/scan_seg/scan_com,新增 bsp_ct1642_init()
*
* @note PG0=CLKPG1=DATA,独立引脚、无总线锁,可随时调用。
* 协议:一帧 18bitC1-C4 位选 + 6 位保留 + Q2-Q9 段码),CLK 上升沿采样
* DATADATA 只在 CLK=低时改变);发完 CLK=高 时 DATA 上升沿=锁存,
* CLK=低 时 DATA 上升沿=输出刷新。
******************************************************************************/
#ifndef __BSP_CT1642_H__
#define __BSP_CT1642_H__
#include <stdint.h>
/**
* @brief 初始化 PG0/PG1 为推挽输出(CLK/DATA 初始 0
* @note app_disp_init() 内调用;被 BOARD_DISP_ENABLE 编译开关保护
* @return 无
*/
void bsp_ct1642_init(void);
/**
* @brief 7 段数码管数字转段码(共阴,A=bit0 ... G=bit6DP=bit7
* @param digit 0~910='-'G 段);11=全灭
* @return 段码
*/
uint8_t bsp_ct1642_seg(uint8_t digit);
/**
* @brief 发一帧刷新一位:18bitC1-C4 位选 + 6 保留 + Q2-Q9 段码,LSB 先)
* @param seg 段码(bit7=DP ... bit0=A,先发 bit0→Q2
* @param com COM 位选掩码(bit3=C4 ... bit0=C1,仅 1 位置位;硬件低有效内部取反)
* @return 无
*/
void bsp_ct1642_send_frame(uint8_t seg, uint8_t com);
#endif /* __BSP_CT1642_H__ */
+225
View File
@@ -0,0 +1,225 @@
/******************************************************************************
* @file bsp_hw.c
* @brief CBR3 板级 GPIO / MCPWMLED、按键、风扇
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布(移植门夹:删电机/充电/电量,改单 LED + 风扇 PWM)
*
* @note CT1642 在 PG0/PG1、AHT20 在 PC4/PC5,各自独立驱动,
* bsp_hw 只管 LED/KEY/FAN,不碰这两组脚。
******************************************************************************/
#include "system/includes.h"
#include "asm/gpio.h"
#include "asm/mcpwm.h"
#include "bsp_hw.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[BSP_HW]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
static uint8_t s_initialized;
/** 风扇 PWM 状态 */
typedef struct {
uint8_t pwm_clk_on; /**< 1=MCPWM 时钟已打开 */
uint16_t duty; /**< 当前占空比 */
} BspFanCtx_t;
static BspFanCtx_t s_fan;
#if (BOARD_LED_ENABLE || BOARD_KEY_ENABLE)
/**
* @brief 配置 GPIO 为推挽输出
* @param pin GPIO 编号
* @param value 输出初值
* @return 无
*/
static void bsp_pin_output(u32 pin, int value)
{
gpio_set_pull_up(pin, 0);
gpio_set_pull_down(pin, 0);
gpio_set_die(pin, 1);
gpio_direction_output(pin, value);
}
#endif
#if BOARD_KEY_ENABLE
/**
* @brief 配置 GPIO 为数字输入(可内部上拉)
* @param pin GPIO 编号
* @param pull_up 1=内部上拉,0=不上拉
* @return 无
*/
static void bsp_pin_input(u32 pin, int pull_up)
{
gpio_direction_output(pin, 1);
gpio_set_direction(pin, 1);
gpio_set_pull_down(pin, 0);
gpio_set_pull_up(pin, pull_up ? 1 : 0);
gpio_set_die(pin, 1);
}
#endif
#if BOARD_FAN_ENABLE
/**
* @brief 初始化风扇 MCPWM ch0,初始占空比 0(关)
* @return 无
*/
static void bsp_fan_pwm_init(void)
{
struct pwm_platform_data pwm;
gpio_set_pull_up(BOARD_FAN_PIN, 0);
gpio_set_pull_down(BOARD_FAN_PIN, 0);
gpio_set_die(BOARD_FAN_PIN, 1);
pwm.pwm_aligned_mode = pwm_edge_aligned;
pwm.frequency = BOARD_FAN_PWM_HZ;
pwm.duty = 0;
pwm.l_pin = (u8) - 1;
pwm.complementary_en = 0;
pwm.pwm_ch_num = pwm_ch0;
pwm.h_pin = BOARD_FAN_PIN;
mcpwm_init(&pwm);
mcpwm_set_duty(pwm_ch0, 0);
s_fan.pwm_clk_on = 1;
s_fan.duty = 0;
log_info("fan pwm %uHz duty=0\n", (unsigned)BOARD_FAN_PWM_HZ);
}
/**
* @brief 打开 MCPWM 并绑回 PC3
* @return 无
*/
static void bsp_fan_pwm_clock_on(void)
{
if (s_fan.pwm_clk_on) {
return;
}
mcpwm_open(pwm_ch0);
gpio_och_sel_output_signal(BOARD_FAN_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
gpio_set_direction(BOARD_FAN_PIN, 0);
s_fan.pwm_clk_on = 1;
}
/**
* @brief 关闭 MCPWM 时钟并拉低(待机/关机必关)
* @return 无
*/
static void bsp_fan_pwm_clock_off(void)
{
mcpwm_set_duty(pwm_ch0, 0);
if (s_fan.pwm_clk_on) {
mcpwm_close(pwm_ch0);
gpio_och_disable_output_signal(BOARD_FAN_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
JL_MCPWM->MCPWM_CON0 = 0;
s_fan.pwm_clk_on = 0;
}
gpio_direction_output(BOARD_FAN_PIN, 0); /* 关 = 拉低(风扇高导通) */
}
#endif
/**
* @brief 初始化 GPIO / 风扇 MCPWM
* @return BSP_HW_OK=成功
*/
BspHw_Ret_t bsp_hw_init(void)
{
if (s_initialized) {
return BSP_HW_OK;
}
#if BOARD_LED_ENABLE
bsp_pin_output(BOARD_LED_PIN, !BOARD_LED_ON_LEVEL); /* 初始灭 */
#endif
#if BOARD_KEY_ENABLE
bsp_pin_input(BOARD_KEY_PIN, 1);
#endif
#if BOARD_FAN_ENABLE
bsp_fan_pwm_init();
bsp_fan_pwm_clock_off(); /* 初始关,待机不耗电 */
#endif
s_initialized = 1;
log_info("hw init led=%d key=%d fan=%d\n",
BOARD_LED_ENABLE, BOARD_KEY_ENABLE, BOARD_FAN_ENABLE);
return BSP_HW_OK;
}
/**
* @brief 查询板级硬件是否已初始化
* @return BSP_HW_OK=已初始化,BSP_HW_ERR_NOT_INIT=未初始化
*/
BspHw_Ret_t bsp_hw_get_status(void)
{
return s_initialized ? BSP_HW_OK : BSP_HW_ERR_NOT_INIT;
}
/**
* @brief 状态 LED 开关(PC2,低电平亮)
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_set(uint8_t on)
{
#if BOARD_LED_ENABLE
gpio_direction_output(BOARD_LED_PIN, on ? BOARD_LED_ON_LEVEL : !BOARD_LED_ON_LEVEL);
#else
(void)on;
#endif
}
/**
* @brief 风扇 PWM 占空比(PC3,高有效,20kHz
* @param duty 0~100000%=关 MCPWM 并拉低;>0 打开时钟输出)
* @return 无
*/
void bsp_fan_set_duty(uint16_t duty)
{
#if BOARD_FAN_ENABLE
if (duty > 10000) {
duty = 10000;
}
if (duty == 0) {
bsp_fan_pwm_clock_off();
s_fan.duty = 0;
return;
}
bsp_fan_pwm_clock_on();
mcpwm_set_duty(pwm_ch0, duty);
s_fan.duty = duty;
#else
(void)duty;
#endif
}
/**
* @brief 读取按键
* @return 1=按下,0=未按下
*/
uint8_t bsp_key_is_pressed(void)
{
#if BOARD_KEY_ENABLE
return (uint8_t)(gpio_read(BOARD_KEY_PIN) == BOARD_KEY_ACTIVE_LEVEL);
#else
return 0;
#endif
}
/**
* @brief 进深睡前:风扇 PWM 关掉并拉低,灯全灭
* @return 无
*/
void bsp_hw_enter_sleep_io(void)
{
bsp_fan_set_duty(0);
bsp_led_set(0);
}
+61
View File
@@ -0,0 +1,61 @@
/******************************************************************************
* @file bsp_hw.h
* @brief CBR3 板级硬件:LED / 按键 / 风扇 PWM
* @author cyWu <1917507415@qq.com>
* @date 2026.08.29
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.29, cyWu, 首次发布(移植门夹:删电机/充电/电量,改单 LED + 风扇 PWM)
******************************************************************************/
#ifndef __BSP_HW_H__
#define __BSP_HW_H__
#include <stdint.h>
#include "board_pin.h"
typedef enum {
BSP_HW_OK = 0,
BSP_HW_ERR_PARAM,
BSP_HW_ERR_NOT_INIT
} BspHw_Ret_t;
/**
* @brief 初始化 GPIO / 风扇 MCPWM
* @return BSP_HW_OK=成功
*/
BspHw_Ret_t bsp_hw_init(void);
/**
* @brief 查询是否已初始化
* @return BSP_HW_OK=已初始化,BSP_HW_ERR_NOT_INIT=未初始化
*/
BspHw_Ret_t bsp_hw_get_status(void);
/**
* @brief 状态 LED 开关(PC2,低电平亮)
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_set(uint8_t on);
/**
* @brief 风扇 PWM 占空比(PC3,高有效,20kHz
* @param duty 0~100000%=关 MCPWM 并拉低;>0 打开时钟输出)
* @return 无
*/
void bsp_fan_set_duty(uint16_t duty);
/**
* @brief 读取按键
* @return 1=按下,0=未按下
*/
uint8_t bsp_key_is_pressed(void);
/**
* @brief 进深睡前:风扇 PWM 关掉并拉低,灯全灭
* @return 无
*/
void bsp_hw_enter_sleep_io(void);
#endif /* __BSP_HW_H__ */