Skip to content

File MotorController.cpp

File List > demo-projets > stm32 > src > MotorController.cpp

Go to the documentation of this file.

#include "MotorController.hpp"
#include "Bsp.hpp"
#include "ControlLink.hpp"
#include "Dispatcher.hpp"
#include "Motor.hpp"
#include "utils/Debug.hpp"
#include "utils/MutexWrapper.hpp"
#include "utils/TaskWrapper.hpp"

#include "stm32f1xx_ll_tim.h"
#include <cstdlib>
#include <mutex>
#include <stdint.h>

static void setPwmValue(TIM_TypeDef* timer, uint8_t motorIndex, uint16_t value);
static void setMotorPower(uint8_t motorIndex, int32_t power, bool brake);

// Debounce ENC signals at ~3.5us (72MHz fDTS)
static constexpr uint32_t encoderFilter = LL_TIM_IC_FILTER_FDIV32_N8;
static constexpr uint16_t maxPwm = 2000;
static std::array<Motor, 4> motor;
static MutexWrapper motorMut;
static TaskWrapper<1024> motorTask;
static void taskFunc();

void motorInit() {
    LL_TIM_InitTypeDef pwmInit;
    LL_TIM_StructInit(&pwmInit);

    pwmInit.Prescaler = 0;
    // this sets interrupts flag when counter reachs TOP:
    pwmInit.CounterMode = LL_TIM_COUNTERMODE_CENTER_DOWN;
    pwmInit.Autoreload = maxPwm;
    pwmInit.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
    pwmInit.RepetitionCounter = 0;

    LL_TIM_OC_InitTypeDef ocInit;
    LL_TIM_OC_StructInit(&ocInit);
    ocInit.OCMode = LL_TIM_OCMODE_PWM2;
    ocInit.OCState = LL_TIM_OCSTATE_ENABLE;

    ocInit.OCNState = LL_TIM_OCSTATE_ENABLE;
    ocInit.CompareValue = 0;
    ocInit.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
    ocInit.OCNPolarity = LL_TIM_OCPOLARITY_HIGH;
    ocInit.OCIdleState = LL_TIM_OCIDLESTATE_HIGH;
    ocInit.OCNIdleState = LL_TIM_OCIDLESTATE_HIGH;

    LL_TIM_Init(pwmTimer, &pwmInit);
    for (uint16_t channel = LL_TIM_CHANNEL_CH1; channel != 0; channel <<= 4) {
        LL_TIM_OC_Init(pwmTimer, channel, &ocInit);
        LL_TIM_OC_EnablePreload(pwmTimer, channel);
    }

    LL_TIM_SetOffStates(pwmTimer, LL_TIM_OSSI_DISABLE, LL_TIM_OSSR_ENABLE);
    LL_TIM_GenerateEvent_UPDATE(pwmTimer);
    LL_TIM_EnableAllOutputs(pwmTimer);
    LL_TIM_EnableCounter(pwmTimer);

    for (int motorIndex : { 0, 1, 2, 3 }) {
        setMotorPower(motorIndex, 0, false);
    }

    LL_TIM_ENCODER_InitTypeDef encInit;
    encInit.EncoderMode = LL_TIM_ENCODERMODE_X4_TI12;
    encInit.IC1Polarity = LL_TIM_IC_POLARITY_RISING;
    encInit.IC1ActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI;
    encInit.IC1Prescaler = LL_TIM_ICPSC_DIV1;
    encInit.IC1Filter = encoderFilter;
    encInit.IC2Polarity = LL_TIM_IC_POLARITY_RISING;
    encInit.IC2ActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI;
    encInit.IC2Prescaler = LL_TIM_ICPSC_DIV1;
    encInit.IC2Filter = encoderFilter;
    for (auto timer : encoderTimer) {
        LL_TIM_ENCODER_Init(timer, &encInit);
        LL_TIM_EnableCounter(timer);
    }

    motorMut.create();
    motorTask.start("motors", motorTaskPrio, taskFunc);
}

static void taskFunc() {
    while (true) {
        auto now = xTaskGetTickCount();
        {
            std::scoped_lock lock(motorMut);

            for (int m : { 0, 1, 2, 3 }) {
                uint16_t encTicks = LL_TIM_GetCounter(encoderTimer[m]);
                auto& targetMotor = motor[m];
                auto modeBefore = targetMotor.mode();
                auto action = targetMotor.poll(encTicks);
                auto modeAfter = targetMotor.mode();
                setMotorPower(m, action, modeAfter == MotorMode_BRAKE);

                if (modeBefore == MotorMode_POSITION
                    && modeAfter == MotorMode_POSITION_IDLE) {
                    CoprocStat stat = {
                        .which_payload = CoprocStat_motorStat_tag,
                    };
                    targetMotor.reportStat(stat.payload.motorStat);
                    stat.payload.motorStat.motorIndex = m;
                    dispatcherEnqueueStatus(stat);
                }
            }
        }
        vTaskDelayUntil(&now, pdMS_TO_TICKS(1000 / motorLoopFreq));
    }
}

