176б. Термометр сопротивления PT100/PT1000: ратиометрия и 2-, 3-, 4-проводное подключение
В 96 температуру мерил NTC-термистор, в 104 — термопара. Для точного измерения от −50 до +200 °C в промышленности берут платиновый термометр сопротивления: PT100 или PT1000, 100 или 1000 Ом при 0 °C. Его кривая известна заранее, датчики взаимозаменяемы, но полезный сигнал мал: десятые доли ома на градус у PT100. Здесь — как перевести сопротивление в градусы, как мерить его встроенным АЦП так, чтобы опора не входила в результат, что делают провода длиной 10 м и сколько градусов на код остаётся у F103 и G474.
Опоры: коэффициенты уравнения — стандарт IEC 60751 (общеизвестные
константы; текст стандарта с разделом не сверялся). Другие наборы коэффициентов
и регистры MAX31865 — из дерева Zephyr v4.4.2 (раздел 34а): драйвер
drivers/sensor/maxim/max31865 и пример samples/shields/lmp90100_evb/rtd.
Даташиты F103 (DocID13587) и G474 (DS12288) — из паков Keil. Все расчёты —
модель на ПК _verify/rtd176c; код пересчёта собран armclang для обоих
камней и прогнан на ПК по всему диапазону против модели. Коэффициент
самонагрева 0,5 °C/мВт, кабель 10 м и разброс жил 5 % — примеры.
Как это называется в документации
Оригинал Перевод resistance temperature detector (RTD) термометр сопротивления Callendar–Van Dusen equation уравнение Каллендара — Ван Дюзена ratiometric measurement ратиометрическое (логометрическое) измерение reference resistor (RREF) опорный резистор excitation current ток возбуждения, ток через датчик 2-, 3-, 4-wire connection двух-, трёх-, четырёхпроводное подключение lead (wire) resistance сопротивление проводов force / sense leads токовые и измерительные провода self-heating самонагрев RTD-to-digital converter преобразователь «термометр сопротивления — код» fault detection обнаружение неисправности
176б.1 Платина и уравнение Каллендара — Ван Дюзена
Сопротивление платины растёт с температурой почти линейно. Точнее — по уравнению Каллендара — Ван Дюзена:
R(T) = R0 · (1 + A·T + B·T² + C·(T − 100)·T³),
где R0 — сопротивление при 0 °C, A = 3,9083·10⁻³, B = −5,775·10⁻⁷, а C = −4,183·10⁻¹² только ниже нуля; выше нуля C = 0. Одни и те же коэффициенты годятся и для PT100, и для PT1000: меняется только R0.
| Температура | PT100, Ом | PT1000, Ом | Крутизна PT100, Ом/°C |
|---|---|---|---|
| −50 °C | 80,306 | 803,06 | 0,3971 |
| 0 °C | 100,000 | 1000,00 | 0,3908 |
| +100 °C | 138,505 | 1385,05 | — |
| +200 °C | 175,856 | 1758,56 | 0,3677 |
Средний коэффициент между 0 и 100 °C — 0,003851 на градус (3850,5 ppm/°C); по нему датчики и различают. Но прямая через 0 и 100 °C — не кривая: при +200 °C она занижает на 3,00 °C, при −50 °C — на 1,15 °C, при +50 °C завышает на 0,37 °C. Крутизна падает с температурой: у PT1000 3,971 Ом/°C при −50 °C и 3,677 Ом/°C при +200 °C.
Наборов коэффициентов в ходу три, и они не совпадают:
| Где | A | B | Ошибка против IEC 60751 |
|---|---|---|---|
| IEC 60751 | 3,9083·10⁻³ | −5,775·10⁻⁷ | — |
| Zephyr, драйвер max31865 («ITS-90») | 3,9080·10⁻³ | −5,870·10⁻⁷ | +0,033 °C при 100 °C, +0,120 °C при 200 °C |
| Zephyr, пример lmp90100_evb/rtd | 3,90802·10⁻³ | −0,58020·10⁻⁶ | +0,015 °C при 100 °C, +0,045 °C при 200 °C, −0,022 °C при −50 °C |
Десятая доля градуса на 200 °C — мелочь для бытового термометра и заметная величина для поверки. Коэффициенты берут из паспорта своего датчика, а не из первого попавшегося драйвера.
176б.2 Из сопротивления в градусы
Выше нуля уравнение квадратное, и решается формулой:
T = (−A + √(A² − 4B·(1 − R/R0))) / (2B).
Модель на ПК сверила её с прямой кривой: расхождение 7,7·10⁻¹³ °C — точное решение. Ниже нуля член с C делает уравнение четвёртой степени. Варианты:
- Та же квадратная формула без C. Ошибка при −50 °C — −0,020 °C. Для многих задач достаточно.
- Итерации Ньютона от квадратного решения. Одна итерация даёт 6,9·10⁻⁸ °C на всём участке −50…0 °C.
- Полином по R. Драйвер max31865 в Zephyr ниже нуля считает полином пятой степени (ссылается на заметку AN709); против IEC 60751 на −50…0 °C он ошибается не больше чем на 0,001 °C.
Без плавающей точки проще таблица и прямая между точками, как у NTC в 96.3. Таблицу хранят в долях R/R0 — тогда она общая для PT100 и PT1000:
| Шаг таблицы | Точек на −60…+210 °C | Наибольшая ошибка интерполяции |
|---|---|---|
| 5 °C | 55 | 0,0011 °C |
| 10 °C | 28 | 0,0043 °C |
| 30 °C | 10 | 0,0389 °C |
Шаг 10 °C: 28 чисел по 4 байта и ошибка в тысячные доли градуса — меньше шага любого АЦП ниже. Запас −60 и +210 °C за краями рабочего диапазона нужен, чтобы шум у края не выбрасывал показание из таблицы.
А вот линейную формулу T = (R − R0) / (R0·A), которую драйвер max31865
использует, если собран без newlib, брать нельзя: −1,47 °C при 100 °C
и −5,90 °C при 200 °C.
176б.3 Ратиометрия: опора сокращается
Прямой путь — источник тока 1 мА и АЦП, который меряет напряжение на датчике относительно своей опоры. Тогда ошибка опоры и ошибка тока входят в результат один к одному: опора на 1 % ниже номинала — это +2,59 °C при 0 °C и +4,83 °C при 200 °C.
Ратиометрическая схема убирает и то и другое. Датчик стоит в делителе с опорным резистором Rref, делитель питается от того же напряжения, что служит опорой АЦП. Код в точке A — отношение напряжений:
a = FS · (R + Rпров) / (Rref + R + Rпров),
и напряжение питания из него ушло. Отсюда R = Rref · a / (FS − a): код
на Rref показывает ток, код на датчике — напряжение, их отношение —
сопротивление. Модель перебрала 2501 температуру от −50 до +200 °C через
0,1 °C при VDDA 3,0, 3,3 и 3,6 В: коды совпали во всех 2501 случаях
(при 0 °C — 5958 из 65536). Тот же приём у NTC назван логометрическим
измерением (96.2).
Теперь эталон — сам Rref, и его ошибка переходит в R один к одному:
| Ошибка Rref | При 0 °C | При 200 °C |
|---|---|---|
| допуск 0,1 % | 0,256 °C | 0,478 °C |
| ТКС 25 ppm/K при нагреве платы на 60 K — 0,15 % | 0,384 °C | 0,717 °C |
Допуск снимает калибровка (202.3): в прошивку пишут измеренное значение Rref, а не номинал (202.5). Дрейф остаётся — резистор с малым ТКС дешевле, чем источник опоры того же класса (177).
Условие ратиометрии — делитель питается от опоры АЦП. У F103
в корпусах LQFP48 (BluePill) и LQFP64 вход VREF+ соединён с VDDA внутри,
в LQFP100 он выведен отдельно (вывод 21) и его подключают к VDDA на плате. У G474 вывод VREF+
в части корпусов сдвоен с VDDA, и тогда VREFBUF недоступен (DS12288);
в остальных VREF+ соединяют с VDDA и VREFBUF не включают. Опора АЦП
2,5 В от VREFBUF при делителе от 3,3 В — это снова абсолютное измерение
с ошибкой питания в результате.
176б.4 Два, три, четыре провода
Жила 0,5 мм² длиной 10 м — 0,35 Ом. При двух проводах датчик меряется вместе с обеими жилами, 0,70 Ом:
| Датчик | Ошибка при 0 °C | При 200 °C | Кабель 0,5 мм² на 0,1 °C |
|---|---|---|---|
| PT100 | +1,792 °C | +1,904 °C | 0,56 м |
| PT1000 | +0,179 °C | +0,190 °C | 5,6 м |
У PT100 два провода годятся только на плате; у PT1000 ошибка в десять раз меньше при том же кабеле — это одна из причин выбрать PT1000.
Три провода. Третья жила идёт от верхнего вывода датчика, и ток по ней не течёт: АЦП видит в точке B напряжение самого вывода. Разность a − b — падение на верхней токовой жиле L1, и если нижняя L2 такая же, её падение равно ему. Отсюда числитель 2b − a: из напряжения на датчике с нижней жилой вычтено падение одной жилы. Компенсация точна, пока жилы равны; если одна на 5 % больше другой, остаётся 0,0448 °C у PT100 и 0,0045 °C у PT1000.
Четыре провода. Две жилы несут ток, две другие — C и D — только снимают напряжение прямо с выводов датчика. Числитель c − d — напряжение на самом датчике, знаменатель FS − a — ток через Rref. Провода в формулу не входят вообще: сопротивление жил и их разброс — ноль.
| Схема | Числитель | Каналов АЦП |
|---|---|---|
| 2 провода | a | 1 |
| 3 провода | 2b − a | 2 |
| 4 провода | c − d | 3 |
У G474 разность c − d можно снять одним дифференциальным каналом (15а.4). Но это не выигрыш: единица кода вдвое крупнее, а синфазное напряжение по DS12288 должно лежать в пределах VREF+/2 ± 0,18 В — 1,47…1,83 В при 3,3 В. В нашем делителе датчик сидит у земли, синфазное — 0,15 В; пришлось бы поднимать датчик резистором снизу. Три несимметричных канала проще.
176б.5 Шаг в градусах, самонагрев и когда нужен внешний АЦП
Сигнал датчика — ток на крутизну. Больше ток — крупнее сигнал, но датчик греется своей мощностью I²·R, и показывает температуру выше среды. Ниже — идеальный источник тока, 0 °C, опора 3,3 В; единица кода несимметричного входа — 0,806 мВ при 12 битах и 0,0504 мВ при 16 битах (передискретизация 256 со сдвигом 4, 15а.5), дифференциального — 1,611 и 0,1007 мВ. Самонагрев — при 0,5 °C/мВт (пример: настоящее число в паспорте датчика, оно зависит от корпуса и среды).
| Датчик и ток | Сигнал, мВ/°C | Мощность | Самонагрев | 12 бит | 16 бит | Дифф. 12 бит | Дифф. 16 бит |
|---|---|---|---|---|---|---|---|
| PT100, 1 мА | 0,391 | 0,100 мВт | 0,0500 °C | 2,061 °C | 0,1288 °C | 4,123 °C | 0,2577 °C |
| PT100, 0,1 мА | 0,039 | 0,001 мВт | 0,0005 °C | 20,614 °C | 1,2884 °C | 41,228 °C | 2,5768 °C |
| PT1000, 1 мА | 3,908 | 1,000 мВт | 0,5000 °C | 0,206 °C | 0,0129 °C | 0,412 °C | 0,0258 °C |
| PT1000, 0,1 мА | 0,391 | 0,010 мВт | 0,0050 °C | 2,061 °C | 0,1288 °C | 4,123 °C | 0,2577 °C |
| PT100, 1 мА, усилитель ×10 | 3,908 | 0,100 мВт | 0,0500 °C | 0,206 °C | 0,0129 °C | 0,412 °C | 0,0258 °C |
Вывод простой. PT100 прямо на 12-битный АЦП — два градуса на код; ему нужен усилитель (инструментальный, раздел 176) или внешний АЦП. PT1000 при 0,1 мА даёт тот же сигнал, что PT100 при 1 мА, а греется вдесятеро меньше. При 1 мА PT1000 даёт крупный сигнал, но полградуса самонагрева. Ошибка усиления, если стоит усилитель, входит в R один к одному, как допуск Rref.
В делителе от VDDA ток задаёт Rref. Для PT1000 при 3,3 В и 0 °C:
| Rref | Ток | Мощность | Самонагрев | F103, 12 бит | G474, 16 бит |
|---|---|---|---|---|---|
| 2,2 кОм | 1,031 мА | 1,063 мВт | 0,532 °C | 0,29 °C | 0,018 °C |
| 4,7 кОм | 0,579 мА | 0,335 мВт | 0,168 °C | 0,43 °C | 0,027 °C |
| 10 кОм | 0,300 мА | 0,090 мВт | 0,045 °C | 0,76 °C | 0,047 °C |
| 33 кОм | 0,097 мА | 0,009 мВт | 0,005 °C | 2,19 °C | 0,137 °C |
| PT100 и 3,3 кОм | 0,971 мА | 0,094 мВт | 0,047 °C | 2,19 °C | 0,137 °C |
Rref 10 кОм уравнивает самонагрев и шаг G474 — около 0,05 °C каждый; его и берёт код ниже. Шестнадцать бит — не точность: лишние четыре бита настоящие, только если шум АЦП их раскачивает (15а.5, раздел 165), а ошибки нелинейности и смещения АЦП никуда не деваются (202).
Специальная микросхема. Когда нужны сотые доли, ставят преобразователь для термометров сопротивления. MAX31865 из дерева Zephyr: код сопротивления — 15 бит в регистрах 0x01–0x02, младший бит D0 — флаг неисправности; R = код / 32768 · Rref. Привязка Zephyr советует Rref 400 Ом для PT100 и 4000 Ом для PT1000 (со ссылкой на даташит): шаг 0,01221 и 0,1221 Ом, в обоих случаях 0,0312 °C при 0 °C. Регистр настройки 0x00: бит 7 включает смещение датчика (VBIAS), бит 6 — непрерывное преобразование, бит 5 — одиночное, бит 4 — трёхпроводную схему, биты 3:2 — цикл поиска неисправности, бит 1 сбрасывает её флаги, бит 0 выбирает фильтр 50 Гц вместо 60 Гц. Регистр 0x07 — причина неисправности, 0x03–0x06 — пороги; при записи к адресу добавляют 0x80. Драйвер после включения смещения ждёт 66 мс до чтения. Формула драйвера R = код / 32768 · Rref — та же ратиометрия: код — доля от Rref.
176б.6 Код: из кодов АЦП в сотые доли градуса
Переносимая часть rtd.c не трогает регистров: на вход — 16-битные коды
точек A, B, C, D, на выход — температура в сотых долях градуса и состояние.
16 бит на G474 даёт сама передискретизация (15а.5), на F103 — сумма
16 измерений в программе (усреднение — 165.8). Параметры:
#define RTD_R0_OHM 1000u /* PT1000: 1000 Ом при 0 °C */
#define RTD_RREF_MOHM 10000000u /* опорный резистор 10 кОм, в миллиомах: сюда — измеренное значение */
#define RTD_FS 65536 /* полная шкала: код, соответствующий напряжению опоры */
#define RTD_VDDA_MV 3600u /* худшее питание делителя — только для проверки тока */
#define RTD_I_MAX_UA 500u /* больше — самонагрев: 0,5 мА через PT1000 — 0,25 мВт */
#define RTD_DEN_MIN 4096 /* на Rref меньше 1/16 опоры — ток не течёт */
#define RTD_W_SHORT 700000 /* R/R0 ниже 0,70 (около −76 °C) — замыкание */
#define RTD_W_OPEN 2000000 /* R/R0 выше 2,00 (около +266 °C) — обрыв */
#define RTD_W_LEAD 10000 /* провода: не больше 1 % R0 (10 Ом у PT1000) */
/* числитель отношения R/Rref для трёх схем; знаменатель — всегда RTD_FS - a */
#define RTD_NUM_2W(a) (a) /* датчик + оба провода */
#define RTD_NUM_3W(a, b) (2 * (b) - (a)) /* b — верх датчика по третьему проводу */
#define RTD_NUM_4W(c, d) ((c) - (d)) /* c, d — измерительные провода */
Проверки на этапе сборки:
_Static_assert((uint64_t)RTD_VDDA_MV * 1000000u / (RTD_RREF_MOHM + RTD_R0_OHM * 1000u) <= RTD_I_MAX_UA,
"ток через датчик больше RTD_I_MAX_UA: самонагрев");
_Static_assert((uint64_t)RTD_FS * RTD_RREF_MOHM / (RTD_RREF_MOHM + RTD_R0_OHM * (RTD_W_OPEN / 1000u)) > RTD_DEN_MIN,
"на пороге обрыва ток должен ещё отличаться от нуля");
_Static_assert(RTD_W_SHORT < 763278 && RTD_W_OPEN > 1795275, "пороги аварии - за краями таблицы");
Первая считает ток при худшем питании 3,6 В: 327,3 мкА (при 3,3 В — 300,0 мкА, 0,090 мВт). Ошибись в Rref на порядок — 1 кОм вместо 10 — и сборка остановится с сообщением про самонагрев. Вторая: даже на пороге обрыва на Rref остаётся 54613 единиц кода — больше порога 4096.
Сопротивление — отношением в 64-битных целых, температура — по таблице R/R0 через 10 °C:
/* R/R0 = (Rref / R0) * num / den, в миллионных, с округлением к ближайшему */
int32_t rtd_w_ppm(int32_t num, int32_t den)
{
int64_t n = (int64_t)num * RTD_RREF_MOHM * 1000;
int64_t d = (int64_t)den * RTD_R0_OHM;
n += (n >= 0 ? d : -d) / 2;
return (int32_t)(n / d);
}
/* R/R0 -> сотые доли градуса: таблица через 10 °C и прямая между точками */
rtd_status_t rtd_temp(int32_t w, int32_t *t_centi)
{
if (w < rtd_tab[0] || w > rtd_tab[RTD_N - 1])
return RTD_RANGE;
unsigned i = 0;
while (i < RTD_N - 2 && w >= rtd_tab[i + 1])
i++;
int32_t span = rtd_tab[i + 1] - rtd_tab[i];
*t_centi = (RTD_T0 + RTD_STEP * (int32_t)i) * 100 + (RTD_STEP * 100 * (w - rtd_tab[i]) + span / 2) / span;
return RTD_OK;
}
Диагностика идёт до пересчёта, и порядок проверок важен:
rtd_status_t rtd_read(int32_t a, int32_t num, int32_t *t_centi)
{
int32_t den = RTD_FS - a; /* напряжение на Rref — это ток */
if (den < RTD_DEN_MIN)
return RTD_OPEN; /* тока нет: оборван токовый провод или датчик */
int32_t w2 = rtd_w_ppm(a, den); /* датчик вместе с проводами */
int32_t w = rtd_w_ppm(num, den);
if (w2 < RTD_W_SHORT)
return RTD_SHORT;
if (w2 - w > RTD_W_LEAD || w - w2 > RTD_W_LEAD)
return RTD_SENSE; /* провода «стали» больше 1 % R0: висит измерительный провод */
if (w > RTD_W_OPEN)
return RTD_OPEN;
return rtd_temp(w, t_centi);
}
Сначала ток. При обрыве токовой жилы в четырёхпроводной схеме ток пропадает, c и d равны, числитель — ноль, и без этой проверки обрыв прочитался бы как замыкание. Потом двухпроводная оценка w2 — датчик вместе с жилами: если и она ниже 0,70 R0 (около −75,8 °C), замкнут сам датчик или кабель. Потом сравнение двух оценок: их разность — это сопротивление жил, и больше 10 Ом у PT1000 оно не бывает. Оборванная измерительная жила оставляет вход АЦП висеть, и разность выходит за предел. Выше 2,00 R0 (+266,3 °C) — обрыв или датчик вне рабочей области.
Прогон на ПК (rtd_host.c + тот же rtd.c, gcc):
- Все 955498 значений R/R0 в миллионных от −50 до +200 °C: ответ совпал с целочисленной моделью на Python во всех 955498; наибольшая ошибка против точного обратного преобразования — 0,0092 °C (у −44,92 °C): интерполяция плюс округление до сотой.
- На сетке через 0,01 °C — 25001 точка — код вернул ровно заданную температуру.
- Полный путь от кодов, 16 бит, кабель 10 м, 2501 температура через 0,1 °C, все совпали с моделью: четыре провода — ошибка ±0,030 °C, три — от −0,060 до +0,070 °C, два — от +0,160 до +0,220 °C (провода плюс шаг). Те же четыре провода с 12-битными кодами F103 — от −0,26 до +0,54 °C.
- Диагностика, 12 случаев: обрыв токовой жилы верхней и нижней — обрыв; замыкание датчика при четырёх и двух проводах и замыкание у платы — КЗ; измерительная жила, читающая 0 или середину шкалы, и оборванная третья жила — измерительный провод; −60 и +250 °C — вне таблицы; +300 °C — обрыв; 0 °C — норма.
176б.7 Грабли
Коэффициенты «как в драйвере». Драйвер max31865 в Zephyr берёт A = 3,9080·10⁻³ и B = −5,870·10⁻⁷ — на 200 °C это +0,120 °C против IEC 60751. Без newlib он вовсе считает прямую: −5,90 °C при 200 °C.
PT100 на двух проводах. Десять метров жилы 0,5 мм² — +1,79 °C. PT1000 на тех же проводах ошибётся на 0,18 °C; дальше — три или четыре провода.
Трёхпроводная схема с разными жилами. Компенсация держится на равенстве L1 и L2. Клемма с плохим контактом или жилы разного сечения — и разность идёт прямо в результат.
Опора АЦП не та, что питает делитель. VREFBUF 2,5 В при делителе от
VDDA 3,3 В — ратиометрии нет, 1 % питания — 2,59 °C при 0 °C.
Ток «для крупного сигнала». PT1000 с Rref 2,2 кОм греется на
0,532 °C (при 0,5 °C/мВт) — в 30 раз больше шага G474 с тем же Rref
(0,018 °C).
_Static_assert на ток ловит ошибку номинала при сборке.
Обрыв похож на замыкание. В четырёхпроводной схеме без тока c = d, числитель ноль. Проверяй ток (знаменатель) раньше сопротивления.
Висящий измерительный провод. Оборванная жила C или D оставляет вход АЦП без привязки, и он может показать правдоподобное число. Ловит это сравнение с двухпроводной оценкой: провода не могут «весить» 10 Ом.
Шестнадцать бит без шума. Сумма одинаковых кодов не добавляет разрядов: F103 без шума остаётся с шагом 0,76 °C.
Дифференциальный канал у земли. Синфазное 0,15 В против нужных 1,47…1,83 В — канал G474 работает вне условий даташита.
176б.8 Итоги
- Кривая платины — Каллендар — Ван Дюзен: PT100 — 80,306 Ом при −50 °C, 138,505 при 100 °C, 175,856 при 200 °C. Прямая через 0 и 100 °C ошибается на 3,00 °C при 200 °C.
- Обратно: квадратная формула выше нуля, одна итерация Ньютона ниже (6,9·10⁻⁸ °C). В целых — таблица R/R0 через 10 °C, 28 точек, 0,0043 °C.
- Ратиометрия: делитель от опоры АЦП, R = Rref · a / (FS − a). Коды не
зависят от
VDDA(2501 из 2501); без неё 1 % опоры — 2,59 °C. Эталоном становится Rref: 0,1 % — 0,256 °C при 0 °C. - Провода 10 м × 0,5 мм²: два провода — +1,792 °C у PT100 и +0,179 °C у PT1000; три — остаток от разброса жил (5 % — 0,0448 °C у PT100); четыре — ноль.
- Шаг: PT100 при 1 мА на 12 битах — 2,061 °C; PT1000 с Rref 10 кОм — 0,76 °C на F103 и 0,047 °C на G474 с передискретизацией при 0,090 мВт на датчике. MAX31865 — 0,0312 °C.
- Код: 64-битное отношение, таблица, сотые градуса; проверки тока, замыкания, проводов и обрыва; 955498 из 955498 совпали с моделью, ошибка не больше 0,0092 °C; путь от кодов при четырёх проводах — ±0,030 °C.
Полные исходники
rtd.h — настройки делителя и интерфейс пересчёта.
/* 176б: термометр сопротивления PT1000 — коды АЦП в сотые доли градуса, в целых числах.
* Делитель VDDA -> Rref -> точка A -> провод -> датчик -> провод -> земля; опора АЦП — тот же VDDA.
* Коды — 16 бит: G474 с передискретизацией 256 и сдвигом 4 или сумма 16 измерений F103. */
#ifndef RTD_H
#define RTD_H
#include <stdint.h>
#define RTD_R0_OHM 1000u /* PT1000: 1000 Ом при 0 °C */
#define RTD_RREF_MOHM 10000000u /* опорный резистор 10 кОм, в миллиомах: сюда — измеренное значение */
#define RTD_FS 65536 /* полная шкала: код, соответствующий напряжению опоры */
#define RTD_VDDA_MV 3600u /* худшее питание делителя — только для проверки тока */
#define RTD_I_MAX_UA 500u /* больше — самонагрев: 0,5 мА через PT1000 — 0,25 мВт */
#define RTD_DEN_MIN 4096 /* на Rref меньше 1/16 опоры — ток не течёт */
#define RTD_W_SHORT 700000 /* R/R0 ниже 0,70 (около −76 °C) — замыкание */
#define RTD_W_OPEN 2000000 /* R/R0 выше 2,00 (около +266 °C) — обрыв */
#define RTD_W_LEAD 10000 /* провода: не больше 1 % R0 (10 Ом у PT1000) */
#define RTD_T0 (-60) /* таблица: от −60 °C — запас за −50 */
#define RTD_STEP 10 /* через 10 °C */
#define RTD_N 28 /* до +210 °C — запас за +200 */
/* числитель отношения R/Rref для трёх схем; знаменатель — всегда RTD_FS - a */
#define RTD_NUM_2W(a) (a) /* датчик + оба провода */
#define RTD_NUM_3W(a, b) (2 * (b) - (a)) /* b — верх датчика по третьему проводу */
#define RTD_NUM_4W(c, d) ((c) - (d)) /* c, d — измерительные провода */
typedef enum { RTD_OK, RTD_OPEN, RTD_SHORT, RTD_SENSE, RTD_RANGE } rtd_status_t;
int32_t rtd_w_ppm(int32_t num, int32_t den);
rtd_status_t rtd_temp(int32_t w, int32_t *t_centi);
rtd_status_t rtd_read(int32_t a, int32_t num, int32_t *t_centi);
#endif
rtd.c — пересчёт кодов АЦП в сотые доли градуса и диагностика, без плавающей точки.
/* 176б: пересчёт кодов АЦП в температуру PT100/PT1000 и диагностика. Без плавающей точки, без регистров. */
#include "rtd.h"
_Static_assert((uint64_t)RTD_VDDA_MV * 1000000u / (RTD_RREF_MOHM + RTD_R0_OHM * 1000u) <= RTD_I_MAX_UA,
"ток через датчик больше RTD_I_MAX_UA: самонагрев");
_Static_assert((uint64_t)RTD_FS * RTD_RREF_MOHM / (RTD_RREF_MOHM + RTD_R0_OHM * (RTD_W_OPEN / 1000u)) > RTD_DEN_MIN,
"на пороге обрыва ток должен ещё отличаться от нуля");
_Static_assert(RTD_W_SHORT < 763278 && RTD_W_OPEN > 1795275, "пороги аварии - за краями таблицы");
/* R/R0 в миллионных долях: W(T) по IEC 60751, A = 3,9083e-3, B = -5,775e-7, C = -4,183e-12 */
static const int32_t rtd_tab[RTD_N] = {
763278, 803063, 842707, 882217, 921599, 960859, 1000000, 1039025, 1077935, 1116729,
1155408, 1193971, 1232419, 1270751, 1308968, 1347069, 1385055, 1422925, 1460680, 1498319,
1535843, 1573251, 1610544, 1647721, 1684783, 1721729, 1758560, 1795275,
};
_Static_assert(sizeof rtd_tab / sizeof rtd_tab[0] == RTD_N, "таблица: -60...+210 °C через 10 °C");
/* R/R0 = (Rref / R0) * num / den, в миллионных, с округлением к ближайшему */
int32_t rtd_w_ppm(int32_t num, int32_t den)
{
int64_t n = (int64_t)num * RTD_RREF_MOHM * 1000;
int64_t d = (int64_t)den * RTD_R0_OHM;
n += (n >= 0 ? d : -d) / 2;
return (int32_t)(n / d);
}
/* R/R0 -> сотые доли градуса: таблица через 10 °C и прямая между точками */
rtd_status_t rtd_temp(int32_t w, int32_t *t_centi)
{
if (w < rtd_tab[0] || w > rtd_tab[RTD_N - 1])
return RTD_RANGE;
unsigned i = 0;
while (i < RTD_N - 2 && w >= rtd_tab[i + 1])
i++;
int32_t span = rtd_tab[i + 1] - rtd_tab[i];
*t_centi = (RTD_T0 + RTD_STEP * (int32_t)i) * 100 + (RTD_STEP * 100 * (w - rtd_tab[i]) + span / 2) / span;
return RTD_OK;
}
/* a — код точки A над токовым проводом; num — RTD_NUM_2W, RTD_NUM_3W или RTD_NUM_4W */
rtd_status_t rtd_read(int32_t a, int32_t num, int32_t *t_centi)
{
int32_t den = RTD_FS - a; /* напряжение на Rref — это ток */
if (den < RTD_DEN_MIN)
return RTD_OPEN; /* тока нет: оборван токовый провод или датчик */
int32_t w2 = rtd_w_ppm(a, den); /* датчик вместе с проводами */
int32_t w = rtd_w_ppm(num, den);
if (w2 < RTD_W_SHORT)
return RTD_SHORT;
if (w2 - w > RTD_W_LEAD || w - w2 > RTD_W_LEAD)
return RTD_SENSE; /* провода «стали» больше 1 % R0: висит измерительный провод */
if (w > RTD_W_OPEN)
return RTD_OPEN;
return rtd_temp(w, t_centi);
}
Дальше: раздел 177 «Источники опорного напряжения» — чем заменить
VDDA и VREFBUF, когда их дрейф съедает бюджет.