8pwm PULL request

By openAI;

To modify the function to work with 8 PWM control, you can split each half-bridge into two PWM signals, so that there are a total of 8 PWM signals to control the 4 half-bridges. Here is an updated version of the function:

scssCopy code

// Set voltage to the pwm pin for 8PWM control
void StepperDriver8PWM::setPwm(float Ualpha, float Ubeta) {
  float duty_cycle1A_h1(0.0f),duty_cycle1A_h2(0.0f),duty_cycle1B_h1(0.0f),duty_cycle1B_h2(0.0f);
  float duty_cycle2A_h1(0.0f),duty_cycle2A_h2(0.0f),duty_cycle2B_h1(0.0f),duty_cycle2B_h2(0.0f);

  // limit the voltage in driver
  Ualpha = _constrain(Ualpha, -voltage_limit, voltage_limit);
  Ubeta = _constrain(Ubeta, -voltage_limit, voltage_limit);

  // hardware specific writing
  if( Ualpha > 0 ) {
    duty_cycle1B_h1 = _constrain(abs(Ualpha)/voltage_power_supply,0.0f,1.0f);
    duty_cycle1B_h2 = 0.0f; // set second half-bridge duty cycle to 0
  else {
    duty_cycle1A_h1 = _constrain(abs(Ualpha)/voltage_power_supply,0.0f,1.0f);
    duty_cycle1A_h2 = 0.0f; // set second half-bridge duty cycle to 0

  if( Ubeta > 0 ) {
    duty_cycle2B_h1 = _constrain(abs(Ubeta)/voltage_power_supply,0.0f,1.0f);
    duty_cycle2B_h2 = 0.0f; // set second half-bridge duty cycle to 0
  else {
    duty_cycle2A_h1 = _constrain(abs(Ubeta)/voltage_power_supply,0.0f,1.0f);
    duty_cycle2A_h2 = 0.0f; // set second half-bridge duty cycle to 0

  // write to hardware
  _writeDutyCycle8PWM(duty_cycle1A_h1, duty_cycle1A_h2, duty_cycle1B_h1, duty_cycle1B_h2,
                      duty_cycle2A_h1, duty_cycle2A_h2, duty_cycle2B_h1, duty_cycle2B_h2, params);

In this version of the function, each half-bridge has two duty cycles, duty_cycle_h1 and duty_cycle_h2, which correspond to the two PWM signals used to control that half-bridge. If the voltage for a half-bridge is positive, then the first duty cycle is set to the absolute value of the voltage divided by the power supply voltage, and the second duty cycle is set to 0. If the voltage for a half-bridge is negative, then the second duty cycle is set to the absolute value of the voltage divided by the power supply voltage, and the first duty cycle is set to 0. Finally, the function calls _writeDutyCycle8PWM with the eight duty cycles to update the PWM signals for all four half-bridges.

What’s the benefit of 8pwm? Why use this over step/dir interface, for example?

Those are HI / LI phases of the 4 ½ bridge driver, running at buck voltage [MAX 15V].
This will clearly need to be configured with dead-time insertion.

// init hardware pins for 8PWM control
int StepperDriver8PWM::init() {

  // PWM pins
  pinMode(pwm1A, OUTPUT);
  pinMode(pwm1B, OUTPUT);
  pinMode(pwm2A, OUTPUT);
  pinMode(pwm2B, OUTPUT);
  pinMode(pwm3A, OUTPUT);
  pinMode(pwm3B, OUTPUT);
  pinMode(pwm4A, OUTPUT);
  pinMode(pwm4B, OUTPUT);

  if( _isset(enable_pin1) ) pinMode(enable_pin1, OUTPUT);
  if( _isset(enable_pin2) ) pinMode(enable_pin2, OUTPUT);

  // sanity check for the voltage limit configuration
  if( !_isset(voltage_limit) || voltage_limit > voltage_power_supply) voltage_limit =  voltage_power_supply;

  // Set the pwm frequency to the pins
  // hardware specific function - depending on driver and mcu
  params = _configure8PWM(pwm_frequency, pwm1A, pwm1B, pwm2A, pwm2B, pwm3A, pwm3B, pwm4A, pwm4B);
  initialized = (params!=SIMPLEFOC_DRIVER_INIT_FAILED);  

_configure8PWM // Getting closer

// function setting the high pwm frequency to the supplied pins
// - Stepper motor - 8PWM setting
// - hardware specific
void* _configure8PWM(long pwm_frequency,
                      const int pinA, const int pinB, const int pinC, const int pinD,
                      const int pinE, const int pinF, const int pinG, const int pinH) {
  if (numTimerPinsUsed + 8 > SIMPLEFOC_STM32_MAX_PINTIMERSUSED) {
    SIMPLEFOC_DEBUG("STM32-DRV: ERR: too many pins used");
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;
  if( !pwm_frequency || !_isset(pwm_frequency) ) pwm_frequency = _PWM_FREQUENCY; // default frequency 25khz
  else pwm_frequency = _constrain(pwm_frequency, 0, _PWM_FREQUENCY_MAX); // constrain to 50kHz max
  // center-aligned frequency is uses two periods
  pwm_frequency *=2;

  int pins[8] = { pinA, pinB, pinC, pinD, pinE, pinF, pinG, pinH };
  PinMap* pinTimers[8] = { NP, NP, NP, NP, NP, NP, NP, NP };
  if (findBestTimerCombination(8, pins, pinTimers) < 0) {
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;

  HardwareTimer* HT1 = _initPinPWM(pwm_frequency, pinTimers[0]);
  HardwareTimer* HT2 = _initPinPWM(pwm_frequency, pinTimers[1]);
  HardwareTimer* HT3 = _initPinPWM(pwm_frequency, pinTimers[2]);
  HardwareTimer* HT4 = _initPinPWM(pwm_frequency, pinTimers[3]);
  HardwareTimer* HT5 = _initPinPWM(pwm_frequency, pinTimers[4]);
  HardwareTimer* HT6 = _initPinPWM(pwm_frequency, pinTimers[5]);
  HardwareTimer* HT7 = _initPinPWM(pwm_frequency, pinTimers[6]);
  HardwareTimer* HT8 = _initPinPWM(pwm_frequency, pinTimers[7]);

  // allign the timers
  _alignPWMTimers(HT1, HT2, HT3, HT4, HT5, HT6, HT7, HT8);

  uint32_t channel1 = STM_PIN_CHANNEL(pinTimers[0]->function);
  uint32_t channel2 = STM_PIN_CHANNEL(pinTimers[1]->function);
  uint32_t channel3 = STM_PIN_CHANNEL(pinTimers[2]->function);
  uint32_t channel4 = STM_PIN_CHANNEL(pinTimers[3]->function);
  uint32_t channel5 = STM_PIN_CHANNEL(pinTimers[4]->function);
  uint32_t channel6 = STM_PIN_CHANNEL(pinTimers[5]->function);
  uint32_t channel7 = STM_PIN_CHANNEL(pinTimers[6]->function);
  uint32_t channel8 = STM_PIN_CHANNEL(pinTimers[7]->function);

  STM32DriverParams* params = new STM32DriverParams {
    .timers = { HT1, HT2, HT3, HT4, HT5, HT6, HT7, HT8 },
    .channels = { channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 },
    .pwm_frequency = pwm_frequency


// function setting the pwm duty cycle to the hardware
// - Stepper motor - 8PWM setting
// - hardware specific
void _writeDutyCycle8PWM(float dc_1a, float dc_1b, float dc_2a, float dc_2b, float dc_3a, float dc_3b, float dc_4a, float dc_4b, void* params) {
  // transform duty cycle from [0,1] to [0,4095]
  _setPwm(((STM32DriverParams*)params)->timers[0], ((STM32DriverParams*)params)->channels[0], _PWM_RANGE * dc_1a, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[1], ((STM32DriverParams*)params)->channels[1], _PWM_RANGE * dc_1b, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[2], ((STM32DriverParams*)params)->channels[2], _PWM_RANGE * dc_2a, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[3], ((STM32DriverParams*)params)->channels[3], _PWM_RANGE * dc_2b, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[4], ((STM32DriverParams*)params)->channels[4], _PWM_RANGE * dc_3a, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[5], ((STM32DriverParams*)params)->channels[5], _PWM_RANGE * dc_3b, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[6], ((STM32DriverParams*)params)->channels[6], _PWM_RANGE * dc_4a, _PWM_RESOLUTION);
  _setPwm(((STM32DriverParams*)params)->timers[7], ((STM32DriverParams*)params)->channels[7], _PWM_RANGE * dc_4b, _PWM_RESOLUTION);
// configure hardware 8pwm
STM32DriverParams* _initHardware8PWM(long PWM_freq, float dead_zone, PinMap* pins, STM32DriverParams* params) {
  // sanity check
  if (pins == NULL)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;

  // check if the number of pins is correct
  if (params->num_channels < 8)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;

  // configure the first 6 pins on TIM1
  params = _initHardware6PWM(PWM_freq, dead_zone, &pins[0], &pins[1], params, 0);
  if (params == NULL)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;

  // configure the remaining 2 pins on TIM8
  uint32_t index = get_timer_index((TIM_TypeDef*)pins[6].peripheral);
  if (HardwareTimer_Handle[index] == NULL) {
    HardwareTimer_Handle[index]->__this = new HardwareTimer((TIM_TypeDef*)pins[6].peripheral);
    HardwareTimer_Handle[index]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
    HardwareTimer_Handle[index]->handle.Init.RepetitionCounter = 1;
    ((HardwareTimer *)HardwareTimer_Handle[index]->__this)->setOverflow((uint32_t)PWM_freq, HERTZ_FORMAT);
  HardwareTimer *HT = (HardwareTimer *)(HardwareTimer_Handle[index]->__this);

  // configure the pins
  HT->setMode(STM_PIN_CHANNEL(pins[6].function), TIMER_OUTPUT_COMPARE_PWM1, pins[6].pin);
  HT->setMode(STM_PIN_CHANNEL(pins[7].function), TIMER_OUTPUT_COMPARE_PWM1, pins[7].pin);

  // configure the dead time
  uint32_t dead_time_ns = (float)(1e9f/PWM_freq)*dead_zone;
  uint32_t dead_time = __LL_TIM_CALC_DEADTIME(SystemCoreClock, LL_TIM_GetClockDivision(HT->getHandle()->Instance), dead_time_ns);
  LL_TIM_OC_SetDeadTime(HT->getHandle()->Instance, dead_time);

  // set the polarity
  LL_TIM_OC_SetPolarity(HT->getHandle()->Instance, getLLChannel(&pins[6]), LL_TIM_OCPOLARITY_LOW);
  LL_TIM_OC_SetPolarity(HT->getHandle()->Instance, getLLChannel(&pins[7]), LL_TIM_OCPOLARITY_LOW);

  // enable the channels
  LL_TIM_CC_EnableChannel(HT->getHandle()->Instance, getLLChannel(&pins[6]) | getLLChannel(&pins[7]));

  // make sure timer output goes to LOW when timer channels are temporarily disabled
  LL_TIM_SetOffStates(HT->getHandle()->Instance, LL_TIM_OSSI_DISABLE, LL_TIM_OSSR_ENABLE);

  // store the timer and channel information in the params struct
params->timers[paramsPos+2] = NULL; // set to NULL to indicate that this is not a complementary pair

// initialize and configure the fourth half-bridge driver on TIM8
PinMap* pinH4 = &PA14;
PinMap* pinL4 = &PB0;

uint32_t channel3 = STM_PIN_CHANNEL(pinH4->function);
uint32_t channel4 = STM_PIN_CHANNEL(pinL4->function);

if (channel3 != channel4 || pinH4->peripheral != pinL4->peripheral)

uint32_t index4 = get_timer_index((TIM_TypeDef*)pinH4->peripheral);

if (HardwareTimer_Handle[index4] == NULL) {
HardwareTimer_Handle[index4]->__this = new HardwareTimer((TIM_TypeDef*)pinH4->peripheral);
HardwareTimer_Handle[index4]->handle.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED3;
HardwareTimer_Handle[index4]->handle.Init.RepetitionCounter = 1;
((HardwareTimer *)HardwareTimer_Handle[index4]->__this)->setOverflow((uint32_t)PWM_freq, HERTZ_FORMAT);
HardwareTimer *HT4 = (HardwareTimer *)(HardwareTimer_Handle[index4]->__this);

HT4->setMode(channel3, TIMER_OUTPUT_COMPARE_PWM1, pinH4->pin);
HT4->setMode(channel4, TIMER_OUTPUT_COMPARE_PWM1, pinL4->pin);

// dead time is set in nanoseconds
uint32_t dead_time_ns = (float)(1e9f/PWM_freq)*dead_zone;
uint32_t dead_time = __LL_TIM_CALC_DEADTIME(SystemCoreClock, LL_TIM_GetClockDivision(HT4->getHandle()->Instance), dead_time_ns);
LL_TIM_OC_SetDeadTime(HT4->getHandle()->Instance, dead_time); // deadtime is non linear!
LL_TIM_OC_SetPolarity(HT4->getHandle()->Instance, getLLChannel(pinH4), LL_TIM_OCPOLARITY_LOW);
LL_TIM_OC_SetPolarity(HT4->getHandle()->Instance, getLLChannel(pinL4), LL_TIM_OCPOLARITY_LOW);
LL_TIM_CC_EnableChannel(HT4->getHandle()->Instance, getLLChannel(pinH4) | getLLChannel(pinL4));

// make sure timer output goes to LOW when timer channels are temporarily disabled
LL_TIM_SetOffStates(HT4->getHandle()->Instance, LL_TIM_OSSI_DISABLE, LL_TIM_OSSR_ENABLE);

// store the timer and channel information in the params struct
params->timers[paramsPos] = HT;
params->timers[paramsPos+1] = HT;
params->timers[paramsPos+2] = HT4;
params->timers[paramsPos+3] = HT4;
params->channels[paramsPos] = channel1;
params->channels[paramsPos+1] = channel2;
params->channels[paramsPos+2] = channel3;
params->channels[paramsPos+3] = channel4;
return params;

[Edit] Maybe this can work?
STM32DriverParams* _initHardware8PWMInterface(long PWM_freq, float dead_zone, PinMap* pinA_h, PinMap* pinA_l, PinMap* pinB_h, PinMap* pinB_l, PinMap* pinC_h, PinMap* pinC_l, PinMap* pinD_h, PinMap* pinD_l) {
  STM32DriverParams* params = new STM32DriverParams {
    .timers = { NP, NP, NP, NP, NP, NP, NP, NP },
    .channels = { 0, 0, 0, 0, 0, 0, 0, 0 },
    .pwm_frequency = PWM_freq,
    .dead_zone = dead_zone,
    .interface_type = _HARDWARE_8PWM

  if (_initHardware6PWMPair(PWM_freq, dead_zone, pinA_h, pinA_l, params, 0) == SIMPLEFOC_DRIVER_INIT_FAILED)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;
  if(_initHardware6PWMPair(PWM_freq, dead_zone, pinB_h, pinB_l, params, 2) == SIMPLEFOC_DRIVER_INIT_FAILED)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;
  if (_initHardware6PWMPair(PWM_freq, dead_zone, pinC_h, pinC_l, params, 4) == SIMPLEFOC_DRIVER_INIT_FAILED)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;
  if (_initHardware6PWMPair(PWM_freq, dead_zone, pinD_h, pinD_l, params, 6) == SIMPLEFOC_DRIVER_INIT_FAILED)
    return (STM32DriverParams*)SIMPLEFOC_DRIVER_INIT_FAILED;

  return params;

The implementation is very similar to _initHardware6PWMInterface , but adds two additional pins to control a fourth motor phase (D). It initializes the STM32DriverParams struct with the correct number of timers and channels, and calls _initHardware6PWMPair four times to initialize each pair of pins. Finally, it returns the params struct.

Have not pulled yet.

Edit: This is compiling.

Juanduino/Arduino-FOC at 8pwm (github.com)

Yes, the StepperDriver8PWM::setPwm(float Ualpha, float Ubeta) function is intended to be called by the setPhaseVoltage function in order to set the appropriate PWM duty cycles based on the calculated alpha and beta voltages. The setPhaseVoltage function performs the necessary calculations to transform the input voltages Uq and Ud to the alpha and beta voltages required for the 8 PWM control. These alpha and beta voltages are then passed as arguments to the setPwm function, which sets the appropriate duty cycles on the PWM pins. So, the setPhaseVoltage and StepperDriver8PWM::setPwm functions are intended to work together as part of a larger system for driving a 2-phase stepper motor using 8 PWM pins.

I have made a pull to dev, could you se if it can be done? Mostly done with OpenAI

Initial 8pwm by Juanduino · Pull Request #260 · simplefoc/Arduino-FOC (github.com)

@runger @Antun_Skuric

if I could make OpenAI work directly in the folder, then I would properly tell it to pass parameters for setting the individual timer (TIM1 or TIM8) and channel e.g. TIM8_CH2/TIM8_CH2N. How can you else command it to use TIM8 on the last two pins? So, what´s “The score”?

No, and unfortunately this timer sync code doesn’t work too well in the first place.
If you’re in need of low side sensing you should definately stick to pins of the same timer at the moment with STM32…

I made some comments in the PR :slight_smile:

Working in VS Code you can install GitHub co-pilot. It’s a very helpful tool, but unfortunately we’re not quite there yet in terms of AI-generated code actually working in our kind of scenario.
The AI is a big time-saver by suggesting and auto-completing stuff, but you have to be very confident in your domain so that you can spot and fix all its errors.

OK remove that part! its still compiling here. Does the Dev branch ( Juanduino/Arduino-FOC at dev (github.com)) look better ?

#include "stm32g4xx_hal.h"
#include "stm32g4xx_hal_gpio.h"
#include "stm32g4xx_hal_tim.h"
#include "stm32g4xx_hal_tim_ex.h"

// Declare timer handles for TIM1 and TIM8
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim8;

// Function to configure PWM output on TIM1 channels 1-6 and TIM8 channel 1
void configure8PWM(void)
    // Initialize HAL library

    // GPIO pin initialization struct
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // Enable clock for TIM1 and TIM8

   // Configure GPIO pins for STSPIN32G4 internal FET driver
       GPIO_InitTypeDef GPIO_InitStruct = {0};
      __HAL_RCC_GPIOE_CLK_ENABLE();   // enable GPIO clock
      GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_8 | GPIO_PIN_11 | GPIO_PIN_10 | GPIO_PIN_13 | GPIO_PIN_12;
      GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;  // alternate function mode with push-pull output
      GPIO_InitStruct.Pull = GPIO_NOPULL;
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
      GPIO_InitStruct.Alternate = GPIO_AF6_TIM1;
      HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    // Configure TIM8 pins for alternate function mode with push-pull output
    GPIO_InitStruct.Pin = GPIO_PIN_14 | GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Alternate = GPIO_AF3_TIM8; 
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);     


    // Set TIM1 and TIM8 dead time values to 50 ns
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 255;
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim1.Init.RepetitionCounter = 0;

    htim8.Instance = TIM8;
    htim8.Init.Prescaler = 0;
    htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim8.Init.Period = 255;
    htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim8.Init.RepetitionCounter = 0;

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);

    sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfig.DeadTime = 50;
    sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
     // Set TIM8 dead time values to 50 ns
    TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfigTIM8 = {0};
    sBreakDeadTimeConfigTIM8.OffStateRunMode = TIM_OSSR_DISABLE;
    sBreakDeadTimeConfigTIM8.OffStateIDLEMode = TIM_OSSI_DISABLE;
    sBreakDeadTimeConfigTIM8.LockLevel = TIM_LOCKLEVEL_OFF;
    sBreakDeadTimeConfigTIM8.DeadTime = 50;
    sBreakDeadTimeConfigTIM8.BreakState = TIM_BREAK_DISABLE;
    sBreakDeadTimeConfigTIM8.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
    sBreakDeadTimeConfigTIM8.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
    HAL_TIMEx_ConfigBreakDeadTime(&htim8, &sBreakDeadTimeConfigTIM8);

    // Configure TIM1 channels 1-6 for PWM output
    TIM_OC_InitTypeDef sConfigOC = {0};
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH1);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH1N);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH2);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH2N);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH3);
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, LL_TIM_CHANNEL_CH3N);

    // Configure PWM output on TIM8 channel 1 and additional channel
    HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, LL_TIM_CHANNEL_CH2);
    HAL_TIM_PWM_ConfigChannel(&htim8, &sConfigOC, LL_TIM_CHANNEL_CH2N);

    // Start TIM1 and TIM8 PWM outputs
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH1);
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH1N);
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH2);
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH2N);
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH3);
    HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH3N);
    HAL_TIM_PWM_Start(&htim8, LL_TIM_CHANNEL_CH2);
    HAL_TIM_PWM_Start(&htim8, LL_TIM_CHANNEL_CH2N);

// Set PWM frequency for TIM1 and TIM8
__HAL_TIM_SET_PRESCALER(&htim1, (SystemCoreClock / (pwm_frequency * 510))-1);
__HAL_TIM_SET_PRESCALER(&htim8, (SystemCoreClock / (pwm_frequency * 510))-1);

// Set initial dead time value
TIM1->BDTR |= (uint32_t)(50 / 11.9);

   return NULL;


Yes, the pins mentioned in the code match the 6 pins on TIM1. The pins are set to alternate function mode with push-pull output to generate the PWM signals on TIM1 channels 1-6. The pin numbers are GPIO_PIN_9, GPIO_PIN_8, GPIO_PIN_11, GPIO_PIN_10, GPIO_PIN_13, and GPIO_PIN_12, respectively. The pins are initialized with GPIO_InitStruct.Alternate = GPIO_AF6_TIM1.

PE7: WAKE, Push-pull output
PE8: INL1, TIM1_CH1N(1)
PE9: INH1, TIM1_CH1(1)
PE10: INL2, TIM1_CH2N(1)
PE11: INH2, TIM1_CH2(1)
PE12: INL3, TIM1_CH3N(1)
PE13: INH3, TIM1_CH3(1)
PE14: READY, Input with pull-up, TIM1_BKIN2(2) (optional)
PE15: NFAULT, Input with pull-up, TIM1_BKIN(2) (optional)
TIM1->CR1 |= TIM_CR1_ARPE; // Auto-reload preload enable
    TIM1->CR1 &= ~TIM_CR1_DIR; // Up counting mode
    TIM1->CCMR1 |= TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM mode 1 on OC1
    TIM1->CCER |= TIM_CCER_CC1E; // Enable output on OC1
    TIM1->PSC = 0; // Set prescaler to 0
    TIM1->ARR = (SystemCoreClock / (38000 * 2)) - 1; // Set auto-reload value for 38kHz frequency
    TIM1->CCR1 = (SystemCoreClock / (38000 * 2)) / 2; // Set duty cycle to 50%
    TIM1->BDTR |= TIM_BDTR_MOE; // Main output enable

    TIM8->CR1 |= TIM_CR1_ARPE; // Auto-reload preload enable
    TIM8->CR1 &= ~TIM_CR1_DIR; // Up counting mode
    TIM8->CCMR1 |= TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM mode 1 on OC1
    TIM8->CCER |= TIM_CCER_CC1E; // Enable output on OC1
    TIM8->PSC = 0; // Set prescaler to 0
    TIM8->ARR = (SystemCoreClock / (38000 * 2)) - 1; // Set auto-reload value for 38kHz frequency
    TIM8->CCR1 = (SystemCoreClock / (38000 * 2)) / 2; // Set duty cycle to 50%
    TIM8->BDTR |= TIM_BDTR_MOE; // Main output enable

    // Set initial dead time value
    TIM1->BDTR |= (uint32_t)(50 / 11.9);


Do you think I can start the PWM outputs like this:

// Enable PWM outputs
        HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH1);
        HAL_TIMEx_PWMN_Start(&htim1, LL_TIM_CHANNEL_CH1N);

        HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH2);
        HAL_TIMEx_PWMN_Start(&htim1, LL_TIM_CHANNEL_CH2N);

        HAL_TIM_PWM_Start(&htim1, LL_TIM_CHANNEL_CH3);
        HAL_TIMEx_PWMN_Start(&htim1, LL_TIM_CHANNEL_CH3N);

        HAL_TIM_PWM_Start(&htim8, LL_TIM_CHANNEL_CH2);
        HAL_TIMEx_PWMN_Start(&htim8, LL_TIM_CHANNEL_CH2N);
