Initial import
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/******************************************************************************
|
||||
* @file board_pin.h
|
||||
* @brief CBC4 弹力魔盒板级引脚、电平与时序(换板只改本文件)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.27
|
||||
* @version V1.5.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 收口:去掉实验开关与未贴件限位
|
||||
* - V1.2.0, 2026.08.24, cyWu, 按键:深睡 3s 开机、5s 开机+配对
|
||||
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 50% 占空比降速
|
||||
* - V1.4.0, 2026.08.26, cyWu, 新增 5% 自动关机门限、开机低电闪灯拦截、
|
||||
* 12s 长按 OTA、自动模式单次最长运行时长;
|
||||
* 取消低电闪烁 1 分钟后自动关机门限
|
||||
* - V1.5.0, 2026.08.27, cyWu, 运行中空电关机红闪 5 下时序宏
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __BOARD_PIN_H__
|
||||
#define __BOARD_PIN_H__
|
||||
|
||||
#include "asm/gpio.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 功能开关:某路硬件异常时可单独关掉,不影响配网 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_LED_ENABLE 1 /**< 1=红绿 LED */
|
||||
#define BOARD_KEY_ENABLE 1 /**< 1=按键 PB1 */
|
||||
#define BOARD_CHG_ENABLE 1 /**< 1=充电检测 PG8 + VPWR */
|
||||
#define BOARD_MOTOR_ENABLE 1 /**< 1=双电机 U13 + U18 */
|
||||
#define BOARD_SHAKE_ENABLE 1 /**< 1=震动输入 PA3 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* GPIO */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_LEFT_FORWARD_PIN IO_PORTA_06 /**< U13-INA,左轮前进 */
|
||||
#define BOARD_LEFT_BACKWARD_PIN IO_PORTC_02 /**< U13-INB,左轮后退 */
|
||||
#define BOARD_RIGHT_BACKWARD_PIN IO_PORTC_03 /**< U18-INA,右轮后退 */
|
||||
#define BOARD_RIGHT_FORWARD_PIN IO_PORTC_04 /**< U18-INB,右轮前进 */
|
||||
#define BOARD_SHAKE_PIN IO_PORTA_03 /**< 震动开机,常闭开关断开时高脉冲有效 */
|
||||
#define BOARD_LED_RED_PIN IO_PORTA_08 /**< 红灯 LED_PWR,低电平亮 */
|
||||
#define BOARD_LED_GREEN_PIN IO_PORTA_07 /**< 绿灯 LED_CHG,低电平亮 */
|
||||
#define BOARD_KEY_PIN IO_PORTB_01 /**< 按键,低电平按下 */
|
||||
#define BOARD_CHG_PIN IO_PORTG_08 /**< ME4054 CHG,外部 2.2k 上拉 */
|
||||
|
||||
#define BOARD_LED_ON_LEVEL 0 /**< LED 点亮电平:0=低亮 */
|
||||
#define BOARD_KEY_ACTIVE_LEVEL 0 /**< 按键有效电平:0=按下为低 */
|
||||
#define BOARD_CHG_CHARGING_LEVEL 0 /**< CHG 充电中为低 */
|
||||
|
||||
#define BOARD_SHAKE_ACTIVE_LEVEL 1 /**< 常闭震动开关:断开时高脉冲有效 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 电量 / 充电 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_VBAT_FULL_MV 4120 /**< 100% */
|
||||
#define BOARD_VBAT_EMPTY_MV 3200 /**< 0%,电量曲线下限(仅用于百分比换算) */
|
||||
#define BOARD_VBAT_CHARGE_FULL_MV 4200 /**< 充电基准未记录时 100%(充电内阻压降,比放电阈值高)*/
|
||||
#define BOARD_VBAT_CHARGE_EMPTY_MV 3400 /**< 充电基准未记录时 0% */
|
||||
#define BOARD_VBAT_LOW_MV 3700 /**< 低电预警阈值:只闪红灯提示,不关机 */
|
||||
#define BOARD_VBAT_LOW_HYST_MV 50 /**< 低电恢复回差 */
|
||||
#define BOARD_VBAT_SHUTDOWN_MV 3250 /**< 约 5%,仅开机拦截用:关机状态下 app_battery 尚未运行,只能摸裸电压 */
|
||||
#define BOARD_BATTERY_SHUTDOWN_PCT 5 /**< 运行中显示电量 <= 此值才自动关机,跟 APP 看到的百分比对齐 */
|
||||
#define BOARD_VPWR_ONLINE_MV 4000 /**< VPWR 判定 USB 插入 */
|
||||
#define BOARD_VPWR_OFF_MV 1500 /**< 低于此值认定已拔出 */
|
||||
#define BOARD_CHG_FULL_MS 1000 /**< CHG 已高且 VPWR 在,满 1s 认充满 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 按键 / 灯效 */
|
||||
/*============================================================================*/
|
||||
|
||||
#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,失败后保持开机 */
|
||||
|
||||
#define BOARD_BOOT_LOWBAT_BLINK_N 5 /**< 关机时电量过低:拒绝开机前红灯闪烁次数 */
|
||||
#define BOARD_BOOT_LOWBAT_ON_MS 200 /**< 拒绝开机提示:闪烁亮时间 */
|
||||
#define BOARD_BOOT_LOWBAT_OFF_MS 200 /**< 拒绝开机提示:闪烁灭时间 */
|
||||
#define BOARD_LED_EMPTY_BLINK_N 5 /**< 运行中电量到 5%:红灯闪烁次数 */
|
||||
#define BOARD_LED_EMPTY_ON_MS 200 /**< 5% 关机提示:闪烁亮时间 */
|
||||
#define BOARD_LED_EMPTY_OFF_MS 200 /**< 5% 关机提示:闪烁灭时间 */
|
||||
#define BOARD_LED_EMPTY_OFF_WAIT_MS ((BOARD_LED_EMPTY_ON_MS + BOARD_LED_EMPTY_OFF_MS) \
|
||||
* BOARD_LED_EMPTY_BLINK_N + 50u) /**< 闪完再掉电,多留 50ms 让 GPIO 落稳 */
|
||||
|
||||
#define BOARD_LED_FAST_ON_MS 100 /**< 配对绿快闪:亮 */
|
||||
#define BOARD_LED_FAST_OFF_MS 100 /**< 配对绿快闪:灭 */
|
||||
#define BOARD_LED_ALT_MS 200 /**< 配对失败 / OTA 红绿交替半周期 */
|
||||
#define BOARD_LED_PAIR_OK_MS 3000 /**< 配对成功绿灯常亮时长 */
|
||||
#define BOARD_LED_PAIR_FAIL_MS 3000 /**< 配对失败提示时长 */
|
||||
#define BOARD_LED_LOWBAT_ON_MS 200 /**< 低电红闪:亮 */
|
||||
#define BOARD_LED_LOWBAT_PERIOD_MS 2000 /**< 低电红闪:周期 */
|
||||
#define BOARD_LED_MODE_OFF_MS 150 /**< 切模式闪:灭 */
|
||||
#define BOARD_LED_MODE_ON_MS 150 /**< 切模式闪:亮 */
|
||||
#define BOARD_LED_MODE_BLINK_N 2 /**< 切模式闪次数 */
|
||||
#define BOARD_LED_STANDBY_ON 1 /**< 1=待机绿灯常亮;0=亮完 BOARD_LED_POWER_ON_MS 后灭(对比电流) */
|
||||
#define BOARD_LED_POWER_ON_MS 3000 /**< BOARD_LED_STANDBY_ON=0 时的开机绿灯时长 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 电机 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_MOTOR_REVERSE_MS 1 /**< 换向前先松开,防 H 桥直通 */
|
||||
#define BOARD_HAND_PULSE_MS 5000 /**< 手动右转/左转单次最长 5s */
|
||||
#define BOARD_MOTOR_PWM_HZ 200 /**< 弹力魔盒验证过的 MCPWM 频率(Hz) */
|
||||
#define BOARD_MOTOR_PWM_DUTY 8000 /**< 占空比 0~10000=0%~100%,80% */
|
||||
#define BOARD_MOTOR_PWM_IDLE_CLOSE 1 /**< 1=空闲/轻睡关 MCPWM;0=保持开着,方便对比待机电流 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 自动玩法 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_AUTO_MODE_MAX_MS (10u * 60u * 1000u) /**< 自动模式 1~5 单次最长运行时长,到点自动回待机 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 震动开机 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_SHAKE_REARM_MS 50u /**< 高电平稳定后重新武装 */
|
||||
#define BOARD_SHAKE_TRIGGER_EDGE_COUNT 1u /**< 验证阶段:单个高脉冲立即触发 */
|
||||
#define BOARD_SHAKE_TRIGGER_WINDOW_MS 250u /**< 保留窗口参数,便于恢复多脉冲策略 */
|
||||
#define BOARD_SHAKE_POLL_MS 10u /**< 与业务调度器共用的采样节拍 */
|
||||
|
||||
/* PG8/CHG_ON is the only fitted USB-related signal that changes while the
|
||||
* product is in softoff; VPWR is disconnected on boards with R18 not fitted. */
|
||||
#define BOARD_CHG_WAKEUP_INDEX 3u
|
||||
|
||||
#endif /* __BOARD_PIN_H__ */
|
||||
@@ -0,0 +1,517 @@
|
||||
/******************************************************************************
|
||||
* @file bsp_hw.c
|
||||
* @brief CBC4 板级 GPIO / ADC:上下双电机 H 桥、震动、LED、按键、充电检测
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.24
|
||||
* @version V1.5.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 停转反转刹车;去掉限位脚
|
||||
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 占空比降速
|
||||
* - V1.4.0, 2026.08.24, cyWu, 增加按占空比驱动,供玩法变速
|
||||
* - V1.5.0, 2026.08.25, cyWu, 空闲关闭 MCPWM 时钟
|
||||
* - V1.6.0, 2026.08.25, cyWu, 去掉停转反接刹车,改自由滑行停止,避免刹车
|
||||
* 电流冲击拉垮供电轨触发 LVD 复位
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "asm/gpio.h"
|
||||
#include "asm/adc_api.h"
|
||||
#include "asm/charge.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;
|
||||
static uint8_t s_motor_active;
|
||||
static uint8_t s_pwm_clk_on;
|
||||
static uint8_t s_motor_switch_pending;
|
||||
static BspMotorDir_t s_left_direction;
|
||||
static BspMotorDir_t s_right_direction;
|
||||
static BspMotorDir_t s_pending_left_direction;
|
||||
static BspMotorDir_t s_pending_right_direction;
|
||||
static uint16_t s_pending_left_duty;
|
||||
static uint16_t s_pending_right_duty;
|
||||
static uint32_t s_motor_switch_started_ms;
|
||||
|
||||
#if (BOARD_LED_ENABLE || BOARD_MOTOR_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 || BOARD_CHG_ENABLE || BOARD_SHAKE_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_MOTOR_ENABLE
|
||||
static void bsp_motor_pwm_channel_init(pwm_ch_num_type channel, u8 pin)
|
||||
{
|
||||
struct pwm_platform_data pwm;
|
||||
|
||||
memset(&pwm, 0, sizeof(pwm));
|
||||
pwm.pwm_aligned_mode = pwm_edge_aligned;
|
||||
pwm.pwm_ch_num = channel;
|
||||
pwm.frequency = BOARD_MOTOR_PWM_HZ;
|
||||
pwm.duty = 0;
|
||||
pwm.h_pin = pin;
|
||||
pwm.l_pin = (u8) - 1;
|
||||
pwm.complementary_en = 0;
|
||||
mcpwm_init(&pwm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 INA/INB 两路 MCPWM,初始占空比 0(停转)
|
||||
* @return 无
|
||||
*/
|
||||
static void bsp_motor_pwm_init(void)
|
||||
{
|
||||
const u32 pins[] = {
|
||||
BOARD_LEFT_FORWARD_PIN, BOARD_LEFT_BACKWARD_PIN,
|
||||
BOARD_RIGHT_BACKWARD_PIN, BOARD_RIGHT_FORWARD_PIN
|
||||
};
|
||||
uint8_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pins); i++) {
|
||||
gpio_set_pull_up(pins[i], 0);
|
||||
gpio_set_pull_down(pins[i], 0);
|
||||
gpio_set_die(pins[i], 1);
|
||||
}
|
||||
|
||||
bsp_motor_pwm_channel_init(pwm_ch0, BOARD_LEFT_FORWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch1, BOARD_LEFT_BACKWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch2, BOARD_RIGHT_BACKWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch3, BOARD_RIGHT_FORWARD_PIN);
|
||||
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
s_pwm_clk_on = 1;
|
||||
log_info("dual motor pwm %uHz\n", (unsigned)BOARD_MOTOR_PWM_HZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打开 MCPWM 并绑回 INA/INB
|
||||
* @return 无
|
||||
*/
|
||||
static void bsp_motor_pwm_clock_on(void)
|
||||
{
|
||||
if (s_pwm_clk_on) {
|
||||
return;
|
||||
}
|
||||
mcpwm_open(pwm_ch0);
|
||||
mcpwm_open(pwm_ch1);
|
||||
mcpwm_open(pwm_ch2);
|
||||
mcpwm_open(pwm_ch3);
|
||||
gpio_och_sel_output_signal(BOARD_LEFT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
|
||||
gpio_och_sel_output_signal(BOARD_LEFT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
|
||||
gpio_och_sel_output_signal(BOARD_RIGHT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM2_H);
|
||||
gpio_och_sel_output_signal(BOARD_RIGHT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM3_H);
|
||||
gpio_set_direction(BOARD_LEFT_FORWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_LEFT_BACKWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_RIGHT_BACKWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_RIGHT_FORWARD_PIN, 0);
|
||||
s_pwm_clk_on = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭 MCPWM 时钟,INA/INB 拉低(待机必关)
|
||||
* @return 无
|
||||
*/
|
||||
static void bsp_motor_pwm_clock_off(void)
|
||||
{
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
if (s_pwm_clk_on) {
|
||||
mcpwm_close(pwm_ch0);
|
||||
mcpwm_close(pwm_ch1);
|
||||
mcpwm_close(pwm_ch2);
|
||||
mcpwm_close(pwm_ch3);
|
||||
gpio_och_disable_output_signal(BOARD_LEFT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
|
||||
gpio_och_disable_output_signal(BOARD_LEFT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
|
||||
gpio_och_disable_output_signal(BOARD_RIGHT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM2_H);
|
||||
gpio_och_disable_output_signal(BOARD_RIGHT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM3_H);
|
||||
JL_MCPWM->MCPWM_CON0 = 0;
|
||||
s_pwm_clk_on = 0;
|
||||
}
|
||||
gpio_direction_output(BOARD_LEFT_FORWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_LEFT_BACKWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_RIGHT_BACKWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_RIGHT_FORWARD_PIN, 0);
|
||||
}
|
||||
|
||||
static void bsp_drive_gpio(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty)
|
||||
{
|
||||
if (left_duty > 10000) {
|
||||
left_duty = 10000;
|
||||
}
|
||||
if (right_duty > 10000) {
|
||||
right_duty = 10000;
|
||||
}
|
||||
if (left_dir == BSP_MOTOR_STOP) {
|
||||
left_duty = 0;
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_STOP) {
|
||||
right_duty = 0;
|
||||
}
|
||||
|
||||
/* Crazy Car's proven driver clears all four channels at every action
|
||||
* boundary, then applies the two wheel outputs as one atomic command. */
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
|
||||
s_motor_active = (uint8_t)(left_duty || right_duty);
|
||||
if (s_motor_active) {
|
||||
bsp_motor_pwm_clock_on();
|
||||
}
|
||||
if (left_dir == BSP_MOTOR_FORWARD) {
|
||||
mcpwm_set_duty(pwm_ch0, left_duty);
|
||||
} else if (left_dir == BSP_MOTOR_BACKWARD) {
|
||||
mcpwm_set_duty(pwm_ch1, left_duty);
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_FORWARD) {
|
||||
mcpwm_set_duty(pwm_ch3, right_duty);
|
||||
} else if (right_dir == BSP_MOTOR_BACKWARD) {
|
||||
mcpwm_set_duty(pwm_ch2, right_duty);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化 GPIO / 充电 ADC
|
||||
* @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_RED_PIN, !BOARD_LED_ON_LEVEL);
|
||||
bsp_pin_output(BOARD_LED_GREEN_PIN, !BOARD_LED_ON_LEVEL);
|
||||
#endif
|
||||
|
||||
#if BOARD_KEY_ENABLE
|
||||
bsp_pin_input(BOARD_KEY_PIN, 1);
|
||||
#endif
|
||||
|
||||
#if BOARD_CHG_ENABLE
|
||||
/* PORTG 作数字输入必须开 dieh,否则读不到 ME4054 CHG */
|
||||
gpio_disable_fun_output_port(BOARD_CHG_PIN);
|
||||
gpio_set_pull_up(BOARD_CHG_PIN, 0);
|
||||
gpio_set_pull_down(BOARD_CHG_PIN, 0);
|
||||
gpio_set_die(BOARD_CHG_PIN, 1);
|
||||
gpio_set_dieh(BOARD_CHG_PIN, 1);
|
||||
gpio_direction_input(BOARD_CHG_PIN);
|
||||
adc_add_sample_ch(AD_CH_LDO5V);
|
||||
adc_set_sample_freq(AD_CH_LDO5V, 500);
|
||||
#endif
|
||||
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
bsp_motor_pwm_init();
|
||||
s_motor_active = 0;
|
||||
s_motor_switch_pending = 0;
|
||||
s_left_direction = BSP_MOTOR_STOP;
|
||||
s_right_direction = BSP_MOTOR_STOP;
|
||||
#if BOARD_MOTOR_PWM_IDLE_CLOSE
|
||||
bsp_motor_pwm_clock_off();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
s_initialized = 1;
|
||||
log_info("hw init led=%d key=%d chg=%d motor=%d shake=%d\n",
|
||||
BOARD_LED_ENABLE, BOARD_KEY_ENABLE, BOARD_CHG_ENABLE,
|
||||
BOARD_MOTOR_ENABLE, BOARD_SHAKE_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 红灯开关
|
||||
* @param on 1=亮,0=灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_red_set(uint8_t on)
|
||||
{
|
||||
#if BOARD_LED_ENABLE
|
||||
gpio_direction_output(BOARD_LED_RED_PIN, on ? BOARD_LED_ON_LEVEL : !BOARD_LED_ON_LEVEL);
|
||||
#else
|
||||
(void)on;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绿灯开关
|
||||
* @param on 1=亮,0=灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_green_set(uint8_t on)
|
||||
{
|
||||
#if BOARD_LED_ENABLE
|
||||
gpio_direction_output(BOARD_LED_GREEN_PIN, on ? BOARD_LED_ON_LEVEL : !BOARD_LED_ON_LEVEL);
|
||||
#else
|
||||
(void)on;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 红绿灯全灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_all_off(void)
|
||||
{
|
||||
bsp_led_red_set(0);
|
||||
bsp_led_green_set(0);
|
||||
}
|
||||
|
||||
void bsp_drive_set(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty)
|
||||
{
|
||||
#if !BOARD_MOTOR_ENABLE
|
||||
(void)left_dir;
|
||||
(void)left_duty;
|
||||
(void)right_dir;
|
||||
(void)right_duty;
|
||||
#else
|
||||
uint8_t left_reversing;
|
||||
uint8_t right_reversing;
|
||||
|
||||
if (left_dir < BSP_MOTOR_BACKWARD || left_dir > BSP_MOTOR_FORWARD ||
|
||||
right_dir < BSP_MOTOR_BACKWARD || right_dir > BSP_MOTOR_FORWARD) {
|
||||
return;
|
||||
}
|
||||
if (left_dir == BSP_MOTOR_STOP) {
|
||||
left_duty = 0;
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_STOP) {
|
||||
right_duty = 0;
|
||||
}
|
||||
|
||||
if (left_duty == 0 && right_duty == 0) {
|
||||
s_motor_switch_pending = 0;
|
||||
/* Keep the last energized directions so an immediate opposite command
|
||||
* still observes the H-bridge dead time. */
|
||||
bsp_drive_gpio(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_motor_switch_pending) {
|
||||
s_pending_left_direction = left_dir;
|
||||
s_pending_left_duty = left_duty;
|
||||
s_pending_right_direction = right_dir;
|
||||
s_pending_right_duty = right_duty;
|
||||
return;
|
||||
}
|
||||
|
||||
left_reversing = (uint8_t)(s_left_direction != BSP_MOTOR_STOP &&
|
||||
left_dir != BSP_MOTOR_STOP &&
|
||||
left_dir != s_left_direction);
|
||||
right_reversing = (uint8_t)(s_right_direction != BSP_MOTOR_STOP &&
|
||||
right_dir != BSP_MOTOR_STOP &&
|
||||
right_dir != s_right_direction);
|
||||
if (left_reversing || right_reversing) {
|
||||
bsp_drive_gpio(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
s_pending_left_direction = left_dir;
|
||||
s_pending_left_duty = left_duty;
|
||||
s_pending_right_direction = right_dir;
|
||||
s_pending_right_duty = right_duty;
|
||||
s_motor_switch_started_ms = timer_get_ms();
|
||||
s_motor_switch_pending = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
bsp_drive_gpio(left_dir, left_duty, right_dir, right_duty);
|
||||
s_left_direction = left_duty ? left_dir : BSP_MOTOR_STOP;
|
||||
s_right_direction = right_duty ? right_dir : BSP_MOTOR_STOP;
|
||||
#endif
|
||||
}
|
||||
|
||||
void bsp_drive_stop(void)
|
||||
{
|
||||
bsp_drive_set(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进换向死区计时,按真实毫秒计时;死区结束后切到目标方向/占空比
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_switch_tick(void)
|
||||
{
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
if (!s_motor_switch_pending ||
|
||||
(uint32_t)(timer_get_ms() - s_motor_switch_started_ms) <
|
||||
BOARD_MOTOR_REVERSE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_motor_switch_pending = 0;
|
||||
bsp_drive_gpio(s_pending_left_direction, s_pending_left_duty,
|
||||
s_pending_right_direction, s_pending_right_duty);
|
||||
s_left_direction = s_pending_left_duty ?
|
||||
s_pending_left_direction : BSP_MOTOR_STOP;
|
||||
s_right_direction = s_pending_right_duty ?
|
||||
s_pending_right_direction : BSP_MOTOR_STOP;
|
||||
#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 读取 ME4054 CHG 脚:充电中为低
|
||||
* @return 1=充电中,0=未充电
|
||||
*/
|
||||
uint8_t bsp_chg_is_low(void)
|
||||
{
|
||||
#if BOARD_CHG_ENABLE
|
||||
return (uint8_t)(gpio_read(BOARD_CHG_PIN) == BOARD_CHG_CHARGING_LEVEL);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取电池电压
|
||||
* @return 电池电压,单位 mV
|
||||
*/
|
||||
uint16_t bsp_vbat_mv(void)
|
||||
{
|
||||
return (uint16_t)(adc_get_voltage(AD_CH_VBAT) * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取 5V 口电压,用于判断 USB
|
||||
* @return VPWR 电压,单位 mV
|
||||
*/
|
||||
uint16_t bsp_vpwr_mv(void)
|
||||
{
|
||||
return (uint16_t)(adc_get_voltage(AD_CH_LDO5V) * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief USB 是否在位(以 ADC 为主,比较器为辅)
|
||||
* @return 1=插入,0=未插入
|
||||
*/
|
||||
uint8_t bsp_vpwr_is_online(void)
|
||||
{
|
||||
#if BOARD_CHG_ENABLE
|
||||
uint16_t mv = bsp_vpwr_mv();
|
||||
|
||||
/* 以 ADC 为准:拔电后比较器可能仍为 1,但电压已经掉到几百 mV */
|
||||
if (mv >= BOARD_VPWR_ONLINE_MV) {
|
||||
return 1;
|
||||
}
|
||||
/* LDO5V_DET is valid immediately after a charge wake, before the ADC has
|
||||
* produced a stable sample. Unlike LVCMP it also drops promptly on pull. */
|
||||
if (get_ldo5v_online_hw()) {
|
||||
return 1;
|
||||
}
|
||||
if (mv <= BOARD_VPWR_OFF_MV) {
|
||||
return 0;
|
||||
}
|
||||
if (get_lvcmp_det()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 轻睡前关掉空闲电机 PWM(电机忙碌时不关,避免玩法被掐断)
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_hw_sleep_pwm_off(void)
|
||||
{
|
||||
#if BOARD_MOTOR_ENABLE && BOARD_MOTOR_PWM_IDLE_CLOSE
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
if (bsp_motor_is_busy()) {
|
||||
return;
|
||||
}
|
||||
bsp_motor_pwm_clock_off();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 电机是否还在转或处于换向死区
|
||||
* @return 1=忙碌,0=已停且 PWM 已关
|
||||
*/
|
||||
uint8_t bsp_motor_is_busy(void)
|
||||
{
|
||||
#if !BOARD_MOTOR_ENABLE
|
||||
return 0;
|
||||
#else
|
||||
return (uint8_t)(s_motor_active || s_motor_switch_pending);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 进深睡前把电机 PWM 关掉并拉低,灯全灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_hw_enter_sleep_io(void)
|
||||
{
|
||||
bsp_drive_stop();
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
s_motor_active = 0;
|
||||
bsp_motor_pwm_clock_off();
|
||||
#endif
|
||||
bsp_led_all_off();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/******************************************************************************
|
||||
* @file bsp_hw.h
|
||||
* @brief CBC4 板级硬件:上下双电机 / 震动 / LED / 按键 / 充电 / 电量
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.6.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 停转改为反转刹车;去掉未贴件限位
|
||||
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 50% 占空比降速
|
||||
* - V1.4.0, 2026.08.24, cyWu, 增加按占空比驱动,供玩法变速
|
||||
* - V1.5.0, 2026.08.25, cyWu, 空闲关闭 MCPWM 时钟,避免待机空转耗电
|
||||
* - V1.6.0, 2026.08.25, cyWu, 去掉停转反接刹车,改自由滑行停止,避免刹车
|
||||
* 电流冲击拉垮供电轨触发 LVD 复位
|
||||
******************************************************************************/
|
||||
|
||||
#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;
|
||||
|
||||
typedef enum {
|
||||
BSP_MOTOR_BACKWARD = -1,
|
||||
BSP_MOTOR_STOP = 0,
|
||||
BSP_MOTOR_FORWARD = 1
|
||||
} BspMotorDir_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化 GPIO / 充电 ADC
|
||||
* @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 红灯开关
|
||||
* @param on 1=亮,0=灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_red_set(uint8_t on);
|
||||
|
||||
/**
|
||||
* @brief 绿灯开关
|
||||
* @param on 1=亮,0=灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_green_set(uint8_t on);
|
||||
|
||||
/**
|
||||
* @brief 红绿灯全灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_led_all_off(void);
|
||||
|
||||
/**
|
||||
* @brief 设置电机方向。STOP 时直接切断驱动,自由滑行到停(不做反接刹车)
|
||||
* @note 占空比用 BOARD_MOTOR_PWM_DUTY(手动点动)
|
||||
* @param left_dir/right_dir 左右轮方向
|
||||
* @param left_duty/right_duty 左右轮占空比
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_drive_set(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty);
|
||||
|
||||
/** 停止左右轮,四路 PWM 全部清零。 */
|
||||
void bsp_drive_stop(void);
|
||||
|
||||
/**
|
||||
* @brief 兼容旧调用;弹力魔盒每个动作一次性更新上下电机四路 PWM
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_switch_tick(void);
|
||||
|
||||
/**
|
||||
* @brief 电机是否还在转
|
||||
* @return 1=忙碌,0=已停且 PWM 已关
|
||||
*/
|
||||
uint8_t bsp_motor_is_busy(void);
|
||||
|
||||
/**
|
||||
* @brief 读取按键
|
||||
* @return 1=按下,0=未按下
|
||||
*/
|
||||
uint8_t bsp_key_is_pressed(void);
|
||||
|
||||
/**
|
||||
* @brief 读取 ME4054 CHG:充电中为低
|
||||
* @return 1=充电中,0=未充电
|
||||
*/
|
||||
uint8_t bsp_chg_is_low(void);
|
||||
|
||||
/**
|
||||
* @brief 读取电池电压
|
||||
* @return 电池电压,单位 mV
|
||||
*/
|
||||
uint16_t bsp_vbat_mv(void);
|
||||
|
||||
/**
|
||||
* @brief 读取 5V 口电压,用于判断 USB
|
||||
* @return VPWR 电压,单位 mV
|
||||
*/
|
||||
uint16_t bsp_vpwr_mv(void);
|
||||
|
||||
/**
|
||||
* @brief USB 是否在位(以 ADC 为主,比较器为辅)
|
||||
* @return 1=插入,0=未插入
|
||||
*/
|
||||
uint8_t bsp_vpwr_is_online(void);
|
||||
|
||||
/**
|
||||
* @brief 轻睡前关掉空闲电机 PWM(电机忙碌时不关)
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_hw_sleep_pwm_off(void);
|
||||
|
||||
/**
|
||||
* @brief 进深睡前把电机 PWM 关掉并拉低,灯全灭
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_hw_enter_sleep_io(void);
|
||||
|
||||
#endif /* __BSP_HW_H__ */
|
||||
@@ -0,0 +1,366 @@
|
||||
/******************************************************************************
|
||||
* @file usr_rtc.c
|
||||
* @brief 板级 RTC 外设实现(BR28 硬件 rtc_dev_ops)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.19
|
||||
* @version V1.1.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.19, cyWu, 首次发布,实现板级硬件 RTC 对时与本地日历读取
|
||||
* - V1.1.0, 2026.08.19, cyWu, 精简公共接口与缓存逻辑
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_config.h"
|
||||
#include "usr_rtc.h"
|
||||
#include "asm/rtc.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[USR_RTC]"
|
||||
#define LOG_ERROR_ENABLE
|
||||
#define LOG_DEBUG_ENABLE
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有宏 / 类型 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define USR_RTC_TZ_MAGIC (0x52544331u) /**< 'RTC1',校验 VM 时区 */
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
int32_t tz_s;
|
||||
} UsrRtcTzVm_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t inited;
|
||||
uint8_t tz_valid;
|
||||
int32_t tz_s;
|
||||
void *dev;
|
||||
} UsrRtc_t;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有变量 */
|
||||
/*============================================================================*/
|
||||
|
||||
static UsrRtc_t s_rtc;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有函数声明 */
|
||||
/*============================================================================*/
|
||||
|
||||
static uint8_t usr_rtc_is_leap(uint16_t year);
|
||||
static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month);
|
||||
static uint8_t usr_rtc_year_ok(uint16_t year);
|
||||
static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s,
|
||||
struct sys_time *t);
|
||||
static UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t);
|
||||
static void usr_rtc_load_tz(void);
|
||||
static void usr_rtc_save_tz(int32_t tz_s);
|
||||
|
||||
/*============================================================================*/
|
||||
/* 公有函数 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 打开 rtc 设备并加载时区
|
||||
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=设备打开失败或 RTC 未使能
|
||||
*/
|
||||
UsrRtc_Ret_t usr_rtc_init(void)
|
||||
{
|
||||
struct sys_time t;
|
||||
|
||||
if (s_rtc.inited)
|
||||
{
|
||||
return USR_RTC_OK;
|
||||
}
|
||||
|
||||
#if TCFG_APP_RTC_EN
|
||||
s_rtc.dev = dev_open("rtc", NULL);
|
||||
if (!s_rtc.dev)
|
||||
{
|
||||
log_error("open rtc fail");
|
||||
return USR_RTC_ERR_NOT_INIT;
|
||||
}
|
||||
#else
|
||||
log_error("TCFG_APP_RTC_EN=0");
|
||||
return USR_RTC_ERR_NOT_INIT;
|
||||
#endif
|
||||
|
||||
usr_rtc_load_tz();
|
||||
s_rtc.inited = 1;
|
||||
|
||||
memset(&t, 0, sizeof(t));
|
||||
read_sys_time(&t);
|
||||
log_info("rtc open %u-%02u-%02u %02u:%02u:%02u tz=%ld tz_ok=%u",
|
||||
t.year, t.month, t.day, t.hour, t.min, t.sec,
|
||||
(long)s_rtc.tz_s, s_rtc.tz_valid);
|
||||
return USR_RTC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前本地时间
|
||||
* @param t 成功时赋值;失败不改动
|
||||
* @return 1=已对时且读成功,0=未初始化/未对时/参数非法
|
||||
*/
|
||||
uint8_t usr_rtc_get_time(struct sys_time *t)
|
||||
{
|
||||
struct sys_time tmp;
|
||||
|
||||
if (!t || !s_rtc.inited || !s_rtc.tz_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
read_sys_time(&tmp);
|
||||
if (!usr_rtc_year_ok(tmp.year))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
*t = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UTC 秒 + 时区 → 本地日历写入 RTC
|
||||
* @param unix_utc UTC 秒
|
||||
* @param tz_s 时区偏移秒
|
||||
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=未初始化,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_INVALID=年份非法
|
||||
*/
|
||||
UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s)
|
||||
{
|
||||
struct sys_time t;
|
||||
UsrRtc_Ret_t ret;
|
||||
|
||||
if (!s_rtc.inited)
|
||||
{
|
||||
return USR_RTC_ERR_NOT_INIT;
|
||||
}
|
||||
if (unix_utc == 0)
|
||||
{
|
||||
log_debug("unix=0, ignore");
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
|
||||
/* 网关采样到写入大约慢 1s,在 UTC 上补偿 */
|
||||
unix_utc += USR_RTC_SYNC_COMPENSATE_S;
|
||||
if ((tz_s > USR_RTC_TZ_ABS_MAX_S) || (tz_s < -USR_RTC_TZ_ABS_MAX_S))
|
||||
{
|
||||
log_error("tz out of range %ld", (long)tz_s);
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
if (unix_utc > 0xFFFFFFFFull)
|
||||
{
|
||||
log_error("unix too large");
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
|
||||
usr_rtc_unix_to_calendar((uint32_t)unix_utc, tz_s, &t);
|
||||
if (!usr_rtc_year_ok(t.year))
|
||||
{
|
||||
log_error("year %u not in %u~%u, wait sync",
|
||||
t.year, USR_RTC_YEAR_MIN, USR_RTC_YEAR_MAX);
|
||||
return USR_RTC_ERR_INVALID;
|
||||
}
|
||||
|
||||
ret = usr_rtc_write(&t);
|
||||
if (ret != USR_RTC_OK)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
usr_rtc_save_tz(tz_s);
|
||||
log_info("set rtc %u-%02u-%02u %02u:%02u:%02u tz=%ld",
|
||||
t.year, t.month, t.day, t.hour, t.min, t.sec, (long)tz_s);
|
||||
return USR_RTC_OK;
|
||||
}
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有函数 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 写硬件日历
|
||||
* @param t 本地日历
|
||||
* @return USR_RTC_OK=成功,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
static UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t)
|
||||
{
|
||||
struct sys_time tmp;
|
||||
|
||||
if (!t)
|
||||
{
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
if (!s_rtc.inited)
|
||||
{
|
||||
return USR_RTC_ERR_NOT_INIT;
|
||||
}
|
||||
if (!usr_rtc_year_ok(t->year) || (t->month < 1) || (t->month > 12) ||
|
||||
(t->day < 1) || (t->hour > 23) || (t->min > 59) || (t->sec > 59))
|
||||
{
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
if (t->day > usr_rtc_days_in_month(t->year, t->month))
|
||||
{
|
||||
return USR_RTC_ERR_PARAM;
|
||||
}
|
||||
|
||||
tmp = *t;
|
||||
write_sys_time(&tmp);
|
||||
return USR_RTC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 是否闰年
|
||||
* @param year 公历年
|
||||
* @return 1=闰年,0=平年
|
||||
*/
|
||||
static uint8_t usr_rtc_is_leap(uint16_t year)
|
||||
{
|
||||
if ((year % 400u) == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if ((year % 100u) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ((year % 4u) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 某月天数
|
||||
* @param year 年
|
||||
* @param month 月 1~12
|
||||
* @return 该月天数;非法月份返回 0
|
||||
*/
|
||||
static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month)
|
||||
{
|
||||
static const uint8_t days[12] = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
if ((month < 1) || (month > 12))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if ((month == 2) && usr_rtc_is_leap(year))
|
||||
{
|
||||
return 29;
|
||||
}
|
||||
return days[month - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 年份是否在合法窗口
|
||||
* @param year 年
|
||||
* @return 1=合法,0=非法
|
||||
*/
|
||||
static uint8_t usr_rtc_year_ok(uint16_t year)
|
||||
{
|
||||
return ((year >= USR_RTC_YEAR_MIN) && (year <= USR_RTC_YEAR_MAX)) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UTC 秒 + 时区 → 本地日历
|
||||
* @param unix_utc UTC 秒
|
||||
* @param tz_s 时区偏移秒
|
||||
* @param t 输出本地日历
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s,
|
||||
struct sys_time *t)
|
||||
{
|
||||
int64_t local;
|
||||
uint32_t days;
|
||||
uint32_t tod;
|
||||
uint16_t y;
|
||||
uint8_t m;
|
||||
uint8_t dim;
|
||||
|
||||
memset(t, 0, sizeof(*t));
|
||||
local = (int64_t)unix_utc + (int64_t)tz_s;
|
||||
if (local < 0)
|
||||
{
|
||||
local = 0;
|
||||
}
|
||||
|
||||
tod = (uint32_t)(local % 86400);
|
||||
days = (uint32_t)(local / 86400);
|
||||
t->sec = (uint8_t)(tod % 60u);
|
||||
t->min = (uint8_t)((tod / 60u) % 60u);
|
||||
t->hour = (uint8_t)(tod / 3600u);
|
||||
|
||||
y = 1970;
|
||||
while (1)
|
||||
{
|
||||
uint32_t diy = usr_rtc_is_leap(y) ? 366u : 365u;
|
||||
if (days < diy)
|
||||
{
|
||||
break;
|
||||
}
|
||||
days -= diy;
|
||||
y++;
|
||||
if (y > 4095u)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
t->year = y;
|
||||
|
||||
for (m = 1; m <= 12u; m++)
|
||||
{
|
||||
dim = usr_rtc_days_in_month(y, m);
|
||||
if (days < dim)
|
||||
{
|
||||
break;
|
||||
}
|
||||
days -= dim;
|
||||
}
|
||||
t->month = m;
|
||||
t->day = (uint8_t)(days + 1u);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 VM 加载时区
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_rtc_load_tz(void)
|
||||
{
|
||||
UsrRtcTzVm_t vm;
|
||||
int ret;
|
||||
|
||||
s_rtc.tz_valid = 0;
|
||||
s_rtc.tz_s = 0;
|
||||
memset(&vm, 0, sizeof(vm));
|
||||
ret = syscfg_read(CFG_USER_TIM_FLAG, (u8 *)&vm, sizeof(vm));
|
||||
if ((ret == (int)sizeof(vm)) && (vm.magic == USR_RTC_TZ_MAGIC))
|
||||
{
|
||||
if ((vm.tz_s <= USR_RTC_TZ_ABS_MAX_S) && (vm.tz_s >= -USR_RTC_TZ_ABS_MAX_S))
|
||||
{
|
||||
s_rtc.tz_s = vm.tz_s;
|
||||
s_rtc.tz_valid = 1;
|
||||
log_info("tz from vm %ld", (long)s_rtc.tz_s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log_debug("tz vm empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 时区写入 VM
|
||||
* @param tz_s 时区偏移秒
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_rtc_save_tz(int32_t tz_s)
|
||||
{
|
||||
UsrRtcTzVm_t vm;
|
||||
|
||||
s_rtc.tz_s = tz_s;
|
||||
s_rtc.tz_valid = 1;
|
||||
vm.magic = USR_RTC_TZ_MAGIC;
|
||||
vm.tz_s = tz_s;
|
||||
syscfg_write(CFG_USER_TIM_FLAG, (u8 *)&vm, sizeof(vm));
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* @file usr_rtc.h
|
||||
* @brief 板级 RTC:硬件对时 + 客户读本地日历
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.19
|
||||
* @version V1.1.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.19, cyWu, 首次发布,实现板级硬件 RTC 对时与本地日历读取
|
||||
* - V1.1.0, 2026.08.19, cyWu, 精简接口;usr_rtc_get_time 返回本地日历
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __USR_RTC_H__
|
||||
#define __USR_RTC_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "system/sys_time.h"
|
||||
|
||||
/*============================================================================*/
|
||||
/* 宏定义 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define USR_RTC_YEAR_MIN (2026u) /**< 合法年份下限 */
|
||||
#define USR_RTC_YEAR_MAX (2098u) /**< 合法年份上限 */
|
||||
#define USR_RTC_TZ_ABS_MAX_S (57600) /**< 时区偏移绝对值上限(±16h) */
|
||||
#define USR_RTC_SYNC_COMPENSATE_S (1u) /**< 网关对时延迟补偿(秒) */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 错误码(init / set_from_unix) */
|
||||
/*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
USR_RTC_OK = 0,
|
||||
USR_RTC_ERR_PARAM,
|
||||
USR_RTC_ERR_NOT_INIT,
|
||||
USR_RTC_ERR_INVALID, /**< 年份非法,等待 App 对时 */
|
||||
} UsrRtc_Ret_t;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 外部接口 */
|
||||
/*============================================================================*/
|
||||
|
||||
/**
|
||||
* @brief 打开 rtc 设备并加载时区
|
||||
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=设备打开失败或 RTC 未使能
|
||||
*/
|
||||
UsrRtc_Ret_t usr_rtc_init(void);
|
||||
|
||||
/**
|
||||
* @brief 获取当前本地时间(客户常用)
|
||||
* @param t 成功时写入年月日时分秒;失败时不改动
|
||||
* @return 1=已对时且读成功,0=未初始化/未对时/参数非法
|
||||
*/
|
||||
uint8_t usr_rtc_get_time(struct sys_time *t);
|
||||
|
||||
/**
|
||||
* @brief App/网关 DEVICEINFO:UTC 秒 + 时区写入 RTC
|
||||
* @param unix_utc UTC Unix 秒;0 无效
|
||||
* @param tz_s 时区偏移秒(东八区 28800)
|
||||
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=未初始化,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_INVALID=年份非法
|
||||
*/
|
||||
UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s);
|
||||
|
||||
#endif /* __USR_RTC_H__ */
|
||||
Reference in New Issue
Block a user