Описание прошивки zh_avr_touch_pad
Общие сведения
zh_avr_touch_pad — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.
Прошивка работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. При первом запуске выполняется автоматическое сопряжение (pairing) со шлюзом для получения идентификатора устройства, который сохраняется в EEPROM.
Основные возможности
- Сверхнизкое энергопотребление — около 2 мкА в режиме сна.
- Передача уровня заряда батареи — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
- Автоматическое сопряжение со шлюзом — получение идентификатора устройства по радиоканалу с сохранением в EEPROM.
- Работа по протоколу nRF24L01+ — передача через SPI, адресная доставка на шлюз.
- Поддержка нескольких МК — ATMega168/168P/168PB/328/328P/328PB.
- Пробуждение по внешнему прерыванию INT1 — rising edge на пине PD3.
- Генерация псевдослучайного токена — на основе шума АЦП и состояния MCUSR для уникализации запроса сопряжения.
- Watchdog-таймер в режиме прерывания — используется для таймаутов при сопряжении без сброса МК.
Внутреннее устройство
Структура передаваемых данных
Прошивка не использует динамическое выделение памяти. Все данные хранятся в статической области и регистрах микроконтроллера.
Пакет формируется в глобальной переменной transmitted_data типа zh_rf_sensor_transmitted_data_t из внешней библиотеки zh_rf_config и сериализуется функцией zh_rf_sensor_build_payload() перед передачей.
| Поле |
Тип |
Описание |
id |
int16_t |
Идентификатор устройства, присвоенный шлюзом при сопряжении |
type |
int16_t |
Тип устройства (ZH_RF_SENSOR_TOUCH_SWITCH) |
battery |
int16_t |
Напряжение батареи × 100 (например, 330 = 3.30 В) |
Конфигурационные макросы
| Макрос |
Значение |
Описание |
GATEWAY_CHANNEL |
120 |
Радиоканал шлюза |
GATEWAY_ADDRESS |
0xDDEEFF |
3-байтовый адрес шлюза |
EEPROM_DEVICE_ID_ADDR |
((uint8_t *)0x00) |
Адрес в EEPROM для хранения идентификатора устройства |
DEVICE_ID_UNASSIGNED |
0xFF |
Значение, обозначающее отсутствие сохранённого идентификатора |
PAIRING_TIMEOUT_ATTEMPTS |
7 |
Максимальное число попыток сопряжения перед уходом в окончательный сон |
WDT_T_250MS |
0x04 |
Таймаут watchdog-таймера 250 мс |
WDT_T_4S |
0x08 |
Таймаут watchdog-таймера 4 с |
Управление памятью
Прошивка не использует динамическую память. Глобальная переменная transmitted_data заполняется при старте (идентификатор и тип), в рабочем цикле обновляется только поле battery. Флаги send_request, nrf_event объявлены как static volatile uint8_t для корректного доступа из ISR и главного цикла.
Флаги синхронизации
| Флаг |
Устанавливается |
Используется |
send_request |
ISR INT1_vect (касание) |
Главный цикл — запуск передачи |
nrf_event |
ISR INT0_vect (IRQ nRF24) |
Конечный автомат сопряжения |
API (интерфейс)
Все функции являются static и доступны только внутри модуля main.c.
Инициализация
| Функция |
Описание |
zh_pad_interrupt_init() |
Настройка внешнего прерывания INT1 (PD3, rising edge) для пробуждения по касанию |
zh_spi_init() |
Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
zh_nrf24_init() |
Инициализация трансивера nRF24L01+ в режим передачи |
zh_nrf24_interrupt_init() |
Настройка внешнего прерывания INT0 (PD2, falling edge) для сигнала IRQ nRF24 |
Работа с EEPROM
| Функция |
Описание |
zh_load_device_id() |
Чтение идентификатора устройства из EEPROM, возврат DEVICE_ID_UNASSIGNED если не сохранён |
zh_save_device_id() |
Сохранение идентификатора устройства в EEPROM |
Измерение
| Функция |
Описание |
zh_get_battery_voltage() |
Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В, возврат значения в вольтах |
zh_get_random_token() |
Генерация 16-битного псевдослучайного токена из шума АЦП и MCUSR для запроса сопряжения |
Передача данных
| Функция |
Описание |
zh_nrf24_send() |
Блокирующая передача буфера через nRF24L01+ с ожиданием завершения по пину IRQ (таймаут 100 мс) |
zh_nrf24_send_no_wait() |
Неблокирующая передача — загружает буфер, импульс CE 15 мкс, завершение по прерыванию IRQ |
Управление режимами nRF24L01+
| Функция |
Описание |
zh_nrf24_enter_rx() |
Переключение nRF24L01+ в режим приёма на трубе 0 |
zh_nrf24_enter_power_down() |
Перевод nRF24L01+ в режим Power Down |
Низкоуровневые SPI-операции
| Функция |
Описание |
zh_spi_transfer() |
Обмен одного байта по SPI (полный дуплекс) |
zh_nrf24_send_command() |
Отправка однобайтовой команды и чтение регистра статуса |
zh_nrf24_write_register() |
Запись одного байта в регистр nRF24L01+ |
zh_nrf24_write_buff_register() |
Запись многобайтового буфера в регистр nRF24L01+ |
zh_nrf24_write_buffer() |
Запись буфера данных с указанной SPI-командой |
zh_nrf24_read_payload() |
Чтение принятого пакета из RX FIFO nRF24 в буфер |
zh_nrf24_read_payload_width() |
Чтение длины пакета в RX FIFO nRF24 |
Watchdog-таймер
| Функция |
Описание |
zh_wdt_start() |
Запуск watchdog-таймера в режиме прерывания с указанным таймаутом |
zh_wdt_stop() |
Остановка watchdog-таймера |
Сон и сопряжение
| Функция |
Описание |
zh_do_pairing() |
Запуск конечного автомата сопряжения со шлюзом для получения идентификатора |
zh_enter_final_sleep() |
Переход в окончательный Power-down сон до сброса: отключение всех прерываний, watchdog, радиомодуля, АЦП, SPI и неиспользуемых периферийных блоков |
Потокобезопасность
Прошивка работает на bare-metal AVR без RTOS. Синхронизация между прерываниями и главным циклом реализуется следующим образом:
- Флаги
volatile — переменные send_request, nrf_event объявлены как static volatile uint8_t, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
- Атомарная запись из ISR — обработка прерывания устанавливает флаг в
true. Запись одного байта на AVR является атомарной операцией.
- Отключение прерываний при проверке и сбросе флага —
cli() перед проверкой и сбросом send_request в главном цикле исключает гонку между ISR и главным циклом.
- Отключение прерываний при инициализации —
cli() перед настройкой периферии исключает гонки при старте.
Алгоритмы
Алгоритм сопряжения (pairing)
Конечный автомат zh_do_pairing() работает в четырёх состояниях:
ZH_RF_PAIR_SEND — генерируется псевдослучайный токен, формируется пакет запроса (zh_rf_sensor_pairing_data_t с magic = ZH_RF_PAIRING_REQ_MAGIC), выполняется неблокирующая передача через zh_nrf24_send_no_wait().
ZH_RF_PAIR_TX_WAIT — ожидание прерывания IRQ (завершение передачи). Если пин IRQ уже низкий — событие считается наступившим. Иначе — переход в сон с watchdog-таймером 250 мс. При флаге TX_DS — переход в приём, при MAX_RT — в backoff.
ZH_RF_PAIR_RX_WAIT — ожидание ответа шлюза. При получении пакета проверяется: длина равна ZH_RF_PAIRING_PACKET_SIZE, magic == ZH_RF_PAIRING_ACK_MAGIC, тип совпадает, токен совпадает, id != 0 и id != DEVICE_ID_UNASSIGNED. При успехе — сохранение id в EEPROM и возврат.
ZH_RF_PAIR_BACKOFF — переход в Power Down, ожидание 4 с через watchdog. После PAIRING_TIMEOUT_ATTEMPTS (7) неудачных попыток — переход в zh_enter_final_sleep().
Алгоритм измерения напряжения батареи
- АЦП конфигурируется с внутренним опорным напряжением 1,1 В на канале внутреннего источника 1,1 В (
REFS0=1, MUX[3:1]=111), измеряется отношение AVcc к опоре 1,1 В.
- Предделитель частоты: 64 (
ADPS[2:0] = 111), что даёт ~125 кГц при 8 МГц.
- Выполняются две последовательные конверсии: первая стабилизирует выборку, вторая даёт результат.
- Формула расчёта:
Vbat = (1024.0 × 1.1) / adc_value.
- При нулевой выборке (
adc == 0) возвращается 0.0.
- После измерения АЦП отключается сбросом бита
ADEN.
Алгоритм генерации псевдослучайного токена
- Инициальное значение — регистр
MCUSR (состояние сброса).
- АЦП запускается на 16 конверсий в канале с внутренним опорным напряжением.
- На каждой итерации:
token = (token << 1) ^ (token >> 15) ^ ADC — сдвиг с переносом и XOR с шумом АЦП.
- Если результат равен 0, возвращается константа
0xACE1.
Алгоритм блокирующей передачи через nRF24L01+
- Переход в Power Up (бит
PWR_UP в CONFIG).
- Ожидание 5 мс (время стартования осциллятора).
- Очистка TX FIFO командой
FLUSH_TX.
- Загрузка полезной нагрузки командой
W_TX_PAYLOAD.
- Установка CE в 1 для запуска передачи.
- Ожидание спада пина IRQ (PD2) с таймаутом 100 мс (проверка каждые 1 мс).
- Сброс CE, чтение статуса, сброс флагов, очистка FIFO, переход в Power Down.
Алгоритм сна и пробуждения
- Главный цикл проверяет флаг
send_request.
- Если флаг не установлен — вызывается
sleep_cpu() (режим Power-down).
- Пробуждение происходит по аппаратному прерыванию INT1 (rising edge на PD3).
- ISR устанавливает
send_request = true, цикл просыпается и выполняет передачу.
Примеры использования
Типовой цикл работы (main)
Назначение пинов AVR для nRF24L01+
Изменение конфигурации шлюза
Ошибки
Прошивка не возвращает коды ошибок в стиле ESP-IDF. Все функции имеют тип void или возвращают однобайтовый статус SPI (uint8_t) для низкоуровневых операций.
| Условие |
Поведение |
| Нулевое показание АЦП |
zh_get_battery_voltage() возвращает 0.0 |
| Превышение таймаута передачи (100 мс) |
Передача прерывается, пакет теряется |
| Неверная длина принятого пакета при сопряжении |
Пакет отбрасывается, переход в ZH_RF_PAIR_BACKOFF |
| Превышение числа попыток сопряжения (7) |
zh_enter_final_sleep() — сон до сброса |
zh_rf_sensor_build_payload() возвращает 0 |
Передача пропускается, переход в zh_enter_final_sleep() |
Ограничения
- Только передача (TX-only) в рабочем цикле — приём используется исключительно на этапе сопряжения, шлюз не может отправить данные устройству в штатном режиме.
- Отсутствие шифрования — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
- Жёстко зашитый адрес и канал шлюза — смена
GATEWAY_ADDRESS или GATEWAY_CHANNEL требует перепрошивки.
- Таймаут блокирующей передачи 100 мс — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через
SETUP_RETR = 0xFF: 15 попыток с максимальным интервалом).
- Одно сенсорное событие за цикл — несколько касаний во время передачи будут потеряны (флаг
send_request — один бит).
- Точность измерения батареи — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
- SPI остаётся включённым в режиме сна — перед окончательным сном SPI отключается в
zh_enter_final_sleep(), но в штатном режиме ожидания SPI не отключается между передачами.
Требования для сборки
- PlatformIO с плагином
platform-atmelavr
- AVR-GCC (входит в состав PlatformIO)
- Библиотека
zh_rf_config (подключается через lib_deps в platformio.ini)
- Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=
0xE2, HFUSE=0xDF, EFUSE=0x07 (8 МГц, без делителя)
Установка
Сборка и прошивка через PlatformIO
Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте src/main.c, добавьте зависимость zh_rf_config и адаптируйте макросы конфигурации.
Подключение заголовков в коде