96. Температура: NTC-термистор и DS18B20
Температуру чаще всего меряют одним из двух способов: дешёвым термистором через АЦП или цифровым датчиком DS18B20 на одном проводе. Первый — аналоговый: точность зависит от делителя, АЦП и того, как код переведён в градусы. Второй сам отдаёт число, но приносит протокол со своими ловушками. Раздел разбирает оба на F103 и F407 с одной целью — получить градусы целыми числами, без плавающей точки.
Шина 1-Wire и её CRC описаны в разделе 83.2, АЦП — в разделе 14, подключение датчиков вообще — в разделе 95.
Как это называется в документации
Оригинал Перевод thermistor, NTC (negative temperature coefficient) терморезистор с отрицательным температурным коэффициентом B value, beta коэффициент B термистора Steinhart–Hart equation уравнение Стейнхарта — Харта self-heating самонагрев током измерения ratiometric measurement логометрическое измерение scratchpad оперативная память датчика, 9 байт conversion time (tCONV) время измерения parasite power паразитное питание от линии данных strong pull-up сильная подтяжка: линию держит ключ ROM code, Skip ROM 64-битный адрес датчика; обращение без адреса
96.1 Два способа
| NTC-термистор | DS18B20 | |
|---|---|---|
| выход | сопротивление → напряжение делителя | число по шине 1-Wire |
| нужно от STM32 | вход АЦП | один вывод и UART или таймер |
| шаг | зависит от температуры: 0,022 °C на код при 25 °C | 0,0625 °C при 12 битах |
| время измерения | микросекунды | до 750 мс при 12 битах |
| диапазон | задаёт таблица: здесь −40…+125 °C | −55…+125 °C |
| несколько датчиков | по входу АЦП на каждый | много на одном проводе |
| слабое место | перевод кода в градусы и самонагрев | протокол и +85 °C до первого измерения |
96.2 NTC: делитель и формула
Сопротивление NTC-термистора падает с ростом температуры:
R(T) = R25 · e^(B·(1/T − 1/T25)),
где T — в кельвинах, R25 — сопротивление при 25 °C (здесь 10 кОм), B — коэффициент из документации (здесь 3950). В делителе с резистором 10 кОм к питанию и термистором к земле код АЦП равен 4095 · R / (R + 10 кОм).
Два решения в этой схеме не случайны:
- Постоянный резистор равен R25. Тогда при 25 °C на входе ровно половина шкалы — код 2048, и именно там кривая круче всего: наибольшая чувствительность приходится на комнатную температуру.
- Делитель питается от того же напряжения, что и опора АЦП. Код — это отношение сопротивлений, и питание сокращается (логометрическое измерение, раздел 81.2).
Конденсатор 100 нФ у входа АЦП держит напряжение, пока конденсатор выборки забирает заряд: для АЦП делитель — источник с сопротивлением 5 кОм (два по 10 кОм параллельно при 25 °C), и без конденсатора ему пришлось бы давать большое время выборки (раздел 14.1).
96.3 Код → градусы целыми числами
Логарифм на F103 без блока плавающей точки обходится дорого, а точности от него больше, чем даёт сам термистор. Проще — таблица кодов через 5 °C и линейная интерполяция между точками:
static const uint16_t ntc_code[NTC_N] = {
3996, 3955, 3900, 3830, 3740, 3629, 3495, 3337, 3156, 2955, 2738, 2510,
2278, 2048, 1825, 1614, 1419, 1241, 1081, 940, 815, 707, 613, 532,
462, 401, 350, 305, 267, 234, 206, 181, 160, 142,
};
int16_t ntc_dc(uint16_t code)
{
if (code > ntc_code[0] || code < ntc_code[NTC_N - 1])
return INT16_MIN; /* обрыв, замыкание или вне −40…+125 °C */
unsigned i = 0;
while (code < ntc_code[i + 1])
i++;
int32_t span = ntc_code[i] - ntc_code[i + 1];
int32_t part = ntc_code[i] - code;
return (int16_t)((NTC_T0 + NTC_STEP * (int32_t)i) * 10 + (NTC_STEP * 10 * part + span / 2) / span);
}
Результат — в десятых долях градуса. Программа на компьютере прошла все
коды от 142 до 3996 и сравнила таблицу с точной формулой: наибольшая
ошибка — 0,203 °C, около −32 °C, где кривая гнётся сильнее всего. Код за
краями таблицы — это не холод и не жара, а обрыв или замыкание: функция
возвращает INT16_MIN, и показывать такое как температуру нельзя.
Сколько градусов приходится на один код, зависит от температуры:
| Температура | Кодов на градус | Градусов на код |
|---|---|---|
| −20 °C | 19,98 | 0,050 |
| 25 °C | 45,49 | 0,022 |
| 100 °C | 7,08 | 0,141 |
Шаг АЦП — ещё не точность: сам термистор с допуском сопротивления и коэффициента B ошибается сильнее, а формула с одним B — приближение. Если нужна точность выше, берут таблицу производителя вместо формулы или уравнение Стейнхарта — Харта с тремя коэффициентами; функция перевода при этом не меняется — меняется только таблица.
Самонагрев. Через делитель при 25 °C течёт 165 мкА, и термистор рассеивает 0,272 мВт — греется сам. На сколько — делят мощность на постоянную рассеяния из документации на термистор: при 1–2 мВт/°C это 0,14–0,27 °C. Если измерять раз в секунду, делитель лучше питать от вывода порта и включать только на время измерения. Но после включения конденсатор 100 нФ у входа заряжается через делитель: при 25 °C постоянная времени 5 кОм × 100 нФ = 0,5 мс, и чтобы недобор до кода 2048 стал меньше одного кода, нужно ln 2048 ≈ 7,6 постоянной — 3,8 мс. При −40 °C сопротивление источника почти 10 кОм, и ждать надо 8,1 мс. Измерили сразу — код занижен. Кроме того, вывод под током 165 мкА отдаёт чуть меньше питания — до 8 мВ, если судить по гарантии «0,4 В при 8 мА»: это 5 кодов в середине шкалы, десятая доля градуса.
96.4 DS18B20: как спросить температуру
Датчик сам измеряет и хранит результат в своей памяти из 9 байт: температура (два байта), пороги TH и TL, байт конфигурации, служебные байты и CRC. Измерение идёт в два захода — «начни» и, когда закончено, «отдай память»:
int ds_start(void)
{
if (!ow_reset())
return 0; /* на шине никого */
ow_write(DS_SKIP_ROM);
ow_write(DS_CONVERT);
return 1;
}
int ds_read(int32_t *mdeg, unsigned bits)
{
uint8_t pad[9];
if (!ow_reset())
return 0;
ow_write(DS_SKIP_ROM);
ow_write(DS_READ_PAD);
for (unsigned i = 0; i < 9; i++)
pad[i] = ow_read();
if (crc8_1wire(pad, 9) != 0)
return 0; /* помеха на проводе */
uint16_t raw = (uint16_t)(pad[0] | (pad[1] << 8));
if (raw == DS_POWER_ON)
return 0; /* измерения не было: сбой питания или прочли раньше срока */
*mdeg = ds_mdeg(raw, bits);
return 1;
}
ow_reset, ow_write и ow_read — функции шины из раздела 83.2 (там
1-Wire сделан на UART в полудуплексе, байт на бит), crc8_1wire — оттуда же.
DS_SKIP_ROM обращается к датчику, не называя адреса, — так можно, только
когда он на линии один. На компьютере драйвер работал с моделью датчика:
ds_start отправляет 0xCC и 0x44, ds_read — 0xCC и 0xBE, читает девять
байт и отдаёт 25,063 °C.
Время измерения зависит от разрешения, а разрешение задают два бита байта конфигурации:
| Разрешение | Шаг | Байт конфигурации | Время измерения, не больше |
|---|---|---|---|
| 9 бит | 0,5 °C | 0x1F | 93,75 мс |
| 10 бит | 0,25 °C | 0x3F | 187,5 мс |
| 11 бит | 0,125 °C | 0x5F | 375 мс |
| 12 бит | 0,0625 °C | 0x7F | 750 мс |
#define DS_CONFIG(bits) ((uint8_t)((((bits) - 9u) << 5) | 0x1Fu))
#define DS_TCONV_US(bits) (750000u >> (12u - (bits)))
96.5 Формат: шестнадцатые доли градуса
Температура — 16-битное число со знаком в дополнительном коде, единица — 1/16 °C:
| Температура | Код |
|---|---|
| +125 °C | 0x07D0 |
| +85 °C | 0x0550 |
| +25,0625 °C | 0x0191 |
| +10,125 °C | 0x00A2 |
| +0,5 °C | 0x0008 |
| 0 °C | 0x0000 |
| −0,5 °C | 0xFFF8 |
| −10,125 °C | 0xFF5E |
| −25,0625 °C | 0xFE6F |
| −55 °C | 0xFC90 |
В тысячные доли градуса — умножить на 625 и разделить на 10; при меньших разрешениях младшие биты не определены, и их сбрасывают:
int32_t ds_mdeg(uint16_t raw, unsigned bits)
{
int32_t v = (int16_t)(raw & (uint16_t)~((1u << (12u - bits)) - 1u));
return (v * 625 + (v < 0 ? -5 : 5)) / 10;
}
Приведение к int16_t обязательно: без него 0xFF5E стало бы +4085,875 °C,
а не −10,125. Код 0x0191 при 9 битах даёт 25,000 °C — три младших бита
отброшены.
96.6 +85 °C — не температура
Сразу после включения память датчика выглядит так:
50 05 4B 46 7F FF 0C 10 1C.
Температура в ней — 0x0550, ровно +85 °C, и CRC у этих девяти байт верная: помехи нет, датчик честно отдаёт то, что лежит в памяти. Такое читают, если прочли раньше, чем закончилось измерение, или если питание датчика просело и он перезапустился. Проверка CRC эту ошибку не ловит, поэтому драйвер отдельно отвергает ровно +85,000 °C.
Если прибор и правда работает около +85 °C, такое правило не годится: там читают строго после полного времени измерения, а ровно +85,000 перемеряют.
96.7 Несколько датчиков и паразитное питание
- Несколько датчиков на одном проводе. У каждого свой 64-битный адрес
(раздел 83.2). Вместо обращения без адреса шлют команду обращения
по адресу и восемь байт адреса. «Начни» можно отправить всем сразу через
DS_SKIP_ROM— они измерят одновременно, а читают потом по одному. - Паразитное питание. Датчик можно подключить двумя проводами: вывод питания — на землю, энергия — с линии данных через подтяжку. Во время измерения ему нужен ток больше, чем даёт подтяжка 4,7 кОм, и линию на всё время измерения держат в единице ключом — «сильной подтяжкой». UART из раздела 83.2 этого не умеет: вывод TX там — открытый сток. При паразитном питании либо переключают вывод на время измерения в двухтактный выход, либо ставят отдельный полевой транзистор. Проще — три провода и питание датчику напрямую.
96.8 Грабли
Термистор показывает −40 или +125 и не меняется. Код ушёл за край таблицы: обрыв (код у 4095) или замыкание (у нуля). Проверять выход за таблицу и не выдавать его за температуру.
Термистор «теплее» комнаты. Самонагрев током делителя. Питать делитель только на время измерения.
Показания термистора плавают вместе с питанием. Делитель запитан не от опоры АЦП. Питать от того же напряжения (раздел 81.2).
DS18B20 отдаёт +85 °C. Прочли раньше конца измерения, или датчик перезапустился по питанию. Ждать полное время измерения; 85,000 после сброса — не температура.
Мороз превращается в тысячи градусов. Код прочитан как беззнаковый.
Приводить к int16_t.
DS18B20 не отвечает. Нет подтяжки 4,7 кОм, или датчик на паразитном питании без сильной подтяжки на время измерения.
Два датчика на линии дают мусор. К ним обратились без адреса, и ответили оба сразу. Читать по адресу.
96.9 Итоги
- NTC в делителе с резистором, равным R25, даёт половину шкалы при 25 °C и наибольшую чувствительность там же; делитель питают от опоры АЦП.
- Код в градусы — таблица через 5 °C и интерполяция целыми числами: ошибка не больше 0,203 °C на всём диапазоне −40…+125 °C; код за краем таблицы — обрыв или замыкание.
- Шаг термистора неравномерен: 0,022 °C на код при 25 °C и 0,141 °C при 100 °C.
- DS18B20: «начни» (0xCC, 0x44), подождать до 750 мс, «отдай память» (0xCC, 0xBE), проверить CRC девяти байт.
- Температура — 1/16 °C в дополнительном коде; без приведения к
int16_tмороз становится жарой. - Ровно +85 °C с верной CRC — память до первого измерения, а не температура.
Дальше: раздел 97 «Влажность: DHT22, SHT31 и AHT20» — однопроводный протокол с длительностью импульсов и два датчика на I²C с проверкой CRC.