14. Аналого-цифровой преобразователь (АЦП)
Всё, что приходит от реального мира — температура, ток, положение движка, напряжение батареи — приходит аналоговым. АЦП превращает его в число. Блок кажется простым, но именно здесь чаще всего получают правдоподобные, но неверные результаты.
Как это называется в документации
Оригинал Перевод successive approximation последовательное приближение sampling time время выборки conversion time время преобразования regular / injected channel регулярный / инжектируемый канал scan mode режим сканирования continuous mode непрерывный режим external trigger внешний запуск analog watchdog аналоговый сторож calibration калибровка reference voltage опорное напряжение source impedance сопротивление источника factory calibration value заводское калибровочное значение chip temperature sensor датчик температуры кристалла
14.1 Как он работает и что из этого следует
Внутри — конденсатор, который заряжается от входа, и схема, шаг за шагом подбирающая двоичный код. Отсюда два следствия, определяющих всё остальное.
Конденсатору нужно время зарядиться. Чем выше сопротивление источника, тем дольше. Не дали времени — результат сместится к напряжению, что было на конденсаторе, то есть к предыдущему каналу, причём ошибка стабильная и правдоподобная.
Вход не бесплатен. АЦП подгружает измеряемую цепь. Делитель из мегаомных резисторов будет измеряться неверно просто потому, что не успевает перезаряжать конденсатор.
Разрядность и опора
У F1 и F4 разрядность 12 бит — 4096 градаций. Код связан с напряжением так:
напряжение = код × Vref / 4095
При опоре 3,3 В один код — примерно 0,8 мВ. Точность результата не может быть лучше точности опорного напряжения: если питание «плавает» от 3,2 до 3,4 В, все измерения плавают вместе с ним.
14.2 Время преобразования
время = (время выборки + время приближения) / ADCCLK
У F1 время приближения — 12,5 такта, время выборки настраивается от 1,5 до 239,5 такта на каждый канал отдельно.
| Выборка | Всего тактов | При 12 МГц |
|---|---|---|
| 1,5 | 14 | 1,17 мкс |
| 13,5 | 26 | 2,17 мкс |
| 55,5 | 68 | 5,67 мкс |
| 239,5 | 252 | 21 мкс |
Как выбирать. Минимума — 1,5 такта — хватает только очень низкоомному
источнику, например выходу операционного усилителя. По паспорту F1 при
ADCCLK = 14 МГц сопротивление источника должно быть не больше (при 12 МГц,
как в этом разделе, пределы чуть выше):
| Выборка, тактов | 1,5 | 7,5 | 13,5 | 28,5 | 41,5 | 55,5 |
|---|---|---|---|---|---|---|
| Источник, не больше | 0,4 кОм | 5,9 кОм | 11,4 кОм | 25,2 кОм | 37,2 кОм | 50 кОм |
Уже делитель 10 кОм / 10 кОм (5 кОм на выходе) требует не меньше 7,5 такта. Для датчика с высоким сопротивлением или длинных проводов берут 55,5 и больше; если выборка должна быть короткой, на вход ставят конденсатор на землю — заряд берётся из него. Признак нехватки — показания занижены и зависят от того, какой канал измерялся перед этим.
Как это выглядит на конденсаторе выборки, если источник — датчик на 50 кОм, а предыдущий канал оставил на конденсаторе ноль:
⚠️ Тактовая АЦП у F1 не должна превышать 14 МГц. При
PCLK2= 72 МГц доступны делители 2, 4, 6, 8 — и только делитель 6 или 8 укладываются в предел. Поставите 2 или 4 — блок продолжит работать и выдавать числа, но они будут неверными. Ошибки не возникнет, диагностики никакой:
c RCC->CFGR &= ~RCC_CFGR_ADCPRE; RCC->CFGR |= RCC_CFGR_ADCPRE_DIV6; /* 12 МГц */У F4 предел выше — 36 МГц, и делители другие.
14.3 ⚠️ Калибровка: обязательна у F1
Ключевое расхождение платформ.
| STM32F1 | STM32F4 | |
|---|---|---|
| калибровка | выполняется программой | программной калибровки нет, запускать нечего |
У F1 после включения блока положено запустить калибровку — иначе погрешность заметно выше паспортной:
ADC1->CR2 |= ADC_CR2_ADON; /* первое включение: просыпается */
for (volatile int i = 0; i < 1000; i++) { } /* пауза на стабилизацию */
ADC1->CR2 |= ADC_CR2_RSTCAL; /* сброс калибровочного регистра */
while (ADC1->CR2 & ADC_CR2_RSTCAL) { }
ADC1->CR2 |= ADC_CR2_CAL; /* сама калибровка */
while (ADC1->CR2 & ADC_CR2_CAL) { }
⚠️ Первая запись
ADONтолько включает блок, не запускает преобразование. Между включением и калибровкой нужна пауза — блоку требуется время на стабилизацию. Запуск преобразования — это повторная записьADONв уже включённый блок. Логика неочевидная, и на ней спотыкаются.
14.4 🔧 Одиночное измерение
uint16_t adc1_read(uint8_t channel)
{
ADC1->SQR3 = channel; /* первым в очереди — нужный канал */
ADC1->SQR1 = 0; /* длина очереди 1 */
ADC1->CR2 |= ADC_CR2_ADON; /* запуск */
while (!(ADC1->SR & ADC_SR_EOC)) { }
return (uint16_t)ADC1->DR; /* чтение сбрасывает флаг */
}
Не забудьте перевести вывод в аналоговый режим — для F1 это CNF = 00
при MODE = 00 (раздел 8). Оставленный цифровым вход даёт показания,
похожие на правду, но искажённые.
14.5 Очередь каналов и режимы
АЦП обходит не один вход, а очередь — до шестнадцати каналов в любом
порядке, с повторами. Порядок задаётся регистрами SQR.
| Режим | Что делает |
|---|---|
| одиночный | одно преобразование по команде |
сканирование (SCAN) |
пройти всю очередь за один запуск |
непрерывный (CONT) |
начинать заново сразу после завершения |
| по внешнему запуску | преобразование стартует от таймера или вывода |
Регулярные и инжектируемые каналы. Помимо основной очереди есть вторая, приоритетная: инжектируемые каналы прерывают обычное преобразование, делают своё и возвращают управление. Так измеряют что-то срочное — например, ток в фазе двигателя, синхронно с ШИМ, — не ломая фоновый опрос датчиков.
14.6 🔧 Сканирование с DMA
Сканирование без DMA почти бессмысленно: у всех каналов один регистр результата, и следующее преобразование затрёт предыдущее раньше, чем процессор успеет прочитать. DMA забирает каждый результат сразу.
/* АЦП уже включён и откалиброван (14.3): последняя запись ADON — запуск.
Время выборки каналов 0–3 при необходимости задают в SMPR2 (14.1). */
void adc1_scan_dma_init(void)
{
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
/* ADC1 жёстко привязан к каналу 1 DMA1 — выбора нет. */
DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel1->CMAR = (uint32_t)adc_buf;
DMA1_Channel1->CNDTR = 4;
DMA1_Channel1->CCR = DMA_CCR1_MINC /* адрес в памяти растёт */
| DMA_CCR1_CIRC /* по кругу */
| DMA_CCR1_PSIZE_0 /* периферия: полуслово */
| DMA_CCR1_MSIZE_0 /* память: полуслово */
| DMA_CCR1_PL_1;
DMA1_Channel1->CCR |= DMA_CCR1_EN;
/* Очередь из четырёх каналов: 0, 1, 2, 3. */
ADC1->SQR1 = (4u - 1u) << 20; /* длина очереди */
ADC1->SQR3 = (0u << 0) | (1u << 5) | (2u << 10) | (3u << 15);
ADC1->CR1 |= ADC_CR1_SCAN;
ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_CONT;
ADC1->CR2 |= ADC_CR2_ADON;
}
Дальше значения просто лежат в массиве и обновляются сами. Массив обязан быть
volatile — его меняет DMA помимо кода (раздел 3).
Запуск от таймера — следующий шаг. Непрерывный режим оцифровывает с максимальной скоростью, что редко нужно: получается гора данных с неравномерными интервалами. Правильнее запускать преобразование таймером, тогда отсчёты идут строго с заданным периодом — а это обязательное условие для любой последующей обработки сигнала.
Связка «таймер → АЦП → DMA» работает вообще без участия процессора: он лишь разбирает готовый буфер.
14.7 Встроенные каналы
Кроме внешних входов есть два внутренних.
Датчик температуры кристалла. Измеряет не окружающую среду, а сам кристалл, который греется от работы. Годится для контроля перегрева и грубой оценки, но не для метеостанции: разброс между экземплярами велик.
Опорное напряжение VREFINT — примерно 1,2 В, стабильное независимо
от питания. Отсюда полезный приём: измерить собственное питание без единой
внешней детали.
Логика такая: код VREFINT тем меньше, чем выше питание — ведь опора
для преобразования и есть питание. Зная точное значение VREFINT, из его
кода вычисляют VDDA. У F4 в системной памяти лежит заводское калибровочное
значение, что делает оценку точной.
Практическая ценность: устройство на батарее может следить за её разрядом, не тратя ни выводов, ни резисторов делителя, ни тока на этот делитель.
14.8 Аналоговый сторож
Блок сравнивает результат с двумя порогами и вызывает прерывание при выходе за границы. Процессор не опрашивает значения вовсе — реагирует только на событие.
Применение очевидное: следить за перегревом, обрывом датчика, просадкой питания. Всё это без единой строки в основном цикле.
14.9 🔧 Милливольты без внешней опоры
Код АЦП — доля от опорного напряжения VREF+, а не милливольты: код 0 —
ноль вольт, 4095 — само VREF+, каким бы оно сейчас ни было. В корпусах
до 64 выводов VREF+ соединён внутри кристалла с выводом аналогового питания
VDDA; в больших корпусах это отдельный вывод. На большинстве плат его
подключают к тому же питанию, и тогда опора — это VDDA: пока питание ровно
3,3 В, код × 3300 / 4095 верен; сядет батарея до 3,0 В — все показания съедут на 10 %.
Если же на VREF+ стоит отдельный точный источник (2,5 В, 2,048 В), шкала
от питания не зависит, и пересчёт ниже не нужен.
Узнавать опору при каждом измерении помогает внутренний источник VREFINT
(14.7): его напряжение от питания не зависит. Способа два:
- по номиналу из даташита —
VREF+ = номинал × 4095 / код_VREFINT; точность ограничена паспортным разбросом самогоVREFINT: у F103 он от 1,16 до 1,24 В (до +85 °C), и оценка 3,3 В гуляет от −106 до +114 мВ от камня к камню. Для точного контроля батареиVREFINTодин раз измеряют при известном питании и хранят свою калибровку во флеш-памяти; - по заводскому коду
VREFINT_CAL— его сняли на заводе приVREF+= 3,3 В, и он учитывает разброс именно этого кристалла:VREF+ = 3300 × VREFINT_CAL / код_VREFINT.
У F4 доступны оба, у F1 заводского кода нет — только первый. Дальше в коде
опора названа vdda: на платах она и есть питание.
F4: заводские коды. В системной памяти лежат три числа, снятые на заводе при питании 3,3 В:
| Адрес | Что | Условия |
|---|---|---|
0x1FFF 7A2A |
код VREFINT |
3,3 В, 30 °C |
0x1FFF 7A2C |
код датчика температуры | 30 °C |
0x1FFF 7A2E |
код датчика температуры | 110 °C |
Адреса — из драйвера LL для всего семейства F4 (stm32f4xx_ll_adc.h),
условия — оттуда же: VREF+ = 3,3 В. Перед использованием ячейку стоит
проверить: незаписанная флеш-память читается как 0xFFFF — значит, заводского
значения нет, и остаётся номинал, как у F1. Из заводского кода — опора,
а через неё милливольты любого канала и температура по двум точкам.
Всё в целых числах:
/* питание VDDA по коду VREFINT: у F4 — по заводскому коду cal, снятому при 3,3 В */
uint32_t vdda_from_cal(uint16_t vref_code, uint16_t cal)
{
return (3300u * cal + vref_code / 2u) / vref_code;
}
uint32_t code_to_mv(uint16_t code, uint32_t vdda_mv)
{
return (code * vdda_mv + 2047u) / 4095u;
}
/* F4: температура кристалла по двум заводским точкам, в десятых долях градуса */
int32_t temp_x10(uint16_t ts_code, uint32_t vdda_mv, uint16_t cal30, uint16_t cal110)
{
int32_t ts = (int32_t)((ts_code * vdda_mv + 1650u) / 3300u); /* код, как при 3,3 В */
return 300 + (ts - cal30) * 800 / (cal110 - cal30);
}
F1: калибровки нет. Остаётся номинал 1,2 В:
/* у F1 калибровки нет — только номинальные 1200 мВ */
uint32_t vdda_from_nominal(uint16_t vref_code)
{
return (1200u * 4095u + vref_code / 2u) / vref_code;
}
Ошибка опоры переходит в ошибку питания один к одному: опора на 1 % ниже номинала — и 3,3 В посчитаются как 3334 мВ.
Включение внутренних каналов. Бит TSVREFE у F1 стоит в ADC1->CR2,
у F4 — в общем ADC->CCR. VREFINT — канал 17, датчик у F405/407 — канал 16.
Время выборки берут самое длинное, потому что оба источника высокоомные:
239,5 такта при 12 МГц — 20 мкс, 480 тактов при 21 МГц — 23 мкс.
void internal_channels_on(void)
{
ADC->CCR |= ADC_CCR_TSVREFE; /* включить датчик и VREFINT */
ADC1->SMPR1 |= ADC_SMPR1_SMP16 | ADC_SMPR1_SMP17; /* 111: 480 тактов — 23 мкс при 21 МГц */
}
uint32_t vdda_now(void)
{
return vdda_from_cal(adc_read_ch(17), VREFINT_CAL_F4);
}
int32_t chip_temp_x10(void) /* канал 16 — датчик у F405/407 */
{
uint32_t vdda = vdda_now();
return temp_x10(adc_read_ch(16), vdda, TS_CAL30_F4, TS_CAL110_F4);
}
Что показал пересчёт. Функции прогнаны на компьютере против идеального АЦП:
питание от 2,0 до 3,6 В через 1 мВ, температура от −40 до 125 °C при питании
2,4–3,6 В. Заводские коды взяты для примера: VREFINT 1,21 В (код 1502),
датчик 0,76 В при 25 °C и 2,5 мВ/°C (коды 959 и 1207).
| Что считали | Наибольшая ошибка |
|---|---|
VDDA по коду VREFINT |
2 мВ |
| милливольты канала (1234 мВ) | 1 мВ |
| температура кристалла | 0,4 °C |
Это ошибки одного лишь округления — от разрядности и целой арифметики. Сам датчик ошибается на градусы и греется вместе с кристаллом (14.7).
14.10 F0 и G0: АЦП без очереди
У F0 и G0 один АЦП, и устроен он не как у F1 и F4. Главное отличие — нет
очереди SQR1…SQR3 с позициями каналов: каналы просто отмечаются битами в
CHSELR, и АЦП проходит их по возрастанию номера (SCANDIR — в обратную
сторону). Данные — в DR по смещению 0x40, запуск — бит ADSTART, конец
измерения — EOC, конец всей маски — EOS. Калибровка обязательна у обеих
серий и делается при выключенном АЦП (ADEN = 0) — в отличие от F1, где
АЦП перед калибровкой, наоборот, включают (14.3):
ADC1->CR |= ADC_CR_ADCAL; /* калибровка — при выключенном АЦП */
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY)) {
}
Одно измерение — маска канала вместо номера:
uint16_t adc_read_f0(uint32_t channel_mask)
{
ADC1->CHSELR = channel_mask; /* один бит на канал, не номер в SQR */
ADC1->CR |= ADC_CR_ADSTART;
while (!(ADC1->ISR & ADC_ISR_EOC)) {
}
return (uint16_t)ADC1->DR;
}
Код с F1 здесь не собирается — и это хорошо, ошибка ловится сразу:
no member named 'SQR3' in 'ADC_TypeDef', то же для CR2.
Что добавляет G0. Три вещи, каждая ловит переносящего код с F0:
- перед калибровкой включают регулятор АЦП —
ADVREGEN— и ждут 20 мкс (число из LL:LL_ADC_DELAY_INTERNAL_REGUL_STAB_US); без этого АЦП молчит; - после записи
CHSELRновая маска вступает не сразу: ждут флагCCRDY, иначе измеряется прежний канал; - калибровочный коэффициент хранится в своём регистре
CALFACT(0xB4) — его можно сохранить и вернуть после сна.
ADC1->CR |= ADC_CR_ADVREGEN; /* сначала регулятор — и 20 мкс ожидания */
wait_us(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US);
ADC1->ISR = ADC_ISR_CCRDY; /* флаг сбрасывается записью единицы */
ADC1->CHSELR = channel_mask;
while (!(ADC1->ISR & ADC_ISR_CCRDY)) { /* у G0 смена каналов вступает не сразу */
}
Ещё у G0 есть передискретизация: АЦП сам складывает до 256 измерений и сдвигает сумму — 16-битный результат без участия ядра:
ADC1->CFGR2 |= ADC_CFGR2_OVSE | LL_ADC_OVS_RATIO_256 | LL_ADC_OVS_SHIFT_RIGHT_4;
Такт и время. Такт АЦП — синхронный от PCLK (CKMODE: /2, /4, у G0
ещё /1) или свой асинхронный; у G0 асинхронный можно делить PRESC до 256.
Время выборки: у F0 от 1,5 до 239,5 такта, у G0 от 1,5 до 160,5, причём G0
держит два разных времени (SMP1, SMP2) и назначает каждому каналу одно
из них битами SMPSEL. В листингах — самое долгое время для VREFINT:
| F0 при 12 МГц | G0 при 16 МГц | |
|---|---|---|
| выборка + преобразование, тактов | 239,5 + 12,5 = 252 | 160,5 + 12,5 = 173 |
| одно измерение | 21 мкс | 10,8 мкс |
Внутренние каналы и заводские коды. Номера каналов у серий разные, и условия заводской калибровки — тоже:
| F0 | G0 | |
|---|---|---|
канал VREFINT |
17 | 13 |
| канал датчика температуры | 16 | 12 |
код VREFINT снят при VREF+ |
3,3 В | 3,0 В |
адрес кода VREFINT |
0x1FFF F7BA |
0x1FFF 75AA |
| точки датчика температуры | 30 и 110 °C | 30 и 130 °C |
Две точки датчика есть не у всех F0: у F030 и F070 заводская точка одна —
TS_CAL1 при 30 °C, и наклон берут паспортный.
Значит, формула из 14.9 у G0 берёт 3000, а не 3300 — иначе питание выйдет завышенным на 10 %:
/* питание VDDA по коду VREFINT и заводскому коду, снятому при 3,0 В */
uint32_t vdda_mv_g0(void)
{
uint16_t code = adc_read_g0(CH_VREFINT);
return (VREFINT_CAL_VREF * (uint32_t)*VREFINT_CAL_ADDR + code / 2u) / code;
}
VREFINT_CAL_VREF и VREFINT_CAL_ADDR — из заголовка LL своей серии
(stm32f0xx_ll_adc.h, stm32g0xx_ll_adc.h): подключите его, и числа
подставятся верные сами; те же имена есть и в LL для F4.
14.11 Грабли
Показания занижены и «плавают» между каналами. Мало времени выборки — конденсатор не успевает зарядиться, а остаточный заряд от предыдущего канала тянет результат за собой. Увеличьте время выборки.
АЦП врёт после разгона. Превышена частота ADCCLK. Блок работает,
но результат недостоверен, и никакого признака ошибки нет.
Вывод не переведён в аналоговый режим. Цифровая обвязка остаётся подключённой и искажает измерение.
Забыта калибровка на F1. Погрешность заметно выше ожидаемой.
При сканировании читается только первый канал. Не включён DMA — остальные результаты затёрты.
Массив под DMA не volatile. Компилятор кэширует значение, и программа
видит одно и то же число.
Шум в измерениях. Аналоговая часть чувствительна к разводке: земля аналоговая и цифровая должны сходиться в одной точке, рядом со входом нужен конденсатор, длинных проводов лучше избегать. Программное усреднение нескольких отсчётов помогает, но не заменяет нормальную схемотехнику.
Опора не там, где кажется. Точность привязана к VDDA. Если питание
берётся от USB через диод, оно вовсе не 3,3 В — и все измерения смещены.
Показания плывут вместе с батареей. код × 3300 / 4095 верен только
при 3,3 В. Питание считают по VREFINT при каждом измерении (14.9).
Внутренние каналы дают ноль. Не включён TSVREFE: у F1 он в ADC1->CR2,
у F4 — в общем ADC->CCR.
14.12 Итоги
- Разрядность 12 бит, шаг при опоре 3,3 В — около 0,8 мВ; точность не лучше точности опоры.
- Время выборки подбирается под сопротивление источника: у F1 1,5 такта допускают источник до 0,4 кОм, 55,5 — до 50 кОм; нехватка даёт заниженные и правдоподобные показания.
- У F1
ADCCLKне выше 14 МГц — при 72 МГц годятся только делители 6 и 8. - У F1 калибровка обязательна, у F4 программной калибровки нет (раздел 14а).
- Первая запись
ADONвключает блок, вторая запускает преобразование. - Сканирование без DMA теряет данные: регистр результата один на все каналы.
- Правильная схема непрерывных измерений — «таймер запускает АЦП, DMA забирает результат», без участия процессора.
VREFINTпозволяет измерить собственное питание без внешних деталей: у F4VDDA = 3300 × VREFINT_CAL / кодпо заводскому коду, у F1 — по номиналу 1,2 В, и ошибка опоры переходит в ошибку питания один к одному. Время выборки для внутренних каналов — самое длинное.- Аналоговый сторож следит за выходом за пределы сам, без опроса.
Полные исходники
adc_f0.c — АЦП STM32F072: калибровка, VREFINT, питание в милливольтах.
/* Раздел 14.10: АЦП STM32F072 — калибровка, VREFINT на канале 17, питание в мВ. */
#include <stddef.h>
#include "stm32f0xx.h"
#include "stm32f0xx_ll_adc.h"
_Static_assert(offsetof(ADC_TypeDef, CHSELR) == 0x28u, "очередь каналов — битовая маска CHSELR");
_Static_assert(offsetof(ADC_TypeDef, DR) == 0x40u, "данные");
_Static_assert(VREFINT_CAL_VREF == 3300u, "заводской код VREFINT снят при 3,3 В");
_Static_assert(TEMPSENSOR_CAL2_TEMP == 110, "вторая точка датчика температуры — 110 °C");
_Static_assert(LL_ADC_DELAY_VREFINT_STAB_US == 10u, "VREFINT устанавливается 10 мкс");
/* такт АЦП PCLK / 4 = 12 МГц; выборка 239,5 такта
* + преобразование 12,5 — 252 такта, 21 мкс на одно измерение */
#define ADC_HZ 12000000u
_Static_assert((2395u + 125u) / 10u == 252u, "тактов на измерение");
_Static_assert(252u * 1000000u / ADC_HZ == 21u, "мкс на измерение при 12 МГц");
#define CH_VREFINT ADC_CHSELR_CHSEL17 /* у F0 VREFINT — канал 17 */
void adc_init_f0(void)
{
RCC->APB2ENR |= RCC_APB2ENR_ADCEN;
ADC1->CFGR2 = ADC_CFGR2_CKMODE_1; /* синхронный такт PCLK / 4 */
ADC1->CR |= ADC_CR_ADCAL; /* калибровка — при выключенном АЦП */
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY)) {
}
ADC1_COMMON->CCR |= ADC_CCR_VREFEN; /* внутренний источник — на вход АЦП */
ADC1->SMPR = LL_ADC_SAMPLINGTIME_239CYCLES_5;
}
uint16_t adc_read_f0(uint32_t channel_mask)
{
ADC1->CHSELR = channel_mask; /* один бит на канал, не номер в SQR */
ADC1->CR |= ADC_CR_ADSTART;
while (!(ADC1->ISR & ADC_ISR_EOC)) {
}
return (uint16_t)ADC1->DR;
}
/* питание VDDA по коду VREFINT и заводскому коду, снятому при 3,3 В */
uint32_t vdda_mv_f0(void)
{
uint16_t code = adc_read_f0(CH_VREFINT);
return (VREFINT_CAL_VREF * (uint32_t)*VREFINT_CAL_ADDR + code / 2u) / code;
}
adc_g0.c — АЦП STM32G071: регулятор, калибровка, VREFINT, передискретизация до 16 бит.
/* Раздел 14.10: АЦП STM32G071 — регулятор, калибровка, VREFINT на канале 13,
* передискретизация до 16 бит. */
#include <stddef.h>
#include "stm32g0xx.h"
#include "stm32g0xx_ll_adc.h"
_Static_assert(offsetof(ADC_TypeDef, CHSELR) == 0x28u, "очередь каналов — как у F0");
_Static_assert(offsetof(ADC_TypeDef, CALFACT) == 0xB4u, "калибровочный коэффициент — свой регистр");
_Static_assert(VREFINT_CAL_VREF == 3000u, "заводской код VREFINT снят при 3,0 В, не 3,3");
_Static_assert(TEMPSENSOR_CAL2_TEMP == 130, "вторая точка датчика температуры — 130 °C");
_Static_assert(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US == 20u, "регулятору АЦП нужно 20 мкс");
/* такт АЦП PCLK / 4 = 16 МГц; выборка 160,5 такта + 12,5 — 173 такта, 10,8 мкс */
#define ADC_HZ 16000000u
_Static_assert((1605u + 125u) / 10u == 173u, "тактов на измерение");
_Static_assert(173u * 1000u / (ADC_HZ / 1000000u) == 10812u, "10,8 мкс на измерение при 16 МГц");
#define CH_VREFINT ADC_CHSELR_CHSEL13 /* у G0 VREFINT — канал 13 */
static void wait_us(uint32_t us)
{
for (volatile uint32_t n = us * (64000000u / 1000000u / 4u); n; n--) {
}
}
void adc_init_g0(void)
{
RCC->APBENR2 |= RCC_APBENR2_ADCEN;
ADC1->CR |= ADC_CR_ADVREGEN; /* сначала регулятор — и 20 мкс ожидания */
wait_us(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US);
ADC1->CFGR2 = ADC_CFGR2_CKMODE_1; /* синхронный такт PCLK / 4 */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY)) {
}
ADC1_COMMON->CCR |= ADC_CCR_VREFEN;
ADC1->SMPR = LL_ADC_SAMPLINGTIME_160CYCLES_5; /* поле SMP1; SMPSEL выбирает его для всех каналов */
}
uint16_t adc_read_g0(uint32_t channel_mask)
{
ADC1->ISR = ADC_ISR_CCRDY; /* флаг сбрасывается записью единицы */
ADC1->CHSELR = channel_mask;
while (!(ADC1->ISR & ADC_ISR_CCRDY)) { /* у G0 смена каналов вступает не сразу */
}
ADC1->CR |= ADC_CR_ADSTART;
while (!(ADC1->ISR & ADC_ISR_EOC)) {
}
return (uint16_t)ADC1->DR;
}
/* 256 измерений складываются в АЦП и делятся на 16: 16-битный результат без участия ядра */
void adc_oversample16_g0(void)
{
ADC1->CFGR2 |= ADC_CFGR2_OVSE | LL_ADC_OVS_RATIO_256 | LL_ADC_OVS_SHIFT_RIGHT_4;
}
/* питание VDDA по коду VREFINT и заводскому коду, снятому при 3,0 В */
uint32_t vdda_mv_g0(void)
{
uint16_t code = adc_read_g0(CH_VREFINT);
return (VREFINT_CAL_VREF * (uint32_t)*VREFINT_CAL_ADDR + code / 2u) / code;
}
Дальше: раздел 14а «Ошибки АЦП: смещение, усиление, нелинейность и шум» — насколько этому коду можно верить и что из его ошибок калибруется.