Kann die STM32-DMA-Geschwindigkeit vom Timer gesteuert werden?

Ist es möglich, dass der DMA-Controller auf einem STM32 jedes Paket nur dann überträgt, wenn ein Timer-Aktualisierungsereignis auftritt, oder können Sie nur den Start eines ganzen DMA-Blocks steuern?

Der Anwendungsfall besteht darin (auf einem STM32 ohne HW-DAC), das PWM-Tastverhältnis von TIMER1 aus einem Block von Beispieldaten im Speicher in einem bestimmten Zeitintervall einzustellen. Derzeit generiere ich einen CPU-Interrupt (mit TIMER2) und fülle den PWM-Impulswert für TIMER1 manuell in die ISR, was funktioniert, aber ich möchte die CPU dort rausholen, wenn ich kann.

Ich habe im Referenzhandbuch nachgesehen und kann keine Standardmethode dafür finden (was ich möglicherweise übersehen habe), aber vielleicht gibt es eine hinterhältige Methode, die einen anderen DMA-Kanal verwendet, um den primären DMA-Ausgangskanal zu optimieren ... oder so...

Update: Code hinzugefügt (der kein DMA verwendet, sondern nur in eine Timer-ISR stopft)

#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)
    {
    }
}
Diese Frage kann vollständig beantwortet werden, indem man sich nur das Dokument des Programmierhandbuchs ansieht.
Ich glaube, du siehst das falsch. Das Auslösen des DMA von einem Timer sollte sicherlich möglich sein, aber was Sie tun möchten, ist das DMA-Modul so zu konfigurieren, dass es nur die Daten eines einzelnen Samples für jeden Trigger überträgt, während Sie dennoch Ihren gesamten Datenblock vor dem Wrapping inkrementieren. Diese Art von Verhalten war sicherlich mit den DMA-Modulen in MCUs möglich, die ich in der Vergangenheit verwendet habe - das Handbuch für Ihr sollte sagen, ob es auch möglich ist.
@brhans - absolut, das würde ich gerne tun, aber ich kann aus dem Referenzhandbuch keinen Weg finden, dies zu tun. Die Übertragungsadressen werden jedes Mal, wenn eine Übertragung gestartet wird, schattiert und aus den 'Set'-Registern neu geladen. Wenn ich also eine DMA-Größe von 1 Byte einstelle und sie wiederholt mit dem Timer auslöse, sendet sie weiterhin dasselbe Byte, muss ich das irgendwie bekommen Adressinkremente zum Übertragen auf die nächste Übertragung.
Sie sollten wirklich den Code hinzufügen, den Sie versuchen. Sie sollten keinen Code im Timer haben, der Interrupt löst nur die Übertragung der Zellen aus. Sie richten nur die Quelladresse, Zieladresse usw. irgendwo in der Hauptschleife ein und lassen sie dann automatisch den gesamten Speicherblock durchlaufen.
Justierung, Code hinzugefügt. Ich sehe nicht, wie ich den DMA dazu bringen kann, die Daten mit einer bestimmten Rate zu übertragen (dh langsamer als afap).

Antworten (1)

Verwendete Variablen:

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

Algorithmus

Initialisieren Sie Ihren DAC.

DMA initialisieren:

  1. Konfigurieren des erforderlichen Stroms/Kanals für den ausgewählten DMA gemäß dem Aktualisierungsereignis des ausgewählten Zeitgebers
  2. DMA-Datenadresse einstellen auf&waf + offset
  3. Setzen Sie die DMA-Peripherieadresse auf DAC_DATA_REGISTER
  4. Stellen Sie die DMA-Länge auf ein((offset + CHUNK_SIZE) < ARRAY_SIZE(wav)) ? CHUNK_SIZE : (ARRAY_SIZE(wav) - offset )
  5. offsetberechnete DMA-Länge für die nächste Verwendung hinzufügen
  6. Aktivieren Sie nur den gesamten vollständigen IRQ

Initialisieren Sie Ihren Timer:

  1. Erforderliche Frequenz einrichten
  2. Aktivieren Sie keinen IRQ eines Timers
  3. DMA-Triggerung durch Timer-Aktualisierungsereignis aktivieren

Timer starten

Wenn DMA TC IRQ auftritt

  1. Wenn offset == ARRAY_SIZE(wav)dann Timer stoppen - Wiedergabe beendet
  2. andernfalls DMA mit neuem Offset neu initialisieren

Das ist alles! Sie müssen sicher sein, dass die Neuinitialisierung von DMA schneller erfolgt, als die Timer-Periode endet! Anderenfalls den Timer durch die Uhr des Gate-Timers anhalten, während der Prozess neu initialisiert wird. Wenn DMA nicht aus dem Flash-Speicher lesen kann, verwenden Sie den Puffer im RAM und kopieren Sie wav stückweise mit memcpy, bevor Sie jeden Chunck abspielen (bevor der DMA-Prozess initialisiert wird).

Entschuldigung für Grammatikfehler.

Wie oft wird also jedes Paket vom DMA übertragen?
Ich interessiere mich für Schritt 1 – ändert er die Rate, mit der die DMA-Pakete geliefert werden? Ich dachte nicht, dass ich mir das Datenblatt angesehen habe, aber ich könnte etwas übersehen haben
DMA wird durch Timer ausgelöst und aktualisiert das DAC-Datenregister bei jedem Triggerereignis, der DAC stellt nur einen neuen Spannungspegel entsprechend dem Datenregister ein. Also Timer-Frequenz – es ist die Frequenz der DMA-Byte-Übertragung und es ist die Ausgangsfrequenz des DAC.