15б. Внешний АЦП ADS1115 по I2C: 16 бит, усилитель PGA, мультиплексор, режимы преобразования и вывод ALERT/RDY
В разделе 14 — встроенный АЦП F1 и F4: 12 бит, последовательное приближение, опора — питание. В 15а у G4 появились дифференциальный вход и 16 бит передискретизацией, но у F103 их нет. Когда на F103 нужно измерить шунт, мост или термопару — малую разность, со знаком, точнее 12 бит, — ставят внешний АЦП. ADS1115 — самый ходовой: 16 бит, четыре входа, усилитель с выбором диапазона, I2C и вывод «готово». Здесь — чем он отличается от встроенного, как подключить к F103, какие регистры нужны, как ждать результат, как перевести код в микровольты, сколько времени уходит на канал, как работают RDY и компаратор и что делать с шумом и сетевой помехой.
Опоры: RM0008 (DocID13902 Rev 17): таблица режимов выводов, с. 160;
AFIO_EXTICR2, с. 190; EXTI_FTSR, с. 211; приём двух байт ведущим,
с. 764; I2C_CCR, с. 783–784; I2C_TRISE, с. 784. DocID13587 Rev 17
(STM32F103x8/xB): PB6 — I2C1_SCL, PB7 — I2C1_SDA, оба FT, PB5 без FT,
с. 32. Документа на ADS1115 в проекте нет: регистры, биты Config,
коды и диапазоны, скорости, адреса, значения после включения, поведение
OS и ALERT/RDY взяты из документации производителя и заложены в
модель. Поэтому всё ниже проверяет согласие драйвера с моделью, а не с
микросхемой. Все числа — прогон драйвера _verify/ads15c/ads15c.c на
компьютере (gcc 12.2.0) со своей моделью ADS1115 как ведомого I2C на
Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3.
Как это называется в документации
Оригинал Перевод delta-sigma ADC дельта-сигма АЦП: много грубых измерений и цифровой фильтр programmable gain amplifier (PGA) усилитель с программным коэффициентом — здесь выбор диапазона full-scale range (FSR) диапазон: от −FS до +FS input multiplexer (MUX) мультиплексор входов single-ended / differential input вход против земли / разность двух входов single-shot / continuous-conversion mode однократное / непрерывное преобразование operational status (OS) бит «начать» при записи и «идёт преобразование» при чтении data rate (DR), SPS скорость: преобразований в секунду address pointer register регистр указателя: с каким регистром обмен conversion ready (RDY) результат готов comparator queue (COMP_QUE) сколько преобразований подряд до срабатывания window / latching comparator компаратор-окно / с защёлкой two's complement дополнительный код
15б.1 Чем он отличается от встроенного АЦП
Встроенный АЦП F1 — последовательное приближение: короткая выборка на конденсатор (180.1) и 12 шагов подбора кода, при 12 МГц — 1,17 мкс на измерение (14.2). ADS1115 по документации производителя устроен иначе: дельта-сигма модулятор всё время преобразования грубо измеряет вход, а цифровой фильтр сводит эти измерения в 16-битный код. Отсюда всё остальное:
| Встроенный АЦП F1 | ADS1115 | |
|---|---|---|
| разрядность | 12 бит, только плюс | 16 бит со знаком |
| принцип | последовательное приближение | дельта-сигма с фильтром |
| время на результат | 1,17 мкс при 12 МГц | 1/DR: от 125 мс до 1162,8 мкс |
| вход | от 0 до VREF+ | разность двух входов или вход против GND |
| диапазон | 0…VREF+, задаёт питание | от ±6,144 до ±0,256 В, задаёт PGA |
| связь | шина микроконтроллера, DMA | I2C, два байта на результат |
Встроенный быстрее в тысячу раз и годится для осциллограммы. Внешний медленный, зато сам усредняет вход за время преобразования (в модели фильтр — ровно такое усреднение), меряет разность со знаком и на диапазоне ±0,256 В даёт единицу кода 7,8125 мкВ — для шунта и моста то, что нужно. Время выборки у него не настраивается: за него отвечает скорость DR.
Код — дополнительный, 32768 единиц на половину диапазона. Вход против GND пользуется только положительной половиной — это 15 бит, а не 16.
15б.2 Подключение к F103
| Вывод ADS1115 | STM32F103 | Назначение |
|---|---|---|
| SCL | PB6 | такт I2C1 |
| SDA | PB7 | данные I2C1 |
| ALERT/RDY | PB5 | «готово» или порог; EXTI5 по спаду |
| ADDR | GND | адрес 0x48 |
| VDD, GND | 3,3 В и земля | общая земля с микроконтроллером |
PB6 и PB7 — выводы I2C1, оба FT, то есть терпят 5 В (DocID13587,
с. 32): модуль, запитанный от 5 В, со своими подтяжками к 5 В их не
сожжёт. PB5 — без FT. ALERT/RDY по документации производителя —
открытый сток; подтяжку даёт сам PB5, вход с подтяжкой (код 1000 в
таблице режимов, с. 160), и тянуть его к 5 В нельзя. Проще всего питать
ADS1115 от 3,3 В — тогда и входы не выйдут за питание (15б.7).
Адрес задаёт вывод ADDR (по документации производителя): на GND —
0x48, на VDD — 0x49, на SDA — 0x4A, на SCL — 0x4B. Так на одну шину
садятся четыре микросхемы, 16 входов. В модели при каждом из четырёх
подключений ads_probe нашла один адрес, а остальные три ответили
отказом — ADS_E_NACK. В линии адрес 0x48 — байты 0x90 и 0x91
(17.2).
I2C1 — как в 17.3, но на 400 кГц, ADS1115 по документации их держит.
В быстром режиме при DUTY = 0 низкий уровень SCL — 2 × CCR,
высокий — CCR тактов APB1 (с. 784): 36 МГц / (3 × 30) = 400 кГц.
TRISE — предельный фронт плюс один: 300 нс × 36 МГц = 10,8, а если не
целое, берут целую часть (с. 784) — 11:
static void i2c1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
/* PB6, PB7 — альтернативная функция, открытый сток, 50 МГц (1111); PB5 — вход с подтяжкой (1000) */
GPIOB->CRL = (GPIOB->CRL & 0x000FFFFFu) | 0xFF800000u;
GPIOB->BSRR = 1u << 5;
I2C1->CR1 = 0; /* настраивать при PE = 0 */
I2C1->CR2 = APB1_MHZ;
I2C1->CCR = I2C_CCR_FS | 30u; /* DUTY = 0: 36 МГц / (3 × 30) = 400 кГц */
I2C1->TRISE = APB1_MHZ * 300u / 1000u + 1u; /* 300 нс × 36 МГц = 10,8 → 10 + 1 = 11 */
I2C1->CR1 = I2C_CR1_PE;
}
RM0008 (с. 783) оговаривает: чтобы получить ровно 400 кГц, частота APB1
должна быть кратна 10 МГц, и настоящая частота может отличаться из-за
фильтра на входе. В модели шина — ровно 400 кГц. ALERT/RDY идёт на
EXTI5: AFIO_EXTICR2, биты 7:4 = 0001 — порт B (с. 190), спад включает
EXTI_FTSR (с. 211).
15б.3 Регистры: указатель, Conversion, Config, пороги
Регистров четыре, по 16 бит, и ещё регистр указателя. Первый байт любой
записи попадает в указатель и выбирает регистр: 0 — Conversion
(результат, только чтение), 1 — Config, 2 — Lo_thresh, 3 —
Hi_thresh. Запись трёх байт — указатель, старший и младший байт
значения. Чтение отдаёт два байта регистра под указателем, старшим
вперёд, и указатель не меняет.
Первый обмен в модели после включения — ads_probe, чтение Config:
байты 90 01, повторный старт, 91 — и в ответ 85 83. После
включения Config = 0x8583, Lo_thresh = 0x8000, Hi_thresh = 0x7FFF,
Conversion = 0x0000.
| Биты | Поле | Что задаёт | После включения |
|---|---|---|---|
| 15 | OS |
запись 1 — начать однократное; чтение 0 — идёт преобразование | 1 |
| 14:12 | MUX |
000 — AIN0 − AIN1, 001 — AIN0 − AIN3, 010 — AIN1 − AIN3, 011 — AIN2 − AIN3, 100…111 — AIN0…AIN3 против GND | 000 |
| 11:9 | PGA |
000 — ±6,144 В, 001 — ±4,096, 010 — ±2,048, 011 — ±1,024, 100 — ±0,512, 101…111 — ±0,256 | 010 |
| 8 | MODE |
1 — однократное, 0 — непрерывное | 1 |
| 7:5 | DR |
000…111 — 8, 16, 32, 64, 128, 250, 475, 860 SPS | 100 |
| 4 | COMP_MODE |
0 — компаратор с гистерезисом, 1 — окно | 0 |
| 3 | COMP_POL |
0 — ALERT активен нулём |
0 |
| 2 | COMP_LAT |
1 — держать ALERT до чтения результата |
0 |
| 1:0 | COMP_QUE |
00, 01, 10 — сработать после 1, 2, 4 преобразований; 11 — выключен | 11 |
Чтение регистра в драйвере помнит, куда смотрит указатель: если туда же, лишний байт не нужен.
int ads_read(ads_t *d, uint8_t reg, uint16_t *v)
{
uint8_t b[2];
int rc = d->ptr == reg ? xfer(d, 0, 0, b, 2) /* указатель уже на месте: только чтение */
: xfer(d, ®, 1, b, 2); /* иначе запись указателя и повторный старт */
if (rc == ADS_OK) {
d->ptr = reg;
*v = (uint16_t)(b[0] << 8 | b[1]);
}
return rc;
}
Обмен с записью указателя и повторным стартом — 48 тактов SCL, 120 мкс
на 400 кГц; чтение без указателя — 29 тактов, 72,5 мкс; запись Config
— 38 тактов, 95 мкс. Драйвер для «однократно, AIN0, ±4,096 В, 128 SPS»
записал в Config 0xC383 — модель собрала то же число по полям
таблицы независимо; совпали все четыре проверенных сочетания.
15б.4 Однократное и непрерывное, четыре канала по очереди
Однократное (MODE = 1): запись Config с OS = 1 запускает одно
преобразование, после него микросхема засыпает. Конец узнают по OS
(чтение 1 — готово) или по выводу RDY (15б.6). Опрашивать OS сразу
бессмысленно: результат будет не раньше чем через 1/DR. Драйвер сначала
ждёт это время, потом опрашивает раз в 100 мкс:
/* однократное: OS = 1 запускает; сначала ждём расчётное время, потом опрашиваем OS */
int ads_single(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code)
{
uint16_t c;
uint32_t t = ads_conv_us(dr);
int rc = ads_write(d, ADS_CONFIG, ads_cfg(mux, pga, dr, 1) | ADS_QUE_OFF);
if (rc)
return rc;
ads_port_delay_us(t);
for (uint32_t waited = t;; waited += ADS_POLL_US) {
if ((rc = ads_read(d, ADS_CONFIG, &c)) != ADS_OK)
return rc;
d->polls++;
if (c & ADS_OS) /* 1 — преобразование закончено */
break;
if (waited > 2u * t + 10000u)
return ADS_E_TIMEOUT;
ads_port_delay_us(ADS_POLL_US);
}
return ads_read_conv(d, code);
}
В модели на 400 кГц, вход 1 В, ±2,048 В — код 16000 при любой скорости:
| DR | 1/DR | OS после паузы |
обменов | OS без паузы, опросов |
по RDY |
обменов |
|---|---|---|---|---|---|---|
| 8 SPS | 125 000 мкс | 125 287,5 мкс | 3 | 1726 | 125 212,5 мкс | 2 |
| 128 SPS | 7812,5 мкс | 8100,5 мкс | 3 | 109 | 8025 мкс | 2 |
| 250 SPS | 4000 мкс | 4287,5 мкс | 3 | 57 | 4212,5 мкс | 2 |
| 860 SPS | 1162,8 мкс | 1450,5 мкс | 3 | 18 | 1375,3 мкс | 2 |
1450,5 мкс на 860 SPS — это 95 мкс записи Config, пауза 1163 мкс,
72,5 мкс чтения Config и 120 мкс чтения результата с записью
указателя. Опрос без паузы на 8 SPS — 1726 чтений подряд: шина занята
всё преобразование, и остальным устройствам на ней места нет.
Непрерывное (MODE = 0): преобразования идут одно за другим,
Conversion всегда хранит последнее. Это для одного канала. Сменили
вход — в регистре ещё старый результат: в модели непрерывное на AIN0
(код 4000) переключили на AIN3 и сразу прочитали — 4000; после
ближайшего RDY — 16000. В модели запись Config начинает
преобразование заново; так ли у микросхемы, не проверено — надёжнее
выбросить первый результат после смены входа.
Четыре канала по очереди удобнее однократными преобразованиями: каждый начинается уже с новым входом.
/* четыре несимметричных входа по очереди, каждый — однократным преобразованием */
int ads_scan4(ads_t *d, uint32_t pga, uint32_t dr, int16_t out[4])
{
for (uint32_t ch = 0; ch < 4u; ch++) {
int rc = ads_single(d, ADS_AIN0 + ch, pga, dr, &out[ch]);
if (rc != ADS_OK && rc != ADS_E_OVER)
return rc;
}
return ADS_OK;
}
Входы 0,5, 1,0, 1,5 и 2,0 В на ±4,096 В — коды 4000, 8000, 12000 и 16000, на круг 12 обменов и 4 опроса. Время на канал:
| DR | 1/DR | на канал, 100 кГц | на канал, 400 кГц |
|---|---|---|---|
| 8 SPS | 125 000 мкс | 126 150 мкс | 125 287,5 мкс |
| 128 SPS | 7812,5 мкс | 8963 мкс | 8100,5 мкс |
| 250 SPS | 4000 мкс | 5150 мкс | 4287,5 мкс |
| 475 SPS | 2105,3 мкс | 3256 мкс | 2393,5 мкс |
| 860 SPS | 1162,8 мкс | 2313 мкс | 1450,5 мкс |
Шина добавляет к преобразованию 287,7 мкс на 400 кГц и 1150,2 мкс на 100 кГц: на 860 SPS при 100 кГц канал занимает почти вдвое больше времени, чем само преобразование. Все четыре входа на 860 SPS и 400 кГц — 5802 мкс.
15б.5 Код в микровольты, знак, шум и скорость
Единица кода — диапазон PGA / 32768. Перевод целыми: код × FS в микровольтах / 32768 с округлением. Произведение не влезает в 32 бита (32767 × 6 144 000), поэтому оно 64-битное, а деление на 32768 — сдвигом:
/* код → микровольты целыми: код × FS / 32768 с округлением, без деления */
int32_t ads_uv(int16_t code, uint32_t pga)
{
int64_t x = (int64_t)code * fs_mv[pga > 5u ? 5u : pga] * 1000; /* PGA 110 и 111 — тоже ±0,256 В */
return (int32_t)(x >= 0 ? (x + 16384) >> 15 : -((-x + 16384) >> 15));
}
armclang собрал ads_uv в 84 байта: умножения SMULL и UMULL, без
вызова библиотеки. В модели, вход против GND:
| PGA | Единица кода | 0,2 В: код | обратно, мкВ | 1,0 В: код |
|---|---|---|---|---|
| ±6,144 В | 187,5 мкВ | 1067 | 200 063 | 5333 |
| ±4,096 В | 125 мкВ | 1600 | 200 000 | 8000 |
| ±2,048 В | 62,5 мкВ | 3200 | 200 000 | 16000 |
| ±1,024 В | 31,25 мкВ | 6400 | 200 000 | 32000 |
| ±0,512 В | 15,625 мкВ | 12800 | 200 000 | 32767 — край |
| ±0,256 В | 7,8125 мкВ | 25600 | 200 000 | 32767 — край |
Крайние значения: ads_uv(32767, ±6,144 В) = 6 143 813 мкВ,
ads_uv(−32768, ±2,048 В) = −2 048 000 мкВ, одна единица на ±0,256 В —
8 мкВ после округления.
Знак. Код — int16_t, не uint16_t. Разность AIN0 − AIN1 =
1,25 − 1,00 В на ±2,048 В — код 4000 (0x0FA0), 250 000 мкВ; входы
наоборот — −4000 (0xF060), −250 000 мкВ. И вход против GND бывает
отрицательным: −2 мВ на AIN0 дали −32 (0xFFE0), −2000 мкВ. Прочитанный
как uint16_t, этот код стал бы 65504.
Шум. В модели шум микросхемы задан так: 2 мкВ × √DR (с. к. о.) — это допущение, не данные документа. По 400 результатов на разность 100 мВ:
| DR | Шум модели | ±0,256 В: с. к. о., кодов | разных кодов | ±2,048 В: с. к. о., кодов | разных кодов | ±0,256 В, среднее из 16 |
|---|---|---|---|---|---|---|
| 8 SPS | 5,66 мкВ | 0,78 | 5 | 0 | 1 | 1,62 мкВ |
| 128 SPS | 22,63 мкВ | 2,91 | 15 | 0,43 | 3 | 5,2 мкВ |
| 860 SPS | 58,65 мкВ | 7,56 | 40 | 1,01 | 7 | 12,16 мкВ |
На ±2,048 В и 8 SPS все 400 результатов одинаковы: шум меньше единицы кода, и складывать нечего (14а.6, 165.8). На ±0,256 В шум — несколько кодов, и среднее из 16 снижает его примерно вчетверо. Узкий диапазон PGA и медленная скорость — первое, что даёт точность.
Сетевая помеха. Фильтр-усреднение за 1/DR гасит синус тем сильнее, чем больше его периодов укладывается в одно преобразование. Помеха 50 Гц амплитудой 1 мВ в разности, 64 результата:
| DR | Периодов 50 Гц за преобразование | Остаток помехи |
|---|---|---|
| 8 SPS | 6,25 | 31,2 мкВ |
| 16 SPS | 3,125 | 39,1 мкВ |
| 32 SPS | 1,5625 | 195,3 мкВ |
| 64 SPS | 0,7812 | 257,8 мкВ |
| 128 SPS | 0,3906 | 765,6 мкВ |
| 250 SPS | 0,2 | 929,7 мкВ |
| 475 SPS | 0,1053 | 984,4 мкВ |
| 860 SPS | 0,0581 | 992,2 мкВ |
Нуля нет ни на одной скорости: 50 Гц не кратна ни одной из них. Ноль
даёт среднее ровно за 20 мс подряд идущих результатов: на 250 SPS
ads_average из 5 — остаток 1,6 мкВ, на 860 SPS из 17 (19,767 мс) —
12,4 мкВ, на 128 SPS из 3 (23,438 мс) — 140,6 мкВ. Но приём держится на
точной частоте преобразований: в модели с частотой на 3 % ниже (окно
20,6 мс) остаток на 250 SPS — 29,7 мкВ. Насколько частота микросхемы
отличается от номинальной, не проверено.
15б.6 ALERT/RDY: «готово» и компаратор
У вывода ALERT/RDY две работы. «Готово» включается особым
сочетанием порогов (по документации производителя): старший бит
Hi_thresh = 1, старший бит Lo_thresh = 0, и COMP_QUE ≠ 11:
/* ALERT/RDY как «готово»: старший бит Hi_thresh = 1, старший бит Lo_thresh = 0, COMP_QUE ≠ 11 */
int ads_rdy_mode(ads_t *d)
{
int rc = ads_write(d, ADS_HI_THRESH, 0x8000u);
return rc ? rc : ads_write(d, ADS_LO_THRESH, 0x0000u);
}
int ads_single_rdy(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code)
{
uint32_t t = ads_conv_us(dr);
(void)ads_port_wait_rdy(0); /* снять старый флаг EXTI */
int rc = ads_write(d, ADS_CONFIG, ads_cfg(mux, pga, dr, 1) | ADS_QUE_1);
if (rc)
return rc;
if (!ads_port_wait_rdy(2u * t + 10000u))
return ADS_E_TIMEOUT;
return ads_read_conv(d, code);
}
В прошивке спад на PB5 поднимает флаг в прерывании, порт ждёт его с тайм-аутом:
/* ALERT/RDY: спад на PB5 */
void EXTI9_5_IRQHandler(void)
{
EXTI->PR = 1u << 5;
rdy = 1;
}
int ads_port_wait_rdy(uint32_t timeout_us)
{
uint32_t t0 = DWT->CYCCNT;
while (!rdy) {
if (expired(t0, timeout_us))
return 0;
}
rdy = 0;
return 1;
}
Однократное по RDY — 2 обмена вместо 3, на 860 SPS 1375,3 мкс вместо
1450,5 (15б.4), и шина между ними свободна. В непрерывном режиме RDY
— короткий импульс в конце каждого преобразования: в модели
ads_average на 860 SPS собрала 860 результатов за 1 000 070 мкс, по
одному спаду и одному обмену на результат, среднее — код 16000.
Две ошибки, при которых RDY молчит: COMP_QUE = 11 (компаратор
выключен, вывод отпущен) или пороги после включения не тронуты —
тогда это обычный компаратор с порогом 0x7FFF. В модели в обоих случаях
прошло 10 преобразований, спадов 0, а ads_average вернула
ADS_E_TIMEOUT через 12 326 мкс.
Компаратор. С обычными порогами ALERT следит за уровнем сам, без
опроса. С гистерезисом (COMP_MODE = 0) он срабатывает, когда код выше
Hi_thresh, и отпускает, когда ниже Lo_thresh; окно (COMP_MODE = 1)
— когда код вне промежутка. COMP_QUE требует 1, 2 или 4 преобразования
подряд, COMP_LAT держит срабатывание до чтения результата. В модели
вход медленно качается от 1,35 до 1,55 В (синус 0,5 Гц) с шумом датчика
5 мВ, 128 SPS, ±2,048 В, 10 с — 1280 преобразований и пять подъёмов
через 1,5 В:
| Настройка | Lo_thresh |
Hi_thresh |
Спадов ALERT |
|---|---|---|---|
| без гистерезиса, оба порога 1,50 В | 24000 | 24000 | 14 |
| гистерезис 1,40–1,50 В | 22400 | 24000 | 5 |
без гистерезиса, COMP_QUE — 4 подряд |
24000 | 24000 | 5 |
| окно 1,40–1,50 В | 22400 | 24000 | 25 |
| гистерезис и защёлка | 22400 | 24000 | 1 |
Без гистерезиса шум около порога даёт лишние срабатывания — 14 на пять подъёмов. Гистерезис или очередь из четырёх оставляют ровно пять. С защёлкой вывод сработал один раз и так и остался в нуле: результат никто не читал.
15б.7 Грабли
Вход за пределами питания. По документации производителя напряжение на входах — от GND − 0,3 В до VDD + 0,3 В, и диапазон ±6,144 В этого не отменяет. При питании 3,3 В модель отметила нарушение на 5 В, а на 3,3 и 3,6 В — нет; вход 3,3 В на ±6,144 В — код 17600 из 32767. Что сделает с 5 В настоящая микросхема, не проверено, — делитель или питание ADS1115 от 5 В.
±0,256 В на большом сигнале. Код упирается в 32767, и это похоже на
настоящее число. В модели 1 В на ±0,256 В — 32767, и ads_read_conv
вернула ADS_E_OVER. Край диапазона драйвер должен отличать от
измерения.
Шина зависла. Микроконтроллер сбросили посреди чтения, а
ADS1115 выдавал ноль — он держит SDA и ждёт тактов (17.6). В модели из
1000 случайных сбросов 508 оставили SDA в нуле. Пока шина не
освобождена, каждый обмен кончается ADS_E_BUS по тайм-ауту порта
(1000 мкс). Девять тактов SCL из 17.6 освободили все 508, больше
7 тактов не понадобилось, и следующее измерение прошло во всех 508.
Перепутан порядок байт. ADS1115 шлёт старший байт первым, а F103 —
как и компьютер — хранит младший первым. Байты 0F A0, скопированные в
int16_t как есть, дали −24561 вместо 4000; F0 60 — 24816 вместо
−4000. Собирать сдвигом, как ads_be16.
Сменил вход в непрерывном режиме и сразу прочитал. Прочитан старый вход: 4000 вместо 16000 (15б.4).
RDY молчит. COMP_QUE = 11 или пороги не переставлены — спадов
0, тайм-аут через 12 326 мкс (15б.6).
Устройство не отвечает. Вывод ADDR подключён не туда — по 0x48
ADS_E_NACK. Или в драйвер передан сдвинутый адрес 0x90 вместо 0x48
(17.2).
15б.8 Итоги
- ADS1115 — дельта-сигма, 16 бит со знаком, 8–860 SPS, PGA от ±6,144 до ±0,256 В, четыре входа или две разности, I2C; встроенный F1 — 1,17 мкс, внешний — 1162,8 мкс и дольше, зато сам усредняет.
- F103: I2C1 на PB6 и PB7 (FT), 400 кГц —
CCR=F/S| 30,TRISE= 11;ALERT/RDY— PB5 с подтяжкой на EXTI5;ADDRна GND — 0x48. - Регистр указателя выбирает регистр;
Configпосле включения 0x8583; чтение с тем же указателем — 72,5 мкс вместо 120 мкс. - Однократное: пауза 1/DR, затем
OS, — 1450,5 мкс на канал на 860 SPS и 400 кГц, 2313 мкс на 100 кГц; поRDY— 1375,3 мкс и 2 обмена. - Код × FS / 32768 целыми,
int16_t; 7,8125 мкВ на ±0,256 В; на ±2,048 В и 8 SPS шум меньше кода и среднее ничего не даёт. - 50 Гц ни одна скорость не гасит; среднее из 5 на 250 SPS за 20 мс оставляет 1,6 мкВ из 1000.
RDY: старший битHi_thresh1,Lo_thresh0,COMP_QUE≠ 11; компаратору — гистерезис: 5 срабатываний вместо 14.
Полные исходники
ads15c.h — регистры, биты Config, коды MUX, PGA и DR, порт и функции драйвера.
/* ads15c.h — раздел 15б: драйвер внешнего АЦП ADS1115 по I2C без регистров микроконтроллера.
* Драйвер говорит с микросхемой только через три функции порта (ads_port_*): в прошивке fw15c.c их
* делают I2C1 и EXTI5 STM32F103, в стенде host15c.c — модель model15c.py.
* Карта регистров, биты Config, коды PGA, скорости и адреса — по документации производителя;
* документа в проекте нет, с микросхемой не сверено. */
#ifndef ADS15C_H
#define ADS15C_H
#include <stdint.h>
/* ── регистры: номер пишется первым байтом записи в регистр указателя ── */
#define ADS_CONV 0x00u /* результат, 16 бит, дополнительный код; только чтение */
#define ADS_CONFIG 0x01u
#define ADS_LO_THRESH 0x02u
#define ADS_HI_THRESH 0x03u
/* ── Config ───────────────────────────────────────────────────────────── */
#define ADS_OS 0x8000u /* запись 1 — начать однократное; чтение 0 — идёт преобразование */
#define ADS_MUX_POS 12u /* 14:12 — вход */
#define ADS_PGA_POS 9u /* 11:9 — диапазон */
#define ADS_MODE_SINGLE 0x0100u /* 1 — однократное с засыпанием, 0 — непрерывное */
#define ADS_DR_POS 5u /* 7:5 — скорость */
#define ADS_COMP_WINDOW 0x0010u /* компаратор: 0 — с гистерезисом, 1 — окно */
#define ADS_COMP_POL_HIGH 0x0008u /* 0 — ALERT активен нулём */
#define ADS_COMP_LAT 0x0004u /* 1 — держать ALERT до чтения результата */
#define ADS_QUE_1 0x0000u /* COMP_QUE 1:0: сработать после 1, 2 или 4 преобразований */
#define ADS_QUE_2 0x0001u
#define ADS_QUE_4 0x0002u
#define ADS_QUE_OFF 0x0003u /* компаратор выключен, ALERT отпущен */
#define ADS_CONFIG_RESET 0x8583u /* после включения питания */
enum { ADS_AIN0_AIN1, ADS_AIN0_AIN3, ADS_AIN1_AIN3, ADS_AIN2_AIN3, /* MUX 000…011 — разность */
ADS_AIN0, ADS_AIN1, ADS_AIN2, ADS_AIN3 }; /* MUX 100…111 — против GND */
enum { ADS_FS_6144, ADS_FS_4096, ADS_FS_2048, ADS_FS_1024, ADS_FS_512, ADS_FS_256 }; /* PGA 000…101 */
enum { ADS_DR_8, ADS_DR_16, ADS_DR_32, ADS_DR_64, ADS_DR_128, ADS_DR_250, ADS_DR_475, ADS_DR_860 };
#define ADS_ADDR_GND 0x48u /* вывод ADDR на GND; на VDD — 0x49, на SDA — 0x4A, на SCL — 0x4B */
#define ADS_POLL_US 100u /* шаг опроса OS после расчётного времени */
enum { ADS_OK = 0, ADS_E_NACK = -1, ADS_E_BUS = -2, ADS_E_TIMEOUT = -3, ADS_E_OVER = -4 };
typedef struct {
uint8_t addr; /* семибитный адрес: 0x48…0x4B */
uint8_t ptr; /* что сейчас в регистре указателя; 0xFF — неизвестно */
uint16_t cfg; /* последнее, что драйвер записал в Config */
uint32_t xfers, polls, errors; /* обменов по I2C, опросов OS, ошибок шины */
} ads_t;
/* ── порт: в прошивке — I2C1 и EXTI5, в стенде — строки к модели ─────── */
/* записать ntx байт; если nrx > 0 — повторный старт и прочитать nrx байт; 0, ADS_E_NACK или ADS_E_BUS */
int ads_port_xfer(uint8_t addr7, const uint8_t *tx, uint32_t ntx, uint8_t *rx, uint32_t nrx);
void ads_port_delay_us(uint32_t us);
/* ждать спада на ALERT/RDY (флаг EXTI) не дольше timeout_us; 1 — был, флаг снят; 0 — не было */
int ads_port_wait_rdy(uint32_t timeout_us);
/* девять тактов SCL вручную и STOP (раздел 17.6): число тактов до отпускания SDA или −1 */
int ads_port_bus_recover(void);
/* ── драйвер ──────────────────────────────────────────────────────────── */
uint16_t ads_cfg(uint32_t mux, uint32_t pga, uint32_t dr, int single);
int ads_probe(ads_t *d, uint8_t addr7);
int ads_write(ads_t *d, uint8_t reg, uint16_t v);
int ads_read(ads_t *d, uint8_t reg, uint16_t *v);
uint32_t ads_conv_us(uint32_t dr);
int ads_single(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code);
int ads_rdy_mode(ads_t *d);
int ads_single_rdy(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code);
int ads_continuous(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, uint16_t comp);
int ads_read_conv(ads_t *d, int16_t *code);
int ads_average(ads_t *d, uint32_t n, int32_t *sum);
int ads_scan4(ads_t *d, uint32_t pga, uint32_t dr, int16_t out[4]);
int ads_thresholds(ads_t *d, int16_t lo, int16_t hi);
int ads_recover(ads_t *d);
int32_t ads_uv(int16_t code, uint32_t pga);
int16_t ads_be16(const uint8_t b[2]);
#endif
ads15c.c — драйвер: указатель, однократное и непрерывное, RDY, четыре канала, микровольты.
/* ads15c.c — раздел 15б: драйвер ADS1115 по I2C. Один и тот же файл собирается в прошивку (armclang,
* Cortex-M3) и в стенд на компьютере (gcc): микроконтроллера он не знает, только порт ads_port_*. */
#include "ads15c.h"
static const uint16_t fs_mv[6] = {6144, 4096, 2048, 1024, 512, 256}; /* PGA 000…101 */
static const uint16_t sps[8] = {8, 16, 32, 64, 128, 250, 475, 860}; /* DR 000…111 */
uint16_t ads_cfg(uint32_t mux, uint32_t pga, uint32_t dr, int single)
{
uint32_t c = (mux & 7u) << ADS_MUX_POS | (pga & 7u) << ADS_PGA_POS | (dr & 7u) << ADS_DR_POS;
return (uint16_t)(single ? c | ADS_OS | ADS_MODE_SINGLE : c);
}
static int xfer(ads_t *d, const uint8_t *tx, uint32_t ntx, uint8_t *rx, uint32_t nrx)
{
int rc = ads_port_xfer(d->addr, tx, ntx, rx, nrx);
d->xfers++;
if (rc) {
d->errors++;
d->ptr = 0xFF; /* неизвестно, дошёл ли указатель */
}
return rc;
}
int ads_write(ads_t *d, uint8_t reg, uint16_t v)
{
uint8_t b[3] = {reg, (uint8_t)(v >> 8), (uint8_t)v}; /* старший байт первым */
int rc = xfer(d, b, 3, 0, 0);
if (rc == ADS_OK) {
d->ptr = reg;
if (reg == ADS_CONFIG)
d->cfg = v;
}
return rc;
}
int ads_read(ads_t *d, uint8_t reg, uint16_t *v)
{
uint8_t b[2];
int rc = d->ptr == reg ? xfer(d, 0, 0, b, 2) /* указатель уже на месте: только чтение */
: xfer(d, ®, 1, b, 2); /* иначе запись указателя и повторный старт */
if (rc == ADS_OK) {
d->ptr = reg;
*v = (uint16_t)(b[0] << 8 | b[1]);
}
return rc;
}
int16_t ads_be16(const uint8_t b[2])
{
return (int16_t)(uint16_t)(b[0] << 8 | b[1]);
}
int ads_probe(ads_t *d, uint8_t addr7)
{
uint16_t v;
d->addr = addr7;
d->ptr = 0xFF;
d->xfers = d->polls = d->errors = 0;
int rc = ads_read(d, ADS_CONFIG, &v); /* ответил ли кто-то по этому адресу */
if (rc == ADS_OK)
d->cfg = v;
return rc;
}
uint32_t ads_conv_us(uint32_t dr)
{
return (1000000u + sps[dr & 7u] - 1u) / sps[dr & 7u];
}
int ads_read_conv(ads_t *d, int16_t *code)
{
uint16_t v;
int rc = ads_read(d, ADS_CONV, &v);
if (rc)
return rc;
*code = (int16_t)v;
return v == 0x7FFFu || v == 0x8000u ? ADS_E_OVER : ADS_OK; /* упёрлись в край диапазона PGA */
}
/* однократное: OS = 1 запускает; сначала ждём расчётное время, потом опрашиваем OS */
int ads_single(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code)
{
uint16_t c;
uint32_t t = ads_conv_us(dr);
int rc = ads_write(d, ADS_CONFIG, ads_cfg(mux, pga, dr, 1) | ADS_QUE_OFF);
if (rc)
return rc;
ads_port_delay_us(t);
for (uint32_t waited = t;; waited += ADS_POLL_US) {
if ((rc = ads_read(d, ADS_CONFIG, &c)) != ADS_OK)
return rc;
d->polls++;
if (c & ADS_OS) /* 1 — преобразование закончено */
break;
if (waited > 2u * t + 10000u)
return ADS_E_TIMEOUT;
ads_port_delay_us(ADS_POLL_US);
}
return ads_read_conv(d, code);
}
/* ALERT/RDY как «готово»: старший бит Hi_thresh = 1, старший бит Lo_thresh = 0, COMP_QUE ≠ 11 */
int ads_rdy_mode(ads_t *d)
{
int rc = ads_write(d, ADS_HI_THRESH, 0x8000u);
return rc ? rc : ads_write(d, ADS_LO_THRESH, 0x0000u);
}
int ads_single_rdy(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, int16_t *code)
{
uint32_t t = ads_conv_us(dr);
(void)ads_port_wait_rdy(0); /* снять старый флаг EXTI */
int rc = ads_write(d, ADS_CONFIG, ads_cfg(mux, pga, dr, 1) | ADS_QUE_1);
if (rc)
return rc;
if (!ads_port_wait_rdy(2u * t + 10000u))
return ADS_E_TIMEOUT;
return ads_read_conv(d, code);
}
int ads_continuous(ads_t *d, uint32_t mux, uint32_t pga, uint32_t dr, uint16_t comp)
{
return ads_write(d, ADS_CONFIG, ads_cfg(mux, pga, dr, 0) | (comp & 0x1Fu));
}
/* n результатов подряд в непрерывном режиме по RDY; сумма — в *sum */
int ads_average(ads_t *d, uint32_t n, int32_t *sum)
{
uint32_t t = ads_conv_us(d->cfg >> ADS_DR_POS & 7u);
int16_t code;
*sum = 0;
(void)ads_port_wait_rdy(0);
for (uint32_t i = 0; i < n; i++) {
if (!ads_port_wait_rdy(2u * t + 10000u))
return ADS_E_TIMEOUT;
int rc = ads_read_conv(d, &code);
if (rc != ADS_OK && rc != ADS_E_OVER)
return rc;
*sum += code;
}
return ADS_OK;
}
/* четыре несимметричных входа по очереди, каждый — однократным преобразованием */
int ads_scan4(ads_t *d, uint32_t pga, uint32_t dr, int16_t out[4])
{
for (uint32_t ch = 0; ch < 4u; ch++) {
int rc = ads_single(d, ADS_AIN0 + ch, pga, dr, &out[ch]);
if (rc != ADS_OK && rc != ADS_E_OVER)
return rc;
}
return ADS_OK;
}
int ads_thresholds(ads_t *d, int16_t lo, int16_t hi)
{
int rc = ads_write(d, ADS_LO_THRESH, (uint16_t)lo);
return rc ? rc : ads_write(d, ADS_HI_THRESH, (uint16_t)hi);
}
int ads_recover(ads_t *d)
{
d->ptr = 0xFF;
return ads_port_bus_recover() < 0 ? ADS_E_BUS : ADS_OK;
}
/* код → микровольты целыми: код × FS / 32768 с округлением, без деления */
int32_t ads_uv(int16_t code, uint32_t pga)
{
int64_t x = (int64_t)code * fs_mv[pga > 5u ? 5u : pga] * 1000; /* PGA 110 и 111 — тоже ±0,256 В */
return (int32_t)(x >= 0 ? (x + 16384) >> 15 : -((-x + 16384) >> 15));
}
fw15c.c — прошивка STM32F103: I2C1 на 400 кГц, EXTI5 на ALERT/RDY, восстановление шины.
/* fw15c.c — раздел 15б: ADS1115 на STM32F103 (BluePill), 72 МГц, APB1 = 36 МГц.
* I2C1: PB6 — SCL, PB7 — SDA (открытый сток, 400 кГц); PB5 — ALERT/RDY (вход с подтяжкой, EXTI5 по спаду).
* ADDR микросхемы — на GND: адрес 0x48. Раз в 100 мс — четыре входа по очереди (±4,096 В, 128 SPS) и
* разность AIN0 − AIN1 по RDY (±2,048 В) в микровольтах. Тактирование до 72 МГц — как в разделе 6. */
#include "stm32f10x.h"
#include "ads15c.h"
#define CPU_HZ 72000000u
#define APB1_MHZ 36u
#define I2C_TIMEOUT_US 1000u /* дольше любого флага на 400 кГц; столько ждёт и модель */
static ads_t adc;
static volatile uint8_t rdy;
volatile int32_t uv[5]; /* AIN0…AIN3 и AIN0 − AIN1, мкВ */
volatile uint32_t bus_errors;
static int expired(uint32_t t0, uint32_t us)
{
return DWT->CYCCNT - t0 >= us * (CPU_HZ / 1000000u);
}
void ads_port_delay_us(uint32_t us)
{
uint32_t t0 = DWT->CYCCNT;
while (!expired(t0, us)) {
}
}
static void i2c1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
/* PB6, PB7 — альтернативная функция, открытый сток, 50 МГц (1111); PB5 — вход с подтяжкой (1000) */
GPIOB->CRL = (GPIOB->CRL & 0x000FFFFFu) | 0xFF800000u;
GPIOB->BSRR = 1u << 5;
I2C1->CR1 = 0; /* настраивать при PE = 0 */
I2C1->CR2 = APB1_MHZ;
I2C1->CCR = I2C_CCR_FS | 30u; /* DUTY = 0: 36 МГц / (3 × 30) = 400 кГц */
I2C1->TRISE = APB1_MHZ * 300u / 1000u + 1u; /* 300 нс × 36 МГц = 10,8 → 10 + 1 = 11 */
I2C1->CR1 = I2C_CR1_PE;
}
static int sr1_wait(uint16_t mask)
{
uint32_t t0 = DWT->CYCCNT;
while (!(I2C1->SR1 & mask)) {
if (I2C1->SR1 & I2C_SR1_AF)
return ADS_E_NACK; /* адрес или байт не подтверждён */
if (expired(t0, I2C_TIMEOUT_US))
return ADS_E_BUS;
}
return ADS_OK;
}
static int fail(int rc)
{
I2C1->CR1 |= I2C_CR1_STOP;
I2C1->SR1 = (uint16_t)~I2C_SR1_AF; /* AF снимается записью нуля */
I2C1->CR1 &= (uint16_t)~(I2C_CR1_POS | I2C_CR1_ACK);
return rc;
}
int ads_port_xfer(uint8_t addr7, const uint8_t *tx, uint32_t ntx, uint8_t *rx, uint32_t nrx)
{
int rc;
uint32_t t0 = DWT->CYCCNT;
if (nrx != 0u && nrx != 2u)
return ADS_E_BUS; /* драйвер читает только по два байта */
while (I2C1->SR2 & I2C_SR2_BUSY) { /* SDA держит ведомый — раздел 17.6 */
if (expired(t0, I2C_TIMEOUT_US))
return ADS_E_BUS;
}
if (ntx) {
I2C1->CR1 |= I2C_CR1_START;
if ((rc = sr1_wait(I2C_SR1_SB)) != ADS_OK)
return fail(rc);
I2C1->DR = (uint8_t)(addr7 << 1);
if ((rc = sr1_wait(I2C_SR1_ADDR)) != ADS_OK)
return fail(rc);
(void)I2C1->SR2; /* ADDR снят: SR1 прочитан в sr1_wait, теперь SR2 */
for (uint32_t i = 0; i < ntx; i++) {
if ((rc = sr1_wait(I2C_SR1_TXE)) != ADS_OK)
return fail(rc);
I2C1->DR = tx[i];
}
if ((rc = sr1_wait(I2C_SR1_BTF)) != ADS_OK)
return fail(rc);
}
if (!nrx) {
I2C1->CR1 |= I2C_CR1_STOP;
return ADS_OK;
}
I2C1->CR1 |= I2C_CR1_START; /* после записи — повторный старт */
if ((rc = sr1_wait(I2C_SR1_SB)) != ADS_OK)
return fail(rc);
I2C1->CR1 |= I2C_CR1_POS | I2C_CR1_ACK; /* два байта: RM0008, с. 764 */
I2C1->DR = (uint8_t)(addr7 << 1 | 1u);
if ((rc = sr1_wait(I2C_SR1_ADDR)) != ADS_OK)
return fail(rc);
(void)I2C1->SR2;
I2C1->CR1 &= (uint16_t)~I2C_CR1_ACK;
if ((rc = sr1_wait(I2C_SR1_BTF)) != ADS_OK) /* оба байта приняты, SCL прижат */
return fail(rc);
I2C1->CR1 |= I2C_CR1_STOP;
rx[0] = (uint8_t)I2C1->DR; /* старший байт */
rx[1] = (uint8_t)I2C1->DR;
I2C1->CR1 &= (uint16_t)~I2C_CR1_POS;
return ADS_OK;
}
/* ALERT/RDY: спад на PB5 */
void EXTI9_5_IRQHandler(void)
{
EXTI->PR = 1u << 5;
rdy = 1;
}
int ads_port_wait_rdy(uint32_t timeout_us)
{
uint32_t t0 = DWT->CYCCNT;
while (!rdy) {
if (expired(t0, timeout_us))
return 0;
}
rdy = 0;
return 1;
}
/* то же, что в разделе 17.6, но возвращает число тактов */
int ads_port_bus_recover(void)
{
int n = 0;
I2C1->CR1 &= (uint16_t)~I2C_CR1_PE;
GPIOB->BSRR = 1u << 6 | 1u << 7;
GPIOB->CRL = (GPIOB->CRL & 0x00FFFFFFu) | 0x77000000u; /* PB6, PB7 — выход, открытый сток (0111) */
while (n < 9 && !(GPIOB->IDR & 1u << 7)) { /* пока SDA в нуле */
GPIOB->BRR = 1u << 6;
ads_port_delay_us(5);
GPIOB->BSRR = 1u << 6;
ads_port_delay_us(5);
n++;
}
int stuck = !(GPIOB->IDR & 1u << 7);
GPIOB->BRR = 1u << 6; /* STOP: SDA вверх при высоком SCL */
GPIOB->BRR = 1u << 7;
ads_port_delay_us(5);
GPIOB->BSRR = 1u << 6;
ads_port_delay_us(5);
GPIOB->BSRR = 1u << 7;
ads_port_delay_us(5);
I2C1->CR1 = I2C_CR1_SWRST;
I2C1->CR1 = 0;
i2c1_init();
return stuck ? -1 : n;
}
static void irq_init(void)
{
AFIO->EXTICR[1] = (AFIO->EXTICR[1] & ~0xF0u) | 0x10u; /* EXTI5 ← PB5 */
EXTI->FTSR |= 1u << 5;
EXTI->IMR |= 1u << 5;
NVIC_EnableIRQ(EXTI9_5_IRQn);
}
int main(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
i2c1_init();
irq_init();
while (ads_probe(&adc, ADS_ADDR_GND) != ADS_OK) {
(void)ads_recover(&adc); /* нет ответа: шина или провод */
ads_port_delay_us(100000u);
}
(void)ads_rdy_mode(&adc);
for (;;) {
int16_t c[4], d = 0;
int rc = ads_scan4(&adc, ADS_FS_4096, ADS_DR_128, c);
if (rc == ADS_OK)
for (uint32_t i = 0; i < 4u; i++)
uv[i] = ads_uv(c[i], ADS_FS_4096);
if (rc == ADS_OK)
rc = ads_single_rdy(&adc, ADS_AIN0_AIN1, ADS_FS_2048, ADS_DR_128, &d);
if (rc == ADS_OK || rc == ADS_E_OVER)
uv[4] = ads_uv(d, ADS_FS_2048);
if (rc == ADS_E_BUS || rc == ADS_E_NACK) {
bus_errors++;
if (ads_recover(&adc) == ADS_OK)
(void)ads_rdy_mode(&adc); /* вдруг микросхема перезапустилась: пороги заново */
}
ads_port_delay_us(100000u);
}
}
Дальше: раздел 15в «Внешний ЦАП MCP4725 по I2C: 12 бит, быстрая запись и EEPROM, опорное от питания, генерация сигнала» — обратная задача на той же шине: число в напряжение на F103, у которого своего ЦАП нет.