feat: added sensor pairing to gateway
This commit is contained in:
1 parent
13b74b9306
commit
52f2da6368
4 files changed
+705
-208
No files matched your search
+141
-67
@@ -4,17 +4,18 @@
|
||||
|
||||
**zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+.
|
||||
|
||||
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
|
||||
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. Идентификатор устройства получается автоматически по воздуху в процедуре сопряжения со шлюзом и сохраняется в EEPROM.
|
||||
|
||||
### Основные возможности
|
||||
|
||||
1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна.
|
||||
2. **Измерение климата** — температура, влажность и атмосферное давление датчиком BME280 по шине I2C.
|
||||
3. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
|
||||
4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
|
||||
5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
|
||||
6. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут.
|
||||
7. **Автономная работа** — не требует внешних подтягивающих резисторов.
|
||||
3. **Автоматическое сопряжение (pairing)** — запрос идентификатора устройства у шлюза по воздуху, хранение ID в EEPROM.
|
||||
4. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
|
||||
5. **Работа по протоколу nRF24L01+** — передача и приём через SPI, адресная доставка на шлюз, динамическая длина пакета.
|
||||
6. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
|
||||
7. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут.
|
||||
8. **Автономная работа** — не требует внешних подтягивающих резисторов (используются внутренние pull-up).
|
||||
|
||||
---
|
||||
|
||||
@@ -22,38 +23,51 @@
|
||||
|
||||
### Структура данных
|
||||
|
||||
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
|
||||
Компонент не использует динамическое выделение памяти. Сырые показания хранятся в статических переменных, калибровочные коэффициенты BME280 — в массиве `bme280_calib[33]`. Перед передачей данные сериализуются функцией `zh_rf_sensor_build_payload()` из внешней библиотеки `zh_rf_config` в буфер `uint8_t[ZH_RF_MAX_PAYLOAD_SIZE]` (32 байта) в формате little-endian.
|
||||
|
||||
#### Формат передаваемого пакета
|
||||
|
||||
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
|
||||
Данные формируются в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из библиотеки `zh_rf_config`, затем сериализуются в полезную нагрузку. Для типа `ZH_RF_SENSOR_BME280` полный размер пакета — 12 байт (заголовок id + type + battery и три поля измерений, каждое `int16_t` в little-endian).
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| ------ | ----- | ---------- |
|
||||
| `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
|
||||
| `id` | `int16_t` | Идентификатор устройства, назначенный шлюзом при сопряжении |
|
||||
| `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_BME280`) |
|
||||
| `value_1` | `int16_t` | Влажность (%, например 45 = 45 %) |
|
||||
| `battery_voltage` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
|
||||
| `value_2` | `int16_t` | Температура (°C, например 25 = 25 °C) |
|
||||
| `value_3` | `int16_t` | Давление (мм рт. ст.) |
|
||||
| `value_4` | `int16_t` | Зарезервировано (равно 0) |
|
||||
| `battery` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
|
||||
| `value_1` | `int16_t` | Влажность (% × 10, например 453 = 45.3 %) |
|
||||
| `value_2` | `int16_t` | Температура (°C × 10, например 253 = 25.3 °C) |
|
||||
| `value_3` | `int16_t` | Давление (мм рт. ст. × 10) |
|
||||
|
||||
Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BME280` из библиотеки `zh_rf_config`.
|
||||
|
||||
#### Формат пакета сопряжения
|
||||
|
||||
Пакет сопряжения — структура `zh_rf_sensor_pairing_data_t` (5 байт, `ZH_RF_PAIRING_PACKET_SIZE`):
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| ------ | ----- | ---------- |
|
||||
| `type` | `uint8_t` | Тип датчика (код `ZH_RF_SENSOR_BME280`) |
|
||||
| `magic` | `uint8_t` | Маркер направления: `ZH_RF_PAIRING_REQ_MAGIC` (0xA5) в запросе, `ZH_RF_PAIRING_ACK_MAGIC` (0x5A) в ответе |
|
||||
| `token_hi` | `uint8_t` | Старший байт псевдослучайного токена |
|
||||
| `token_lo` | `uint8_t` | Младший байт псевдослучайного токена |
|
||||
| `id` | `uint8_t` | Идентификатор, назначенный шлюзом (в ответе) |
|
||||
|
||||
### Конфигурационные макросы
|
||||
|
||||
| Макрос | Значение | Описание |
|
||||
| -------- | ---------- | ---------- |
|
||||
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
|
||||
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
|
||||
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
|
||||
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до пробуждения (≈ 5 минут) |
|
||||
| `ZH_TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
|
||||
| `EEPROM_DEVICE_ID_ADDR` | `(uint8_t *)0x00` | Адрес ячейки EEPROM для хранения ID устройства |
|
||||
| `DEVICE_ID_UNASSIGNED` | `0xFF` | Признак отсутствующего (несопряжённого) идентификатора |
|
||||
| `PAIRING_TIMEOUT_ATTEMPTS` | `7` | Максимальное число попыток сопряжения |
|
||||
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до цикла измерения (≈ 5 минут) |
|
||||
| `TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
|
||||
| `BME280_ADDR` | `0x76` | 7-битный адрес датчика BME280 на шине I2C |
|
||||
|
||||
### Управление памятью
|
||||
|
||||
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` объявлена как `static` и заполняется при старте (идентификатор и тип). В цикле обновляются все поля данных: `battery_voltage`, `value_1` (влажность), `value_2` (температура), `value_3` (давление).
|
||||
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` заполняется при старте (тип устройства, затем идентификатор из EEPROM или из сопряжения). В цикле обновляются поля `battery`, `value_1` (влажность), `value_2` (температура), `value_3` (давление). Идентификатор устройства сохраняется в EEPROM адресом `0x00` (`eeprom_update_byte`).
|
||||
|
||||
---
|
||||
|
||||
@@ -65,11 +79,23 @@
|
||||
|
||||
| Функция | Описание |
|
||||
| --------- | ---------- |
|
||||
| `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
|
||||
| `zh_wdt_start()` | Запуск watchdog-таймера в режиме прерывания с указанным таймаутом (биты WDP3:0) |
|
||||
| `zh_wdt_stop()` | Остановка watchdog-таймера |
|
||||
| `zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
|
||||
| `zh_bme280_init()` | Инициализация датчика BME280: проверка CHIP_ID, сброс, чтение калибровки, настройка режима |
|
||||
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/2) |
|
||||
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
|
||||
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
|
||||
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+: адреса TX/RX, канал, ретрансмиты, динамические пакеты, CRC |
|
||||
| `zh_nrf24_interrupt_init()` | Настройка внешнего прерывания INT0 (PD2, падающий фронт) для сигнала IRQ трансивера |
|
||||
|
||||
### Идентификатор устройства и сопряжение
|
||||
|
||||
| Функция | Описание |
|
||||
| --------- | ---------- |
|
||||
| `zh_load_device_id()` | Чтение ID устройства из EEPROM (возвращает `DEVICE_ID_UNASSIGNED` при 0x00/0xFF) |
|
||||
| `zh_save_device_id()` | Сохранение ID устройства в EEPROM через `eeprom_update_byte` |
|
||||
| `zh_get_random_token()` | Генерация 16-битного псевдослучайного токена из MCUSR и шума АЦП (никогда не 0) |
|
||||
| `zh_do_pairing()` | Неблокирующая процедура сопряжения: запрос → ожидание ACK в RX-режиме → backoff; возвращает назначенный ID |
|
||||
| `zh_enter_final_sleep()` | Переход в постоянный сон Power-down до сброса (отключение прерываний, WDT, радио, тактов периферии) |
|
||||
|
||||
### Измерение
|
||||
|
||||
@@ -81,16 +107,19 @@
|
||||
|
||||
| Функция | Описание |
|
||||
| --------- | ---------- |
|
||||
| `zh_bme280_read()` | Чтение 8 сырых байт из BME280 и распаковка ADC значений температуры, давления, влажности |
|
||||
| `zh_bme280_read()` | Перевод BME280 в forced mode, чтение 8 сырых байт и распаковка ADC значений температуры, давления, влажности |
|
||||
| `zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
|
||||
| `zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
|
||||
| `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %, диапазон [0, 100]) |
|
||||
| `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %) |
|
||||
|
||||
### Передача данных
|
||||
### Передача данных nRF24L01+
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `zh_nrf24_send()` | Передача буфера через nRF24L01+: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом 100 мс, сброс статуса |
|
||||
| --------- | ---------- |
|
||||
| `zh_nrf24_send()` | Передача буфера: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом ~100 мс, сброс статуса |
|
||||
| `zh_nrf24_send_no_wait()` | Запуск передачи без ожидания завершения (импульс CE 15 мкс), завершение сигнализируется IRQ |
|
||||
| `zh_nrf24_enter_rx()` | Переход в режим приёма на pipe 0 (PRIM_RX, очистка FIFO, CE) |
|
||||
| `zh_nrf24_enter_power_down()` | Перевод трансивера в режим Power Down |
|
||||
|
||||
### Низкоуровневые SPI-операции
|
||||
|
||||
@@ -101,6 +130,8 @@
|
||||
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
|
||||
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой (CSN управляется вручную через PB2) |
|
||||
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
|
||||
| `zh_nrf24_read_payload()` | Чтение принятой полезной нагрузки из RX FIFO (`R_RX_PAYLOAD`) |
|
||||
| `zh_nrf24_read_payload_width()` | Чтение длины пакета в RX FIFO (`R_RX_PL_WID`) |
|
||||
|
||||
### Низкоуровневые TWI-операции (аппаратный I2C)
|
||||
|
||||
@@ -124,33 +155,50 @@
|
||||
|
||||
## Потокобезопасность
|
||||
|
||||
Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (`ISR(WDT_vect)`), которая увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
|
||||
Компонент работает на bare-metal AVR без RTOS. Точки синхронизации — два ISR:
|
||||
|
||||
- `ISR(WDT_vect)` — увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`, инкремент защищён проверкой верхнего предела), заново включает бит `WDIE`.
|
||||
- `ISR(INT0_vect)` — выставляет флаг `nrf_event` (`static volatile uint8_t`) при фронте на пине IRQ трансивера.
|
||||
|
||||
Главный цикл и машина состояний сопряжения проверяют эти флаги после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
|
||||
|
||||
---
|
||||
|
||||
## Алгоритмы
|
||||
|
||||
### Алгоритм загрузки идентификатора и сопряжения
|
||||
|
||||
- При старте читается ID из EEPROM (`eeprom_read_byte`, адрес `0x00`); значения `0x00` и `0xFF` считаются признаком несопряжённого устройства.
|
||||
- При отсутствии ID выполняется `zh_do_pairing()` — неблокирующая машина состояний (`ZH_RF_PAIR_SEND` → `ZH_RF_PAIR_TX_WAIT` → `ZH_RF_PAIR_RX_WAIT` → `ZH_RF_PAIR_BACKOFF`):
|
||||
- генерируется токен (`zh_get_random_token()` из MCUSR и шума АЦП);
|
||||
- отправляется запрос `zh_rf_sensor_pairing_data_t` (magic `0xA5`) без ожидания (`zh_nrf24_send_no_wait`);
|
||||
- после TX_DS трансивер переводится в RX-режим, ожидание ответа со сном и WDT-таймаутом 250 мс;
|
||||
- ответ валидируется по magic `0x5A`, типу, токену и ненулевому ID; ширина пакета проверяется через `R_RX_PL_WID`;
|
||||
- при неудаче — backoff-пауза ~4 с (WDT), не более `PAIRING_TIMEOUT_ATTEMPTS` (7) попыток.
|
||||
- Назначенный ID сохраняется в EEPROM и записывается в `transmitted_data.id`.
|
||||
- После инициализации выполняется пробная сборка пакета `zh_rf_sensor_build_payload()`; при нулевом результате (неизвестный тип) — переход в постоянный сон.
|
||||
|
||||
### Алгоритм инициализации BME280
|
||||
|
||||
- Задержка 100 мс после включения.
|
||||
- Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`).
|
||||
- Сброс записью `0xB6` в регистр `BME280_REG_RESET`.
|
||||
- Сброс записью `0xB6` в регистр `BME280_REG_RESET`, задержка 10 мс.
|
||||
- Чтение калибровочных коэффициентов: 26 байт из `BME280_REG_CALIB00` и 7 байт из `BME280_REG_CALIB26` (всего 33 байта, массив `bme280_calib`).
|
||||
- Конфигурация влажности: `ctrl_hum = 0x01` (oversampling x1).
|
||||
- Конфигурация режима: `config = 0x00` (фильтр off, standby off).
|
||||
- Конфигурация измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode).
|
||||
- Запуск измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode) в `zh_bme280_read()` перед каждым чтением.
|
||||
|
||||
### Алгоритм компенсации BME280
|
||||
|
||||
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280.
|
||||
|
||||
- **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C. Деление на `dig_t1` не выполняется (проверка `dig_t1 == 0` не реализована).
|
||||
- **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C.
|
||||
- **Давление**: `zh_bme280_compensate_pressure()` — возвращает результат в Па. При `var1 == 0` (знаменатель ноль) возвращает 0.0.
|
||||
- **Влажность**: `zh_bme280_compensate_humidity()` — результат ограничивается диапазоном [0, 419430400] на промежуточном шаге, затем делится на 1024.0 и возвращается в процентах.
|
||||
|
||||
### Алгоритм аппаратного TWI
|
||||
|
||||
- Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = ZH_TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц.
|
||||
- Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц.
|
||||
- Стартовое/повторное стартовое условие: установка `TWSTA`, ожидание `TWINT`, проверка статуса (`0x08` — START, `0x10` — REPEATED START).
|
||||
- Передача байта: запись в `TWDR`, сброс `TWINT`, ожидание завершения, проверка статусов подтверждения (`0x18`/`0x28` — данные, `0x40` — адрес чтения).
|
||||
- Приём байта: установка `TWEA` для всех байт кроме последнего, чтение `TWDR` после `TWINT`.
|
||||
@@ -162,18 +210,21 @@
|
||||
- Переход в Power Up (бит `PWR_UP` в CONFIG).
|
||||
- Ожидание 5 мс (время стартования осциллятора).
|
||||
- Очистка TX FIFO командой `FLUSH_TX`.
|
||||
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
|
||||
- Импульс CE длительностью 20 мкс для запуска передачи.
|
||||
- Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс.
|
||||
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD` (размер пакета определяет `zh_rf_sensor_build_payload()`, динамические пакеты включены: `FEATURE = EN_DPL`, `DYNPD = DPL_P0`).
|
||||
- Импульс CE для запуска передачи.
|
||||
- Ожидание завершения по пину IRQ (PD2) с таймаутом ~100 мс.
|
||||
- Сброс флагов статуса, очистка FIFO, переход в Power Down.
|
||||
|
||||
Для процедуры сопряжения используется вариант без блокировки (`zh_nrf24_send_no_wait`, импульс CE 15 мкс) с завершением по прерыванию INT0.
|
||||
|
||||
### Алгоритм сна и пробуждения
|
||||
|
||||
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDP3 | WDP0`), без сброса MCU.
|
||||
- ISR `WDT_vect`: увеличивает `wdt_wake_count`, заново включает бит `WDIE`.
|
||||
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDT_T_8S`), без сброса MCU.
|
||||
- ISR `WDT_vect`: увеличивает `wdt_wake_count` (до предела `WDT_WAKE_COUNT`), заново включает бит `WDIE`.
|
||||
- Главный цикл: переход в Power-down (`sleep_cpu()`). После пробуждения проверяется `wdt_wake_count >= WDT_WAKE_COUNT` (38 × 8 с ≈ 5 минут).
|
||||
- При достижении порога: обнуление счётчика, повторная инициализация BME280 (если датчик недоступен), чтение показаний и передача пакета через nRF24L01+.
|
||||
- Первое измерение выполняется сразу после инициализации перед входом в цикл.
|
||||
- При достижении порога: обнуление счётчика, повторная инициализация BME280 (если датчик недоступен), чтение показаний, сериализация пакета и передача через nRF24L01+.
|
||||
- Первое измерение выполняется сразу после инициализации и сопряжения, до входа в цикл.
|
||||
- `zh_enter_final_sleep()` — аварийный постоянный сон: отключение прерываний, WDT, радио, АЦП, TWI, SPI и тактов всей неиспользуемой периферии (PRR).
|
||||
|
||||
---
|
||||
|
||||
@@ -184,30 +235,43 @@
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
transmitted_data.type = ZH_RF_SENSOR_BME280;
|
||||
MCUSR &= ~(1 << WDRF);
|
||||
wdt_disable();
|
||||
cli();
|
||||
zh_i2c_init();
|
||||
bme280_present = zh_bme280_init();
|
||||
zh_spi_init();
|
||||
zh_nrf24_init();
|
||||
zh_wdt_init();
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
sei();
|
||||
|
||||
uint8_t device_id = zh_load_device_id();
|
||||
if (device_id == DEVICE_ID_UNASSIGNED) {
|
||||
device_id = zh_do_pairing();
|
||||
}
|
||||
transmitted_data.id = (int16_t)device_id;
|
||||
|
||||
zh_wdt_start(WDT_T_8S);
|
||||
int32_t adc_t = 0, adc_p = 0, adc_h = 0;
|
||||
|
||||
// Первое измерение сразу после инициализации
|
||||
// Первое измерение сразу после инициализации и сопряжения
|
||||
if (bme280_present == true) {
|
||||
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
|
||||
transmitted_data.battery_voltage =
|
||||
transmitted_data.battery =
|
||||
(int16_t)lroundf(zh_get_battery_voltage() * 100.0);
|
||||
transmitted_data.value_2 =
|
||||
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
|
||||
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t) * 10.0);
|
||||
transmitted_data.value_3 =
|
||||
(int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062);
|
||||
(int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062 * 10.0);
|
||||
transmitted_data.value_1 =
|
||||
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
|
||||
zh_nrf24_send((uint8_t *)&transmitted_data,
|
||||
sizeof(transmitted_data));
|
||||
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h) * 10.0);
|
||||
uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
|
||||
uint8_t size = zh_rf_sensor_build_payload(&transmitted_data,
|
||||
payload, sizeof(payload));
|
||||
if (size > 0) {
|
||||
zh_nrf24_send(payload, size);
|
||||
}
|
||||
} else {
|
||||
bme280_present = false;
|
||||
}
|
||||
@@ -225,7 +289,7 @@ int main(void)
|
||||
}
|
||||
if (bme280_present == true) {
|
||||
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
|
||||
transmitted_data.battery_voltage =
|
||||
transmitted_data.battery =
|
||||
(int16_t)lroundf(zh_get_battery_voltage() * 100.0);
|
||||
transmitted_data.value_2 =
|
||||
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
|
||||
@@ -233,8 +297,12 @@ int main(void)
|
||||
(int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062);
|
||||
transmitted_data.value_1 =
|
||||
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
|
||||
zh_nrf24_send((uint8_t *)&transmitted_data,
|
||||
sizeof(transmitted_data));
|
||||
uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
|
||||
uint8_t size = zh_rf_sensor_build_payload(&transmitted_data,
|
||||
payload, sizeof(payload));
|
||||
if (size > 0) {
|
||||
zh_nrf24_send(payload, size);
|
||||
}
|
||||
} else {
|
||||
bme280_present = false;
|
||||
}
|
||||
@@ -247,8 +315,9 @@ int main(void)
|
||||
### Изменение конфигурации под другое устройство
|
||||
|
||||
```c
|
||||
// Измените идентификатор для нового устройства
|
||||
#define DEVICE_ID 7 // Уникальный ID в сети
|
||||
// Идентификатор устройства назначается шлюзом при сопряжении
|
||||
// и хранится в EEPROM (адрес 0x00). Для повторного сопряжения
|
||||
// обнулите ячейку EEPROM.
|
||||
#define GATEWAY_CHANNEL 120
|
||||
#define GATEWAY_ADDRESS 0xDDEEFF
|
||||
// Тип устройства задаётся перечислением ZH_RF_SENSOR_BME280
|
||||
@@ -263,10 +332,10 @@ int main(void)
|
||||
// PB3 -> MOSI (назначается аппаратным SPI)
|
||||
// PB5 -> SCK (назначается аппаратным SPI)
|
||||
// PB1 -> CE (управление передачей, выход)
|
||||
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC)
|
||||
// PD2 -> IRQ (вход, активный низкий, INT0, с подтяжкой к VCC)
|
||||
// BME280 (I2C, аппаратный TWI):
|
||||
// PC4 -> SDA (аппаратный TWI, pull-up)
|
||||
// PC5 -> SCL (аппаратный TWI, pull-up)
|
||||
// PC4 -> SDA (аппаратный TWI, внутренний pull-up)
|
||||
// PC5 -> SCL (аппаратный TWI, внутренний pull-up)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -279,7 +348,7 @@ int main(void)
|
||||
|
||||
## Ошибки
|
||||
|
||||
Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
|
||||
Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). `zh_load_device_id` возвращает `DEVICE_ID_UNASSIGNED` при отсутствии сохранённого ID. Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
|
||||
|
||||
| Условие | Поведение |
|
||||
| --------- | ---------- |
|
||||
@@ -291,21 +360,24 @@ int main(void)
|
||||
| Ошибка чтения BME280 | `zh_bme280_read()` возвращает false, пакет не формируется, `bme280_present = false` |
|
||||
| `var1 == 0` при компенсации давления | `zh_bme280_compensate_pressure()` возвращает 0.0 |
|
||||
| Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной |
|
||||
| Нет ответа шлюза при сопряжении | После 7 попыток (`PAIRING_TIMEOUT_ATTEMPTS`) — переход в постоянный сон до сброса |
|
||||
| Ширина принятого пакета ≠ 5 байт / > 32 байт | FIFO очищается, состояние сопряжения переходит в backoff |
|
||||
| `zh_rf_sensor_build_payload()` вернул 0 | Неизвестный тип датчика или слишком мал буфер — переход в постоянный сон |
|
||||
|
||||
---
|
||||
|
||||
## Ограничения
|
||||
|
||||
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
|
||||
1. **Широковещательная передача данных** — приём используется только на этапе сопряжения; шлюз не подтверждает пакеты измерений на уровне приложения.
|
||||
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
|
||||
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
|
||||
3. **Жёстко зашитый адрес шлюза** — смена адреса или канала требует перепрошивки (ID устройства при этом хранится в EEPROM).
|
||||
4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены.
|
||||
5. **Отсутствие валидации `dig_t1`** — в `zh_bme280_compensate_temperature()` нет проверки `dig_t1 == 0`, что может привести к делению на ноль.
|
||||
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В.
|
||||
7. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются.
|
||||
8. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся.
|
||||
9. **Давление в мм рт. ст.** — `zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.).
|
||||
10. **Температура и влажность без масштабирования** — значения передаются как целые числа (°C и %), дробная часть теряется.
|
||||
5. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В.
|
||||
6. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются.
|
||||
7. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся.
|
||||
8. **Давление в мм рт. ст.** — `zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.).
|
||||
9. **Масштабирование значений ×10 только в первом цикле** — при первом измерении температура, влажность и давление умножаются на 10 (одна дробная цифра); в цикле пробуждения значения записываются без масштабирования (см. `main`).
|
||||
10. **Сопряжение требует шлюза** — без ответившего шлюза устройство после 7 попыток уходит в постоянный сон до сброса/перепрошивки.
|
||||
|
||||
---
|
||||
|
||||
@@ -313,7 +385,7 @@ int main(void)
|
||||
|
||||
- PlatformIO с плагином `platform-atmelavr`
|
||||
- AVR-GCC (входит в состав PlatformIO)
|
||||
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
|
||||
- Библиотека `zh_rf_config` (ветка `dev`, подключается через `lib_deps` в `platformio.ini`)
|
||||
- Программатор USBasp (или совместимый)
|
||||
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
||||
|
||||
@@ -334,16 +406,18 @@ pio run -e ATmega328P -t upload
|
||||
```ini
|
||||
; platformio.ini
|
||||
lib_deps =
|
||||
https://github.com/aZholtikov/zh_rf_config
|
||||
https://github.com/aZholtikov/zh_rf_config#dev
|
||||
```
|
||||
|
||||
```c
|
||||
// Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола
|
||||
#include "avr/io.h"
|
||||
#include "avr/sleep.h"
|
||||
#include "avr/fuse.h"
|
||||
#include "avr/interrupt.h"
|
||||
#include "avr/sleep.h"
|
||||
#include "avr/eeprom.h"
|
||||
#include "avr/wdt.h"
|
||||
#include "util/delay.h"
|
||||
#include "stdbool.h"
|
||||
#include "math.h"
|
||||
#include "zh_rf_config.h"
|
||||
```
|
||||
Reference in new issue
Block a user