Compare commits

..
1 Commits
Author SHA1 Message Date
aZholtikov 0512194e47 doc: updated library link 2026-10-26 21:00:01 +03:00
5 changed files with 968 additions and 1487 deletions

No files matched your search

+8 -10
View File
@@ -4,19 +4,17 @@
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. Automatic over-the-air pairing: the sensor requests a device id from the gateway and stores it in EEPROM. 3. Transmits device identifier, battery voltage, and all three BME280 sensor readings on each measurement cycle.
4. Transmits device identifier, battery voltage, and all three BME280 sensor readings (×10 scaling) on each measurement cycle. 4. Battery measurement through internal ADC with 1.1 V bandgap reference.
5. Battery measurement through internal ADC with 1.1 V bandgap reference. 5. Wake-up from Power-down mode by watchdog timer interrupt with configurable interval.
6. Wake-up from Power-down mode by watchdog timer interrupt with configurable interval. 6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, and ATMega328PB.
7. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, and ATMega328PB. 7. Hardware TWI (I2C) for BME280 communication on PORTC pins PC4 (SDA) and PC5 (SCL).
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 packets and assign a device id during pairing. 1. A compatible RF Gateway is required to receive and process transmitted packets.
2. The watchdog timer interval is fixed at 8 seconds; the sensor transmits every 38 ticks (~5 minutes). 2. Changing gateway address or channel requires reflashing the microcontroller.
3. Changing gateway address or channel requires reflashing the microcontroller. 3. The watchdog timer interval is fixed at 8 seconds; the sensor transmits every 38 ticks (~5 minutes).
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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
[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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
[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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
[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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
[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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
[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_nrf24_config#dev https://github.com/aZholtikov/zh_nrf24_config
+827
View File
@@ -0,0 +1,827 @@
#include "avr/io.h"
#include "avr/interrupt.h"
#include "avr/sleep.h"
#include "avr/wdt.h"
#include "util/delay.h"
#include "stdbool.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
#ifndef TWBR
#define TWBR TWBR0
#endif
#ifndef TWCR
#define TWCR TWCR0
#endif
#ifndef TWSR
#define TWSR TWSR0
#endif
#ifndef TWDR
#define TWDR TWDR0
#endif
#endif
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
#define DEVICE_ID 6
#define WDT_WAKE_COUNT 38
#define ZH_TWI_TWBR 32
#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
#define BME280_ADDR 0x76
#define BME280_REG_ID 0xD0
#define BME280_REG_RESET 0xE0
#define BME280_REG_CTRL_HUM 0xF2
#define BME280_REG_CTRL_MEAS 0xF4
#define BME280_REG_CONFIG 0xF5
#define BME280_REG_DATA 0xF7
#define BME280_REG_CALIB00 0x88
#define BME280_REG_CALIB26 0xE1
/**
* @brief Initialize the watchdog timer for periodic wake-up.
*
* Configures the watchdog timer with a ~8 second interval using prescaler
* 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);
/**
* @brief Initialize the hardware TWI (I2C) peripheral.
*
* Configures PC4 (SDA) and PC5 (SCL) as inputs with pull-up enabled,
* sets the TWI bit-rate register (TWBR) and enables the TWI module
* in master mode. The hardware TWI is used for communication with
* the BME280 sensor.
*/
static void zh_i2c_init(void);
/**
* @brief Wait for the TWI interrupt flag with a timeout.
*
* Polls the TWINT bit in TWCR until it is set or the 16-bit timeout
* counter expires. Returns true if the transfer completed successfully.
*
* @return true if TWINT was set before the timeout expired
* @return false on timeout
*/
static bool zh_i2c_wait(void);
/**
* @brief Generate a TWI start (or repeated start) condition.
*
* Asserts START and waits for completion, then verifies the status code
* (0x08 = start transmitted, 0x10 = repeated start transmitted).
*
* @return true when the start condition was acknowledged by the controller
* @return false on timeout or unexpected status
*/
static bool zh_i2c_start(void);
/**
* @brief Generate a TWI stop condition.
*
* Asserts STOP and waits (bounded by a timeout loop) until the TWSTO
* bit is cleared by hardware.
*/
static void zh_i2c_stop(void);
/**
* @brief Write one byte on the TWI bus and return the ACK status.
*
* Loads TWDR, triggers transmission and checks the TWI status code.
* Returns true when the slave acknowledges the byte (statuses 0x18,
* 0x28 for data transmit, 0x40 for SLA+R transmit).
*
* @param data Byte to transmit
*
* @return true if the slave acknowledged the byte
* @return false on timeout or NACK
*/
static bool zh_i2c_write(uint8_t data);
/**
* @brief Read one byte from the TWI bus and send an ACK after reading.
*
* Enables the acknowledge bit (TWEA), triggers the read cycle and
* returns the contents of TWDR. Returns 0xFF on timeout.
*
* @return Received byte
*/
static uint8_t zh_i2c_read_ack(void);
/**
* @brief Read one byte from the TWI bus and send a NACK after reading.
*
* Disables the acknowledge bit, triggers the read cycle and returns
* the contents of TWDR. Returns 0xFF on timeout.
*
* @return Received byte
*/
static uint8_t zh_i2c_read_nack(void);
/**
* @brief Write a register value to the BME280 sensor over TWI.
*
* Initiates a TWI transaction with the BME280 address, writes the
* register address followed by the value, and stops the bus. A delay
* of 2 ms is applied after the write for the sensor's internal timing.
*
* @param reg Register address to write
* @param value Value to write to the register
*
* @return true on success
* @return false if the start/stop condition or any byte transfer failed
*/
static bool zh_bme280_write_reg(uint8_t reg, uint8_t value);
/**
* @brief Read multiple bytes from a BME280 register over TWI.
*
* Performs a repeated-start transaction: writes the register address
* then reads the specified number of bytes, sending ACK for each byte
* except the last one, where a NACK is sent.
*
* @param reg Register address to read from
* @param buf Pointer to the buffer where the data will be stored (must not be NULL)
* @param len Number of bytes to read
*
* @return true on success
* @return false if any start condition or byte transfer failed
*/
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.
*
* Configures the sensor for a measurement cycle, waits for conversion
* to complete, then reads 8 bytes of raw data and unpacks the pressure,
* temperature, and humidity ADC values.
*
* @param temperature Pointer to store the raw temperature ADC value (must not be NULL)
* @param pressure Pointer to store the raw pressure ADC value (must not be NULL)
* @param humidity Pointer to store the raw humidity ADC value (must not be NULL)
*
* @return true on success
* @return false if the register write or read operation fails
*/
static bool zh_bme280_read(int32_t *temperature, int32_t *pressure, int32_t *humidity);
/**
* @brief Compensate raw temperature using Bosch calibration coefficients.
*
* Applies the 32-bit compensation algorithm defined in the BME280 datasheet
* using calibration data read during initialization. The result is stored
* in bme280_t_fine for use by the pressure and humidity compensators.
*
* @param adc_t Raw temperature ADC value
*
* @return Compensated temperature in degrees Celsius
*/
static float zh_bme280_compensate_temperature(int32_t adc_t);
/**
* @brief Compensate raw pressure using Bosch calibration coefficients.
*
* Applies the 32-bit compensation algorithm defined in the BME280 datasheet.
* The result is in Pascals. Returns zero if the denominator becomes zero.
*
* @param adc_p Raw pressure ADC value
*
* @return Compensated pressure in Pascals
*/
static float zh_bme280_compensate_pressure(int32_t adc_p);
/**
* @brief Compensate raw humidity using Bosch calibration coefficients.
*
* Applies the 32-bit compensation algorithm defined in the BME280 datasheet.
* The result is clamped to the range [0, 100] percent.
*
* @param adc_h Raw humidity ADC value
*
* @return Compensated relative humidity in percent
*/
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.
*
* Configures the CE and IRQ pins, sets up TX and RX addresses, configures
* the channel, enables automatic retransmission, and enables CRC. The
* module is left in standby mode with RX and TX interrupts masked.
*/
static void zh_nrf24_init(void);
/**
* @brief Transmit a packet via the nRF24L01 module.
*
* Powers up the radio, flushes the TX FIFO, writes the payload, asserts
* CE high to trigger transmission, waits for the IRQ line, then deasserts
* CE. The status register is read, cleared, and the TX FIFO is flushed
* again after transmission.
*
* @param buf Pointer to the data buffer to transmit (must not be NULL)
* @param size Number of bytes to transmit
*/
static void zh_nrf24_send(uint8_t *buf, uint8_t size);
/**
* @brief Write a payload to the nRF24L01 using a write command.
*
* Asserts CSN low, transfers the command byte, writes the payload data,
* then deasserts CSN. Returns the status register value.
*
* @param cmd SPI command byte for payload write
* @param buf Pointer to the data buffer (must not be NULL)
* @param count Number of bytes to write
*
* @return SPI status register value after the command byte
*/
static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count);
/**
* @brief Write a single byte to an nRF24L01 register.
*
* Asserts CSN low, transfers the register address ORed with the write
* command mask, writes the value, then deasserts CSN.
*
* @param reg Register address
* @param val Value to write
*
* @return SPI status register value after the command byte
*/
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val);
/**
* @brief Write a buffer of data to an nRF24L01 register block.
*
* Asserts CSN low, transfers the register address ORed with the write
* command mask, writes each byte from the buffer, then deasserts CSN.
*
* @param reg Register address for the block
* @param buf Pointer to the data buffer (must not be NULL)
* @param count Number of bytes to write
*
* @return SPI status register value after the command byte
*/
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.
*
* 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 Measure the battery voltage using the AVR ADC.
*
* Configures the ADC with the internal 1.1 V reference and selects the
* 1.1 V bandgap input. Takes two conversions and returns the average
* as a voltage value. Returns zero if the ADC reading is zero.
*
* @return Battery voltage in volts
*/
static float zh_get_battery_voltage(void);
static volatile uint8_t wdt_wake_count = 0;
static uint8_t bme280_calib[33] = {0};
static int32_t bme280_t_fine = 0;
static bool bme280_present = false;
zh_rf_sensor_transmitted_data_t transmitted_data = {DEVICE_ID, ZH_RF_SENSOR_BME280, 0, 0, 0, 0, 0};
int main(void)
{
cli();
zh_i2c_init();
bme280_present = zh_bme280_init();
zh_spi_init();
zh_nrf24_init();
zh_wdt_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();
int32_t adc_t = 0;
int32_t adc_p = 0;
int32_t adc_h = 0;
if (bme280_present == 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.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_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bme280_present = false;
}
}
for (;;)
{
sleep_enable();
sleep_cpu();
sleep_disable();
if (wdt_wake_count >= WDT_WAKE_COUNT)
{
wdt_wake_count = 0;
if (bme280_present == false)
{
bme280_present = zh_bme280_init();
}
if (bme280_present == 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.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_1 = (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bme280_present = false;
}
}
}
}
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)
{
WDTCSR |= (1 << WDIE);
++wdt_wake_count;
}
static void zh_i2c_init(void)
{
DDRC &= ~((1 << PC4) | (1 << PC5));
PORTC |= (1 << PC4) | (1 << PC5);
TWSR = 0x00;
TWBR = ZH_TWI_TWBR;
TWCR = (1 << TWEN);
}
static bool zh_i2c_wait(void)
{
uint16_t timeout = 65535;
while (!(TWCR & (1 << TWINT)))
{
if (--timeout == 0)
{
return false;
}
}
return true;
}
static bool zh_i2c_start(void)
{
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
if (zh_i2c_wait() == false)
{
return false;
}
uint8_t status = TWSR & 0xF8;
return (status == 0x08 || status == 0x10);
}
static void zh_i2c_stop(void)
{
TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
uint16_t timeout = 1000;
while ((TWCR & (1 << TWSTO)) && --timeout)
{
}
}
static bool zh_i2c_write(uint8_t data)
{
TWDR = data;
TWCR = (1 << TWINT) | (1 << TWEN);
if (zh_i2c_wait() == false)
{
return false;
}
uint8_t status = TWSR & 0xF8;
return (status == 0x18 || status == 0x28 || status == 0x40);
}
static uint8_t zh_i2c_read_ack(void)
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
if (zh_i2c_wait() == false)
{
return 0xFF;
}
return TWDR;
}
static uint8_t zh_i2c_read_nack(void)
{
TWCR = (1 << TWINT) | (1 << TWEN);
if (zh_i2c_wait() == false)
{
return 0xFF;
}
return TWDR;
}
static bool zh_bme280_write_reg(uint8_t reg, uint8_t value)
{
if (zh_i2c_start() == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(BME280_ADDR << 1) == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(reg) == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(value) == false)
{
zh_i2c_stop();
return false;
}
zh_i2c_stop();
_delay_ms(2);
return true;
}
static bool zh_bme280_read_reg(uint8_t reg, uint8_t *buf, uint8_t len)
{
if (zh_i2c_start() == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(BME280_ADDR << 1) == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(reg) == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_start() == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write((BME280_ADDR << 1) | 1) == false)
{
zh_i2c_stop();
return false;
}
for (uint8_t i = 0; i < len; ++i)
{
buf[i] = (i == len - 1) ? zh_i2c_read_nack() : zh_i2c_read_ack();
}
zh_i2c_stop();
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)
{
if (zh_bme280_write_reg(BME280_REG_CTRL_MEAS, 0x25) == false)
{
return false;
}
_delay_ms(10);
uint8_t raw[8] = {0};
if (zh_bme280_read_reg(BME280_REG_DATA, raw, sizeof(raw)) == false)
{
return false;
}
*pressure = (int32_t)(((uint32_t)raw[0] << 12) | ((uint32_t)raw[1] << 4) | (raw[2] >> 4));
*temperature = (int32_t)(((uint32_t)raw[3] << 12) | ((uint32_t)raw[4] << 4) | (raw[5] >> 4));
*humidity = (int32_t)(((uint32_t)raw[6] << 8) | (uint32_t)raw[7]);
return true;
}
static float zh_bme280_compensate_temperature(int32_t adc_t)
{
int32_t dig_t1 = (int32_t)(uint16_t)(bme280_calib[1] << 8 | bme280_calib[0]);
int32_t dig_t2 = (int16_t)(bme280_calib[3] << 8 | bme280_calib[2]);
int32_t dig_t3 = (int16_t)(bme280_calib[5] << 8 | bme280_calib[4]);
int32_t var1 = ((((adc_t >> 3) - (dig_t1 << 1))) * dig_t2) >> 11;
int32_t var2 = (((((adc_t >> 4) - dig_t1) * ((adc_t >> 4) - dig_t1)) >> 12) * dig_t3) >> 14;
bme280_t_fine = var1 + var2;
return (float)((bme280_t_fine * 5 + 128) >> 8) / 100.0;
}
static float zh_bme280_compensate_pressure(int32_t adc_p)
{
int32_t dig_p1 = (int32_t)(uint16_t)(bme280_calib[7] << 8 | bme280_calib[6]);
int32_t dig_p2 = (int16_t)(bme280_calib[9] << 8 | bme280_calib[8]);
int32_t dig_p3 = (int16_t)(bme280_calib[11] << 8 | bme280_calib[10]);
int32_t dig_p4 = (int16_t)(bme280_calib[13] << 8 | bme280_calib[12]);
int32_t dig_p5 = (int16_t)(bme280_calib[15] << 8 | bme280_calib[14]);
int32_t dig_p6 = (int16_t)(bme280_calib[17] << 8 | bme280_calib[16]);
int32_t dig_p7 = (int16_t)(bme280_calib[19] << 8 | bme280_calib[18]);
int32_t dig_p8 = (int16_t)(bme280_calib[21] << 8 | bme280_calib[20]);
int32_t dig_p9 = (int16_t)(bme280_calib[23] << 8 | bme280_calib[22]);
int32_t var1 = (((int32_t)bme280_t_fine) >> 1) - 64000;
int32_t var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * dig_p6;
var2 = var2 + ((var1 * dig_p5) << 1);
var2 = (var2 >> 2) + (((int32_t)dig_p4) << 16);
var1 = (((dig_p3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + (((int32_t)dig_p2 * var1) >> 1)) >> 18;
var1 = ((((32768 + var1)) * dig_p1) >> 15);
if (var1 == 0)
{
return 0.0;
}
uint32_t p = (((uint32_t)(((int32_t)1048576) - adc_p) - (var2 >> 12))) * 3125;
if (p < 0x80000000)
{
p = (p << 1) / ((uint32_t)var1);
}
else
{
p = (p / (uint32_t)var1) * 2;
}
var1 = (((int32_t)dig_p9) * ((int32_t)(((p >> 3) * (p >> 3)) >> 13))) >> 12;
var2 = (((int32_t)(p >> 2)) * ((int32_t)dig_p8)) >> 13;
return (float)((int32_t)p + ((var1 + var2 + dig_p7) >> 4));
}
static float zh_bme280_compensate_humidity(int32_t adc_h)
{
int32_t dig_h1 = bme280_calib[25];
int32_t dig_h2 = (int16_t)(bme280_calib[27] << 8 | bme280_calib[26]);
int32_t dig_h3 = bme280_calib[28];
int32_t dig_h4 = (int16_t)((bme280_calib[29] << 4) | (bme280_calib[30] & 0x0F));
int32_t dig_h5 = (int16_t)((bme280_calib[31] << 4) | ((bme280_calib[30] >> 4) & 0x0F));
int8_t dig_h6 = (int8_t)bme280_calib[32];
int32_t v_x1_u32r = (bme280_t_fine - ((int32_t)76800));
v_x1_u32r = (((((adc_h << 14) - (dig_h4 << 20) - (dig_h5 * v_x1_u32r)) + 16384) >> 15) * (((((((v_x1_u32r * dig_h6) >> 10) * (((v_x1_u32r * dig_h3) >> 11) + 32768)) >> 10) + 2097152) * dig_h2 + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * dig_h1) >> 4));
if (v_x1_u32r < 0)
{
v_x1_u32r = 0;
}
if (v_x1_u32r > 419430400)
{
v_x1_u32r = 419430400;
}
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)
{
_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;
}
File diff suppressed because it is too large. Load diff
+127 -223
View File
@@ -4,18 +4,17 @@
**zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+. **zh_avr_bme280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BME280 и трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. Идентификатор устройства получается автоматически по воздуху в процедуре сопряжения со шлюзом и сохраняется в EEPROM. Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру, влажность и давление на датчике BME280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
### Основные возможности ### Основные возможности
1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна. 1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна.
2. **Измерение климата** — температура, влажность и атмосферное давление датчиком BME280 по шине I2C. 2. **Измерение климата** — температура, влажность и атмосферное давление датчиком BME280 по шине I2C.
3. **Автоматическое сопряжение (pairing)** — запрос идентификатора устройства у шлюза по воздуху, хранение ID в EEPROM. 3. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
4. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В. 4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
5. **Работа по протоколу nRF24L01+** — передача и приём через SPI, адресная доставка на шлюз, динамическая длина пакета. 5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
6. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB. 6. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут.
7. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут. 7. **Автономная работа** — не требует внешних подтягивающих резисторов.
8. **Автономная работа** — не требует внешних подтягивающих резисторов (используются внутренние pull-up).
--- ---
@@ -23,34 +22,23 @@
### Структура данных ### Структура данных
Компонент не использует динамическое выделение памяти. Сырые показания хранятся в статических переменных, калибровочные коэффициенты BME280 — в массиве `_bme280_calib[33]`. Перед передачей данные сериализуются функцией `zh_nrf24_sensor_build_payload()` из внешней библиотеки `zh_nrf24_config` в буфер `uint8_t[ZH_NRF24_MAX_PAYLOAD_SIZE]` (32 байта) в формате little-endian. Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
#### Формат передаваемого пакета #### Формат передаваемого пакета
Данные формируются в глобальной переменной `_transmitted_data` типа `zh_nrf24_sensor_transmitted_data_t` из библиотеки `zh_nrf24_config`, затем сериализуются в полезную нагрузку. Для типа `ZH_NRF24_SENSOR_BME280` полный размер пакета — 12 байт (заголовок id + type + battery и три поля измерений, каждое `int16_t` в little-endian). Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
| Поле | Тип | Описание | | Поле | Тип | Описание |
| ------ | ----- | ---------- | | ------ | ----- | ---------- |
| `id` | `int16_t` | Идентификатор устройства, назначенный шлюзом при сопряжении | | `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
| `type` | `int16_t` | Тип устройства (`ZH_NRF24_SENSOR_BME280`) | | `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_BME280`) |
| `battery` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) | | `value_1` | `int16_t` | Влажность (%, например 45 = 45 %) |
| `value_1` | `int16_t` | Влажность (% × 10, например 453 = 45.3 %) | | `battery_voltage` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
| `value_2` | `int16_t` | Температура (°C × 10, например 253 = 25.3 °C) | | `value_2` | `int16_t` | Температура (°C, например 25 = 25 °C) |
| `value_3` | `int16_t` | Давление (мм рт. ст. × 10) | | `value_3` | `int16_t` | Давление (мм рт. ст.) |
| `value_4` | `int16_t` | Зарезервировано (равно 0) |
Тип устройства задаётся значением перечисления `ZH_NRF24_SENSOR_BME280` из библиотеки `zh_nrf24_config`. Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BME280` из библиотеки `zh_rf_config`.
#### Формат пакета сопряжения
Пакет сопряжения — структура `zh_nrf24_sensor_pairing_data_t` (5 байт, `ZH_NRF24_PAIRING_PACKET_SIZE`):
| Поле | Тип | Описание |
| ------ | ----- | ---------- |
| `type` | `uint8_t` | Тип датчика (код `ZH_NRF24_SENSOR_BME280`) |
| `magic` | `uint8_t` | Маркер направления: `ZH_NRF24_PAIRING_REQ_MAGIC` (0xA5) в запросе, `ZH_NRF24_PAIRING_ACK_MAGIC` (0x5A) в ответе |
| `token_hi` | `uint8_t` | Старший байт псевдослучайного токена |
| `token_lo` | `uint8_t` | Младший байт псевдослучайного токена |
| `id` | `uint8_t` | Идентификатор, назначенный шлюзом (в ответе) |
### Конфигурационные макросы ### Конфигурационные макросы
@@ -58,186 +46,134 @@
| -------- | ---------- | ---------- | | -------- | ---------- | ---------- |
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза | | `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза | | `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
| `EEPROM_DEVICE_ID_ADDR` | `(uint8_t *)0x00` | Адрес ячейки EEPROM для хранения ID устройства | | `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
| `DEVICE_ID_UNASSIGNED` | `0xFF` | Признак отсутствующего (несопряжённого) идентификатора | | `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до пробуждения (≈ 5 минут) |
| `PAIRING_TIMEOUT_ATTEMPTS` | `7` | Максимальное число попыток сопряжения | | `ZH_TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
| `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` заполняется при старте (тип устройства, затем идентификатор из EEPROM или из сопряжения). В цикле обновляются поля `battery`, `value_1` (влажность), `value_2` (температура), `value_3` (давление). Идентификатор устройства сохраняется в EEPROM адресом `0x00` (`eeprom_update_byte`). Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` объявлена как `static` и заполняется при старте (идентификатор и тип). В цикле обновляются все поля данных: `battery_voltage`, `value_1` (влажность), `value_2` (температура), `value_3` (давление).
--- ---
## API (интерфейс) ## API (интерфейс)
Все функции являются `static` и доступны только внутри модуля `zh_avr_bme280_sensor.c`. Все функции являются `static` и доступны только внутри модуля `main.c`.
### Инициализация ### Инициализация
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `_zh_wdt_start()` | Запуск watchdog-таймера в режиме прерывания с указанным таймаутом (биты WDP3:0) | | `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
| `_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, радио, тактов периферии) |
### Измерение ### Измерение
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `_zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В (две конверсии, усреднение) | | `zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В (две конверсии, усреднение) |
### Чтение данных BME280 ### Чтение данных BME280
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `_zh_bme280_read()` | Перевод BME280 в forced mode, чтение 8 сырых байт и распаковка ADC значений температуры, давления, влажности | | `zh_bme280_read()` | Чтение 8 сырых байт из BME280 и распаковка ADC значений температуры, давления, влажности |
| `_zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) | | `zh_bme280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
| `_zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) | | `zh_bme280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
| `_zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %) | | `zh_bme280_compensate_humidity()` | Компенсация сырой влажности по калибровочным коэффициентам (результат в %, диапазон [0, 100]) |
### Передача данных nRF24L01+ ### Передача данных
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `_zh_nrf24_send()` | Передача буфера: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом ~100 мс, сброс статуса | | `zh_nrf24_send()` | Передача буфера через nRF24L01+: 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-операции
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `_zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) | | `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
| `_zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ | | `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
| `_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)
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `_zh_i2c_wait()` | Ожидание флага `TWINT` с таймаутом 65535 итераций (возвращает false при зависании шины) | | `zh_i2c_wait()` | Ожидание флага `TWINT` с таймаутом 65535 итераций (возвращает false при зависании шины) |
| `_zh_i2c_start()` | Генерация стартового (или повторного стартового) условия, проверка статусов `0x08`/`0x10` | | `zh_i2c_start()` | Генерация стартового (или повторного стартового) условия, проверка статусов `0x08`/`0x10` |
| `_zh_i2c_stop()` | Генерация стоп-условия с ограниченным ожиданием сброса бита `TWSTO` | | `zh_i2c_stop()` | Генерация стоп-условия с ограниченным ожиданием сброса бита `TWSTO` |
| `_zh_i2c_write()` | Передача одного байта через `TWDR`, проверка статусов ACK (`0x18`, `0x28`, `0x40`) | | `zh_i2c_write()` | Передача одного байта через `TWDR`, проверка статусов ACK (`0x18`, `0x28`, `0x40`) |
| `_zh_i2c_read_ack()` | Чтение байта из `TWDR` с подтверждением ACK (возвращает `0xFF` при таймауте) | | `zh_i2c_read_ack()` | Чтение байта из `TWDR` с подтверждением ACK (возвращает `0xFF` при таймауте) |
| `_zh_i2c_read_nack()` | Чтение последнего байта из `TWDR` без подтверждения NACK (возвращает `0xFF` при таймауте) | | `zh_i2c_read_nack()` | Чтение последнего байта из `TWDR` без подтверждения NACK (возвращает `0xFF` при таймауте) |
### Служебные TWI-функции BME280 ### Служебные TWI-функции BME280
| Функция | Описание | | Функция | Описание |
| --------- | ---------- | | --------- | ---------- |
| `_zh_bme280_write_reg()` | Запись одного байта в регистр BME280 (start, адрес, регистр, данные, stop, задержка 2 мс) | | `zh_bme280_write_reg()` | Запись одного байта в регистр BME280 (start, адрес, регистр, данные, stop, задержка 2 мс) |
| `_zh_bme280_read_reg()` | Чтение нескольких байт из регистров BME280 (repeated-start, ACK/NACK) | | `zh_bme280_read_reg()` | Чтение нескольких байт из регистров BME280 (repeated-start, ACK/NACK) |
### Статические переменные модуля
Все переменные объявлены `static` и доступны только внутри модуля.
| Переменная | Объявление | Описание |
| --------- | ---------- | ---------- |
| `_wdt_wake_count` | `static volatile uint8_t` | Счётчик прерываний WDT, инкрементируется в `ISR(WDT_vect)` до предела `WDT_WAKE_COUNT` |
| `_nrf_event` | `static volatile bool` | Флаг события трансивера, выставляется в `ISR(INT0_vect)` и сбрасывается в машине состояний сопряжения |
| `_bme280_calib` | `static uint8_t [33]` | Калибровочные коэффициенты BME280, загружаются при инициализации датчика |
| `_bme280_temp_fine` | `static int32_t` | Промежуточное значение `t_fine`, общее для компенсаторов давления и влажности |
| `_bme280_present` | `static bool` | Признак доступности датчика BME280 (сбрасывается при ошибке чтения, восстанавливается повторной инициализацией) |
| `_transmitted_data` | `zh_nrf24_sensor_transmitted_data_t` | Глобальная (в пределах модуля) структура формируемого пакета |
--- ---
## Потокобезопасность ## Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Точки синхронизации — два ISR: Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (`ISR(WDT_vect)`), которая увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
- `ISR(WDT_vect)` — увеличивает `_wdt_wake_count` (объявлен как `static volatile uint8_t`, инкремент защищён проверкой верхнего предела), заново включает бит `WDIE`.
- `ISR(INT0_vect)` — выставляет флаг `_nrf_event` (`static volatile bool`) при фронте на пине IRQ трансивера.
Главный цикл и машина состояний сопряжения проверяют эти флаги после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
--- ---
## Алгоритмы ## Алгоритмы
### Алгоритм загрузки идентификатора и сопряжения
- При старте читается ID из EEPROM (`eeprom_read_byte`, адрес `0x00`); значения `0x00` и `0xFF` считаются признаком несопряжённого устройства.
- При отсутствии ID выполняется `_zh_do_pairing()` — неблокирующая машина состояний (`ZH_NRF24_PAIR_SEND` → `ZH_NRF24_PAIR_TX_WAIT` → `ZH_NRF24_PAIR_RX_WAIT` → `ZH_NRF24_PAIR_BACKOFF`):
- генерируется токен (`_zh_get_random_token()` из MCUSR и шума АЦП);
- отправляется запрос `zh_nrf24_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_nrf24_sensor_build_payload()`; при нулевом результате (неизвестный тип) — переход в постоянный сон.
### Алгоритм инициализации BME280 ### Алгоритм инициализации BME280
- Задержка 100 мс после включения. - Задержка 100 мс после включения.
- Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`). - Проверка `CHIP_ID` (регистр `BME280_REG_ID`, ожидается `0x60`).
- Сброс записью `0xB6` в регистр `BME280_REG_RESET`, задержка 10 мс. - Сброс записью `0xB6` в регистр `BME280_REG_RESET`.
- Чтение калибровочных коэффициентов: 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) в `_zh_bme280_read()` перед каждым чтением. - Конфигурация измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode).
### Алгоритм компенсации BME280 ### Алгоритм компенсации BME280
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280. Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BME280.
- **Температура**: `_zh_bme280_compensate_temperature()` — вычисляет `_bme280_temp_fine` (используется для давления и влажности), возвращает результат в °C. - **Температура**: `zh_bme280_compensate_temperature()` — вычисляет `bme280_t_fine` (используется для давления и влажности), возвращает результат в °C. Деление на `dig_t1` не выполняется (проверка `dig_t1 == 0` не реализована).
- **Давление**: `_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 = TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц. - Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = ZH_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`.
- Останов: установка `TWSTO`, ограниченное ожидание (1000 итераций) сброса бита аппаратно. - Останов: установка `TWSTO`, ограниченное ожидание (1000 итераций) сброса бита аппаратно.
- Обработка ошибок: каждая операция возвращает `false` при таймауте ожидания или неверном статусе, вызывающая сторона выполняет `_zh_i2c_stop()`. - Обработка ошибок: каждая операция возвращает `false` при таймауте ожидания или неверном статусе, вызывающая сторона выполняет `zh_i2c_stop()`.
### Алгоритм передачи через nRF24L01+ ### Алгоритм передачи через nRF24L01+
- Переход в Power Up (бит `PWR_UP` в CONFIG). - Переход в Power Up (бит `PWR_UP` в CONFIG).
- Ожидание 5 мс (время стартования осциллятора). - Ожидание 5 мс (время стартования осциллятора).
- Очистка TX FIFO командой `FLUSH_TX`. - Очистка TX FIFO командой `FLUSH_TX`.
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD` (размер пакета определяет `zh_nrf24_sensor_build_payload()`, динамические пакеты включены: `FEATURE = EN_DPL`, `DYNPD = DPL_P0`). - Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
- Импульс CE для запуска передачи. - Импульс CE длительностью 20 мкс для запуска передачи.
- Ожидание завершения по пину IRQ (PD2) с таймаутом ~100 мс. - Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс.
- Сброс флагов статуса, очистка FIFO, переход в Power Down. - Сброс флагов статуса, очистка FIFO, переход в Power Down.
Для процедуры сопряжения используется вариант без блокировки (`_zh_nrf24_send_no_wait`, импульс CE 15 мкс) с завершением по прерыванию INT0.
### Алгоритм сна и пробуждения ### Алгоритм сна и пробуждения
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDT_T_8S`), без сброса MCU. - Инициализация WDT: прерывание по таймауту ~8 секунд (`WDP3 | WDP0`), без сброса MCU.
- ISR `WDT_vect`: увеличивает `_wdt_wake_count` (до предела `WDT_WAKE_COUNT`), заново включает бит `WDIE`. - ISR `WDT_vect`: увеличивает `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).
--- ---
@@ -248,54 +184,32 @@
```c ```c
int main(void) int main(void)
{ {
_transmitted_data.type = ZH_NRF24_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_NRF24_MAX_PAYLOAD_SIZE];
if (zh_nrf24_sensor_build_payload(&_transmitted_data, probe,
sizeof(probe)) == 0) {
_zh_enter_final_sleep();
}
}
_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 = transmitted_data.battery_voltage =
(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) * 10.0); (int16_t)lroundf(zh_bme280_compensate_temperature(adc_t));
_transmitted_data.value_3 = transmitted_data.value_3 =
(int16_t)lroundf(_zh_bme280_compensate_pressure(adc_p) * 0.00750062 * 10.0); (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) * 10.0); (int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
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, sizeof(transmitted_data));
payload, sizeof(payload));
if (size > 0) {
_zh_nrf24_send(payload, size);
}
} else { } else {
_bme280_present = false; bme280_present = false;
} }
} }
@@ -304,29 +218,25 @@ int main(void)
sleep_enable(); sleep_enable();
sleep_cpu(); sleep_cpu();
sleep_disable(); sleep_disable();
if (_wdt_wake_count >= WDT_WAKE_COUNT) { if (wdt_wake_count >= WDT_WAKE_COUNT) {
_wdt_wake_count = 0; wdt_wake_count = 0;
if (_bme280_present == false) { if (bme280_present == false) {
_bme280_present = _zh_bme280_init(); bme280_present = zh_bme280_init();
}
if (_bme280_present == true) {
if (_zh_bme280_read(&adc_t, &adc_p, &adc_h) == true) {
_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_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));
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);
} }
if (bme280_present == 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.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_1 =
(int16_t)lroundf(zh_bme280_compensate_humidity(adc_h));
zh_nrf24_send((uint8_t *)&transmitted_data,
sizeof(transmitted_data));
} else { } else {
_bme280_present = false; bme280_present = false;
} }
} }
} }
@@ -337,12 +247,11 @@ int main(void)
### Изменение конфигурации под другое устройство ### Изменение конфигурации под другое устройство
```c ```c
// Идентификатор устройства назначается шлюзом при сопряжении // Измените идентификатор для нового устройства
// и хранится в EEPROM (адрес 0x00). Для повторного сопряжения #define DEVICE_ID 7 // Уникальный ID в сети
// обнулите ячейку EEPROM.
#define GATEWAY_CHANNEL 120 #define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF #define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_NRF24_SENSOR_BME280 // Тип устройства задаётся перечислением ZH_RF_SENSOR_BME280
``` ```
### Подключение по SPI и I2C (схема выводов) ### Подключение по SPI и I2C (схема выводов)
@@ -354,10 +263,10 @@ int main(void)
// PB3 -> MOSI (назначается аппаратным SPI) // PB3 -> MOSI (назначается аппаратным SPI)
// PB5 -> SCK (назначается аппаратным SPI) // PB5 -> SCK (назначается аппаратным SPI)
// PB1 -> CE (управление передачей, выход) // PB1 -> CE (управление передачей, выход)
// PD2 -> IRQ (вход, активный низкий, INT0, с подтяжкой к VCC) // PD2 -> IRQ (вход, активный низкий, с подтяжкой к 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)
``` ```
--- ---
@@ -370,36 +279,33 @@ int main(void)
## Ошибки ## Ошибки
Компонент не возвращает коды ошибок. Функции `_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`). Компонент не возвращает коды ошибок. Функции `zh_bme280_init`, `zh_bme280_read`, `zh_bme280_write_reg`, `zh_bme280_read_reg` возвращают `bool` (true при успехе). Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
| Условие | Поведение | | Условие | Поведение |
| --------- | ---------- | | --------- | ---------- |
| Нулевое показание АЦП | `_zh_get_battery_voltage()` возвращает 0.0 | | Нулевое показание АЦП | `zh_get_battery_voltage()` возвращает 0.0 |
| Неверный `CHIP_ID` BME280 | `_zh_bme280_init()` возвращает false, флаг `_bme280_present = false`, при следующем цикле предпринимается повторная попытка инициализации | | Неверный `CHIP_ID` BME280 | `zh_bme280_init()` возвращает false, флаг `bme280_present = false`, при следующем цикле предпринимается повторная попытка инициализации |
| Таймаут ожидания `TWINT` | `_zh_i2c_wait()` возвращает false, операция TWI прерывается | | Таймаут ожидания `TWINT` | `zh_i2c_wait()` возвращает false, операция TWI прерывается |
| Неверный статус TWI (NACK, ошибка шины) | `_zh_i2c_start()` / `_zh_i2c_write()` возвращают false, вызывающая функция выполняет `_zh_i2c_stop()` | | Неверный статус TWI (NACK, ошибка шины) | `zh_i2c_start()` / `zh_i2c_write()` возвращают false, вызывающая функция выполняет `zh_i2c_stop()` |
| Таймаут при чтении TWI | `_zh_i2c_read_ack()` / `_zh_i2c_read_nack()` возвращают `0xFF` | | Таймаут при чтении TWI | `zh_i2c_read_ack()` / `zh_i2c_read_nack()` возвращают `0xFF` |
| Ошибка чтения 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_nrf24_sensor_build_payload()` вернул 0 | Неизвестный тип датчика или слишком мал буфер — переход в постоянный сон |
--- ---
## Ограничения ## Ограничения
1. **Широковещательная передача данных** — приём используется только на этапе сопряжения; шлюз не подтверждает пакеты измерений на уровне приложения. 1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены. 2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
3. **Жёстко зашитый адрес шлюза** — смена адреса или канала требует перепрошивки (ID устройства при этом хранится в EEPROM). 3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены. 4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены.
5. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В. 5. **Отсутствие валидации `dig_t1`** — в `zh_bme280_compensate_temperature()` нет проверки `dig_t1 == 0`, что может привести к делению на ноль.
6. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются. 6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В.
7. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся. 7. **Программные задержки в TWI** — обмен с BME280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются.
8. **Давление в мм рт. ст.** — `_zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.). 8. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся.
9. **Масштабирование значений ×10 только в первом цикле** — при первом измерении температура, влажность и давление умножаются на 10 (одна дробная цифра); в цикле пробуждения значения записываются без масштабирования (см. `main`). 9. **Давление в мм рт. ст.** — `zh_bme280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.).
10. **Сопряжение требует шлюза** — без ответившего шлюза устройство после 7 попыток уходит в постоянный сон до сброса/перепрошивки. 10. **Температура и влажность без масштабирования** — значения передаются как целые числа (°C и %), дробная часть теряется.
--- ---
@@ -407,7 +313,7 @@ int main(void)
- PlatformIO с плагином `platform-atmelavr` - PlatformIO с плагином `platform-atmelavr`
- AVR-GCC (входит в состав PlatformIO) - AVR-GCC (входит в состав PlatformIO)
- Библиотека `zh_nrf24_config` (ветка `dev`, подключается через `lib_deps` в `platformio.ini`) - Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
- Программатор USBasp (или совместимый) - Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя) - Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
@@ -423,23 +329,21 @@ pio run -e ATmega328P -t upload
### Подключение в проекте ### Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/zh_avr_bme280_sensor.c`, добавьте зависимость `zh_nrf24_config` и адаптируйте макросы конфигурации. Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c`, добавьте зависимость `zh_rf_config` и адаптируйте макросы конфигурации.
```ini ```ini
; platformio.ini ; platformio.ini
lib_deps = lib_deps =
https://github.com/aZholtikov/zh_nrf24_config#dev https://github.com/aZholtikov/zh_rf_config
``` ```
```c ```c
// Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола // Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола
#include "avr/io.h" #include "avr/io.h"
#include "avr/interrupt.h"
#include "avr/sleep.h" #include "avr/sleep.h"
#include "avr/eeprom.h" #include "avr/fuse.h"
#include "avr/wdt.h" #include "avr/interrupt.h"
#include "util/delay.h" #include "util/delay.h"
#include "stdbool.h"
#include "math.h" #include "math.h"
#include "zh_nrf24_config.h" #include "zh_rf_config.h"
``` ```