¿Se puede controlar la velocidad del STM32 DMA con el temporizador?

¿Es posible hacer que el controlador DMA en un STM32 transfiera cada paquete solo cuando ocurre un evento de actualización del temporizador o solo puede controlar el inicio de una parte completa de DMA?

El caso de uso es (en un STM32 sin HW DAC) establecer el ciclo de trabajo PWM de TIMER1 desde un bloque de datos de muestra en la memoria en un intervalo de tiempo específico. Actualmente genero una interrupción de CPU (usando TIMER2) y relleno manualmente el valor de pulso PWM para TIMER1 en el ISR, lo cual funciona, pero me gustaría sacar la CPU de allí si puedo.

He mirado el manual de referencia y no puedo encontrar una forma estándar de hacer esto (que bien podría haber pasado por alto), pero tal vez haya un método engañoso que usaría otro canal DMA para modificar el canal DMA de salida principal... o algo...

Actualización: código agregado (que no usa DMA, solo lo mete en un temporizador 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)
    {
    }
}
Esta pregunta se puede responder completamente con solo mirar el documento del manual de programación.
Creo que estás viendo esto de manera equivocada. Sin duda, debería ser posible activar el DMA desde un temporizador, pero lo que desea hacer es configurar el módulo DMA para que solo transfiera los datos de una sola muestra para cada activador, mientras continúa incrementando todo el bloque de datos antes de envolver. Este tipo de comportamiento ciertamente ha sido posible con los módulos DMA en MCU que he usado en el pasado; el manual para el suyo también debería indicar si es posible.
@brhans: absolutamente, eso es lo que me gustaría hacer, pero no puedo encontrar la manera de hacerlo desde el manual de referencia. Las direcciones de transferencia se sombrean y se recargan desde los registros 'establecidos' cada vez que se inicia una transferencia, por lo que si configuro un tamaño de DMA de 1 byte y lo activo repetidamente con el temporizador, sigue enviando el mismo byte, de alguna manera necesito obtener el incrementos de dirección para llevar a la próxima transferencia.
Realmente deberías agregar el código que estás intentando. No debe tener ningún código en el temporizador, la interrupción solo activará la transferencia de celdas. Solo configura la dirección de origen, la dirección de destino, etc. en el bucle principal en algún lugar, luego deja que pase automáticamente por todo el fragmento de memoria.
justing, código agregado. No veo cómo hacer que el DMA transfiera los datos a una determinada velocidad (es decir, más lento que afap)

Respuestas (1)

Variables utilizadas:

#define CHUNK_SIZE 255
// uint8_t buffer[CHUNK_SIZE]; optional!!!
uint16_t offset = 0;

Algoritmo

Inicialice su DAC.

Inicializar DMA:

  1. configure el flujo/canal requerido para el DMA seleccionado según el evento de actualización del temporizador seleccionado
  2. establecer la dirección de datos DMA en&waf + offset
  3. establecer la dirección del periférico DMA en DAC_DATA_REGISTER
  4. establecer la longitud de DMA en((offset + CHUNK_SIZE) < ARRAY_SIZE(wav)) ? CHUNK_SIZE : (ARRAY_SIZE(wav) - offset )
  5. agregue la longitud DMA calculada offsetpara el próximo uso
  6. habilitar solo IRQ completo total

Inicializa tu temporizador:

  1. configurar la frecuencia requerida
  2. no habilite ninguna IRQ de temporizador
  3. habilitar la activación de DMA por evento de actualización del temporizador

Temporizador de inicio

Cuándo ocurrirá DMA TC IRQ

  1. si offset == ARRAY_SIZE(wav)entonces detener el temporizador - reproducción terminada
  2. de lo contrario, reinicialice DMA con un nuevo desplazamiento

¡Eso es todo! ¡Debe asegurarse de reiniciar el DMA más rápido que el final del período del temporizador! De lo contrario, pause el temporizador junto al reloj del temporizador de la puerta mientras reinicia el proceso. Si DMA no puede leer desde la memoria flash, use el búfer en RAM y copie wav por partes con memcpy antes de reproducir cada fragmento (antes del proceso de inicialización de DMA).

Lo siento por los errores gramaticales.

Entonces, ¿con qué frecuencia el DMA transferirá cada paquete?
Estoy interesado en el paso 1: ¿cambia la velocidad a la que se entregan los paquetes DMA? No lo pensé al mirar la hoja de datos, pero podría haberme perdido algo
DMA activado por temporizador y actualización de datos DAC registra cada evento de activación, DAC simplemente establece un nuevo nivel de voltaje de acuerdo con su registro de datos. Entonces, la frecuencia del temporizador: es la frecuencia de transferencia de bytes DMA y es la frecuencia de salida de DAC.