Введение
Аналогично аналоговым входам Arduino из основного цикла (статья 295), STM32 содержит встроенный АЦП (аналого-цифровой преобразователь, ADC) для считывания аналоговых сигналов с датчиков, и в некоторых моделях — ЦАП (цифро-аналоговый преобразователь, DAC) для генерации аналогового сигнала. В статье разберём настройку и использование обоих через HAL.
Концепция
ADC STM32 значительно более продвинут, чем простой analogRead() Arduino — поддерживает несколько каналов, настраиваемое разрешение (от 6 до 12 бит в зависимости от модели), различные режимы запуска преобразования (программный, по таймеру, непрерывный), и DMA для автоматического переноса результатов без участия процессора (статья 357). DAC, доступный в некоторых моделях STM32 (но не во всех — в отличие от ADC, присутствующего практически везде), позволяет генерировать произвольный аналоговый сигнал на выходном пине, что не имеет прямого аналога в стандартном Arduino (где для аналогового вывода обычно используется ШИМ, лишь имитирующий аналоговый сигнал через усреднение).
Пример кода: ADC и DAC на STM32
// Настройка ADC1 для считывания одного канала
ADC_HandleTypeDef hadc1;
void ADC1_Init(void)
{
__HAL_RCC_ADC1_CLK_ENABLE();
hadc1.Instance = ADC1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B; // 12-битное разрешение — значения от 0 до 4095
hadc1.Init.ContinuousConvMode = DISABLE; // каждое преобразование запускается явно
hadc1.Init.ScanConvMode = DISABLE; // один канал, а не сканирование нескольких
HAL_ADC_Init(&hadc1);
ADC_ChannelConfTypeDef channelConfig = {0};
channelConfig.Channel = ADC_CHANNEL_0; // соответствует определённому физическому пину (например, PA0)
channelConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &channelConfig);
}
uint16_t readAdcValue(void)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); // блокирующее ожидание завершения преобразования
uint16_t value = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
return value; // значение от 0 до 4095 (12 бит), пропорциональное входному напряжению
}
void mainLoopAdcExample(void)
{
uint16_t rawValue = readAdcValue();
// Преобразование в напряжение, если опорное напряжение ADC — 3.3В
float voltage = (rawValue / 4095.0f) * 3.3f;
}
// Настройка DAC для генерации произвольного аналогового сигнала (доступно не на всех моделях STM32)
DAC_HandleTypeDef hdac;
void DAC_Init(void)
{
__HAL_RCC_DAC_CLK_ENABLE();
hdac.Instance = DAC;
HAL_DAC_Init(&hdac);
DAC_ChannelConfTypeDef dacChannelConfig = {0};
dacChannelConfig.DAC_Trigger = DAC_TRIGGER_NONE;
dacChannelConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &dacChannelConfig, DAC_CHANNEL_1);
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
}
void setDacVoltage(float voltage)
{
// Преобразование напряжения в 12-битное значение для записи в DAC
uint16_t dacValue = (uint16_t)((voltage / 3.3f) * 4095.0f);
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacValue);
}
Пояснения к коду
HAL_ADC_PollForConversion() с HAL_MAX_DELAY — простейший, блокирующий способ ожидания результата преобразования ADC, аналогичный по подходу блокирующему UART из статьи 354; для непрерывного, частого считывания без блокировки основного цикла стоит рассмотреть DMA-режим (статья 357) или прерывания по завершению преобразования. Расчёт напряжения из «сырого» значения ADC ((rawValue / 4095.0f) * 3.3f) — стандартное преобразование на основе известного разрешения (12 бит, максимальное значение 4095) и опорного напряжения (3.3В в типичной конфигурации STM32) — без знания точного опорного напряжения, используемого конкретной платой, преобразование в реальные единицы измерения (вольты) будет неточным, хотя относительные сравнения значений ADC всё ещё будут корректны.
Подводные камни
- Использование
ADC_SAMPLETIME_15CYCLES(как в примере) без учёта импеданса конкретного подключённого аналогового датчика — слишком короткое время выборки относительно высокого выходного импеданса некоторых датчиков может привести к неполному заряду внутреннего конденсатора выборки ADC, что даёт систематически неточные результаты измерения; для датчиков с высоким выходным импедансом (некоторые типы фоторезисторов, термисторы без буферного усилителя) стоит увеличить время выборки или использовать промежуточный буферный операционный усилитель. - Неверное предположение о точном значении опорного напряжения ADC (3.3В) без учёта реальных допусков напряжения питания конкретной платы (которое может незначительно отклоняться от номинального значения) — для приложений, требующих высокой точности абсолютных измерений напряжения (а не только относительных сравнений), стоит либо откалибровать ADC против известного эталонного напряжения, либо использовать встроенный во многие модели STM32 внутренний канал измерения опорного напряжения для динамической компенсации отклонений питания.
- Попытка использовать DAC на модели STM32, не имеющей этой периферии — в отличие от ADC, присутствующего практически в каждой модели STM32, DAC доступен только в определённых, не всех семействах и моделях чипов; перед проектированием схемы, рассчитывающей на использование DAC, обязательно нужно проверить наличие этой периферии в конкретной выбранной модели чипа по её документации.
- Игнорирование настройки
DAC_OUTPUTBUFFER_ENABLE/DISABLEбез понимания разницы — встроенный буфер DAC увеличивает выходной ток (способность DAC «толкать» больший ток в нагрузку без существенного падения напряжения), но также вносит небольшое смещение напряжения (offset) и снижает скорость нарастания сигнала; для задач, требующих максимальной точности абсолютного значения напряжения или высокой скорости изменения сигнала, может потребоваться отключение буфера с пониманием соответствующего компромисса в нагрузочной способности выхода.