Files
zh_avr_touch_pad/wiki_ru.md
T
2026-10-25 11:48:46 +03:00

21 KiB
Raw Blame History

Описание прошивки zh_avr_touch_pad

Общие сведения

zh_avr_touch_pad — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.

Прошивка работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. При первом запуске выполняется автоматическое сопряжение (pairing) со шлюзом для получения идентификатора устройства, который сохраняется в EEPROM.

Основные возможности

  1. Сверхнизкое энергопотребление — около 2 мкА в режиме сна.
  2. Передача уровня заряда батареи — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
  3. Автоматическое сопряжение со шлюзом — получение идентификатора устройства по радиоканалу с сохранением в EEPROM.
  4. Работа по протоколу nRF24L01+ — передача через SPI, адресная доставка на шлюз.
  5. Поддержка нескольких МК — ATMega168/168P/168PB/328/328P/328PB.
  6. Пробуждение по внешнему прерыванию INT1 — rising edge на пине PD3.
  7. Генерация псевдослучайного токена — на основе шума АЦП и состояния MCUSR для уникализации запроса сопряжения.
  8. Watchdog-таймер в режиме прерывания — используется для таймаутов при сопряжении без сброса МК.

Внутреннее устройство

Структура передаваемых данных

Прошивка не использует динамическое выделение памяти. Все данные хранятся в статической области и регистрах микроконтроллера.

Пакет формируется в глобальной переменной transmitted_data типа zh_nrf24_sensor_transmitted_data_t из внешней библиотеки zh_nrf24_config и сериализуется функцией zh_nrf24_sensor_build_payload() перед передачей.

Поле Тип Описание
id int16_t Идентификатор устройства, присвоенный шлюзом при сопряжении
type int16_t Тип устройства (ZH_NRF24_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. Синхронизация между прерываниями и главным циклом реализуется следующим образом:

  1. Флаги volatile — переменные send_request, nrf_event объявлены как static volatile uint8_t, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
  2. Атомарная запись из ISR — обработка прерывания устанавливает флаг в true. Запись одного байта на AVR является атомарной операцией.
  3. Отключение прерываний при проверке и сбросе флага — cli() перед проверкой и сбросом send_request в главном цикле исключает гонку между ISR и главным циклом.
  4. Отключение прерываний при инициализации — cli() перед настройкой периферии исключает гонки при старте.

Алгоритмы

Алгоритм сопряжения (pairing)

Конечный автомат zh_do_pairing() работает в четырёх состояниях:

  1. ZH_NRF24_PAIR_SEND — генерируется псевдослучайный токен, формируется пакет запроса (zh_nrf24_sensor_pairing_data_t с magic = ZH_NRF24_PAIRING_REQ_MAGIC), выполняется неблокирующая передача через zh_nrf24_send_no_wait().
  2. ZH_NRF24_PAIR_TX_WAIT — ожидание прерывания IRQ (завершение передачи). Если пин IRQ уже низкий — событие считается наступившим. Иначе — переход в сон с watchdog-таймером 250 мс. При флаге TX_DS — переход в приём, при MAX_RT — в backoff.
  3. ZH_NRF24_PAIR_RX_WAIT — ожидание ответа шлюза. При получении пакета проверяется: длина равна ZH_NRF24_PAIRING_PACKET_SIZE, magic == ZH_NRF24_PAIRING_ACK_MAGIC, тип совпадает, токен совпадает, id != 0 и id != DEVICE_ID_UNASSIGNED. При успехе — сохранение id в EEPROM и возврат.
  4. ZH_NRF24_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)

#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/interrupt.h"
#include "avr/wdt.h"
#include "math.h"
#include "stdbool.h"
#include "zh_nrf24_config.h"

static volatile uint8_t send_request = false;
zh_nrf24_sensor_transmitted_data_t transmitted_data = {0};

ISR(INT1_vect)
{
    send_request = true;
}

