Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7c4ade170 | ||
|
|
696091857e | ||
|
|
52f2da6368 |
No files matched your search
@@ -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
@@ -12,7 +12,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega168P]
|
[env:ATmega168P]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -28,7 +28,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega168PB]
|
[env:ATmega168PB]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -44,7 +44,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328]
|
[env:ATmega328]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -60,7 +60,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328P]
|
[env:ATmega328P]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -76,7 +76,7 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
|
|
||||||
[env:ATmega328PB]
|
[env:ATmega328PB]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
@@ -92,4 +92,4 @@ upload_flags =
|
|||||||
-Pusb
|
-Pusb
|
||||||
-e
|
-e
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#dev
|
||||||
-827
@@ -1,827 +0,0 @@
|
|||||||
#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
+225
-129
@@ -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,23 +23,34 @@
|
|||||||
|
|
||||||
### Структура данных
|
### Структура данных
|
||||||
|
|
||||||
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
|
Компонент не использует динамическое выделение памяти. Сырые показания хранятся в статических переменных, калибровочные коэффициенты BME280 — в массиве `_bme280_calib[33]`. Перед передачей данные сериализуются функцией `zh_nrf24_sensor_build_payload()` из внешней библиотеки `zh_nrf24_config` в буфер `uint8_t[ZH_NRF24_MAX_PAYLOAD_SIZE]` (32 байта) в формате little-endian.
|
||||||
|
|
||||||
#### Формат передаваемого пакета
|
#### Формат передаваемого пакета
|
||||||
|
|
||||||
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
|
Данные формируются в глобальной переменной `_transmitted_data` типа `zh_nrf24_sensor_transmitted_data_t` из библиотеки `zh_nrf24_config`, затем сериализуются в полезную нагрузку. Для типа `ZH_NRF24_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_NRF24_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_NRF24_SENSOR_BME280` из библиотеки `zh_nrf24_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` | Идентификатор, назначенный шлюзом (в ответе) |
|
||||||
|
|
||||||
### Конфигурационные макросы
|
### Конфигурационные макросы
|
||||||
|
|
||||||
@@ -46,134 +58,186 @@
|
|||||||
| -------- | ---------- | ---------- |
|
| -------- | ---------- | ---------- |
|
||||||
| `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`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## API (интерфейс)
|
## API (интерфейс)
|
||||||
|
|
||||||
Все функции являются `static` и доступны только внутри модуля `main.c`.
|
Все функции являются `static` и доступны только внутри модуля `zh_avr_bme280_sensor.c`.
|
||||||
|
|
||||||
### Инициализация
|
### Инициализация
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
| --------- | ---------- |
|
| --------- | ---------- |
|
||||||
| `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
|
| `_zh_wdt_start()` | Запуск watchdog-таймера в режиме прерывания с указанным таймаутом (биты WDP3:0) |
|
||||||
| `zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
|
| `_zh_wdt_stop()` | Остановка watchdog-таймера |
|
||||||
| `zh_bme280_init()` | Инициализация датчика BME280: проверка CHIP_ID, сброс, чтение калибровки, настройка режима |
|
| `_zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
|
||||||
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/2) |
|
| `_zh_bme280_init()` | Инициализация датчика BME280: проверка CHIP_ID, сброс, чтение калибровки, настройка режима |
|
||||||
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
|
| `_zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
|
||||||
|
| `_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()` | Чтение 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-операции
|
||||||
|
|
||||||
| Функция | Описание |
|
| Функция | Описание |
|
||||||
| --------- | ---------- |
|
| --------- | ---------- |
|
||||||
| `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 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 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`.
|
- Сброс записью `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_temp_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`.
|
||||||
- Останов: установка `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`.
|
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD` (размер пакета определяет `zh_nrf24_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,32 +248,54 @@
|
|||||||
```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_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_NRF24_MAX_PAYLOAD_SIZE];
|
||||||
sizeof(transmitted_data));
|
uint8_t size = zh_nrf24_sensor_build_payload(&_transmitted_data,
|
||||||
|
payload, sizeof(payload));
|
||||||
|
if (size > 0) {
|
||||||
|
_zh_nrf24_send(payload, size);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bme280_present = false;
|
_bme280_present = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,25 +304,29 @@ 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 (_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));
|
||||||
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);
|
||||||
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_NRF24_MAX_PAYLOAD_SIZE];
|
||||||
sizeof(transmitted_data));
|
uint8_t size = zh_nrf24_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,11 +337,12 @@ 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_NRF24_SENSOR_BME280
|
||||||
```
|
```
|
||||||
|
|
||||||
### Подключение по SPI и I2C (схема выводов)
|
### Подключение по SPI и I2C (схема выводов)
|
||||||
@@ -263,10 +354,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,33 +370,36 @@ 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`).
|
||||||
|
|
||||||
| Условие | Поведение |
|
| Условие | Поведение |
|
||||||
| --------- | ---------- |
|
| --------- | ---------- |
|
||||||
| Нулевое показание АЦП | `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. **Только передача (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 +407,7 @@ int main(void)
|
|||||||
|
|
||||||
- PlatformIO с плагином `platform-atmelavr`
|
- PlatformIO с плагином `platform-atmelavr`
|
||||||
- AVR-GCC (входит в состав PlatformIO)
|
- AVR-GCC (входит в состав PlatformIO)
|
||||||
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
|
- Библиотека `zh_nrf24_config` (ветка `dev`, подключается через `lib_deps` в `platformio.ini`)
|
||||||
- Программатор USBasp (или совместимый)
|
- Программатор USBasp (или совместимый)
|
||||||
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
||||||
|
|
||||||
@@ -329,21 +423,23 @@ pio run -e ATmega328P -t upload
|
|||||||
|
|
||||||
### Подключение в проекте
|
### Подключение в проекте
|
||||||
|
|
||||||
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c`, добавьте зависимость `zh_rf_config` и адаптируйте макросы конфигурации.
|
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/zh_avr_bme280_sensor.c`, добавьте зависимость `zh_nrf24_config` и адаптируйте макросы конфигурации.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
; platformio.ini
|
; platformio.ini
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/aZholtikov/zh_rf_config
|
https://github.com/aZholtikov/zh_nrf24_config#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_nrf24_config.h"
|
||||||
```
|
```
|
||||||
Reference in new issue
Block a user