Update 2:
With suggestions from liaifat85, and also using BSRR (Bit Set Reset Register), I have updated the code below. I think using the BSRR is preferable in this scenario after reading this question. I have verified this works as intended by changing duty cycles and checking outputs with an oscilloscope.
#include "Arduino.h"
#include "stm32f4xx_hal.h"
// Pins to update
const uint16_t GPIOG_PINS[] = {GPIO_PIN_10, GPIO_PIN_9, GPIO_PIN_6, GPIO_PIN_5, GPIO_PIN_4, GPIO_PIN_3, GPIO_PIN_2}; // Outputs 1 to 7
const uint8_t NUM_PINS_G = sizeof(GPIOG_PINS) / sizeof(GPIOG_PINS[0]);
const uint16_t GPIOF_PINS[] = {GPIO_PIN_15, GPIO_PIN_14, GPIO_PIN_13, GPIO_PIN_12, GPIO_PIN_2, GPIO_PIN_1, GPIO_PIN_0}; // Outputs 8 to 14
const uint8_t NUM_PINS_F = sizeof(GPIOF_PINS) / sizeof(GPIOF_PINS[0]);
// DMA buffer for multiple pins
uint32_t pwmBufferG[100] = {0};
uint32_t pwmBufferF[100] = {0};
// Independent duty cycle tracking
uint8_t dutyCycles[14] = {0};
// Timer and DMA handles
TIM_HandleTypeDef htim8;
TIM_HandleTypeDef htim1;
// Setup PWM buffer
void updatePWMDutyCycle(uint8_t pinIndex, uint8_t dutyCycle)
{
if (pinIndex >= NUM_PINS_G + NUM_PINS_F)
return; // Ensure valid index
dutyCycles[pinIndex] = dutyCycle; // Store the new duty cycle [cite: 6]
for (int i = 0; i < 100; i++)
{
uint32_t setMask;
uint32_t resetMask;
if (pinIndex < NUM_PINS_G)
{
setMask = GPIOG_PINS[pinIndex];
resetMask = GPIOG_PINS[pinIndex] << 16; // BSRR reset value is the pin shifted left by 16
if (i < dutyCycle)
{
pwmBufferG[i] |= setMask; // Set pin high
pwmBufferG[i] &= ~resetMask;
}
else
{
pwmBufferG[i] |= resetMask; // Set pin low
pwmBufferG[i] &= ~setMask;
}
}
else
{
setMask = GPIOF_PINS[pinIndex - NUM_PINS_G];
resetMask = GPIOF_PINS[pinIndex - NUM_PINS_G] << 16; // BSRR reset value is the pin shifted left by 16
if (i < dutyCycle)
{
pwmBufferF[i] |= setMask; // Set pin high
pwmBufferF[i] &= ~resetMask;
}
else
{
pwmBufferF[i] |= resetMask; // Set pin low
pwmBufferF[i] &= ~setMask;
}
}
}
}
void configureDMA()
{
__HAL_RCC_DMA2_CLK_ENABLE();
// First DMA (Stream 1, Channel 7)
static DMA_HandleTypeDef hdma;
hdma.Instance = DMA2_Stream1;
hdma.Init.Channel = DMA_CHANNEL_7;
hdma.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma.Init.PeriphInc = DMA_PINC_DISABLE;
hdma.Init.MemInc = DMA_MINC_ENABLE;
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma.Init.Mode = DMA_CIRCULAR;
hdma.Init.Priority = DMA_PRIORITY_HIGH;
hdma.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma);
htim8.hdma[TIM_DMA_ID_UPDATE] = &hdma;
HAL_DMA_Start(&hdma, (uint32_t)pwmBufferG, (uint32_t)&GPIOG->BSRR, 100);
// Second DMA (Stream 5, Channel 6)
static DMA_HandleTypeDef hdma_tim1_up;
hdma_tim1_up.Instance = DMA2_Stream5;
hdma_tim1_up.Init.Channel = DMA_CHANNEL_6;
hdma_tim1_up.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim1_up.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim1_up.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim1_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim1_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim1_up.Init.Mode = DMA_CIRCULAR;
hdma_tim1_up.Init.Priority = DMA_PRIORITY_HIGH;
hdma_tim1_up.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_tim1_up);
htim1.hdma[TIM_DMA_ID_UPDATE] = &hdma;
HAL_DMA_Start(&hdma_tim1_up, (uint32_t)pwmBufferF, (uint32_t)&GPIOF->BSRR, 100);
}
void configureTimer()
{
__HAL_RCC_TIM8_CLK_ENABLE();
htim8.Instance = TIM8;
htim8.Init.Prescaler = 84 - 1; // 84MHz / 84 = 1MHz
htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
htim8.Init.Period = 100 - 1; // 1MHz / 100 = 10kHz
htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim8);
HAL_TIM_Base_Start(&htim8);
__HAL_TIM_ENABLE_DMA(&htim8, TIM_DMA_UPDATE);
__HAL_RCC_TIM1_CLK_ENABLE();
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84 - 1; // 84MHz / 84 = 1MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 100 - 1; // 1MHz / 100 = 10kHz
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim1);
HAL_TIM_Base_Start(&htim1);
__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_UPDATE);
}
void setupGPIO()
{
__HAL_RCC_GPIOG_CLK_ENABLE();
GPIO_InitTypeDef GPIOG_InitStruct = {0};
GPIOG_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_9 | GPIO_PIN_6 | GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2;
GPIOG_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIOG_InitStruct.Pull = GPIO_NOPULL;
GPIOG_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOG, &GPIOG_InitStruct);
__HAL_RCC_GPIOF_CLK_ENABLE();
GPIO_InitTypeDef GPIOF_InitStruct = {0};
GPIOF_InitStruct.Pin = GPIO_PIN_15 | GPIO_PIN_14 | GPIO_PIN_13 | GPIO_PIN_12 | GPIO_PIN_2 | GPIO_PIN_1 | GPIO_PIN_0;
GPIOF_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIOF_InitStruct.Pull = GPIO_NOPULL;
GPIOF_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOF, &GPIOF_InitStruct);
}
void setup()
{
// Initialize serial for debugging (optional)
Serial.begin(115200);
setupGPIO();
for (int i = 0; i < NUM_PINS_G + NUM_PINS_F; i++)
{
updatePWMDutyCycle(i, 50);
}
configureDMA();
configureTimer();
}
void loop()
{
}