Files
2026-09-01 17:31:15 +08:00

518 lines
14 KiB
C

/******************************************************************************
* @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();
}