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)
{
}
}
Best Answer
Used variables:
Algorithm
Initialize your DAC.
Initialize DMA:
&waf + offset
((offset + CHUNK_SIZE) < ARRAY_SIZE(wav)) ? CHUNK_SIZE : (ARRAY_SIZE(wav) - offset )
offset
for next useInitialize your timer:
Start timer
When DMA TC IRQ will occur
offset == ARRAY_SIZE(wav)
then stop timer - playing finishedThat's all! You must be sure that reinitialize of DMA faster then timer period end! Otherwise pause timer by gate timer's clock while reinit process. If DMA can't read from flash memory use buffer in ram and copy wav by parts with memcpy before each chunck playing (before DMA initialize process).
Sorry for grammatical errors.