int main(void)
{
    transmitted_data.type = ZH_NRF24_SENSOR_TOUCH_SWITCH;
    MCUSR &= ~(1 << WDRF);
    wdt_disable();
    cli();
    zh_pad_interrupt_init();
    zh_spi_init();
    zh_nrf24_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;
    for (;;)
    {
        if (send_request == true)
        {
            cli();
            send_request = false;
            sei();
            transmitted_data.battery = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
            uint8_t payload[ZH_NRF24_MAX_PAYLOAD_SIZE];
            uint8_t size = zh_nrf24_sensor_build_payload(&transmitted_data, payload, sizeof(payload));
            if (size > 0)
            {
                zh_nrf24_send(payload, size);
            }
        }
        else
        {
            cli();
            if (send_request == false)
            {
                sleep_enable();
                sei();
                sleep_cpu();
            }
            sei();
            sleep_disable();
        }
    }
    return 0;
}

Назначение пинов AVR для nRF24L01+

// PB2 (SS)   -> CSN  (выбор кристалла SPI, активный низкий)
// PB3 (MOSI) -> MOSI
// PB4 (MISO) -> MISO
// PB5 (SCK)  -> SCK
// PB1        -> CE   (запуск передачи/приёма)
// PD2 (INT0) -> IRQ  (вход, активный низкий, с подтяжкой к VCC)
// PD3 (INT1) -> Выход сенсорного датчика (rising edge, без подтяжки)

Изменение конфигурации шлюза

// Измените адрес и канал под ваш шлюз
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_NRF24_SENSOR_TOUCH_SWITCH из zh_nrf24_config

Ошибки

Прошивка не возвращает коды ошибок в стиле ESP-IDF. Все функции имеют тип void или возвращают однобайтовый статус SPI (uint8_t) для низкоуровневых операций.

Условие Поведение
Нулевое показание АЦП zh_get_battery_voltage() возвращает 0.0
Превышение таймаута передачи (100 мс) Передача прерывается, пакет теряется
Неверная длина принятого пакета при сопряжении Пакет отбрасывается, переход в ZH_NRF24_PAIR_BACKOFF
Превышение числа попыток сопряжения (7) zh_enter_final_sleep() — сон до сброса
zh_nrf24_sensor_build_payload() возвращает 0 Передача пропускается, переход в zh_enter_final_sleep()

Ограничения

  1. Только передача (TX-only) в рабочем цикле — приём используется исключительно на этапе сопряжения, шлюз не может отправить данные устройству в штатном режиме.
  2. Отсутствие шифрования — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
  3. Жёстко зашитый адрес и канал шлюза — смена GATEWAY_ADDRESS или GATEWAY_CHANNEL требует перепрошивки.
  4. Таймаут блокирующей передачи 100 мс — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через SETUP_RETR = 0xFF: 15 попыток с максимальным интервалом).
  5. Одно сенсорное событие за цикл — несколько касаний во время передачи будут потеряны (флаг send_request — один бит).
  6. Точность измерения батареи — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
  7. SPI остаётся включённым в режиме сна — перед окончательным сном SPI отключается в zh_enter_final_sleep(), но в штатном режиме ожидания SPI не отключается между передачами.

Требования для сборки

  • PlatformIO с плагином platform-atmelavr
  • AVR-GCC (входит в состав PlatformIO)
  • Библиотека zh_nrf24_config (подключается через lib_deps в platformio.ini)
  • Программатор USBasp (или совместимый)
  • Фьюзы: LFUSE=0xE2, HFUSE=0xDF, EFUSE=0x07 (8 МГц, без делителя)

Установка

Сборка и прошивка через PlatformIO

git clone https://github.com/aZholtikov/zh_avr_touch_pad
cd zh_avr_touch_pad
pio run -e ATmega328P -t upload

Подключение в проекте

Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте src/main.c, добавьте зависимость zh_nrf24_config и адаптируйте макросы конфигурации.

; platformio.ini
lib_deps =
    https://github.com/aZholtikov/zh_nrf24_config

Подключение заголовков в коде

// Зависимости: стандартные заголовки AVR-libc и конфигурация протокола
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h"
#include "avr/eeprom.h"
#include "avr/wdt.h"
#include "util/delay.h"
#include "math.h"
#include "stdbool.h"
#include "zh_nrf24_config.h"