Рассмотрим на конкретном примере работу ADC DMA в микроконтроллерах STM32. Настраивать периферию будем с помощью CubeMX, писать код в CubeIDE. Ну и конечно использовать ADC HAL. Решим следующую задачу: необходимо вычислить RMS сигнала с частотой, скажем 512 Гц. Так же у нас есть сигнал начала измерения. У ADC задействовано 2 канала, на 1 заведен наш сигнал частотой 512 Гц, на второй сигнал с датчика.
Как уже понятно из заголовка статьи читать ADC будем с помощью DMA. Использовать будем микроконтроллер STM32F446RET.
Настройка ADC и DMA в CubeMX
Для начала нам необходимо настроить ADC и DMA в CubeMX для правильной работы в дальнейшем.
Настройка ADC в CubeMX
Вкладка с настройками ADC в CubeMX выглядит следующим образом:
Как видим у нас выбраны 2 канала ADC1: IN10 и IN11. Теперь рассмотрим каждую установку:
- Mode — режим работы ADC. Если задействован только один блок ADC, то в настройках только 1 режим Independent mode.
- Clock Prescaler — делитель частоты тактирования ADC. Здесь все просто, чем меньше делитель, тем больше частота.
- Resolution — здесь настраиваем разрядность ADC. Можно выбрать от 6 до 12 бит. Чем выше нужна точность, тем больше выставляем разрядность.
- Data Alignment — Выравнивание результата преобразования в регистре данных. Справа налево или наоборот.
- Scan Conversion Mode — режим сканирования. Так как у нас 2 канала ADC, включаем режим сканирования.
- Сontinuous Conversion Mode — активировав данный режим, ADC , будет работать в циклическом режиме. Это значит, что после завершения преобразования двух каналов, будет автоматически стартовать новое преобразование. Также наш DMA будет настроен на режим Circular, по этому этот пункт необходимо обязательно включить. Иначе мы получим только одно значение.
- Discontinuous Conversion Mode — Противоположное значение предыдущему пункту. Его конечно отключаем.
- DMA Continuous Requests — активирует непрерывное обращение к DMA. Работает в связке с настройкой DMA Circular и Сontinuous Conversion Mode.
- End Of Conversion Selection — настраиваем, когда поднимется флаг завершения преобразования, после измерения каждого канала или по завершению преобразования на всех каналах. Нам необходим второй вариант.
- Number Of Conversion — количество каналов для преобразования. У нас 2 регулярных канала.
- External Trigger Conversion Sourse — внешний тригер старта преобразования. У нас старт будет производится программно. Можно настроить и по таймеру.
- External Trigger Conversion Edge — автоматически активируется значение None, если будет программный старт.
- Rank — здесь настраиваем очередность чтения каналов ADC. Опрос происходит не по номеру канала, а по номеру Rank.
- Chanel — присваиваем ранку номер канала.
- Sampling Time — время сэмплирования данного канала.
Настройка ADC DMA в CubeMX
После настройки ADC переходим на вкладку DMA Settings.
Нажав на кнопку Add, выбираем ADC1. Далее выбираем режим Circular, это значит DMA будет работать непрерывно в циклическом режиме, пока пользователь не остановит работу. Так же необходимо настроить размер данных (Data Width), выбираем Word (32 бита).
Пример кода ADC с DMA
Теперь поговорим про написание кода, для решения нашей задачи. Решать ее будем так:запишем в массив значения ADC за один период, а затем посчитаем среднеквадратичное значение.
Посчитаем сколько мы сможем сделать измерений за один период с учетом наших настроек ADC.
Время на одно преобразование будет равно сумме преобразования на каждый канал плюс время на преобразование 12 бит, а так же 12,5 циклов на общие нужды (по даташиту).
56+56+12 + 12.5 = 136.5
Так как, наш ADC1 размещен на шине APB2 с частотой 45 МГц. С учетом делителя на 2 получаем 22,5МГц.
Разделив 136,5 на 22500000 получим значение 6.066 us (микросекунд). Это значит что одно преобразование двух каналов ADC равно этому времени.
Теперь посчитаем сколько сможем сделать преобразований за один период 512 герц.
1953,125/6,06 = 322,29 (322)
Получаем 322 измерения за период. Так как у нас 2 канала нам нужен массив на 644 значения. Ниже представлен пример программного кода:
#define ADC_REFERENCE_VOLTAGE 3.102 #define ADC_MAX 0xFFF uint32_t AllCounterMesh = 644; uint32_t ArrayOfADC_DMA[644] = {0,}; //на эту ножку подключен сигнал начала преобразования if(GPIO_Pin == GPIO_PIN_11) { if (start_meshure!=0) { if ((adcFlag == false)&&(counterADC==0)) { // Стартуем ADC DMA HAL_ADC_Start_DMA(&hadc1, ArrayOfADC_DMA, AllCounterMesh); // Устанавливаем флаг начала преобразования adcFlag = true; } counterADC++; // счетчик периодов // Period равен 1, но можно указать больше, тогда нужно увеличить массив с данными с учетом количества периодов if (counterADC > Period) { counterADC = 0; HAL_ADC_Stop_DMA(&hadc1); //рассчитываем напряжения float sqr_Un = 0; float sqr_In = 0; for (uint32_t i = 0; i < AllCounterMesh - 2; i+=2) { sqr_In = sqr_In + powf(ArrayOfADC_DMA[i],2.0); //chanel 10 sqr_Un = sqr_Un + powf(ArrayOfADC_DMA[i+1],2.0); //chanel 11 } adcDataIn = sqrtf(sqr_In/(CounterMesh)); //CounterMesh = 322 adcDataUn = sqrtf(sqr_Un/(CounterMesh)); sqr_In = 0; sqr_Un = 0; adcUn = ADC_REFERENCE_VOLTAGE * adcDataUn/ADC_MAX; adcIn = (ADC_REFERENCE_VOLTAGE * adcDataIn/ADC_MAX); adcFlag = false; } } }
Автор, поясните откуда в коде взялась переменная adcDataUp, и что такое ADC_REFERENCE_VOLTAGE???
Добрый день. ADC_REFERENCE_VOLTAGE — это опорное напряжение, на основе которого рассчитывается реальное напряжение. Для этого проекта оно размещено в дефайне #define ADC_REFERENCE_VOLTAGE 3.102 adcDataUp, переменная которая хранит расчетно напряжение одного из каналов. Этот код из работающего проекта, не все лишнее удалил. Спасибо что заметили неточноть!
Доброго времени суток. Еще один вопрос и ваш совет. Задача измерить переменное напряжение частотой 50 Гц. Мой вариант алгоритма — запускаю таймер частотой 20мс (50Гц), который в свое время толкает АЦП, который делает 200 замеров напряжения канала за это время опроса и по прерыванию отдает все это через ДМА в память, где проводятся вычисления RMS. В итоге у меня выходной результат на мониторе прыгает, хотя напряжение не меняется. Где ошибка??
Добрый день! Для начала частота семплирования должна быть как минимум в 2 раза больше измеряемой. В вашем случае необходимо посмотреть настройки ADC. Возможно 200 измерений длятся дольше 20 мс. Вам нужно уложиться в это время.