From 04a96b837ac889690d4c3d7e5e362117d191552e Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Tue, 20 Oct 2026 10:08:17 +0300 Subject: [PATCH] perf: reworked sleep logic --- README.md | 34 +++++-- platformio.ini | 83 ++++++++++++++++ src/main.c | 262 ++++++++++++++++++++++++++++++++++++++++--------- wiki_ru.md | 229 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 555 insertions(+), 53 deletions(-) create mode 100644 platformio.ini create mode 100644 wiki_ru.md diff --git a/README.md b/README.md index 4392b9b..61a44e5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,33 @@ -# Touch pad on ATMega 8/48/168/328 + nRF24L01 +# AVR firmware for wireless touch pad (nRF24L01+) ## Features -1. Power consumption in the sleep mode approximately 2 μA. -2. When triggered transmits battery level charge. -3. Easy installation into any standard touch pad enclosure. +1. Wireless touch pad sensor for ATMega 168/328 microcontrollers with nRF24L01+ transceiver. +2. Power consumption in the sleep mode approximately 2 uA. +3. Transmits device identifier and battery voltage level on each touch event. +4. Battery measurement through internal ADC with 1.1 V bandgap reference. +5. Wake-up from power-down mode by external interrupt on falling edge. +6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB. +7. Easy installation into any standard touch pad enclosure. -## Note +## Attention -A gateway is required. For details see [RF Gateway](http://git.zh.com.ru/alexey.zholtikov/RF-Gateway). +1. A compatible RF Gateway is required to receive and process transmitted packets. +2. After power-on reset the first touch event may not trigger a transmission. +3. Multi-touch is not supported, only a single touch event per transmission cycle. +4. Changing gateway address or channel requires reflashing the microcontroller. + +## Using + +In an existing PlatformIO project, run the following command to install the firmware: + +```bash +cd your_project +git clone https://github.com/aZholtikov/zh_avr_touch_pad +``` + +In the `platformio.ini`, select the target microcontroller environment and upload: + +```bash +pio run -e ATmega328P -t upload +``` diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..8630d6a --- /dev/null +++ b/platformio.ini @@ -0,0 +1,83 @@ +[env:ATmega168] +platform = atmelavr +board = ATmega168 +board_build.mcu = atmega168 +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e + +[env:ATmega168P] +platform = atmelavr +board = ATmega168P +board_build.mcu = atmega168p +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e + +[env:ATmega168PB] +platform = atmelavr +board = ATmega168PB +board_build.mcu = atmega168pb +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e + +[env:ATmega328] +platform = atmelavr +board = ATmega328 +board_build.mcu = atmega328 +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e + +[env:ATmega328P] +platform = atmelavr +board = ATmega328P +board_build.mcu = atmega328p +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e + +[env:ATmega328PB] +platform = atmelavr +board = ATmega328PB +board_build.mcu = atmega328pb +board_build.f_cpu = 8000000L +framework = arduino +upload_protocol = usbasp +board_fuses.lfuse = 0xE2 +board_fuses.hfuse = 0xDF +board_fuses.efuse = 0x07 +upload_flags = + -Pusb + -e \ No newline at end of file diff --git a/src/main.c b/src/main.c index 9e522d7..1ffb5c7 100644 --- a/src/main.c +++ b/src/main.c @@ -4,23 +4,165 @@ #include "avr/interrupt.h" #include "util/delay.h" -FUSES = {0xE2, 0xDF, 0x07}; +#if defined(__AVR_ATmega328PB__) +#ifndef SPCR +#define SPCR SPCR0 +#endif +#ifndef SPDR +#define SPDR SPDR0 +#endif +#ifndef SPSR +#define SPSR SPSR0 +#endif +#ifndef SPE +#define SPE SPE0 +#endif +#ifndef MSTR +#define MSTR MSTR0 +#endif +#ifndef SPR0 +#define SPR0 SPR00 +#endif +#ifndef SPIF +#define SPIF SPIF0 +#endif +#endif #define GATEWAY_CHANNEL 120 #define GATEWAY_ADDRESS 0xDDEEFF #define DEVICE_ID 6 #define DEVICE_TYPE 4 +#define NRF_CMD_W_REGISTER 0x20 +#define NRF_CMD_W_TX_PAYLOAD 0xA0 +#define NRF_CMD_FLUSH_TX 0xE1 +#define NRF_CMD_NOP 0xFF + +#define NRF_REG_CONFIG 0x00 +#define NRF_REG_EN_AA 0x01 +#define NRF_REG_EN_RXADDR 0x02 +#define NRF_REG_SETUP_AW 0x03 +#define NRF_REG_SETUP_RETR 0x04 +#define NRF_REG_RF_CH 0x05 +#define NRF_REG_RF_SETUP 0x06 +#define NRF_REG_STATUS 0x07 +#define NRF_REG_RX_ADDR_P0 0x0A +#define NRF_REG_TX_ADDR 0x10 + +#define NRF_CONFIG_MASK_RX_DR 0x40 +#define NRF_CONFIG_EN_CRC 0x08 +#define NRF_CONFIG_PWR_UP 0x02 + +#define NRF_STATUS_RX_DR 0x40 +#define NRF_STATUS_TX_DS 0x20 +#define NRF_STATUS_MAX_RT 0x10 + +/** + * @brief Initialize INT1 external interrupt for the touch-pad wake-up signal. + * + * Configures PD3 as a falling-edge triggered external interrupt source and + * enables the INT1 interrupt mask bit. The ISR sets the send_request flag + * to trigger a radio transmission from the main loop. + */ static void zh_pad_interrupt_init(void); + +/** + * @brief Initialize the hardware SPI peripheral in master mode. + * + * Configures MOSI, SCK and SS pins as outputs, drives SS high, and enables + * SPI with Fosc/16 clock rate. + */ static void zh_spi_init(void); + +/** + * @brief Perform a single full-duplex SPI byte transfer. + * + * @param data Byte to transmit + * + * @return Byte received from the slave during the transfer + */ static uint8_t zh_spi_transfer(uint8_t data); + +/** + * @brief Initialize the nRF24L01+ transceiver for TX-only operation. + * + * Drives CE low, configures auto-acknowledgment, address width, retransmit + * settings, channel, RF power and data rate, and loads the gateway address + * into both TX and RX pipe 0 registers. + */ static void zh_nrf24_init(void); + +/** + * @brief Transmit a payload buffer through the nRF24L01+ module. + * + * Powers up the radio, flushes the TX FIFO, loads the payload, pulses CE to + * start transmission, waits for the TX_DS or MAX_RT flag, then clears status + * flags and returns the radio to power-down mode. + * + * @param buf Pointer to the payload data to transmit (must not be NULL) + * @param size Number of bytes in the payload buffer (must be > 0) + */ static void zh_nrf24_send(uint8_t *buf, uint8_t size); + +/** + * @brief Write a data buffer to the nRF24L01+ via SPI with a given command. + * + * Asserts CSN low, sends the command byte followed by the buffer contents, + * then deasserts CSN. + * + * @param cmd SPI command byte to send before the data + * @param buf Pointer to the data buffer to write (must not be NULL) + * @param count Number of bytes to transfer from the buffer + * + * @return Status register value returned by the nRF24L01+ + */ static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count); + +/** + * @brief Write a single byte value to an nRF24L01+ register. + * + * @param reg Register address to write to + * @param val Byte value to store in the register + * + * @return Status register value returned by the nRF24L01+ + */ static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val); + +/** + * @brief Write a multi-byte buffer into an nRF24L01+ register. + * + * Convenience wrapper around zh_nrf24_write_buffer that prepends the + * W_REGISTER command to the register address. + * + * @param reg Register address to write to + * @param buf Pointer to the data buffer to write (must not be NULL) + * @param count Number of bytes to transfer from the buffer + * + * @return Status register value returned by the nRF24L01+ + */ static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count); + +/** + * @brief Send a single-byte command to the nRF24L01+ and read the status. + * + * @param cmd Command byte to send + * + * @return Status register value returned by the nRF24L01+ + */ static uint8_t zh_nrf24_send_command(uint8_t cmd); -static float zh_get_battery_level_charge(void); + +/** + * @brief Measure the battery voltage using the internal AVcc reference. + * + * Enables the ADC with the 1.1 V internal reference on the 1.1 V supply + * channel, performs two conversions and returns the calculated battery + * voltage based on the AVcc-to-internal-reference ratio. + * + * @return Battery voltage in volts, or 0.0 if the ADC reading is zero + */ +static float zh_get_battery_voltage(void); + +static volatile uint8_t send_request = 0; int main(void) { @@ -33,30 +175,40 @@ int main(void) sei(); for (;;) { - sleep_cpu(); + if (send_request == 1) + { + send_request = 0; + int16_t data[] = {DEVICE_ID, DEVICE_TYPE, (int16_t)(zh_get_battery_voltage() * 100.0), 0, 0, 0, 0}; + zh_nrf24_send((uint8_t *)data, sizeof(data)); + } + else + { + sleep_cpu(); + } } return 0; } ISR(INT1_vect) { - cli(); - int16_t data[] = {DEVICE_ID, DEVICE_TYPE, (zh_get_battery_level_charge() * 100), 0, 0, 0, 0}; - zh_nrf24_send((uint8_t *)&data, sizeof(data)); - sei(); + send_request = 1; } static void zh_pad_interrupt_init(void) { - EICRA |= ((1 << ISC11) | (1 << ISC10)); + DDRD &= ~(1 << PD3); + PORTD &= ~(1 << PD3); + EICRA &= ~((1 << ISC11) | (1 << ISC10)); + EICRA |= (1 << ISC11) | (1 << ISC10); + EIFR |= (1 << INTF1); EIMSK |= (1 << INT1); } static void zh_spi_init(void) { - DDRB |= ((1 << PORTB2) | (1 << PORTB3) | (1 << PORTB5)); - PORTB |= (1 << PORTB2); - SPCR |= ((1 << SPE) | (1 << MSTR)); + DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5); + PORTB |= (1 << PB2); + SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); } static uint8_t zh_spi_transfer(uint8_t data) @@ -70,86 +222,102 @@ static uint8_t zh_spi_transfer(uint8_t data) static void zh_nrf24_init(void) { - uint64_t gateway_address = GATEWAY_ADDRESS; - DDRB |= (1 << PORTB1); - PORTB &= ~(1 << PORTB1); - zh_nrf24_write_register(0x00, 0x48); - zh_nrf24_write_register(0x01, 0x01); - zh_nrf24_write_register(0x02, 0x01); - zh_nrf24_write_register(0x03, 0x01); - zh_nrf24_write_register(0x04, 0xFF); - zh_nrf24_write_register(0x05, GATEWAY_CHANNEL); - zh_nrf24_write_register(0x06, 0x26); - zh_nrf24_write_buff_register(0x0A, (uint8_t *)&gateway_address, 3); - zh_nrf24_write_buff_register(0x10, (uint8_t *)&gateway_address, 3); - PORTB &= ~(1 << PORTB2); + _delay_ms(100); + DDRB |= (1 << PB1); + PORTB &= ~(1 << PB1); + uint8_t gateway_address[3] = { + (uint8_t)((GATEWAY_ADDRESS >> 16) & 0xFF), + (uint8_t)((GATEWAY_ADDRESS >> 8) & 0xFF), + (uint8_t)(GATEWAY_ADDRESS & 0xFF)}; + zh_nrf24_write_register(NRF_REG_CONFIG, 0x00); + zh_nrf24_write_register(NRF_REG_EN_AA, 0x01); + zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01); + zh_nrf24_write_register(NRF_REG_SETUP_AW, 0x01); + zh_nrf24_write_register(NRF_REG_SETUP_RETR, 0xFF); + zh_nrf24_write_register(NRF_REG_RF_CH, GATEWAY_CHANNEL); + zh_nrf24_write_register(NRF_REG_RF_SETUP, 0x26); + zh_nrf24_write_buff_register(NRF_REG_TX_ADDR, gateway_address, 3); + zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, gateway_address, 3); + zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT); + zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC); } static void zh_nrf24_send(uint8_t *buf, uint8_t size) { - zh_nrf24_write_register(0x00, 0x4A); + zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP); _delay_ms(5); - zh_nrf24_send_command(0xE1); - zh_nrf24_write_buffer(0xA0, buf, size); - PORTB |= (1 << PORTB1); + zh_nrf24_send_command(NRF_CMD_FLUSH_TX); + zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size); + PORTB |= (1 << PB1); uint8_t count = 0; - while ((PIND & (1 << PIND2)) != 0) + while ((PIND & (1 << PD2)) != 0) { - if (++count > 100) + if (++count > 50) { break; } _delay_ms(1); } - zh_nrf24_write_register(0x07, zh_nrf24_send_command(0xFF)); - zh_nrf24_send_command(0xE1); - zh_nrf24_write_register(0x00, 0x48); - PORTB &= ~((1 << PORTB1) | (1 << PORTB2)); + PORTB &= ~(1 << PB1); + uint8_t status = zh_nrf24_send_command(NRF_CMD_NOP); + zh_nrf24_write_register(NRF_REG_STATUS, status); + zh_nrf24_send_command(NRF_CMD_FLUSH_TX); + zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC); } static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count) { - PORTB &= ~(1 << PORTB2); + PORTB &= ~(1 << PB2); uint8_t status = zh_spi_transfer(cmd); while (count-- != 0) { zh_spi_transfer(*(buf++)); } - PORTB |= (1 << PORTB2); + PORTB |= (1 << PB2); return status; } static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val) { - PORTB &= ~(1 << PORTB2); - uint8_t status = zh_spi_transfer(reg | 0x20); + PORTB &= ~(1 << PB2); + uint8_t status = zh_spi_transfer(reg | NRF_CMD_W_REGISTER); zh_spi_transfer(val); - PORTB |= (1 << PORTB2); + PORTB |= (1 << PB2); return status; } static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count) { - return zh_nrf24_write_buffer((reg | 0x20), buf, count); + return zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count); } static uint8_t zh_nrf24_send_command(uint8_t cmd) { - PORTB &= ~(1 << PORTB2); + PORTB &= ~(1 << PB2); uint8_t status = zh_spi_transfer(cmd); - PORTB |= (1 << PORTB2); + PORTB |= (1 << PB2); return status; } -static float zh_get_battery_level_charge(void) +static float zh_get_battery_voltage(void) { - ADMUX = ((1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1)); - ADCSRA |= (1 << ADEN); + ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1); + ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); _delay_ms(10); ADCSRA |= (1 << ADSC); - while ((ADCSRA & (1 << ADSC)) != 0) + while (ADCSRA & (1 << ADSC)) { } + ADCSRA |= (1 << ADSC); + while (ADCSRA & (1 << ADSC)) + { + } + uint16_t adc = ADCL; + adc |= (uint16_t)ADCH << 8; ADCSRA &= ~(1 << ADEN); - return ((1024 * 1.1) / (ADCL + (ADCH * 256))); + if (adc == 0) + { + return 0.0; + } + return (1024.0 * 1.1) / (float)adc; } \ No newline at end of file diff --git a/wiki_ru.md b/wiki_ru.md new file mode 100644 index 0000000..ac07327 --- /dev/null +++ b/wiki_ru.md @@ -0,0 +1,229 @@ +# Описание компонента zh_avr_touch_pad + +## Общие сведения + +**zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+. + +Компонент работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. + +### Основные возможности + +1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна. +2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В. +3. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз. +4. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB. +5. **Минимальная обвязка** — установка в стандартный корпус сенсорного выключателя. +6. **Пробуждение по внешнему прерыванию INT1** — falling edge на пине PD3. +7. **Автономная работа** — не требует внешних подтягивающих резисторов. + +--- + +## Внутреннее устройство + +### Структура данных + +Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины. + +#### Формат передаваемого пакета + +| Поле | Тип | Описание | +|------|-----|----------| +| `data[0]` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) | +| `data[1]` | `int16_t` | Тип устройства (`DEVICE_TYPE = 4`) | +| `data[2]` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) | +| `data[3]`–`data[6]` | `int16_t` | Зарезервированы (равны 0) | + +### Конфигурационные макросы + +| Макрос | Значение | Описание | +|--------|----------|----------| +| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза | +| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза | +| `DEVICE_ID` | `6` | Уникальный идентификатор устройства | +| `DEVICE_TYPE` | `4` | Код типа устройства (touch pad) | + +### Управление памятью + +Компонент не использует динамическую память. Все данные хранятся в стеке и регистрах микроконтроллера. Флаг `send_request` объявлен как `static volatile uint8_t` для корректного доступа из ISR и главного цикла. + +--- + +## API (интерфейс) + +Все функции являются `static` и доступны только внутри модуля `main.c`. + +### Инициализация + +| Функция | Описание | +|---------|----------| +| `zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, falling edge) для пробуждения по касанию | +| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) | +| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи | + +### Измерение + +| Функция | Описание | +|---------|----------| +| `zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В | + +### Передача данных + +| Функция | Описание | +|---------|----------| +| `zh_nrf24_send()` | Передача буфера через nRF24L01+ с полным циклом TX | + +### Низкоуровневые SPI-операции + +| Функция | Описание | +|---------|----------| +| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) | +| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ | +| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ | +| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой | +| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса | + +--- + +## Потокобезопасность + +Компонент работает на bare-metal AVR без RTOS. Синхронизация между прерыванием и главным циклом реализуется следующим образом: + +1. **`volatile` флаг** — переменная `send_request` объявлена как `volatile`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле. +2. **Атомарная запись из ISR** — обработка INT1 устанавливает флаг в единицу. Запись одного байта на AVR является атомарной операцией. +3. **Отключение прерываний при инициализации** — `cli()` перед настройкой периферии исключает гонки при старте. + +--- + +## Алгоритмы + +### Алгоритм измерения напряжения батареи + +- ADC конфигурируется с внутренним опорным напряжением 1,1 В на канале AVcc (MUX[3:1] = 111). +- Предделитель частоты: 64 (ADPS[2:0] = 111), что даёт ~125 кГц при 8 МГц. +- Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат. +- Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`. +- При обрыве датчика (`adc == 0`) возвращается 0.0. + +### Алгоритм передачи через nRF24L01+ + +- Переход в Power Up (бит `PWR_UP` в CONFIG). +- Ожидание 5 мс (время стартования осциллятора). +- Очистка TX FIFO командой `FLUSH_TX`. +- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`. +- Импульс CE (установка в 1) для запуска передачи. +- Ожидание завершения по пину IRQ (PD2) с таймаутом 50 мс. +- Сброс флагов статуса, очистка FIFO, переход в Power Down. + +### Алгоритм сна и пробуждения + +- Главный цикл проверяет флаг `send_request`. +- Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down). +- Пробуждение происходит по аппаратному прерыванию INT1 (falling edge на PD3). +- ISR устанавливает `send_request = 1`, цикл просыпается и выполняет передачу. + +--- + +## Примеры использования + +### Типовой цикл работы (main) + +```c +int main(void) +{ + // Отключаем прерывания на время инициализации + cli(); + // Настраиваем периферию + zh_pad_interrupt_init(); + zh_spi_init(); + zh_nrf24_init(); + // Конфигурируем режим сна Power-down + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + sleep_enable(); + // Включаем прерывания + sei(); + + for (;;) + { + if (send_request == 1) + { + // Собираем и отправляем пакет + send_request = 0; + int16_t data[] = {DEVICE_ID, DEVICE_TYPE, + (int16_t)(zh_get_battery_voltage() * 100.0), + 0, 0, 0, 0}; + zh_nrf24_send((uint8_t *)data, sizeof(data)); + } + else + { + // Спим до следующего прерывания + sleep_cpu(); + } + } +} +``` + +### Изменение конфигурации под другое устройство + +```c +// Измените идентификатор и тип для нового устройства +#define DEVICE_ID 7 // Уникальный ID в сети +#define DEVICE_TYPE 4 // 4 = touch pad +#define GATEWAY_CHANNEL 120 +#define GATEWAY_ADDRESS 0xDDEEFF +``` + +### Подключение по SPI (схема выводов) + +```c +// Назначение пинов AVR для nRF24L01+: +// PB2 (SS) -> CE (через резистор, или напрямую) +// PB3 (MOSI) -> MOSI +// PB5 (SCK) -> SCK +// PB1 -> CE (управление передачей) +// PD2 -> IRQ (вход, активный низкий) +// PD3 -> INT1 (вход сенсорного датчика) +``` + +--- + +## Ограничения + +1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения. +2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены. +3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки. +4. **Таймаут передачи 50 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом). +5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг — один бит). +6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%. +7. **Нет механизма глубокого сна для периферии** — АЦП полностью отключается после измерения (`ADEN = 0`), но SPI остаётся включённым в режиме сна. + +--- + +## Требования для сборки + +- PlatformIO с плагином `platform-atmelavr` +- AVR-GCC (входит в состав PlatformIO) +- Программатор USBasp (или совместимый) +- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя) + +--- + +## Установка + +### Сборка и прошивка через PlatformIO + +```bash +pio run -e ATmega328P -t upload +``` + +### Подключение в проекте + +Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c` и адаптируйте макросы конфигурации. + +```c +// Зависимости — только стандартные заголовки AVR-libc: +#include "avr/io.h" +#include "avr/sleep.h" +#include "avr/fuse.h" +#include "avr/interrupt.h" +#include "util/delay.h" +```