15а. АЦП нового образца на G4: дифференциальный вход, передискретизация до 16 бит, две калибровки
Раздел 14 разобрал АЦП F1 и F4 и отдельно — упрощённый АЦП F0 и G0 (14.10).
У G4, L4, H7 и U5 АЦП другого поколения: регистры CR, ISR и CFGR вместо
CR1 и CR2, регулятор, который надо включать, глубокий сон после сброса,
дифференциальные входы, аппаратная передискретизация до 16 бит, смещение
и коррекция усиления прямо в блоке. Здесь — на G474: что это даёт, как
включить по регистрам и где этот АЦП ловит переносящего код.
Опоры: справочное руководство RM0440 (G4, глава ADC) и даташит STM32G474 (DS12288) из пака Keil, заголовок LL G4. Таблица результатов передискретизации из RM0440 воспроизведена расчётом полностью; код собран armclang для G474. QEMU этот АЦП не моделирует.
Как это называется в документации
Оригинал Перевод single-ended, differential input несимметричный и дифференциальный вход oversampling ratio (OVSR), shift (OVSS) сколько измерений сложить и на сколько бит сдвинуть сумму ADCALDIF калибровка для дифференциальных входов CALFACT_S, CALFACT_D калибровочные коэффициенты: несимметричный и дифференциальный deep power-down (DEEPPWD) глубокий сон АЦП после сброса voltage regulator (ADVREGEN) внутренний регулятор АЦП gain compensation (GCOMP) коррекция усиления: код умножается на коэффициент offset (OFRy) вычитание постоянного смещения из результата
15а.1 Что нового по сравнению с F4
У G474 пять 12-битных АЦП; даташит обещает измерение за 0,25 мкс и до 16 бит с аппаратной передискретизацией. Отличия от F1 и F4:
- после сброса АЦП в глубоком сне (
DEEPPWD= 1) и с выключенным регулятором: сначала снять DEEPPWD, включитьADVREGENи подождать 20 мкс (LL_ADC_DELAY_INTERNAL_REGUL_STAB_USв LL); - две калибровки: отдельно для несимметричных и для дифференциальных входов;
- дифференциальный вход на любом канале, у которого есть пара;
- передискретизация — сумма до 256 измерений со сдвигом, в регистре данных до 16 бит;
- смещение (четыре регистра
OFRy) и коррекция усиления (GCOMP) внутри блока.
Регистры другие: код с F4 (CR2, SQR3) не соберётся — и это хорошо,
ошибку видно сразу.
15а.2 Такт и время измерения
Время одного измерения (RM0440, 21.4.12):
TCONV = время выборки + 12,5 такта АЦП.
Самое короткое время выборки — 2,5 такта, самое длинное — 640,5. Такт АЦП по даташиту — не выше 60 МГц для одного АЦП (52 МГц, если работают все при VDDA не ниже 2,7 В):
| Такт АЦП | Выборка | Измерение | Измерений в секунду |
|---|---|---|---|
| 60 МГц | 2,5 | 0,250 мкс | 4,000 млн |
| 52 МГц | 2,5 | 0,288 мкс | 3,467 млн |
| 42,5 МГц | 2,5 | 0,353 мкс | 2,833 млн |
| 60 МГц | 640,5 | 10,883 мкс | 0,092 млн |
Такт выбирает CKMODE в общем регистре ADC12_COMMON->CCR (RM0440, 21.4.3):
00 — отдельный асинхронный такт от RCC (системный или PLL «P»), 01, 10, 11 —
HCLK, делённый на 1, 2 или 4. У G474 HCLK — до 170 МГц, поэтому HCLK / 1
и HCLK / 2 для АЦП слишком быстрые; синхронно остаётся HCLK / 4 = 42,5 МГц.
Для 60 МГц и 0,25 мкс нужен асинхронный такт от PLL. Синхронный такт хорош
другим: задержка от события таймера до начала измерения без дрожания.
15а.3 Включение и две калибровки
Калибровка на G4 обязательна и делается при выключенном АЦП (ADEN = 0).
Коэффициент для несимметричных входов и для дифференциальных — разный
(RM0440, 21.4.8): бит ADCALDIF выбирает, какой считать. Если АЦП будет
мерить и те и другие, калибруют дважды — и дальше АЦП сам подставляет
нужный коэффициент при переходе между каналами:
void adc_power_and_calibrate(void)
{
RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN;
ADC12_COMMON->CCR = 3u << ADC_CCR_CKMODE_Pos; /* HCLK / 4: 170 / 4 = 42,5 МГц, не больше 60 */
ADC1->CR &= ~ADC_CR_DEEPPWD; /* после сброса АЦП в глубоком сне */
ADC1->CR |= ADC_CR_ADVREGEN;
wait_us(20); /* LL_ADC_DELAY_INTERNAL_REGUL_STAB_US */
ADC1->CR &= ~ADC_CR_ADCALDIF; /* 1: несимметричные входы → CALFACT_S */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR |= ADC_CR_ADCALDIF; /* 2: дифференциальные → CALFACT_D */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR &= ~ADC_CR_ADCALDIF;
ADC1->ISR = ADC_ISR_ADRDY;
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY)) {
}
}
Коэффициенты — 7 бит каждый — лежат в ADC_CALFACT. Питание АЦП
пропадает в Standby и VBAT, и калибровка вместе с ним; чтобы не калибровать
заново, их можно сохранить и записать обратно (RM0440, 21.4.8).
15а.4 Дифференциальный вход
Бит канала в DIFSEL переводит его в дифференциальный режим; писать
DIFSEL можно только при выключенном АЦП. Код по RM0440:
разность −VREF+ — 0x000, ноль — 0x800, +VREF+ — 0xFFF. То есть
код = 2048 + 2048 × (VINP − VINN) / VREF+.
| VINP − VINN при VREF+ = 3,3 В | Код |
|---|---|
| −3,3 В | 0x000 |
| −1,0 В | 0x593 |
| 0 | 0x800 |
| +1,0 В | 0xA6D |
| +3,3 В | 0xFFF |
Единица кода — 1,611 мВ против 0,806 мВ у несимметричного входа: RM прямо пишет, что чувствительность вдвое ниже. Зато синфазная помеха вычитается, а знак разности виден сразу. Оба входа надо держать около VREF+/2 — синфазное напряжение не должно гулять.
Отрицательный вход дифференциального канала — вывод другого канала: тот
канал больше нельзя мерить ни как несимметричный, ни как
дифференциальный. Внутренние каналы — VREFINT, датчик температуры —
только несимметричные.
15а.5 Передискретизация до 16 бит
АЦП сам складывает N измерений (от 2 до 256, поле OVSR), сдвигает
сумму вправо на M бит (0–8, поле OVSS) с округлением и кладёт в DR.
Регистр данных — 16 бит, и если сумма после сдвига длиннее, старшие биты
теряются. Наибольший результат для кода 0xFFF (RM0440, таблица 172; расчёт
совпал с ней во всех 80 ячейках):
| N | Сумма | Сдвиг 0 | 2 | 4 | 8 |
|---|---|---|---|---|---|
| 16 | 0xFFF0 | 0xFFF0 | 0x3FFC | 0x0FFF | 0x0100 |
| 64 | 0x3FFC0 | 0xFFC0 (усечено) | 0xFFF0 | 0x3FFC | 0x0400 |
| 256 | 0xFFF00 | 0xFF00 (усечено) | 0xFFC0 (усечено) | 0xFFF0 | 0x0FFF |
Три сочетания дают полные 16 бит без усечения: 16 измерений без сдвига, 64 со сдвигом на 2 и 256 со сдвигом на 4 — везде 0xFFF0. 256 измерений по 0,25 мкс — результат раз в 64 мкс, 15,6 кГц; 16 измерений — раз в 4 мкс, 250 кГц.
void adc_oversample_16bit(void)
{
ADC1->CFGR2 = ADC_CFGR2_ROVSE | (OVSR_256 << ADC_CFGR2_OVSR_Pos) | (OVSS_4 << ADC_CFGR2_OVSS_Pos);
}
armclang превращает её в одну запись 0x9D по адресу 0x5000 0010 — это
ADC1->CFGR2.
Сколько бит на самом деле даёт сумма, зависит от шума: складывать одинаковые коды бесполезно. Модель на ПК — АЦП с шумом 1 единица кода, случайные напряжения:
| N | Сдвиг | Остаточный шум, единиц 12 бит | Бит сверх 12 |
|---|---|---|---|
| 1 | 0 | 1,039 | 0,0 |
| 4 | 1 | 0,552 | 0,9 |
| 16 | 2 | 0,271 | 1,9 |
| 64 | 3 | 0,136 | 2,9 |
| 256 | 4 | 0,069 | 3,9 |
Каждые четыре измерения — около одного бита: шум падает как √N (раздел 165). 256 измерений дают почти 4 бита — это и есть 16 бит из 12-битного АЦП. Если шума нет, дополнительные биты ложные: их добавляют подмешиванием (раздел 165).
15а.6 Смещение и коррекция усиления
Смещение: регистр OFRy (их четыре) привязывается к каналу и вычитает
из результата заданное число; результат становится знаковым. Коррекция
усиления: код умножается на GCOMPCOEFF / 4096, от 0 до 3,999756
(RM0440); 0x1000 — ровно 1. Коэффициент 0x1040 (1,01562) превращает
код 3000 примерно в 3047, 0x0FC0 (0,98438) — в 2953. Так поправляют
заводской разброс делителя на входе, не тратя на это процессор. Включает
коррекцию бит GCOMP в том же регистре, что и передискретизацию; запись
в него целиком, как в листинге ниже, этот бит сбрасывает.
С передискретизацией смещение не работает: при включённой
передискретизации биты OFFSETy_EN игнорируются (RM0440).
15а.7 Грабли
- АЦП молчит после сброса. Не снят
DEEPPWDили не включёнADVREGENс ожиданием 20 мкс: калибровка не кончится. - Одна калибровка на два вида входов. Дифференциальный канал
с несимметричной калибровкой меряет со смещением. Калибровать дважды
(
ADCALDIF= 0 и 1). - HCLK / 1 на 170 МГц. Синхронный такт без делителя у G474 даст 170 МГц на АЦП при пределе 60. HCLK / 4 или асинхронный такт от PLL.
- Сдвиг меньше нужного. 256 измерений без сдвига — сумма 0xFFF00, в
DRостаётся 0xFF00: старшие 4 бита потеряны, результат «плавает» непонятно как. - Смещение и передискретизация вместе.
OFRyпри передискретизации не действует. DIFSELна включённом АЦП. RM0440 разрешает писатьDIFSELтолько при выключенном АЦП: дифференциальный режим выставляют доADEN.- Соседний канал дифференциальной пары в очереди. Его вывод уже занят отрицательным входом — измерять его нельзя.
15а.8 Итоги
- АЦП G4: глубокий сон и регулятор после сброса, две калибровки, дифференциальные входы, передискретизация, смещение и усиление в блоке.
- TCONV = выборка + 12,5 такта: 0,25 мкс при 60 МГц; у G474 синхронно — HCLK / 4 = 42,5 МГц, 0,353 мкс.
- Дифференциальный код: 0x800 — ноль, единица — 1,611 мВ при 3,3 В.
- Передискретизация: сумма N измерений со сдвигом M; 16 бит без усечения — 16×0, 64×2, 256×4; каждые 4 измерения — около бита, если есть шум.
- Коррекция усиления: код ×
GCOMPCOEFF/ 4096; смещение с передискретизацией не работает.
Полные исходники
adc15a.c — АЦП STM32G474 по регистрам: калибровки, дифференциальный канал, передискретизация.
/* 15а: АЦП STM32G474 по регистрам — две калибровки, дифференциальный канал, передискретизация 256x.
Собирается armclang: -DSTM32G474xx. */
#include <stdint.h>
extern void wait_us(uint32_t us);
#define OVSR_256 7u /* CFGR2.OVSR: 000 — 2x … 111 — 256x */
#define OVSS_4 4u /* сдвиг на 4 бита: 12 + 8 − 4 = 16 бит */
_Static_assert(ADC_CFGR2_OVSR_Pos == 2 && ADC_CFGR2_OVSS_Pos == 5, "поля передискретизации в CFGR2");
_Static_assert(((OVSR_256 << ADC_CFGR2_OVSR_Pos) | (OVSS_4 << ADC_CFGR2_OVSS_Pos)) == 0x9Cu, "256x, сдвиг 4");
void adc_power_and_calibrate(void)
{
RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN;
ADC12_COMMON->CCR = 3u << ADC_CCR_CKMODE_Pos; /* HCLK / 4: 170 / 4 = 42,5 МГц, не больше 60 */
ADC1->CR &= ~ADC_CR_DEEPPWD; /* после сброса АЦП в глубоком сне */
ADC1->CR |= ADC_CR_ADVREGEN;
wait_us(20); /* LL_ADC_DELAY_INTERNAL_REGUL_STAB_US */
ADC1->CR &= ~ADC_CR_ADCALDIF; /* 1: несимметричные входы → CALFACT_S */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR |= ADC_CR_ADCALDIF; /* 2: дифференциальные → CALFACT_D */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL) {
}
ADC1->CR &= ~ADC_CR_ADCALDIF;
ADC1->ISR = ADC_ISR_ADRDY;
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY)) {
}
}
/* канал ch — дифференциальный: его отрицательный вход занимает другой канал (RM0440) */
void adc_differential(uint32_t ch)
{
ADC1->DIFSEL |= 1u << ch; /* DIFSEL пишут при ADEN = 0 */
}
/* 16 бит из 256 измерений: 64 мкс на результат при 0,25 мкс на измерение */
void adc_oversample_16bit(void)
{
ADC1->CFGR2 = ADC_CFGR2_ROVSE | (OVSR_256 << ADC_CFGR2_OVSR_Pos) | (OVSS_4 << ADC_CFGR2_OVSS_Pos);
}
uint16_t adc_read(uint32_t ch)
{
ADC1->SQR1 = ch << ADC_SQR1_SQ1_Pos; /* L = 0: одно измерение в очереди */
ADC1->CR |= ADC_CR_ADSTART;
while (!(ADC1->ISR & ADC_ISR_EOC)) {
}
return (uint16_t)ADC1->DR;
}
Дальше: раздел 15б «Внешний АЦП ADS1115 по I2C: 16 бит, усилитель PGA, мультиплексор, режимы преобразования и вывод ALERT/RDY» — когда встроенных 12 бит мало: внешний дельта-сигма АЦП на шине I2C.