feat: initial

This commit is contained in:
aZholtikov committed 2026-10-22 12:37:06 +03:00
1 parent 50c79ce045
commit eb4568b0f4
6 files changed
+1300 -1

No files matched your search

+349
View File
@@ -0,0 +1,349 @@
# Описание компонента zh_avr_bme280_sensor
## Общие сведения
**zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
### Основные возможности
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. **Автономная работа** — не требует внешних подтягивающих резисторов.
---
## Внутреннее устройство
### Структура данных
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
#### Формат передаваемого пакета
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
| Поле | Тип | Описание |
| ------ | ----- | ---------- |
| `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
| `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) |
Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BME280` из библиотеки `zh_rf_config`.
### Конфигурационные макросы
| Макрос | Значение | Описание |
| -------- | ---------- | ---------- |
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до пробуждения (≈ 5 минут) |
| `ZH_TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
| `BME280_ADDR` | `0x76` | 7-битный адрес датчика BME280 на шине I2C |
### Управление памятью
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` объявлена как `static` и заполняется при старте (идентификатор и тип). В цикле обновляются все поля данных: `battery_voltage`, `value_1` (влажность), `value_2` (температура), `value_3` (давление).
---
## API (интерфейс)
Все функции являются `static` и доступны только внутри модуля `main.c`.
### Инициализация
| Функция | Описание |
| --------- | ---------- |
| `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
| `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_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В (две конверсии, усреднение) |
### Чтение данных BME280
| Функция | Описание |
| --------- | ---------- |
| `zh_bme280_read()` | Чтение 8 сырых байт из BME280 и распаковка ADC значений температуры, давления, влажности |
| `zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
| `zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
| `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %, диапазон [0, 100]) |
### Передача данных
| Функция | Описание |
|---------|----------|
| `zh_nrf24_send()` | Передача буфера через nRF24L01+: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом 100 мс, сброс статуса |
### Низкоуровневые SPI-операции
| Функция | Описание |
| --------- | ---------- |
| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой (CSN управляется вручную через PB2) |
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
### Низкоуровневые TWI-операции (аппаратный I2C)
| Функция | Описание |
| --------- | ---------- |
| `zh_i2c_wait()` | Ожидание флага `TWINT` с таймаутом 65535 итераций (возвращает false при зависании шины) |
| `zh_i2c_start()` | Генерация стартового (или повторного стартового) условия, проверка статусов `0x08`/`0x10` |
| `zh_i2c_stop()` | Генерация стоп-условия с ограниченным ожиданием сброса бита `TWSTO` |
| `zh_i2c_write()` | Передача одного байта через `TWDR`, проверка статусов ACK (`0x18`, `0x28`, `0x40`) |
| `zh_i2c_read_ack()` | Чтение байта из `TWDR` с подтверждением ACK (возвращает `0xFF` при таймауте) |
| `zh_i2c_read_nack()` | Чтение последнего байта из `TWDR` без подтверждения NACK (возвращает `0xFF` при таймауте) |
### Служебные TWI-функции BME280
| Функция | Описание |
| --------- | ---------- |
| `zh_bme280_write_reg()` | Запись одного байта в регистр BME280 (start, адрес, регистр, данные, stop, задержка 2 мс) |
| `zh_bme280_read_reg()` | Чтение нескольких байт из регистров BME280 (repeated-start, ACK/NACK) |
---
## Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (`ISR(WDT_vect)`), которая увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
---
## Алгоритмы
### Алгоритм инициализации BME280
- Задержка 100 мс после включения.
- Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`).
- Сброс записью `0xB6` в регистр `BME280_REG_RESET`.
- Чтение калибровочных коэффициентов: 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).
### Алгоритм компенсации BME280
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280.
- **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C. Деление на `dig_t1` не выполняется (проверка `dig_t1 == 0` не реализована).
- **Давление**: `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 МГц.
- Стартовое/повторное стартовое условие: установка `TWSTA`, ожидание `TWINT`, проверка статуса (`0x08` — START, `0x10` — REPEATED START).
- Передача байта: запись в `TWDR`, сброс `TWINT`, ожидание завершения, проверка статусов подтверждения (`0x18`/`0x28` — данные, `0x40` — адрес чтения).
- Приём байта: установка `TWEA` для всех байт кроме последнего, чтение `TWDR` после `TWINT`.
- Останов: установка `TWSTO`, ограниченное ожидание (1000 итераций) сброса бита аппаратно.
- Обработка ошибок: каждая операция возвращает `false` при таймауте ожидания или неверном статусе, вызывающая сторона выполняет `zh_i2c_stop()`.
### Алгоритм передачи через nRF24L01+
- Переход в Power Up (бит `PWR_UP` в CONFIG).
- Ожидание 5 мс (время стартования осциллятора).
- Очистка TX FIFO командой `FLUSH_TX`.
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
- Импульс CE длительностью 20 мкс для запуска передачи.
- Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс.
- Сброс флагов статуса, очистка FIFO, переход в Power Down.
### Алгоритм сна и пробуждения
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDP3 | WDP0`), без сброса MCU.
- ISR `WDT_vect`: увеличивает `wdt_wake_count`, заново включает бит `WDIE`.
- Главный цикл: переход в Power-down (`sleep_cpu()`). После пробуждения проверяется `wdt_wake_count >= WDT_WAKE_COUNT` (38 × 8 с ≈ 5 минут).
- При достижении порога: обнуление счётчика, повторная инициализация BME280 (если датчик недоступен), чтение показаний и передача пакета через nRF24L01+.
- Первое измерение выполняется сразу после инициализации перед входом в цикл.
---
## Примеры использования
### Типовой цикл работы (main)
```c
int main(void)
{
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();
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 =
(int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 =
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
transmitted_data.value_3 =
(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));
} else {
bme280_present = false;
}
}
for (;;)
{
sleep_enable();
sleep_cpu();
sleep_disable();
if (wdt_wake_count >= WDT_WAKE_COUNT) {
wdt_wake_count = 0;
if (bme280_present == false) {
bme280_present = zh_bme280_init();
}
if (bme280_present == true) {
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
transmitted_data.battery_voltage =
(int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 =
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
transmitted_data.value_3 =
(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));
} else {
bme280_present = false;
}
}
}
}
}
```
### Изменение конфигурации под другое устройство
```c
// Измените идентификатор для нового устройства
#define DEVICE_ID 7 // Уникальный ID в сети
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_RF_SENSOR_BME280
```
### Подключение по SPI и I2C (схема выводов)
```c
// Назначение пинов AVR:
// nRF24L01+ (SPI):
// PB2 -> CSN (управление SPI-селектом, выход)
// PB3 -> MOSI (назначается аппаратным SPI)
// PB5 -> SCK (назначается аппаратным SPI)
// PB1 -> CE (управление передачей, выход)
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC)
// BME280 (I2C, аппаратный TWI):
// PC4 -> SDA (аппаратный TWI, pull-up)
// PC5 -> SCL (аппаратный TWI, pull-up)
```
---
## Логирование
Логирование отсутствует.
---
## Ошибки
Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
| Условие | Поведение |
| --------- | ---------- |
| Нулевое показание АЦП | `zh_get_battery_voltage()` возвращает 0.0 |
| Неверный `CHIP_ID` BME280 | `zh_bme280_init()` возвращает false, флаг `bme280_present = false`, при следующем цикле предпринимается повторная попытка инициализации |
| Таймаут ожидания `TWINT` | `zh_i2c_wait()` возвращает false, операция TWI прерывается |
| Неверный статус TWI (NACK, ошибка шины) | `zh_i2c_start()` / `zh_i2c_write()` возвращают false, вызывающая функция выполняет `zh_i2c_stop()` |
| Таймаут при чтении TWI | `zh_i2c_read_ack()` / `zh_i2c_read_nack()` возвращают `0xFF` |
| Ошибка чтения BME280 | `zh_bme280_read()` возвращает false, пакет не формируется, `bme280_present = false` |
| `var1 == 0` при компенсации давления | `zh_bme280_compensate_pressure()` возвращает 0.0 |
| Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной |
---
## Ограничения
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
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 и %), дробная часть теряется.
---
## Требования для сборки
- PlatformIO с плагином `platform-atmelavr`
- AVR-GCC (входит в состав PlatformIO)
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
- Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
---
## Установка
### Сборка и прошивка через PlatformIO
```bash
pio run -e ATmega328P -t upload
```
### Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c`, добавьте зависимость `zh_rf_config` и адаптируйте макросы конфигурации.
```ini
; platformio.ini
lib_deps =
https://github.com/aZholtikov/zh_rf_config
```
```c
// Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h"
#include "util/delay.h"
#include "math.h"
#include "zh_rf_config.h"
```