STM32F303: АЦП с DMA работает только несколько раз

В настоящее время я работаю с платой Nucleo-64 с чипом STM32F303RE. Для программирования я использую Arduino IDE с пакетами STM32. На данный момент я хочу избежать HAL, потому что я думаю, что это довольно сбивает с толку, когда вам нужно одновременно изучать регистры и функции библиотеки.

Я хочу иметь возможность дискретизировать 4 входных сигнала параллельно со скоростью 5,1 Msps (максимум F303). Мой план состоял в том, чтобы поддерживать АЦП в рабочем состоянии. Затем, когда я хочу взять образцы, я сбрасываю флаги DMA и устанавливаю счетчик (CNDTR-Register) на количество образцов, которое я хочу захватить.

В следующей части показаны мои попытки добиться этого. В основном это почти работает, но только ограниченное количество раз. Как часто это работает, похоже, зависит от случайных значений сна, которые я ввожу в определенных частях программы. Например: если я введу задержку в 10 мс после функции takeSamples (), программа будет работать в течение 41 цикла основного цикла - тогда она застрянет.

Когда он застревает, он делает следующее: регистр DMA-CNDTR уменьшается только на одно значение, а затем просто остается там. Таким образом, программа ожидает, пока значение регистра станет равным нулю, но этого никогда не происходит. АЦП все время производит выборку, я прекрасно могу читать регистры данных АЦП.

Кто-нибудь знает, что могло заставить DMA прекращать передачу данных через определенное количество раз?

Вот соответствующие части программы:

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);
}

Буду очень благодарен за любые советы или подсказки по этому поводу!

Привет Бенни

РЕДАКТИРОВАТЬ: У меня также была такая же проблема с нуклео-64 с чипом STM32F401. С другой стороны, STM32F4 Discovery работал нормально. Таких проблем не было и с моей платой полетного контроллера F103.


person Pixel    schedule 18.09.2017    source источник
comment
1. Прежде всего начните использовать значения, понятные человеку.   -  person 0___________    schedule 19.09.2017
comment
2. Откажитесь от IDE arduino и начните использовать что-нибудь приличное - например, eclipse с установленным openSTM32.   -  person 0___________    schedule 19.09.2017
comment
3. Используйте определения CMSIS.   -  person 0___________    schedule 19.09.2017
comment
4. Забудьте о подходах artuino setup и loop.   -  person 0___________    schedule 19.09.2017


Ответы (1)


Например, что-то простое с таймером, запускающим преобразования.

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;
 }
person 0___________    schedule 19.09.2017
comment
Спасибо за ваш ответ! Использование определений сделало бы программу намного более читаемой. Может быть, проблема может быть отделена от самого кода в другом вопросе: что могло заставить ADC + DMA работать какое-то время, но затем, после определенного количества полных циклов DMA, DMA перестает передавать данные? Количество так или иначе зависит от интервалов сна, которые я вставляю в разные части программы. Может быть, какая-то проблема с синхронизацией с разными часами? - person Pixel; 22.09.2017
comment
Вопрос сложный, и на него нельзя ответить в целом. При возникновении проблемы вам необходимо отладить свою программу и проверить значения регистров DMA, ADC и TIM, чтобы увидеть фактический статус и ошибки. - person 0___________; 22.09.2017