2025-07-14 4:52 AM
I can't get TIM1 to trigger DMA in my code (main.c)
Am I missing something?
Solved! Go to Solution.
2025-07-24 7:00 AM
Thank you very much for your remark, you got me thinking.
I'm going to drive the WS2812B lamps with multiple parallel alternate outputs. One alternate output was already working.
What surprises me is that ST didn't make any effort to support my question.
2025-07-14 5:51 AM
Hello @TEl B
Did the DMA transfer work without TIM triggering?
2025-07-14 5:53 AM
Yes
2025-07-20 12:54 AM
Problem is not solved.
2025-07-20 5:36 AM
Transfer direction looks backwards. Memory is the source, would anticipate GPIO bank is not treated as a peripheral, but considered a M2M transaction.
Widths look wrong, BSRR is 32-bit
Check for error status on DMA
Make a simpler test example. Driving a pattern to GPIO via TIM Update.
2025-07-22 1:10 AM
Copying array to array through Triggered DMA from timer is no problem, it's working
I get continu transfer error when i write to port GPIOB through DMA
#include "main.h"
#define BUFFER_SIZE 32
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static uint32_t aSRC_Const_Buffer[BUFFER_SIZE] =
{
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1)),
(1 << 1), (1 << (16 + 1)), (1 << 1), (1 << (16 + 1))
};
static uint32_t aDST_Buffer[BUFFER_SIZE];
volatile uint8_t ctr = 0;
void TransferComplete();
void TransferError();
void ctl_handle_error(CTL_ERROR_CODE_t e)
{
while (1);
e++; //dummy remove warning
}
//*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
/* simple delay counter to waste time, don't rely on for accurate timing */
void Delay(__IO uint32_t nCount) {
while(nCount--) {
}
}
void SystemClock_Config(void) {
// 1. Enable HSI (internal 16 MHz oscillator)
LL_RCC_HSI_Enable();
while (!LL_RCC_HSI_IsReady());
// 2. Set FLASH latency for 64 MHz
LL_FLASH_SetLatency(LL_FLASH_LATENCY_1);
// 3. Configure PLL: input = HSI/1 = 16 MHz, multiply by 8, divide by 2 → 64 MHz
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSI, LL_RCC_PLLM_DIV_1, 8, LL_RCC_PLLR_DIV_2);
// 4. Enable PLL and wait until it's ready
LL_RCC_PLL_Enable();
LL_RCC_PLL_EnableDomain_SYS(); // <-- This is necessary!
while (!LL_RCC_PLL_IsReady());
// 5. Set AHB and APB1 prescalers
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
// 6. Set PLL as system clock source
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
while (LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL);
// 7. Update system core clock variable
SystemCoreClock = 64000000;
}
void GPIO_init(void)
{
// Enable GPIOB clock
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
// Configure PB0, PB1, PB2, PB3 as general purpose output (push-pull, no pull-up, low speed)
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_0, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_1, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_2, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_3, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_0, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_1, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_2, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_3, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_0, LL_GPIO_PULL_NO);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_1, LL_GPIO_PULL_NO);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_2, LL_GPIO_PULL_NO);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_3, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_0, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_1, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_2, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_3, LL_GPIO_SPEED_FREQ_LOW);
// Set all pins low initially
LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_0 | LL_GPIO_PIN_1 | LL_GPIO_PIN_2 | LL_GPIO_PIN_3);
}
void TIM1_init(void)
{
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM1);
LL_TIM_SetPrescaler(TIM1, 63); // 64 MHz / (63+1) = 1 MHz
LL_TIM_SetAutoReload(TIM1, 100); // overflow elke 100 µs
LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP);
LL_TIM_EnableARRPreload(TIM1);
// CH1 configureren als output compare (om DMA trigger te genereren)
LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FROZEN);
LL_TIM_OC_SetCompareCH1(TIM1, 10);
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1);
LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1);
// Enable DMA op TIM1_CH1
LL_TIM_EnableDMAReq_CC1(TIM1);
// Cruciaal: update event genereren zodat registers laden
LL_TIM_GenerateEvent_UPDATE(TIM1);
LL_TIM_EnableCounter(TIM1);
}
void DMA_init(void)
{
LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_2, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PRIORITY_HIGH);
LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MODE_NORMAL);
LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PDATAALIGN_WORD);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MDATAALIGN_WORD);
// Zet de peripheral request op TIM1_CH1
LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_2, LL_DMAMUX_REQ_TIM1_CH1);
// Adressen en lengte
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_2,
(uint32_t)&aSRC_Const_Buffer,
(uint32_t)&(GPIOB->ODR),
LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, BUFFER_SIZE);
// Interrupts
LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_2);
LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_2);
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
}
void DMA1_Channel2_3_IRQHandler(void)
{
if(LL_DMA_IsActiveFlag_TC2(DMA1) == 1)
{
LL_DMA_ClearFlag_TC2(DMA1); // Correct flag clear
TransferComplete();
}
else if(LL_DMA_IsActiveFlag_TE2(DMA1) == 1)
{
LL_DMA_ClearFlag_TE2(DMA1);
TransferError();
}
}
void DMA_test_start(void)
{
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
while (LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_2));
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, BUFFER_SIZE);
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_2,
(uint32_t)&aSRC_Const_Buffer,
(uint32_t)&(GPIOB->ODR), // :white_heavy_check_mark: juiste peripheral-adres
LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
}
uint8_t Buffercmp(uint32_t* pBuffer1, uint32_t* pBuffer2, uint32_t BufferLength)
{
while (BufferLength--)
{
if (*pBuffer1 != *pBuffer2)
{
return 1;
}
pBuffer1++;
pBuffer2++;
}
return 0;
}
void TransferComplete()
{
/* DMA transfer completed */
/* Verify the data transferred */
LL_GPIO_TogglePin(GPIOB, LL_GPIO_PIN_0);
ctr = 1;
}
/**
* @brief DMA transfer error callback
* @note This function is executed when the transfer error interrupt
* is generated during DMA transfer
* @retval None
*/
void TransferError()
{
/* Error detected during DMA transfer */
LL_GPIO_TogglePin(GPIOB, LL_GPIO_PIN_0);
}
int main(void)
{
SystemClock_Config();
GPIO_init();
DMA_init();
TIM1_init();
Delay(100000);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2); // DMA inschakelen, TIM1 triggert
while (1) {
if (ctr) {
ctr = 0;
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
while (LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_2));
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, BUFFER_SIZE);
LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_2,
(uint32_t)&aSRC_Const_Buffer,
(uint32_t)&(GPIOB->ODR),
LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
}
}
}
2025-07-22 1:37 AM
In 'G0, GPIO are connected to the cpu-private IOPORT bus, not accessible from the rest of the chip, i.e. DMA cannot access GPIO in 'G0.
JW
2025-07-22 3:20 AM
Thank you very much.
Writing to BSRR or BRR register is also impossible?
2025-07-22 4:57 AM
DMA on GPIO must be possible following datasheet of the controller
2025-07-22 5:14 AM - edited 2025-07-22 5:17 AM
No that path is not possible.
That diagram is simplified in the fact, that the matrix consists of masters and slaves. Only master can initiate a transfer, providing address and direction of transfer and actively produce/consume data; and only slave can passively "consume" or "produce" the data of that transfer according to the address and direction provided by master. In other words, while the double-ended arrows do indicate the bi-directional flow of data, they don't correctly indicate the unidirectinal flow of address and control.
JW