22 KiB
Описание компонента zh_avr_bmp280_sensor
Общие сведения
zh_avr_bmp280_sensor — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BMP280 и трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру и давление на датчике BMP280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
Основные возможности
- Сверхнизкое энергопотребление — около 5 мкА в режиме сна.
- Измерение климата — температура и атмосферное давление датчиком BMP280 по шине I2C.
- Передача уровня заряда батареи — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
- Работа по протоколу nRF24L01+ — передача через SPI, адресная доставка на шлюз.
- Поддержка нескольких МК — ATMega168/168P/168PB/328/328P/328PB.
- Пробуждение по watchdog-таймеру — WDT с интервалом ~8 секунд, цикл работы каждые
WDT_WAKE_COUNT(38) прерываний ≈ 5 минут. - Автономная работа — не требует внешних подтягивающих резисторов.
Внутреннее устройство
Структура данных
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив 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_BMP280) |
value_1 |
int16_t |
Температура (°C, например 25 = 25 °C) |
battery_voltage |
int16_t |
Напряжение батареи × 100 (например, 330 = 3.30 В) |
value_2 |
int16_t |
Давление (мм рт. ст.) |
value_3 |
int16_t |
Зарезервировано (равно 0) |
value_4 |
int16_t |
Зарезервировано (равно 0) |
Тип устройства задаётся значением перечисления ZH_RF_SENSOR_BMP280 из библиотеки 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 |
BMP280_ADDR |
0x76 |
7-битный адрес датчика BMP280 на шине I2C |
Управление памятью
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная transmitted_data объявлена как глобальная (не static) и заполняется при инициализации (идентификатор и тип). В цикле обновляются все поля данных: battery_voltage, value_1 (температура), value_2 (давление). Массив калибровки bmp280_calib размером 24 байта хранит калибровочные коэффициенты датчика.
API (интерфейс)
Все функции являются static и доступны только внутри модуля main.c.
Инициализация
| Функция | Описание |
|---|---|
zh_wdt_init() |
Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
zh_i2c_init() |
Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
zh_bmp280_init() |
Инициализация датчика BMP280: проверка CHIP_ID (0x58), сброс, чтение калибровки (24 байта) |
zh_spi_init() |
Инициализация аппаратного SPI в режиме мастера (Fosc/2) |
zh_nrf24_init() |
Инициализация трансивера nRF24L01+ в режим передачи |
Измерение
| Функция | Описание |
|---|---|
zh_get_battery_voltage() |
Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В (две конверсии, усреднение) |
Чтение данных BMP280
| Функция | Описание |
|---|---|
zh_bmp280_read() |
Чтение 6 сырых байт из BMP280 и распаковка ADC значений температуры и давления |
zh_bmp280_compensate_temperature() |
Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
zh_bmp280_compensate_pressure() |
Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
Передача данных
| Функция | Описание |
|---|---|
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-функции BMP280
| Функция | Описание |
|---|---|
zh_bmp280_write_reg() |
Запись одного байта в регистр BMP280 (start, адрес, регистр, данные, stop, задержка 2 мс) |
zh_bmp280_read_reg() |
Чтение нескольких байт из регистров BMP280 (repeated-start, ACK/NACK) |
Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (ISR(WDT_vect)), которая увеличивает wdt_wake_count (объявлен как static volatile uint8_t). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
Алгоритмы
Алгоритм инициализации BMP280
- Задержка 100 мс после включения.
- Проверка
CHIP_ID(регистрBMP280_REG_ID, ожидается0x58). - Сброс записью
0xB6в регистрBMP280_REG_RESET. - Задержка 10 мс после сброса.
- Чтение калибровочных коэффициентов: 24 байта из
BMP280_REG_CALIB00(массивbmp280_calib). - Конфигурация измерения:
ctrl_meas = 0x25(temp/pressure oversampling x16, forced mode).
Алгоритм компенсации BMP280
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BMP280.
- Температура:
zh_bmp280_compensate_temperature()— вычисляетbmp280_t_fine(используется для давления), возвращает результат в °C. Формула:((bmp280_t_fine * 5 + 128) >> 8) / 100.0. Деление наdig_t1не выполняется. - Давление:
zh_bmp280_compensate_pressure()— возвращает результат в Па. Приvar1 == 0(знаменатель ноль) возвращает 0.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 минут). - При достижении порога: обнуление счётчика, повторная инициализация BMP280 (если датчик недоступен), чтение показаний и передача пакета через nRF24L01+.
- Первое измерение выполняется сразу после инициализации перед входом в цикл.
Примеры использования
Типовой цикл работы (main)
int main(void)
{
cli();
zh_i2c_init();
bmp280_present = zh_bmp280_init();
zh_spi_init();
zh_nrf24_init();
zh_wdt_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();
int32_t adc_t = 0;
int32_t adc_p = 0;
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_present = false;
}
}
for (;;)
{
sleep_enable();
sleep_cpu();
sleep_disable();
if (wdt_wake_count >= WDT_WAKE_COUNT)
{
wdt_wake_count = 0;
if (bmp280_present == false)
{
bmp280_present = zh_bmp280_init();
}
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_present = false;
}
}
}
}
return 0;
}
Изменение конфигурации под другое устройство
// Измените идентификатор для нового устройства
#define DEVICE_ID 7 // Уникальный ID в сети
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_RF_SENSOR_BMP280
Подключение по SPI и I2C (схема выводов)
// Назначение пинов AVR:
// nRF24L01+ (SPI):
// PB2 -> CSN (управление SPI-селектом, выход)
// PB3 -> MOSI (назначается аппаратным SPI)
// PB5 -> SCK (назначается аппаратным SPI)
// PB1 -> CE (управление передачей, выход)
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC)
// BMP280 (I2C, аппаратный TWI):
// PC4 -> SDA (аппаратный TWI, pull-up)
// PC5 -> SCL (аппаратный TWI, pull-up)
Логирование
Логирование отсутствует.
Ошибки
Компонент не возвращает коды ошибок. Функции zh_bmp280_init, zh_bmp280_read, zh_bmp280_write_reg, zh_bmp280_read_reg возвращают bool (true при успехе). Остальные функции имеют тип void или возвращают однобайтовый статус SPI (uint8_t).
| Условие | Поведение |
|---|---|
| Нулевое показание АЦП | zh_get_battery_voltage() возвращает 0.0 |
Неверный CHIP_ID BMP280 |
zh_bmp280_init() возвращает false, флаг bmp280_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 |
| Ошибка чтения BMP280 | zh_bmp280_read() возвращает false, пакет не формируется, bmp280_present = false |
var1 == 0 при компенсации давления |
zh_bmp280_compensate_pressure() возвращает 0.0 |
| Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной |
Ограничения
- Только передача (TX-only) — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
- Отсутствие шифрования — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
- Жёстко зашитый адрес шлюза — смена адреса требует перепрошивки.
- Таймаут передачи ~100 мс — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (
SETUP_RETR = 0xFF: 15 попыток с интервалом до 500 мс) включены. - Отсутствие валидации
dig_t1— вzh_bmp280_compensate_temperature()нет проверкиdig_t1 == 0, что может привести к делению на ноль. - Точность измерения батареи — зависит от стабильности внутреннего опорного напряжения 1,1 В.
- Программные задержки в TWI — обмен с BMP280 выполняется на аппаратном TWI, но ожидание
TWINTреализовано поллингом с таймаутом; прерывания TWI не используются. - Задержка между измерениями ~5 минут — определяется произведением
WDT_WAKE_COUNT(38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся. - Давление в мм рт. ст. —
zh_bmp280_compensate_pressure()возвращает давление в Паскалях, для перевода в пакет умножается на0.00750062(коэффициент пересчёта Па → мм рт. ст.). - Температура и давление без масштабирования — значения передаются как целые числа (°C и мм рт. ст.), дробная часть теряется.
Требования для сборки
- PlatformIO с плагином
platform-atmelavr - AVR-GCC (входит в состав PlatformIO)
- Библиотека
zh_rf_config(подключается черезlib_depsвplatformio.ini) - Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=
0xE2, HFUSE=0xDF, EFUSE=0x07(8 МГц, без делителя)
Установка
Сборка и прошивка через PlatformIO
pio run -e ATmega328P -t upload
Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте src/main.c, добавьте зависимость zh_rf_config и адаптируйте макросы конфигурации.
; platformio.ini
lib_deps =
https://github.com/aZholtikov/zh_rf_config
// Зависимости: стандартные заголовки 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"