This commit is contained in:
2026-03-28 13:48:31 +08:00
parent ca76b115ea
commit 0e9f24d3f6
10 changed files with 329 additions and 248 deletions

View File

@@ -5,22 +5,15 @@
#include "tim.h"
/*
* 这个文件负责两件事:
* 1) 把 LADRC 输出的有符号控制量,转换成 H 桥两路 PWM
* 2) 读取四路编码器,得到实时 RPM并累加成供 CAN 上传的里程计增量
*
* 约定:
* - 上层统一把“车体前进”视为正方向
* - 由于左右电机安装方向镜像,左侧电机的硬件方向需要在底层翻转
*/
/* ================== 全局静态变量 ================== */
static uint16_t s_last_count[4] = {0U, 0U, 0U, 0U};
static volatile float s_current_rpm[4] = {0.0f, 0.0f, 0.0f, 0.0f};
static volatile int32_t s_odom_acc_ticks[4] = {0, 0, 0, 0};
/* ================== 内部辅助函数声明 ================== */
/* 速度估计滚动窗口:降低 5ms 单拍增量测速的量化噪声 */
static int32_t s_speed_acc_ticks[4] = {0, 0, 0, 0};
static uint8_t s_speed_window_count = 0U;
static float s_speed_window_dt = 0.0f;
static inline void Motor_WriteBridge(TIM_HandleTypeDef *htim,
uint32_t channel_in1,
uint32_t channel_in2,
@@ -30,12 +23,6 @@ static void Motor_PrimeEncoderCounters(void);
static uint16_t Motor_ClampAbsToPwm(int32_t value);
static int16_t Motor_SaturateToI16(int32_t value);
/**
* @brief 给 H 桥两路输入分别写入 PWM。
* @note AT8236 方向约定:
* - 正转IN1 = PWMIN2 = 0
* - 反转IN1 = 0 IN2 = PWM
*/
static inline void Motor_WriteBridge(TIM_HandleTypeDef *htim,
uint32_t channel_in1,
uint32_t channel_in2,
@@ -46,9 +33,6 @@ static inline void Motor_WriteBridge(TIM_HandleTypeDef *htim,
__HAL_TIM_SET_COMPARE(htim, channel_in2, pwm_in2);
}
/**
* @brief 把任意有符号数限幅并取绝对值,转换成 PWM 占空比。
*/
static uint16_t Motor_ClampAbsToPwm(int32_t value)
{
if (value > PWM_LIMIT)
@@ -68,9 +52,6 @@ static uint16_t Motor_ClampAbsToPwm(int32_t value)
return (uint16_t)value;
}
/**
* @brief int32 饱和到 int16防止 CAN 打包时溢出回绕。
*/
static int16_t Motor_SaturateToI16(int32_t value)
{
if (value > 32767)
@@ -84,10 +65,6 @@ static int16_t Motor_SaturateToI16(int32_t value)
return (int16_t)value;
}
/**
* @brief 在编码器启动后,用当前计数值作为初值。
* @note 这样可以避免“last_count 初始为 0”导致首个控制周期出现巨大假速度。
*/
static void Motor_PrimeEncoderCounters(void)
{
s_last_count[MOTOR_FL] = (uint16_t)__HAL_TIM_GET_COUNTER(&htim5);
@@ -96,9 +73,6 @@ static void Motor_PrimeEncoderCounters(void)
s_last_count[MOTOR_RR] = (uint16_t)__HAL_TIM_GET_COUNTER(&htim1);
}
/**
* @brief 启动所有 PWM 和编码器接口。
*/
void Motor_Init(void)
{
uint32_t primask = __get_PRIMASK();
@@ -117,7 +91,6 @@ void Motor_Init(void)
if (HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL) != HAL_OK) { Error_Handler(); }
if (HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL) != HAL_OK) { Error_Handler(); }
/* 启动完成后,把输出和测速状态清零。 */
Motor_Brake_All();
Motor_PrimeEncoderCounters();
@@ -126,31 +99,27 @@ void Motor_Init(void)
s_current_rpm[MOTOR_RL] = 0.0f;
s_current_rpm[MOTOR_FR] = 0.0f;
s_current_rpm[MOTOR_RR] = 0.0f;
s_odom_acc_ticks[MOTOR_FL] = 0;
s_odom_acc_ticks[MOTOR_RL] = 0;
s_odom_acc_ticks[MOTOR_FR] = 0;
s_odom_acc_ticks[MOTOR_RR] = 0;
s_speed_acc_ticks[MOTOR_FL] = 0;
s_speed_acc_ticks[MOTOR_RL] = 0;
s_speed_acc_ticks[MOTOR_FR] = 0;
s_speed_acc_ticks[MOTOR_RR] = 0;
s_speed_window_count = 0U;
s_speed_window_dt = 0.0f;
__set_PRIMASK(primask);
}
/**
* @brief 把有符号控制量转换为电机驱动输出。
* @param id 电机编号
* @param control_out 上层控制量,正值表示“希望车体前进”
* @note
* 1) 这里先按“车体坐标系”理解 control_out。
* 2) 左侧电机由于安装镜像,硬件方向要翻转一次。
* 3) 变量 forward 的语义始终保持一致:
* true -> IN1 = PWM, IN2 = 0
* false -> IN1 = 0, IN2 = PWM
*/
void Set_Motor_Output(Motor_ID_t id, int16_t control_out)
{
int32_t hw_command = (int32_t)control_out;
bool forward;
uint16_t pwm_val;
/* 左侧硬件方向镜像:同样的“车体前进”命令,左轮需要反向转。 */
if ((id == MOTOR_FL) || (id == MOTOR_RL))
{
hw_command = -hw_command;
@@ -210,12 +179,6 @@ void Set_Motor_Output(Motor_ID_t id, int16_t control_out)
}
}
/**
* @brief 停止四个电机输出。
* @note 这里使用“两个方向脚都置 0”的空转停车方式。
* 如果你的驱动板支持真正的短刹车,并且你确认接线与驱动逻辑,
* 可以单独再加一个 Brake 模式。
*/
void Motor_Brake_All(void)
{
Motor_WriteBridge(&htim2, TIM_CHANNEL_3, TIM_CHANNEL_4, 0U, 0U);
@@ -224,13 +187,6 @@ void Motor_Brake_All(void)
Motor_WriteBridge(&htim8, TIM_CHANNEL_3, TIM_CHANNEL_4, 0U, 0U);
}
/**
* @brief 读取编码器脉冲并更新实时转速。
* @param dt_s 控制周期,单位秒,例如 10ms 就传 0.01f
* @note
* - 利用 int16_t 差分天然处理 16 位编码器计数器回绕
* - 左侧编码器输入在这里反相,保证上层统一把“车体前进”看成正 RPM
*/
void Motor_Update_RPM(float dt_s)
{
uint16_t curr_count_fl;
@@ -239,6 +195,7 @@ void Motor_Update_RPM(float dt_s)
uint16_t curr_count_rr;
int16_t delta[4];
float rpm_scale;
int i;
if (dt_s <= 0.0f)
{
@@ -260,27 +217,37 @@ void Motor_Update_RPM(float dt_s)
s_last_count[MOTOR_FR] = curr_count_fr;
s_last_count[MOTOR_RR] = curr_count_rr;
/* 左侧输入镜像修正:让“车体前进”统一表现为正脉冲 / 正 RPM。 */
delta[MOTOR_FL] = (int16_t)(-delta[MOTOR_FL]);
delta[MOTOR_RL] = (int16_t)(-delta[MOTOR_RL]);
// delta[MOTOR_FR] = (int16_t)(-delta[MOTOR_FR]);
// delta[MOTOR_RR] = (int16_t)(-delta[MOTOR_RR]);
/* 用 32 位内部累加,避免偶发高速度时 16 位回绕。 */
s_odom_acc_ticks[MOTOR_FL] += delta[MOTOR_FL];
s_odom_acc_ticks[MOTOR_RL] += delta[MOTOR_RL];
s_odom_acc_ticks[MOTOR_FR] += delta[MOTOR_FR];
s_odom_acc_ticks[MOTOR_RR] += delta[MOTOR_RR];
rpm_scale = 60.0f / ((float)PULSES_PER_REV * dt_s);
s_current_rpm[MOTOR_FL] = (float)delta[MOTOR_FL] * rpm_scale;
s_current_rpm[MOTOR_RL] = (float)delta[MOTOR_RL] * rpm_scale;
s_current_rpm[MOTOR_FR] = (float)delta[MOTOR_FR] * rpm_scale;
s_current_rpm[MOTOR_RR] = (float)delta[MOTOR_RR] * rpm_scale;
/* 速度估计窗口累加 */
s_speed_acc_ticks[MOTOR_FL] += delta[MOTOR_FL];
s_speed_acc_ticks[MOTOR_RL] += delta[MOTOR_RL];
s_speed_acc_ticks[MOTOR_FR] += delta[MOTOR_FR];
s_speed_acc_ticks[MOTOR_RR] += delta[MOTOR_RR];
s_speed_window_dt += dt_s;
s_speed_window_count++;
if (s_speed_window_count >= MOTOR_SPEED_WINDOW_SAMPLES)
{
rpm_scale = 60.0f / ((float)PULSES_PER_REV * s_speed_window_dt);
for (i = 0; i < 4; ++i)
{
s_current_rpm[i] = (float)s_speed_acc_ticks[i] * rpm_scale;
s_speed_acc_ticks[i] = 0;
}
s_speed_window_count = 0U;
s_speed_window_dt = 0.0f;
}
}
/**
* @brief 获取某个电机的实时 RPM。
*/
float Get_Motor_RPM(Motor_ID_t id)
{
if ((id < MOTOR_FL) || (id > MOTOR_RR))
@@ -291,11 +258,6 @@ float Get_Motor_RPM(Motor_ID_t id)
return s_current_rpm[id];
}
/**
* @brief 读取并清空供 CAN 上传的里程计增量。
* @note 这个函数会被主循环调用,而累加动作发生在 10ms 中断里,
* 所以“读取 + 清零”必须放在同一个临界区,避免丢计数。
*/
void Motor_Get_And_Clear_Delta_Ticks(int16_t *d_fl, int16_t *d_rl, int16_t *d_fr, int16_t *d_rr)
{
int32_t fl;