/** @defgroup TIM_LL_EC_CHANNEL Channel
  * @{
#define LL_TIM_CHANNEL_CH1                     TIM_CCER_CC1E     /*!< Timer input/output channel 1 */
#define LL_TIM_CHANNEL_CH1N                    TIM_CCER_CC1NE    /*!< Timer complementary output channel 1 */
#define LL_TIM_CHANNEL_CH2                     TIM_CCER_CC2E     /*!< Timer input/output channel 2 */
#define LL_TIM_CHANNEL_CH2N                    TIM_CCER_CC2NE    /*!< Timer complementary output channel 2 */
#define LL_TIM_CHANNEL_CH3                     TIM_CCER_CC3E     /*!< Timer input/output channel 3 */
#define LL_TIM_CHANNEL_CH3N                    TIM_CCER_CC3NE    /*!< Timer complementary output channel 3 */
#define LL_TIM_CHANNEL_CH4                     TIM_CCER_CC4E     /*!< Timer input/output channel 4 */
#define LL_TIM_CHANNEL_CH4N                    TIM_CCER_CC4NE     /*!< Timer complementary output channel 4 */
#define LL_TIM_CHANNEL_CH5                     TIM_CCER_CC5E     /*!< Timer output channel 5 */
#define LL_TIM_CHANNEL_CH6                     TIM_CCER_CC6E     /*!< Timer output channel 6 */
  * @}

Arduino-FOC/stm32_mcu.cpp at dev · Juanduino/Arduino-FOC (github.com)

If i not mistaken, these AFn definitions point to the datasheet (Alternative Function) ?

GPIO_InitStruct.Alternate = GPIO_AF2_TIM1;