198х. Мини-проект: датчики платы B-U585I-IOT02A — климат, движение и свет
На плате B-U585I-IOT02A рядом с STM32U585 стоят пять датчиков: влажности и температуры HTS221, давления LPS22HH, ускорения и угловой скорости ISM330DHCX, магнитного поля IIS2MDC и освещённости VEML6030. Все пятеро сидят на одной шине I2C2 и все отдают сырые числа: отсчёт АЦП, счёт импульсов, 24 бита давления. Превратить их в градусы, гектопаскали, мг и люксы — дело программы, и у каждого датчика своё правило: у HTS221 — две точки калибровки внутри самого кристалла, у LPS22HH — деление на 4096, у ISM330DHCX — чувствительность, зависящая от диапазона, у VEML6030 — коэффициент от усиления и времени накопления.
Здесь всё это собрано в один прибор: раз в секунду опросить пятерых и
выдать строку в USART1. Основа — пакет платы B-U585I-IOT02A_BSP 1.1.0:
общий слой b_u585i_iot02a_env_sensors.c, _motion_sensors.c,
_light_sensor.c, шина _bus.c и драйверы компонентов hts221,
lps22hh, ism330dhcx, iis2mdc, veml6030 (лицензия BSD-3-Clause).
Наш код повторяет те же регистры без HAL и читает каждый датчик одной
транзакцией.
Проверено без платы — U585 в QEMU нет. Модель регистров пяти датчиков
(model198h.c) крутится на ПК, и на ней работают и наш код, и драйверы
ST без единой правки: те же сырые байты, два ответа, плюс независимый
пересчёт на Python точными дробями. Регистры настройки после нашей
инициализации и после BSP совпали все 15 из 15. Прошивка sens_u585.c
собрана armclang под Cortex-M33 без предупреждений; на плате она не
запускалась.
Опоры: раздел 17 (шина I²C; обмен с новым блоком — 17.10, здесь не
повторяется), 96 (термометры: там датчик отдавал готовые доли градуса),
98 (давление и барометрическая формула высоты), 81 (шум датчиков и
усреднение), 198г (акселерометр и его поток данных), 2д (U585 и плата).
RM0456 (ред. 4) — поле SADD регистра I2C_CR2.
Даташит HTS221 (DocID026333, ред. 5) — адрес, автоинкремент, калибровка;
остальные даташиты не использовались, их регистры и коэффициенты — по
драйверам пакета. Второй независимый источник — описание платы и драйвер
veml7700 в Zephyr (github.com/zephyrproject-rtos/zephyr, v4.4.2,
коммит dccb095, лицензия Apache-2.0).
Как это называется в документации
Оригинал Перевод two-point calibration калибровка по двум точкам: прямая через два отсчёта с известными значениями component driver, RegisterBusIOдрайвер компонента и привязка его к функциям шины instance экземпляр: номер датчика в своей группе BSP data ready (DRDY) готовность данных: флаг в регистре или вывод low-noise mode ( LOW_NOISE_EN)режим малого шума integration time ( ALS_IT)время накопления света mdps/LSB, mgauss/LSB миллиградусов в секунду, миллигаусс на единицу отсчёта
198х.1 Что стоит на плате и на какой шине
Адреса и паспорта — из заголовков драйверов пакета (*_reg.h, veml6030.h),
7-битные адреса совпадают с описанием платы в Zephyr
(b_u585i_iot02a-common.dtsi):
| Датчик | Что меряет | Адрес 7 бит | В BSP (8 бит) | Паспорт | Автоинкремент |
|---|---|---|---|---|---|
| HTS221 | влажность, температура | 0x5F | 0xBF |
0x0F = 0xBC |
бит 7 подадреса |
| LPS22HH | давление, температура | 0x5D | 0xBB |
0x0F = 0xB3 |
IF_ADD_INC в CTRL_REG2 |
| ISM330DHCX | ускорение, угловая скорость | 0x6B | 0xD7 |
0x0F = 0x6B |
IF_INC в CTRL3_C |
| IIS2MDC | магнитное поле | 0x1E | 0x3D |
0x4F = 0x40 |
BSP ставит бит 7 |
| VEML6030 | освещённость | 0x10 | 0x21 / 0x20 |
нет | 16-битные регистры |
Шина I2C2 — выводы PH4 (SCL) и PH5 (SDA), альтернативная функция 4,
открытый сток с подтяжкой, 400 кГц (BUS_I2C2_FREQUENCY в
b_u585i_iot02a_bus.h). На ней же BSP держит дальномер VL53L5CX (0x29),
EEPROM M24LR64 (0x56) и контроллер питания USB (TCPP03, на плате
расширения); камера — на I2C1. Выходы готовности данных у ISM330DHCX,
LPS22HH и IIS2MDC Zephyr ведёт на PE11, PG2 и PD10; BSP их не
использует, и мы тоже — опрос раз в секунду обходится без прерываний.
У VEML6030 паспортного регистра нет вовсе: VEML6030_ReadID проверяет,
что адрес ответил, и возвращает константу 0x6030.
198х.2 Слой BSP и наш слой
BSP делит датчики на три группы, в каждой — экземпляры: ENV 0 —
HTS221 (температура, влажность), ENV 1 — LPS22HH (температура,
давление), MOTION 0 — ISM330DHCX (гироскоп, акселерометр), MOTION 1 —
IIS2MDC, LIGHT 0 — VEML6030. Вызов BSP_ENV_SENSOR_Init(0, ENV_TEMPERATURE
| ENV_HUMIDITY) идёт так:
HTS221_Probeзаполняет структуру шины — адресHTS221_I2C_ADDRESS, функцииBSP_I2C2_ReadReg/WriteReg,BSP_GetTick— и отдаёт её драйверу компонента (HTS221_RegisterBusIO); тот сразу зовётBSP_I2C2_Init.- Читает паспорт (
HTS221_ReadID) и сравнивает сHTS221_ID. - Запоминает таблицу функций компонента и вызывает его
Init. BSP_ENV_SENSOR_Enableвключает функцию,BSP_ENV_SENSOR_GetValueвозвращаетfloatв единицах: °C, %, гПа.
Удобно, но дорого: каждый GetValue — отдельные транзакции, а HTS221 на
каждое чтение заново перечитывает калибровку. На модели одно чтение всех
пяти через драйверы ST — 21 транзакция и 47 байт, из них у HTS221 —
12 транзакций и 18 байт. Наш слой — функции i2c_rd и i2c_wr поверх
регистров I2C2 (обмен тот же, что в 17.10, только NBYTES = n) и по одному
чтению на датчик: 5 транзакций и 29 байт, 396 тактов SCL против 990.
static int wait_flag(uint32_t flag) /* 0 — флаг есть; 1 — NACK или нет ответа */
{
for (uint32_t n = 0; n < TMO; n++) {
uint32_t isr = I2C2->ISR;
if (isr & I2C_ISR_NACKF)
return 1;
if (isr & flag)
return 0;
}
return 1;
}
int i2c_rd(uint8_t a7, uint8_t reg, uint8_t *buf, uint16_t n) /* подадрес, повторный старт, n байт */
{
if (n == 0u || n > 255u)
return 1;
I2C2->CR2 = (uint32_t)a7 << 1 | 1u << I2C_CR2_NBYTES_Pos | I2C_CR2_START;
if (wait_flag(I2C_ISR_TXIS))
return fail();
I2C2->TXDR = reg;
if (wait_flag(I2C_ISR_TC)) /* подадрес ушёл, STOP не выдан */
return fail();
I2C2->CR2 = (uint32_t)a7 << 1 | I2C_CR2_RD_WRN | (uint32_t)n << I2C_CR2_NBYTES_Pos
| I2C_CR2_AUTOEND | I2C_CR2_START;
for (uint16_t i = 0; i < n; i++) {
if (wait_flag(I2C_ISR_RXNE))
return fail();
buf[i] = (uint8_t)I2C2->RXDR;
}
return finish();
}
TIMINGR = 0x00500A11 — не подобран руками. Три функции расчёта из
b_u585i_iot02a_bus.c (I2C_GetTiming и две вспомогательные) вырезаны без
правок и прогнаны на ПК: для такта блока 16 МГц и шины 400 кГц — PRESC
0, SCLDEL 5, SDADEL 0, SCLH 10, SCLL 17, по формулам BSP период
2529 нс, 395,4 кГц. От 4 МГц после сброса алгоритм тоже находит
решение (0x00100003), но с SCLH = 0, поэтому I2C2 и USART1 здесь
тактуются от HSI16 (CCIPR1: I2C2SEL и USART1SEL = 10), а ядро
остаётся на MSIS 4 МГц. BSP же считает TIMINGR от PCLK1.
Главная проверка слоя — регистры. После sens_init на модели и после
цепочки BSP Init + Enable для всех функций сравнены 15 регистров
настройки — CTRL_REG1 HTS221 (0x85), IF_CTRL, CTRL_REG1,
CTRL_REG2 LPS22HH (0x82, 0x32, 0x10), CTRL1_XL, CTRL2_G,
CTRL3_C, CTRL9_XL ISM330DHCX (0x40, 0x4C, 0x44, 0x02),
CFG_REG_A и CFG_REG_C IIS2MDC (0x0C, 0x10), ALS_CONF VEML6030 и
другие. Совпали 15 из 15. Вся настройка — 24 транзакции, 900 тактов SCL,
около 2,3 мс шины.
198х.3 HTS221: две точки калибровки внутри датчика
HTS221 отдаёт влажность и температуру как 16-битные отсчёты АЦП со знаком
(0x28…0x2B). Чтобы получить единицы, нужны регистры 0x30…0x3F: там
на заводе записаны две точки каждой прямой — отсчёт и значение.
Температуры точек хранятся ×8 и занимают 10 бит: младшие 8 — в 0x32 и
0x33, старшие по 2 — в 0x35; влажности — ×2, в 0x30 и 0x31
(даташит, раздел 8, таблица 19). Дальше — прямая через две точки. В
драйвере ST (hts221.c, BSD-3-Clause) она записана одной строкой:
(((y1 - y0) * Coeff) + ((x1 * y0) - (x0 * y1))) / (x1 - x0).
Наш код читает калибровку один раз, всю, одной транзакцией — для этого
бит 7 подадреса (0x30 | 0x80): без него HTS221 адрес не наращивает
(даташит, раздел 5.1.1).
static int hts_init(void)
{
uint8_t id, cal[16];
if (rd1(HTS_A7, 0x0F, &id) || id != 0xBC) /* WHO_AM_I */
return 1;
if (i2c_rd(HTS_A7, 0x30 | AI, cal, 16)) /* калибровка 0x30…0x3F одним чтением */
return 1;
hts_cal_parse(cal, &hts_k);
return wr1(HTS_A7, 0x20, 0x85); /* CTRL_REG1: PD, BDU, 1 Гц */
}
void hts_cal_parse(const uint8_t c[16], hts_cal_t *k) /* c[0] — регистр 0x30, c[15] — 0x3F */
{
k->h0_x2 = c[0x00];
k->h1_x2 = c[0x01];
k->t0_x8 = c[0x02] | (int32_t)(c[0x05] & 0x03u) << 8; /* старшие биты T0 — 0x35[1:0] */
k->t1_x8 = c[0x03] | (int32_t)(c[0x05] & 0x0Cu) << 6; /* старшие биты T1 — 0x35[3:2] */
k->h0_out = le16(&c[0x06]); /* 0x36, 0x37 */
k->h1_out = le16(&c[0x0A]); /* 0x3A, 0x3B */
k->t0_out = le16(&c[0x0C]); /* 0x3C, 0x3D */
k->t1_out = le16(&c[0x0E]); /* 0x3E, 0x3F */
}
int32_t hts_temp_c100(const hts_cal_t *k, const uint8_t out[4]) /* out — 0x28…0x2B */
{
int64_t dx = (int64_t)k->t1_out - k->t0_out;
if (dx == 0)
return INT32_MIN; /* испорченная калибровка */
int64_t x = le16(&out[2]) - (int64_t)k->t0_out; /* TEMP_OUT — 0x2A, 0x2B */
/* T = T0 + (x − x0)·(T1 − T0)/(x1 − x0); T0 и T1 хранятся ×8, ответ — ×100 */
return (int32_t)div_round((int64_t)k->t0_x8 * 100 * dx + x * (k->t1_x8 - k->t0_x8) * 100, 8 * dx);
}
Считаем пример — калибровка модели и один отсчёт:
| Точка | Отсчёт | Регистры | Значение |
|---|---|---|---|
| T0 | T0_OUT = 250 |
0x32 = 0x50, 0x35[1:0] = 0 |
80 / 8 = 10,0 °C |
| T1 | T1_OUT = 2140 |
0x33 = 0x1C, 0x35[3:2] = 1 |
284 / 8 = 35,5 °C |
| H0 | H0_T0_OUT = 1200 |
0x30 = 66 |
66 / 2 = 33,0 % |
| H1 | H1_T0_OUT = 9000 |
0x31 = 154 |
154 / 2 = 77,0 % |
Отсчёт T_OUT = 1250: 10,0 + (1250 − 250) · 25,5 / 1890 = 23,492 °C
(0,013492 °C на единицу), в строке — T=23.49. H_OUT = 3500:
33,0 + 2300 · 44 / 7800 = 45,974 %, в строке — RH=46.0. Драйвер ST на
тех же байтах дал 23,4921 и 45,9744 — то же самое во float. Пример из
даташита (рисунки 9 и 10: отсчёты 300 и 500 при 10 и 20 °C, 400 → 15 °C;
0x4000 и 0x6000 при 20 и 40 %, 0x5000 → 30 %) оба кода повторяют
ровно: 15,00 °C и 30,0 %.
Влажность по прямой может выйти за 0…100 %, и даташит велит обрезать
программно. Драйвер ST обрезает, наш код — тоже: отсчёт 20 000 даёт 100,0,
отсчёт −8000 — 0. Шум по умолчанию задан усреднением AV_CONF = 011 —
16 отсчётов температуры и 32 влажности, 0,03 °C и 0,15 % (даташит,
таблица 16; о том, что даёт усреднение, — раздел 81). Ни BSP, ни мы этот
регистр не трогаем. CTRL_REG1 = 0x85: включён, BDU, 1 Гц — как
после HTS221_Init и Enable.
198х.4 LPS22HH: давление 24 бита и высота
Давление — три байта PRESS_OUT_XL, _L, _H (0x28…0x2A), за ними
температура 0x2B, 0x2C. Пересчёт в драйвере ST (lps22hh_reg.c):
24 бита сдвигаются влево на 8 и делятся на 1 048 576, то есть делятся на
4096 — шаг 0,000244 гПа (0,0244 Па); температура — делится на 100. Всё
это — одно чтение пяти байт; адрес наращивает сам датчик, если в
CTRL_REG2 стоит IF_ADD_INC (бит 4). Мы, как и BSP, ставим его явно.
static int lps_init(void)
{
uint8_t id;
if (rd1(LPS_A7, 0x0F, &id) || id != 0xB3)
return 1;
return set_bits(LPS_A7, 0x0E, 0, 0x82) /* IF_CTRL: I3C_DISABLE и INT_EN_I3C, как BSP */
|| wr1(LPS_A7, 0x11, 0x10) /* CTRL_REG2: IF_ADD_INC */
|| wr1(LPS_A7, 0x10, 0x32); /* CTRL_REG1: 25 Гц, BDU */
}
uint32_t lps_press_pa(const uint8_t b[5]) /* b — 0x28…0x2C: давление 3 байта, температура 2 */
{
uint32_t raw = b[0] | (uint32_t)b[1] << 8 | (uint32_t)b[2] << 16;
return (uint32_t)((raw * 25u + 512u) / 1024u); /* raw/4096 гПа = raw·100/4096 Па */
}
int32_t alt_dm(uint32_t p_pa, uint32_t p0_pa) /* барометрическая формула из раздела 98 */
{
float h = 44330.0f * (1.0f - powf((float)p_pa / (float)p0_pa, 1.0f / 5.255f));
return (int32_t)lroundf(h * 10.0f);
}
Сцена модели: байты 0x00, 0xE0, 0x3D — 0x3DE000 = 4 055 040,
делённое на 4096, — 990,00 гПа, в строке P=990.00; температура
2410 — TP=24.10. Высота по формуле раздела 98 от 1013,25 гПа —
195,4 м (Python — 195,391 м), для 1012,00 гПа — 10,4 м. Это высота
относительно выбранного p0; как погода сдвигает её на сотни метров —
в разделе 98.
Находка в драйвере ST. LPS22HH_Init включает режим малого шума: после
него CTRL_REG2 = 0x12 (LOW_NOISE_EN и IF_ADD_INC). Но
LPS22HH_TEMP_Enable записывает частоту 25 Гц (LPS22HH_25_Hz = 0x03), а
вместе с ней — бит малого шума из того же числа, то есть 0. После Enable
в регистре 0x10: малого шума нет. Наш код повторяет итог BSP (0x10),
чтобы сравнение регистров было честным; нужен малый шум — надо писать
CTRL_REG2 после выбора частоты. И IF_CTRL = 0x82, а не 0x02:
значение LPS22HH_I3C_DISABLE = 0x11 ставит заодно INT_EN_I3C — мы
пишем то же.
198х.5 ISM330DHCX и IIS2MDC: диапазон решает всё
ISM330DHCX отдаёт шесть осей подряд: гироскоп 0x22…0x27, акселерометр
0x28…0x2D — 12 байт одним чтением при IF_INC в CTRL3_C.
Чувствительность — в ism330dhcx.h:
| Акселерометр | мг на единицу | Гироскоп | мград/с на единицу |
|---|---|---|---|
| ±2g | 0,061 | ±125 °/с | 4,375 |
| ±4g | 0,122 | ±250 °/с | 8,75 |
| ±8g | 0,244 | ±500 °/с | 17,5 |
| ±16g | 0,488 | ±1000 °/с | 35 |
| ±2000 °/с | 70 | ||
| ±4000 °/с | 140 |
BSP ставит ±2g и ±2000 °/с, частоту 104 Гц (CTRL1_XL = 0x40,
CTRL2_G = 0x4C). При ±2g 1 g — это 16 393 единицы. Коды поля FS_XL
идут не по порядку: 00 — ±2g, 01 — ±16g, 10 — ±4g, 11 — ±8g
(перечисление ism330dhcx_fs_xl_t). У гироскопа четыре бита: FS_4000,
FS_125 и два бита FS_G.
static int ism_init(void)
{
uint8_t id, v = 1;
if (rd1(ISM_A7, 0x0F, &id) || id != 0x6B)
return 1;
if (wr1(ISM_A7, 0x12, 0x01)) /* CTRL3_C: SW_RESET */
return 1;
for (int n = 0; n < 1000 && (v & 1u); n++) /* бит снимается сам */
if (rd1(ISM_A7, 0x12, &v))
return 1;
return (v & 1u)
|| wr1(ISM_A7, 0x12, 0x44) /* CTRL3_C: BDU, IF_INC */
|| set_bits(ISM_A7, 0x18, 0, 0x02) /* CTRL9_XL: DEVICE_CONF */
|| set_bits(ISM_A7, 0x0A, 0x07, 0) /* FIFO_CTRL4: FIFO выключен */
|| wr1(ISM_A7, 0x10, 0x40 | ISM_FS_XL << 2) /* CTRL1_XL: 104 Гц, ±2g */
|| wr1(ISM_A7, 0x11, 0x40 | ISM_FS_G); /* CTRL2_G: 104 Гц, ±2000 °/с */
}
int32_t ism_acc_mg(int16_t raw, uint8_t fs_xl) /* FS_XL: 00 ±2g, 01 ±16g, 10 ±4g, 11 ±8g */
{
static const int32_t ug_per_lsb[4] = {61, 488, 122, 244};
return (int32_t)div_round((int64_t)raw * ug_per_lsb[fs_xl & 3u], 1000);
}
int32_t ism_gyr_mdps(int16_t raw, uint8_t fs_g) /* 4 бита CTRL2_G[3:0]: FS_G[1:0], FS_125, FS_4000 */
{
int32_t udps; /* микроградусы в секунду на единицу */
if (fs_g & 1u) udps = 140000; /* ±4000 */
else if (fs_g & 2u) udps = 4375; /* ±125 */
else udps = 8750 << (fs_g >> 2); /* ±250, 500, 1000, 2000 */
return (int32_t)div_round((int64_t)raw * udps, 1000);
}
Драйвер ST умножает отсчёт на float и приводит к int32_t — дробь
отбрасывается к нулю. Мы округляем:
| Отсчёт | Точно, мг | Наш код | Драйвер ST |
|---|---|---|---|
| 328 | 20,008 | 20 | 20 |
| −205 | −12,505 | −13 | −12 |
| 16 393 | 999,973 | 1000 | 999 |
Разница не больше единицы, но «999 мг» на столе, лежащем ровно, сбивает с
толку. Гироскоп при ±2000 °/с — ровно 70 мград/с на единицу: отсчёт 16 —
1120 мград/с (G=1.12), −3 — −210.
У IIS2MDC диапазон один — 50 Гс, 1,5 мГс на единицу
(IIS2MDC_MAG_SENSITIVITY_FS_50GAUSS), шесть байт с 0x68. BSP включает
BDU в CFG_REG_C (0x10) и непрерывный режим 100 Гц в CFG_REG_A
(0x0C). Отсчёт −101: точно −151,5, у нас −152, у ST −151.
198х.6 VEML6030: люксы от усиления и времени, строка в UART
VEML6030 — не датчик ST, и устроен иначе: регистры 16-битные, у каждого
код команды (0x00 — ALS_CONF, 0x04 — ALS, 0x05 — WHITE), два
байта на регистр. Драйвер пакета люксы не считает вовсе:
VEML6030_GetValues отдаёт счёт. Коэффициент есть в драйвере Zephyr
veml7700 — его же Zephyr назначает для VEML6030 этой платы: таблица 4 × 6,
от 0,0036 лк на единицу (усиление ×2, 800 мс) до 1,8432 (×1/8, 25 мс).
Таблица целиком сворачивается в одну формулу — 0,0036 · (800 / время) ·
(2 / усиление); прогон сверил все 24 числа.
uint32_t veml_res_ulx(uint8_t gain, uint8_t it) /* микролюксы на единицу счёта */
{
static const uint8_t gmul[4] = {2, 1, 16, 8}; /* ALS_GAIN: 00 ×1, 01 ×2, 10 ×1/8, 11 ×1/4 */
uint32_t tmul;
switch (it) { /* ALS_IT, мс: 25, 50, 100, 200, 400, 800 */
case 0x0C: tmul = 32; break;
case 0x08: tmul = 16; break;
case 0x00: tmul = 8; break;
case 0x01: tmul = 4; break;
case 0x02: tmul = 2; break;
case 0x03: tmul = 1; break;
default: return 0;
}
return 3600u * gmul[gain & 3u] * tmul; /* 0,0036 лк при ×2 и 800 мс */
}
BSP оставляет усиление ×1 и 100 мс (BSP_LIGHT_SENSOR_Init записывает их
только в свою структуру, в датчик не пишет; наш veml_init пишет явно): 0,0576 лк на
единицу, счёт 6000 — 345,6 лк (L=345.6), потолок 65 535 — 3774,8 лк.
Для яркого солнца — усиление ×1/8 и 25 мс: потолок 120 794 лк.
Сводный опрос — пять чтений подряд, по одному на датчик:
void sens_poll(uint8_t mask, sens_t *v)
{
uint8_t b[12];
v->ok = 0;
if ((mask & 1u) && !i2c_rd(HTS_A7, 0x28 | AI, b, 4)) { /* HUMIDITY_OUT, TEMP_OUT */
v->rh_x10 = hts_rh_x10(&hts_k, b);
v->t_c100 = hts_temp_c100(&hts_k, b);
v->ok |= 1u;
}
if ((mask & 2u) && !i2c_rd(LPS_A7, 0x28, b, 5)) { /* PRESS_OUT_XL…TEMP_OUT_H */
v->p_pa = lps_press_pa(b);
v->tp_c100 = lps_temp_c100(b);
v->alt_dm = alt_dm(v->p_pa, P0_PA);
v->ok |= 2u;
}
if ((mask & 4u) && !i2c_rd(ISM_A7, 0x22, b, 12)) { /* OUTX_L_G…OUTZ_H_A */
for (int i = 0; i < 3; i++) {
v->gyr_mdps[i] = ism_gyr_mdps(le16(&b[2 * i]), ISM_FS_G);
v->acc_mg[i] = ism_acc_mg(le16(&b[6 + 2 * i]), ISM_FS_XL);
}
v->ok |= 4u;
}
if ((mask & 8u) && !i2c_rd(MAG_A7, 0x68 | AI, b, 6)) { /* OUTX_L_REG…OUTZ_H_REG */
for (int i = 0; i < 3; i++)
v->mag_mg[i] = mag_mg(le16(&b[2 * i]));
v->ok |= 8u;
}
if ((mask & 16u) && !i2c_rd(VEML_A7, 0x04, b, 2)) { /* ALS */
v->lux_mlx = veml_mlx((uint16_t)le16(b), VEML_GAIN, VEML_IT);
v->ok |= 16u;
}
}
int main(void)
{
static sens_t v;
static char line[160];
board_init();
uint8_t mask = sens_init();
uint32_t next = ms + 1000u;
for (;;) {
if ((int32_t)(ms - next) < 0) {
__WFI(); /* SysTick будит раз в миллисекунду */
continue;
}
next += 1000u;
sens_poll(mask, &v);
uart_send(line, fmt_line(line, &v));
}
}
Строка модели — 106 байт вместе с \r\n:
S=1F T=23.49 RH=46.0 P=990.00 TP=24.10 ALT=195.4 A=20,-13,1000 G=1.12,-0.21,0.00 M=300,-152,-600 L=345.6
S=1F — ответили все пять. При 115 200 бод (BRR = 139 от 16 МГц,
115 108 бод, −0,08 %) строка уходит за 9,2 мс; шина на весь опрос —
396 тактов SCL, около 1,0 мс. Прошивку armclang 6.24 собрал без
предупреждений: код sens_u585.c — 840 байт, sens_dev.c — 946,
sens_conv.c — 1348 (всего 3134 байта), ОЗУ — 256 байт.
_Static_assert держат BRR = 139 и поля TIMINGR: копия с 9600 бод не
собирается.
198х.7 Грабли
Адрес 8 бит вместо 7. BSP хранит адреса со сдвигом и битом чтения:
HTS221 — 0xBF, а на шине он 0x5F. Отдать 0xBF функции, которая сама
сдвигает адрес (как наш i2c_rd), — в поле SADD окажется 0x17E, а блок
в 7-битном режиме смотрит только SADD[7:1] (RM0456, регистр I2C_CR2) —
на шину уйдёт 0x3F. На
модели все пять 8-битных адресов BSP, поданные как 7-битные, — 0 ответов
из 5. Обратная ошибка — 0x5F в HAL_I2C_Mem_Read, который ждёт уже
сдвинутый адрес: на шину уйдёт 0x2F.
Чтение пачкой без автоинкремента. HTS221 наращивает адрес только при
бите 7 подадреса. Без него четыре байта с 0x28 — четыре копии одного
регистра (0xAC), и модель показывает −281,19 °C. Калибровка без бита 7 —
16 копий 0x30: T0_OUT = T1_OUT, делитель прямой — ноль (наш код
возвращает INT32_MIN и не делит). У LPS22HH и ISM330DHCX правило другое
— бит в регистре управления; LPS22HH со сброшенным IF_ADD_INC на модели
отдаёт пять одинаковых байт — 0,00 гПа.
Порядок байт. У датчиков ST младший байт первым. Собрать старшим
первым — отсчёт 16 393 (байты 0x09, 0x40) превращается в 2368, и 1 g
читается как 144 мг. У IIS2MDC есть бит BLE в CFG_REG_C: 0 — младший
байт по младшему адресу, 1 — старший (iis2mdc_ble_t); держать его в нуле.
VEML6030: драйверы спорят о порядке байт. Zephyr собирает регистр
младшим байтом первым (sys_get_le16), драйвер ST после чтения байты
переставляет (veml6030_read_reg). На модели с младшим первым драйвер ST
читает 6000 как 0x7017 = 28 695 (1652,8 лк); если прав ST, то ошибается
наш код — ровно так же. С даташитом Vishay это не сверено, рассудит
плата.
Половина uint32_t. VEML6030_GetValues пишет 16 бит через
(uint16_t *) в массив uint32_t — старшая половина остаётся, какой
была. Массив, заполненный 0xDEADBEEF, после вызова — 0xDEAD7017.
Обнулять массив перед вызовом.
Код 01 в FS_XL — это ±16g. Кто ждёт ±4g по порядку, получит 1 g как
2049 единиц и, умножив на 0,122, — 250 мг.
Калибровку — один раз. Драйвер ST перечитывает её при каждом замере: 12 транзакций на одно чтение HTS221 вместо одной.
Малый шум LPS22HH пропадает в Enable. См. 198х.4: после
LPS22HH_Init бит стоит, после Enable с 25 Гц — снят.
198х.8 Итоги
- Пять датчиков на I2C2 (
PH4,PH5): HTS221 0x5F, LPS22HH 0x5D, ISM330DHCX 0x6B, IIS2MDC 0x1E, VEML6030 0x10; паспорта0xBC,0xB3,0x6B,0x40, у VEML6030 паспорта нет. - HTS221: прямая через две заводские точки; пример — 23,492 °C и 45,974 %, драйвер ST совпал, пример даташита — ровно 15,00 °C и 30,0 %.
- LPS22HH: 24 бита / 4096 = гПа, 0x3DE000 — 990,00 гПа, 195,4 м от
1013,25 гПа; малый шум из
LPS22HH_InitснимаетEnable. - ISM330DHCX: 0,061 мг и 70 мград/с на единицу при ±2g и ±2000 °/с; ST
отбрасывает дробь (999 мг вместо 1000); код
FS_XL01 — ±16g. - VEML6030: 0,0036 · (800 / время) · (2 / усиление) лк — все 24 числа таблицы Zephyr; ×1 и 100 мс — 0,0576 лк, 6000 — 345,6 лк.
- Опрос: 5 транзакций, 29 байт, около 1,0 мс шины при 395,4 кГц против 21 транзакции и 47 байт через BSP; настройка — 15 регистров из 15 как у BSP.
- Строка 106 байт раз в секунду, 9,2 мс при 115 200 бод; прошивка — 3134 байта кода, 256 байт ОЗУ.
Полные исходники
sens_dev.c — настройка и опрос датчиков B-U585I-IOT02A.
/* sens_dev.c — раздел 198х: настройка и опрос датчиков B-U585I-IOT02A.
Регистры и значения — как у драйверов компонентов пакета B-U585I-IOT02A_BSP (BSD-3-Clause):
после sens_init регистры настройки совпадают с тем, что оставляют BSP_*_Init + BSP_*_Enable. */
#include "sens_dev.h"
#define AI 0x80u /* бит 7 подадреса: автоинкремент у HTS221 (и так же делает BSP для IIS2MDC) */
#define ISM_FS_XL 0u /* ±2g */
#define ISM_FS_G 12u /* ±2000 °/с */
#define VEML_GAIN 0u /* ×1 */
#define VEML_IT 0u /* 100 мс */
static hts_cal_t hts_k;
static int wr1(uint8_t a7, uint8_t reg, uint8_t v)
{
return i2c_wr(a7, reg, &v, 1);
}
static int rd1(uint8_t a7, uint8_t reg, uint8_t *v)
{
return i2c_rd(a7, reg, v, 1);
}
static int set_bits(uint8_t a7, uint8_t reg, uint8_t clr, uint8_t set) /* чтение-правка-запись */
{
uint8_t v;
if (rd1(a7, reg, &v))
return 1;
return wr1(a7, reg, (uint8_t)((v & ~clr) | set));
}
static int hts_init(void)
{
uint8_t id, cal[16];
if (rd1(HTS_A7, 0x0F, &id) || id != 0xBC) /* WHO_AM_I */
return 1;
if (i2c_rd(HTS_A7, 0x30 | AI, cal, 16)) /* калибровка 0x30…0x3F одним чтением */
return 1;
hts_cal_parse(cal, &hts_k);
return wr1(HTS_A7, 0x20, 0x85); /* CTRL_REG1: PD, BDU, 1 Гц */
}
static int lps_init(void)
{
uint8_t id;
if (rd1(LPS_A7, 0x0F, &id) || id != 0xB3)
return 1;
return set_bits(LPS_A7, 0x0E, 0, 0x82) /* IF_CTRL: I3C_DISABLE и INT_EN_I3C, как BSP */
|| wr1(LPS_A7, 0x11, 0x10) /* CTRL_REG2: IF_ADD_INC */
|| wr1(LPS_A7, 0x10, 0x32); /* CTRL_REG1: 25 Гц, BDU */
}
static int ism_init(void)
{
uint8_t id, v = 1;
if (rd1(ISM_A7, 0x0F, &id) || id != 0x6B)
return 1;
if (wr1(ISM_A7, 0x12, 0x01)) /* CTRL3_C: SW_RESET */
return 1;
for (int n = 0; n < 1000 && (v & 1u); n++) /* бит снимается сам */
if (rd1(ISM_A7, 0x12, &v))
return 1;
return (v & 1u)
|| wr1(ISM_A7, 0x12, 0x44) /* CTRL3_C: BDU, IF_INC */
|| set_bits(ISM_A7, 0x18, 0, 0x02) /* CTRL9_XL: DEVICE_CONF */
|| set_bits(ISM_A7, 0x0A, 0x07, 0) /* FIFO_CTRL4: FIFO выключен */
|| wr1(ISM_A7, 0x10, 0x40 | ISM_FS_XL << 2) /* CTRL1_XL: 104 Гц, ±2g */
|| wr1(ISM_A7, 0x11, 0x40 | ISM_FS_G); /* CTRL2_G: 104 Гц, ±2000 °/с */
}
static int mag_init(void)
{
uint8_t id;
if (rd1(MAG_A7, 0x4F | AI, &id) || id != 0x40)
return 1;
return set_bits(MAG_A7, 0x62 | AI, 0x02, 0x10) /* CFG_REG_C: BDU, без самотеста */
|| wr1(MAG_A7, 0x60 | AI, 0x0C); /* CFG_REG_A: 100 Гц, непрерывно */
}
static int veml_init(void)
{
uint8_t conf[2];
if (i2c_rd(VEML_A7, 0x00, conf, 2)) /* паспорта нет: хватит ответа на адрес */
return 1;
conf[0] = (uint8_t)(VEML_GAIN << 11 | VEML_IT << 6); /* ALS_CONF, младший байт первым; SD = 0 */
conf[1] = (uint8_t)((VEML_GAIN << 11 | VEML_IT << 6) >> 8);
return i2c_wr(VEML_A7, 0x00, conf, 2);
}
uint8_t sens_init(void)
{
uint8_t m = 0;
m |= (uint8_t)(!hts_init() << 0);
m |= (uint8_t)(!lps_init() << 1);
m |= (uint8_t)(!ism_init() << 2);
m |= (uint8_t)(!mag_init() << 3);
m |= (uint8_t)(!veml_init() << 4);
return m;
}
void sens_poll(uint8_t mask, sens_t *v)
{
uint8_t b[12];
v->ok = 0;
if ((mask & 1u) && !i2c_rd(HTS_A7, 0x28 | AI, b, 4)) { /* HUMIDITY_OUT, TEMP_OUT */
v->rh_x10 = hts_rh_x10(&hts_k, b);
v->t_c100 = hts_temp_c100(&hts_k, b);
v->ok |= 1u;
}
if ((mask & 2u) && !i2c_rd(LPS_A7, 0x28, b, 5)) { /* PRESS_OUT_XL…TEMP_OUT_H */
v->p_pa = lps_press_pa(b);
v->tp_c100 = lps_temp_c100(b);
v->alt_dm = alt_dm(v->p_pa, P0_PA);
v->ok |= 2u;
}
if ((mask & 4u) && !i2c_rd(ISM_A7, 0x22, b, 12)) { /* OUTX_L_G…OUTZ_H_A */
for (int i = 0; i < 3; i++) {
v->gyr_mdps[i] = ism_gyr_mdps(le16(&b[2 * i]), ISM_FS_G);
v->acc_mg[i] = ism_acc_mg(le16(&b[6 + 2 * i]), ISM_FS_XL);
}
v->ok |= 4u;
}
if ((mask & 8u) && !i2c_rd(MAG_A7, 0x68 | AI, b, 6)) { /* OUTX_L_REG…OUTZ_H_REG */
for (int i = 0; i < 3; i++)
v->mag_mg[i] = mag_mg(le16(&b[2 * i]));
v->ok |= 8u;
}
if ((mask & 16u) && !i2c_rd(VEML_A7, 0x04, b, 2)) { /* ALS */
v->lux_mlx = veml_mlx((uint16_t)le16(b), VEML_GAIN, VEML_IT);
v->ok |= 16u;
}
}
sens_conv.c — сырые байты датчиков → единицы, целыми числами.
/* sens_conv.c — раздел 198х: сырые байты датчиков → единицы, целыми числами.
Формулы и коэффициенты — из драйверов компонентов пакета B-U585I-IOT02A_BSP (BSD-3-Clause),
коэффициенты VEML6030 — из драйвера veml7700 проекта Zephyr (Apache-2.0). */
#include <math.h>
#include "sens_conv.h"
static int64_t div_round(int64_t a, int64_t b) /* деление с округлением к ближайшему, b > 0 */
{
return (a >= 0 ? a + b / 2 : a - b / 2) / b;
}
int16_t le16(const uint8_t *p) /* младший байт первым */
{
return (int16_t)(uint16_t)(p[0] | (uint16_t)p[1] << 8);
}
void hts_cal_parse(const uint8_t c[16], hts_cal_t *k) /* c[0] — регистр 0x30, c[15] — 0x3F */
{
k->h0_x2 = c[0x00];
k->h1_x2 = c[0x01];
k->t0_x8 = c[0x02] | (int32_t)(c[0x05] & 0x03u) << 8; /* старшие биты T0 — 0x35[1:0] */
k->t1_x8 = c[0x03] | (int32_t)(c[0x05] & 0x0Cu) << 6; /* старшие биты T1 — 0x35[3:2] */
k->h0_out = le16(&c[0x06]); /* 0x36, 0x37 */
k->h1_out = le16(&c[0x0A]); /* 0x3A, 0x3B */
k->t0_out = le16(&c[0x0C]); /* 0x3C, 0x3D */
k->t1_out = le16(&c[0x0E]); /* 0x3E, 0x3F */
}
int32_t hts_temp_c100(const hts_cal_t *k, const uint8_t out[4]) /* out — 0x28…0x2B */
{
int64_t dx = (int64_t)k->t1_out - k->t0_out;
if (dx == 0)
return INT32_MIN; /* испорченная калибровка */
int64_t x = le16(&out[2]) - (int64_t)k->t0_out; /* TEMP_OUT — 0x2A, 0x2B */
/* T = T0 + (x − x0)·(T1 − T0)/(x1 − x0); T0 и T1 хранятся ×8, ответ — ×100 */
return (int32_t)div_round((int64_t)k->t0_x8 * 100 * dx + x * (k->t1_x8 - k->t0_x8) * 100, 8 * dx);
}
int32_t hts_rh_x10(const hts_cal_t *k, const uint8_t out[4])
{
int64_t dx = (int64_t)k->h1_out - k->h0_out;
if (dx == 0)
return INT32_MIN;
int64_t x = le16(&out[0]) - (int64_t)k->h0_out; /* HUMIDITY_OUT — 0x28, 0x29 */
int64_t rh = div_round((int64_t)k->h0_x2 * 10 * dx + x * (k->h1_x2 - k->h0_x2) * 10, 2 * dx);
return (int32_t)(rh < 0 ? 0 : rh > 1000 ? 1000 : rh); /* как драйвер ST: 0…100 % */
}
uint32_t lps_press_pa(const uint8_t b[5]) /* b — 0x28…0x2C: давление 3 байта, температура 2 */
{
uint32_t raw = b[0] | (uint32_t)b[1] << 8 | (uint32_t)b[2] << 16;
return (uint32_t)((raw * 25u + 512u) / 1024u); /* raw/4096 гПа = raw·100/4096 Па */
}
int32_t lps_temp_c100(const uint8_t b[5])
{
return le16(&b[3]); /* уже в сотых °C */
}
int32_t alt_dm(uint32_t p_pa, uint32_t p0_pa) /* барометрическая формула из раздела 98 */
{
float h = 44330.0f * (1.0f - powf((float)p_pa / (float)p0_pa, 1.0f / 5.255f));
return (int32_t)lroundf(h * 10.0f);
}
int32_t ism_acc_mg(int16_t raw, uint8_t fs_xl) /* FS_XL: 00 ±2g, 01 ±16g, 10 ±4g, 11 ±8g */
{
static const int32_t ug_per_lsb[4] = {61, 488, 122, 244};
return (int32_t)div_round((int64_t)raw * ug_per_lsb[fs_xl & 3u], 1000);
}
int32_t ism_gyr_mdps(int16_t raw, uint8_t fs_g) /* 4 бита CTRL2_G[3:0]: FS_G[1:0], FS_125, FS_4000 */
{
int32_t udps; /* микроградусы в секунду на единицу */
if (fs_g & 1u) udps = 140000; /* ±4000 */
else if (fs_g & 2u) udps = 4375; /* ±125 */
else udps = 8750 << (fs_g >> 2); /* ±250, 500, 1000, 2000 */
return (int32_t)div_round((int64_t)raw * udps, 1000);
}
int32_t mag_mg(int16_t raw) /* 1,5 мГс на единицу */
{
return (int32_t)div_round((int64_t)raw * 3, 2);
}
uint32_t veml_res_ulx(uint8_t gain, uint8_t it) /* микролюксы на единицу счёта */
{
static const uint8_t gmul[4] = {2, 1, 16, 8}; /* ALS_GAIN: 00 ×1, 01 ×2, 10 ×1/8, 11 ×1/4 */
uint32_t tmul;
switch (it) { /* ALS_IT, мс: 25, 50, 100, 200, 400, 800 */
case 0x0C: tmul = 32; break;
case 0x08: tmul = 16; break;
case 0x00: tmul = 8; break;
case 0x01: tmul = 4; break;
case 0x02: tmul = 2; break;
case 0x03: tmul = 1; break;
default: return 0;
}
return 3600u * gmul[gain & 3u] * tmul; /* 0,0036 лк при ×2 и 800 мс */
}
uint32_t veml_mlx(uint16_t counts, uint8_t gain, uint8_t it)
{
return (uint32_t)(((uint64_t)counts * veml_res_ulx(gain, it) + 500u) / 1000u);
}
/* ── строка в UART ──────────────────────────────────────────────────────── */
static char *put_u(char *p, uint32_t v)
{
char t[10];
int n = 0;
do {
t[n++] = (char)('0' + v % 10u);
v /= 10u;
} while (v);
while (n)
*p++ = t[--n];
return p;
}
static char *put_fix(char *p, int32_t v, unsigned dec) /* v в единицах 10^-dec */
{
uint32_t a = v < 0 ? 0u - (uint32_t)v : (uint32_t)v, d = 1;
for (unsigned i = 0; i < dec; i++)
d *= 10u;
if (v < 0)
*p++ = '-';
p = put_u(p, a / d);
if (dec) {
uint32_t f = a % d;
*p++ = '.';
for (uint32_t q = d / 10u; q; q /= 10u) {
*p++ = (char)('0' + f / q);
f %= q;
}
}
return p;
}
static char *put_s(char *p, const char *s)
{
while (*s)
*p++ = *s++;
return p;
}
static char *put_xyz(char *p, const char *name, const int32_t v[3], unsigned dec)
{
p = put_s(p, name);
for (int i = 0; i < 3; i++) {
if (i)
*p++ = ',';
p = put_fix(p, v[i], dec);
}
return p;
}
int fmt_line(char *s, const sens_t *v) /* нужно до 160 байт */
{
static const char hex[] = "0123456789ABCDEF";
int32_t g[3];
char *p = put_s(s, "S=");
*p++ = hex[v->ok >> 4 & 0xFu];
*p++ = hex[v->ok & 0xFu];
p = put_s(p, " T="); p = put_fix(p, v->t_c100, 2);
p = put_s(p, " RH="); p = put_fix(p, v->rh_x10, 1);
p = put_s(p, " P="); p = put_fix(p, (int32_t)v->p_pa, 2);
p = put_s(p, " TP="); p = put_fix(p, v->tp_c100, 2);
p = put_s(p, " ALT="); p = put_fix(p, v->alt_dm, 1);
p = put_xyz(p, " A=", v->acc_mg, 0);
for (int i = 0; i < 3; i++)
g[i] = (int32_t)div_round(v->gyr_mdps[i], 10); /* сотые °/с */
p = put_xyz(p, " G=", g, 2);
p = put_xyz(p, " M=", v->mag_mg, 0);
p = put_s(p, " L="); p = put_fix(p, (int32_t)((v->lux_mlx + 50u) / 100u), 1);
p = put_s(p, "\r\n");
*p = 0;
return (int)(p - s);
}
sens_u585.c — прошивка опроса датчиков регистрами, без HAL.
/* sens_u585.c — раздел 198х: прошивка опроса датчиков B-U585I-IOT02A, регистрами, без HAL.
Ядро на MSIS 4 МГц после сброса; I2C2 и USART1 тактуются от HSI16. Раз в секунду — строка в USART1.
Выводы — как в BSP платы: I2C2 — PH4 (SCL) и PH5 (SDA), AF4; COM1 — USART1, PA9 (TX), AF7. */
#include "stm32u585xx.h"
#include "sens_dev.h"
#define HSI_HZ 16000000u
#define BAUD 115200u
#define UART_BRR ((HSI_HZ + BAUD / 2u) / BAUD)
#define I2C_TIMING 0x00500A11u /* 400 кГц от 16 МГц — алгоритм I2C_GetTiming из BSP (timing198h.py) */
#define TMO 100000u /* предел ожидания флага, итераций */
_Static_assert(UART_BRR == 139u, "USART1: 16 МГц / 115200");
_Static_assert((I2C_TIMING >> 28) == 0u && (I2C_TIMING & 0xFFu) == 17u && ((I2C_TIMING >> 8) & 0xFFu) == 10u, "поля TIMINGR");
static volatile uint32_t ms;
void SysTick_Handler(void)
{
ms++;
}
static void board_init(void)
{
RCC->CR |= RCC_CR_HSION;
while (!(RCC->CR & RCC_CR_HSIRDY)) {
}
RCC->CCIPR1 = (RCC->CCIPR1 & ~(RCC_CCIPR1_USART1SEL | RCC_CCIPR1_I2C2SEL))
| 2u << RCC_CCIPR1_USART1SEL_Pos | 2u << RCC_CCIPR1_I2C2SEL_Pos; /* 10 — HSI16 */
RCC->AHB2ENR1 |= RCC_AHB2ENR1_GPIOAEN | RCC_AHB2ENR1_GPIOHEN;
RCC->APB1ENR1 |= RCC_APB1ENR1_I2C2EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
(void)RCC->APB2ENR;
/* PH4, PH5: альтернативная функция 4, открытый сток, подтяжка вверх, высокая скорость — как BSP */
GPIOH->AFR[0] = (GPIOH->AFR[0] & ~(0xFFu << 16)) | 4u << 16 | 4u << 20;
GPIOH->OTYPER |= 3u << 4;
GPIOH->OSPEEDR = (GPIOH->OSPEEDR & ~(0xFu << 8)) | 0xAu << 8;
GPIOH->PUPDR = (GPIOH->PUPDR & ~(0xFu << 8)) | 0x5u << 8;
GPIOH->MODER = (GPIOH->MODER & ~(0xFu << 8)) | 0xAu << 8;
/* PA9: USART1_TX, альтернативная функция 7 */
GPIOA->AFR[1] = (GPIOA->AFR[1] & ~(0xFu << 4)) | 7u << 4;
GPIOA->MODER = (GPIOA->MODER & ~(3u << 18)) | 2u << 18;
I2C2->CR1 = 0; /* PE = 0: TIMINGR меняется только выключенным */
I2C2->TIMINGR = I2C_TIMING;
I2C2->CR1 = I2C_CR1_PE; /* аналоговый фильтр включён (ANFOFF = 0) */
USART1->BRR = UART_BRR;
USART1->CR1 = USART_CR1_TE | USART_CR1_UE;
SysTick->LOAD = 4000u - 1u; /* 1 мс при MSIS 4 МГц */
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
}
static int wait_flag(uint32_t flag) /* 0 — флаг есть; 1 — NACK или нет ответа */
{
for (uint32_t n = 0; n < TMO; n++) {
uint32_t isr = I2C2->ISR;
if (isr & I2C_ISR_NACKF)
return 1;
if (isr & flag)
return 0;
}
return 1;
}
static int fail(void) /* после NACK блок сам выдаёт STOP; иначе — просим */
{
if (!(I2C2->ISR & I2C_ISR_NACKF))
I2C2->CR2 |= I2C_CR2_STOP;
for (uint32_t n = 0; n < TMO && !(I2C2->ISR & I2C_ISR_STOPF); n++) {
}
I2C2->ICR = I2C_ICR_NACKCF | I2C_ICR_STOPCF;
return 1;
}
static int finish(void)
{
if (wait_flag(I2C_ISR_STOPF))
return fail();
I2C2->ICR = I2C_ICR_STOPCF; /* пока STOPF стоит, блок занят */
return 0;
}
int i2c_rd(uint8_t a7, uint8_t reg, uint8_t *buf, uint16_t n) /* подадрес, повторный старт, n байт */
{
if (n == 0u || n > 255u)
return 1;
I2C2->CR2 = (uint32_t)a7 << 1 | 1u << I2C_CR2_NBYTES_Pos | I2C_CR2_START;
if (wait_flag(I2C_ISR_TXIS))
return fail();
I2C2->TXDR = reg;
if (wait_flag(I2C_ISR_TC)) /* подадрес ушёл, STOP не выдан */
return fail();
I2C2->CR2 = (uint32_t)a7 << 1 | I2C_CR2_RD_WRN | (uint32_t)n << I2C_CR2_NBYTES_Pos
| I2C_CR2_AUTOEND | I2C_CR2_START;
for (uint16_t i = 0; i < n; i++) {
if (wait_flag(I2C_ISR_RXNE))
return fail();
buf[i] = (uint8_t)I2C2->RXDR;
}
return finish();
}
int i2c_wr(uint8_t a7, uint8_t reg, const uint8_t *buf, uint16_t n) /* подадрес и n байт одной посылкой */
{
if (n > 254u)
return 1;
I2C2->CR2 = (uint32_t)a7 << 1 | (uint32_t)(n + 1u) << I2C_CR2_NBYTES_Pos | I2C_CR2_AUTOEND | I2C_CR2_START;
for (uint16_t i = 0; i <= n; i++) {
if (wait_flag(I2C_ISR_TXIS))
return fail();
I2C2->TXDR = i ? buf[i - 1u] : reg;
}
return finish();
}
static void uart_send(const char *s, int n)
{
for (int i = 0; i < n; i++) {
while (!(USART1->ISR & USART_ISR_TXE_TXFNF)) {
}
USART1->TDR = (uint8_t)s[i];
}
}
int main(void)
{
static sens_t v;
static char line[160];
board_init();
uint8_t mask = sens_init();
uint32_t next = ms + 1000u;
for (;;) {
if ((int32_t)(ms - next) < 0) {
__WFI(); /* SysTick будит раз в миллисекунду */
continue;
}
next += 1000u;
sens_poll(mask, &v);
uart_send(line, fmt_line(line, &v));
}
}
Дальше: раздел 198ц «Мини-проект: журнал во внешней Octo-SPI флешке B-U585I-IOT02A» — строки этого опроса ложатся в MX25LM51245G на OCTOSPI2 и переживают выключение.