diff --git a/src/drivers/hardware_specific/silabs/silabs_mcu.cpp b/src/drivers/hardware_specific/silabs/silabs_mcu.cpp new file mode 100644 index 00000000..742218ed --- /dev/null +++ b/src/drivers/hardware_specific/silabs/silabs_mcu.cpp @@ -0,0 +1,369 @@ + +#include "./silabs_mcu.h" + + +#if defined(ARDUINO_ARCH_SILABS) + +#include "em_device.h" +#include "em_chip.h" +#include "em_cmu.h" +#include "em_emu.h" +#include "em_gpio.h" +#include "em_timer.h" + +#pragma message("") +#pragma message("SimpleFOC: compiling for SiliconLabs") +#pragma message("") + + +#ifndef SIMPLEFOC_SILABS_MAX_MOTORS +#define SIMPLEFOC_SILABS_MAX_MOTORS 5 +#endif + +SilabsDriverParams* configured_motors[SIMPLEFOC_SILABS_MAX_MOTORS] = {NULL}; +uint8_t num_configured_motors = 0; + +void printPortLetter(GPIO_Port_TypeDef port); +int8_t getTimerNumber(TIMER_TypeDef* timer); +CMU_Clock_TypeDef getTimerClock(TIMER_TypeDef* timer); + + +void setupPWM(int pin_nr, long pwm_frequency, bool active_high, SilabsDriverParams* params, uint8_t index, TIMER_TypeDef* timer, uint8_t channel) { + PinName pin_n = pinToPinName(pin_nr); + GPIO_Port_TypeDef port = getSilabsPortFromArduinoPin(pin_n); + uint32_t pin = getSilabsPinFromArduinoPin(pin_n); + int8_t timer_nr = getTimerNumber(timer); + TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT; + timerCCInit.mode = timerCCModePWM; + timerCCInit.coist = !active_high; + timerCCInit.outInvert = !active_high; + switch(channel) { + case 0: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CC0PEN; + GPIO->TIMERROUTE[timer_nr].CC0ROUTE = (port << _GPIO_TIMER_CC0ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CC0ROUTE_PIN_SHIFT); + break; + case 1: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CC1PEN; + GPIO->TIMERROUTE[timer_nr].CC1ROUTE = (port << _GPIO_TIMER_CC1ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CC1ROUTE_PIN_SHIFT); + break; + case 2: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CC2PEN; + GPIO->TIMERROUTE[timer_nr].CC2ROUTE = (port << _GPIO_TIMER_CC2ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CC2ROUTE_PIN_SHIFT); + break; + } + TIMER_InitCC(timer, channel, &timerCCInit); + + params->pins[index] = pin_nr; + params->timer[index] = timer; + params->channel[index] = channel; + + SimpleFOCDebug::print("DRV (Silabs): Pin "); + SimpleFOCDebug::print(pin_nr); + SimpleFOCDebug::print(" (P"); + printPortLetter(port); + SimpleFOCDebug::print((int)pin); + SimpleFOCDebug::print(") on TIMER"); + SimpleFOCDebug::print(timer_nr); + SimpleFOCDebug::print(" CH"); + SimpleFOCDebug::print(channel); + SimpleFOCDebug::print(" top "); + SimpleFOCDebug::println((int)params->resolution); +} + + + +void setupComplementaryPWM(int pin_nr, SilabsDriverParams* params, uint8_t index, TIMER_TypeDef* timer, uint8_t channel){ + int8_t timer_nr = getTimerNumber(timer); + PinName pin_n = pinToPinName(pin_nr); + GPIO_Port_TypeDef port = getSilabsPortFromArduinoPin(pin_n); + uint32_t pin = getSilabsPinFromArduinoPin(pin_n); + switch(channel) { + case 0: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CCC0PEN; + GPIO->TIMERROUTE[timer_nr].CDTI0ROUTE = (port << _GPIO_TIMER_CDTI0ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CDTI0ROUTE_PIN_SHIFT); + break; + case 1: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CCC1PEN; + GPIO->TIMERROUTE[timer_nr].CDTI1ROUTE = (port << _GPIO_TIMER_CDTI1ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CDTI1ROUTE_PIN_SHIFT); + break; + case 2: + GPIO->TIMERROUTE[timer_nr].ROUTEEN |= GPIO_TIMER_ROUTEEN_CCC2PEN; + GPIO->TIMERROUTE[timer_nr].CDTI2ROUTE = (port << _GPIO_TIMER_CDTI2ROUTE_PORT_SHIFT) + | (pin << _GPIO_TIMER_CDTI2ROUTE_PIN_SHIFT); + break; + } + + params->pins[index] = pin_nr; + params->timer[index] = timer; + params->channel[index] = channel; + + SimpleFOCDebug::print("DRV (Silabs): Pin "); + SimpleFOCDebug::print(pin_nr); + SimpleFOCDebug::print(" (P"); + printPortLetter(port); + SimpleFOCDebug::print((int)pin); + SimpleFOCDebug::print(") on TIMER"); + SimpleFOCDebug::print(timer_nr); + SimpleFOCDebug::print(" CH"); + SimpleFOCDebug::print(channel); + SimpleFOCDebug::print("COMP top "); + SimpleFOCDebug::println((int)params->resolution); +} + + + + + +bool isTimerUsed(TIMER_TypeDef* timer) { + for (int i=0;itimer[j] == timer) return true; + } + } + return false; +} + + + +TIMER_TypeDef* findFreeTimer(int* pins, uint8_t num_pins) { + TIMER_TypeDef* max_timer = NULL; + for (int i=0;iTOP = topValue; + params->pwm_frequency = pwm_frequency; + params->resolution = topValue; + params->timer[0] = timer; + configured_motors[num_configured_motors++] = params; +} + + + + + +void initDeadTimeInsertion(TIMER_TypeDef* timer, long pwm_frequency, bool highside_active_high, bool lowside_active_high, SilabsDriverParams* params){ + TIMER_InitDTI_TypeDef dtiInit = TIMER_INITDTI_DEFAULT; + dtiInit.enable = false; // or true? need to check first PWM cycle for this + dtiInit.activeLowOut = !highside_active_high; + dtiInit.invertComplementaryOut = !lowside_active_high; + dtiInit.riseTime = params->dead_zone * params->resolution; // divided by 2 for symmetric dead-time, times 2 due to up-down counting + if (dtiInit.riseTime > 64) dtiInit.riseTime = 64; // max dead-time is 64 counts (each side) + dtiInit.fallTime = dtiInit.riseTime; + dtiInit.autoRestart = true; + SimpleFOCDebug::print("DRV (Silabs): Dead time "); + Serial.println(dtiInit.riseTime); + dtiInit.outputsEnableMask = TIMER_DTOGEN_DTOGCC0EN | TIMER_DTOGEN_DTOGCDTI0EN + | TIMER_DTOGEN_DTOGCC1EN | TIMER_DTOGEN_DTOGCDTI1EN + | TIMER_DTOGEN_DTOGCC2EN | TIMER_DTOGEN_DTOGCDTI2EN; + unsigned long long max = TIMER_MaxCount(timer) + 1; + CMU_Clock_TypeDef timer_clock = getTimerClock(timer); + dtiInit.prescale = (TIMER_Prescale_TypeDef)(CMU_ClockFreqGet(timer_clock) / pwm_frequency / 2 / max); + TIMER_InitDTI(timer, &dtiInit); +} + + + + +void* _configure1PWM(long pwm_frequency, const int pinA) { + SilabsDriverParams* params = new SilabsDriverParams(); + int pins[1] = {pinA}; + TIMER_TypeDef* timer = findFreeTimer(pins, 1); + initTimer(timer, pwm_frequency, params); + setupPWM(pinA, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 0, timer, 0); + TIMER_Enable(timer, true); + return params; +} + + + +void* _configure2PWM(long pwm_frequency, const int pinA, const int pinB) { + SilabsDriverParams* params = new SilabsDriverParams(); + int pins[2] = {pinA, pinB}; + TIMER_TypeDef* timer = findFreeTimer(pins, 2); + initTimer(timer, pwm_frequency, params); + setupPWM(pinA, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 0, timer, 0); + setupPWM(pinB, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 1, timer, 1); + TIMER_Enable(timer, true); + return params; +} + + + +void* _configure3PWM(long pwm_frequency, const int pinA, const int pinB, const int pinC) { + SilabsDriverParams* params = new SilabsDriverParams(); + int pins[3] = {pinA, pinB, pinC}; + TIMER_TypeDef* timer = findFreeTimer(pins, 3); + initTimer(timer, pwm_frequency, params); + setupPWM(pinA, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 0, timer, 0); + setupPWM(pinB, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 1, timer, 1); + setupPWM(pinC, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 2, timer, 2); + TIMER_Enable(timer, true); + return params; +} + + + + +void* _configure4PWM(long pwm_frequency, const int pin1A, const int pin1B, const int pin2A, const int pin2B) { + SilabsDriverParams* params = new SilabsDriverParams(); + int pins[2] = { pin1A, pin1B }; + TIMER_TypeDef* timer0 = findFreeTimer(pins, 2); + initTimer(timer0, pwm_frequency, params); + pins[0] = pin2A; pins[1] = pin2B; + TIMER_TypeDef* timer1 = findFreeTimer(pins, 2); + initTimer(timer1, pwm_frequency, params); + setupPWM(pin1A, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 0, timer0, 0); + setupPWM(pin1B, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 1, timer0, 1); + setupPWM(pin2A, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 2, timer1, 0); + setupPWM(pin2B, pwm_frequency, SIMPLEFOC_PWM_ACTIVE_HIGH, params, 3, timer1, 1); + TIMER_Enable(timer0, true); + TIMER_Enable(timer1, true); + return params; +} + + +void* _configure6PWM(long pwm_frequency, float dead_zone, const int pinA_h, const int pinA_l, const int pinB_h, const int pinB_l, const int pinC_h, const int pinC_l) { + SilabsDriverParams* params = new SilabsDriverParams(); + int pins[6] = {pinA_h, pinA_l, pinB_h, pinB_l, pinC_h, pinC_l}; + TIMER_TypeDef* timer = findFreeTimer(pins, 6); + initTimer(timer, pwm_frequency, params); + // init using DTI + params->dead_zone = dead_zone; + setupPWM(pinA_h, pwm_frequency, SIMPLEFOC_PWM_HIGHSIDE_ACTIVE_HIGH, params, 0, timer, 0); + setupPWM(pinB_h, pwm_frequency, SIMPLEFOC_PWM_HIGHSIDE_ACTIVE_HIGH, params, 2, timer, 1); + setupPWM(pinC_h, pwm_frequency, SIMPLEFOC_PWM_HIGHSIDE_ACTIVE_HIGH, params, 4, timer, 2); + initDeadTimeInsertion(timer, pwm_frequency, SIMPLEFOC_PWM_HIGHSIDE_ACTIVE_HIGH, SIMPLEFOC_PWM_LOWSIDE_ACTIVE_HIGH, params); + setupComplementaryPWM(pinA_l, params, 1, timer, 0); + setupComplementaryPWM(pinB_l, params, 3, timer, 1); + setupComplementaryPWM(pinC_l, params, 5, timer, 2); + TIMER_EnableDTI(timer, true); + TIMER_Enable(timer, true); + return params; +} + + + + + +void writeDutyCycle(float val, TIMER_TypeDef* timer, uint8_t chan) { + timer->CC[chan].OC = val * timer->TOP; + //TIMER_CompareSet(timer, chan, val * timer->TOP); +} + + + + +void _writeDutyCycle1PWM(float dc_a, void* params) { + writeDutyCycle(dc_a, ((SilabsDriverParams*)params)->timer[0], ((SilabsDriverParams*)params)->channel[0]); +} + + + + +void _writeDutyCycle2PWM(float dc_a, float dc_b, void* params) { + writeDutyCycle(dc_a, ((SilabsDriverParams*)params)->timer[0], ((SilabsDriverParams*)params)->channel[0]); + writeDutyCycle(dc_b, ((SilabsDriverParams*)params)->timer[1], ((SilabsDriverParams*)params)->channel[1]); +} + + + +void _writeDutyCycle3PWM(float dc_a, float dc_b, float dc_c, void* params) { + writeDutyCycle(dc_a, ((SilabsDriverParams*)params)->timer[0], ((SilabsDriverParams*)params)->channel[0]); + writeDutyCycle(dc_b, ((SilabsDriverParams*)params)->timer[1], ((SilabsDriverParams*)params)->channel[1]); + writeDutyCycle(dc_c, ((SilabsDriverParams*)params)->timer[2], ((SilabsDriverParams*)params)->channel[2]); +} + + + +void _writeDutyCycle4PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, void* params) { + writeDutyCycle(dc_1a, ((SilabsDriverParams*)params)->timer[0], ((SilabsDriverParams*)params)->channel[0]); + writeDutyCycle(dc_1b, ((SilabsDriverParams*)params)->timer[1], ((SilabsDriverParams*)params)->channel[1]); + writeDutyCycle(dc_2a, ((SilabsDriverParams*)params)->timer[2], ((SilabsDriverParams*)params)->channel[2]); + writeDutyCycle(dc_2b, ((SilabsDriverParams*)params)->timer[3], ((SilabsDriverParams*)params)->channel[3]); +} + + +void _writeDutyCycle6PWM(float dc_a, float dc_b, float dc_c, PhaseState *phase_state, void* params) { + // TODO: handle phase state + writeDutyCycle(dc_a, ((SilabsDriverParams*)params)->timer[0], ((SilabsDriverParams*)params)->channel[0]); + writeDutyCycle(dc_b, ((SilabsDriverParams*)params)->timer[2], ((SilabsDriverParams*)params)->channel[2]); + writeDutyCycle(dc_c, ((SilabsDriverParams*)params)->timer[4], ((SilabsDriverParams*)params)->channel[4]); +} + + + +CMU_Clock_TypeDef getTimerClock(TIMER_TypeDef* timer) { + if(timer == TIMER0) return cmuClock_TIMER0; + if(timer == TIMER1) return cmuClock_TIMER1; + if(timer == TIMER2) return cmuClock_TIMER2; + if(timer == TIMER3) return cmuClock_TIMER3; + if(timer == TIMER4) return cmuClock_TIMER4; + return cmuClock_TIMER0; +} + + + +int8_t getTimerNumber(TIMER_TypeDef* timer) { + if(timer == TIMER0) return 0; + if(timer == TIMER1) return 1; + if(timer == TIMER2) return 2; + if(timer == TIMER3) return 3; + if(timer == TIMER4) return 4; + return -1; +} + +void printPortLetter(GPIO_Port_TypeDef port) { + if(port == gpioPortA) SimpleFOCDebug::print("A"); + else if(port == gpioPortB) SimpleFOCDebug::print("B"); + else if(port == gpioPortC) SimpleFOCDebug::print("C"); + else if(port == gpioPortD) SimpleFOCDebug::print("D"); + else SimpleFOCDebug::print("?"); +} + + +#endif + diff --git a/src/drivers/hardware_specific/silabs/silabs_mcu.h b/src/drivers/hardware_specific/silabs/silabs_mcu.h new file mode 100644 index 00000000..0ca2c41f --- /dev/null +++ b/src/drivers/hardware_specific/silabs/silabs_mcu.h @@ -0,0 +1,87 @@ +/** + * Silabs support for SimpleFOC library + * + * The Silabs Gecko EFR32 procossors as used in the Arduino Nano Matter have the following + * features: + * - 2 × 32-bit Timer/Counter with 3 Compare/Capture/PWM channels (TIMER0, TIMER1) + * - 3 × 16-bit Timer/Counter with 3 Compare/Capture/PWM channels (TIMER2, TIMER3, TIMER4) + * - All timers support dead-time insertion (DTI) + * - Centre-aligned PWM mode + * - Buffered compare register to ensure glitch-free update of compare values + * - Timers can trigger ADC and DMA via reflex system + * - Timers can be started/stopped synchronously by other timers + * + * The MCU is very flexible regarding its pin assigments. The following is the possible + * assignments: + * + * GPIO port A: TIMER0, TIMER1, TIMER2, TIMER4 + * GPIO port B: TIMER0, TIMER1, TIMER2, TIMER4 + * GPIO port C: TIMER0, TIMER1, TIMER3 + * GPIO port D: TIMER0, TIMER1, TIMER3 + * + * Any pin of these ports can be used with any of the 3 channels or the inverted + * channels of the possible timers. + * + * This suggests the following usage paradigm: + * - 3-PWM or 6-PWM use a single timer, all 3 channels + * - 2-PWM uses two channels of a single timer + * - 1-PWM uses a single channel + * - 4-PWM uses two timers, two channels each (we could try to sync them) + * - TIMER0 and TIMER1 are preferred, since they can be used with all GPIO ports + * + * Nano Matter Pin/Port assignments: + * D0 TX: PA04 + * D1 RX: PA05 + * D3: PC06 + * D4 SDA1: PC07 + * D5 SCL1: PC08 + * D6: PC09 + * D7: PD02 + * D8: PD03 + * D9: PTI_DATA: PD04 + * D10 SS PTI_SYNC: PD05 + * D11 MOSI: PA09 + * D12 MISO: PA08 + * D13 CLK: PB04 + * A0: PB00 + * A1: PB02 + * A2: PB05 + * A3: PC00 + * A4 SDA: PA06 + * A5 SCL: PA07 + * A6: PB01 + * A7: PB03 + * RGB_R: PC01 + * RGB_G: PC02 + * RGB_B: PC03 + * + * So the Nano Matter could use any of TIMER0, TIMER1, or TIMER3 with + * the SimpleFOC Nano Shield (3-PWM mode). + * + */ + + +#pragma once + +#include "../../hardware_api.h" +#include + +#if defined(ARDUINO_ARCH_SILABS) + +#include "em_timer.h" + +#define SIMPLEFOC_SILABS_DEFAULT_PWM_FREQUENCY 25000 + + +typedef struct SilabsDriverParams { + int pins[6] = { -1 }; + TIMER_TypeDef* timer[6] = { NULL }; + uint8_t channel[6]; + uint32_t pwm_frequency; + uint32_t resolution; + float dead_zone; +} SilabsDriverParams; + + + +#endif \ No newline at end of file