Изучаем работу АЦП в микроконтроллере STM32F103.

Страница на этапе разработки

В данном уроке мы с Вами изучим работу АЦП (ADC) микроконтроллера STM32F103, но сначала рассмотрим возможности нашего АЦП:

  1. В нашем микроконтроллере есть два 12-битных ADC. Каждый из которых обслуживать несколько каналов — 10 внешних и 2 внутренних.
  2. Имеет несколько режимов преобразования:
    • однократное
    • непрерывное
    • по триггеру
    • по таймеру
  3.  Есть выравнивание битов результата (вправо / влево).
  4. Может генерировать прерывание для DMA.
  5. Скорость оцифровки — до 0.9 MSPS
  6.  Есть автокалибровка
  7. Режим сканирования входов по списку
  8. Аналоговый сторож (watchdog)

Сами каналы АЦП микроконтроллеров STM32 делятся на две группы:

  1. регулярные каналы (regular).
  2. инжектированные (injected).

Результаты измерений регулярных каналов хранятся в одном регистре и требуют сохранения результатов в памяти микроконтроллера, в то время как инжектированные каналы имеют собственные регистры для хранения результата.

Кроме того АЦП можно настроить на работу в качестве аналогового сторожа (watchdog), то есть задаются верхний и нижний пороги входного сигнала, АЦП отслеживает уровень сигнала и, если сигнал выходит за указанные пределы, генерируется прерывание.

Режимы работы АЦП:

1. Single-channel (Одноканальный) АЦП выполняет одно преобразование одного канала, сохраняет полученное значение в выходном регистре и останавливается.

2. Single continuous (Одноканальный длительный) Этот режим аналогичен первому, но АЦП не останавливается, а продолжает работу с выбранным каналом. При этом результат постоянно перезаписывается в выходном регистре.

3. Scan (Многоканальный) В этом режиме возможно сконфигурировать АЦП для выполнения последовательных преобразований нескольких каналов в заданной последовательности. Преобразовании также настраивается отдельно для каждого канала. После обработки указанного числа каналов АЦП останавливается.

4. Scan continuous (Многоканальный длительный) То же самое что и Scan, только АЦП не останавливается после опроса всех каналов, а снова начинает обработку каналов. При этом все результаты сохраняются в один регистр и надо вовремя забирать данные, пока они не будут затертые данным преобразования следующего канала.

5. Discontinuous (Прерывистый) Сканируются не все каналы за раз, а только те, которые заранее установленные, при следующем сканировании, сканируется следующая группа каналов и так далее.

Входы питания:
Vref+  — вход опорного напряжения
Vdda — вход аналогового питания
Vssa — вход общего потенциала аналогового питания

Для начала изучим работу 4 инжекторных каналов (в микроконтроллере STM32F103 их 4 штуки). В данном случаи результат каждого измерения будет помещен в свой регистр и нам не нужно «переживать», что результаты измерений будут затертые чужим каналом.
Но вначале нам необходимо рассмотреть некоторые особенности работы АЦП:
1. Калибровка АЦП — так как внутренние конденсаторы МК имеют не однородность,  для уменьшения погрешности преобразования проводят калибровку. Во время калибровки вычисляется цифровое значение АЦП для каждого конденсатора в виде корректирующего кода.
Перед началом калибровки АЦП должно находиться в отключенном состоянии, то есть бит ADON должен быть равен нулю. Запуск калибровки производиться установкой бита CAL в регистре ADC_CR2. Как только калибровка закончится, бит CAL сбросится аппаратно, после чего можно выполнять преобразования. Рекомендуется калибровать АЦП при подачи питания.
2. Время преобразования — для каждого канала можно выставить свое время преобразования, которое возможно выбрать из диапазона 1,5-239,5 дискретно (8 значений). Выставляется время в регистрах ADC_SMPR1  и ADC_SMPR2.
Полное время преобразования Т=(программное время выборки + 12,5 циклов)/частота преобразования.
Например частота ADCCLK задана равной 14МГц, а время выборки 1,5 цикла, тогда Т=(1,5+12,5)/14= 1мкс.

Напишем программный код:

int main(void) {

uint16_t ADC_value0, ADC_value1;

while (1) {
HAL_ADCEx_InjectedStart(&hadc1);
HAL_Delay(100);
ADC_value0 = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1);
ADC_value1 = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_2);
HAL_Delay(100);
 }

Теперь когда у нас появились данные, прежде чем продолжить рассмотрим вначале самый простой фильтр, который можно применить для фильтрации данных (конечно его параметры зависят от скорости изменения аналоговых значений), в качестве примера рассмотрим классический RC фильтр:

Данный фильтр является фильтром первого порядка и срезает высокие частоты. Резистор R1 является эквивалентом выходного сопротивления источника сигнала, таким образом конденсатор подбирают так, что бы максимальная частота изменения сигнала была до начала среза фильтра:
F=1/(2*π*R*C) , Гц

Теперь рассмотрим зависимость скорости обработки данных от выходного сопротивления источника аналогового сигнала:

Если мы используем резистивный делитель, то выходное сопротивление рассчитывается как параллельное сопротивление этих резисторов.
Если нам нужно мерять напряжение на аккумуляторе, то конечно нам необходимо, уменьшить до минимума ток через резистивный резистор. А так как мы ограничены в значениях выходного сопротивления на помощь нам придет конденсатор. С учетом того, что изменения значения происходит очень медленно, конденсатор нам не как не повлияет на измерение:

Теперь давайте рассчитаем значение конденсатора используя эмпирическую формулу :
С>>(времени выборки АЦП*Rвых)/(700*разрядность АЦП)
Например, у нас делитель из двух резисторов на 100кОм, тогда выходное сопротивление 50кОм. Время выборки возьмем 1мкс, разрядность 10 бит:
С>>50кОм*1000нс(1мкс)/(700*10)=7,14нФ
В данном случаи мы высчитали минимальную емкость, которая дает возможность получить результат 0,5LSB.
Мы возьмем емкость 100нФ, что должно полностью компенсировать большое выходное сопротивление, но при этом надо понимать, что при подаче питания, необходимо время для заряда данной емкости.  

Теперь проделаем все тоже самое но для режима с DMA. В данном случаи мы переложим все действия на внутреннюю периферию, тем самым освободим ядро от дополнительных вычислений и действий. Все что нам нужно будет это забрать наши значения после всех преобразований.
DMA работает только с ADC1, в микроконтроллере STM32F103C8T6.

Для настройки DMA нам нужно будет сделать следующее:
1. Включить ADC1: Вкладка Analog, в нашем случаи два канала ADC1_IN0, ADC1_IN1.
2. Настроить ADC:
     Scan Conversion Mode: Enabled
     Continuous Conversion Mode: Enabled
     DMA Continuous Requests: Enabled
     Nbr of Conversion: 2
3. Вкладка «Configuration» → ADC1: включить два наших канала Rank 1 — Channel 0 (PA0), Rank 2 —  Channel 1 (PA1)
4. DMA:
         DMA1: Channel1
        Mode: Circular

        Data width: Half Word
        Direction: Peripheral to Memory

Программный код:

#define ADC_CHANNELS 2

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

uint16_t adc_values[ADC_CHANNELS];

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
if (hadc->Instance == ADC1) {
   adc_values[0] -> PA0
   adc_values[1] -> PA1
}
}

int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, ADC_CHANNELS);

while (1) {
// adc_values[] автоматически обновляется
}
}