feat: added sensor pairing to gateway

This commit is contained in:
aZholtikov committed 2026-10-25 08:56:20 +03:00
1 parent 13b74b9306
commit 52f2da6368
4 files changed
+705 -208

No files matched your search

+10 -8
View File
@@ -4,17 +4,19 @@
1. Wireless sensor firmware for ATMega168/328 microcontrollers with BME280 temperature, pressure, and humidity sensor. 1. Wireless sensor firmware for ATMega168/328 microcontrollers with BME280 temperature, pressure, and humidity sensor.
2. Power consumption in sleep mode approximately 5 uA using Power-down mode. 2. Power consumption in sleep mode approximately 5 uA using Power-down mode.
3. Transmits device identifier, battery voltage, and all three BME280 sensor readings on each measurement cycle. 3. Automatic over-the-air pairing: the sensor requests a device id from the gateway and stores it in EEPROM.
4. Battery measurement through internal ADC with 1.1 V bandgap reference. 4. Transmits device identifier, battery voltage, and all three BME280 sensor readings (×10 scaling) on each measurement cycle.
5. Wake-up from Power-down mode by watchdog timer interrupt with configurable interval. 5. Battery measurement through internal ADC with 1.1 V bandgap reference.
6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, and ATMega328PB. 6. Wake-up from Power-down mode by watchdog timer interrupt with configurable interval.
7. Hardware TWI (I2C) for BME280 communication on PORTC pins PC4 (SDA) and PC5 (SCL). 7. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, and ATMega328PB.
8. Hardware TWI (I2C) for BME280 communication on PORTC pins PC4 (SDA) and PC5 (SCL).
## Attention ## Attention
1. A compatible RF Gateway is required to receive and process transmitted packets. 1. A compatible RF Gateway is required to receive packets and assign a device id during pairing.
2. Changing gateway address or channel requires reflashing the microcontroller. 2. The watchdog timer interval is fixed at 8 seconds; the sensor transmits every 38 ticks (~5 minutes).
3. The watchdog timer interval is fixed at 8 seconds; the sensor transmits every 38 ticks (~5 minutes). 3. Changing gateway address or channel requires reflashing the microcontroller.
4. If the gateway does not respond during pairing, the device enters permanent sleep until reset.
## Using ## Using
+6 -6
View File
@@ -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_rf_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_rf_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_rf_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_rf_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_rf_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_rf_config#dev
+548 -127
View File
@@ -1,6 +1,7 @@
#include "avr/io.h" #include "avr/io.h"
#include "avr/interrupt.h" #include "avr/interrupt.h"
#include "avr/sleep.h" #include "avr/sleep.h"
#include "avr/eeprom.h"
#include "avr/wdt.h" #include "avr/wdt.h"
#include "util/delay.h" #include "util/delay.h"
#include "stdbool.h" #include "stdbool.h"
@@ -41,19 +42,40 @@
#ifndef TWDR #ifndef TWDR
#define TWDR TWDR0 #define TWDR TWDR0
#endif #endif
#ifndef PRR
#define PRR PRR0
#endif
#ifndef PRTWI
#define PRTWI PRTWI0
#endif
#ifndef PRSPI
#define PRSPI PRSPI0
#endif
#endif #endif
#define GATEWAY_CHANNEL 120 #define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF #define GATEWAY_ADDRESS 0xDDEEFF
#define DEVICE_ID 6
#define EEPROM_DEVICE_ID_ADDR ((uint8_t *)0x00)
#define DEVICE_ID_UNASSIGNED 0xFF
#define PAIRING_TIMEOUT_ATTEMPTS 7
#define WDT_WAKE_COUNT 38 #define WDT_WAKE_COUNT 38
#define ZH_TWI_TWBR 32 #define WDT_T_250MS 0x04
#define WDT_T_4S 0x08
#define WDT_T_8S 0x09
#define TWI_TWBR 32
#define NRF_CMD_W_REGISTER 0x20 #define NRF_CMD_W_REGISTER 0x20
#define NRF_CMD_W_TX_PAYLOAD 0xA0 #define NRF_CMD_W_TX_PAYLOAD 0xA0
#define NRF_CMD_R_RX_PAYLOAD 0x61
#define NRF_CMD_FLUSH_TX 0xE1 #define NRF_CMD_FLUSH_TX 0xE1
#define NRF_CMD_FLUSH_RX 0xE2
#define NRF_CMD_NOP 0xFF #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_CONFIG 0x00
#define NRF_REG_EN_AA 0x01 #define NRF_REG_EN_AA 0x01
@@ -65,15 +87,24 @@
#define NRF_REG_STATUS 0x07 #define NRF_REG_STATUS 0x07
#define NRF_REG_RX_ADDR_P0 0x0A #define NRF_REG_RX_ADDR_P0 0x0A
#define NRF_REG_TX_ADDR 0x10 #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_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_EN_CRC 0x08
#define NRF_CONFIG_PWR_UP 0x02 #define NRF_CONFIG_PWR_UP 0x02
#define NRF_CONFIG_PRIM_RX 0x01
#define NRF_STATUS_RX_DR 0x40 #define NRF_STATUS_RX_DR 0x40
#define NRF_STATUS_TX_DS 0x20 #define NRF_STATUS_TX_DS 0x20
#define NRF_STATUS_MAX_RT 0x10 #define NRF_STATUS_MAX_RT 0x10
#define NRF_FEATURE_EN_DPL 0x04
#define NRF_DYNPD_DPL_P0 0x01
#define BME280_ADDR 0x76 #define BME280_ADDR 0x76
#define BME280_REG_ID 0xD0 #define BME280_REG_ID 0xD0
#define BME280_REG_RESET 0xE0 #define BME280_REG_RESET 0xE0
@@ -85,14 +116,40 @@
#define BME280_REG_CALIB26 0xE1 #define BME280_REG_CALIB26 0xE1
/** /**
* @brief Initialize the watchdog timer for periodic wake-up. * @brief Load the device identifier from EEPROM.
* *
* Configures the watchdog timer with a ~8 second interval using prescaler * @return Stored device id, or DEVICE_ID_UNASSIGNED if none is stored
* values WDP3 and WDP0, enables the watchdog interrupt, and clears the
* MCU reset flag. The watchdog interrupt service routine increments
* wdt_wake_count on each trigger.
*/ */
static void zh_wdt_init(void); 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 the SPI peripheral for nRF24L01 communication.
*
* Configures PB2 (CE), PB3 (MISO), and PB5 (SCK) as output, sets PB2
* high, and configures the SPI peripheral for master mode with Fck/16
* clock divider. The SS pin is not managed by hardware and must be
* controlled manually via PB2.
*/
static void zh_spi_init(void);
/**
* @brief Transfer one byte over SPI and return the received byte.
*
* Writes the data to SPDR, waits for the transfer complete flag, then
* returns the contents of SPDR.
*
* @param data Byte to transmit
*
* @return Received byte
*/
static uint8_t zh_spi_transfer(uint8_t data);
/** /**
* @brief Initialize the hardware TWI (I2C) peripheral. * @brief Initialize the hardware TWI (I2C) peripheral.
@@ -168,6 +225,18 @@ static uint8_t zh_i2c_read_ack(void);
*/ */
static uint8_t zh_i2c_read_nack(void); static uint8_t zh_i2c_read_nack(void);
/**
* @brief Initialize the BME280 sensor and read calibration data.
*
* Verifies the device ID, performs a soft reset, reads all calibration
* registers (33 bytes total), and configures humidity and filter settings.
* A 100 ms delay is applied after power-up before reading the device ID.
*
* @return true if the sensor is present and initialized successfully
* @return false if the device ID does not match or a register operation fails
*/
static bool zh_bme280_init(void);
/** /**
* @brief Write a register value to the BME280 sensor over TWI. * @brief Write a register value to the BME280 sensor over TWI.
* *
@@ -199,18 +268,6 @@ static bool zh_bme280_write_reg(uint8_t reg, uint8_t value);
*/ */
static bool zh_bme280_read_reg(uint8_t reg, uint8_t *buf, uint8_t len); static bool zh_bme280_read_reg(uint8_t reg, uint8_t *buf, uint8_t len);
/**
* @brief Initialize the BME280 sensor and read calibration data.
*
* Verifies the device ID, performs a soft reset, reads all calibration
* registers (33 bytes total), and configures humidity and filter settings.
* A 100 ms delay is applied after power-up before reading the device ID.
*
* @return true if the sensor is present and initialized successfully
* @return false if the device ID does not match or a register operation fails
*/
static bool zh_bme280_init(void);
/** /**
* @brief Read raw sensor data from the BME280. * @brief Read raw sensor data from the BME280.
* *
@@ -264,28 +321,6 @@ static float zh_bme280_compensate_pressure(int32_t adc_p);
*/ */
static float zh_bme280_compensate_humidity(int32_t adc_h); static float zh_bme280_compensate_humidity(int32_t adc_h);
/**
* @brief Initialize the SPI peripheral for nRF24L01 communication.
*
* Configures PB2 (CE), PB3 (MISO), and PB5 (SCK) as output, sets PB2
* high, and configures the SPI peripheral for master mode with Fck/2
* clock divider. The SS pin is not managed by hardware and must be
* controlled manually via PB2.
*/
static void zh_spi_init(void);
/**
* @brief Transfer one byte over SPI and return the received byte.
*
* Writes the data to SPDR, waits for the transfer complete flag, then
* returns the contents of SPDR.
*
* @param data Byte to transmit
*
* @return Received byte
*/
static uint8_t zh_spi_transfer(uint8_t data);
/** /**
* @brief Initialize the nRF24L01 radio module. * @brief Initialize the nRF24L01 radio module.
* *
@@ -295,6 +330,15 @@ static uint8_t zh_spi_transfer(uint8_t data);
*/ */
static void zh_nrf24_init(void); 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 packet via the nRF24L01 module. * @brief Transmit a packet via the nRF24L01 module.
* *
@@ -308,6 +352,29 @@ static void zh_nrf24_init(void);
*/ */
static void zh_nrf24_send(uint8_t *buf, uint8_t size); 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 command to the nRF24L01 without a data payload.
*
* Asserts CSN low, transfers the command byte, then deasserts CSN.
* Used for commands such as FLUSH_TX, FLUSH_RX, and NOP.
*
* @param cmd Command byte to send
*
* @return SPI status register value after the command byte
*/
static uint8_t zh_nrf24_send_command(uint8_t cmd);
/** /**
* @brief Write a payload to the nRF24L01 using a write command. * @brief Write a payload to the nRF24L01 using a write command.
* *
@@ -350,16 +417,67 @@ static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val);
static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count); static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count);
/** /**
* @brief Send a command to the nRF24L01 without a data payload. * @brief Read the received payload from the nRF24L01+ into a buffer.
* *
* Asserts CSN low, transfers the command byte, then deasserts CSN. * @param buf Pointer to the destination buffer (must not be NULL)
* Used for commands such as FLUSH_TX, FLUSH_RX, and NOP. * @param count Number of bytes to read from the RX FIFO
*
* @param cmd Command byte to send
*
* @return SPI status register value after the command byte
*/ */
static uint8_t zh_nrf24_send_command(uint8_t cmd); 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 AVR ADC. * @brief Measure the battery voltage using the AVR ADC.
@@ -373,22 +491,39 @@ static uint8_t zh_nrf24_send_command(uint8_t cmd);
static float zh_get_battery_voltage(void); static float zh_get_battery_voltage(void);
static volatile uint8_t wdt_wake_count = 0; static volatile uint8_t wdt_wake_count = 0;
static volatile uint8_t nrf_event = false;
static uint8_t bme280_calib[33] = {0}; static uint8_t bme280_calib[33] = {0};
static int32_t bme280_t_fine = 0; static int32_t bme280_t_fine = 0;
static bool bme280_present = false; static bool bme280_present = false;
zh_rf_sensor_transmitted_data_t transmitted_data = {DEVICE_ID, ZH_RF_SENSOR_BME280, 0, 0, 0, 0, 0}; zh_rf_sensor_transmitted_data_t transmitted_data = {0};
int main(void) int main(void)
{ {
transmitted_data.type = ZH_RF_SENSOR_BME280;
MCUSR &= ~(1 << WDRF);
wdt_disable();
cli(); cli();
zh_i2c_init(); zh_i2c_init();
bme280_present = zh_bme280_init(); bme280_present = zh_bme280_init();
zh_spi_init(); zh_spi_init();
zh_nrf24_init(); zh_nrf24_init();
zh_wdt_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN); set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei(); 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_RF_MAX_PAYLOAD_SIZE];
if (zh_rf_sensor_build_payload(&transmitted_data, probe, sizeof(probe)) == 0)
{
zh_enter_final_sleep();
}
}
zh_wdt_start(WDT_T_8S);
int32_t adc_t = 0; int32_t adc_t = 0;
int32_t adc_p = 0; int32_t adc_p = 0;
int32_t adc_h = 0; int32_t adc_h = 0;
@@ -396,11 +531,16 @@ int main(void)
{ {
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true)
{ {
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0); transmitted_data.battery = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 = (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t)); transmitted_data.value_2 = (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t) * 10.0);
transmitted_data.value_3 = (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062); transmitted_data.value_3 = (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062 * 10.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h)); transmitted_data.value_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h) * 10.0);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data)); uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
uint8_t size = zh_rf_sensor_build_payload(&transmitted_data, payload, sizeof(payload));
if (size > 0)
{
zh_nrf24_send(payload, size);
}
} }
else else
{ {
@@ -423,11 +563,16 @@ int main(void)
{ {
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true)
{ {
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0); transmitted_data.battery = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 = (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t)); transmitted_data.value_2 = (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
transmitted_data.value_3 = (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062); transmitted_data.value_3 = (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062);
transmitted_data.value_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h)); transmitted_data.value_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data)); uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
uint8_t size = zh_rf_sensor_build_payload(&transmitted_data, payload, sizeof(payload));
if (size > 0)
{
zh_nrf24_send(payload, size);
}
} }
else else
{ {
@@ -439,20 +584,49 @@ int main(void)
return 0; return 0;
} }
static void zh_wdt_init(void)
{
cli();
wdt_reset();
MCUSR &= ~(1 << WDRF);
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);
sei();
}
ISR(WDT_vect) ISR(WDT_vect)
{ {
WDTCSR |= (1 << WDIE); WDTCSR |= (1 << WDIE);
++wdt_wake_count; if (wdt_wake_count < WDT_WAKE_COUNT)
{
++wdt_wake_count;
}
}
ISR(INT0_vect)
{
nrf_event = true;
}
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_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_i2c_init(void) static void zh_i2c_init(void)
@@ -460,14 +634,14 @@ static void zh_i2c_init(void)
DDRC &= ~((1 << PC4) | (1 << PC5)); DDRC &= ~((1 << PC4) | (1 << PC5));
PORTC |= (1 << PC4) | (1 << PC5); PORTC |= (1 << PC4) | (1 << PC5);
TWSR = 0x00; TWSR = 0x00;
TWBR = ZH_TWI_TWBR; TWBR = TWI_TWBR;
TWCR = (1 << TWEN); TWCR = (1 << TWEN);
} }
static bool zh_i2c_wait(void) static bool zh_i2c_wait(void)
{ {
uint16_t timeout = 65535; uint16_t timeout = 65535;
while (!(TWCR & (1 << TWINT))) while ((TWCR & (1 << TWINT)) == 0)
{ {
if (--timeout == 0) if (--timeout == 0)
{ {
@@ -492,7 +666,7 @@ static void zh_i2c_stop(void)
{ {
TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
uint16_t timeout = 1000; uint16_t timeout = 1000;
while ((TWCR & (1 << TWSTO)) && --timeout) while ((TWCR & (1 << TWSTO)) != 0 && --timeout)
{ {
} }
} }
@@ -529,6 +703,38 @@ static uint8_t zh_i2c_read_nack(void)
return TWDR; return TWDR;
} }
static bool zh_bme280_init(void)
{
_delay_ms(100);
uint8_t id = 0;
if (zh_bme280_read_reg(BME280_REG_ID, &id, sizeof(id)) == false || id != 0x60)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_RESET, 0xB6) == false)
{
return false;
}
_delay_ms(10);
if (zh_bme280_read_reg(BME280_REG_CALIB00, bme280_calib, 26) == false)
{
return false;
}
if (zh_bme280_read_reg(BME280_REG_CALIB26, bme280_calib + 26, 7) == false)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_CTRL_HUM, 0x01) == false)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_CONFIG, 0x00) == false)
{
return false;
}
return true;
}
static bool zh_bme280_write_reg(uint8_t reg, uint8_t value) static bool zh_bme280_write_reg(uint8_t reg, uint8_t value)
{ {
if (zh_i2c_start() == false) if (zh_i2c_start() == false)
@@ -591,38 +797,6 @@ static bool zh_bme280_read_reg(uint8_t reg, uint8_t *buf, uint8_t len)
return true; return true;
} }
static bool zh_bme280_init(void)
{
_delay_ms(100);
uint8_t id = 0;
if (zh_bme280_read_reg(BME280_REG_ID, &id, sizeof(id)) == false || id != 0x60)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_RESET, 0xB6) == false)
{
return false;
}
_delay_ms(10);
if (zh_bme280_read_reg(BME280_REG_CALIB00, bme280_calib, 26) == false)
{
return false;
}
if (zh_bme280_read_reg(BME280_REG_CALIB26, bme280_calib + 26, 7) == false)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_CTRL_HUM, 0x01) == false)
{
return false;
}
if (zh_bme280_write_reg(BME280_REG_CONFIG, 0x00) == false)
{
return false;
}
return true;
}
static bool zh_bme280_read(int32_t *temperature, int32_t *pressure, int32_t *humidity) static bool zh_bme280_read(int32_t *temperature, int32_t *pressure, int32_t *humidity)
{ {
if (zh_bme280_write_reg(BME280_REG_CTRL_MEAS, 0x25) == false) if (zh_bme280_write_reg(BME280_REG_CTRL_MEAS, 0x25) == false)
@@ -709,22 +883,6 @@ static float zh_bme280_compensate_humidity(int32_t adc_h)
return (float)(v_x1_u32r >> 12) / 1024.0; return (float)(v_x1_u32r >> 12) / 1024.0;
} }
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) static void zh_nrf24_init(void)
{ {
_delay_ms(100); _delay_ms(100);
@@ -733,6 +891,7 @@ static void zh_nrf24_init(void)
DDRD &= ~(1 << PD2); DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2); PORTD |= (1 << PD2);
uint64_t gateway_address = GATEWAY_ADDRESS; 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_CONFIG, 0x00);
zh_nrf24_write_register(NRF_REG_EN_AA, 0x01); zh_nrf24_write_register(NRF_REG_EN_AA, 0x01);
zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01); zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01);
@@ -743,9 +902,22 @@ static void zh_nrf24_init(void)
zh_nrf24_write_buff_register(NRF_REG_TX_ADDR, (uint8_t *)&gateway_address, 3); 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_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_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); 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) 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); zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
@@ -769,6 +941,27 @@ 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); 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) static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count)
{ {
PORTB &= ~(1 << PB2); PORTB &= ~(1 << PB2);
@@ -795,12 +988,240 @@ static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t c
return zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count); return zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count);
} }
static uint8_t zh_nrf24_send_command(uint8_t cmd) static void zh_nrf24_read_payload(uint8_t *buf, uint8_t count)
{ {
PORTB &= ~(1 << PB2); PORTB &= ~(1 << PB2);
uint8_t status = zh_spi_transfer(cmd); zh_spi_transfer(NRF_CMD_R_RX_PAYLOAD);
while (count-- != 0)
{
*buf++ = zh_spi_transfer(0xFF);
}
PORTB |= (1 << PB2); PORTB |= (1 << PB2);
return status; }
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_rf_sensor_pairing_data_t pairing_request;
pairing_request.type = (uint8_t)transmitted_data.type;
pairing_request.magic = ZH_RF_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_rf_sensor_pairing_state_t state = ZH_RF_PAIR_SEND;
uint8_t attempts = 0;
for (;;)
{
switch (state)
{
case ZH_RF_PAIR_SEND:
nrf_event = false;
zh_nrf24_send_no_wait((uint8_t *)&pairing_request, ZH_RF_PAIRING_PACKET_SIZE);
state = ZH_RF_PAIR_TX_WAIT;
break;
case ZH_RF_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_RF_PAIR_RX_WAIT;
}
else
{
state = ZH_RF_PAIR_BACKOFF;
}
}
else
{
state = ZH_RF_PAIR_BACKOFF;
}
break;
case ZH_RF_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_RF_MAX_PAYLOAD_SIZE)
{
zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR);
state = ZH_RF_PAIR_BACKOFF;
break;
}
if (width != ZH_RF_PAIRING_PACKET_SIZE)
{
uint8_t dummy[ZH_RF_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_RF_PAIR_BACKOFF;
break;
}
zh_rf_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_RF_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_RF_PAIR_BACKOFF;
}
else
{
state = ZH_RF_PAIR_BACKOFF;
}
break;
case ZH_RF_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_RF_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;
TWCR = 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) static float zh_get_battery_voltage(void)
@@ -809,11 +1230,11 @@ static float zh_get_battery_voltage(void)
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
_delay_ms(10); _delay_ms(10);
ADCSRA |= (1 << ADSC); ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC)) while ((ADCSRA & (1 << ADSC)) != 0)
{ {
} }
ADCSRA |= (1 << ADSC); ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC)) while ((ADCSRA & (1 << ADSC)) != 0)
{ {
} }
uint16_t adc = ADCL; uint16_t adc = ADCL;
@@ -824,4 +1245,4 @@ static float zh_get_battery_voltage(void)
return 0.0; return 0.0;
} }
return (1024.0 * 1.1) / (float)adc; return (1024.0 * 1.1) / (float)adc;
} }
+141 -67
View File
@@ -4,17 +4,18 @@
**zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+. **zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. Идентификатор устройства получается автоматически по воздуху в процедуре сопряжения со шлюзом и сохраняется в EEPROM.
### Основные возможности ### Основные возможности
1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна. 1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна.
2. **Измерение климата** — температура, влажность и атмосферное давление датчиком BME280 по шине I2C. 2. **Измерение климата** — температура, влажность и атмосферное давление датчиком BME280 по шине I2C.
3. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В. 3. **Автоматическое сопряжение (pairing)** — запрос идентификатора устройства у шлюза по воздуху, хранение ID в EEPROM.
4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз. 4. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB. 5. **Работа по протоколу nRF24L01+** — передача и приём через SPI, адресная доставка на шлюз, динамическая длина пакета.
6. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут. 6. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
7. **Автономная работа** — не требует внешних подтягивающих резисторов. 7. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут.
8. **Автономная работа** — не требует внешних подтягивающих резисторов (используются внутренние pull-up).
--- ---
@@ -22,38 +23,51 @@
### Структура данных ### Структура данных
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины. Компонент не использует динамическое выделение памяти. Сырые показания хранятся в статических переменных, калибровочные коэффициенты BME280 — в массиве `bme280_calib[33]`. Перед передачей данные сериализуются функцией `zh_rf_sensor_build_payload()` из внешней библиотеки `zh_rf_config` в буфер `uint8_t[ZH_RF_MAX_PAYLOAD_SIZE]` (32 байта) в формате little-endian.
#### Формат передаваемого пакета #### Формат передаваемого пакета
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт). Данные формируются в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из библиотеки `zh_rf_config`, затем сериализуются в полезную нагрузку. Для типа `ZH_RF_SENSOR_BME280` полный размер пакета — 12 байт (заголовок id + type + battery и три поля измерений, каждое `int16_t` в little-endian).
| Поле | Тип | Описание | | Поле | Тип | Описание |
| ------ | ----- | ---------- | | ------ | ----- | ---------- |
| `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) | | `id` | `int16_t` | Идентификатор устройства, назначенный шлюзом при сопряжении |
| `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_BME280`) | | `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_BME280`) |
| `value_1` | `int16_t` | Влажность (%, например 45 = 45 %) | | `battery` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
| `battery_voltage` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) | | `value_1` | `int16_t` | Влажность (% × 10, например 453 = 45.3 %) |
| `value_2` | `int16_t` | Температура (°C, например 25 = 25 °C) | | `value_2` | `int16_t` | Температура (°C × 10, например 253 = 25.3 °C) |
| `value_3` | `int16_t` | Давление (мм рт. ст.) | | `value_3` | `int16_t` | Давление (мм рт. ст. × 10) |
| `value_4` | `int16_t` | Зарезервировано (равно 0) |
Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BME280` из библиотеки `zh_rf_config`. Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BME280` из библиотеки `zh_rf_config`.
#### Формат пакета сопряжения
Пакет сопряжения — структура `zh_rf_sensor_pairing_data_t` (5 байт, `ZH_RF_PAIRING_PACKET_SIZE`):
| Поле | Тип | Описание |
| ------ | ----- | ---------- |
| `type` | `uint8_t` | Тип датчика (код `ZH_RF_SENSOR_BME280`) |
| `magic` | `uint8_t` | Маркер направления: `ZH_RF_PAIRING_REQ_MAGIC` (0xA5) в запросе, `ZH_RF_PAIRING_ACK_MAGIC` (0x5A) в ответе |
| `token_hi` | `uint8_t` | Старший байт псевдослучайного токена |
| `token_lo` | `uint8_t` | Младший байт псевдослучайного токена |
| `id` | `uint8_t` | Идентификатор, назначенный шлюзом (в ответе) |
### Конфигурационные макросы ### Конфигурационные макросы
| Макрос | Значение | Описание | | Макрос | Значение | Описание |
| -------- | ---------- | ---------- | | -------- | ---------- | ---------- |
| `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 для хранения ID устройства |
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до пробуждения (≈ 5 минут) | | `DEVICE_ID_UNASSIGNED` | `0xFF` | Признак отсутствующего (несопряжённого) идентификатора |
| `ZH_TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI | | `PAIRING_TIMEOUT_ATTEMPTS` | `7` | Максимальное число попыток сопряжения |
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до цикла измерения (≈ 5 минут) |
| `TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
| `BME280_ADDR` | `0x76` | 7-битный адрес датчика BME280 на шине I2C | | `BME280_ADDR` | `0x76` | 7-битный адрес датчика BME280 на шине I2C |
### Управление памятью ### Управление памятью
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` объявлена как `static` и заполняется при старте (идентификатор и тип). В цикле обновляются все поля данных: `battery_voltage`, `value_1` (влажность), `value_2` (температура), `value_3` (давление). Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` заполняется при старте (тип устройства, затем идентификатор из EEPROM или из сопряжения). В цикле обновляются поля `battery`, `value_1` (влажность), `value_2` (температура), `value_3` (давление). Идентификатор устройства сохраняется в EEPROM адресом `0x00` (`eeprom_update_byte`).
--- ---
@@ -65,11 +79,23 @@
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения | | `zh_wdt_start()` | Запуск watchdog-таймера в режиме прерывания с указанным таймаутом (биты WDP3:0) |
| `zh_wdt_stop()` | Остановка watchdog-таймера |
| `zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 | | `zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
| `zh_bme280_init()` | Инициализация датчика BME280: проверка CHIP_ID, сброс, чтение калибровки, настройка режима | | `zh_bme280_init()` | Инициализация датчика BME280: проверка CHIP_ID, сброс, чтение калибровки, настройка режима |
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/2) | | `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи | | `zh_nrf24_init()` | Инициализация трансивера nRF24L01+: адреса TX/RX, канал, ретрансмиты, динамические пакеты, CRC |
| `zh_nrf24_interrupt_init()` | Настройка внешнего прерывания INT0 (PD2, падающий фронт) для сигнала IRQ трансивера |
### Идентификатор устройства и сопряжение
| Функция | Описание |
| --------- | ---------- |
| `zh_load_device_id()` | Чтение ID устройства из EEPROM (возвращает `DEVICE_ID_UNASSIGNED` при 0x00/0xFF) |
| `zh_save_device_id()` | Сохранение ID устройства в EEPROM через `eeprom_update_byte` |
| `zh_get_random_token()` | Генерация 16-битного псевдослучайного токена из MCUSR и шума АЦП (никогда не 0) |
| `zh_do_pairing()` | Неблокирующая процедура сопряжения: запрос → ожидание ACK в RX-режиме → backoff; возвращает назначенный ID |
| `zh_enter_final_sleep()` | Переход в постоянный сон Power-down до сброса (отключение прерываний, WDT, радио, тактов периферии) |
### Измерение ### Измерение
@@ -81,16 +107,19 @@
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `zh_bme280_read()` | Чтение 8 сырых байт из BME280 и распаковка ADC значений температуры, давления, влажности | | `zh_bme280_read()` | Перевод BME280 в forced mode, чтение 8 сырых байт и распаковка ADC значений температуры, давления, влажности |
| `zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) | | `zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
| `zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) | | `zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
| `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %, диапазон [0, 100]) | | `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %) |
### Передача данных ### Передача данных nRF24L01+
| Функция | Описание | | Функция | Описание |
|---------|----------| | --------- | ---------- |
| `zh_nrf24_send()` | Передача буфера через nRF24L01+: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом 100 мс, сброс статуса | | `zh_nrf24_send()` | Передача буфера: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом ~100 мс, сброс статуса |
| `zh_nrf24_send_no_wait()` | Запуск передачи без ожидания завершения (импульс CE 15 мкс), завершение сигнализируется IRQ |
| `zh_nrf24_enter_rx()` | Переход в режим приёма на pipe 0 (PRIM_RX, очистка FIFO, CE) |
| `zh_nrf24_enter_power_down()` | Перевод трансивера в режим Power Down |
### Низкоуровневые SPI-операции ### Низкоуровневые SPI-операции
@@ -101,6 +130,8 @@
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ | | `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой (CSN управляется вручную через PB2) | | `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой (CSN управляется вручную через PB2) |
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса | | `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
| `zh_nrf24_read_payload()` | Чтение принятой полезной нагрузки из RX FIFO (`R_RX_PAYLOAD`) |
| `zh_nrf24_read_payload_width()` | Чтение длины пакета в RX FIFO (`R_RX_PL_WID`) |
### Низкоуровневые TWI-операции (аппаратный I2C) ### Низкоуровневые TWI-операции (аппаратный I2C)
@@ -124,33 +155,50 @@
## Потокобезопасность ## Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (`ISR(WDT_vect)`), которая увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле. Компонент работает на bare-metal AVR без RTOS. Точки синхронизации — два ISR:
- `ISR(WDT_vect)` — увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`, инкремент защищён проверкой верхнего предела), заново включает бит `WDIE`.
- `ISR(INT0_vect)` — выставляет флаг `nrf_event` (`static volatile uint8_t`) при фронте на пине IRQ трансивера.
Главный цикл и машина состояний сопряжения проверяют эти флаги после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
--- ---
## Алгоритмы ## Алгоритмы
### Алгоритм загрузки идентификатора и сопряжения
- При старте читается ID из EEPROM (`eeprom_read_byte`, адрес `0x00`); значения `0x00` и `0xFF` считаются признаком несопряжённого устройства.
- При отсутствии ID выполняется `zh_do_pairing()` — неблокирующая машина состояний (`ZH_RF_PAIR_SEND` → `ZH_RF_PAIR_TX_WAIT` → `ZH_RF_PAIR_RX_WAIT` → `ZH_RF_PAIR_BACKOFF`):
- генерируется токен (`zh_get_random_token()` из MCUSR и шума АЦП);
- отправляется запрос `zh_rf_sensor_pairing_data_t` (magic `0xA5`) без ожидания (`zh_nrf24_send_no_wait`);
- после TX_DS трансивер переводится в RX-режим, ожидание ответа со сном и WDT-таймаутом 250 мс;
- ответ валидируется по magic `0x5A`, типу, токену и ненулевому ID; ширина пакета проверяется через `R_RX_PL_WID`;
- при неудаче — backoff-пауза ~4 с (WDT), не более `PAIRING_TIMEOUT_ATTEMPTS` (7) попыток.
- Назначенный ID сохраняется в EEPROM и записывается в `transmitted_data.id`.
- После инициализации выполняется пробная сборка пакета `zh_rf_sensor_build_payload()`; при нулевом результате (неизвестный тип) — переход в постоянный сон.
### Алгоритм инициализации BME280 ### Алгоритм инициализации BME280
- Задержка 100 мс после включения. - Задержка 100 мс после включения.
- Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`). - Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`).
- Сброс записью `0xB6` в регистр `BME280_REG_RESET`. - Сброс записью `0xB6` в регистр `BME280_REG_RESET`, задержка 10 мс.
- Чтение калибровочных коэффициентов: 26 байт из `BME280_REG_CALIB00` и 7 байт из `BME280_REG_CALIB26` (всего 33 байта, массив `bme280_calib`). - Чтение калибровочных коэффициентов: 26 байт из `BME280_REG_CALIB00` и 7 байт из `BME280_REG_CALIB26` (всего 33 байта, массив `bme280_calib`).
- Конфигурация влажности: `ctrl_hum = 0x01` (oversampling x1). - Конфигурация влажности: `ctrl_hum = 0x01` (oversampling x1).
- Конфигурация режима: `config = 0x00` (фильтр off, standby off). - Конфигурация режима: `config = 0x00` (фильтр off, standby off).
- Конфигурация измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode). - Запуск измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode) в `zh_bme280_read()` перед каждым чтением.
### Алгоритм компенсации BME280 ### Алгоритм компенсации BME280
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280. Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280.
- **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C. Деление на `dig_t1` не выполняется (проверка `dig_t1 == 0` не реализована). - **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C.
- **Давление**: `zh_bme280_compensate_pressure()` — возвращает результат в Па. При `var1 == 0` (знаменатель ноль) возвращает 0.0. - **Давление**: `zh_bme280_compensate_pressure()` — возвращает результат в Па. При `var1 == 0` (знаменатель ноль) возвращает 0.0.
- **Влажность**: `zh_bme280_compensate_humidity()` — результат ограничивается диапазоном [0, 419430400] на промежуточном шаге, затем делится на 1024.0 и возвращается в процентах. - **Влажность**: `zh_bme280_compensate_humidity()` — результат ограничивается диапазоном [0, 419430400] на промежуточном шаге, затем делится на 1024.0 и возвращается в процентах.
### Алгоритм аппаратного TWI ### Алгоритм аппаратного TWI
- Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = ZH_TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц. - Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц.
- Стартовое/повторное стартовое условие: установка `TWSTA`, ожидание `TWINT`, проверка статуса (`0x08` — START, `0x10` — REPEATED START). - Стартовое/повторное стартовое условие: установка `TWSTA`, ожидание `TWINT`, проверка статуса (`0x08` — START, `0x10` — REPEATED START).
- Передача байта: запись в `TWDR`, сброс `TWINT`, ожидание завершения, проверка статусов подтверждения (`0x18`/`0x28` — данные, `0x40` — адрес чтения). - Передача байта: запись в `TWDR`, сброс `TWINT`, ожидание завершения, проверка статусов подтверждения (`0x18`/`0x28` — данные, `0x40` — адрес чтения).
- Приём байта: установка `TWEA` для всех байт кроме последнего, чтение `TWDR` после `TWINT`. - Приём байта: установка `TWEA` для всех байт кроме последнего, чтение `TWDR` после `TWINT`.
@@ -162,18 +210,21 @@
- Переход в 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` (размер пакета определяет `zh_rf_sensor_build_payload()`, динамические пакеты включены: `FEATURE = EN_DPL`, `DYNPD = DPL_P0`).
- Импульс CE длительностью 20 мкс для запуска передачи. - Импульс CE для запуска передачи.
- Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс. - Ожидание завершения по пину IRQ (PD2) с таймаутом ~100 мс.
- Сброс флагов статуса, очистка FIFO, переход в Power Down. - Сброс флагов статуса, очистка FIFO, переход в Power Down.
Для процедуры сопряжения используется вариант без блокировки (`zh_nrf24_send_no_wait`, импульс CE 15 мкс) с завершением по прерыванию INT0.
### Алгоритм сна и пробуждения ### Алгоритм сна и пробуждения
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDP3 | WDP0`), без сброса MCU. - Инициализация WDT: прерывание по таймауту ~8 секунд (`WDT_T_8S`), без сброса MCU.
- ISR `WDT_vect`: увеличивает `wdt_wake_count`, заново включает бит `WDIE`. - ISR `WDT_vect`: увеличивает `wdt_wake_count` (до предела `WDT_WAKE_COUNT`), заново включает бит `WDIE`.
- Главный цикл: переход в Power-down (`sleep_cpu()`). После пробуждения проверяется `wdt_wake_count >= WDT_WAKE_COUNT` (38 × 8 с ≈ 5 минут). - Главный цикл: переход в Power-down (`sleep_cpu()`). После пробуждения проверяется `wdt_wake_count >= WDT_WAKE_COUNT` (38 × 8 с ≈ 5 минут).
- При достижении порога: обнуление счётчика, повторная инициализация BME280 (если датчик недоступен), чтение показаний и передача пакета через nRF24L01+. - При достижении порога: обнуление счётчика, повторная инициализация BME280 (если датчик недоступен), чтение показаний, сериализация пакета и передача через nRF24L01+.
- Первое измерение выполняется сразу после инициализации перед входом в цикл. - Первое измерение выполняется сразу после инициализации и сопряжения, до входа в цикл.
- `zh_enter_final_sleep()` — аварийный постоянный сон: отключение прерываний, WDT, радио, АЦП, TWI, SPI и тактов всей неиспользуемой периферии (PRR).
--- ---
@@ -184,30 +235,43 @@
```c ```c
int main(void) int main(void)
{ {
transmitted_data.type = ZH_RF_SENSOR_BME280;
MCUSR &= ~(1 << WDRF);
wdt_disable();
cli(); cli();
zh_i2c_init(); zh_i2c_init();
bme280_present = zh_bme280_init(); bme280_present = zh_bme280_init();
zh_spi_init(); zh_spi_init();
zh_nrf24_init(); zh_nrf24_init();
zh_wdt_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN); set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei(); 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;
zh_wdt_start(WDT_T_8S);
int32_t adc_t = 0, adc_p = 0, adc_h = 0; int32_t adc_t = 0, adc_p = 0, adc_h = 0;
// Первое измерение сразу после инициализации // Первое измерение сразу после инициализации и сопряжения
if (bme280_present == true) { if (bme280_present == true) {
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) { if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
transmitted_data.battery_voltage = transmitted_data.battery =
(int16_t)lroundf(zh_get_battery_voltage() * 100.0); (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 = transmitted_data.value_2 =
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t)); (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t) * 10.0);
transmitted_data.value_3 = transmitted_data.value_3 =
(int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062); (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062 * 10.0);
transmitted_data.value_1 = transmitted_data.value_1 =
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h)); (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h) * 10.0);
zh_nrf24_send((uint8_t *)&transmitted_data, uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
sizeof(transmitted_data)); uint8_t size = zh_rf_sensor_build_payload(&transmitted_data,
payload, sizeof(payload));
if (size > 0) {
zh_nrf24_send(payload, size);
}
} else { } else {
bme280_present = false; bme280_present = false;
} }
@@ -225,7 +289,7 @@ int main(void)
} }
if (bme280_present == true) { if (bme280_present == true) {
if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) { if (zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
transmitted_data.battery_voltage = transmitted_data.battery =
(int16_t)lroundf(zh_get_battery_voltage() * 100.0); (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_2 = transmitted_data.value_2 =
(int16_t)lroundf(zh_bme280_compensate_temperature(adc_t)); (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
@@ -233,8 +297,12 @@ int main(void)
(int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062); (int16_t)lroundf(zh_bme280_compensate_pressure(adc_p) * 0.00750062);
transmitted_data.value_1 = transmitted_data.value_1 =
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h)); (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
zh_nrf24_send((uint8_t *)&transmitted_data, uint8_t payload[ZH_RF_MAX_PAYLOAD_SIZE];
sizeof(transmitted_data)); uint8_t size = zh_rf_sensor_build_payload(&transmitted_data,
payload, sizeof(payload));
if (size > 0) {
zh_nrf24_send(payload, size);
}
} else { } else {
bme280_present = false; bme280_present = false;
} }
@@ -247,8 +315,9 @@ int main(void)
### Изменение конфигурации под другое устройство ### Изменение конфигурации под другое устройство
```c ```c
// Измените идентификатор для нового устройства // Идентификатор устройства назначается шлюзом при сопряжении
#define DEVICE_ID 7 // Уникальный ID в сети // и хранится в EEPROM (адрес 0x00). Для повторного сопряжения
// обнулите ячейку EEPROM.
#define GATEWAY_CHANNEL 120 #define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF #define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_RF_SENSOR_BME280 // Тип устройства задаётся перечислением ZH_RF_SENSOR_BME280
@@ -263,10 +332,10 @@ int main(void)
// PB3 -> MOSI (назначается аппаратным SPI) // PB3 -> MOSI (назначается аппаратным SPI)
// PB5 -> SCK (назначается аппаратным SPI) // PB5 -> SCK (назначается аппаратным SPI)
// PB1 -> CE (управление передачей, выход) // PB1 -> CE (управление передачей, выход)
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC) // PD2 -> IRQ (вход, активный низкий, INT0, с подтяжкой к VCC)
// BME280 (I2C, аппаратный TWI): // BME280 (I2C, аппаратный TWI):
// PC4 -> SDA (аппаратный TWI, pull-up) // PC4 -> SDA (аппаратный TWI, внутренний pull-up)
// PC5 -> SCL (аппаратный TWI, pull-up) // PC5 -> SCL (аппаратный TWI, внутренний pull-up)
``` ```
--- ---
@@ -279,7 +348,7 @@ int main(void)
## Ошибки ## Ошибки
Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`). Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). `zh_load_device_id` возвращает `DEVICE_ID_UNASSIGNED` при отсутствии сохранённого ID. Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
| Условие | Поведение | | Условие | Поведение |
| --------- | ---------- | | --------- | ---------- |
@@ -291,21 +360,24 @@ int main(void)
| Ошибка чтения BME280 | `zh_bme280_read()` возвращает false, пакет не формируется, `bme280_present = false` | | Ошибка чтения BME280 | `zh_bme280_read()` возвращает false, пакет не формируется, `bme280_present = false` |
| `var1 == 0` при компенсации давления | `zh_bme280_compensate_pressure()` возвращает 0.0 | | `var1 == 0` при компенсации давления | `zh_bme280_compensate_pressure()` возвращает 0.0 |
| Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной | | Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной |
| Нет ответа шлюза при сопряжении | После 7 попыток (`PAIRING_TIMEOUT_ATTEMPTS`) — переход в постоянный сон до сброса |
| Ширина принятого пакета ≠ 5 байт / > 32 байт | FIFO очищается, состояние сопряжения переходит в backoff |
| `zh_rf_sensor_build_payload()` вернул 0 | Неизвестный тип датчика или слишком мал буфер — переход в постоянный сон |
--- ---
## Ограничения ## Ограничения
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения. 1. **Широковещательная передача данных** — приём используется только на этапе сопряжения; шлюз не подтверждает пакеты измерений на уровне приложения.
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены. 2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки. 3. **Жёстко зашитый адрес шлюза** — смена адреса или канала требует перепрошивки (ID устройства при этом хранится в EEPROM).
4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены. 4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены.
5. **Отсутствие валидации `dig_t1`** — в `zh_bme280_compensate_temperature()` нет проверки `dig_t1 == 0`, что может привести к делению на ноль. 5. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В.
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В. 6. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются.
7. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются. 7. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся.
8. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся. 8. **Давление в мм рт. ст.** — `zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.).
9. **Давление в мм рт. ст.** — `zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.). 9. **Масштабирование значений ×10 только в первом цикле** — при первом измерении температура, влажность и давление умножаются на 10 (одна дробная цифра); в цикле пробуждения значения записываются без масштабирования (см. `main`).
10. **Температура и влажность без масштабирования** — значения передаются как целые числа (°C и %), дробная часть теряется. 10. **Сопряжение требует шлюза** — без ответившего шлюза устройство после 7 попыток уходит в постоянный сон до сброса/перепрошивки.
--- ---
@@ -313,7 +385,7 @@ int main(void)
- PlatformIO с плагином `platform-atmelavr` - PlatformIO с плагином `platform-atmelavr`
- AVR-GCC (входит в состав PlatformIO) - AVR-GCC (входит в состав PlatformIO)
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`) - Библиотека `zh_rf_config` (ветка `dev`, подключается через `lib_deps` в `platformio.ini`)
- Программатор USBasp (или совместимый) - Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя) - Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
@@ -334,16 +406,18 @@ pio run -e ATmega328P -t upload
```ini ```ini
; platformio.ini ; platformio.ini
lib_deps = lib_deps =
https://github.com/aZholtikov/zh_rf_config https://github.com/aZholtikov/zh_rf_config#dev
``` ```
```c ```c
// Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола // Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола
#include "avr/io.h" #include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h" #include "avr/interrupt.h"
#include "avr/sleep.h"
#include "avr/eeprom.h"
#include "avr/wdt.h"
#include "util/delay.h" #include "util/delay.h"
#include "stdbool.h"
#include "math.h" #include "math.h"
#include "zh_rf_config.h" #include "zh_rf_config.h"
``` ```