2
votes

I am currently working with a Nucleo-64 board that has the STM32F303RE chip. For programming I use the Arduino IDE with the STM32 packages. I want to avoid HAL for now because I think it is rather confusing when you have to learn the registers and the library functions at the same time.

I want to be able to sample 4 input signals parallel at 5.1 Msps (max of the F303). My plan was to keep the ADC running. Then, when I want to take the samples, I reset the DMA flags and set the counter (CNDTR-Register) to the ammount of samples I want to capture.

The following part shows my attemp to achieve this. It is basically almost working, but only a limited amount of times. How often it works seems to depend on random sleep values which I enter at certain parts in the programm. For example: If I enter a delay of 10ms after the takeSamples()-function, the programm will work for 41 cycles of the main loop - then it gets stuck.

When it gets stuck it does the following: The DMA-CNDTR register only gets reduced by one value, then it just stays there. So the programm is waiting for the register value to become zero, but this never happens. The ADC is sampling the whole time, I can read the ADC data registers just fine.

Does anyone have an idea what could be causing the DMA to stop transfering data after a certain amount of times?

Here are the relevant parts of the program:

void setup() {
  Serial.begin(57600);

  // Enable clocks
  RCC->AHBENR |= (1 << 17); // GPIOA
  RCC->AHBENR |= (1 << 18); // GPIOB

  // Set ADC pins to analog input
  GPIOA->MODER |= (0b11 << 0); // PA0 for ADC1
  GPIOA->MODER |= (0b11 << 8); // PA4 for ADC2
  GPIOB->MODER |= (0b11 << 2); // PB1 for ADC3
  GPIOB->MODER |= (0b11 << 24); // PB1 for ADC4

  initClock();
  DMA_init();
  ADC_init();

  // Start conversion
  ADC1->CR |= (1 << 2);
  ADC3->CR |= (1 << 2);
}

void initClock()
{
  FLASH->ACR |= (0b10 << 0); // add two wait states

  RCC->CR |= (1 << 18); // Bypass HSE, use external clock signal from STLink instead

  RCC->CR &= ~(1 << 24); // turn off PLL
  delay(100);
  RCC->CFGR |= (0b0000 << 4); // Do not divide system clock
  RCC->CFGR |= (0b0111 << 18);  // PLL multiply = 9
  RCC->CFGR |= (0b10 << 15); // use HSE as PLL source
  RCC->CFGR |= (1 << 10);  // not divided
  delay(100);
  RCC->CR |= (1 << 24); // turn on PLL
  delay(100);
}

