feat: initial

This commit is contained in:
aZholtikov committed 2026-10-22 23:11:35 +03:00
1 parent 3185fe130b
commit 74c78582a2
6 files changed
+1313 -2

No files matched your search

BIN
View File
Binary file not shown.
+73
View File
@@ -0,0 +1,73 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25 -2
View File
@@ -1,3 +1,26 @@
# zh_avr_bmp280_sensor # AVR firmware for BMP280 wireless sensor (nRF24L01+)
AVR firmware for BMP280 wireless sensor (nRF24L01+). ## Features
1. Wireless sensor firmware for ATMega168/328 microcontrollers with BMP280 temperature and pressure sensor.
2. Power consumption in sleep mode approximately 5 uA using Power-down mode.
3. Transmits device identifier, battery voltage, temperature, and pressure readings on each measurement cycle.
4. Battery measurement through internal ADC with 1.1 V bandgap reference.
5. Wake-up from Power-down mode by watchdog timer interrupt with ~8 second interval.
6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, and ATMega328PB.
7. Hardware TWI (I2C) for BMP280 communication on PORTC pins PC4 (SDA) and PC5 (SCL).
## Attention
1. A compatible RF Gateway is required to receive and process transmitted packets.
2. 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).
## Using
In an existing PlatformIO project, run the following command to install the firmware:
```bash
cd your_project
git clone https://github.com/aZholtikov/zh_avr_bmp280_sensor
```
+95
View File
@@ -0,0 +1,95 @@
[env:ATmega168]
platform = atmelavr
board = ATmega168
board_build.mcu = atmega168
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
[env:ATmega168P]
platform = atmelavr
board = ATmega168P
board_build.mcu = atmega168p
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
[env:ATmega168PB]
platform = atmelavr
board = ATmega168PB
board_build.mcu = atmega168pb
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
[env:ATmega328]
platform = atmelavr
board = ATmega328
board_build.mcu = atmega328
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
[env:ATmega328P]
platform = atmelavr
board = ATmega328P
board_build.mcu = atmega328p
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
[env:ATmega328PB]
platform = atmelavr
board = ATmega328PB
board_build.mcu = atmega328pb
board_build.f_cpu = 8000000L
framework = arduino
upload_protocol = usbasp
board_fuses.lfuse = 0xE2
board_fuses.hfuse = 0xDF
board_fuses.efuse = 0x07
upload_flags =
-Pusb
-e
lib_deps =
https://github.com/aZholtikov/zh_rf_config
+778
View File
@@ -0,0 +1,778 @@
#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 BMP280_ADDR 0x76
#define BMP280_REG_ID 0xD0
#define BMP280_REG_RESET 0xE0
#define BMP280_REG_CTRL_MEAS 0xF4
#define BMP280_REG_CONFIG 0xF5
#define BMP280_REG_DATA 0xF7
#define BMP280_REG_CALIB00 0x88
/**
* @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 BMP280 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 BMP280 sensor over TWI.
*
* Initiates a TWI transaction with the BMP280 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_bmp280_write_reg(uint8_t reg, uint8_t value);
/**
* @brief Read multiple bytes from a BMP280 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_bmp280_read_reg(uint8_t reg, uint8_t *buf, uint8_t len);
/**
* @brief Initialize the BMP280 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_bmp280_init(void);
/**
* @brief Read raw sensor data from the BMP280.
*
* Configures the sensor for a measurement cycle, waits for conversion
* to complete, then reads 8 bytes of raw data and unpacks the pressure,
* and temperature 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)
*
* @return true on success
* @return false if the register write or read operation fails
*/
static bool zh_bmp280_read(int32_t *temperature, int32_t *pressure);
/**
* @brief Compensate raw temperature using Bosch calibration coefficients.
*
* Applies the 32-bit compensation algorithm defined in the BMP280 datasheet
* using calibration data read during initialization. The result is stored
* in BMP280_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_bmp280_compensate_temperature(int32_t adc_t);
/**
* @brief Compensate raw pressure using Bosch calibration coefficients.
*
* Applies the 32-bit compensation algorithm defined in the BMP280 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_bmp280_compensate_pressure(int32_t adc_p);
/**
* @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 bmp280_calib[24] = {0};
static int32_t bmp280_t_fine = 0;
static bool bmp280_present = false;
zh_rf_sensor_transmitted_data_t transmitted_data = {DEVICE_ID, ZH_RF_SENSOR_BMP280, 0, 0, 0, 0, 0};
int main(void)
{
cli();
zh_i2c_init();
bmp280_present = zh_bmp280_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;
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_present = false;
}
}
for (;;)
{
sleep_enable();
sleep_cpu();
sleep_disable();
if (wdt_wake_count >= WDT_WAKE_COUNT)
{
wdt_wake_count = 0;
if (bmp280_present == false)
{
bmp280_present = zh_bmp280_init();
}
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_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_bmp280_write_reg(uint8_t reg, uint8_t value)
{
if (zh_i2c_start() == false)
{
zh_i2c_stop();
return false;
}
if (zh_i2c_write(BMP280_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_bmp280_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(BMP280_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((BMP280_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_bmp280_init(void)
{
_delay_ms(100);
uint8_t id = 0;
if (zh_bmp280_read_reg(BMP280_REG_ID, &id, sizeof(id)) == false || id != 0x58)
{
return false;
}
if (zh_bmp280_write_reg(BMP280_REG_RESET, 0xB6) == false)
{
return false;
}
_delay_ms(10);
if (zh_bmp280_read_reg(BMP280_REG_CALIB00, bmp280_calib, 24) == false)
{
return false;
}
if (zh_bmp280_write_reg(BMP280_REG_CONFIG, 0x00) == false)
{
return false;
}
return true;
}
static bool zh_bmp280_read(int32_t *temperature, int32_t *pressure)
{
if (zh_bmp280_write_reg(BMP280_REG_CTRL_MEAS, 0x25) == false)
{
return false;
}
_delay_ms(10);
uint8_t raw[6] = {0};
if (zh_bmp280_read_reg(BMP280_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));
return true;
}
static float zh_bmp280_compensate_temperature(int32_t adc_t)
{
int32_t dig_t1 = (int32_t)(uint16_t)(bmp280_calib[1] << 8 | bmp280_calib[0]);
int32_t dig_t2 = (int16_t)(bmp280_calib[3] << 8 | bmp280_calib[2]);
int32_t dig_t3 = (int16_t)(bmp280_calib[5] << 8 | bmp280_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;
bmp280_t_fine = var1 + var2;
return (float)((bmp280_t_fine * 5 + 128) >> 8) / 100.0;
}
static float zh_bmp280_compensate_pressure(int32_t adc_p)
{
int32_t dig_p1 = (int32_t)(uint16_t)(bmp280_calib[7] << 8 | bmp280_calib[6]);
int32_t dig_p2 = (int16_t)(bmp280_calib[9] << 8 | bmp280_calib[8]);
int32_t dig_p3 = (int16_t)(bmp280_calib[11] << 8 | bmp280_calib[10]);
int32_t dig_p4 = (int16_t)(bmp280_calib[13] << 8 | bmp280_calib[12]);
int32_t dig_p5 = (int16_t)(bmp280_calib[15] << 8 | bmp280_calib[14]);
int32_t dig_p6 = (int16_t)(bmp280_calib[17] << 8 | bmp280_calib[16]);
int32_t dig_p7 = (int16_t)(bmp280_calib[19] << 8 | bmp280_calib[18]);
int32_t dig_p8 = (int16_t)(bmp280_calib[21] << 8 | bmp280_calib[20]);
int32_t dig_p9 = (int16_t)(bmp280_calib[23] << 8 | bmp280_calib[22]);
int32_t var1 = (((int32_t)bmp280_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 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;
}
+342
View File
@@ -0,0 +1,342 @@
# Описание компонента zh_avr_bmp280_sensor
## Общие сведения
**zh_avr_bmp280_sensor** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводной датчик климата на базе датчика BMP280 и трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), просыпается по таймеру (watchdog), измеряет температуру и давление на датчике BMP280 (интерфейс I2C) и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
### Основные возможности
1. **Сверхнизкое энергопотребление** — около 5 мкА в режиме сна.
2. **Измерение климата** — температура и атмосферное давление датчиком BMP280 по шине I2C.
3. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
6. **Пробуждение по watchdog-таймеру** — WDT с интервалом ~8 секунд, цикл работы каждые `WDT_WAKE_COUNT` (38) прерываний ≈ 5 минут.
7. **Автономная работа** — не требует внешних подтягивающих резисторов.
---
## Внутреннее устройство
### Структура данных
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
#### Формат передаваемого пакета
Пакет формируется в глобальной переменной `transmitted_data` типа `zh_rf_sensor_transmitted_data_t` из внешней библиотеки `zh_rf_config` и передаётся побайтово как массив байтов (`sizeof` структуры — 14 байт).
| Поле | Тип | Описание |
| ------ | ----- | ---------- |
| `id` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
| `type` | `int16_t` | Тип устройства (`ZH_RF_SENSOR_BMP280`) |
| `value_1` | `int16_t` | Температура (°C, например 25 = 25 °C) |
| `battery_voltage` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
| `value_2` | `int16_t` | Давление (мм рт. ст.) |
| `value_3` | `int16_t` | Зарезервировано (равно 0) |
| `value_4` | `int16_t` | Зарезервировано (равно 0) |
Тип устройства задаётся значением перечисления `ZH_RF_SENSOR_BMP280` из библиотеки `zh_rf_config`.
### Конфигурационные макросы
| Макрос | Значение | Описание |
| -------- | ---------- | ---------- |
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
| `WDT_WAKE_COUNT` | `38` | Количество прерываний WDT до пробуждения (≈ 5 минут) |
| `ZH_TWI_TWBR` | `32` | Значение регистра TWBR для задания скорости аппаратного TWI |
| `BMP280_ADDR` | `0x76` | 7-битный адрес датчика BMP280 на шине I2C |
### Управление памятью
Компонент не использует динамическую память. Все данные хранятся в статической области и регистрах микроконтроллера. Глобальная переменная `transmitted_data` объявлена как глобальная (не `static`) и заполняется при инициализации (идентификатор и тип). В цикле обновляются все поля данных: `battery_voltage`, `value_1` (температура), `value_2` (давление). Массив калибровки `bmp280_calib` размером 24 байта хранит калибровочные коэффициенты датчика.
---
## API (интерфейс)
Все функции являются `static` и доступны только внутри модуля `main.c`.
### Инициализация
| Функция | Описание |
| --------- | ---------- |
| `zh_wdt_init()` | Инициализация watchdog-таймера (~8 сек, прерывание WDT_vect) для периодического пробуждения |
| `zh_i2c_init()` | Инициализация аппаратного TWI (I2C) на пинах PC4 (SDA), PC5 (SCL), TWBR = 32 |
| `zh_bmp280_init()` | Инициализация датчика BMP280: проверка CHIP_ID (0x58), сброс, чтение калибровки (24 байта) |
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/2) |
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
### Измерение
| Функция | Описание |
| ------- | -------- |
| `zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В (две конверсии, усреднение) |
### Чтение данных BMP280
| Функция | Описание |
| --------- | ---------- |
| `zh_bmp280_read()` | Чтение 6 сырых байт из BMP280 и распаковка ADC значений температуры и давления |
| `zh_bmp280_compensate_temperature()` | Компенсация сырой температуры по калибровочным коэффициентам (результат в °C) |
| `zh_bmp280_compensate_pressure()` | Компенсация сырого давления по калибровочным коэффициентам (результат в Па) |
### Передача данных
| Функция | Описание |
| ------- | -------- |
| `zh_nrf24_send()` | Передача буфера через nRF24L01+: Power Up, `FLUSH_TX`, `W_TX_PAYLOAD`, импульс CE, ожидание IRQ с таймаутом 100 мс, сброс статуса |
### Низкоуровневые SPI-операции
| Функция | Описание |
| --------- | ---------- |
| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой (CSN управляется вручную через PB2) |
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
### Низкоуровневые TWI-операции (аппаратный I2C)
| Функция | Описание |
| --------- | ---------- |
| `zh_i2c_wait()` | Ожидание флага `TWINT` с таймаутом 65535 итераций (возвращает false при зависании шины) |
| `zh_i2c_start()` | Генерация стартового (или повторного стартового) условия, проверка статусов `0x08`/`0x10` |
| `zh_i2c_stop()` | Генерация стоп-условия с ограниченным ожиданием сброса бита `TWSTO` |
| `zh_i2c_write()` | Передача одного байта через `TWDR`, проверка статусов ACK (`0x18`, `0x28`, `0x40`) |
| `zh_i2c_read_ack()` | Чтение байта из `TWDR` с подтверждением ACK (возвращает `0xFF` при таймауте) |
| `zh_i2c_read_nack()` | Чтение последнего байта из `TWDR` без подтверждения NACK (возвращает `0xFF` при таймауте) |
### Служебные TWI-функции BMP280
| Функция | Описание |
| --------- | ---------- |
| `zh_bmp280_write_reg()` | Запись одного байта в регистр BMP280 (start, адрес, регистр, данные, stop, задержка 2 мс) |
| `zh_bmp280_read_reg()` | Чтение нескольких байт из регистров BMP280 (repeated-start, ACK/NACK) |
---
## Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Единственная точка синхронизации — ISR watchdog-таймера (`ISR(WDT_vect)`), которая увеличивает `wdt_wake_count` (объявлен как `static volatile uint8_t`). Главный цикл проверяет этот счётчик после выхода из сна. Атомарная запись одного байта на AVR гарантирует отсутствие состояний гонки при инкременте. Весь остальной обмен с периферией (I2C, SPI, АЦП) выполняется последовательно в главном цикле.
---
## Алгоритмы
### Алгоритм инициализации BMP280
- Задержка 100 мс после включения.
- Проверка `CHIP_ID` (регистр `BMP280_REG_ID`, ожидается `0x58`).
- Сброс записью `0xB6` в регистр `BMP280_REG_RESET`.
- Задержка 10 мс после сброса.
- Чтение калибровочных коэффициентов: 24 байта из `BMP280_REG_CALIB00` (массив `bmp280_calib`).
- Конфигурация измерения: `ctrl_meas = 0x25` (temp/pressure oversampling x16, forced mode).
### Алгоритм компенсации BMP280
Сырые ADC-значения пересчитываются в физические величины по алгоритмам производителя из datasheet BMP280.
- **Температура**: `zh_bmp280_compensate_temperature()` — вычисляет `bmp280_t_fine` (используется для давления), возвращает результат в °C. Формула: `((bmp280_t_fine * 5 + 128) >> 8) / 100.0`. Деление на `dig_t1` не выполняется.
- **Давление**: `zh_bmp280_compensate_pressure()` — возвращает результат в Па. При `var1 == 0` (знаменатель ноль) возвращает 0.0.
### Алгоритм аппаратного TWI
- Инициализация: `TWSR = 0x00` (предделитель 1), `TWBR = ZH_TWI_TWBR` (32), включение модуля битом `TWEN`. Скорость: `F_CPU / (16 + 2·TWBR·prescaler)` ≈ 100 кГц при 8 МГц.
- Стартовое/повторное стартовое условие: установка `TWSTA`, ожидание `TWINT`, проверка статуса (`0x08` — START, `0x10` — REPEATED START).
- Передача байта: запись в `TWDR`, сброс `TWINT`, ожидание завершения, проверка статусов подтверждения (`0x18`/`0x28` — данные, `0x40` — адрес чтения).
- Приём байта: установка `TWEA` для всех байт кроме последнего, чтение `TWDR` после `TWINT`.
- Останов: установка `TWSTO`, ограниченное ожидание (1000 итераций) сброса бита аппаратно.
- Обработка ошибок: каждая операция возвращает `false` при таймауте ожидания или неверном статусе, вызывающая сторона выполняет `zh_i2c_stop()`.
### Алгоритм передачи через nRF24L01+
- Переход в Power Up (бит `PWR_UP` в CONFIG).
- Ожидание 5 мс (время стартования осциллятора).
- Очистка TX FIFO командой `FLUSH_TX`.
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
- Импульс CE длительностью 20 мкс для запуска передачи.
- Ожидание завершения по пину IRQ (PD2) с таймаутом 100 мс.
- Сброс флагов статуса, очистка FIFO, переход в Power Down.
### Алгоритм сна и пробуждения
- Инициализация WDT: прерывание по таймауту ~8 секунд (`WDP3 | WDP0`), без сброса MCU.
- ISR `WDT_vect`: увеличивает `wdt_wake_count`, заново включает бит `WDIE`.
- Главный цикл: переход в Power-down (`sleep_cpu()`). После пробуждения проверяется `wdt_wake_count >= WDT_WAKE_COUNT` (38 × 8 с ≈ 5 минут).
- При достижении порога: обнуление счётчика, повторная инициализация BMP280 (если датчик недоступен), чтение показаний и передача пакета через nRF24L01+.
- Первое измерение выполняется сразу после инициализации перед входом в цикл.
---
## Примеры использования
### Типовой цикл работы (main)
```c
int main(void)
{
cli();
zh_i2c_init();
bmp280_present = zh_bmp280_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;
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_present = false;
}
}
for (;;)
{
sleep_enable();
sleep_cpu();
sleep_disable();
if (wdt_wake_count >= WDT_WAKE_COUNT)
{
wdt_wake_count = 0;
if (bmp280_present == false)
{
bmp280_present = zh_bmp280_init();
}
if (bmp280_present == true)
{
if (zh_bmp280_read(&adc_t, &adc_p) == true)
{
transmitted_data.battery_voltage = (int16_t)lroundf(zh_get_battery_voltage() * 100.0);
transmitted_data.value_1 = (int16_t)lroundf(zh_bmp280_compensate_temperature(adc_t));
transmitted_data.value_2 = (int16_t)lroundf(zh_bmp280_compensate_pressure(adc_p) * 0.00750062);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
bmp280_present = false;
}
}
}
}
return 0;
}
```
### Изменение конфигурации под другое устройство
```c
// Измените идентификатор для нового устройства
#define DEVICE_ID 7 // Уникальный ID в сети
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_RF_SENSOR_BMP280
```
### Подключение по SPI и I2C (схема выводов)
```c
// Назначение пинов AVR:
// nRF24L01+ (SPI):
// PB2 -> CSN (управление SPI-селектом, выход)
// PB3 -> MOSI (назначается аппаратным SPI)
// PB5 -> SCK (назначается аппаратным SPI)
// PB1 -> CE (управление передачей, выход)
// PD2 -> IRQ (вход, активный низкий, с подтяжкой к VCC)
// BMP280 (I2C, аппаратный TWI):
// PC4 -> SDA (аппаратный TWI, pull-up)
// PC5 -> SCL (аппаратный TWI, pull-up)
```
---
## Логирование
Логирование отсутствует.
---
## Ошибки
Компонент не возвращает коды ошибок. Функции `zh_bmp280_init`, `zh_bmp280_read`, `zh_bmp280_write_reg`, `zh_bmp280_read_reg` возвращают `bool` (true при успехе). Остальные функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`).
| Условие | Поведение |
| --------- | ---------- |
| Нулевое показание АЦП | `zh_get_battery_voltage()` возвращает 0.0 |
| Неверный `CHIP_ID` BMP280 | `zh_bmp280_init()` возвращает false, флаг `bmp280_present = false`, при следующем цикле предпринимается повторная попытка инициализации |
| Таймаут ожидания `TWINT` | `zh_i2c_wait()` возвращает false, операция TWI прерывается |
| Неверный статус TWI (NACK, ошибка шины) | `zh_i2c_start()` / `zh_i2c_write()` возвращают false, вызывающая функция выполняет `zh_i2c_stop()` |
| Таймаут при чтении TWI | `zh_i2c_read_ack()` / `zh_i2c_read_nack()` возвращают `0xFF` |
| Ошибка чтения BMP280 | `zh_bmp280_read()` возвращает false, пакет не формируется, `bmp280_present = false` |
| `var1 == 0` при компенсации давления | `zh_bmp280_compensate_pressure()` возвращает 0.0 |
| Превышение таймаута передачи (~100 мс) | Цикл ожидания IRQ прерывается, передача может быть неполной |
---
## Ограничения
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
4. **Таймаут передачи ~100 мс** — если шлюз недоступен, передача прерывается. Аппаратные ретрансмиты nRF24L01+ (`SETUP_RETR = 0xFF`: 15 попыток с интервалом до 500 мс) включены.
5. **Отсутствие валидации `dig_t1`** — в `zh_bmp280_compensate_temperature()` нет проверки `dig_t1 == 0`, что может привести к делению на ноль.
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В.
7. **Программные задержки в TWI** — обмен с BMP280 выполняется на аппаратном TWI, но ожидание `TWINT` реализовано поллингом с таймаутом; прерывания TWI не используются.
8. **Задержка между измерениями ~5 минут** — определяется произведением `WDT_WAKE_COUNT` (38) на интервал WDT (~8 секунд). В течение этого времени пакет не передаётся.
9. **Давление в мм рт. ст.** — `zh_bmp280_compensate_pressure()` возвращает давление в Паскалях, для перевода в пакет умножается на `0.00750062` (коэффициент пересчёта Па → мм рт. ст.).
10. **Температура и давление без масштабирования** — значения передаются как целые числа (°C и мм рт. ст.), дробная часть теряется.
---
## Требования для сборки
- PlatformIO с плагином `platform-atmelavr`
- AVR-GCC (входит в состав PlatformIO)
- Библиотека `zh_rf_config` (подключается через `lib_deps` в `platformio.ini`)
- Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
---
## Установка
### Сборка и прошивка через PlatformIO
```bash
pio run -e ATmega328P -t upload
```
### Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c`, добавьте зависимость `zh_rf_config` и адаптируйте макросы конфигурации.
```ini
; platformio.ini
lib_deps =
https://github.com/aZholtikov/zh_rf_config
```
```c
// Зависимости: стандартные заголовки AVR-libc, математика и конфигурация протокола
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h"
#include "util/delay.h"
#include "math.h"
#include "zh_rf_config.h"
```