feat: 完成弹力魔盒初版固件

This commit is contained in:
2026-08-31 20:15:19 +08:00
parent 026d6d5da7
commit b63a928b92
34 changed files with 1320 additions and 781 deletions
+285 -156
View File
@@ -1,15 +1,3 @@
/******************************************************************************
* @file app_mode.c
* @brief 自动模式 1~6:按节拍改方向与 PWM 占空比
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.2.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 接入 H 桥往返节拍
* - V1.2.0, 2026.08.24, cyWu, 节拍带占空比;六档波形加花样
******************************************************************************/
#include "system/includes.h"
#include "app_mode.h"
#include "bsp_hw.h"
@@ -19,200 +7,341 @@
#define LOG_INFO_ENABLE
#include "debug.h"
typedef struct {
BspMotorDir_t dir;
uint16_t ms;
uint16_t duty; /**< 0~10000STOP 忽略 */
} AppModeStep_t;
#define MAGIC_BOX_HIGH_PERCENT 100
#define MAGIC_BOX_MEDIUM_LOW_PERCENT 45
#define MAGIC_BOX_MODE1_FORWARD_MS 585
#define MAGIC_BOX_MODE1_BACKWARD_MS 600
#define MAGIC_BOX_MODE2_FORWARD_MS 260
#define MAGIC_BOX_MODE2_BACKWARD_MS 200
#define MAGIC_BOX_MODE3_FORWARD_MS 585
#define MAGIC_BOX_MODE3_BACKWARD_MS 600
#define MAGIC_BOX_MODE3_STOP_MS 2000
#define MAGIC_BOX_MODE3_LOWER_SEGMENTS 10
#define MAGIC_BOX_MODE4_FORWARD_MS 390
#define MAGIC_BOX_MODE4_BACKWARD_MS 300
#define MAGIC_BOX_MODE4_STOP_MS 3000
#define MAGIC_BOX_MODE5_BACKWARD_MS 600
#define MAGIC_BOX_MODE5_UPPER_STOP_MS 2000
#define MAGIC_BOX_MODE5_CYCLE_MS 20000
#define DUTY_SOFT 3500 /* 轻 */
#define DUTY_MID 5500 /* 中 */
#define DUTY_RUN 8000 /* 主速度 80% */
#define DUTY_PUNCH 9500 /* 猛一下 */
#define STEP_OUT(ms, duty) { BSP_MOTOR_OUT, (ms), (duty) }
#define STEP_IN(ms, duty) { BSP_MOTOR_IN, (ms), (duty) }
#define STEP_STOP(ms) { BSP_MOTOR_STOP, (ms), 0 }
#define STEP_END { BSP_MOTOR_STOP, 0, 0 }
/* 节拍以 ms=0 结尾,循环播放。同向连续步进只改占空比,换向才刹车。 */
static const AppModeStep_t s_mode1[] = { /* 弹力呼吸:慢加速放、慢加速收,最长 2s */
STEP_OUT(500, DUTY_MID), STEP_OUT(1200, DUTY_RUN), STEP_OUT(500, DUTY_PUNCH),
STEP_STOP(400),
STEP_IN(500, DUTY_MID), STEP_IN(1200, DUTY_RUN), STEP_IN(500, DUTY_PUNCH),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode2[] = { /* 飞鸟俯冲:快冲出去,2s 慢慢收回 */
STEP_OUT(500, DUTY_PUNCH), STEP_OUT(500, DUTY_RUN),
STEP_STOP(400),
STEP_IN(2800, DUTY_MID),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode3[] = { /* 云阶陀螺:短步往返加长,力度一档档加 */
STEP_OUT(800, DUTY_SOFT), STEP_IN(800, DUTY_SOFT),
STEP_OUT(800, DUTY_MID), STEP_IN(800, DUTY_MID),
STEP_OUT(800, DUTY_RUN), STEP_IN(800, DUTY_RUN),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode4[] = { /* 起伏攻防:2s 慢逗再猛收 */
STEP_OUT(500, DUTY_SOFT), STEP_OUT(1200, DUTY_MID), STEP_OUT(500, DUTY_RUN),
STEP_STOP(400),
STEP_IN(500, DUTY_PUNCH), STEP_IN(500, DUTY_MID),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode5[] = { /* 微风拂动:轻点后歇 2s */
STEP_OUT(500, DUTY_MID), STEP_STOP(1000),
STEP_IN(500, DUTY_MID), STEP_STOP(1200),
STEP_OUT(500, DUTY_RUN), STEP_STOP(800),
STEP_IN(500, DUTY_RUN), STEP_STOP(1500),
STEP_END
};
static const AppModeStep_t s_mode6[] = { /* 闪电快闪:两下轻、两下重,短停防 LVD */
STEP_OUT(400, DUTY_MID), STEP_IN(400, DUTY_MID),
STEP_OUT(800, DUTY_RUN), STEP_IN(800, DUTY_RUN),
STEP_OUT(500, DUTY_PUNCH), STEP_IN(500, DUTY_PUNCH),
STEP_OUT(700, DUTY_PUNCH), STEP_IN(700, DUTY_PUNCH),
STEP_STOP(300),
STEP_END
};
static const AppModeStep_t *const s_tables[7] = {
0, s_mode1, s_mode2, s_mode3, s_mode4, s_mode5, s_mode6
};
/** 模块状态,替代原来散落的 4 个 static */
typedef struct {
uint8_t initialized;
uint8_t active; /**< 0=停转,1~6=当前玩法 */
uint8_t step; /**< 当前节拍在表里的下标 */
u32 step_t0; /**< 当前节拍起始时间戳,用于判断是否该跳下一步 */
uint8_t active;
uint8_t output_valid;
uint8_t upper_phase;
uint8_t lower_pattern;
uint8_t lower_segment;
int8_t upper_direction;
int8_t lower_direction;
uint8_t upper_speed;
uint16_t upper_duration_ms;
uint16_t lower_duration_ms;
uint32_t state_started_ms;
uint32_t upper_started_ms;
uint32_t lower_started_ms;
int8_t output_upper_direction;
int8_t output_lower_direction;
uint8_t output_upper_speed;
uint8_t output_lower_speed;
} AppModeCtx_t;
static AppModeCtx_t s_mode;
extern u32 timer_get_ms(void);
/**
* @brief 输出当前节拍的方向与占空比;ms=0 表示表尾,回到第 0 步循环
* @return 无
*/
static void app_mode_apply_step(void)
static void app_mode_outputs_set(int8_t upper_direction, uint8_t upper_speed,
int8_t lower_direction, uint8_t lower_speed)
{
const AppModeStep_t *tbl = s_tables[s_mode.active];
if (!tbl) {
bsp_motor_set(BSP_MOTOR_STOP);
if (s_mode.output_valid &&
upper_direction == s_mode.output_upper_direction &&
upper_speed == s_mode.output_upper_speed &&
lower_direction == s_mode.output_lower_direction &&
lower_speed == s_mode.output_lower_speed) {
return;
}
if (tbl[s_mode.step].ms == 0) {
s_mode.step = 0; /* 表尾,回到第 0 步循环 */
}
s_mode.step_t0 = timer_get_ms();
if (tbl[s_mode.step].dir == BSP_MOTOR_STOP) {
bsp_motor_set(BSP_MOTOR_STOP);
} else {
bsp_motor_set_pwm(tbl[s_mode.step].dir, tbl[s_mode.step].duty);
bsp_drive_set((BspMotorDir_t)upper_direction, (uint16_t)upper_speed * 100,
(BspMotorDir_t)lower_direction, (uint16_t)lower_speed * 100);
s_mode.output_upper_direction = upper_direction;
s_mode.output_upper_speed = upper_speed;
s_mode.output_lower_direction = lower_direction;
s_mode.output_lower_speed = lower_speed;
s_mode.output_valid = 1;
}
static uint16_t app_mode4_random_step_ms(void)
{
static const uint16_t step_ms[] = {80, 100, 150, 200};
return step_ms[rand32() % ARRAY_SIZE(step_ms)];
}
static uint16_t app_mode5_random_forward_ms(void)
{
static const uint16_t forward_ms[] = {555, 780, 1140};
return forward_ms[rand32() % ARRAY_SIZE(forward_ms)];
}
static void app_mode5_select_lower_pattern(uint32_t now_ms)
{
s_mode.lower_pattern = rand32() % 4;
s_mode.lower_segment = 0;
s_mode.lower_started_ms = now_ms;
switch (s_mode.lower_pattern) {
case 0:
s_mode.lower_direction = BSP_MOTOR_FORWARD;
s_mode.lower_duration_ms = 2000;
break;
case 1:
s_mode.lower_direction = BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 2000;
break;
case 2:
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 100;
break;
default:
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 200;
break;
}
}
/**
* @brief 玩法初始化,电机停
* @return APP_MODE_OK=成功
*/
static void app_mode5_start_cycle(uint32_t now_ms)
{
s_mode.state_started_ms = now_ms;
s_mode.upper_phase = 0;
s_mode.upper_started_ms = now_ms;
s_mode.upper_duration_ms = app_mode5_random_forward_ms();
s_mode.upper_direction = BSP_MOTOR_FORWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
app_mode5_select_lower_pattern(now_ms);
}
static void app_mode_start_current(uint32_t now_ms)
{
bsp_drive_stop();
s_mode.output_valid = 0;
s_mode.state_started_ms = now_ms;
s_mode.upper_phase = 0;
s_mode.upper_started_ms = now_ms;
s_mode.lower_started_ms = now_ms;
if (s_mode.active == 4) {
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
} else if (s_mode.active == 5) {
app_mode5_start_cycle(now_ms);
}
}
static void app_mode1_process(uint32_t now_ms)
{
const uint16_t pair_ms = MAGIC_BOX_MODE1_FORWARD_MS +
MAGIC_BOX_MODE1_BACKWARD_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % pair_ms;
if (elapsed_ms < MAGIC_BOX_MODE1_FORWARD_MS) {
app_mode_outputs_set(BSP_MOTOR_FORWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
} else {
app_mode_outputs_set(BSP_MOTOR_BACKWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
}
}
static void app_mode2_process(uint32_t now_ms)
{
const uint16_t pair_ms = MAGIC_BOX_MODE2_FORWARD_MS +
MAGIC_BOX_MODE2_BACKWARD_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % pair_ms;
if (elapsed_ms < MAGIC_BOX_MODE2_FORWARD_MS) {
app_mode_outputs_set(BSP_MOTOR_FORWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
} else {
app_mode_outputs_set(BSP_MOTOR_BACKWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
}
}
static void app_mode3_process(uint32_t now_ms)
{
const uint16_t upper_move_ms = MAGIC_BOX_MODE3_FORWARD_MS +
MAGIC_BOX_MODE3_BACKWARD_MS;
const uint16_t cycle_ms = upper_move_ms + MAGIC_BOX_MODE3_STOP_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % cycle_ms;
uint8_t lower_segment = ((uint32_t)elapsed_ms *
MAGIC_BOX_MODE3_LOWER_SEGMENTS) / cycle_ms;
int8_t upper_direction;
uint8_t upper_speed;
int8_t lower_direction;
if (elapsed_ms < MAGIC_BOX_MODE3_FORWARD_MS) {
upper_direction = BSP_MOTOR_FORWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else if (elapsed_ms < upper_move_ms) {
upper_direction = BSP_MOTOR_BACKWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else {
upper_direction = BSP_MOTOR_STOP;
upper_speed = 0;
}
lower_direction = (lower_segment & 1) ?
BSP_MOTOR_BACKWARD : BSP_MOTOR_FORWARD;
app_mode_outputs_set(upper_direction, upper_speed,
lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
static void app_mode4_process(uint32_t now_ms)
{
const uint16_t upper_move_ms = MAGIC_BOX_MODE4_FORWARD_MS +
MAGIC_BOX_MODE4_BACKWARD_MS;
const uint16_t cycle_ms = upper_move_ms + MAGIC_BOX_MODE4_STOP_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms);
int8_t upper_direction;
uint8_t upper_speed;
if (elapsed_ms >= cycle_ms) {
s_mode.state_started_ms = now_ms;
elapsed_ms = 0;
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
}
if ((uint32_t)(now_ms - s_mode.lower_started_ms) >=
s_mode.lower_duration_ms) {
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
}
if (elapsed_ms < MAGIC_BOX_MODE4_FORWARD_MS) {
upper_direction = BSP_MOTOR_FORWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else if (elapsed_ms < upper_move_ms) {
upper_direction = BSP_MOTOR_BACKWARD;
upper_speed = MAGIC_BOX_MEDIUM_LOW_PERCENT;
} else {
upper_direction = BSP_MOTOR_STOP;
upper_speed = 0;
}
app_mode_outputs_set(upper_direction, upper_speed,
s_mode.lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
static void app_mode5_process(uint32_t now_ms)
{
if ((uint32_t)(now_ms - s_mode.state_started_ms) >=
MAGIC_BOX_MODE5_CYCLE_MS) {
app_mode5_start_cycle(now_ms);
}
if ((uint32_t)(now_ms - s_mode.upper_started_ms) >=
s_mode.upper_duration_ms) {
s_mode.upper_started_ms = now_ms;
s_mode.upper_phase++;
if (s_mode.upper_phase == 1) {
s_mode.upper_direction = BSP_MOTOR_BACKWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
s_mode.upper_duration_ms = MAGIC_BOX_MODE5_BACKWARD_MS;
} else if (s_mode.upper_phase == 2) {
s_mode.upper_direction = BSP_MOTOR_STOP;
s_mode.upper_speed = 0;
s_mode.upper_duration_ms = MAGIC_BOX_MODE5_UPPER_STOP_MS;
} else {
s_mode.upper_phase = 0;
s_mode.upper_direction = BSP_MOTOR_FORWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
s_mode.upper_duration_ms = app_mode5_random_forward_ms();
}
}
if ((uint32_t)(now_ms - s_mode.lower_started_ms) >=
s_mode.lower_duration_ms) {
if (s_mode.lower_pattern >= 2 && ++s_mode.lower_segment < 8) {
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
} else {
app_mode5_select_lower_pattern(now_ms);
}
}
app_mode_outputs_set(s_mode.upper_direction, s_mode.upper_speed,
s_mode.lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
AppMode_Ret_t app_mode_init(void)
{
s_mode.active = 0;
s_mode.step = 0;
s_mode.step_t0 = timer_get_ms();
memset(&s_mode, 0, sizeof(s_mode));
s_mode.initialized = 1;
bsp_motor_set(BSP_MOTOR_STOP);
bsp_drive_stop();
return APP_MODE_OK;
}
/**
* @brief 查询模块是否已初始化
* @return APP_MODE_OK=已初始化,APP_MODE_ERR_NOT_INIT=未初始化
*/
AppMode_Ret_t app_mode_get_status(void)
{
return s_mode.initialized ? APP_MODE_OK : APP_MODE_ERR_NOT_INIT;
}
/**
* @brief 读取当前自动模式
* @return 0=停转,1~6=对应玩法
*/
uint8_t app_mode_get_active(void)
{
return s_mode.active;
}
/**
* @brief 启动自动模式 1~6,按节拍表循环
* @param mode 玩法编号,范围 1~6
* @return APP_MODE_OK=成功,APP_MODE_ERR_NOT_INIT=未初始化,APP_MODE_ERR_PARAM=参数非法
*/
AppMode_Ret_t app_mode_start(uint8_t mode)
{
if (!s_mode.initialized) {
return APP_MODE_ERR_NOT_INIT;
}
if (mode < 1 || mode > 6) {
if (mode < 1 || mode > 5) {
app_mode_stop();
return APP_MODE_ERR_PARAM;
}
s_mode.active = mode;
s_mode.step = 0; /* 从节拍表第 0 步重新开始 */
app_mode_apply_step();
log_info("auto mode %d start\n", mode);
app_mode_start_current(timer_get_ms());
log_info("magic box mode %d start\n", mode);
return APP_MODE_OK;
}
/**
* @brief 停止当前玩法并刹车
* @return 无
*/
void app_mode_stop(void)
{
if (s_mode.active) {
log_info("auto mode %d stop\n", s_mode.active);
}
s_mode.active = 0;
s_mode.step = 0;
s_mode.step_t0 = timer_get_ms();
bsp_motor_set(BSP_MOTOR_STOP);
s_mode.output_valid = 0;
bsp_drive_stop();
}
/**
* @brief 推进当前节拍(按 timer_get_ms 真实时间)
* @return 无
*/
void app_mode_run(void)
{
const AppModeStep_t *tbl;
uint32_t now_ms;
bsp_motor_switch_tick(); /* 换向死区:H 桥松开后再切方向 */
if (!s_mode.initialized || s_mode.active == 0) {
bsp_motor_switch_tick();
if (!s_mode.initialized || !s_mode.active) {
return;
}
tbl = s_tables[s_mode.active];
if (!tbl) {
return;
}
if ((timer_get_ms() - s_mode.step_t0) >= tbl[s_mode.step].ms) {
s_mode.step++;
app_mode_apply_step();
now_ms = timer_get_ms();
switch (s_mode.active) {
case 1:
app_mode1_process(now_ms);
break;
case 2:
app_mode2_process(now_ms);
break;
case 3:
app_mode3_process(now_ms);
break;
case 4:
app_mode4_process(now_ms);
break;
case 5:
app_mode5_process(now_ms);
break;
default:
app_mode_stop();
break;
}
}