Motor-control state machine
Core/Src/main.cpp enum class RideState : uint8_t
{
Stop = 0u,
RideForward,
RideBackward,
RideForwardRamp,
RideBackwardRamp
};
switch(motor_state.motor)
{
case RideState::RideForwardRamp:
StartForwardRamp(now_ms);
break;
case RideState::RideForward:
HandleForwardState(now_ms);
break;
case RideState::RideBackwardRamp:
StartBackwardRamp(now_ms);
break;
case RideState::RideBackward:
HandleBackwardState(now_ms);
break;
case RideState::Stop:
HandleStopState(now_ms);
break;
}
This snippet captures the main control idea: driving is not handled by random if statements, but through explicit states. Forward motion starts through RideForwardRamp, where the PWM ramp is started. The firmware then moves into RideForward and updates power through MotorSpeed().
Non-blocking PWM ramp
Core/Src/main.cpp void RampStart(PercentRamp& ramp, uint8_t from, uint8_t to, uint32_t duration_ms)
{
ramp.from = from;
ramp.value = from;
ramp.target = to;
ramp.start_ms = HAL_GetTick();
ramp.duration_ms = duration_ms;
ramp.active = true;
}
uint8_t RampUpdate(PercentRamp& ramp)
{
uint32_t elapsed = HAL_GetTick() - ramp.start_ms;
if (elapsed >= ramp.duration_ms)
{
ramp.value = ramp.target;
ramp.active = false;
return ramp.value;
}
ramp.value = ramp.from + (elapsed * (ramp.target - ramp.from)) / ramp.duration_ms;
return ramp.value;
}
The ramp calculates the new value from the system tick, so it does not stop the main loop with a long HAL_Delay(). This matters because the controller can still react to user inputs.
Mapping percent to PWM with start boost
Core/Src/MotorDriver.cpp void MotorPwm::MotorSpeed(uint8_t percent)
{
if (timer_ == nullptr)
{
return;
}
if (percent > kPercentMax)
{
percent = kPercentMax;
}
const uint32_t now_ms = HAL_GetTick();
const uint32_t period_plus_one = __HAL_TIM_GET_AUTORELOAD(timer_) + 1u;
// 1) Martwa strefa: STOP i reset
if (percent < input_min_)
{
blast_active = 0u;
prev_speed_percent = percent;
__HAL_TIM_SET_COMPARE(timer_, channel_, 0u);
return;
}
// 2) Kierunek zmiany (żeby anulować blast przy schodzeniu)
const bool increasing = (percent > prev_speed_percent);
// 3) Start blasta TYLKO na krawędzi startu (<input_min_ -> >= input_min_)
const bool start_edge = (prev_speed_percent < input_min_) && (percent >= input_min_);
if (start_edge)
{
blast_active = 1u;
blast_end_ms = now_ms + blast_time_;
}
// 4) Jeśli użytkownik schodzi w dół – nie trzymaj kopa
if (blast_active && !increasing)
{
blast_active = 0u;
}
// 5) Jeśli czas blasta minął – wyłącz
if (blast_active && (int32_t)(now_ms - blast_end_ms) >= 0)
{
blast_active = 0u;
}
// 6) Mapowanie percent -> duty (tu liniowo dla lepszego dołu)
float norm = (float)(percent - input_min_) / (100.0f - (float)input_min_);
if (norm < 0.0f) norm = 0.0f;
if (norm > 1.0f) norm = 1.0f;
float shaped = norm; // lepsza kontrola 0..niski zakres
// float shaped = norm * norm; // jeśli chcesz spłaszczyć dół
float duty_f = (float)duty_min_ + shaped * (float)(duty_max_ - duty_min_);
if (duty_f < 0.0f) duty_f = 0.0f;
if (duty_f > 100.0f) duty_f = 100.0f;
// 7) Blast jako MINIMUM przez blast_time_ (nie blokuje sterowania na stałe)
if (blast_active)
{
if (duty_f < (float)blast_duty_)
duty_f = (float)blast_duty_;
}
// 8) Ustaw CCR z rozdzielczością (bez schodków 1%)
uint32_t ccr = (uint32_t)(duty_f * (float)period_plus_one / 100.0f + 0.5f);
if (ccr > period_plus_one) ccr = period_plus_one;
__HAL_TIM_SET_COMPARE(timer_, channel_, ccr);
prev_speed_percent = percent;
}
This is the most practical part of the driver. The code separates requested percent from real PWM duty. On startup it applies a short stronger boost so the motor can physically start, then falls back to normal mapping.