Is it possible to have the DMA controller on an STM32 transfer each packet only when a timer update event occurs or can you only control the start of a whole chunk of DMA?
The use case is (on an STM32 with no HW DAC) to set the PWM duty cycle of TIMER1 from a block of sample data in memory at a specific timing interval. Currently I generate a CPU interrupt (using TIMER2) and manually stuff the PWM pulse value for TIMER1 in the ISR, which works, but I'd like to get the CPU out of there if I can.
I've looked at the reference manual and can't find a standard way to do this (which I may well have missed), but perhaps there's a sneaky method which would use another DMA channel to tweak the primary output DMA channel... or something...
Update: code added (which doesn't use DMA, just stuffs it in a timer ISR)
#define WAV
extern "C" {
#include "misc.h"
#include "GPIO_stm32f10x.h"
#include "stm32f10x_tim.h"
#include "TIM_ex.h"
#include "wav.h"
#include "stm32f10x_dma.h"
}
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
extern "C" void TIM2_IRQHandler()
{
static uint32_t i = 1;
static uint32_t j = 0;
// clear timer2 irq status
TIM2->SR = (uint16_t)~TIM_IT_Update;
// heartbeat toggle PORTA:0 every tick
GPIOA->BSRR = i;
i ^= 0x10001;
#ifdef WAV
TIM_SetChannel1Pulse(TIM1, wav[j]);
if(++j >= ARRAY_SIZE(wav))
{
j = 0;
}
#else
static uint32_t k = 0;
TIM1->CCR1 = sine[j & 0xff];
j += (sine[(k >> 6) & 0xff] >> 1) + 32;
k += 1;
#endif
}
int main()
{
// switch on some peripheral clocks
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // DMA1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIMER2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA +
RCC_APB2Periph_AFIO +
RCC_APB2Periph_TIM1, ENABLE); // PORTA, AFIO, TIMER1
// set PORTA:0 to output
GPIO_InitTypeDef a0Init;
a0Init.GPIO_Pin = GPIO_Pin_0;
a0Init.GPIO_Mode = GPIO_Mode_Out_PP;
a0Init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &a0Init);
// setup timer2 @ 8KHz
TIM_TimeBaseInitTypeDef t2Init;
t2Init.TIM_CounterMode = TIM_CounterMode_Up;
t2Init.TIM_Prescaler = 0;
t2Init.TIM_Period = 72000000 / 8000 - 1;
t2Init.TIM_ClockDivision = TIM_CKD_DIV1;
t2Init.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &t2Init);
// enable TIMER2 IRQs
NVIC_InitTypeDef nvicInit;
nvicInit.NVIC_IRQChannel = TIM2_IRQn;
nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
nvicInit.NVIC_IRQChannelSubPriority = 1;
nvicInit.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicInit);
// switch on TIMER2 update IRQs
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// setup timer1 for 7 bit PWM
TIM_TimeBaseInitTypeDef t1Init;
t1Init.TIM_Prescaler = 0;
t1Init.TIM_CounterMode = TIM_CounterMode_Up;
t1Init.TIM_Period = 256;
t1Init.TIM_ClockDivision = TIM_CKD_DIV1;
t1Init.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &t1Init);
// setup timer1 output channel for PWM
TIM_OCInitTypeDef t1_OCInit;
t1_OCInit.TIM_OCMode = TIM_OCMode_PWM2;
t1_OCInit.TIM_OutputState = TIM_OutputState_Enable;
t1_OCInit.TIM_OutputNState = TIM_OutputNState_Enable;
t1_OCInit.TIM_Pulse = 0;
t1_OCInit.TIM_OCPolarity = TIM_OCPolarity_Low;
t1_OCInit.TIM_OCNPolarity = TIM_OCNPolarity_Low;
t1_OCInit.TIM_OCIdleState = TIM_OCIdleState_Set;
t1_OCInit.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC1Init(TIM1, &t1_OCInit);
// switch timer1 to PWM mode
TIM_EnablePWMOutputs(TIM1);
// set PORTA:8 to alt. function output (ie timer1 PWM)
GPIO_InitTypeDef a8Init;
a8Init.GPIO_Pin = GPIO_Pin_8;
a8Init.GPIO_Mode = GPIO_Mode_AF_PP;
a8Init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &a8Init);
TIM_Cmd(TIM1, ENABLE);
TIM_Cmd(TIM2, ENABLE); // start timers
while(1)
{
}
}