diff --git a/iar/plsr.dep b/iar/plsr.dep
index da69f5f..d833f96 100644
--- a/iar/plsr.dep
+++ b/iar/plsr.dep
@@ -1509,7 +1509,7 @@
ICCARM
- 299 159 195 448 426 360 212 282 289 11 9 223 12 4 2 10 3 16 0 142 17 6 7 8 184 149 221 230 231 232 13 227 1 233 229 228 226 222 224 225 219 374 392 377 70 173 387 401 368 347 39 216
+ 299 159 195 448 426 360 212 282 386 289 11 9 223 12 4 2 10 3 16 0 142 17 6 7 8 184 149 221 230 231 232 13 227 1 233 229 228 226 222 224 225 219 374 392 377 70 173 387 401 368 347 39 216
@@ -1682,7 +1682,7 @@
ICCARM
- 289 11 9 223 12 4 2 10 159 195 448 426 360 212 3 16 0 142 17 6 7 8 184 149 221 230 231 232 13 227 1 233 229 228 226 222 224 225 219 374 392 377 70 173 387 401 368 347 39 216 381 299 399 5 15 386
+ 289 11 9 223 12 4 2 10 159 195 448 426 360 212 3 16 0 142 17 6 7 8 184 149 221 230 231 232 13 227 1 233 229 228 226 222 224 225 219 374 392 377 70 173 387 401 368 347 39 216 381 299 399 5 15 386 282
diff --git a/plsr/accel_curve/plsr_accel_curve.c b/plsr/accel_curve/plsr_accel_curve.c
index ef8a7b9..7af7227 100644
--- a/plsr/accel_curve/plsr_accel_curve.c
+++ b/plsr/accel_curve/plsr_accel_curve.c
@@ -488,12 +488,13 @@ static uint32_t PlsrAccelCurveFitPeak(uint32_t total_pulses,
if (f_want >= f_cur)
{
peak = f_cur + mid;
+ n_a = PlsrAccelCurvePulsesForRamp(f_cur, peak, a_acc);
}
else
{
peak = f_cur - mid;
+ n_a = PlsrAccelCurvePulsesForRamp(f_cur, peak, a_dec);
}
- n_a = PlsrAccelCurvePulsesForRamp(f_cur, peak, a_acc);
n_d = PlsrAccelCurvePulsesForRamp(peak, f_end, a_dec);
if ((n_a + n_d) <= total_pulses)
@@ -602,7 +603,15 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
plan->a_acc = a_acc;
plan->a_dec = a_dec;
- acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_acc);
+ if (f_cur > f_peak)
+ {
+ /* 入口高于峰值:首相为减速,用 a_dec(与 rampTimeMs 减速分支一致) */
+ acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_dec);
+ }
+ else
+ {
+ acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_acc);
+ }
dec_n = PlsrAccelCurvePulsesForRamp(f_peak, f_end, a_dec);
if (total_pulses == 0U)
@@ -627,7 +636,14 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
{
f_peak = PlsrAccelCurveFitPeak(total_pulses, f_cur, f_tgt, f_end,
a_acc, a_dec);
- acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_acc);
+ if (f_cur > f_peak)
+ {
+ acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_dec);
+ }
+ else
+ {
+ acc_n = PlsrAccelCurvePulsesForRamp(f_cur, f_peak, a_acc);
+ }
dec_n = PlsrAccelCurvePulsesForRamp(f_peak, f_end, a_dec);
if (acc_n > total_pulses)
{
@@ -701,7 +717,9 @@ uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan,
rising = (plan->f_tgt >= plan->f_cur) ? 1U : 0U;
if (plan->mode == PLSR_ACCEL_LINEAR)
{
- f = PlsrAccelCurveFreqKinematic(plan->f_cur, plan->a_acc,
+ uint32_t a_use = (rising != 0U) ? plan->a_acc : plan->a_dec;
+
+ f = PlsrAccelCurveFreqKinematic(plan->f_cur, a_use,
pulse_done, rising, plan->f_tgt);
}
else
@@ -785,10 +803,10 @@ void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt,
rt->f0 = plan->f_cur;
rt->f1 = plan->f_tgt;
rt->f = plan->f_cur;
- rt->a = plan->a_acc;
rt->n = 0U;
rt->n_total = plan->acc_n;
rt->rising = (plan->f_tgt >= plan->f_cur) ? 1U : 0U;
+ rt->a = (rt->rising != 0U) ? plan->a_acc : plan->a_dec;
rt->mode = plan->mode;
rt->active = (plan->acc_n > 0U) && (plan->f_cur != plan->f_tgt) ? 1U : 0U;
PlsrAccelPulseRtSetupShape(rt, rt->n_total);
diff --git a/plsr/accel_curve/plsr_accel_curve.h b/plsr/accel_curve/plsr_accel_curve.h
index 718f231..942c335 100644
--- a/plsr/accel_curve/plsr_accel_curve.h
+++ b/plsr/accel_curve/plsr_accel_curve.h
@@ -32,7 +32,7 @@ typedef struct {
PlsrAccelMode_e mode; /* 直线 / S / 正弦 */
} PlsrAccelPlan_t;
-/** 配置起速 → 规划用真实起点(Jump;起速 0 也不从 0Hz 开跑) */
+/** 配置起速 → 规划用真实起点(0=Jump 起跳;非 0 且 >f_tgt 则直接用起速走减速) */
uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg,
uint32_t f_tgt,
uint32_t default_spd,
diff --git a/plsr/run_control/plsr_run_control.c b/plsr/run_control/plsr_run_control.c
index d97bde7..f9f9a66 100644
--- a/plsr/run_control/plsr_run_control.c
+++ b/plsr/run_control/plsr_run_control.c
@@ -136,6 +136,10 @@ static uint32_t s_plan_acc_n; /* PlanSeg 锁定加速脉冲预算
static uint32_t s_plan_dec_n; /* PlanSeg 锁定减速脉冲预算 */
static uint32_t s_decel_budget; /* EnterDecel 时锁定的减速步进预算 */
+/** 本次 Start 的启动段(0-based);仅该段首次冷启动可套用「起速>目标」入口规则 */
+static uint16_t s_run_entry_seg0;
+static uint8_t s_run_first_entry;
+
/**
* 策略层解析后交给 PlanSeg 的段端点(不含 wait_type,仅频率与时间)
*/
@@ -667,11 +671,29 @@ static uint8_t PlsrRunControlResolveSegPlanEndpoints(uint16_t seg_idx0, uint32_t
}
else
{
- endpoints->freq_start_hz = PlsrAccelCurveResolveStartHz(cfg->start_speed,
- f_tgt,
- cfg->default_speed,
- endpoints->accel_ms,
- endpoints->decel_ms);
+ /*
+ * 停表后冷启动:仅本次运行的启动段、且目标频低于配置起速时,
+ * 入口直接用起速(再规划减速到目标);其余仍走起跳频率。
+ */
+ if ((s_run_first_entry != 0U) &&
+ (seg_idx0 == s_run_entry_seg0) &&
+ (cfg->start_speed >= 1U) && (f_tgt < cfg->start_speed))
+ {
+ s_run_first_entry = 0U;
+ endpoints->freq_start_hz = PlsrRunControlClampSpeed(cfg->start_speed);
+ }
+ else
+ {
+ if ((s_run_first_entry != 0U) && (seg_idx0 == s_run_entry_seg0))
+ {
+ s_run_first_entry = 0U;
+ }
+ endpoints->freq_start_hz = PlsrAccelCurveResolveStartHz(cfg->start_speed,
+ f_tgt,
+ cfg->default_speed,
+ endpoints->accel_ms,
+ endpoints->decel_ms);
+ }
}
/*
@@ -1261,6 +1283,7 @@ static void PlsrRunControlFinishAll(void)
s_wait_expire_req = 0U;
s_act_time_ms = 0U;
s_last_period_wait = 0U;
+ s_run_first_entry = 0U;
}
/**
@@ -1554,6 +1577,8 @@ uint8_t PlsrRunControlStart(uint16_t start_seg_1based)
s_follow_cont = 0U;
s_chain_valid = 0U;
s_chain_freq = 0U;
+ s_run_entry_seg0 = (uint16_t)(start_seg_1based - 1U);
+ s_run_first_entry = 1U;
s_cur_freq = PlsrRunControlClampSpeed(cfg->start_speed);
/*
* 绝对原点:仅在首次 Start(或 ClearAccPulse 之后的第一次)锁定。
@@ -1592,6 +1617,7 @@ void PlsrRunControlStop(void)
s_wait_expire_req = 0U;/*段后等待到期请求标志*/
s_act_time_ms = 0U;/*本段 ACT 定时时长*/
s_last_period_wait = 0U;
+ s_run_first_entry = 0U;
}
/** @brief 运行中改频(见 plsr_run_control.h) */
diff --git a/plsr/signal_io/plsr_signal_io.c b/plsr/signal_io/plsr_signal_io.c
index 3dc8e3c..a383252 100644
--- a/plsr/signal_io/plsr_signal_io.c
+++ b/plsr/signal_io/plsr_signal_io.c
@@ -2,11 +2,29 @@
* @file plsr_signal_io.c
* @brief WAIT/EXT 输入 + TIM5 单次延时 + OS 节拍消抖
*
- * @details
- * EXTI 下降沿 → pending;DebouncePoll 满约 20ms 且仍低 → fell。
- * TIM5(32 位,APB1 84MHz):有延时才开表。
- * PSC=8399 → 10kHz(1 次计数 = 100µs)
- * ARR = delay_ms×10 − 1 → 一次溢出 = 设定的 ms(分辨率 0.1ms,够用)
+ * @details 实现要点
+ * 【EXTI 输入】
+ * X4=PB5 → EXTI9_5;X5=PG12 → EXTI15_10。
+ * 下降沿触发 OnFalling:仅 pending=1,不在 ISR 里判有效(避免抖动/毛刺)。
+ *
+ * 【消抖】
+ * DebouncePoll(任务上下文,约 1ms 一次):
+ * pending 且首次 → 记 arm_tick;
+ * 满 PLSR_SIGNAL_DEBOUNCE_MS(20ms)且 PinIsLow → fell=1。
+ * 上升沿恢复高电平则 Confirm 末尾不置 fell(pending 被清掉)。
+ *
+ * 【TIM5 单次延时】
+ * 32 位 TIM5,APB1 84MHz;仅在有延时时开表(URS+OPM)。
+ * PSC=8399 → 10kHz(1 计数 = 100µs);ARR = delay_ms×10 − 1。
+ * 一次 UPDATE 溢出 = delay_ms;中断内 Cancel 并 PlsrRunControlOnDelayTimer()。
+ *
+ * 【与 run_control 配合】
+ * ScheduleDelayMs :方向延时 / WAIT 时间 / ACT 定时
+ * TakeExtFalling :RUN 中 EXT 切段、WAIT_COND 等
+ * TakeWaitFalling :WAIT 信号段后等待
+ * ClearEdges :EXT 段开跑前丢旧沿
+ * ClearPending :纯 EXT 段末保留 fell、清 pending
+ * DelayRemainMs :ACT 脉冲先完后续装剩余时间
*/
#include "plsr_signal_io.h"
#include "plsr_param.h"
@@ -14,34 +32,38 @@
#include "main.h"
#include "ucos_ii.h"
+/* ---------- 硬件引脚(与 CubeMX / 方案书一致) ---------- */
#define PLSR_X4_PORT GPIOB
-#define PLSR_X4_PIN GPIO_PIN_5
+#define PLSR_X4_PIN GPIO_PIN_5 /* X4:WAIT/EXT 可选 */
#define PLSR_X5_PORT GPIOG
-#define PLSR_X5_PIN GPIO_PIN_12
+#define PLSR_X5_PIN GPIO_PIN_12 /* X5:WAIT/EXT 可选 */
/*
- * 84MHz / 8400 = 10kHz → 100µs/格。
- * PSC=8399 落在 16 位内;不必用 1µs(那只是为了硬凑,ms 延时用不上)。
+ * TIM5 时基:84MHz / (8399+1) = 10kHz → 100µs/格。
+ * PSC=8399 落在 16 位内;ms 级延时用 100µs 分辨率足够,不必 1µs 时基。
*/
-#define PLSR_TIM5_PSC_100US 8399U
-#define PLSR_TIM5_TICKS_PER_MS 10U /* 10×100µs = 1ms */
-#define PLSR_DELAY_MS_MAX 65535U
+#define PLSR_TIM5_PSC_100US 8399U
+#define PLSR_TIM5_TICKS_PER_MS 10U /* 10×100µs = 1ms */
+#define PLSR_DELAY_MS_MAX 65535U
+/** OS 节拍消抖窗口(ms),与 PlsrTask tick 配合 */
#define PLSR_SIGNAL_DEBOUNCE_MS 20U
+/** 单路 X 口消抖状态(pending / fell 由 ISR 与任务共享,volatile) */
typedef struct {
- volatile uint8_t pending;
- volatile uint8_t stamp_ok;
- volatile INT32U arm_tick;
- volatile uint8_t fell;
+ volatile uint8_t pending; /* 1=EXTI 已见下降沿,待 Confirm */
+ volatile uint8_t stamp_ok; /* 1=已记 arm_tick,正在计时 */
+ volatile INT32U arm_tick; /* 消抖起点 OS tick */
+ volatile uint8_t fell; /* 1=消抖确认的有效下降沿,Take* 消费 */
GPIO_TypeDef *port;
uint16_t pin;
} PlsrEdge_t;
-static PlsrEdge_t s_db_x4; /* X4 输入消抖状态(WAIT/EXT 共用) */
-static PlsrEdge_t s_db_x5; /* X5 输入消抖状态 */
-static volatile uint8_t s_delay_armed; /* 1=TIM5 单次延时正在计时 */
+static PlsrEdge_t s_db_x4; /* X4 消抖状态 */
+static PlsrEdge_t s_db_x5; /* X5 消抖状态 */
+static volatile uint8_t s_delay_armed; /* 1=TIM5 单次延时正在计时 */
+/** 毫秒 → OS tick 数(向上取整,至少 1 tick) */
static INT32U PlsrSignalIoMsToTicks(uint32_t ms)
{
INT32U t;
@@ -58,17 +80,23 @@ static INT32U PlsrSignalIoMsToTicks(uint32_t ms)
return t;
}
+/** @return 1=脚当前为低(有效按下/接地) */
static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *db)
{
return (HAL_GPIO_ReadPin(db->port, db->pin) == GPIO_PIN_RESET) ? 1U : 0U;
}
+/** EXTI 下降沿:仅置 pending,消抖在 DebouncePoll 完成 */
static void PlsrSignalIoOnFalling(PlsrEdge_t *db)
{
db->pending = 1U;
db->stamp_ok = 0U;
}
+/**
+ * @brief 单路消抖确认(DebouncePoll 每 tick 调用)
+ * @note 满 debounce_ms 仍低 → fell;否则丢弃 pending
+ */
static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db)
{
INT32U need;
@@ -99,6 +127,7 @@ static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db)
}
}
+/** X4/X5:下降沿 EXTI + 上拉;先关 NVIC 再配 GPIO,避免配置期间误触发 */
static void PlsrSignalIoGpioExtiInit(void)
{
GPIO_InitTypeDef gpio = {0};
@@ -130,6 +159,7 @@ static void PlsrSignalIoGpioExtiInit(void)
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
+/** TIM5:预配 PSC/最大 ARR,默认停表;ScheduleDelayMs 时再装 ARR 并 OPM 启动 */
static void PlsrSignalIoTim5Init(void)
{
__HAL_RCC_TIM5_CLK_ENABLE();
@@ -189,7 +219,7 @@ void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms)
delay_ms = PLSR_DELAY_MS_MAX;
}
- /* N ms → N×10 个 100µs 格 */
+ /* N ms → N×10 个 100µs 格;OPM 计满一次 UPDATE 即到期 */
ticks = delay_ms * PLSR_TIM5_TICKS_PER_MS;
TIM5->CR1 = 0U;
@@ -263,6 +293,7 @@ void PlsrSignalIoClearPending(void)
s_db_x5.stamp_ok = 0U;
}
+/** wait_x_sel / ext_x_sel:0→X4,非 0→X5 */
static PlsrEdge_t *PlsrSignalIoDbBySel(uint16_t sel_01)
{
return (sel_01 != 0U) ? &s_db_x5 : &s_db_x4;
diff --git a/plsr/signal_io/plsr_signal_io.h b/plsr/signal_io/plsr_signal_io.h
index fc2d228..9808dd8 100644
--- a/plsr/signal_io/plsr_signal_io.h
+++ b/plsr/signal_io/plsr_signal_io.h
@@ -3,48 +3,105 @@
* @brief WAIT/EXT 外部输入与 TIM5 单次延时
*
* @details 模块职责
- * 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(上拉空闲高,飞线接地出沿);
- * 2. 消抖:EXTI 置 pending,任务侧按 OS 节拍确认 20ms 仍低 → fell;
- * 3. TIM5(32 位)仅在有延时时开启:100µs 时基,ARR=ms×10−1,
- * 一次计满 = 设定 ms(最长 65535ms),到期 UPDATE → OnDelayTimer。
+ * 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(内部上拉,空闲高;飞线/触点接地出有效沿);
+ * 2. 消抖:EXTI 仅置 pending,任务侧 DebouncePoll 按 OS 节拍确认约 20ms 仍低 → fell;
+ * 3. TIM5(32 位)按需单次计时:100µs 时基,ARR=ms×10−1,一次 UPDATE = 设定 ms
+ * (最长 65535ms);到期回调 run_control::OnDelayTimer。
+ *
+ * 硬件与参数映射
+ * X4 (PB5, EXTI9_5) / X5 (PG12, EXTI15_10)
+ * wait_x_sel (0x1002) → 0=X4,1=X5,供 WAIT 信号等待(PLSR_WAIT_SIGNAL)
+ * ext_x_sel (0x1003) → 0=X4,1=X5,供 EXT 切段(PLSR_WAIT_EXT / EXT_OR_DONE)
+ * WAIT 与 EXT 可独立选口,共用同一套消抖状态机。
+ *
+ * 消抖状态机(每路 PlsrEdge_t)
+ * pending=1 ← EXTI 下降沿
+ * DebouncePoll:首次见 pending 记 arm_tick;满 20ms 且脚仍低 → fell=1
+ * Take*Falling 消费 fell;ClearPending 只清 pending/stamp,保留已确认 fell
+ *
+ * TIM5 单次延时用途(run_control 调用)
+ * - 方向切换延时 dir_delay_ms(RC_DIR_WAIT → Start)
+ * - WAIT 时间 PLSR_WAIT_TIME 段后等待
+ * - ACT 时间 PLSR_WAIT_ACT 段内定时(到期 OPM 收当前拍再切段)
+ * - ACT 脉冲先完但定时未到:DelayRemainMs 续装剩余 ms
*
* 与其它模块关系
* - plsr_param :wait_x_sel / ext_x_sel
- * - plsr_run_control :TakeWait/ExtFalling、ScheduleDelayMs、OnDelayTimer
- * - plsr.c :周期调用 DebouncePoll
- * - stm32f4xx_it :TIM5 / EXTI 转发
+ * - plsr_run_control :TakeWait/ExtFalling、Schedule/CancelDelay、OnDelayTimer
+ * - plsr.c :PlsrTask 每 OS 节拍 DebouncePoll
+ * - stm32f4xx_it :TIM5 / EXTI9_5 / EXTI15_10 转发至本模块 IRQ 入口
+ *
+ * 线程/中断约定
+ * fell/pending 由 ISR 写、任务读;Take* 仅在任务上下文消费。
+ * ScheduleDelayMs / CancelDelay 由任务调用;OnTim5Irq 在 TIM5 中断内回调 run_control。
*/
#ifndef PLSR_SIGNAL_IO_H
#define PLSR_SIGNAL_IO_H
#include
+/**
+ * @brief 初始化 X4/X5 EXTI 消抖状态与 TIM5(不启动延时)
+ * @note 调用时机:PlsrInit,在 RunControl 之前
+ * @note 副作用:清 pending/fell;TIM5 停表、OPM 单次模式预配置
+ */
void PlsrSignalIoInit(void);
/**
- * @brief 武装 TIM5 单次延时(ms),到期进 OnDelayTimer
+ * @brief 武装 TIM5 单次延时(ms),到期 OnTim5Irq → PlsrRunControlOnDelayTimer
* @param delay_ms 0 按 1ms;上限 65535ms
+ * @note 重入会先 Cancel 再重装;OPM 计满自动停表
*/
void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms);
+/** @brief 停止 TIM5 单次延时,s_delay_armed=0 */
void PlsrSignalIoCancelDelay(void);
/**
* @brief 读 TIM5 单次延时剩余 ms(未武装或已停表返回 0;向下取整)
+ * @note ACT 脉冲先完、定时仍走时,用于续装剩余 ACT 时间
*/
uint32_t PlsrSignalIoDelayRemainMs(void);
+/**
+ * @brief OS 节拍消抖轮询(X4/X5 各跑一遍 Confirm)
+ * @note 由 PlsrTask 每 tick 调用;满 PLSR_SIGNAL_DEBOUNCE_MS 且仍低才置 fell
+ */
void PlsrSignalIoDebouncePoll(void);
+/**
+ * @brief 清 X4/X5 的 pending 与 fell(开跑 EXT 段、FinishAll 时用)
+ * @note 丢弃尚未确认的沿与已锁存沿,防误切段
+ */
void PlsrSignalIoClearEdges(void);
+
+/**
+ * @brief 只清 pending/stamp,保留已确认的 fell
+ * @note 纯 EXT 段末:清消抖中的假沿,已确认沿留给 TakeExtFalling
+ */
void PlsrSignalIoClearPending(void);
+
+/**
+ * @brief 消费 WAIT 口(wait_x_sel)上已确认的下降沿
+ * @return 1=本次取到有效沿并已清零 fell;0=无沿
+ * @note 仅认消抖后的 fell,不读当前 GPIO 电平(防虚低误跳)
+ */
uint8_t PlsrSignalIoTakeWaitFalling(void);
+
+/**
+ * @brief 消费 EXT 口(ext_x_sel)上已确认的下降沿
+ * @return 1=本次取到有效沿并已清零 fell;0=无沿
+ * @note RUN 中 EXT/EXT_OR_DONE 切段、WAIT_COND 等路径共用
+ */
uint8_t PlsrSignalIoTakeExtFalling(void);
-/** @brief TIM5 UPDATE:单次延时到期 */
+/** @brief TIM5 UPDATE 中断入口:单次延时到期 → run_control */
void PlsrSignalIoOnTim5Irq(void);
+/** @brief EXTI9_5 中断入口(X4 / PB5) */
void PlsrSignalIoOnExti9_5(void);
+
+/** @brief EXTI15_10 中断入口(X5 / PG12) */
void PlsrSignalIoOnExti15_10(void);
#endif