Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a8094e9ff | ||
|
|
e334aec37f | ||
|
|
cb5c29518b | ||
|
|
6261106b30 | ||
|
|
035e5edfd4 | ||
|
|
4514ce4a10 |
No files matched your search
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Wireless touch pad sensor for ATMega 168/328 microcontrollers with nRF24L01+ transceiver.
|
1. Wireless touch pad sensor for ATMega168/328 microcontrollers with nRF24L01+ transceiver.
|
||||||
2. Power consumption in the sleep mode approximately 2 uA.
|
2. Power consumption in the sleep mode approximately 2 uA.
|
||||||
3. Transmits device identifier and battery voltage level on each touch event.
|
3. Transmits device identifier and battery voltage level on each touch event.
|
||||||
4. Battery measurement through internal ADC with 1.1 V bandgap reference.
|
4. Battery measurement through internal ADC with 1.1 V bandgap reference.
|
||||||
5. Wake-up from power-down mode by external interrupt on rising edge.
|
5. Wake-up from power-down mode by external interrupt on rising edge.
|
||||||
6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB.
|
6. Automatic pairing with the gateway to obtain a device identifier stored in EEPROM.
|
||||||
7. Easy installation into any standard touch pad enclosure.
|
7. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB.
|
||||||
|
|
||||||
## Attention
|
## Attention
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
2. After power-on reset the first touch event may not trigger a transmission.
|
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.
|
3. Multi-touch is not supported, only a single touch event per transmission cycle.
|
||||||
4. Changing gateway address or channel requires reflashing the microcontroller.
|
4. Changing gateway address or channel requires reflashing the microcontroller.
|
||||||
|
5. If the gateway does not respond during pairing, the device enters permanent sleep until reset.
|
||||||
|
|
||||||
## Using
|
## Using
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -12,7 +12,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega168P]
|
[env:ATmega168P]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -28,7 +28,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega168PB]
|
[env:ATmega168PB]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -44,7 +44,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328]
|
[env:ATmega328]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -60,7 +60,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328P]
|
[env:ATmega328P]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -76,7 +76,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328PB]
|
[env:ATmega328PB]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -92,4 +92,4 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
-334
@@ -1,334 +0,0 @@
|
|||||||
#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"
|
|
||||||
|
|
||||||
#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 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 rising-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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
|
|
||||||
zh_rf_sensor_transmitted_data_t transmitted_data = {DEVICE_ID, ZH_RF_SENSOR_TOUCH_SWITCH, 0, 0, 0, 0, 0};
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
cli();
|
|
||||||
zh_pad_interrupt_init();
|
|
||||||
zh_spi_init();
|
|
||||||
zh_nrf24_init();
|
|
||||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
||||||
sei();
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (send_request == 1)
|
|
||||||
{
|
|
||||||
cli();
|
|
||||||
send_request = 0;
|
|
||||||
sei();
|
|
||||||
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
|
|
||||||
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cli();
|
|
||||||
if (send_request == 0)
|
|
||||||
{
|
|
||||||
sleep_enable();
|
|
||||||
sei();
|
|
||||||
sleep_cpu();
|
|
||||||
}
|
|
||||||
sei();
|
|
||||||
sleep_disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ISR(INT1_vect)
|
|
||||||
{
|
|
||||||
send_request = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zh_pad_interrupt_init(void)
|
|
||||||
{
|
|
||||||
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 << PB2) | (1 << PB3) | (1 << PB5);
|
|
||||||
PORTB |= (1 << PB2);
|
|
||||||
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t zh_spi_transfer(uint8_t data)
|
|
||||||
{
|
|
||||||
SPDR = data;
|
|
||||||
while ((SPSR & (1 << SPIF)) == 0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
return SPDR;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void zh_nrf24_init(void)
|
|
||||||
{
|
|
||||||
_delay_ms(100);
|
|
||||||
DDRB |= (1 << PB1);
|
|
||||||
PORTB &= ~(1 << PB1);
|
|
||||||
DDRD &= ~(1 << PD2);
|
|
||||||
PORTD |= (1 << PD2);
|
|
||||||
uint64_t gateway_address = GATEWAY_ADDRESS;
|
|
||||||
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, (uint8_t *)&gateway_address, 3);
|
|
||||||
zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, (uint8_t *)&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(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
|
|
||||||
_delay_ms(5);
|
|
||||||
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 << PD2)) != 0)
|
|
||||||
{
|
|
||||||
if (++count > 100)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_delay_ms(1);
|
|
||||||
}
|
|
||||||
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 << PB2);
|
|
||||||
uint8_t status = zh_spi_transfer(cmd);
|
|
||||||
while (count-- != 0)
|
|
||||||
{
|
|
||||||
zh_spi_transfer(*(buf++));
|
|
||||||
}
|
|
||||||
PORTB |= (1 << PB2);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val)
|
|
||||||
{
|
|
||||||
PORTB &= ~(1 << PB2);
|
|
||||||
uint8_t status = zh_spi_transfer(reg | NRF_CMD_W_REGISTER);
|
|
||||||
zh_spi_transfer(val);
|
|
||||||
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 | NRF_CMD_W_REGISTER), buf, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t zh_nrf24_send_command(uint8_t cmd)
|
|
||||||
{
|
|
||||||
PORTB &= ~(1 << PB2);
|
|
||||||
uint8_t status = zh_spi_transfer(cmd);
|
|
||||||
PORTB |= (1 << PB2);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static float zh_get_battery_voltage(void)
|
|
||||||
{
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
ADCSRA |= (1 << ADSC);
|
|
||||||
while (ADCSRA & (1 << ADSC))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
uint16_t adc = ADCL;
|
|
||||||
adc |= (uint16_t)ADCH << 8;
|
|
||||||
ADCSRA &= ~(1 << ADEN);
|
|
||||||
if (adc == 0)
|
|
||||||
{
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
return (1024.0 * 1.1) / (float)adc;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,771 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
#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
|
||||||
|
#ifndef PRR
|
||||||
|
#define PRR PRR0
|
||||||
|
#endif
|
||||||
|
#ifndef PRTWI
|
||||||
|
#define PRTWI PRTWI0
|
||||||
|
#endif
|
||||||
|
#ifndef PRSPI
|
||||||
|
#define PRSPI PRSPI0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define GATEWAY_CHANNEL 120
|
||||||
|
#define GATEWAY_ADDRESS 0xDDEEFF
|
||||||
|
|
||||||
|
#define EEPROM_DEVICE_ID_ADDR ((uint8_t *)0x00)
|
||||||
|
#define DEVICE_ID_UNASSIGNED 0xFF
|
||||||
|
|
||||||
|
#define PAIRING_TIMEOUT_ATTEMPTS 7
|
||||||
|
|
||||||
|
#define WDT_T_250MS 0x04
|
||||||
|
#define WDT_T_4S 0x08
|
||||||
|
|
||||||
|
#define NRF_CMD_W_REGISTER 0x20
|
||||||
|
#define NRF_CMD_W_TX_PAYLOAD 0xA0
|
||||||
|
#define NRF_CMD_R_RX_PAYLOAD 0x61
|
||||||
|
#define NRF_CMD_FLUSH_TX 0xE1
|
||||||
|
#define NRF_CMD_FLUSH_RX 0xE2
|
||||||
|
#define NRF_CMD_NOP 0xFF
|
||||||
|
#define NRF_CMD_ACTIVATE 0x50
|
||||||
|
#define NRF_CMD_R_RX_PL_WID 0x60
|
||||||
|
|
||||||
|
#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_REG_RX_PW_P0 0x11
|
||||||
|
#define NRF_REG_DYNPD 0x1C
|
||||||
|
#define NRF_REG_FEATURE 0x1D
|
||||||
|
|
||||||
|
#define NRF_CONFIG_MASK_RX_DR 0x40
|
||||||
|
#define NRF_CONFIG_MASK_TX_DS 0x20
|
||||||
|
#define NRF_CONFIG_MASK_MAX_RT 0x10
|
||||||
|
#define NRF_CONFIG_EN_CRC 0x08
|
||||||
|
#define NRF_CONFIG_PWR_UP 0x02
|
||||||
|
#define NRF_CONFIG_PRIM_RX 0x01
|
||||||
|
|
||||||
|
#define NRF_STATUS_RX_DR 0x40
|
||||||
|
#define NRF_STATUS_TX_DS 0x20
|
||||||
|
#define NRF_STATUS_MAX_RT 0x10
|
||||||
|
|
||||||
|
#define NRF_FEATURE_EN_DPL 0x04
|
||||||
|
#define NRF_DYNPD_DPL_P0 0x01
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the device identifier from EEPROM.
|
||||||
|
*
|
||||||
|
* @return Stored device id, or DEVICE_ID_UNASSIGNED if none is stored
|
||||||
|
*/
|
||||||
|
static uint8_t _zh_load_device_id(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the device identifier to EEPROM.
|
||||||
|
*
|
||||||
|
* @param id Device identifier to store
|
||||||
|
*/
|
||||||
|
static void _zh_save_device_id(uint8_t id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize INT1 external interrupt for the touch-pad wake-up signal.
|
||||||
|
*
|
||||||
|
* Configures PD3 as a rising-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 Initialize INT0 external interrupt for the nRF24 IRQ signal.
|
||||||
|
*
|
||||||
|
* Configures PD2 as a falling-edge triggered external interrupt source and
|
||||||
|
* enables the INT0 interrupt mask bit. The ISR sets the _nrf_event flag used
|
||||||
|
* by the pairing state machine.
|
||||||
|
*/
|
||||||
|
static void _zh_nrf24_interrupt_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 Start a transmission without blocking on completion.
|
||||||
|
*
|
||||||
|
* Loads the payload, pulses CE for 15 us and returns immediately. The
|
||||||
|
* transmission completion is signalled by the nRF24 IRQ pin.
|
||||||
|
*
|
||||||
|
* @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_no_wait(uint8_t *buf, uint8_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 Read the received payload from the nRF24L01+ into a buffer.
|
||||||
|
*
|
||||||
|
* @param buf Pointer to the destination buffer (must not be NULL)
|
||||||
|
* @param count Number of bytes to read from the RX FIFO
|
||||||
|
*/
|
||||||
|
static void _zh_nrf24_read_payload(uint8_t *buf, uint8_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the width of the payload in the nRF24L01+ RX FIFO.
|
||||||
|
*
|
||||||
|
* @return Payload width in bytes
|
||||||
|
*/
|
||||||
|
static uint8_t _zh_nrf24_read_payload_width(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switch the nRF24L01+ to RX mode on pipe 0.
|
||||||
|
*/
|
||||||
|
static void _zh_nrf24_enter_rx(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Put the nRF24L01+ into power-down mode.
|
||||||
|
*/
|
||||||
|
static void _zh_nrf24_enter_power_down(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the watchdog timer in interrupt mode with the given timeout.
|
||||||
|
*
|
||||||
|
* @param timeout_bits Watchdog timeout prescaler bits (WDP3:0)
|
||||||
|
*/
|
||||||
|
static void _zh_wdt_start(uint8_t timeout_bits);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop the watchdog timer.
|
||||||
|
*/
|
||||||
|
static void _zh_wdt_stop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a pseudo-random token from MCU state and ADC noise.
|
||||||
|
*
|
||||||
|
* @return 16-bit random token (never zero)
|
||||||
|
*/
|
||||||
|
static uint16_t _zh_get_random_token(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run the non-blocking pairing procedure to obtain a device id.
|
||||||
|
*
|
||||||
|
* Sends pairing requests, waits for the gateway acknowledgment in RX mode
|
||||||
|
* and retries with watchdog-timed backoff. Sleeps forever if the gateway
|
||||||
|
* does not answer within the allowed number of attempts.
|
||||||
|
*
|
||||||
|
* @return Device identifier assigned by the gateway
|
||||||
|
*/
|
||||||
|
static uint8_t _zh_do_pairing(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enter permanent power-down sleep until the next reset.
|
||||||
|
*
|
||||||
|
* Disables all interrupts, the watchdog, the radio, the ADC, SPI and all
|
||||||
|
* unused peripheral clocks, then loops in power-down sleep mode.
|
||||||
|
*/
|
||||||
|
static void _zh_enter_final_sleep(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 bool _send_request = false;
|
||||||
|
static volatile bool _nrf_event = false;
|
||||||
|
|
||||||
|
zh_nrf24_sensor_transmitted_data_t _transmitted_data = {0};
|
||||||
|
|
||||||
|
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;
|
||||||
|
{
|
||||||
|
uint8_t probe[ZH_NRF24_MAX_PAYLOAD_SIZE];
|
||||||
|
if (zh_nrf24_sensor_build_payload(&_transmitted_data, probe, sizeof(probe)) == 0)
|
||||||
|
{
|
||||||
|
_zh_enter_final_sleep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ISR(INT1_vect)
|
||||||
|
{
|
||||||
|
_send_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ISR(INT0_vect)
|
||||||
|
{
|
||||||
|
_nrf_event = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ISR(WDT_vect)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_load_device_id(void)
|
||||||
|
{
|
||||||
|
uint8_t id = eeprom_read_byte(EEPROM_DEVICE_ID_ADDR);
|
||||||
|
if (id == 0xFF || id == 0x00)
|
||||||
|
{
|
||||||
|
return DEVICE_ID_UNASSIGNED;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_save_device_id(uint8_t id)
|
||||||
|
{
|
||||||
|
eeprom_update_byte(EEPROM_DEVICE_ID_ADDR, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_pad_interrupt_init(void)
|
||||||
|
{
|
||||||
|
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 << PB2) | (1 << PB3) | (1 << PB5);
|
||||||
|
PORTB |= (1 << PB2);
|
||||||
|
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_spi_transfer(uint8_t data)
|
||||||
|
{
|
||||||
|
SPDR = data;
|
||||||
|
while ((SPSR & (1 << SPIF)) == 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return SPDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_init(void)
|
||||||
|
{
|
||||||
|
_delay_ms(100);
|
||||||
|
DDRB |= (1 << PB1);
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
DDRD &= ~(1 << PD2);
|
||||||
|
PORTD |= (1 << PD2);
|
||||||
|
uint64_t gateway_address = GATEWAY_ADDRESS;
|
||||||
|
uint8_t activation_key = 0x73;
|
||||||
|
_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, (uint8_t *)&gateway_address, 3);
|
||||||
|
_zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, (uint8_t *)&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_buffer(NRF_CMD_ACTIVATE, &activation_key, sizeof(activation_key));
|
||||||
|
_zh_nrf24_write_register(NRF_REG_FEATURE, NRF_FEATURE_EN_DPL);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_DYNPD, NRF_DYNPD_DPL_P0);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_interrupt_init(void)
|
||||||
|
{
|
||||||
|
DDRD &= ~(1 << PD2);
|
||||||
|
PORTD |= (1 << PD2);
|
||||||
|
EICRA &= ~((1 << ISC01) | (1 << ISC00));
|
||||||
|
EICRA |= (1 << ISC01);
|
||||||
|
EIFR |= (1 << INTF0);
|
||||||
|
EIMSK |= (1 << INT0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_send(uint8_t *buf, uint8_t size)
|
||||||
|
{
|
||||||
|
_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(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 << PD2)) != 0)
|
||||||
|
{
|
||||||
|
if (++count > 100)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_delay_ms(1);
|
||||||
|
}
|
||||||
|
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 void _zh_nrf24_send_no_wait(uint8_t *buf, uint8_t size)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
|
||||||
|
_delay_ms(2);
|
||||||
|
_zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
|
||||||
|
_zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size);
|
||||||
|
PORTB |= (1 << PB1);
|
||||||
|
_delay_us(15);
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_nrf24_send_command(uint8_t cmd)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB2);
|
||||||
|
uint8_t status = _zh_spi_transfer(cmd);
|
||||||
|
PORTB |= (1 << PB2);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB2);
|
||||||
|
uint8_t status = _zh_spi_transfer(cmd);
|
||||||
|
while (count-- != 0)
|
||||||
|
{
|
||||||
|
_zh_spi_transfer(*(buf++));
|
||||||
|
}
|
||||||
|
PORTB |= (1 << PB2);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_nrf24_write_register(uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB2);
|
||||||
|
uint8_t status = _zh_spi_transfer(reg | NRF_CMD_W_REGISTER);
|
||||||
|
_zh_spi_transfer(val);
|
||||||
|
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 | NRF_CMD_W_REGISTER), buf, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_read_payload(uint8_t *buf, uint8_t count)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB2);
|
||||||
|
_zh_spi_transfer(NRF_CMD_R_RX_PAYLOAD);
|
||||||
|
while (count-- != 0)
|
||||||
|
{
|
||||||
|
*buf++ = _zh_spi_transfer(0xFF);
|
||||||
|
}
|
||||||
|
PORTB |= (1 << PB2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_nrf24_read_payload_width(void)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB2);
|
||||||
|
_zh_spi_transfer(NRF_CMD_R_RX_PL_WID);
|
||||||
|
uint8_t width = _zh_spi_transfer(0xFF);
|
||||||
|
PORTB |= (1 << PB2);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_enter_rx(void)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
|
||||||
|
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP | NRF_CONFIG_PRIM_RX);
|
||||||
|
_delay_ms(2);
|
||||||
|
PORTB |= (1 << PB1);
|
||||||
|
_delay_us(130);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_nrf24_enter_power_down(void)
|
||||||
|
{
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_EN_CRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_wdt_start(uint8_t timeout_bits)
|
||||||
|
{
|
||||||
|
uint8_t wdp = (timeout_bits & 0x07) | ((timeout_bits & 0x08) ? (1 << WDP3) : 0);
|
||||||
|
cli();
|
||||||
|
wdt_reset();
|
||||||
|
WDTCSR = (1 << WDCE) | (1 << WDE);
|
||||||
|
WDTCSR = (1 << WDIE) | wdp;
|
||||||
|
sei();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_wdt_stop(void)
|
||||||
|
{
|
||||||
|
cli();
|
||||||
|
wdt_reset();
|
||||||
|
WDTCSR = (1 << WDCE) | (1 << WDE);
|
||||||
|
WDTCSR = 0;
|
||||||
|
sei();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t _zh_get_random_token(void)
|
||||||
|
{
|
||||||
|
uint16_t token = MCUSR;
|
||||||
|
MCUSR = 0;
|
||||||
|
ADMUX = (1 << REFS0);
|
||||||
|
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
|
||||||
|
_delay_ms(5);
|
||||||
|
for (uint8_t i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
ADCSRA |= (1 << ADSC);
|
||||||
|
while ((ADCSRA & (1 << ADSC)) != 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
token = (token << 1) ^ (token >> 15) ^ ADC;
|
||||||
|
}
|
||||||
|
ADCSRA &= ~(1 << ADEN);
|
||||||
|
if (token == 0)
|
||||||
|
{
|
||||||
|
token = 0xACE1;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t _zh_do_pairing(void)
|
||||||
|
{
|
||||||
|
uint16_t token = _zh_get_random_token();
|
||||||
|
zh_nrf24_sensor_pairing_data_t pairing_request;
|
||||||
|
pairing_request.type = (uint8_t)_transmitted_data.type;
|
||||||
|
pairing_request.magic = ZH_NRF24_PAIRING_REQ_MAGIC;
|
||||||
|
pairing_request.token_hi = (uint8_t)(token >> 8);
|
||||||
|
pairing_request.token_lo = (uint8_t)(token & 0xFF);
|
||||||
|
pairing_request.id = 0;
|
||||||
|
_zh_nrf24_interrupt_init();
|
||||||
|
zh_nrf24_sensor_pairing_state_t state = ZH_NRF24_PAIR_SEND;
|
||||||
|
uint8_t attempts = 0;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ZH_NRF24_PAIR_SEND:
|
||||||
|
_nrf_event = false;
|
||||||
|
_zh_nrf24_send_no_wait((uint8_t *)&pairing_request, ZH_NRF24_PAIRING_PACKET_SIZE);
|
||||||
|
state = ZH_NRF24_PAIR_TX_WAIT;
|
||||||
|
break;
|
||||||
|
case ZH_NRF24_PAIR_TX_WAIT:
|
||||||
|
if ((PIND & (1 << PD2)) == 0)
|
||||||
|
{
|
||||||
|
_nrf_event = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_nrf_event = false;
|
||||||
|
_zh_wdt_start(WDT_T_250MS);
|
||||||
|
sleep_enable();
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
sleep_disable();
|
||||||
|
_zh_wdt_stop();
|
||||||
|
sei();
|
||||||
|
}
|
||||||
|
if (_nrf_event == true)
|
||||||
|
{
|
||||||
|
_nrf_event = false;
|
||||||
|
uint8_t status = _zh_nrf24_send_command(NRF_CMD_NOP);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
|
||||||
|
if ((status & NRF_STATUS_TX_DS) != 0)
|
||||||
|
{
|
||||||
|
_zh_nrf24_enter_rx();
|
||||||
|
state = ZH_NRF24_PAIR_RX_WAIT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ZH_NRF24_PAIR_RX_WAIT:
|
||||||
|
if ((PIND & (1 << PD2)) == 0)
|
||||||
|
{
|
||||||
|
_nrf_event = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_nrf_event = false;
|
||||||
|
_zh_wdt_start(WDT_T_250MS);
|
||||||
|
sleep_enable();
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
sleep_disable();
|
||||||
|
_zh_wdt_stop();
|
||||||
|
sei();
|
||||||
|
}
|
||||||
|
if (_nrf_event == true)
|
||||||
|
{
|
||||||
|
_nrf_event = false;
|
||||||
|
uint8_t status = _zh_nrf24_send_command(NRF_CMD_NOP);
|
||||||
|
if ((status & NRF_STATUS_RX_DR) != 0)
|
||||||
|
{
|
||||||
|
uint8_t width = _zh_nrf24_read_payload_width();
|
||||||
|
if (width > ZH_NRF24_MAX_PAYLOAD_SIZE)
|
||||||
|
{
|
||||||
|
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR);
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (width != ZH_NRF24_PAIRING_PACKET_SIZE)
|
||||||
|
{
|
||||||
|
uint8_t dummy[ZH_NRF24_MAX_PAYLOAD_SIZE];
|
||||||
|
_zh_nrf24_read_payload(dummy, width);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR);
|
||||||
|
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
zh_nrf24_sensor_pairing_data_t pairing_response;
|
||||||
|
_zh_nrf24_read_payload((uint8_t *)&pairing_response, width);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT);
|
||||||
|
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
|
||||||
|
if (pairing_response.magic == ZH_NRF24_PAIRING_ACK_MAGIC && pairing_response.type == pairing_request.type &&
|
||||||
|
pairing_response.token_hi == pairing_request.token_hi && pairing_response.token_lo == pairing_request.token_lo &&
|
||||||
|
pairing_response.id != 0 && pairing_response.id != DEVICE_ID_UNASSIGNED)
|
||||||
|
{
|
||||||
|
_zh_nrf24_enter_power_down();
|
||||||
|
EIMSK &= ~(1 << INT0);
|
||||||
|
_zh_save_device_id(pairing_response.id);
|
||||||
|
return pairing_response.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = ZH_NRF24_PAIR_BACKOFF;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ZH_NRF24_PAIR_BACKOFF:
|
||||||
|
_zh_nrf24_enter_power_down();
|
||||||
|
_nrf_event = false;
|
||||||
|
_zh_wdt_start(WDT_T_4S);
|
||||||
|
sleep_enable();
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
sleep_disable();
|
||||||
|
_zh_wdt_stop();
|
||||||
|
sei();
|
||||||
|
if (++attempts >= PAIRING_TIMEOUT_ATTEMPTS)
|
||||||
|
{
|
||||||
|
_zh_enter_final_sleep();
|
||||||
|
}
|
||||||
|
state = ZH_NRF24_PAIR_SEND;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _zh_enter_final_sleep(void)
|
||||||
|
{
|
||||||
|
cli();
|
||||||
|
EIMSK = 0;
|
||||||
|
EIFR = 0xFF;
|
||||||
|
_zh_wdt_stop();
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
|
_zh_nrf24_write_register(NRF_REG_CONFIG, 0x00);
|
||||||
|
ADCSRA = 0;
|
||||||
|
SPCR = 0;
|
||||||
|
PRR = (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM1) | (1 << PRTIM0) | (1 << PRSPI) | (1 << PRUSART0);
|
||||||
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
sleep_enable();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
sleep_cpu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static float _zh_get_battery_voltage(void)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ADCSRA |= (1 << ADSC);
|
||||||
|
while ((ADCSRA & (1 << ADSC)) != 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
uint16_t adc = ADCL;
|
||||||
|
adc |= (uint16_t)ADCH << 8;
|
||||||
|
ADCSRA &= ~(1 << ADEN);
|
||||||
|
if (adc == 0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return (1024.0 * 1.1) / (float)adc;
|
||||||
|
}
|
||||||
+180
-100
@@ -1,129 +1,186 @@
|
|||||||
# Описание компонента zh_avr_touch_pad
|
# Описание прошивки zh_avr_touch_pad
|
||||||
|
|
||||||
## Общие сведения
|
## Общие сведения
|
||||||
|
|
||||||
**zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.
|
**zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.
|
||||||
|
|
||||||
Компонент работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
|
Прошивка работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. При первом запуске выполняется автоматическое сопряжение (pairing) со шлюзом для получения идентификатора устройства, который сохраняется в EEPROM.
|
||||||
|
|
||||||
### Основные возможности
|
### Основные возможности
|
||||||
|
|
||||||
1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна.
|
1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна.
|
||||||
2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
|
2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
|
||||||
3. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
|
3. **Автоматическое сопряжение со шлюзом** — получение идентификатора устройства по радиоканалу с сохранением в EEPROM.
|
||||||
4. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
|
4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
|
||||||
5. **Минимальная обвязка** — установка в стандартный корпус сенсорного выключателя.
|
5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
|
||||||
6. **Пробуждение по внешнему прерыванию INT1** — rising edge на пине PD3.
|
6. **Пробуждение по внешнему прерыванию INT1** — rising edge на пине PD3.
|
||||||
7. **Автономная работа** — не требует внешних подтягивающих резисторов.
|
7. **Генерация псевдослучайного токена** — на основе шума АЦП и состояния MCUSR для уникализации запроса сопряжения.
|
||||||
|
8. **Watchdog-таймер в режиме прерывания** — используется для таймаутов при сопряжении без сброса МК.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Внутреннее устройство
|
## Внутреннее устройство
|
||||||
|
|
||||||
### Структура данных
|
### Структура передаваемых данных
|
||||||
|
|
||||||
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
|
Прошивка не использует динамическое выделение памяти. Все данные хранятся в статической области и регистрах микроконтроллера.
|
||||||
|
|
||||||
#### Формат передаваемого пакета
|
Пакет формируется в глобальной переменной `_transmitted_data` типа `zh_nrf24_sensor_transmitted_data_t` из внешней библиотеки `zh_nrf24_config` и сериализуется функцией `zh_nrf24_sensor_build_payload()` перед передачей.
|
||||||
|
|
||||||
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
|
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
| Поле | Тип | Описание |
|
||||||
| ------ | ----- | ---------- |
|
| ------ | ----- | ---------- |
|
||||||
| `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
|
| `id` | `int16_t` | Идентификатор устройства, присвоенный шлюзом при сопряжении |
|
||||||
| `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_TOUCH_SWITCH`) |
|
| `type` | `int16_t` | Тип устройства (`ZH_NRF24_SENSOR_TOUCH_SWITCH`) |
|
||||||
| `battery_voltage` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
|
| `battery` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
|
||||||
| `value_1`–`value_4` | `int16_t` | Зарезервированы (равны 0) |
|
|
||||||
|
|
||||||
### Конфигурационные макросы
|
### Конфигурационные макросы
|
||||||
|
|
||||||
| Макрос | Значение | Описание |
|
| Макрос | Значение | Описание |
|
||||||
|--------|----------|----------|
|
| -------- | ---------- | ---------- |
|
||||||
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
|
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
|
||||||
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
|
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
|
||||||
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
|
| `EEPROM_DEVICE_ID_ADDR` | `((uint8_t *)0x00)` | Адрес в EEPROM для хранения идентификатора устройства |
|
||||||
|
| `DEVICE_ID_UNASSIGNED` | `0xFF` | Значение, обозначающее отсутствие сохранённого идентификатора |
|
||||||
Тип устройства задаётся не макросом, а значением перечисления `ZH_RF_SENSOR_TOUCH_SWITCH` из библиотеки `zh_rf_config`.
|
| `PAIRING_TIMEOUT_ATTEMPTS` | `7` | Максимальное число попыток сопряжения перед уходом в окончательный сон |
|
||||||
|
| `WDT_T_250MS` | `0x04` | Таймаут watchdog-таймера 250 мс |
|
||||||
|
| `WDT_T_4S` | `0x08` | Таймаут watchdog-таймера 4 с |
|
||||||
|
|
||||||
### Управление памятью
|
### Управление памятью
|
||||||
|
|
||||||
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` заполняется один раз при старте (идентификатор и тип), в рабочем цикле обновляется только поле `battery_voltage`. Флаг `send_request` объявлен как `static volatile uint8_t` для корректного доступа из ISR и главного цикла.
|
Прошивка не использует динамическую память. Глобальная переменная `_transmitted_data` заполняется при старте (идентификатор и тип), в рабочем цикле обновляется только поле `battery`. Флаги `_send_request`, `_nrf_event` объявлены как `static volatile bool` для корректного доступа из ISR и главного цикла.
|
||||||
|
|
||||||
|
### Флаги синхронизации
|
||||||
|
|
||||||
|
| Флаг | Устанавливается | Используется |
|
||||||
|
| ------ | ----------------- | -------------- |
|
||||||
|
| `_send_request` | ISR `INT1_vect` (касание) | Главный цикл — запуск передачи |
|
||||||
|
| `_nrf_event` | ISR `INT0_vect` (IRQ nRF24) | Конечный автомат сопряжения |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## API (интерфейс)
|
## API (интерфейс)
|
||||||
|
|
||||||
Все функции являются `static` и доступны только внутри модуля `main.c`.
|
Все функции являются `static` и доступны только внутри модуля `src/zh_avr_touch_pad.c`. Имена функций имеют префикс `_zh_`.
|
||||||
|
|
||||||
### Инициализация
|
### Инициализация
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
| --------- | ---------- |
|
| --------- | ---------- |
|
||||||
| `zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, rising edge) для пробуждения по касанию |
|
| `_zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, rising edge) для пробуждения по касанию |
|
||||||
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
|
| `_zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
|
||||||
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
|
| `_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_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В, возврат значения в вольтах |
|
||||||
|
| `_zh_get_random_token()` | Генерация 16-битного псевдослучайного токена из шума АЦП и MCUSR для запроса сопряжения |
|
||||||
|
|
||||||
### Передача данных
|
### Передача данных
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
|---------|----------|
|
|---------|----------|
|
||||||
| `zh_nrf24_send()` | Передача буфера через nRF24L01+ с полным циклом TX: Power Up, `FLUSH_TX`, загрузка `W_TX_PAYLOAD`, импульс CE, ожидание флага TX_DS/MAX_RT по пину IRQ, сброс статуса и переход в Power Down |
|
| `_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-операции
|
### Низкоуровневые SPI-операции
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
| --------- | ---------- |
|
| --------- | ---------- |
|
||||||
| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
|
| `_zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
|
||||||
| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
|
| `_zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение регистра статуса |
|
||||||
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
|
| `_zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
|
||||||
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой |
|
| `_zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
|
||||||
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
|
| `_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. Синхронизация между прерыванием и главным циклом реализуется следующим образом:
|
Прошивка работает на bare-metal AVR без RTOS. Синхронизация между прерываниями и главным циклом реализуется следующим образом:
|
||||||
|
|
||||||
1. **`volatile` флаг** — переменная `send_request` объявлена как `volatile`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
|
1. **Флаги `volatile`** — переменные `_send_request`, `_nrf_event` объявлены как `static volatile bool`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
|
||||||
2. **Атомарная запись из ISR** — обработка INT1 устанавливает флаг в единицу. Запись одного байта на AVR является атомарной операцией.
|
2. **Атомарная запись из ISR** — обработка прерывания устанавливает флаг в `true`. Запись одного байта на AVR является атомарной операцией.
|
||||||
3. **Отключение прерываний при инициализации** — `cli()` перед настройкой периферии исключает гонки при старте.
|
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()`.
|
||||||
|
|
||||||
### Алгоритм измерения напряжения батареи
|
### Алгоритм измерения напряжения батареи
|
||||||
|
|
||||||
- ADC конфигурируется с внутренним опорным напряжением 1,1 В на канале внутреннего источника 1,1 В (`REFS0=1`, `MUX[3:1]=111`), вход AVcc сравнивается с опорой.
|
- АЦП конфигурируется с внутренним опорным напряжением 1,1 В на канале внутреннего источника 1,1 В (`REFS0=1`, `MUX[3:1]=111`), измеряется отношение AVcc к опоре 1,1 В.
|
||||||
- Предделитель частоты: 64 (ADPS[2:0] = 111), что даёт ~125 кГц при 8 МГц.
|
- Предделитель частоты: 64 (`ADPS[2:0] = 111`), что даёт ~125 кГц при 8 МГц.
|
||||||
- Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат.
|
- Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат.
|
||||||
- Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`.
|
- Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`.
|
||||||
- При нулевой выборке (`adc == 0`) возвращается 0.0.
|
- При нулевой выборке (`adc == 0`) возвращается `0.0`.
|
||||||
- После измерения АЦП отключается сбросом бита `ADEN`.
|
- После измерения АЦП отключается сбросом бита `ADEN`.
|
||||||
|
|
||||||
### Алгоритм передачи через nRF24L01+
|
### Алгоритм генерации псевдослучайного токена
|
||||||
|
|
||||||
|
- Инициальное значение — регистр `MCUSR` (состояние сброса).
|
||||||
|
- АЦП запускается на 16 конверсий в канале с внутренним опорным напряжением.
|
||||||
|
- На каждой итерации: `token = (token << 1) ^ (token >> 15) ^ ADC` — сдвиг с переносом и XOR с шумом АЦП.
|
||||||
|
- Если результат равен 0, возвращается константа `0xACE1`.
|
||||||
|
|
||||||
|
### Алгоритм блокирующей передачи через nRF24L01+
|
||||||
|
|
||||||
- Переход в Power Up (бит `PWR_UP` в CONFIG).
|
- Переход в Power Up (бит `PWR_UP` в CONFIG).
|
||||||
- Ожидание 5 мс (время стартования осциллятора).
|
- Ожидание 5 мс (время стартования осциллятора).
|
||||||
- Очистка TX FIFO командой `FLUSH_TX`.
|
- Очистка TX FIFO командой `FLUSH_TX`.
|
||||||
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
|
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
|
||||||
- Импульс CE (установка в 1) для запуска передачи.
|
- Установка CE в 1 для запуска передачи.
|
||||||
- Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс.
|
- Ожидание спада пина IRQ (PD2) с таймаутом 100 мс (проверка каждые 1 мс).
|
||||||
- Сброс флагов статуса, очистка FIFO, переход в Power Down.
|
- Сброс CE, чтение статуса, сброс флагов, очистка FIFO, переход в Power Down.
|
||||||
|
|
||||||
### Алгоритм сна и пробуждения
|
### Алгоритм сна и пробуждения
|
||||||
|
|
||||||
- Главный цикл проверяет флаг `send_request`.
|
- Главный цикл проверяет флаг `_send_request`.
|
||||||
- Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down).
|
- Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down).
|
||||||
- Пробуждение происходит по аппаратному прерыванию INT1 (rising edge на PD3).
|
- Пробуждение происходит по аппаратному прерыванию INT1 (rising edge на PD3).
|
||||||
- ISR устанавливает `send_request = 1`, цикл просыпается и выполняет передачу.
|
- ISR устанавливает `_send_request = true`, цикл просыпается и выполняет передачу.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -132,40 +189,59 @@
|
|||||||
### Типовой цикл работы (main)
|
### Типовой цикл работы (main)
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
#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 bool _send_request = false;
|
||||||
|
zh_nrf24_sensor_transmitted_data_t _transmitted_data = {0};
|
||||||
|
|
||||||
|
ISR(INT1_vect)
|
||||||
|
{
|
||||||
|
_send_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
// Отключаем прерывания на время инициализации
|
_transmitted_data.type = ZH_NRF24_SENSOR_TOUCH_SWITCH;
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
wdt_disable();
|
||||||
cli();
|
cli();
|
||||||
// Настраиваем периферию
|
_zh_pad_interrupt_init();
|
||||||
zh_pad_interrupt_init();
|
_zh_spi_init();
|
||||||
zh_spi_init();
|
_zh_nrf24_init();
|
||||||
zh_nrf24_init();
|
|
||||||
// Конфигурируем режим сна Power-down
|
|
||||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
sei();
|
sei();
|
||||||
|
// Загрузка или получение идентификатора устройства
|
||||||
// Глобальная структура из zh_rf_config
|
uint8_t device_id = _zh_load_device_id();
|
||||||
zh_rf_sensor_transmitted_data_t transmitted_data =
|
if (device_id == DEVICE_ID_UNASSIGNED)
|
||||||
{DEVICE_ID, ZH_RF_SENSOR_TOUCH_SWITCH, 0, 0, 0, 0, 0};
|
{
|
||||||
|
device_id = _zh_do_pairing();
|
||||||
|
}
|
||||||
|
_transmitted_data.id = (int16_t)device_id;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (send_request == 1)
|
if (_send_request == true)
|
||||||
{
|
{
|
||||||
// Собираем и отправляем пакет
|
|
||||||
cli();
|
cli();
|
||||||
send_request = 0;
|
_send_request = false;
|
||||||
sei();
|
sei();
|
||||||
transmitted_data.battery_voltage =
|
_transmitted_data.battery = (int16_t)lroundf(_zh_get_battery_voltage() * 100.0);
|
||||||
(int16_t)(zh_get_battery_voltage() * 100.0);
|
uint8_t payload[ZH_NRF24_MAX_PAYLOAD_SIZE];
|
||||||
zh_nrf24_send((uint8_t *)&transmitted_data,
|
uint8_t size = zh_nrf24_sensor_build_payload(&_transmitted_data, payload, sizeof(payload));
|
||||||
sizeof(transmitted_data));
|
if (size > 0)
|
||||||
|
{
|
||||||
|
_zh_nrf24_send(payload, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Спим до следующего прерывания
|
|
||||||
cli();
|
cli();
|
||||||
if (send_request == 0)
|
if (_send_request == false)
|
||||||
{
|
{
|
||||||
sleep_enable();
|
sleep_enable();
|
||||||
sei();
|
sei();
|
||||||
@@ -175,60 +251,56 @@ int main(void)
|
|||||||
sleep_disable();
|
sleep_disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Изменение конфигурации под другое устройство
|
### Назначение пинов AVR для nRF24L01+
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Измените идентификатор для нового устройства
|
// PB2 (SS) -> CSN (выбор кристалла SPI, активный низкий)
|
||||||
#define DEVICE_ID 7 // Уникальный ID в сети
|
// PB3 (MOSI) -> MOSI
|
||||||
// Тип устройства задаётся перечислением ZH_RF_SENSOR_TOUCH_SWITCH
|
// PB4 (MISO) -> MISO
|
||||||
|
// PB5 (SCK) -> SCK
|
||||||
|
// PB1 -> CE (запуск передачи/приёма)
|
||||||
|
// PD2 (INT0) -> IRQ (вход, активный низкий, с подтяжкой к VCC)
|
||||||
|
// PD3 (INT1) -> Выход сенсорного датчика (rising edge, без подтяжки)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Изменение конфигурации шлюза
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Измените адрес и канал под ваш шлюз
|
||||||
#define GATEWAY_CHANNEL 120
|
#define GATEWAY_CHANNEL 120
|
||||||
#define GATEWAY_ADDRESS 0xDDEEFF
|
#define GATEWAY_ADDRESS 0xDDEEFF
|
||||||
|
// Тип устройства задаётся перечислением ZH_NRF24_SENSOR_TOUCH_SWITCH из zh_nrf24_config
|
||||||
```
|
```
|
||||||
|
|
||||||
### Подключение по SPI (схема выводов)
|
|
||||||
|
|
||||||
```c
|
|
||||||
// Назначение пинов AVR для nRF24L01+:
|
|
||||||
// PB2 (SS) -> CSN (управление SPI-селектом)
|
|
||||||
// PB3 (MOSI) -> MOSI
|
|
||||||
// PB5 (SCK) -> SCK
|
|
||||||
// PB1 -> CE (управление передачей)
|
|
||||||
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC)
|
|
||||||
// PD3 -> INT1 (вход сенсорного датчика, rising edge)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Логирование
|
|
||||||
|
|
||||||
Компонент не использует ESP-IDF логирование (прошивка для AVR, а не ESP32). Логирование отсутствует.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ошибки
|
## Ошибки
|
||||||
|
|
||||||
Компонент не возвращает коды ошибок. Все функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`) для низкоуровневых операций.
|
Прошивка не возвращает коды ошибок в стиле ESP-IDF. Все функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`) для низкоуровневых операций.
|
||||||
|
|
||||||
| Условие | Поведение |
|
| Условие | Поведение |
|
||||||
| --------- | ---------- |
|
| --------- | ----------- |
|
||||||
| Нулевое показание АЦП | `zh_get_battery_voltage()` возвращает 0.0 |
|
| Нулевое показание АЦП | `_zh_get_battery_voltage()` возвращает `0.0` |
|
||||||
| Превышение таймаута передачи | Передача прерывается, пакет теряется |
|
| Превышение таймаута передачи (100 мс) | Передача прерывается, пакет теряется |
|
||||||
| Неверный аргумент | Не применимо — все функции `static`, аргументы не валидируются |
|
| Неверная длина принятого пакета при сопряжении | Пакет отбрасывается, переход в `ZH_NRF24_PAIR_BACKOFF` |
|
||||||
|
| Превышение числа попыток сопряжения (7) | `_zh_enter_final_sleep()` — сон до сброса |
|
||||||
|
| `zh_nrf24_sensor_build_payload()` возвращает 0 | Передача пропускается, переход в `_zh_enter_final_sleep()` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Ограничения
|
## Ограничения
|
||||||
|
|
||||||
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
|
1. **Только передача (TX-only) в рабочем цикле** — приём используется исключительно на этапе сопряжения, шлюз не может отправить данные устройству в штатном режиме.
|
||||||
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
|
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
|
||||||
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
|
3. **Жёстко зашитый адрес и канал шлюза** — смена `GATEWAY_ADDRESS` или `GATEWAY_CHANNEL` требует перепрошивки.
|
||||||
4. **Таймаут передачи 100 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом).
|
4. **Таймаут блокирующей передачи 100 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом).
|
||||||
5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг — один бит).
|
5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг `_send_request` — один бит).
|
||||||
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
|
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
|
||||||
7. **Нет механизма глубокого сна для периферии** — АЦП полностью отключается после измерения (`ADEN = 0`), но SPI остаётся включённым в режиме сна.
|
7. **SPI остаётся включённым в режиме сна** — перед окончательным сном SPI отключается в `_zh_enter_final_sleep()`, но в штатном режиме ожидания SPI не отключается между передачами.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -236,7 +308,7 @@ int main(void)
|
|||||||
|
|
||||||
- PlatformIO с плагином `platform-atmelavr`
|
- PlatformIO с плагином `platform-atmelavr`
|
||||||
- AVR-GCC (входит в состав PlatformIO)
|
- AVR-GCC (входит в состав PlatformIO)
|
||||||
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
|
- Библиотека `zh_nrf24_config` (подключается через `lib_deps` в `platformio.ini`)
|
||||||
- Программатор USBasp (или совместимый)
|
- Программатор USBasp (или совместимый)
|
||||||
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
||||||
|
|
||||||
@@ -247,25 +319,33 @@ int main(void)
|
|||||||
### Сборка и прошивка через PlatformIO
|
### Сборка и прошивка через PlatformIO
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git clone https://github.com/aZholtikov/zh_avr_touch_pad
|
||||||
|
cd zh_avr_touch_pad
|
||||||
pio run -e ATmega328P -t upload
|
pio run -e ATmega328P -t upload
|
||||||
```
|
```
|
||||||
|
|
||||||
### Подключение в проекте
|
### Подключение в проекте
|
||||||
|
|
||||||
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c`, добавьте зависимость `zh_rf_config` и адаптируйте макросы конфигурации.
|
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/zh_avr_touch_pad.c`, добавьте зависимость `zh_nrf24_config` и адаптируйте макросы конфигурации.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
; platformio.ini
|
; platformio.ini
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Подключение заголовков в коде
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Зависимости: стандартные заголовки AVR-libc и конфигурация протокола
|
// Зависимости: стандартные заголовки AVR-libc и конфигурация протокола
|
||||||
#include "avr/io.h"
|
#include "avr/io.h"
|
||||||
#include "avr/sleep.h"
|
#include "avr/sleep.h"
|
||||||
#include "avr/fuse.h"
|
#include "avr/fuse.h"
|
||||||
#include "avr/interrupt.h"
|
#include "avr/interrupt.h"
|
||||||
|
#include "avr/eeprom.h"
|
||||||
|
#include "avr/wdt.h"
|
||||||
#include "util/delay.h"
|
#include "util/delay.h"
|
||||||
#include "zh_rf_config.h"
|
#include "math.h"
|
||||||
|
#include "stdbool.h"
|
||||||
|
#include "zh_nrf24_config.h"
|
||||||
```
|
```
|
||||||
Reference in new issue
Block a user