15д. Прецизионный АЦП ADS1256 по SPI: 24 бита, 8 входов, PGA до 64, буфер, скорости до 30 000 отсчётов в секунду, самокалибровка и синхронизация
В 15г ADS1220 мерил мост и PT100: четыре входа, до 2000 отсчётов в секунду, опорное от датчика. ADS1256 — для другой задачи: много входов и высокая скорость. Восемь входов (или четыре пары), усиление до 64, входной буфер, скорости от 2,5 до 30 000 отсчётов в секунду, свой кварц 7,68 МГц и калибровка нуля и усиления по команде. Здесь — драйвер: команды и одиннадцать регистров, пауза t6 между командой и чтением, режим SPI 1 и предел такта, плата-модуль с питанием 5 В и логикой 3,3 В, буфер и пределы входа, скорость и шум, непрерывное чтение RDATAC, обход восьми входов со SYNC и WAKEUP — сколько результатов отбросить после смены входа и какая скорость остаётся на канал, калибровка после смены усиления и скорости и грабли. Что такое дельта-сигма, фильтр-усреднение, PGA и 24 бита в дополнительном коде — в 15г; как встроенный АЦП догоняет соседний канал — в 180.3, здесь причина другая.
Опоры: RM0008 (DocID13902 Rev 17): выводы SPI — таблица 25, с. 166; без
ремапа SPI1 — PA4–PA7, с. 185; AFIO_EXTICR1, с. 190; EXTI_FTSR,
с. 211; BR[2:0] = 100 — fPCLK/32, 101 — fPCLK/64, с. 743; CPOL и
CPHA, с. 744; BSY, с. 745. DocID13587 Rev 17 (STM32F103x8/xB):
PA4–PA7 — выводы SPI1, PA4–PA7 и PB0 без допуска 5 В, с. 29. Документа
на ADS1256 в проекте нет, открытого драйвера на диске тоже: в Klipper и
других открытых проектах его нет. Коды команд, адреса и поля
регистров, значения после сброса, коды DRATE, такт 7,68 МГц, паузы t6 и
t11, предел такта SPI, пределы входа с буфером и без, входное
сопротивление — по документации производителя, не проверено; это
заложено в модель и сверено с ней. Все числа ниже — прогон драйвера
_verify/adc15f/ads15f.c, собранного arm-none-eabi-gcc 12.2.1 под
Cortex-M3 и запущенного в QEMU 7.2 (netduino2), против своей модели
ADS1256 на Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3.
Числа говорят о модели, а не о микросхеме.
Как это называется в документации
Оригинал Перевод analog input common (AINCOM) общий вход: минус для несимметричных измерений input buffer enable (BUFEN) включение входного буфера: вход почти не нагружает источник absolute input voltage напряжение входа относительно AGND master clock input (CLKIN), τCLKIN вход тактового генератора; период его такта DIN-to-DOUT delay (t6) пауза от команды чтения до первого бита ответа read data continuous (RDATAC), stop read data continuous (SDATAC) выдача каждого результата без команды; её остановка SYNC / WAKEUP commands остановить фильтр; запустить его заново multiplexer cycling обход входов по кругу self offset / self gain calibration (SELFOCAL, SELFGCAL) самокалибровка нуля / усиления system offset / system gain calibration (SYSOCAL, SYSGCAL) калибровка по сигналу на входе offset / full-scale calibration registers (OFC, FSC) регистры поправки нуля и шкалы auto-calibration (ACAL) калибровка сама после смены настроек noise-free resolution разрядность без шума: размах шума меньше единицы кода
15д.1 Чем он отличается от ADS1220
| ADS1220 (15г) | ADS1256 | |
|---|---|---|
| входы | 4, пары и против AVSS | 8 несимметричных против AINCOM или 4 пары, любой со любым |
| усиление | 1…128 | 1…64 |
| входной буфер | нет | есть, BUFEN |
| скорость | 20…1000 SPS, в turbo 40…2000 | 2,5…30 000 SPS, 16 кодов DRATE |
| такт | свой внутренний | кварц или генератор 7,68 МГц на CLKIN |
| опорное | своё 2,048 В, REF0, REF1, питание | только внешнее, VREFP − VREFN |
| калибровка | нет (смещение — входы накоротко) | команды SELFCAL, SYSOCAL, SYSGCAL и регистры OFC, FSC |
| питание | 3,3 В | AVDD 5 В, DVDD 1,8…3,6 В |
Диапазон — ±2·VREF / PGA: с опорным 2,5 В и усилением 1 это ±5 В, единица кода — 596,047 нВ. Код — 24 бита в дополнительном коде, как в 15г.4; +2·VREF / PGA даёт 0x7FFFFF.
15д.2 Подключение к F103, плата и буфер
| Вывод ADS1256 | STM32F103 | Назначение |
|---|---|---|
| SCLK | PA5 | такт SPI1 |
| DIN | PA7 | MOSI: команды и данные в микросхему |
| DOUT | PA6 | MISO: ответ |
| CS | PA4 | выбор, обычный выход |
| DRDY | PB0 | «готово», EXTI0 по спаду |
| AVDD, DVDD, AGND, DGND | 5 В, 3,3 В, земля | общая земля с микроконтроллером |
Выводы — как в 15г.2: SPI1 без ремапа на PA4–PA7 (RM0008, с. 185), SCK и
MOSI — альтернативная функция push-pull, MISO — вход (таблица 25,
с. 166), DRDY — EXTI0 по спаду (AFIO_EXTICR1, с. 190, EXTI_FTSR,
с. 211). Режим SPI — 1 (18.2): микросхема выдаёт бит по фронту такта, а
читать его надо по спаду (по документации производителя).
static void spi1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_SPI1EN;
GPIOA->BSRR = 1u << 4; /* CS вверх до настройки вывода */
/* PA4 — выход 50 МГц (0011), PA5 и PA7 — альтернативная функция push-pull (1011), PA6 — вход (0100) */
GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu) | 0xB4B30000u;
GPIOB->CRL = (GPIOB->CRL & ~0xFu) | 0x8u; /* PB0 — вход с подтяжкой (1000) */
GPIOB->BSRR = 1u << 0;
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_CPHA | SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_SSM | SPI_CR1_SSI; /* режим 1, /64 */
SPI1->CR1 |= SPI_CR1_SPE;
}
Такт SPI. Период SCLK — не меньше 4 τCLKIN (по документации
производителя): τ = 1 / 7,68 МГц = 130,208 нс, предел — 520,8 нс,
то есть 1,92 МГц. SPI1 на F103 сидит на 72 МГц: BR = 100 даёт
72 / 32 = 2,25 МГц — уже слишком быстро, BR = 101 — 1,125 МГц
(с. 743). Байт на 1,125 МГц идёт 7,111 мкс.
Питание. Аналоговой части нужно AVDD = 5 В, цифровой — DVDD от 1,8 до 3,6 В (по документации производителя). Выводы DOUT и DRDY выдают уровень DVDD, а PA4–PA7 и PB0 у F103 5 В не терпят (DocID13587, с. 29): DVDD — 3,3 В. Распространённые платы-модули питаются от 5 В и делают 3,3 В для DVDD своим стабилизатором, на них же кварц 7,68 МГц и опорное 2,5 В — но платы разные: схему своей проверьте, мы платы не проверяли. Если DVDD на плате 5 В, к F103 её напрямую не подключить.
Буфер. Без буфера вход — переключаемые конденсаторы, и источник их заряжает: входное сопротивление около 150 кОм / PGA, с буфером — 80 МОм (по документации производителя). Зато с буфером вход должен лежать от AGND до AVDD − 2 В, без него — от AGND − 0,1 В до AVDD + 0,1 В. В модели, AVDD = 5 В, вход AIN0 против AINCOM, ×1:
| AIN0 | С буфером | Без буфера |
|---|---|---|
| 1 В | 1 000 000 мкВ | 1 000 000 мкВ |
| 2,9 В | 2 900 000 мкВ | 2 900 000 мкВ |
| 3,3 В | 3 000 000 мкВ | 3 300 000 мкВ |
| 4,5 В | 3 000 000 мкВ | 4 500 000 мкВ |
Выше 3 В с буфером показывает 3 В — без ошибки и без флага. Источник 20 мВ с сопротивлением Rs:
| Rs | С буфером, ×1 | С буфером, ×64 | Без буфера, ×1 | Без буфера, ×64 |
|---|---|---|---|---|
| 0 | 20 000 мкВ | 20 000 мкВ | 20 000 мкВ | 20 000 мкВ |
| 1 кОм | 20 000 мкВ | 20 000 мкВ | 19 867 мкВ | 14 019 мкВ |
| 10 кОм | 19 997 мкВ | 19 998 мкВ | 18 750 мкВ | 3797 мкВ |
Делитель на 10 кОм без буфера теряет 6,25 % на ×1 и 81 % на ×64. Правило: источник с сопротивлением больше нуля — буфер; сигнал выше 3 В — делитель и буфер или без буфера, но с низкоомным источником (180.2).
15д.3 Команды, регистры и пауза t6
| Байт | Команда | Что делает |
|---|---|---|
| 0x00, 0xFF | WAKEUP | пуск после SYNC или STANDBY |
| 0x01 | RDATA | после паузы t6 — три байта результата |
| 0x03 | RDATAC | дальше каждый результат по спаду DRDY без команды |
| 0x0F | SDATAC | перестать; снова слушать команды |
| 0x1r, 0n | RREG | прочитать n + 1 регистров с адреса r; после паузы t6 |
| 0x5r, 0n | WREG | записать n + 1 регистров с адреса r |
| 0xF0 | SELFCAL | самокалибровка нуля и усиления |
| 0xF1, 0xF2 | SELFOCAL, SELFGCAL | только нуля; только усиления |
| 0xF3, 0xF4 | SYSOCAL, SYSGCAL | ноль и шкала по тому, что сейчас на входе |
| 0xFC | SYNC | остановить фильтр до WAKEUP |
| 0xFD | STANDBY | уснуть до WAKEUP |
| 0xFE | RESET | регистры — по умолчанию |
| Адрес | Регистр | Поля | После RESET |
|---|---|---|---|
| 0 | STATUS | 7:4 — ID (у ADS1256 — 3), 3 — ORDER, 2 — ACAL, 1 — BUFEN, 0 — DRDY |
0x3x |
| 1 | MUX | 7:4 — PSEL, 3:0 — NSEL: 0…7 — AIN0…AIN7, 1xxx — AINCOM |
0x01 |
| 2 | ADCON | 6:5 — CLK (такт на D0/CLKOUT), 4:3 — SDCS (токи обрыва), 2:0 — PGA: 1, 2, 4 … 64 |
0x20 |
| 3 | DRATE | код скорости (15д.4) | 0xF0 |
| 4 | IO | 7:4 — направления D3…D0, 3:0 — их уровни | 0xE0 |
| 5–7 | OFC0…OFC2 | поправка нуля, 24 бита со знаком, младший байт первым | — |
| 8–10 | FSC0…FSC2 | поправка шкалы, 24 бита, младший байт первым | — |
Всё это — по документации производителя; модель и драйвер с ней
согласны, но микросхема не подключалась. ADCON = 0x20 после сброса
выводит такт 7,68 МГц на D0/CLKOUT; если он не нужен, драйвер пишет
CLK = 00 — лишний сигнал рядом со входами ни к чему.
t6. После RDATA и RREG микросхеме нужно 50 τCLKIN, чтобы
приготовить ответ: 50 × 130,208 нс = 6,51 мкс. SPI F103 шлёт байты
подряд, и следующий такт пришёл бы через полтакта — поэтому драйвер
делит кадр на две передачи и ждёт между ними ADS_T6_US = 7 мкс:
/* RREG: два байта команды, пауза t6 — и только потом ответ, всё в одном кадре */
void ads_rreg(ads_t *d, uint32_t reg, uint8_t *v, uint32_t n)
{
uint8_t tx[11] = {(uint8_t)(ADS_CMD_RREG | (reg & 15u)), (uint8_t)((n - 1u) & 15u)}, rx[11];
ads_port_cs(0);
ads_port_xfer(tx, rx, 2);
ads_port_delay_us(ADS_T6_US);
ads_port_xfer(tx + 2, v, n); /* на DIN — нули */
ads_port_cs(1);
d->frames++;
d->bytes += n + 2u;
}
После SYNC и RDATAC до следующей команды — t11 = 24 τ = 3,125 мкс
(ADS_T11_US = 4 мкс, его ждёт ads_cmd); после остальных команд —
4 τ = 0,52 мкс, и столько уходит на подъём и опускание CS и так.
/* SDATAC на случай, если микросхема осталась в RDATAC, RESET, первый результат — и пять регистров */
int ads_reset(ads_t *d, uint8_t r[5])
{
ads_cmd(d, ADS_CMD_SDATAC);
(void)ads_port_wait_drdy(0);
ads_cmd(d, ADS_CMD_RESET);
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_rreg(d, ADS_STATUS, r, 5);
for (uint32_t i = 0; i < 5u; i++)
d->reg[i] = r[i];
return r[0] >> ADS_ID_POS == ADS_ID ? ADS_OK : ADS_E_ID;
}
В модели сброс — SDATAC, RESET, первый результат на 30 000 SPS через
166,7 мкс и RREG пяти регистров — занял 237,7 мкс и прочитал
30 01 20 F0 E0: ID = 3, MUX, ADCON, DRATE и IO — как в таблице. Настройка
ads_config — кадр WREG 50 03 02 08 00 A1 (буфер, AIN0 против AINCOM, CLKOUT
выключен, ×1, 1000 SPS) и чтение обратно — 92,3 мкс; прочитано
32 08 00 A1 E0: в STATUS DRDY = 0, и драйвер сравнивает только ORDER,
ACAL и BUFEN. Кадры на 1,125 МГц: команда — 7,1 мкс, RREG одного
регистра — 28,3 мкс, RDATA — 35,4 мкс, три байта без команды —
21,3 мкс, WREG четырёх регистров — 42,7 мкс.
15д.4 Код, скорость и шум
Код в вольты. Вход = код × 2·VREF / (PGA × (2²³ − 1)):
/* код → микровольты: вход = код × 2·VREF / (PGA × (2^23 − 1)); PGA 110 и 111 — оба ×64 */
int32_t ads_uv(int32_t code, uint32_t vref_uv, uint32_t pga)
{
uint32_t sh = pga > ADS_PGA64 ? ADS_PGA64 : pga;
int64_t x = (int64_t)code * 2 * vref_uv;
int64_t den = (int64_t)ADS_FS << sh;
return (int32_t)(x >= 0 ? (x + den / 2) / den : -((-x + den / 2) / den));
}
В модели, VREF = 2,5 В, разность AIN0 − AIN1 после SELFCAL:
| PGA | Диапазон | Единица кода | +1 мВ | +50 мВ | −50 мВ |
|---|---|---|---|---|---|
| 1 | ±5000 мВ | 596,047 нВ | 1678 | 83 886 | −83 886 |
| 8 | ±625 мВ | 74,506 нВ | 13 422 | 671 089 | −671 088 |
| 64 | ±78,125 мВ | 9,313 нВ | 107 374 | 5 368 709 | −5 368 708 |
Крайние коды — 5 000 000 и −5 000 001 мкВ. Несимметричный вход против AINCOM на земле отрицательным не бывает: AIN0 = 0, 1, 3,3 и 4,9 В дали коды 0, 1 677 721, 5 536 481 и 8 220 835 — работает половина шкалы, 23 бита. Нужен знак — AINCOM на 2,5 В или пара входов.
Скорость. В модели фильтр — sinc5 с шагом 1/30 000 с и усреднение N его результатов, N = 30 000 / DR (допущение модели, настоящий фильтр по документации производителя устроен так же, но его времена мы не проверяли). Первый результат после WAKEUP — через (N + 4) / 30 000 с, дальше — каждые N / 30 000 с:
Код DRATE |
SPS | N | После WAKEUP | Период |
|---|---|---|---|---|
| 0xF0 | 30 000 | 1 | 166,7 мкс | 33,3 мкс |
| 0xE0 | 15 000 | 2 | 200 мкс | 66,7 мкс |
| 0xD0 | 7500 | 4 | 266,7 мкс | 133,3 мкс |
| 0xC0 | 3750 | 8 | 400 мкс | 266,7 мкс |
| 0xB0 | 2000 | 15 | 633,3 мкс | 500 мкс |
| 0xA1 | 1000 | 30 | 1133,3 мкс | 1000 мкс |
| 0x92 | 500 | 60 | 2133,3 мкс | 2000 мкс |
| 0x82 | 100 | 300 | 10 133,3 мкс | 10 000 мкс |
| 0x72 | 60 | 500 | 16 800 мкс | 16 666,7 мкс |
| 0x63 | 50 | 600 | 20 133,3 мкс | 20 000 мкс |
| 0x53 | 30 | 1000 | 33 466,7 мкс | 33 333,3 мкс |
| 0x43 | 25 | 1200 | 40 133,3 мкс | 40 000 мкс |
| 0x33 | 15 | 2000 | 66 800 мкс | 66 666,7 мкс |
| 0x23 | 10 | 3000 | 100 133,3 мкс | 100 000 мкс |
| 0x13 | 5 | 6000 | 200 133,3 мкс | 200 000 мкс |
| 0x03 | 2,5 | 12 000 | 400 133,3 мкс | 400 000 мкс |
На 30 000 SPS первый результат — через пять периодов, на 1000 — через 1,13 периода, на 2,5 — почти через один.
Шум. В модели шум на входе — √((30 мкВ / PGA)² + (1,5 мкВ)²) × √(DR / 30 000) (допущение модели, не данные документа). По 256 результатов через RDATAC, вход закорочен на 2,5 В; эффективные биты — log₂(4·VREF / PGA / шум), без шума — по размаху 6,6 шума:
| SPS | ×1: шум | бит | без шума | ×64: шум | бит | без шума |
|---|---|---|---|---|---|---|
| 30 000 | 31,214 мкВ | 18,3 | 15,6 | 1,528 мкВ | 16,6 | 13,9 |
| 7500 | 15,343 мкВ | 19,3 | 16,6 | 0,79 мкВ | 17,6 | 14,9 |
| 1000 | 5,5 мкВ | 20,8 | 18,1 | 0,285 мкВ | 19,1 | 16,3 |
| 100 | 1,668 мкВ | 22,5 | 19,8 | 0,094 мкВ | 20,7 | 17,9 |
| 10 | 0,562 мкВ | 24,1 | 21,4 | 0,031 мкВ | 22,3 | 19,5 |
| 2,5 | 0,296 мкВ | 25 | 22,3 | 0,015 мкВ | 23,3 | 20,6 |
Чем медленнее, тем тише: в модели каждое замедление в четыре раза даёт бит. Больше 24 эффективных бит на ×1 и 2,5 SPS — не ошибка: шум меньше единицы кода, и 24 бита его уже не различают. Усиление ×64 на 30 000 SPS снижает шум на входе в 20 раз, но и диапазон — в 64: бит становится меньше.
15д.5 DRDY, RDATA и RDATAC
ADS1256 преобразует непрерывно: каждый период DRDY падает, результат лежит в микросхеме до следующего. Прочитать его можно двумя способами.
RDATA — команда, пауза t6, три байта: 35,4 мкс на 1,125 МГц. На 30 000 SPS период — 33,3 мкс, и RDATA не успевает: в модели из 3000 результатов подряд ушло 106 333,3 мкс и 190 результатов пропало — их затёрли новые, пока шло чтение.
RDATAC — один раз команда, дальше по каждому спаду DRDY три байта без команды: 21,3 мкс, шина занята на 64 %. В модели 3000 результатов за 100 010,2 мкс, пропал 1 — тот, что лежал непрочитанным ещё до RDATAC.
/* RDATAC: дальше каждый результат выдвигается по спаду DRDY без команды; команды не слышны до SDATAC */
void ads_rdatac_start(ads_t *d)
{
(void)ads_port_wait_drdy(0);
ads_cmd(d, ADS_CMD_RDATAC);
}
int ads_rdatac_read(ads_t *d, int32_t *code)
{
uint8_t z[3] = {0, 0, 0}, b[3];
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_frame(d, z, b, 3);
*code = ads_s24(b);
return range(*code);
}
int ads_rdatac_stop(ads_t *d)
{
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_cmd(d, ADS_CMD_SDATAC);
return ADS_OK;
}
В RDATAC микросхема слышит только SDATAC и RESET (по документации
производителя): RREG и WREG до SDATAC не дойдут (15д.7). После
ads_rdatac_stop регистры снова читаются — 33 08 00 F0 E0. Время работы
процессора в модели не считается: на настоящем F103 чтение в
обработчике EXTI добавит свои микросекунды.
STANDBY. Микросхема засыпает до WAKEUP: в модели ожидание DRDY
после STANDBY вернуло ADS_E_TIMEOUT, а после WAKEUP первый результат
пришёл через 1133,3 мкс — как после SYNC.
15д.6 Каналы по кругу, SYNC и калибровка
Смена входа без SYNC. Фильтр копит отсчёты непрерывно, и запись MUX
его не останавливает (допущение модели). Сразу после записи результат —
смесь старого входа и нового. В модели AIN0 = 1 В, AIN1 = 2 В, ads_set
переключает MUX, дальше результаты подряд; доля нового входа:
| SPS | Когда записан MUX | 1-й | 2-й | 3-й | 4-й | 5-й |
|---|---|---|---|---|---|---|
| 30 000 | в начале периода | 0 % | 3 % | 38 % | 88,8 % | 100 % |
| 30 000 | в середине периода | 0,4 % | 16,6 % | 70,6 % | 98,4 % | 100 % |
| 3750 | в начале периода | 53,7 % | 100 % | 100 % | 100 % | 100 % |
| 3750 | в середине периода | 17 % | 100 % | 100 % | 100 % | 100 % |
| 1000 | в начале периода | 87,7 % | 100 % | 100 % | 100 % | 100 % |
| 1000 | в середине периода | 41,2 % | 100 % | 100 % | 100 % | 100 % |
| 10 | в начале периода | 99,9 % | 100 % | 100 % | 100 % | 100 % |
| 10 | в середине периода | 49,9 % | 100 % | 100 % | 100 % | 100 % |
Ниже 30 000 SPS отбрасывать надо один результат, на 30 000 — четыре. Проще не гадать: SYNC обнуляет фильтр, WAKEUP запускает его заново, и первый результат после WAKEUP — уже нового входа целиком.
По кругу. Так обходят входы по документации производителя: на спаде DRDY — WREG MUX следующего входа, SYNC, WAKEUP и только потом RDATA. Читается результат прежнего входа: он лежит в микросхеме, пока фильтр копит новый.
/* выбрать вход и начать заново: WREG MUX, SYNC (фильтр стоит и пуст), WAKEUP (пошёл) */
void ads_scan_start(ads_t *d, uint8_t mux)
{
ads_wreg(d, ADS_MUX, &mux, 1);
d->reg[ADS_MUX] = mux;
ads_cmd(d, ADS_CMD_SYNC);
(void)ads_port_wait_drdy(0); /* старый спад — не наш */
ads_cmd(d, ADS_CMD_WAKEUP);
}
/* по кругу: спад DRDY — следующий вход, SYNC, WAKEUP, и только потом RDATA — результат прежнего входа
* лежит в микросхеме, пока новый фильтр копит отсчёты */
int ads_scan_next(ads_t *d, uint8_t next, int32_t *code)
{
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_wreg(d, ADS_MUX, &next, 1);
d->reg[ADS_MUX] = next;
ads_cmd(d, ADS_CMD_SYNC);
ads_cmd(d, ADS_CMD_WAKEUP);
return ads_read_data(d, code);
}
Восемь входов AIN0…AIN7 против AINCOM, на них 0,2…2,3 В, по два круга:
| SPS | Шаг | Все входы | На вход | Окно фильтра |
|---|---|---|---|---|
| 30 000 | 208,4 мкс | 4797,6 Гц | 599,7 Гц | 166,7 мкс |
| 7500 | 308,4 мкс | 3242,1 Гц | 405,3 Гц | 266,7 мкс |
| 1000 | 1175,1 мкс | 851 Гц | 106,4 Гц | 1133,3 мкс |
| 100 | 10 175,1 мкс | 98,3 Гц | 12,3 Гц | 10 133,3 мкс |
| 50 | 20 175,1 мкс | 49,6 Гц | 6,2 Гц | 20 133,3 мкс |
| 10 | 100 175,1 мкс | 10 Гц | 1,2 Гц | 100 133,3 мкс |
Ошибка ни на одном входе не больше 1 кода: смешения нет. Цена —
окно фильтра на каждый шаг: на 30 000 SPS восемь входов дают по
599,7 Гц, а не 3750; на 1000 SPS — по 106,4 Гц. Ровно столько в
прошивке fw15f.c:
/* восемь входов по кругу: результат k-го приходит, когда фильтр уже копит (k + 1)-й */
static int scan8(void)
{
int32_t code;
for (uint32_t k = 0; k < 8u; k++) {
int rc = ads_scan_next(&adc, ADS_SE((k + 1u) % 8u), &code);
if (rc != ADS_OK)
return rc;
ch_uv[k] = ads_uv(code, VREF_UV, ADS_PGA1);
}
rounds++;
return ADS_OK;
}
Калибровка. У модели свои ошибки: смещение 10 + 50 / PGA мкВ и усиление на 0,3 % × (1 + 0,2·log₂PGA) больше (допущения модели). Вход 50 мВ, разность AIN0 − AIN1:
| Что | PGA | Показано | OFC | FSC |
|---|---|---|---|---|
| до калибровки | 1 | 50 210 мкВ | 0 | 4 194 304 |
| SELFCAL | 1 | 50 000 мкВ | 101 | 4 181 759 |
| сменили PGA, без калибровки | 64 | 50 189 мкВ | то же | то же |
| SELFCAL | 64 | 50 000 мкВ | 1165 | 4 166 803 |
После RESET в модели OFC = 0 и FSC = 0x400000; что в них в микросхеме,
не проверено. SELFCAL на ×1 убрал 210 мкВ ошибки, но после смены
усиления на ×64 поправки ×1 уже не годятся — 50 189 мкВ вместо
50 000. Снова SELFCAL — и 50 000 мкВ. Калибруют после каждой смены
PGA, скорости и буфера. Можно поручить это микросхеме: ACAL = 1, и
запись ADCON сама запустила SELFCAL (в модели — команда SELFCAL),
результат — 50 000 мкВ.
/* SELFCAL, SELFOCAL, SELFGCAL, SYSOCAL, SYSGCAL: калибровка кончилась — DRDY упал */
int ads_calibrate(ads_t *d, uint8_t cmd)
{
(void)ads_port_wait_drdy(0);
ads_cmd(d, cmd);
return ads_port_wait_drdy(ADS_TIMEOUT_US) ? ADS_OK : ADS_E_TIMEOUT;
}
Калибровка длится по окну фильтра на ноль и на усиление, и ещё окно до
первого результата (допущение модели): SELFCAL на 1000 SPS — 3407,1
мкс, SELFOCAL и SELFGCAL — по 2273,8 мкс, на 30 000 SPS —
507,1 мкс, на 100 — 30 407,1 мкс, на 2,5 — 1 200 407,1 мкс. Отсюда
ADS_TIMEOUT_US = 2 с.
Системная калибровка. SELFCAL замыкает входы внутри и не видит датчик. Мост со своим смещением 150 мкВ на AIN2 − AIN3, ×64: после SELFCAL ноль показал 150 мкВ, после SYSOCAL при разгруженном мосте — 0 мкВ (OFC = 17 378). SYSGCAL — то же для шкалы: подать на вход полную шкалу и отдать команду.
15д.7 Грабли
Нет паузы t6. RREG STATUS и сразу ответ в том же кадре: в модели
прочитано 00 вместо 30 — ID = 0, «микросхемы нет». RDATA без
паузы: 13 107 (0x003333) вместо 1 677 721 (0x199999) —
результат сдвинут на семь бит: семь тактов прошли раньше, чем
микросхема приготовила ответ.
Такт SPI выше CLKIN / 4. BR = 100 — 2,25 МГц при пределе
1,92 МГц. Что сделает микросхема, не проверено; в модели она молчит:
сброс прочитал 00 00 00 00 00 и вернул ADS_E_ID. На 1,125 МГц тот же
сброс прочитал 30 01 20 F0 E0.
SPI в режиме 0. Ответ защёлкивается на такт раньше (18.2): сброс
прочитал 18 00 90 78 70 — каждый байт сдвинут на бит, ID = 1, ADS_E_ID.
Режим 1: CPHA = 1.
Регистры в RDATAC без SDATAC. ads_set записал MUX = 0x18 и вернул
ADS_E_VERIFY: в модели запись не дошла, MUX остался 0x08, и
следующие результаты — всё тот же AIN0, 1 677 721 кода. Сначала
ads_rdatac_stop, потом регистры.
Смена входа без отбрасывания. Результат сразу после записи MUX — смесь двух входов: на 1000 SPS 87,7 % нового, на 30 000 — четыре результата подряд неверны (15д.6). SYNC и WAKEUP после смены MUX — или отбросить.
WAKEUP сразу после SYNC. Без паузы t11 в модели WAKEUP не услышан, преобразование не пошло, ожидание DRDY кончилось тайм-аутом.
Буфер и пределы входа. С BUFEN = 1 вход выше AVDD − 2 В
обрезается: 3,3 и 4,5 В показали 3 000 000 мкВ. Без буфера источник
10 кОм на ×64 показал 3797 мкВ из 20 000 (15д.2).
Калибровка старого усиления. Сменили PGA без SELFCAL — на ×64 50 189 мкВ вместо 50 000 (15д.6).
15д.8 Итоги
- ADS1256 — 24 бита, 8 входов против AINCOM или 4 пары, PGA 1…64, буфер, 16 скоростей от 2,5 до 30 000 SPS, такт 7,68 МГц, опорное внешнее; диапазон ±2·VREF / PGA, на 2,5 В и ×1 — 596,047 нВ на код.
- Команды: WAKEUP 0x00, RDATA 0x01, RDATAC 0x03, SDATAC 0x0F, RREG 0x1r, WREG 0x5r, SELFCAL 0xF0…SYSGCAL 0xF4, SYNC 0xFC, STANDBY 0xFD, RESET 0xFE; после сброса STATUS 0x3x — ID 3.
- t6 = 50 τ = 6,51 мкс между RDATA/RREG и ответом; t11 = 3,125 мкс после SYNC; SPI — режим 1, не быстрее 1,92 МГц: на F103 — 1,125 МГц.
- Плата — 5 В на AVDD, 3,3 В на DVDD: иначе DOUT и DRDY сожгут PA4–PA7. С буфером вход от 0 до 3 В, без буфера 10 кОм на ×64 — минус 81 %.
- На 30 000 SPS RDATA не успевает (190 пропусков из 3000), RDATAC успевает и занимает шину на 64 %.
- После смены MUX без SYNC — один смешанный результат, на 30 000 SPS — четыре; SYNC и WAKEUP дают чистый результат за окно фильтра.
- Восемь входов по кругу: по 599,7 Гц на вход на 30 000 SPS, по 106,4 Гц на 1000.
- Шум в модели: 31,214 мкВ на 30 000 SPS и 0,296 мкВ на 2,5 SPS при ×1.
- SELFCAL после каждой смены PGA и скорости: без неё ×64 показал
50 189 мкВ вместо 50 000; или
ACAL= 1.
Полные исходники
ads15f.h — команды, регистры и их поля, коды скоростей, времена, порт и функции драйвера.
/* ads15f.h — раздел 15д: драйвер прецизионного 24-битного АЦП ADS1256 по SPI без регистров микроконтроллера.
* Драйвер говорит с микросхемой только через функции порта (ads_port_*): в прошивке fw15f.c их делают
* SPI1 и EXTI0 STM32F103, в стенде bench15f.c — модель model15f.py.
* Документа на ADS1256 в проекте нет: коды команд, адреса и поля регистров, значения после сброса, таблица
* DRATE, паузы t6 и t11 и предел такта SPI — по документации производителя, не проверено; модель повторяет их. */
#ifndef ADS15F_H
#define ADS15F_H
#include <stdint.h>
/* ── команды ──────────────────────────────────────────────────────────── */
#define ADS_CMD_WAKEUP 0x00u /* пуск после SYNC или STANDBY (0xFF — то же) */
#define ADS_CMD_RDATA 0x01u /* следующие три байта — результат; перед ними пауза t6 */
#define ADS_CMD_RDATAC 0x03u /* выдавать каждый результат без команды */
#define ADS_CMD_SDATAC 0x0Fu /* перестать: снова слушать команды */
#define ADS_CMD_RREG 0x10u /* 0001 rrrr, второй байт 0000 nnnn: nnnn + 1 регистров */
#define ADS_CMD_WREG 0x50u /* 0101 rrrr, второй байт 0000 nnnn */
#define ADS_CMD_SELFCAL 0xF0u /* самокалибровка нуля и усиления */
#define ADS_CMD_SELFOCAL 0xF1u /* только нуля */
#define ADS_CMD_SELFGCAL 0xF2u /* только усиления */
#define ADS_CMD_SYSOCAL 0xF3u /* ноль по тому, что сейчас на входе */
#define ADS_CMD_SYSGCAL 0xF4u /* полная шкала по тому, что сейчас на входе */
#define ADS_CMD_SYNC 0xFCu /* остановить и обнулить фильтр до WAKEUP */
#define ADS_CMD_STANDBY 0xFDu /* уснуть до WAKEUP */
#define ADS_CMD_RESET 0xFEu
/* ── регистры ─────────────────────────────────────────────────────────── */
#define ADS_STATUS 0x00u
#define ADS_MUX 0x01u
#define ADS_ADCON 0x02u
#define ADS_DRATE 0x03u
#define ADS_IO 0x04u
#define ADS_OFC0 0x05u /* OFC0…OFC2 — поправка нуля, младший байт первым */
#define ADS_FSC0 0x08u /* FSC0…FSC2 — поправка шкалы, младший байт первым */
/* STATUS */
#define ADS_ID_POS 4u /* 7:4 — номер микросхемы, только чтение */
#define ADS_ID 3u
#define ADS_ORDER 0x08u /* 1 — младший бит первым; здесь всегда 0 */
#define ADS_ACAL 0x04u /* самокалибровка сама после записи PGA, DRATE, BUFEN */
#define ADS_BUFEN 0x02u /* входной буфер */
#define ADS_DRDY_BIT 0x01u /* копия вывода DRDY, только чтение */
#define ADS_STATUS_RW (ADS_ORDER | ADS_ACAL | ADS_BUFEN)
/* MUX: 7:4 — положительный вход, 3:0 — отрицательный; 0…7 — AIN0…AIN7, 8 — AINCOM */
#define ADS_AINCOM 8u
#define ADS_DIFF(p, n) ((uint8_t)((p) << 4 | (n)))
#define ADS_SE(ch) ADS_DIFF(ch, ADS_AINCOM)
/* ADCON */
#define ADS_CLK_POS 5u /* 6:5 — такт на D0/CLKOUT: 00 выключен, 01 — CLKIN, 10 — /2, 11 — /4 */
#define ADS_SDCS_POS 3u /* 4:3 — токи поиска обрыва: 00 выкл., 0,5, 2, 10 мкА */
#define ADS_PGA_POS 0u /* 2:0 — усиление 1, 2, 4 … 64 */
enum { ADS_PGA1, ADS_PGA2, ADS_PGA4, ADS_PGA8, ADS_PGA16, ADS_PGA32, ADS_PGA64 };
/* DRATE — коды скоростей, отсчётов в секунду */
#define ADS_DR_30000 0xF0u
#define ADS_DR_15000 0xE0u
#define ADS_DR_7500 0xD0u
#define ADS_DR_3750 0xC0u
#define ADS_DR_2000 0xB0u
#define ADS_DR_1000 0xA1u
#define ADS_DR_500 0x92u
#define ADS_DR_100 0x82u
#define ADS_DR_60 0x72u
#define ADS_DR_50 0x63u
#define ADS_DR_30 0x53u
#define ADS_DR_25 0x43u
#define ADS_DR_15 0x33u
#define ADS_DR_10 0x23u
#define ADS_DR_5 0x13u
#define ADS_DR_2_5 0x03u
/* ── времена: такт CLKIN 7,68 МГц, τ = 130,2 нс ──────────────────────── */
#define ADS_CLKIN_HZ 7680000u
#define ADS_T6_US 7u /* t6 = 50 τ = 6,51 мкс: от команды RDATA/RREG до чтения */
#define ADS_T11_US 4u /* t11 = 24 τ = 3,13 мкс: после SYNC и RDATAC до следующей команды */
#define ADS_TIMEOUT_US 2000000u /* дольше любой калибровки на 2,5 SPS, с запасом */
#define ADS_FS 8388607 /* код полной шкалы: +2·VREF / PGA */
enum { ADS_OK = 0, ADS_E_TIMEOUT = -1, ADS_E_VERIFY = -2, ADS_E_ID = -3, ADS_E_RANGE = -4 };
typedef struct {
uint8_t reg[5]; /* что драйвер записал в STATUS…IO */
uint8_t rdatac; /* 1 — микросхема в RDATAC и команд не слушает */
uint32_t frames, bytes; /* кадров CS и байт по SPI */
} ads_t;
/* ── порт: в прошивке — SPI1 и EXTI0, в стенде — строки к модели ───── */
void ads_port_cs(int level); /* 0 — микросхема выбрана */
void ads_port_xfer(const uint8_t *tx, uint8_t *rx, uint32_t n); /* n байт подряд, CS не трогает */
void ads_port_delay_us(uint32_t us);
int ads_port_wait_drdy(uint32_t timeout_us); /* спад DRDY (флаг EXTI) за timeout_us: 1 — был и снят */
/* ── драйвер ──────────────────────────────────────────────────────────── */
void ads_frame(ads_t *d, const uint8_t *tx, uint8_t *rx, uint32_t n);
void ads_cmd(ads_t *d, uint8_t cmd);
void ads_wreg(ads_t *d, uint32_t reg, const uint8_t *v, uint32_t n);
void ads_rreg(ads_t *d, uint32_t reg, uint8_t *v, uint32_t n);
int ads_reset(ads_t *d, uint8_t r[5]);
int ads_config(ads_t *d, uint8_t status, uint8_t mux, uint8_t adcon, uint8_t drate);
int ads_set(ads_t *d, uint32_t reg, uint8_t v);
int ads_calibrate(ads_t *d, uint8_t cmd);
void ads_read_cal(ads_t *d, int32_t *ofc, uint32_t *fsc);
int32_t ads_s24(const uint8_t b[3]);
int ads_read_data(ads_t *d, int32_t *code);
int ads_convert(ads_t *d, uint8_t mux, int32_t *code);
void ads_scan_start(ads_t *d, uint8_t mux);
int ads_scan_next(ads_t *d, uint8_t next, int32_t *code);
void ads_rdatac_start(ads_t *d);
int ads_rdatac_read(ads_t *d, int32_t *code);
int ads_rdatac_stop(ads_t *d);
uint32_t ads_sps_x10(uint8_t drate);
int32_t ads_uv(int32_t code, uint32_t vref_uv, uint32_t pga);
#endif
ads15f.c — драйвер: кадры, t6, сброс и настройка, калибровка, RDATA и RDATAC, обход по кругу, микровольты.
/* ads15f.c — раздел 15д: драйвер ADS1256 по SPI. Один и тот же файл собирается в прошивку (armclang,
* Cortex-M3) и в стенд (arm-none-eabi-gcc, QEMU): микроконтроллера он не знает, только порт ads_port_*. */
#include "ads15f.h"
/* DRATE: код и скорость × 10 (2,5 SPS — 25) */
static const uint8_t dr_code[16] = {0xF0, 0xE0, 0xD0, 0xC0, 0xB0, 0xA1, 0x92, 0x82,
0x72, 0x63, 0x53, 0x43, 0x33, 0x23, 0x13, 0x03};
static const uint32_t dr_x10[16] = {300000, 150000, 75000, 37500, 20000, 10000, 5000, 1000,
600, 500, 300, 250, 150, 100, 50, 25};
/* один кадр: CS вниз, n байт туда и обратно, CS вверх */
void ads_frame(ads_t *d, const uint8_t *tx, uint8_t *rx, uint32_t n)
{
ads_port_cs(0);
ads_port_xfer(tx, rx, n);
ads_port_cs(1);
d->frames++;
d->bytes += n;
}
/* команда без данных; после SYNC и RDATAC — пауза t11 */
void ads_cmd(ads_t *d, uint8_t cmd)
{
uint8_t rx;
ads_frame(d, &cmd, &rx, 1);
if (cmd == ADS_CMD_SYNC || cmd == ADS_CMD_RDATAC)
ads_port_delay_us(ADS_T11_US);
if (cmd == ADS_CMD_RDATAC)
d->rdatac = 1;
if (cmd == ADS_CMD_SDATAC || cmd == ADS_CMD_RESET)
d->rdatac = 0;
}
void ads_wreg(ads_t *d, uint32_t reg, const uint8_t *v, uint32_t n)
{
uint8_t tx[13], rx[13];
tx[0] = (uint8_t)(ADS_CMD_WREG | (reg & 15u));
tx[1] = (uint8_t)((n - 1u) & 15u);
for (uint32_t i = 0; i < n; i++)
tx[2 + i] = v[i];
ads_frame(d, tx, rx, n + 2u);
}
/* RREG: два байта команды, пауза t6 — и только потом ответ, всё в одном кадре */
void ads_rreg(ads_t *d, uint32_t reg, uint8_t *v, uint32_t n)
{
uint8_t tx[11] = {(uint8_t)(ADS_CMD_RREG | (reg & 15u)), (uint8_t)((n - 1u) & 15u)}, rx[11];
ads_port_cs(0);
ads_port_xfer(tx, rx, 2);
ads_port_delay_us(ADS_T6_US);
ads_port_xfer(tx + 2, v, n); /* на DIN — нули */
ads_port_cs(1);
d->frames++;
d->bytes += n + 2u;
}
/* SDATAC на случай, если микросхема осталась в RDATAC, RESET, первый результат — и пять регистров */
int ads_reset(ads_t *d, uint8_t r[5])
{
ads_cmd(d, ADS_CMD_SDATAC);
(void)ads_port_wait_drdy(0);
ads_cmd(d, ADS_CMD_RESET);
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_rreg(d, ADS_STATUS, r, 5);
for (uint32_t i = 0; i < 5u; i++)
d->reg[i] = r[i];
return r[0] >> ADS_ID_POS == ADS_ID ? ADS_OK : ADS_E_ID;
}
/* STATUS, MUX, ADCON, DRATE одним WREG и обратно; в STATUS сравниваются только ORDER, ACAL и BUFEN */
int ads_config(ads_t *d, uint8_t status, uint8_t mux, uint8_t adcon, uint8_t drate)
{
uint8_t w[4] = {status, mux, adcon, drate}, r[4];
ads_wreg(d, ADS_STATUS, w, 4);
ads_rreg(d, ADS_STATUS, r, 4);
for (uint32_t i = 0; i < 4u; i++)
d->reg[i] = w[i];
if ((r[0] ^ status) & ADS_STATUS_RW || r[1] != mux || r[2] != adcon || r[3] != drate)
return ADS_E_VERIFY;
return ADS_OK;
}
int ads_set(ads_t *d, uint32_t reg, uint8_t v)
{
uint8_t r;
ads_wreg(d, reg, &v, 1);
ads_rreg(d, reg, &r, 1);
if (reg < 5u)
d->reg[reg] = v;
return (reg == ADS_STATUS ? (r ^ v) & ADS_STATUS_RW : r ^ v) ? ADS_E_VERIFY : ADS_OK;
}
/* SELFCAL, SELFOCAL, SELFGCAL, SYSOCAL, SYSGCAL: калибровка кончилась — DRDY упал */
int ads_calibrate(ads_t *d, uint8_t cmd)
{
(void)ads_port_wait_drdy(0);
ads_cmd(d, cmd);
return ads_port_wait_drdy(ADS_TIMEOUT_US) ? ADS_OK : ADS_E_TIMEOUT;
}
/* OFC — 24 бита со знаком, FSC — без знака; оба младшим байтом вперёд */
void ads_read_cal(ads_t *d, int32_t *ofc, uint32_t *fsc)
{
uint8_t r[6], b[3] = {0, 0, 0};
ads_rreg(d, ADS_OFC0, r, 6);
b[0] = r[2];
b[1] = r[1];
b[2] = r[0];
*ofc = ads_s24(b);
*fsc = (uint32_t)r[5] << 16 | (uint32_t)r[4] << 8 | r[3];
}
/* 24 бита в дополнительном коде, старший байт первым → int32_t */
int32_t ads_s24(const uint8_t b[3])
{
int32_t v = (int32_t)((uint32_t)b[0] << 16 | (uint32_t)b[1] << 8 | b[2]);
return v & 0x800000 ? v - 0x1000000 : v;
}
static int range(int32_t code)
{
return code == ADS_FS || code == -ADS_FS - 1 ? ADS_E_RANGE : ADS_OK; /* упёрлись в край */
}
/* RDATA, пауза t6, три байта — в одном кадре */
int ads_read_data(ads_t *d, int32_t *code)
{
uint8_t c = ADS_CMD_RDATA, z[3] = {0, 0, 0}, rx, b[3];
ads_port_cs(0);
ads_port_xfer(&c, &rx, 1);
ads_port_delay_us(ADS_T6_US);
ads_port_xfer(z, b, 3);
ads_port_cs(1);
d->frames++;
d->bytes += 4;
*code = ads_s24(b);
return range(*code);
}
/* выбрать вход и начать заново: WREG MUX, SYNC (фильтр стоит и пуст), WAKEUP (пошёл) */
void ads_scan_start(ads_t *d, uint8_t mux)
{
ads_wreg(d, ADS_MUX, &mux, 1);
d->reg[ADS_MUX] = mux;
ads_cmd(d, ADS_CMD_SYNC);
(void)ads_port_wait_drdy(0); /* старый спад — не наш */
ads_cmd(d, ADS_CMD_WAKEUP);
}
/* по кругу: спад DRDY — следующий вход, SYNC, WAKEUP, и только потом RDATA — результат прежнего входа
* лежит в микросхеме, пока новый фильтр копит отсчёты */
int ads_scan_next(ads_t *d, uint8_t next, int32_t *code)
{
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_wreg(d, ADS_MUX, &next, 1);
d->reg[ADS_MUX] = next;
ads_cmd(d, ADS_CMD_SYNC);
ads_cmd(d, ADS_CMD_WAKEUP);
return ads_read_data(d, code);
}
/* один установившийся результат с входа mux */
int ads_convert(ads_t *d, uint8_t mux, int32_t *code)
{
ads_scan_start(d, mux);
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
return ads_read_data(d, code);
}
/* RDATAC: дальше каждый результат выдвигается по спаду DRDY без команды; команды не слышны до SDATAC */
void ads_rdatac_start(ads_t *d)
{
(void)ads_port_wait_drdy(0);
ads_cmd(d, ADS_CMD_RDATAC);
}
int ads_rdatac_read(ads_t *d, int32_t *code)
{
uint8_t z[3] = {0, 0, 0}, b[3];
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_frame(d, z, b, 3);
*code = ads_s24(b);
return range(*code);
}
int ads_rdatac_stop(ads_t *d)
{
if (!ads_port_wait_drdy(ADS_TIMEOUT_US))
return ADS_E_TIMEOUT;
ads_cmd(d, ADS_CMD_SDATAC);
return ADS_OK;
}
/* скорость × 10 по коду DRATE; неизвестный код — 0 */
uint32_t ads_sps_x10(uint8_t drate)
{
for (uint32_t i = 0; i < 16u; i++)
if (dr_code[i] == drate)
return dr_x10[i];
return 0;
}
/* код → микровольты: вход = код × 2·VREF / (PGA × (2^23 − 1)); PGA 110 и 111 — оба ×64 */
int32_t ads_uv(int32_t code, uint32_t vref_uv, uint32_t pga)
{
uint32_t sh = pga > ADS_PGA64 ? ADS_PGA64 : pga;
int64_t x = (int64_t)code * 2 * vref_uv;
int64_t den = (int64_t)ADS_FS << sh;
return (int32_t)(x >= 0 ? (x + den / 2) / den : -((-x + den / 2) / den));
}
fw15f.c — прошивка STM32F103: SPI1 в режиме 1, DRDY на EXTI0, восемь входов по кругу.
/* fw15f.c — раздел 15д: ADS1256 на STM32F103 (BluePill), 72 МГц, APB2 = 72 МГц.
* SPI1 без ремапа: PA5 — SCLK, PA6 — DOUT (MISO), PA7 — DIN (MOSI), режим 1 (CPOL = 0, CPHA = 1),
* 72 МГц / 64 = 1,125 МГц — ниже предела CLKIN / 4 = 1,92 МГц; PA4 — CS, обычный выход; PB0 — DRDY, вход с
* подтяжкой, EXTI0 по спаду. Опорное на плате — 2,5 В. Восемь несимметричных входов AIN0…AIN7 против AINCOM
* по кругу с SYNC и WAKEUP, буфер включён, ×1, 1000 SPS; после смены усиления или скорости — SELFCAL.
* Тактирование до 72 МГц — как в разделе 6. */
#include "stm32f10x.h"
#include "ads15f.h"
#define CPU_HZ 72000000u
#define VREF_UV 2500000u /* опорное платы: сюда — измеренное значение */
static ads_t adc;
static volatile uint8_t drdy;
volatile int32_t ch_uv[8];
volatile uint32_t rounds, 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 spi1_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_SPI1EN;
GPIOA->BSRR = 1u << 4; /* CS вверх до настройки вывода */
/* PA4 — выход 50 МГц (0011), PA5 и PA7 — альтернативная функция push-pull (1011), PA6 — вход (0100) */
GPIOA->CRL = (GPIOA->CRL & 0x0000FFFFu) | 0xB4B30000u;
GPIOB->CRL = (GPIOB->CRL & ~0xFu) | 0x8u; /* PB0 — вход с подтяжкой (1000) */
GPIOB->BSRR = 1u << 0;
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_CPHA | SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_SSM | SPI_CR1_SSI; /* режим 1, /64 */
SPI1->CR1 |= SPI_CR1_SPE;
}
void ads_port_cs(int level)
{
if (level)
GPIOA->BSRR = 1u << 4;
else
GPIOA->BRR = 1u << 4;
}
void ads_port_xfer(const uint8_t *tx, uint8_t *rx, uint32_t n)
{
for (uint32_t i = 0; i < n; i++) {
while (!(SPI1->SR & SPI_SR_TXE)) {
}
SPI1->DR = tx[i];
while (!(SPI1->SR & SPI_SR_RXNE)) {
}
rx[i] = (uint8_t)SPI1->DR;
}
while (SPI1->SR & SPI_SR_BSY) { /* перед подъёмом CS — раздел 18.5 */
}
}
/* DRDY: спад на PB0 */
void EXTI0_IRQHandler(void)
{
EXTI->PR = 1u << 0;
drdy = 1;
}
int ads_port_wait_drdy(uint32_t timeout_us)
{
uint32_t t0 = DWT->CYCCNT;
while (!drdy) {
if (expired(t0, timeout_us))
return 0;
}
drdy = 0;
return 1;
}
static void irq_init(void)
{
AFIO->EXTICR[0] = (AFIO->EXTICR[0] & ~0xFu) | 0x1u; /* EXTI0 ← PB0 */
EXTI->FTSR |= 1u << 0;
EXTI->IMR |= 1u << 0;
NVIC_EnableIRQ(EXTI0_IRQn);
}
/* восемь входов по кругу: результат k-го приходит, когда фильтр уже копит (k + 1)-й */
static int scan8(void)
{
int32_t code;
for (uint32_t k = 0; k < 8u; k++) {
int rc = ads_scan_next(&adc, ADS_SE((k + 1u) % 8u), &code);
if (rc != ADS_OK)
return rc;
ch_uv[k] = ads_uv(code, VREF_UV, ADS_PGA1);
}
rounds++;
return ADS_OK;
}
int main(void)
{
uint8_t r[5];
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
spi1_init();
irq_init();
while (ads_reset(&adc, r) != ADS_OK)
ads_port_delay_us(100000u); /* нет ответа: провод, питание или такт */
while (ads_config(&adc, ADS_BUFEN, ADS_SE(0), 0 << ADS_CLK_POS | ADS_PGA1, ADS_DR_1000) != ADS_OK
|| ads_calibrate(&adc, ADS_CMD_SELFCAL) != ADS_OK)
errors++;
ads_scan_start(&adc, ADS_SE(0));
for (;;) {
if (scan8() != ADS_OK) {
errors++;
ads_scan_start(&adc, ADS_SE(0));
}
}
}
Дальше: раздел 16 «UART» — самый первый интерфейс, который подключают.