Embedded HandbookПриложения
Все разделы › VII. Документация и её язык
STM32F103, STM32F407 · уровень средний · 14 мин чтения
Открыть в интерактивном справочнике

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. Температура и влажность — линейная интерполяция между точками.

Карта калибровочных регистров HTS221 с 30h по 3Fh: H0_rH_x2, H1_rH_x2, T0_degC_x8, T1_degC_x8, резерв, старшие биты T1 и T0, H0_T0_OUT, резерв, H1_T0_OUT, T0_OUT, T1_OUT с байтами модели, чтение одной транзакцией с B0h и сборка T0 = 10,125 °C, T1 = 34,875 °C, H0 = 33,5 %rH, H1 = 73 %rH и формулы интерполяции

Три тонкости, из-за которых драйверы врут:

Даташит даёт по примеру на каждую прямую: 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 модель кладёт единицы, чтобы проверить маски.

Драйвер работает с моделью через подставную шину. Результаты:

Сверка с чужими драйверами — машинная. Из 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_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 «Англо-русские соответствия по темам».

Открыть в интерактивном справочнике