Изучаем работу АЦП в микроконтроллере 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 фильтр:

*картинка*

*объяснения*

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

Если мы используем резистивный делитель, то выходное сопротивление рассчитывается как параллельное сопротивление этих резисторов.

Як оцінити, «наскільки великий» має бути конденсатор?

Приблизне емпіричне правило:

Rвих⋅C≪tacqR_{\text{вих}} \cdot C \ll t_{\text{acq}}

Де:

  • RвихR_{\text{вих}} — вихідний опір (наприклад, R1⋅R2R1+R2\frac{R_1 \cdot R_2}{R_1 + R_2}),

  • CC — вихідний конденсатор,

  • tacqt_{\text{acq}} — час вибірки АЦП (acquisition time), наприклад, 1–2 мкс для STM32.


🔢 Приклад:

  • R1=100 kΩR_1 = 100\,\text{k}\Omega, R2=100 kΩR_2 = 100\,\text{k}\OmegaRвих=50 kΩR_{\text{вих}} = 50\,\text{k}\Omega

  • Щоб RC < 1 мкс:

    C≫1⋅10−650⋅103=20 нФC \gg \frac{1 \cdot 10^{-6}}{50 \cdot 10^3} = 20\,\text{нФ}

➡️ Рекомендується 100 нФ або більше.

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

Для настройки DMA нам нужно будет сделать следующее:

1. Включите ADC1:

  • Вкладка Analog: включите ADC1_IN0, ADC1_IN1, ADC1_IN2

2. Настройка ADC:

  • Scan Conversion Mode: Enabled

  • Continuous Conversion Mode: Enabled

  • DMA Continuous Requests: Enabled

  • Nbr of Conversion: 3

3. Вкладка «Configuration» → ADC1:

  • Rank 1: Channel 0 (PA0)

  • Rank 2: Channel 1 (PA1)

  • Rank 3: Channel 2 (PA2)

4. DMA:

  • DMA1 Channel1

  • Mode: Circular

  • Data width: Half Word

  • Direction: Peripheral to Memory

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

#define ADC_CHANNELS 3

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
// adc_values[2] -> PA2
}
}

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[] автоматически обновляется
}
}