DMA interrupt with Arduino IDE?

Post here first, or if you can't find a relevant section!
Post Reply
dummibaer
Posts: 4
Joined: Fri Jun 19, 2020 8:32 am

DMA interrupt with Arduino IDE?

Post by dummibaer »

Hello everyone,

I would like to ask, if there is by chance a way to create an interrupt service routine which triggers on the DMA in the Arduino IDE. If yes, how do i do this?

The background: I am using a bluepill board (stm32f103c8t6). The plan is to implement an ADC read for a bunch of conversions in circular mode. This converted values are transfered into RAM (incremented memory address) by the DMA. As soon as the DMA sets the transfer complete flag I would like to disable timer 4 which is triggering the ADC and finish the whole conversion and DMA transfer automatically. This is supposed to be done through an interrupt service routine.
Until now I use in the code bit manipulation and native Arduino functions only and don't have any libraries included.

As far as I know there is the possibility to use Timerx.attachInterrupt() as intterupt handler for timers. Is there by chance something similar for DMA? Personaly, I doubt it because there isn't any DMA support by the Arduino IDE (I had to access the DMA registers via hardware addresses). But better ask than guess...
Or can I implement an interrupt service routine similar to the procedure on Arduino UNO with ISR(Interrupt_vect)? How would be the the name of the interrupt vector if this is possible?
In the register datasheet I found in the chapter about interrupts the table with the interrupt vectors and their memory address but I can't get an ISR to function with interrupt "DMA_1Channel1".
Or maybe you have a better idea how an automated not periodical occouring ADC conversion with DMA can be realized?

I would be very glad and thankful if you can fill me in how I can reslize an interrupt on internal vectors in Arduino IDE.

Regards,
Sven

Attached: latest code

Code: Select all

int x = 0;
int y = 0;
int z = 0;

boolean a = LOW;
boolean b = LOW;
boolean c = LOW;

uint16_t adcoutput[3696] = {0};

// DMA1 pointers on register memory (register not recognized by arduino IDE)

uint32_t *DMA1_ISR = (uint32_t *) 0x40020000;
uint32_t *DMA1_IFCR = (uint32_t *) 0x40020004;
uint32_t *DMA1_CCR1 = (uint32_t *) 0x40020008;
uint32_t *DMA1_CNDTR1 = (uint32_t *) 0x4002000C;
uint32_t *DMA1_CPAR1 = (uint32_t *) 0x40020010;
uint32_t *DMA1_CMAR1 = (uint32_t *) 0x40020014;

uint8_t *NVIC_DMAch1 = (uint8_t *) 0x6C; //memory of NVIC DMA1_Channel1 global interrupt

}


void setup() {

  pinMode(PC13, OUTPUT);
  digitalWrite(PC13, LOW);

  RCC_BASE->APB2ENR |= (1 << 2); //enable IO clock output port a - IOPAEN=1
  RCC_BASE->APB2ENR |= (1 << 3); //enable IO clock output port b - IOPBEN=1
  RCC_BASE->APB2ENR |= (1 << 9); //enable ADC1 clock - ADC1EN=1
  RCC_BASE->APB1ENR |= (1 << 2); //enable timer 4 clock - TIM4EN=1
  RCC_BASE->AHBENR |= (1 << 0); //enable DMA1 clock - DMA1EN=1

  // TIMER 4 CH4 ---------------------------------------------------------

  GPIOB_BASE->CRH |= (1 << 4) | (1 << 5); //for PB9 select output mode, 50MHz - MODEy=11
  GPIOB_BASE->CRH |= (1 << 7); //select alternate push-pull mode CNFy=10
  GPIOB_BASE->CRH &= ~(1 << 6); //select alternate push-pull mode CNFy=10

  TIMER4_BASE->ARR = 71; //frequency selection -> 36MHz/(ARR+1)=0,5MHz
  TIMER4_BASE->CCR4 = 1; //duty cycle selection -> 0%
  TIMER4_BASE->CNT = 0;

  TIMER4_BASE->CCMR2 |= (1 << 14) | (1 << 13); //select PWM mode 1 - OCxM=110
  TIMER4_BASE->CCMR2 |= (1 << 11); //enable preload - OCxPE=1
  TIMER4_BASE->CR1 |= (1 << 7); //enable auto-reload preload - ARPE=1
  TIMER4_BASE->CCER |= (1 << 12); //enable output at OCx - CCxE=1
  TIMER4_BASE->DIER |= (1 << 4); //enable CC4 interrupt - CC4IE=1

  // ADC1 ---------------------------------------------------------------

  ADC1_BASE->SQR3 |= (1 << 0) | (1 << 1) | (1 << 2); //select PA7 as input
  ADC1_BASE->CR2 |= (1 << 8); //enable DMA request - DMA=1
  ADC1_BASE->CR2 |= (1 << 1); //continous conversion mode - CONT=1
  ADC1_BASE->CR2 |= (1 << 20); //enable external trigger - EXTTRIG=1
  ADC1_BASE->CR2 |= (1 << 19) | (1 << 17); //select timer 4 CC4 event as trigger - EXTSEL=101

  // DMA CH1 - ADC1 -------------------------------------------------

  *DMA1_CPAR1 = (int)1073816652; //0x40012400 + 0x0000004C; //ADC1_BASE->DR; //peripheral address
  *DMA1_CMAR1 = (int)adcoutput; //memory address
  *DMA1_CNDTR1 = (int)3696; //data number to be transferred

  *DMA1_CCR1 |= (1 << 12) | (1 << 13); //select very high priority - PL=11
  *DMA1_CCR1 |= (1 << 10); //select memory size 16 bit - MSIZE=01
  *DMA1_CCR1 |= (1 << 8); //select peripheral size 16 bit - PSIZE=01
  *DMA1_CCR1 |= (1 << 7); //enable memory increment mode - MINC=1
  *DMA1_CCR1 |= (1 << 1); //enable transfer complete interrupt TCIE=1
  *DMA1_CCR1 |= (1 << 5); //enable circular mode - CIRC=1

  // ----------------------------------------------

  TIMER4_BASE->CR1 |= (1 << 0); //enable counter 4 - CEN=1

  ADC1_BASE->CR2 |= (1 << 0); //wake up ADC - ADON=1

  *DMA1_IFCR |= 0x00000002; //clears transfer complete flag
  *DMA1_CCR1 |= (1 << 0); //DMA1 enable - EN=1

  Serial.begin(115200);
}

