cancel
Showing results for 
Search instead for 
Did you mean: 

Problem using DMA and a timer while CPU is in sleep mode

neoxic
Associate II

I'm completely baffled as to how to make DMA and sleep mode work together on an STM32F0. I presume though that the issue is not family specific. It appears when the CPU enters sleep mode via the WFI instruction, DMA stops working. If a busy loop is used instead of WFI, everything works as it should.

The following is a piece of code that illustrates the problem. It uses USART1_TX on pin B6 for logging. TIM1_CH1 output is on pin A8.

#include <stdio.h>
#include <errno.h>
#include <libopencm3/cm3/scb.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/timer.h>
#include <libopencm3/stm32/dma.h>
#include <libopencm3/stm32/usart.h>
 
int _write(int fd, char *buf, int len);
int _write(int fd, char *buf, int len) { // STDOUT -> USART1_TX (blocking)
	if (fd != 1) {
		errno = EIO;
		return -1;
	}
	for (int i = 0; i < len; ++i) usart_send_blocking(USART1, buf[i]);
	return len;
}
 
void tim1_brk_up_trg_com_isr(void) {
	TIM1_SR = ~TIM_SR_UIF;
	// Dummy interrupt that wakes up the CPU
}
 
void main(void) {
	rcc_clock_setup_in_hsi_out_48mhz();
 
	RCC_AHBENR = RCC_AHBENR_DMAEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN;
	RCC_APB2ENR = RCC_APB2ENR_SYSCFGCOMPEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_USART1EN;
 
	GPIOA_AFRH  |= 0x00000002; // A8 (TIM1_CH1)
	GPIOA_MODER |= 0x00020000; // A8 (TIM1_CH1)
	GPIOB_MODER |= 0x00002000; // B6 (USART1_TX)
 
	nvic_enable_irq(NVIC_TIM1_BRK_UP_TRG_COM_IRQ);
 
	TIM1_PSC = 299;
	TIM1_ARR = 9999;
	TIM1_EGR = TIM_EGR_UG;
	TIM1_CR1 = TIM_CR1_CEN;
	TIM1_BDTR = TIM_BDTR_MOE;
	TIM1_CCMR1 = TIM_CCMR1_OC1PE | TIM_CCMR1_OC1M_PWM1;
	TIM1_CCER = TIM_CCER_CC1E;
	TIM1_DIER = TIM_DIER_UIE | TIM_DIER_UDE;
 
	int buf[8] = {1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888};
	DMA1_CPAR(5) = (uint32_t)&TIM1_CCR1;
	DMA1_CMAR(5) = (uint32_t)&buf;
	DMA1_CNDTR(5) = 8;
	DMA1_CCR(5) = DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_PSIZE_32BIT | DMA_CCR_MSIZE_32BIT;
 
	usart_set_baudrate(USART1, 115200);
	usart_set_mode(USART1, USART_MODE_TX);
	usart_enable(USART1);
 
	for (;;) {
		__asm__("wfi");
		// while (TIM1_CNT); // <------ all works if this is used instead of the "wfi" above
		printf("%d\n", (int)TIM1_CCR1);
	}
}

The code in a nutshell:

  • TIM1 is free running.
  • DMA channel 5 is configured to constantly update TIM_CCR1 from a circular buffer.
  • The CPU is in sleep mode.
  • A DMA transaction is triggered when TIM1 overflows.
  • The CPU wakes up via an update interrupt, prints the contents of TIM_CCR1 and goes back to sleep.

Unexpected output (TIM_CCR1 is not updated):

0
0
0
0
0
0
...

If I comment out the WFI instruction and uncomment the busy waiting, I get the expected results (TIM_CCR1 is updated each time):

1111
2222
3333
4444
5555
6666
7777
8888
1111
2222
3333
4444
5555
6666
7777
8888
...

So basically, I don't understand how I can configure a DMA channel to transfer data to/from a timer while the CPU is in sleep mode. What am I missing?

1 ACCEPTED SOLUTION

Accepted Solutions
neoxic
Associate II

Answering my own question. The problem with DMA not working in Sleep mode stems from the fact that DMA needs SRAM and possibly FLASH clocked in Sleep mode. Hence SRAMEN and possibly FLITF in RCC_AHBENR must be set. Both bits are set by default after a reset, but in my code, I simply forgot to set them explicitly in an assignment.

So this problem is NOT chip specific, and different results observed are solely due to subtle differences in run/sleep/run transitions and corner conditions of SRAM clocking between them. Overall, the code has the aforementioned bug that manifests itself as "glitching" DMA.

View solution in original post

3 REPLIES 3

Interesting.

Well, maybe 'F0 the DMA trigger transfer mechanism is clocked by the CPU clock. I am only speculating here, of course.

Try perhaps waking up, instead of by Update interrupt, by interrupt from another channel set to compare close to ARR, so that the processor clock is already running at the moment of timer overflow (update). Did it make any difference?

JW

neoxic
Associate II

Well, I tried different ways to wake up. And it seems waking up is not the root of the problem. The correct way to wake up would be by (Half) Transfer Complete interrupt from the DMA channel. And it works! It wakes up the CPU. But the core of the problem is that it looks like no data is transferred while the CPU is in sleep.

neoxic
Associate II

Answering my own question. The problem with DMA not working in Sleep mode stems from the fact that DMA needs SRAM and possibly FLASH clocked in Sleep mode. Hence SRAMEN and possibly FLITF in RCC_AHBENR must be set. Both bits are set by default after a reset, but in my code, I simply forgot to set them explicitly in an assignment.

So this problem is NOT chip specific, and different results observed are solely due to subtle differences in run/sleep/run transitions and corner conditions of SRAM clocking between them. Overall, the code has the aforementioned bug that manifests itself as "glitching" DMA.