Maszyna stanów sterowania silnikiem
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;
}
Ten fragment opisuje główną ideę sterowania: jazda nie jest prowadzona przez luźne if-y, tylko przez jawne stany. Przykładowo start jazdy do przodu przechodzi przez rideForwardRamp, gdzie uruchamiana jest rampa PWM. Dopiero po osiągnięciu zamierzonej prędkości firmware przechodzi do rideForward i aktualizuje moc przez MotorSpeed().
Rampa PWM bez blokowania programu
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;
}
Rampa liczy nową wartość na podstawie czasu systemowego, więc nie zatrzymuje pętli głównej długim HAL_Delay(). To jest ważne, bo mikrokontroler może dalej reagować na wejścia użytkownika.
Mapowanie procentu na PWM z impulsem startowym
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;
}
To jest najbardziej praktyczna część sterownika. Kod rozróżnia żądany procent od realnego wypełnienia PWM. Przy starcie daje krótki mocniejszy impuls, aby silnik fizycznie ruszył, a potem wraca do normalnego mapowania.