/****************************************************************************** * @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); /* ME4054 CHRG is open-drain/high-Z when not charging. Keep PG8 pulled * high so the digital input has a deterministic level in that state. */ gpio_set_pull_up(BOARD_CHG_PIN, 1); 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); gpio_disable_fun_output_port(BOARD_STDBY_PIN); gpio_set_pull_up(BOARD_STDBY_PIN, 1); gpio_set_pull_down(BOARD_STDBY_PIN, 0); gpio_set_die(BOARD_STDBY_PIN, 1); gpio_set_dieh(BOARD_STDBY_PIN, 1); gpio_direction_input(BOARD_STDBY_PIN); #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 } /** @return 1 when TP4056 STDBY is low; otherwise 0. */ uint8_t bsp_stby_is_low(void) { #if BOARD_CHG_ENABLE return (uint8_t)(gpio_read(BOARD_STDBY_PIN) == BOARD_STDBY_FULL_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(); }