Problem using DMA and a timer while CPU is in sleep mode
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?