void loop() {

  // test ADC conversion ----------------------------------------

  //  if(ADC1_BASE->SR & 0x0002){ //triggered on EOC flag
  //    Serial.println("ADC");
  //    ADC1_BASE->SR &= ~(1 << 1); //clear EOC flag
  //  }
  //  Serial.print("ADC1_BASE->DR: ");
  //  Serial.println(ADC1_BASE->DR);

  // test DMA ---------------------------------------------------
//    if (*DMA1_ISR & 0x00000002) { //triggered on TCIFx flag
//      Serial.println("DMA");
//      Serial.print("DMA1_ISR: ");
//      Serial.println(*DMA1_ISR);
//      *DMA1_CCR1 &= ~(1 << 0); //DMA1 disable - EN=0
//      *DMA1_IFCR |= 0x00000002; //clears transfer complete flag
//      delay(500);
//      for (x = 0; x < 3696; x++) {
//        Serial.print(adcoutput[x]);
//        Serial.println(";");
//      }
//      Serial.print("DMA1_ISR: ");
//      Serial.println(*DMA1_ISR);
//      delay(500);
//      *DMA1_CCR1 |= (1 << 0); //DMA1 enable - EN=0
//    }


Serial.println(*NVIC_DMAch1, HEX);
}
stevestrong
Posts: 502
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: DMA interrupt with Arduino IDE?

Post by stevestrong »

Have a look on these examples.
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: DMA interrupt with Arduino IDE?

Post by ag123 »

for the official core there is some discussions here
viewtopic.php?f=41&t=110
viewtopic.php?f=62&t=374
dummibaer
Posts: 4
Joined: Fri Jun 19, 2020 8:32 am

Re: DMA interrupt with Arduino IDE?

Post by dummibaer »

Thank you very much for your replies!

The STM32ADC library and especially the example "SingleChannelAtSampleRateCircularBuffer" did the trick.
It covers my requirements except for the need to use timer 4. In the example timer 3 is used as the trigger for the ADC.

Do you know, if there is any manual or reference for this library that describes which functions are available and which syntax has to be used? I did some search but cannot find more information about this library than the git page of roger clark.

I tried to change the example and configure timer 4 as trigger input for the ADC. Setting up the timer was not the problem. But if I try to change the input of the function myADC.setTrigger() to "ADC_EXT_EV_TIM4_TRGO", assuming this is the right syntax, I get an error message "'ADC_EXT_EV_TIM4_TRGO' was not declared in this scope".
I am guessing this has something to do with the library and the defined inputs for this function, because I cannot find any declaration of "ADC_EXT_EV_TIM3_TRGO" in the code.

Code: Select all

// Timer3.setPeriod(samplePeriodus);
  // Timer3.setMasterModeTrGo(TIMER_CR2_MMS_UPDATE);

  Timer4.setPeriod(samplePeriodus);
  Timer4.setMasterModeTrGo(TIMER_CR2_MMS_UPDATE);

  myADC.calibrate();
  myADC.setSampleRate(ADC_SMPR_1_5); // ?
  myADC.setPins(&pins, 1);
  myADC.setDMA(buffer, maxSamples, (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_HALF_TRNS | DMA_TRNS_CMPLT), DmaIRQ);
  // myADC.setTrigger(ADC_EXT_EV_TIM3_TRGO);
  myADC.setTrigger(ADC_EXT_EV_TIM4_TRGO);
  myADC.startConversion();
  
