39в. Даташит чужой микросхемы → драйвер: HTS221 от первой страницы до проверки без платы
Разделы 39–39б учили читать документы ST на сам микроконтроллер. Но половина работы с платой — чужие микросхемы: датчики, микросхемы памяти, преобразователи. У них свой даташит, и драйвер к ним чаще всего пишут сами. Здесь весь путь на одном примере — датчике влажности и температуры HTS221: что читать в даташите и в каком порядке, что из него выписать, как устроить драйвер, чтобы он работал и на F103 без плавающей точки, и на F407 с ней, и как проверить его без платы — против модели датчика на ПК.
Опоры: даташит HTS221 DocID026333 Rev 5 (март 2023) с сайта st.com.
Для сверки — драйвер ST
hts221_reg.h (BSD-3) и драйвер Zephyr (Apache-2.0) из дерева Zephyr на
диске: их текст не переписан, с ними машинно сверены адреса, биты и
формулы. Драйвер и модель — свои, собраны armclang и gcc.
Как это называется в документации
Оригинал Перевод datasheet даташит, техническое описание pin description назначение выводов absolute maximum ratings предельно допустимые режимы register address map карта регистров slave address, SAD адрес на шине sub-address, SUB адрес регистра внутри микросхемы auto-increment автоувеличение адреса регистра block data update, BDU обновление выхода блоком (оба байта из одного измерения) one-shot однократное измерение power-down режим сна factory calibration заводская калибровка not recommended for new design не рекомендуется для новых разработок
39в.1 Даташит по порядку: что выписать
Первая страница — не реклама, а решение «брать или нет». У HTS221 там сразу сказано: изделие ещё выпускается, но не рекомендуется для новых разработок, и названа замена. Для учебного драйвера это не мешает, для нового изделия — повод остановиться. Дальше даташит читают в таком порядке и выписывают вот что:
| Раздел даташита | Что выписать для драйвера |
|---|---|
| Features, табл. 3–4 | питание 1,7…3,6 В; 2 мкА при 1 Гц, 0,5 мкА во сне; чувствительность 64 разряда на °C и 256 на %rH |
| табл. 2, 8; раздел 4 | выводы; CS к питанию или в воздухе — I²C; конденсатор 100 нФ у питания; подтяжки на SCL и SDA |
| табл. 7 | предел по питанию 4,8 В |
| табл. 6, раздел 5.1 | I²C до 400 кГц; адрес; как читать несколько байт подряд |
| табл. 15 | карта регистров и исходные значения; что нельзя трогать |
| раздел 7 | биты управления, флаги готовности |
| раздел 8, табл. 19 | где лежит калибровка и как из неё получить °C и %rH |
Чего в даташите нет, тоже выписывают: время однократного измерения и время выхода из сна здесь не названы. Значит, драйвер ждёт не паузу, а флаги готовности — с таймаутом.
39в.2 Выводы, шина и адрес
Шесть выводов: питание, земля, SCL, SDA, DRDY (готовность данных) и CS. Интерфейс выбирает CS: подтянут к питанию или висит в воздухе — работает I²C (внутри есть подтяжка), прижат к земле — SPI. По I²C датчик работает в обычном и быстром режиме, до 400 кГц.
Адрес даташит даёт в 8-битном виде: BEh для записи и BFh для чтения (табл. 10). Семибитный адрес — 5Fh; какой из видов ждёт функция шины, надо смотреть в её описании (раздел 17.2).
Чтение регистра — по табл. 13: старт, адрес с битом записи, номер регистра, повторный старт, адрес с битом чтения, данные, стоп. Номер регистра занимает 7 бит, а старший бит включает автоинкремент: если он 1, датчик сам переходит к следующему регистру, и несколько байт читаются одной транзакцией (табл. 14). Поэтому выход читают с адреса A8h (28h плюс бит 7), а калибровку — с B0h.
39в.3 Карта регистров и управление
| Регистр | Адрес | После включения | Что в нём |
|---|---|---|---|
| WHO_AM_I | 0Fh | BCh | код микросхемы |
| AV_CONF | 10h | 1Bh | сколько отсчётов усреднять |
| CTRL_REG1 | 20h | 00h | PD (бит 7), BDU (бит 2), ODR (биты 1:0) |
| CTRL_REG2 | 21h | 00h | BOOT (7), Heater (1), ONE_SHOT (0) |
| CTRL_REG3 | 22h | 00h | вывод DRDY |
| STATUS_REG | 27h | 00h | H_DA (бит 1), T_DA (бит 0) |
| HUMIDITY_OUT_L/H, TEMP_OUT_L/H | 28h…2Bh | — | отсчёты, 16 бит со знаком |
| CALIB_0…F | 30h…3Fh | — | калибровка, не писать |
После включения датчик спит: PD = 0. ODR = 00 — однократный режим: запись 1 в ONE_SHOT запускает одно измерение, по его окончании в STATUS поднимаются H_DA и T_DA, а ONE_SHOT сам сбрасывается. H_DA гаснет при чтении HUMIDITY_OUT_H, T_DA — при чтении TEMP_OUT_H. BDU = 1 не даёт датчику обновить выход, пока не прочитаны оба байта.
Строгие слова даташита стоит выписать дословно по смыслу: запись в регистры с пометкой Reserved может необратимо повредить микросхему, а калибровку 30h…3Fh менять нельзя. Нагреватель (Heater) берёт 33 мА при 3,3 В — в тысячи раз больше рабочего тока. Пока он включён, выходы читать нельзя: верные данные — только после выключения нагревателя.
AV_CONF по умолчанию 1Bh: 16 отсчётов температуры и 32 влажности на одно значение; при 1 Гц это 2,10 мкА и шум 0,03 °C и 0,15 %rH.
39в.4 Калибровка: 16 байт и две прямые
Каждый датчик на заводе откалиброван по двум точкам, и точки лежат в нём самом (раздел 8, табл. 19): для температуры T0 и T1 в градусах и отсчёты АЦП T0_OUT и T1_OUT в этих точках, для влажности — H0, H1 и H0_T0_OUT, H1_T0_OUT. Температура и влажность — линейная интерполяция между точками.
Три тонкости, из-за которых драйверы врут:
- T0 и T1 хранятся умноженными на 8 и занимают 10 бит: 8 младших — в 32h и 33h, два старших — в битах 1:0 и 3:2 регистра 35h. Старшая тетрада 35h — резерв, её отбрасывают маской.
- H0 и H1 хранятся умноженными на 2.
- Отсчёты H0_T0_OUT, H1_T0_OUT, T0_OUT, T1_OUT и сами выходы — 16 бит со знаком.
Даташит даёт по примеру на каждую прямую: T0_OUT = 300 при 80/8 = 10 °C, T1_OUT = 500 при 160/8 = 20 °C, отсчёт 400 — 15,0 °C; H0_T0_OUT = 4000h при 40/2 = 20 %rH, H1_T0_OUT = 6000h при 80/2 = 40 %rH, отсчёт 5000h — 30,0 %rH. Оба варианта драйвера дают на этих числах ровно 15,0000 и 30,0000.
39в.5 Драйвер: шина через указатели, ошибки, два варианта счёта
Драйвер не знает, на каком он кристалле: шину ему дают три указателя на функции — чтение, запись и пауза, — как в драйверах ST. На F4 это три строки вокруг HAL (адрес в HAL — 8-битный, поэтому сдвиг):
static int bus_read(void *h, uint8_t addr7, uint8_t sub, uint8_t *d, uint16_t n)
{
return HAL_I2C_Mem_Read(h, (uint16_t)(addr7 << 1), sub, I2C_MEMADD_SIZE_8BIT, d, n, 10) != HAL_OK;
}
Коды ошибок: HTS_EBUS (−1, нет подтверждения на шине), HTS_EID (−2,
чужой WHO_AM_I), HTS_ETIMEOUT (−3, измерение не кончилось), HTS_ECAL
(−4, калибровка с одинаковыми точками — делить на ноль нельзя).
Инициализация проверяет WHO_AM_I, читает 16 байт калибровки одной
транзакцией и пишет в CTRL_REG1 значение 84h: PD = 1, BDU = 1,
однократный режим. Измерение:
for (uint32_t ms = 0;; ms++) {
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_STATUS_REG, &st, 1))
return HTS_EBUS;
if ((st & ready) == ready)
break;
if (ms >= timeout_ms)
return HTS_ETIMEOUT;
d->bus.delay_ms(d->bus.handle, 1);
}
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_HUMIDITY_OUT_L | HTS221_AUTOINC, b, 4))
return HTS_EBUS;
Счёт — в двух вариантах. Для F103 без блока плавающей точки
(-DHTS_FIXED) — целые: результат в сотых долях °C и %rH, промежуточное
произведение в 64 битах:
int32_t hts_temp_centi(const hts_calib *c, int16_t t_out)
{
int64_t den = (int64_t)c->t1_out - c->t0_out, num;
num = ((int64_t)c->t0_x8 * den + ((int64_t)c->t1_x8 - c->t0_x8) * ((int64_t)t_out - c->t0_out)) * 100;
if (den < 0) { num = -num; den = -den; }
return div_round(num, 8 * den);
}
Для F407 — то же в float. Влажность в обоих вариантах обрезается до
0…100 %: даташит прямо требует обрезать выход за диапазон программно.
armclang собирает драйвер без предупреждений: для F103 (Cortex-M3,
целый вариант) — 626 байт кода, и из библиотеки нужна только 64-битная
беззнаковая __aeabi_uldivmod, ни одной функции плавающей точки; для
F407 (Cortex-M4 с FPU, float) — 578 байт. Модуль шины на HAL F4
собирается там же.
39в.6 Проверка без платы
Модель HTS221 на ПК написана по тому же даташиту: карта регистров с исходными значениями, автоинкремент по биту 7, ONE_SHOT, флаги STATUS, сброс флагов чтением старших байтов, BDU, отказ отвечать на чужой адрес. Калибровка модели задана в физических величинах (T0 = 10,125 °C, T1 = 34,875 °C, H0 = 33,5 %rH, H1 = 73 %rH, отсчёты −1300, 284, −3500, 6612 — 64 разряда на градус и 256 на процент, как в табл. 3) и закодирована в байты по табл. 19 независимо от разбора в драйвере; в резервные байты и старшую тетраду 35h модель кладёт единицы, чтобы проверить маски.
Драйвер работает с моделью через подставную шину. Результаты:
- 1601 измерение от −40 до 120 °C с шагом 0,1 °C и влажностью с шагом
0,13 % — ни одной ошибки; наибольшее отклонение от заданного: 0,0063 °C
и 0,0019 %rH в варианте с
float, 0,01 °C и 0 %rH в целом (сотые доли). Это ошибка округления отсчёта: полразряда — 0,0078 °C и 0,0020 %rH. - Каждое измерение — три чтения STATUS и две паузы по 1 мс (так настроена модель); запись в калибровку и в резервные регистры — ни одной.
- Чужой WHO_AM_I (BDh) —
HTS_EID; нет ответа —HTS_EBUS; измерение не кончается —HTS_ETIMEOUTпосле 51 чтения STATUS и 50 мс; T1_OUT = T0_OUT —HTS_ECAL; датчик спит (PD = 0) — ONE_SHOT не работает, тожеHTS_ETIMEOUT.
Сверка с чужими драйверами — машинная. Из hts221_reg.h ST проверка
вынимает все адреса регистров, код BCh, адрес BFh и раскладку битовых
полей CTRL_REG1, CTRL_REG2, STATUS_REG и 35h — всё совпадает со своими
определениями. Из драйвера Zephyr вынимаются выражения разбора
калибровки и пересчёта: на тех же 16 байтах они дают те же T0, T1, H0,
H1 и отсчёты. Но пересчёт там целый, в восьмых долях градуса и
половинах процента: на этой калибровке он отстаёт от точной прямой до
0,11 °C и 0,5 %rH — для термометра заметно, для драйвера,
которому нужна лишь «сухо или сыро», неважно.
39в.7 Грабли
Температура скачет на 4 °C. Без BDU младший и старший байты могут прийти из разных измерений. В модели выход меняется с 00FFh на 0100h между чтениями байтов: без BDU прочитано 01FFh = 511 разрядов, с BDU — честные 00FFh = 255.
Все четыре байта одинаковые. Забыт бит 7 в адресе регистра: без автоинкремента четыре чтения с 28h дают четыре раза один и тот же байт (в модели — D4 D4 D4 D4). Калибровка, прочитанная так, превращается в мусор: T0 = 835/8 °C.
−40 °C показывает как +984 °C. Отсчёт прочитан как беззнаковый: −4508
превращается в 61028. Все отсчёты и точки калибровки — int16_t.
Температура в 8 раз больше, влажность в 2 раза. Забыто деление T0/T1 на 8 и H0/H1 на 2. И забыты два старших бита T0/T1 в 35h — тогда 34,875 °C превращаются в 2,875 °C.
Датчик не измеряет. PD = 0 после включения: пока не записан PD = 1, ONE_SHOT ничего не запускает. Паузу вместо ожидания флагов не ставят — время измерения в даташите не указано.
Адрес 5Fh «не отвечает». Функция шины ждёт 8-битный адрес BEh, а ей дали 7-битный, или наоборот.
Записали в Reserved. Даташит предупреждает о необратимом повреждении; драйвер пишет только в CTRL_REG1 и CTRL_REG2.
Опечатка в даташите. В табл. 19 младший бит регистра 30h подписан H0.1 — так же, как соседний; это H0.0, байт используется целиком.
39в.8 Итоги
- Порядок чтения: первая страница (статус изделия), выводы и питание, шина и адрес, карта регистров, биты управления, калибровка; отдельно — чего в даташите нет (время измерения).
- HTS221: адрес BEh/BFh (5Fh), WHO_AM_I = BCh, автоинкремент — бит 7 адреса регистра, CTRL_REG1 = 84h (PD, BDU, однократно), ONE_SHOT и флаги H_DA/T_DA.
- Калибровка: T0/T1 — 10 бит, делить на 8; H0/H1 — делить на 2; отсчёты — 16 бит со знаком; примеры даташита — 15,0 °C и 30,0 %rH.
- Драйвер: шина через указатели, коды ошибок, таймаут; F103 — целые,
626 байт; F407 —
float, 578 байт. - Без платы: 1601 измерение против модели, ошибка не больше полразряда (0,0063 °C и 0,0019 %rH); пять отрицательных случаев; адреса и биты сверены с драйвером ST, формулы — с Zephyr.
Полные исходники
hts221_drv.c — драйвер HTS221 по даташиту.
/* hts221_drv.c — раздел 39в: драйвер HTS221 по даташиту DS10866 Rev 5. */
#include "hts221_drv.h"
_Static_assert(HTS221_ADDR7 == (0xBEu >> 1), "BEh/BFh — это 7-битный адрес 5Fh со сдвигом");
_Static_assert((HTS221_CTRL1_PD | HTS221_CTRL1_BDU) == 0x84u, "PD (бит 7) и BDU (бит 2), ODR = 00");
_Static_assert((HTS221_HUMIDITY_OUT_L | HTS221_AUTOINC) == 0xA8u && (HTS221_CALIB_0 | HTS221_AUTOINC) == 0xB0u, "автоинкремент — бит 7");
_Static_assert(0x3Fu - HTS221_CALIB_0 + 1u == 16u, "калибровка — 16 регистров 30h…3Fh");
void hts_decode_calib(hts_calib *c, const uint8_t b[16]) /* b[0] — регистр 30h */
{
c->h0_x2 = b[0]; /* 30h */
c->h1_x2 = b[1]; /* 31h */
c->t0_x8 = (uint16_t)(b[2] | (b[5] & 0x03u) << 8); /* 32h и биты 1:0 регистра 35h */
c->t1_x8 = (uint16_t)(b[3] | (b[5] & 0x0Cu) << 6); /* 33h и биты 3:2 регистра 35h */
c->h0_out = (int16_t)(uint16_t)(b[6] | b[7] << 8); /* 36h, 37h — со знаком */
c->h1_out = (int16_t)(uint16_t)(b[10] | b[11] << 8); /* 3Ah, 3Bh */
c->t0_out = (int16_t)(uint16_t)(b[12] | b[13] << 8); /* 3Ch, 3Dh */
c->t1_out = (int16_t)(uint16_t)(b[14] | b[15] << 8); /* 3Eh, 3Fh */
}
int hts_init(hts221 *d)
{
uint8_t id = 0, cal[16], v;
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_WHO_AM_I, &id, 1))
return HTS_EBUS;
if (id != HTS221_ID)
return HTS_EID;
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_CALIB_0 | HTS221_AUTOINC, cal, 16))
return HTS_EBUS;
hts_decode_calib(&d->cal, cal);
if (d->cal.t1_out == d->cal.t0_out || d->cal.h1_out == d->cal.h0_out)
return HTS_ECAL;
v = HTS221_CTRL1_PD | HTS221_CTRL1_BDU; /* ODR = 00: однократный режим */
if (d->bus.write(d->bus.handle, HTS221_ADDR7, HTS221_CTRL_REG1, &v, 1))
return HTS_EBUS;
return HTS_OK;
}
int hts_measure_raw(hts221 *d, int16_t *h_out, int16_t *t_out, uint32_t timeout_ms)
{
const uint8_t ready = HTS221_STATUS_H_DA | HTS221_STATUS_T_DA;
uint8_t v = HTS221_CTRL2_ONE_SHOT, st = 0, b[4];
if (d->bus.write(d->bus.handle, HTS221_ADDR7, HTS221_CTRL_REG2, &v, 1))
return HTS_EBUS;
for (uint32_t ms = 0;; ms++) {
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_STATUS_REG, &st, 1))
return HTS_EBUS;
if ((st & ready) == ready)
break;
if (ms >= timeout_ms)
return HTS_ETIMEOUT;
d->bus.delay_ms(d->bus.handle, 1);
}
if (d->bus.read(d->bus.handle, HTS221_ADDR7, HTS221_HUMIDITY_OUT_L | HTS221_AUTOINC, b, 4))
return HTS_EBUS;
*h_out = (int16_t)(uint16_t)(b[0] | b[1] << 8);
*t_out = (int16_t)(uint16_t)(b[2] | b[3] << 8);
return HTS_OK;
}
#ifdef HTS_FIXED
/* n / d с округлением к ближайшему, d > 0 */
static int32_t div_round(int64_t n, int64_t d)
{
return (int32_t)(n >= 0 ? (n + d / 2) / d : -((-n + d / 2) / d));
}
/* T = (T0·8 + (T1·8 − T0·8)·(T_OUT − T0_OUT)/(T1_OUT − T0_OUT)) / 8, в сотых °C */
int32_t hts_temp_centi(const hts_calib *c, int16_t t_out)
{
int64_t den = (int64_t)c->t1_out - c->t0_out, num;
num = ((int64_t)c->t0_x8 * den + ((int64_t)c->t1_x8 - c->t0_x8) * ((int64_t)t_out - c->t0_out)) * 100;
if (den < 0) { num = -num; den = -den; }
return div_round(num, 8 * den);
}
/* RH = (H0·2 + (H1·2 − H0·2)·(H_OUT − H0_OUT)/(H1_OUT − H0_OUT)) / 2, в сотых %, обрезка 0…100 % */
int32_t hts_hum_centi(const hts_calib *c, int16_t h_out)
{
int64_t den = (int64_t)c->h1_out - c->h0_out, num;
int32_t rh;
num = ((int64_t)c->h0_x2 * den + ((int64_t)c->h1_x2 - c->h0_x2) * ((int64_t)h_out - c->h0_out)) * 100;
if (den < 0) { num = -num; den = -den; }
rh = div_round(num, 2 * den);
return rh < 0 ? 0 : rh > 10000 ? 10000 : rh;
}
#else
float hts_temp_c(const hts_calib *c, int16_t t_out)
{
float k = (float)(t_out - c->t0_out) / (float)(c->t1_out - c->t0_out);
return ((float)c->t0_x8 + ((float)c->t1_x8 - (float)c->t0_x8) * k) / 8.0f;
}
float hts_hum_rh(const hts_calib *c, int16_t h_out)
{
float k = (float)(h_out - c->h0_out) / (float)(c->h1_out - c->h0_out);
float rh = ((float)c->h0_x2 + ((float)c->h1_x2 - (float)c->h0_x2) * k) / 2.0f;
return rh < 0.0f ? 0.0f : rh > 100.0f ? 100.0f : rh;
}
#endif
Дальше: раздел 40 «Англо-русские соответствия по темам».