void ADC_init(void) {

  RCC->CFGR2 |= (0b10000 << 4); // Prescaler
  RCC->CFGR2 |= (0b10000 << 9); // Prescaler
  RCC->AHBENR |= (1 << 28); // turn on ADC12 clock
  RCC->AHBENR |= (1 << 29); // turn on ADC34 clock

  // Set ADC clock
  ADC12_COMMON->CCR |= (0b01 << 16); // 0b01
  ADC34_COMMON->CCR |= (0b01 << 16); // 0b01


  // disable the ADC
  ADC1->CR &= ~(1 << 0);
  ADC2->CR &= ~(1 << 0);
  ADC3->CR &= ~(1 << 0);
  ADC4->CR &= ~(1 << 0);

  // enable the ADC voltage regulator
  ADC1->CR &= ~(1 << 29);
  ADC2->CR &= ~(1 << 29);
  ADC3->CR &= ~(1 << 29);
  ADC4->CR &= ~(1 << 29);

  ADC1->CR |= (1 << 28);
  ADC2->CR |= (1 << 28);
  ADC3->CR |= (1 << 28);
  ADC4->CR |= (1 << 28);

  // start ADC calibration cycle
  ADC1->CR |= (1 << 31);
  // wait for calibration to complete
  while (ADC1->CR & (1 << 31));

  // start ADC calibration cycle
  ADC2->CR |= (1 << 31);
  // wait for calibration to complete
  while (ADC2->CR & (1 << 31));

  // start ADC calibration cycle
  ADC3->CR |= (1 << 31);
  // wait for calibration to complete
  while (ADC3->CR & (1 << 31));

  // start ADC calibration cycle
  ADC4->CR |= (1 << 31);
  // wait for calibration to complete
  while (ADC4->CR & (1 << 31));

  // enable the ADC
  ADC1->CR |= (1 << 0);
  ADC2->CR |= (1 << 0);
  ADC3->CR |= (1 << 0);
  ADC4->CR |= (1 << 0);

  while (!(ADC1->ISR & (1 << 0)));
  while (!(ADC2->ISR & (1 << 0)));
  while (!(ADC3->ISR & (1 << 0)));
  while (!(ADC4->ISR & (1 << 0)));

  // Select ADC Channels
  ADC1->SQR1 = (1 << 6);
  ADC2->SQR1 = (1 << 6);
  ADC3->SQR1 = (1 << 6);
  ADC4->SQR1 = (3 << 6);

  // Set sampling time for regular group 1
  ADC1->SMPR1 |= (0b000 << 3); // 0b000 -> 1.5 clock cycles, shortest available sampling time
  ADC2->SMPR1 |= (0b000 << 3);
  ADC3->SMPR1 |= (0b000 << 3);
  ADC4->SMPR1 |= (0b000 << 3);

  // Regular sequence settings
  ADC1->SQR1 |= (0b0000 << 0); // One conversion in the regular sequence
  ADC2->SQR1 |= (0b0000 << 0);
  ADC3->SQR1 |= (0b0000 << 0);
  ADC4->SQR1 |= (0b0000 << 0);

  // Enable continuous conversion mode
  ADC1->CFGR |= (1 << 13); // Master ADC1 + ADC2
  ADC3->CFGR |= (1 << 13); // Master ADC3 + ADC4

  ADC12_COMMON->CCR |= (0b00110 << 0);
  ADC34_COMMON->CCR |= (0b00110 << 0);

  // DMA mode
  ADC12_COMMON->CCR |= (0 << 13); // 0 -> One Shot; 1 -> Circular
  ADC34_COMMON->CCR |= (0 << 13);

  // DMA mode for 12-bit resolution
  ADC12_COMMON->CCR |= (0b10 << 14);
  ADC34_COMMON->CCR |= (0b10 << 14);
}

void DMA_init(void) {

  // Enable clocks
  RCC->AHBENR |= (1 << 0); // DMA1
  RCC->AHBENR |= (1 << 1); // DMA2

  // Transfer complete interrupt enable
  DMA1_Channel1->CCR |= (1 << 1);
  DMA2_Channel5->CCR |= (1 << 1);

  // Memory increment mode
  DMA1_Channel1->CCR |= (1 << 7);
  DMA2_Channel5->CCR |= (1 << 7);

  // Peripheral size
  DMA1_Channel1->CCR |= (0b11 << 8);
  DMA2_Channel5->CCR |= (0b11 << 8);

  // Memory size
  DMA1_Channel1->CCR |= (0b11 << 10);
  DMA2_Channel5->CCR |= (0b11 << 10);

  // Number of data to transfer
  DMA1_Channel1->CNDTR = uint32_t(maxSamples);
  DMA2_Channel5->CNDTR = uint32_t(maxSamples);

  // Peripheral address register
  DMA1_Channel1->CPAR |= (uint32_t)&ADC12_COMMON->CDR;
  DMA2_Channel5->CPAR |= (uint32_t)&ADC34_COMMON->CDR;

  // Memory address register
  DMA1_Channel1->CMAR |= uint32_t(&dataPoints1232);
  DMA2_Channel5->CMAR |= uint32_t(&dataPoints3432);

  // Reset flags
  DMA1->IFCR |= 0xFF;
  DMA2->IFCR |= 0xFF;
}