Do you have a suggestion what could be the problem here and how I can solve it? Or do you know what the available inputs for the myADC.setTrigger() function are?
I only found the use of ADC_EXT_EV_SWSTART as the other example for an input in the code of SingleChannelSingleConversion.
stevestrong
Posts: 502
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: DMA interrupt with Arduino IDE?

Post by stevestrong »

Unfortunately there is no documentation on this lib, no one had time to write it.
You can only use the defines liested here: https://github.com/rogerclarkmelbourne/ ... #L148-L181
The DMA cannot be triggered by Timer4 update event, that is why that define does not exist. But you could use Timer4 channel 4 compare output signal as trigger: ADC_EXT_EV_TIM4_CC4.
dummibaer
Posts: 4
Joined: Fri Jun 19, 2020 8:32 am

Re: DMA interrupt with Arduino IDE? - solved

Post by dummibaer »

Thanks a lot! This is exactly what I was looking for.

With your help and the mentioned defines I was able to get the code to work.

Now, the ADC is triggered by timer 4 CCR4 event. After ADC conversion and DMA transfer the interrupt handler DmaIRQ() is called in which timer 4 is paused. Then, Serial is started in loop() and the captured ADC values are written to the console. After this Serial is terminated and timer 4 is resumed. This is essential. In tests I found it is not possible to use Serial while timer 4 is active.
The trigger for tha ADC by timer 4 (conversion repetition frequency) is kept at 500kHz. The trigger itself is half of a period delayed due to the capture compare event which is defined at a DutyCycle of 50%. The data lenght of DMA transfer (number of values to be captured by the ADC) is 3696.

Here is the final code.

Code: Select all

#include <HardwareTimer.h>
#include <STM32ADC.h>

#define pinLED  PC13

// Channels to be acquired.
// A0 (adc1 channel 1)
uint8 pins = 0;

boolean x = LOW;
boolean y = LOW;
boolean z = LOW;
int a = 0;
int b = 0;
int c = 0;

#define maxSamples 3696
uint16_t buffer[maxSamples];

#define sampleFreqKhz 500

STM32ADC myADC(ADC1);

void DmaIRQ(void) {

  //    x = !x;
  //    digitalWrite(pinLED, x);

  Timer4.pause();
  y = HIGH;

}

void setup() {

  pinMode(pinLED, OUTPUT);
  pinMode(PB9, PWM);
  pinMode(pins, INPUT_ANALOG);

  Timer4.setPrescaleFactor(1); //clock source is 72MHz
  Timer4.setOverflow(72000 / sampleFreqKhz - 1);
  Timer4.setCompare(TIMER_CH4, (72000 / sampleFreqKhz - 1) / 2); // 50% DutyCycle
  Timer4.setCount(0);

  Timer4.setMode(TIMER_CH4, TIMER_PWM);
  TIMER4_BASE->DIER |= (1 << 4); //enable CC4 interrupt - CC4IE=1;
  Timer4.refresh();
  Timer4.resume();

  myADC.calibrate();
  myADC.setSampleRate(ADC_SMPR_1_5); // conversion time - 1,5 clock cycles
  myADC.setPins(&pins, 1);
  myADC.setDMA(buffer, maxSamples, (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_TRNS_CMPLT), DmaIRQ);
  myADC.setTrigger(ADC_EXT_EV_TIM4_CC4);
  myADC.startConversion();

}

void loop() {

  if (y == HIGH) {
    y = LOW;
    delay(100);

    Serial.begin(115200);

    Serial.print("Measurement ");
    Serial.print(b);
    Serial.println("----------");
    delay(1000);
    for (a = 0; a < maxSamples ; a++) {
      Serial.println(buffer[a]);
    }
    b++;

    Serial.end();

    delay(100);
    Timer4.resume();
  }

}
stevestrong
Posts: 502
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: DMA interrupt with Arduino IDE? - solved

Post by stevestrong »

dummibaer wrote: Sat Jun 20, 2020 12:00 pm After ADC conversion and DMA transfer the interrupt handler DmaIRQ() is called in which timer 4 is paused. Then, Serial is started in loop() and the captured ADC values are written to the console. After this Serial is terminated and timer 4 is resumed. This is essential. In tests I found it is not possible to use Serial while timer 4 is active.
Hm, I find weird that Serial (USB serial) does not work when Timer 4 is running. How did you come to this conclusion?

Btw, I think that you do not need to enable the compare interrupt.
dummibaer
Posts: 4
Joined: Fri Jun 19, 2020 8:32 am

Re: DMA interrupt with Arduino IDE? - solved

Post by dummibaer »

Yes, you are right. Serial via USB has to work independetly to timer 4 (did not tested it myself). But I am using an USB to UART programming adaptor which connects to pins PA9 and PA10 and as far as I know are these pins connected to timer 4 periphery. I tested if I can get user defined signals at timer4 ch4 while I get Serial output through the programming adaptor simultaneously. This did not work. But if I stop Serial transmission I can utilize user configured timer 4 and vice versa. And because ADC measurement is needed very scarcely I am perfectly fine with this solution.
Post Reply

Return to “General discussion”