void motorDispatch(const CoprocReq_MotorReq& request) {
    if (request.motorIndex > 3) {
        return;
    }

    auto& targetMotor = motor[request.motorIndex];
    std::scoped_lock lock(motorMut);

    switch (request.which_motorCmd) {
    case CoprocReq_MotorReq_getState_tag: {
        CoprocStat stat = {
            .which_payload = CoprocStat_motorStat_tag,
        };
        targetMotor.reportStat(stat.payload.motorStat);
        stat.payload.motorStat.motorIndex = request.motorIndex;
        controlLinkTx(stat);
    } break;
    case CoprocReq_MotorReq_setPower_tag:
        targetMotor.setTargetPower(request.motorCmd.setPower);
        break;
    case CoprocReq_MotorReq_setBrake_tag:
        targetMotor.setTargetBrakingPower(request.motorCmd.setBrake);
        break;
    case CoprocReq_MotorReq_setVelocity_tag: {
        auto ticksPerSec = request.motorCmd.setVelocity;
        if (ticksPerSec > INT16_MAX || ticksPerSec < INT16_MIN) {
            DEBUG("Motor %d target velocity out of range <-32768; 32767> "
                  "(%d).\n",
                int(request.motorIndex), int(ticksPerSec));
            return;
        }
        targetMotor.setTargetVelocity(ticksPerSec);
    } break;
    case CoprocReq_MotorReq_homePosition_tag:
        targetMotor.homePosition(request.motorCmd.homePosition);
        break;
    case CoprocReq_MotorReq_setPosition_tag:
        targetMotor.setTargetPosition(request.motorCmd.setPosition, false);
        break;
    case CoprocReq_MotorReq_addPosition_tag:
        targetMotor.setTargetPosition(request.motorCmd.addPosition, true);
        break;
    case CoprocReq_MotorReq_setVelocityRegCoefs_tag:
        targetMotor.setVelocityPid(request.motorCmd.setVelocityRegCoefs);
        break;
    case CoprocReq_MotorReq_setPositionRegCoefs_tag:
        targetMotor.setPositionPid(request.motorCmd.setPositionRegCoefs);
        break;
    case CoprocReq_MotorReq_setConfig_tag:
        targetMotor.setConfig(request.motorCmd.setConfig);
        break;
    }
}

void motorReset() {
    std::scoped_lock lock(motorMut);

    for (int idx : { 0, 1, 2, 3 }) {
        motor[idx].reset();
        setMotorPower(idx, 0, false);
    }
}

static void setPwmValue(
    TIM_TypeDef* timer, uint8_t motorIndex, uint16_t value) {
    reinterpret_cast<__IO uint16_t*>(&timer->CCR1)[motorIndex << 1] = value;
}

static void setMotorPower(uint8_t motorIndex, int32_t power, bool brake) {
    if (power > SHRT_MAX || power < SHRT_MIN) {
        DEBUG("Motor %d power out of range <-32768; 32767> (%d).\n",
            int(motorIndex), int(power));
        return;
    }

    uint16_t pwm = uint32_t(abs(power) * maxPwm) / 32768;
    setPwmValue(pwmTimer, motorIndex, pwm);
    if (pwm == 0 || brake) {
        switch (motorIndex) {
        case 3:
            IN4PORT->BRR = IN4AMASK | IN4BMASK; // set LOW on IN4A and IN4B
            pwmTimer->CCER |= TIM_CCER_CC4P; // invert channel 4
            break;
        default:
            // set PWM on both channels and invert positive channel
            pwmTimer->CCER
                = (pwmTimer->CCER & ~(TIM_CCER_CC1NP << (motorIndex << 2)))
                | (TIM_CCER_CC1E << (motorIndex << 2))
                | (TIM_CCER_CC1NE << (motorIndex << 2))
                | (TIM_CCER_CC1P << (motorIndex << 2));
            break;
        }
    } else {
        switch (motorIndex) {
        case 3:
            IN4PORT->BSRR = power < 0
                ? IN4AMASK | (IN4BMASK << 16) // pinWrite(in4aPin, power < 0);
                : IN4BMASK | (IN4AMASK << 16); // pinWrite(in4bPin, power > 0);
            pwmTimer->CCER &= ~TIM_CCER_CC4P; // make channel 4 non-inverted
            break;
        default:
            if (power > 0) {
                // set HIGH on positive channel and inverted PWM on negative channel
                pwmTimer->CCER
                    = (pwmTimer->CCER
                          & ~((TIM_CCER_CC1P << (motorIndex << 2))
                              | (TIM_CCER_CC1NE << (motorIndex << 2))))
                    | (TIM_CCER_CC1NP << (motorIndex << 2))
                    | (TIM_CCER_CC1E << (motorIndex << 2));
            } else {
                // set HIGH on negative channel and inverted PWM on positive channel
                pwmTimer->CCER
                    = (pwmTimer->CCER
                          & ~((TIM_CCER_CC1E << (motorIndex << 2))
                              | (TIM_CCER_CC1NP << (motorIndex << 2))))
                    | (TIM_CCER_CC1NE << (motorIndex << 2))
                    | (TIM_CCER_CC1P << (motorIndex << 2));
            }
            break;
        }
    }
}