void takeSamples(void) {

  // Reset flags
  DMA1->IFCR |= (0b1111111111111111111111111111111 << 0);
  DMA2->IFCR |= (0b1111111111111111111111111111111 << 0);

  // Number of data to transfer
  DMA1_Channel1->CNDTR = uint32_t(maxSamples);
  DMA2_Channel5->CNDTR = uint32_t(maxSamples);

  delay(10); // does not work without this random delay

  elapsedTime = micros();
  // Enable DMA
  DMA1_Channel1->CCR |= (1 << 0);
  DMA2_Channel5->CCR |= (1 << 0);

  while ((DMA1_Channel1->CNDTR > 0) || (DMA2_Channel5->CNDTR > 0))
  }

  elapsedTime = micros() - elapsedTime;

  // Reset flags
  DMA1->IFCR |= (0b1111111111111111111111111111111 << 0);
  DMA2->IFCR |= (0b1111111111111111111111111111111 << 0);;

  DMA1_Channel1->CCR &= ~(1 << 0);
  DMA2_Channel5->CCR &= ~(1 << 0);

  // ADC stop conversion
  ADC1->CR |= (1 << 4);
  ADC3->CR |= (1 << 4);

  while ((ADC1->CR & (1 << 2)) || (ADC3->CR & (1 << 2)));

  ADC12_COMMON->CCR &= ~(0b10 << 14);
  ADC34_COMMON->CCR &= ~(0b10 << 14);

  ADC12_COMMON->CCR |= (0b10 << 14);
  ADC34_COMMON->CCR |= (0b10 << 14);

  // ADC start conversion
  ADC1->CR |= (1 << 2);
  ADC3->CR |= (1 << 2);
}

void loop() {
  takeSamples();
  Serial.print("Elapsed time: ");
  Serial.println(elapsedTime);
}

I would be really thankfull for any tips or hints regarding this issue!

Greetings Benny

EDIT: I also had the same issue with the nucleo-64 with the STM32F401 chip. The STM32F4 Discovery on the other hand worked just fine. There was no issue like that with my F103 flightcontroller-board aswell.

1
1. First of all start to use human readable values.0___________
2. Give up arduino IDE and start using something decent - for example eclipse with openSTM32 installed.0___________
3. Use CMSIS definitions0___________
4. Forget artuino setup and loop approach0___________

1 Answers

2
votes

As an example something simple with the timer trigered conversions.

void ReadChannels(int channel, size_t nsamples, uint8_t *obuff) 
 {
     TIM1 -> CR1 = 0;
     TIM1 -> CR2 = 0;
     TIM1 -> PSC = PSC;
     TIM1 -> ARR = ARR;
     TIM1 -> EGR |= TIM_EGR_UG;

     DMA1_Channel1 -> CPAR = (uint32_t)&(ADC1 -> DR);
     DMA1_Channel1 -> CMAR = (uint32_t)obuff;
     DMA1_Channel1 -> CNDTR = nsamples;
     DMA1_Channel1 -> CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_EN;

     ADC1 -> CFGR = ADC_CFGR_DMAEN | (0b10 << ADC_CFGR_RES_Pos) | (9 << ADC_CFGR_EXTSEL_Pos) | (0b01 << ADC_CFGR_EXTEN_Pos);
     ADC1 -> SMPR1 = 0;
     ADC1 -> SMPR2 = 0;

     ADC1 -> SQR1 &= ~(ADC_SQR1_L_Msk);
     ADC1 -> SQR1 &= ~(ADC_SQR1_SQ1_Msk);

     ADC1 -> SQR1 |= channel << ADC_SQR1_SQ1_Pos);
     ADC1 -> CR |= ADC_CR_ADSTART;

     TIM1 -> CR2 |= TIM_CR2_MMS_1;
     TIM1 -> CR1 |= TIM_CR1_CEN;

     DMA1_Channel1 -> CCR = 0;
     TIM1 -> CR1 = 0;
 }