/****************************************************************************** * @file bsp_hw.c * @brief CBC2 板级 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; /** 电机状态,替代原来散落的 7 个 static */ typedef struct { BspMotorDir_t dir; /**< 当前实际方向 */ BspMotorDir_t pending; /**< 换向死区结束后要切到的方向 */ uint16_t duty; /**< 当前实际占空比 */ uint16_t pending_duty; /**< 换向死区结束后要用的占空比 */ uint16_t switch_ms; /**< 换向死区剩余时长,0=空闲/不在换向中 */ u32 switch_t0; /**< 换向死区起始时间戳 */ uint8_t pwm_clk_on; /**< 1=MCPWM 时钟已打开 */ } BspMotorCtx_t; static BspMotorCtx_t s_motor; extern u32 timer_get_ms(void); #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) /** * @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 /** * @brief 初始化 INA/INB 两路 MCPWM,初始占空比 0(停转) * @return 无 */ static void bsp_motor_pwm_init(void) { struct pwm_platform_data pwm; gpio_set_pull_up(BOARD_MOTOR_INA_PIN, 0); gpio_set_pull_down(BOARD_MOTOR_INA_PIN, 0); gpio_set_die(BOARD_MOTOR_INA_PIN, 1); gpio_set_pull_up(BOARD_MOTOR_INB_PIN, 0); gpio_set_pull_down(BOARD_MOTOR_INB_PIN, 0); gpio_set_die(BOARD_MOTOR_INB_PIN, 1); pwm.pwm_aligned_mode = pwm_edge_aligned; pwm.frequency = BOARD_MOTOR_PWM_HZ; pwm.duty = 0; pwm.l_pin = (u8) - 1; pwm.complementary_en = 0; pwm.pwm_ch_num = pwm_ch0; pwm.h_pin = BOARD_MOTOR_INA_PIN; mcpwm_init(&pwm); pwm.pwm_ch_num = pwm_ch1; pwm.h_pin = BOARD_MOTOR_INB_PIN; mcpwm_init(&pwm); mcpwm_set_duty(pwm_ch0, 0); mcpwm_set_duty(pwm_ch1, 0); s_motor.pwm_clk_on = 1; log_info("motor pwm %uHz duty=%u/10000\n", (unsigned)BOARD_MOTOR_PWM_HZ, (unsigned)BOARD_MOTOR_PWM_DUTY); } /** * @brief 打开 MCPWM 并绑回 INA/INB * @return 无 */ static void bsp_motor_pwm_clock_on(void) { if (s_motor.pwm_clk_on) { return; } mcpwm_open(pwm_ch0); mcpwm_open(pwm_ch1); gpio_och_sel_output_signal(BOARD_MOTOR_INA_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H); gpio_och_sel_output_signal(BOARD_MOTOR_INB_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H); gpio_set_direction(BOARD_MOTOR_INA_PIN, 0); gpio_set_direction(BOARD_MOTOR_INB_PIN, 0); s_motor.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); if (s_motor.pwm_clk_on) { mcpwm_close(pwm_ch0); mcpwm_close(pwm_ch1); gpio_och_disable_output_signal(BOARD_MOTOR_INA_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H); gpio_och_disable_output_signal(BOARD_MOTOR_INB_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H); JL_MCPWM->MCPWM_CON0 = 0; s_motor.pwm_clk_on = 0; } gpio_direction_output(BOARD_MOTOR_INA_PIN, 0); gpio_direction_output(BOARD_MOTOR_INB_PIN, 0); } /** * @brief 立刻改 INA/INB PWM。运转脚输出 duty,另一脚 0%;STOP=两路 0% * @param dir 目标方向 * @param duty 占空比 0~10000 * @return 无 */ static void bsp_motor_gpio(BspMotorDir_t dir, uint16_t duty) { u16 ina_duty = 0; u16 inb_duty = 0; if (duty > 10000) { duty = 10000; } if (dir == BSP_MOTOR_IN) { if (BOARD_MOTOR_IN_INA_HIGH) { ina_duty = duty; } else { inb_duty = duty; } } else if (dir == BSP_MOTOR_OUT) { if (BOARD_MOTOR_IN_INA_HIGH) { inb_duty = duty; } else { ina_duty = duty; } } if ((ina_duty == 0) && (inb_duty == 0)) { /* 换向死区(BOARD_MOTOR_REVERSE_MS)、刹车瞬间也会短暂占空比为 0, * 但马上(1~30ms 内)还要再驱动,不能当成“真空闲”去关时钟: * 频繁 close/open MCPWM 外设比单纯调占空比激进得多,玩法换向越快 * 关得越勤,有卡死风险。这里只清占空比(两脚拉低,电气效果一样), * 真正的空闲关时钟统一交给 bsp_hw_sleep_pwm_off()——它在系统真的 * 要进轻睡前才调用,并且会先查 bsp_motor_is_busy()。 */ mcpwm_set_duty(pwm_ch0, 0); mcpwm_set_duty(pwm_ch1, 0); return; } bsp_motor_pwm_clock_on(); mcpwm_set_duty(pwm_ch0, ina_duty); mcpwm_set_duty(pwm_ch1, inb_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.dir = BSP_MOTOR_STOP; s_motor.pending = BSP_MOTOR_STOP; s_motor.duty = 0; s_motor.pending_duty = 0; s_motor.switch_ms = 0; #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\n", BOARD_LED_ENABLE, BOARD_KEY_ENABLE, BOARD_CHG_ENABLE, BOARD_MOTOR_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); } /** * @brief 设置电机方向。STOP 时直接切断驱动,自由滑行到停(不做反接刹车) * @param dir 收绳 / 放绳 / 停转 * @return 无 */ void bsp_motor_set(BspMotorDir_t dir) { bsp_motor_set_pwm(dir, (dir == BSP_MOTOR_STOP) ? 0 : BOARD_MOTOR_PWM_DUTY); } /** * @brief 设置电机方向与 PWM 占空比 * @param dir 收绳 / 放绳 / 停转 * @param duty 占空比 0~10000;STOP 时按 0 处理 * @return 无 */ void bsp_motor_set_pwm(BspMotorDir_t dir, uint16_t duty) { #if !BOARD_MOTOR_ENABLE (void)dir; (void)duty; return; #else if (duty > 10000) { duty = 10000; } if (dir == BSP_MOTOR_STOP) { duty = 0; } /* 同向只改占空比:直接改 PWM,不走换向死区 */ if (dir == s_motor.dir && s_motor.switch_ms == 0) { if (duty == s_motor.duty) { return; } s_motor.duty = duty; s_motor.pending = dir; s_motor.pending_duty = duty; bsp_motor_gpio(dir, duty); return; } /* 停转:直接切断驱动,让电机自由滑行到停,不做反接刹车——反接刹车电流比 * 正常驱动大(电池要同时顶住反向驱动电流和电机反电动势),实测会把供电轨 * 拉低触发 LVD 复位。哪怕正在换向死区里,也直接取消死区改成停。 */ if (dir == BSP_MOTOR_STOP) { s_motor.switch_ms = 0; s_motor.dir = BSP_MOTOR_STOP; s_motor.pending = BSP_MOTOR_STOP; s_motor.duty = 0; s_motor.pending_duty = 0; bsp_motor_gpio(BSP_MOTOR_STOP, 0); return; } /* 换向:先松开再输出,避免 H 桥直通 */ if (s_motor.dir != BSP_MOTOR_STOP && dir != s_motor.dir) { bsp_motor_gpio(BSP_MOTOR_STOP, 0); s_motor.pending = dir; s_motor.pending_duty = duty; s_motor.switch_ms = BOARD_MOTOR_REVERSE_MS; s_motor.switch_t0 = timer_get_ms(); s_motor.dir = BSP_MOTOR_STOP; s_motor.duty = 0; return; } s_motor.switch_ms = 0; s_motor.dir = dir; s_motor.pending = dir; s_motor.duty = duty; s_motor.pending_duty = duty; bsp_motor_gpio(dir, duty); #endif } /** * @brief 推进换向死区计时,按真实毫秒计时;死区结束后切到目标方向/占空比 * @return 无 */ void bsp_motor_switch_tick(void) { #if !BOARD_MOTOR_ENABLE return; #else if (s_motor.switch_ms == 0) { return; } if ((timer_get_ms() - s_motor.switch_t0) < s_motor.switch_ms) { return; } s_motor.switch_ms = 0; s_motor.dir = s_motor.pending; s_motor.duty = s_motor.pending_duty; bsp_motor_gpio(s_motor.dir, s_motor.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 读取 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; } if (mv <= BOARD_VPWR_OFF_MV) { return 0; } if (get_ldo5v_online_hw() || 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 if (s_motor.switch_ms) { return 1; } if (s_motor.dir != BSP_MOTOR_STOP) { return 1; } if (s_motor.pending != BSP_MOTOR_STOP) { return 1; } return 0; #endif } /** * @brief 进深睡前把电机 PWM 关掉并拉低,灯全灭 * @return 无 */ void bsp_hw_enter_sleep_io(void) { bsp_motor_set(BSP_MOTOR_STOP); #if BOARD_MOTOR_ENABLE s_motor.switch_ms = 0; s_motor.dir = BSP_MOTOR_STOP; s_motor.pending = BSP_MOTOR_STOP; s_motor.duty = 0; bsp_motor_pwm_clock_off(); #endif bsp_led_all_off(); }