Compare commits

..
12 Commits
Author SHA1 Message Date
aZholtikov 5a8094e9ff style: big renaming 2026-10-31 07:37:20 +03:00
aZholtikov e334aec37f style: big renaming 2026-10-25 11:48:46 +03:00
aZholtikov cb5c29518b doc: updated wiki 2026-10-25 08:48:53 +03:00
aZholtikov 6261106b30 style: minor changes 2026-10-25 08:31:08 +03:00
aZholtikov 035e5edfd4 perf: removed unused variable 2026-10-24 11:09:19 +03:00
aZholtikov 4514ce4a10 feat: added sensor pairing to gateway 2026-10-24 10:03:54 +03:00
aZholtikov cd81e27665 doc: added license 2026-10-22 22:11:59 +03:00
aZholtikov c909c40d6a perf: added rounding of raw battery voltage 2026-10-22 13:30:21 +03:00
aZholtikov 10226a5add doc: added pcb images 2026-10-22 13:04:09 +03:00
aZholtikov a70300c259 doc: updated wiki 2026-10-21 20:13:58 +03:00
aZholtikov 286ab8fb81 doc: updated wiki 2026-10-21 20:02:24 +03:00
aZholtikov 564d628338 doc: updated wiki 2026-10-21 20:00:26 +03:00
8 changed files with 1059 additions and 425 deletions

No files matched your search

+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.
+4 -3
View File
@@ -2,13 +2,13 @@
## Features ## Features
1. Wireless touch pad sensor for ATMega 168/328 microcontrollers with nRF24L01+ transceiver. 1. Wireless touch pad sensor for ATMega168/328 microcontrollers with nRF24L01+ transceiver.
2. Power consumption in the sleep mode approximately 2 uA. 2. Power consumption in the sleep mode approximately 2 uA.
3. Transmits device identifier and battery voltage level on each touch event. 3. Transmits device identifier and battery voltage level on each touch event.
4. Battery measurement through internal ADC with 1.1 V bandgap reference. 4. Battery measurement through internal ADC with 1.1 V bandgap reference.
5. Wake-up from power-down mode by external interrupt on rising edge. 5. Wake-up from power-down mode by external interrupt on rising edge.
6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB. 6. Automatic pairing with the gateway to obtain a device identifier stored in EEPROM.
7. Easy installation into any standard touch pad enclosure. 7. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB.
## Attention ## Attention
@@ -16,6 +16,7 @@
2. After power-on reset the first touch event may not trigger a transmission. 2. After power-on reset the first touch event may not trigger a transmission.
3. Multi-touch is not supported, only a single touch event per transmission cycle. 3. Multi-touch is not supported, only a single touch event per transmission cycle.
4. Changing gateway address or channel requires reflashing the microcontroller. 4. Changing gateway address or channel requires reflashing the microcontroller.
5. If the gateway does not respond during pairing, the device enters permanent sleep until reset.
## Using ## Using
Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

+6 -6
View File
@@ -12,7 +12,7 @@ upload_flags =
-Pusb -Pusb
-e -e
lib_deps = lib_deps =
https://github.com/aZholtikov/zh_rf_config https://github.com/aZholtikov/zh_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
-333
View File
@@ -1,333 +0,0 @@
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h"
#include "util/delay.h"
#include "zh_rf_config.h"
#if defined(__AVR_ATmega328PB__)
#ifndef SPCR
#define SPCR SPCR0
#endif
#ifndef SPDR
#define SPDR SPDR0
#endif
#ifndef SPSR
#define SPSR SPSR0
#endif
#ifndef SPE
#define SPE SPE0
#endif
#ifndef MSTR
#define MSTR MSTR0
#endif
#ifndef SPR0
#define SPR0 SPR00
#endif
#ifndef SPIF
#define SPIF SPIF0
#endif
#endif
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
#define DEVICE_ID 6
#define NRF_CMD_W_REGISTER 0x20
#define NRF_CMD_W_TX_PAYLOAD 0xA0
#define NRF_CMD_FLUSH_TX 0xE1
#define NRF_CMD_NOP 0xFF
#define NRF_REG_CONFIG 0x00
#define NRF_REG_EN_AA 0x01
#define NRF_REG_EN_RXADDR 0x02
#define NRF_REG_SETUP_AW 0x03
#define NRF_REG_SETUP_RETR 0x04
#define NRF_REG_RF_CH 0x05
#define NRF_REG_RF_SETUP 0x06
#define NRF_REG_STATUS 0x07
#define NRF_REG_RX_ADDR_P0 0x0A
#define NRF_REG_TX_ADDR 0x10
#define NRF_CONFIG_MASK_RX_DR 0x40
#define NRF_CONFIG_EN_CRC 0x08
#define NRF_CONFIG_PWR_UP 0x02
#define NRF_STATUS_RX_DR 0x40
#define NRF_STATUS_TX_DS 0x20
#define NRF_STATUS_MAX_RT 0x10
/**
* @brief Initialize INT1 external interrupt for the touch-pad wake-up signal.
*
* Configures PD3 as a rising-edge triggered external interrupt source and
* enables the INT1 interrupt mask bit. The ISR sets the send_request flag
* to trigger a radio transmission from the main loop.
*/
static void zh_pad_interrupt_init(void);
/**
* @brief Initialize the hardware SPI peripheral in master mode.
*
* Configures MOSI, SCK and SS pins as outputs, drives SS high, and enables
* SPI with Fosc/16 clock rate.
*/
static void zh_spi_init(void);
/**
* @brief Perform a single full-duplex SPI byte transfer.
*
* @param data Byte to transmit
*
* @return Byte received from the slave during the transfer
*/
static uint8_t zh_spi_transfer(uint8_t data);
/**
* @brief Initialize the nRF24L01+ transceiver for TX-only operation.
*
* Drives CE low, configures auto-acknowledgment, address width, retransmit
* settings, channel, RF power and data rate, and loads the gateway address
* into both TX and RX pipe 0 registers.
*/
static void zh_nrf24_init(void);
/**
* @brief Transmit a payload buffer through the nRF24L01+ module.
*
* Powers up the radio, flushes the TX FIFO, loads the payload, pulses CE to
* start transmission, waits for the TX_DS or MAX_RT flag, then clears status
* flags and returns the radio to power-down mode.
*
* @param buf Pointer to the payload data to transmit (must not be NULL)
* @param size Number of bytes in the payload buffer (must be > 0)
*/
static void zh_nrf24_send(uint8_t *buf, uint8_t size);
/**
* @brief Write a data buffer to the nRF24L01+ via SPI with a given command.
*
* Asserts CSN low, sends the command byte followed by the buffer contents,
* then deasserts CSN.
*
* @param cmd SPI command byte to send before the data
* @param buf Pointer to the data buffer to write (must not be NULL)
* @param count Number of bytes to transfer from the buffer
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count);
/**
* @brief Write a single byte value to an nRF24L01+ register.
*
* @param reg Register address to write to
* @param val Byte value to store in the register
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val);
/**
* @brief Write a multi-byte buffer into an nRF24L01+ register.
*
* Convenience wrapper around zh_nrf24_write_buffer that prepends the
* W_REGISTER command to the register address.
*
* @param reg Register address to write to
* @param buf Pointer to the data buffer to write (must not be NULL)
* @param count Number of bytes to transfer from the buffer
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count);
/**
* @brief Send a single-byte command to the nRF24L01+ and read the status.
*
* @param cmd Command byte to send
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t zh_nrf24_send_command(uint8_t cmd);
/**
* @brief Measure the battery voltage using the internal AVcc reference.
*
* Enables the ADC with the 1.1 V internal reference on the 1.1 V supply
* channel, performs two conversions and returns the calculated battery
* voltage based on the AVcc-to-internal-reference ratio.
*
* @return Battery voltage in volts, or 0.0 if the ADC reading is zero
*/
static float zh_get_battery_voltage(void);
static volatile uint8_t send_request = 0;
zh_rf_sensor_transmitted_data_t transmitted_data = {DEVICE_ID, ZH_RF_SENSOR_TOUCH_SWITCH, 0, 0, 0, 0, 0};
int main(void)
{
cli();
zh_pad_interrupt_init();
zh_spi_init();
zh_nrf24_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();
for (;;)
{
if (send_request == 1)
{
cli();
send_request = 0;
sei();
transmitted_data.battery_voltage = (int16_t)(zh_get_battery_voltage() * 100.0);
zh_nrf24_send((uint8_t *)&transmitted_data, sizeof(transmitted_data));
}
else
{
cli();
if (send_request == 0)
{
sleep_enable();
sei();
sleep_cpu();
}
sei();
sleep_disable();
}
}
return 0;
}
ISR(INT1_vect)
{
send_request = 1;
}
static void zh_pad_interrupt_init(void)
{
DDRD &= ~(1 << PD3);
PORTD &= ~(1 << PD3);
EICRA &= ~((1 << ISC11) | (1 << ISC10));
EICRA |= (1 << ISC11) | (1 << ISC10);
EIFR |= (1 << INTF1);
EIMSK |= (1 << INT1);
}
static void zh_spi_init(void)
{
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5);
PORTB |= (1 << PB2);
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}
static uint8_t zh_spi_transfer(uint8_t data)
{
SPDR = data;
while ((SPSR & (1 << SPIF)) == 0)
{
}
return SPDR;
}
static void zh_nrf24_init(void)
{
_delay_ms(100);
DDRB |= (1 << PB1);
PORTB &= ~(1 << PB1);
DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2);
uint64_t gateway_address = GATEWAY_ADDRESS;
zh_nrf24_write_register(NRF_REG_CONFIG, 0x00);
zh_nrf24_write_register(NRF_REG_EN_AA, 0x01);
zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01);
zh_nrf24_write_register(NRF_REG_SETUP_AW, 0x01);
zh_nrf24_write_register(NRF_REG_SETUP_RETR, 0xFF);
zh_nrf24_write_register(NRF_REG_RF_CH, GATEWAY_CHANNEL);
zh_nrf24_write_register(NRF_REG_RF_SETUP, 0x26);
zh_nrf24_write_buff_register(NRF_REG_TX_ADDR, (uint8_t *)&gateway_address, 3);
zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, (uint8_t *)&gateway_address, 3);
zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT);
zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
}
static void zh_nrf24_send(uint8_t *buf, uint8_t size)
{
zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
_delay_ms(5);
zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size);
PORTB |= (1 << PB1);
uint8_t count = 0;
while ((PIND & (1 << PD2)) != 0)
{
if (++count > 100)
{
break;
}
_delay_ms(1);
}
PORTB &= ~(1 << PB1);
uint8_t status = zh_nrf24_send_command(NRF_CMD_NOP);
zh_nrf24_write_register(NRF_REG_STATUS, status);
zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
}
static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count)
{
PORTB &= ~(1 << PB2);
uint8_t status = zh_spi_transfer(cmd);
while (count-- != 0)
{
zh_spi_transfer(*(buf++));
}
PORTB |= (1 << PB2);
return status;
}
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val)
{
PORTB &= ~(1 << PB2);
uint8_t status = zh_spi_transfer(reg | NRF_CMD_W_REGISTER);
zh_spi_transfer(val);
PORTB |= (1 << PB2);
return status;
}
static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count)
{
return zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count);
}
static uint8_t zh_nrf24_send_command(uint8_t cmd)
{
PORTB &= ~(1 << PB2);
uint8_t status = zh_spi_transfer(cmd);
PORTB |= (1 << PB2);
return status;
}
static float zh_get_battery_voltage(void)
{
ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
_delay_ms(10);
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC))
{
}
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC))
{
}
uint16_t adc = ADCL;
adc |= (uint16_t)ADCH << 8;
ADCSRA &= ~(1 << ADEN);
if (adc == 0)
{
return 0.0;
}
return (1024.0 * 1.1) / (float)adc;
}
+771
View File
@@ -0,0 +1,771 @@
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/fuse.h"
#include "avr/interrupt.h"
#include "avr/eeprom.h"
#include "avr/wdt.h"
#include "util/delay.h"
#include "math.h"
#include "stdbool.h"
#include "zh_nrf24_config.h"
#if defined(__AVR_ATmega328PB__)
#ifndef SPCR
#define SPCR SPCR0
#endif
#ifndef SPDR
#define SPDR SPDR0
#endif
#ifndef SPSR
#define SPSR SPSR0
#endif
#ifndef SPE
#define SPE SPE0
#endif
#ifndef MSTR
#define MSTR MSTR0
#endif
#ifndef SPR0
#define SPR0 SPR00
#endif
#ifndef SPIF
#define SPIF SPIF0
#endif
#ifndef PRR
#define PRR PRR0
#endif
#ifndef PRTWI
#define PRTWI PRTWI0
#endif
#ifndef PRSPI
#define PRSPI PRSPI0
#endif
#endif
#define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF
#define EEPROM_DEVICE_ID_ADDR ((uint8_t *)0x00)
#define DEVICE_ID_UNASSIGNED 0xFF
#define PAIRING_TIMEOUT_ATTEMPTS 7
#define WDT_T_250MS 0x04
#define WDT_T_4S 0x08
#define NRF_CMD_W_REGISTER 0x20
#define NRF_CMD_W_TX_PAYLOAD 0xA0
#define NRF_CMD_R_RX_PAYLOAD 0x61
#define NRF_CMD_FLUSH_TX 0xE1
#define NRF_CMD_FLUSH_RX 0xE2
#define NRF_CMD_NOP 0xFF
#define NRF_CMD_ACTIVATE 0x50
#define NRF_CMD_R_RX_PL_WID 0x60
#define NRF_REG_CONFIG 0x00
#define NRF_REG_EN_AA 0x01
#define NRF_REG_EN_RXADDR 0x02
#define NRF_REG_SETUP_AW 0x03
#define NRF_REG_SETUP_RETR 0x04
#define NRF_REG_RF_CH 0x05
#define NRF_REG_RF_SETUP 0x06
#define NRF_REG_STATUS 0x07
#define NRF_REG_RX_ADDR_P0 0x0A
#define NRF_REG_TX_ADDR 0x10
#define NRF_REG_RX_PW_P0 0x11
#define NRF_REG_DYNPD 0x1C
#define NRF_REG_FEATURE 0x1D
#define NRF_CONFIG_MASK_RX_DR 0x40
#define NRF_CONFIG_MASK_TX_DS 0x20
#define NRF_CONFIG_MASK_MAX_RT 0x10
#define NRF_CONFIG_EN_CRC 0x08
#define NRF_CONFIG_PWR_UP 0x02
#define NRF_CONFIG_PRIM_RX 0x01
#define NRF_STATUS_RX_DR 0x40
#define NRF_STATUS_TX_DS 0x20
#define NRF_STATUS_MAX_RT 0x10
#define NRF_FEATURE_EN_DPL 0x04
#define NRF_DYNPD_DPL_P0 0x01
/**
* @brief Load the device identifier from EEPROM.
*
* @return Stored device id, or DEVICE_ID_UNASSIGNED if none is stored
*/
static uint8_t _zh_load_device_id(void);
/**
* @brief Save the device identifier to EEPROM.
*
* @param id Device identifier to store
*/
static void _zh_save_device_id(uint8_t id);
/**
* @brief Initialize INT1 external interrupt for the touch-pad wake-up signal.
*
* Configures PD3 as a rising-edge triggered external interrupt source and
* enables the INT1 interrupt mask bit. The ISR sets the _send_request flag
* to trigger a radio transmission from the main loop.
*/
static void _zh_pad_interrupt_init(void);
/**
* @brief Initialize the hardware SPI peripheral in master mode.
*
* Configures MOSI, SCK and SS pins as outputs, drives SS high, and enables
* SPI with Fosc/16 clock rate.
*/
static void _zh_spi_init(void);
/**
* @brief Perform a single full-duplex SPI byte transfer.
*
* @param data Byte to transmit
*
* @return Byte received from the slave during the transfer
*/
static uint8_t _zh_spi_transfer(uint8_t data);
/**
* @brief Initialize the nRF24L01+ transceiver for TX-only operation.
*
* Drives CE low, configures auto-acknowledgment, address width, retransmit
* settings, channel, RF power and data rate, and loads the gateway address
* into both TX and RX pipe 0 registers.
*/
static void _zh_nrf24_init(void);
/**
* @brief Initialize INT0 external interrupt for the nRF24 IRQ signal.
*
* Configures PD2 as a falling-edge triggered external interrupt source and
* enables the INT0 interrupt mask bit. The ISR sets the _nrf_event flag used
* by the pairing state machine.
*/
static void _zh_nrf24_interrupt_init(void);
/**
* @brief Transmit a payload buffer through the nRF24L01+ module.
*
* Powers up the radio, flushes the TX FIFO, loads the payload, pulses CE to
* start transmission, waits for the TX_DS or MAX_RT flag, then clears status
* flags and returns the radio to power-down mode.
*
* @param buf Pointer to the payload data to transmit (must not be NULL)
* @param size Number of bytes in the payload buffer (must be > 0)
*/
static void _zh_nrf24_send(uint8_t *buf, uint8_t size);
/**
* @brief Start a transmission without blocking on completion.
*
* Loads the payload, pulses CE for 15 us and returns immediately. The
* transmission completion is signalled by the nRF24 IRQ pin.
*
* @param buf Pointer to the payload data to transmit (must not be NULL)
* @param size Number of bytes in the payload buffer (must be > 0)
*/
static void _zh_nrf24_send_no_wait(uint8_t *buf, uint8_t size);
/**
* @brief Send a single-byte command to the nRF24L01+ and read the status.
*
* @param cmd Command byte to send
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t _zh_nrf24_send_command(uint8_t cmd);
/**
* @brief Write a data buffer to the nRF24L01+ via SPI with a given command.
*
* Asserts CSN low, sends the command byte followed by the buffer contents,
* then deasserts CSN.
*
* @param cmd SPI command byte to send before the data
* @param buf Pointer to the data buffer to write (must not be NULL)
* @param count Number of bytes to transfer from the buffer
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t _zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count);
/**
* @brief Write a single byte value to an nRF24L01+ register.
*
* @param reg Register address to write to
* @param val Byte value to store in the register
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t _zh_nrf24_write_register(uint8_t reg, uint8_t val);
/**
* @brief Write a multi-byte buffer into an nRF24L01+ register.
*
* Convenience wrapper around _zh_nrf24_write_buffer that prepends the
* W_REGISTER command to the register address.
*
* @param reg Register address to write to
* @param buf Pointer to the data buffer to write (must not be NULL)
* @param count Number of bytes to transfer from the buffer
*
* @return Status register value returned by the nRF24L01+
*/
static uint8_t _zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count);
/**
* @brief Read the received payload from the nRF24L01+ into a buffer.
*
* @param buf Pointer to the destination buffer (must not be NULL)
* @param count Number of bytes to read from the RX FIFO
*/
static void _zh_nrf24_read_payload(uint8_t *buf, uint8_t count);
/**
* @brief Read the width of the payload in the nRF24L01+ RX FIFO.
*
* @return Payload width in bytes
*/
static uint8_t _zh_nrf24_read_payload_width(void);
/**
* @brief Switch the nRF24L01+ to RX mode on pipe 0.
*/
static void _zh_nrf24_enter_rx(void);
/**
* @brief Put the nRF24L01+ into power-down mode.
*/
static void _zh_nrf24_enter_power_down(void);
/**
* @brief Start the watchdog timer in interrupt mode with the given timeout.
*
* @param timeout_bits Watchdog timeout prescaler bits (WDP3:0)
*/
static void _zh_wdt_start(uint8_t timeout_bits);
/**
* @brief Stop the watchdog timer.
*/
static void _zh_wdt_stop(void);
/**
* @brief Generate a pseudo-random token from MCU state and ADC noise.
*
* @return 16-bit random token (never zero)
*/
static uint16_t _zh_get_random_token(void);
/**
* @brief Run the non-blocking pairing procedure to obtain a device id.
*
* Sends pairing requests, waits for the gateway acknowledgment in RX mode
* and retries with watchdog-timed backoff. Sleeps forever if the gateway
* does not answer within the allowed number of attempts.
*
* @return Device identifier assigned by the gateway
*/
static uint8_t _zh_do_pairing(void);
/**
* @brief Enter permanent power-down sleep until the next reset.
*
* Disables all interrupts, the watchdog, the radio, the ADC, SPI and all
* unused peripheral clocks, then loops in power-down sleep mode.
*/
static void _zh_enter_final_sleep(void);
/**
* @brief Measure the battery voltage using the internal AVcc reference.
*
* Enables the ADC with the 1.1 V internal reference on the 1.1 V supply
* channel, performs two conversions and returns the calculated battery
* voltage based on the AVcc-to-internal-reference ratio.
*
* @return Battery voltage in volts, or 0.0 if the ADC reading is zero
*/
static float _zh_get_battery_voltage(void);
static volatile bool _send_request = false;
static volatile bool _nrf_event = false;
zh_nrf24_sensor_transmitted_data_t _transmitted_data = {0};
int main(void)
{
_transmitted_data.type = ZH_NRF24_SENSOR_TOUCH_SWITCH;
MCUSR &= ~(1 << WDRF);
wdt_disable();
cli();
_zh_pad_interrupt_init();
_zh_spi_init();
_zh_nrf24_init();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sei();
uint8_t device_id = _zh_load_device_id();
if (device_id == DEVICE_ID_UNASSIGNED)
{
device_id = _zh_do_pairing();
}
_transmitted_data.id = (int16_t)device_id;
{
uint8_t probe[ZH_NRF24_MAX_PAYLOAD_SIZE];
if (zh_nrf24_sensor_build_payload(&_transmitted_data, probe, sizeof(probe)) == 0)
{
_zh_enter_final_sleep();
}
}
for (;;)
{
if (_send_request == true)
{
cli();
_send_request = false;
sei();
_transmitted_data.battery = (int16_t)lroundf(_zh_get_battery_voltage() * 100.0);
uint8_t payload[ZH_NRF24_MAX_PAYLOAD_SIZE];
uint8_t size = zh_nrf24_sensor_build_payload(&_transmitted_data, payload, sizeof(payload));
if (size > 0)
{
_zh_nrf24_send(payload, size);
}
}
else
{
cli();
if (_send_request == false)
{
sleep_enable();
sei();
sleep_cpu();
}
sei();
sleep_disable();
}
}
return 0;
}
ISR(INT1_vect)
{
_send_request = true;
}
ISR(INT0_vect)
{
_nrf_event = true;
}
ISR(WDT_vect)
{
}
static uint8_t _zh_load_device_id(void)
{
uint8_t id = eeprom_read_byte(EEPROM_DEVICE_ID_ADDR);
if (id == 0xFF || id == 0x00)
{
return DEVICE_ID_UNASSIGNED;
}
return id;
}
static void _zh_save_device_id(uint8_t id)
{
eeprom_update_byte(EEPROM_DEVICE_ID_ADDR, id);
}
static void _zh_pad_interrupt_init(void)
{
DDRD &= ~(1 << PD3);
PORTD &= ~(1 << PD3);
EICRA &= ~((1 << ISC11) | (1 << ISC10));
EICRA |= (1 << ISC11) | (1 << ISC10);
EIFR |= (1 << INTF1);
EIMSK |= (1 << INT1);
}
static void _zh_spi_init(void)
{
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5);
PORTB |= (1 << PB2);
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
}
static uint8_t _zh_spi_transfer(uint8_t data)
{
SPDR = data;
while ((SPSR & (1 << SPIF)) == 0)
{
}
return SPDR;
}
static void _zh_nrf24_init(void)
{
_delay_ms(100);
DDRB |= (1 << PB1);
PORTB &= ~(1 << PB1);
DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2);
uint64_t gateway_address = GATEWAY_ADDRESS;
uint8_t activation_key = 0x73;
_zh_nrf24_write_register(NRF_REG_CONFIG, 0x00);
_zh_nrf24_write_register(NRF_REG_EN_AA, 0x01);
_zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01);
_zh_nrf24_write_register(NRF_REG_SETUP_AW, 0x01);
_zh_nrf24_write_register(NRF_REG_SETUP_RETR, 0xFF);
_zh_nrf24_write_register(NRF_REG_RF_CH, GATEWAY_CHANNEL);
_zh_nrf24_write_register(NRF_REG_RF_SETUP, 0x26);
_zh_nrf24_write_buff_register(NRF_REG_TX_ADDR, (uint8_t *)&gateway_address, 3);
_zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, (uint8_t *)&gateway_address, 3);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT);
_zh_nrf24_write_buffer(NRF_CMD_ACTIVATE, &activation_key, sizeof(activation_key));
_zh_nrf24_write_register(NRF_REG_FEATURE, NRF_FEATURE_EN_DPL);
_zh_nrf24_write_register(NRF_REG_DYNPD, NRF_DYNPD_DPL_P0);
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
}
static void _zh_nrf24_interrupt_init(void)
{
DDRD &= ~(1 << PD2);
PORTD |= (1 << PD2);
EICRA &= ~((1 << ISC01) | (1 << ISC00));
EICRA |= (1 << ISC01);
EIFR |= (1 << INTF0);
EIMSK |= (1 << INT0);
}
static void _zh_nrf24_send(uint8_t *buf, uint8_t size)
{
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
_delay_ms(5);
_zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
_zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size);
PORTB |= (1 << PB1);
uint8_t count = 0;
while ((PIND & (1 << PD2)) != 0)
{
if (++count > 100)
{
break;
}
_delay_ms(1);
}
PORTB &= ~(1 << PB1);
uint8_t status = _zh_nrf24_send_command(NRF_CMD_NOP);
_zh_nrf24_write_register(NRF_REG_STATUS, status);
_zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
}
static void _zh_nrf24_send_no_wait(uint8_t *buf, uint8_t size)
{
PORTB &= ~(1 << PB1);
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
_delay_ms(2);
_zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
_zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size);
PORTB |= (1 << PB1);
_delay_us(15);
PORTB &= ~(1 << PB1);
}
static uint8_t _zh_nrf24_send_command(uint8_t cmd)
{
PORTB &= ~(1 << PB2);
uint8_t status = _zh_spi_transfer(cmd);
PORTB |= (1 << PB2);
return status;
}
static uint8_t _zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count)
{
PORTB &= ~(1 << PB2);
uint8_t status = _zh_spi_transfer(cmd);
while (count-- != 0)
{
_zh_spi_transfer(*(buf++));
}
PORTB |= (1 << PB2);
return status;
}
static uint8_t _zh_nrf24_write_register(uint8_t reg, uint8_t val)
{
PORTB &= ~(1 << PB2);
uint8_t status = _zh_spi_transfer(reg | NRF_CMD_W_REGISTER);
_zh_spi_transfer(val);
PORTB |= (1 << PB2);
return status;
}
static uint8_t _zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count)
{
return _zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count);
}
static void _zh_nrf24_read_payload(uint8_t *buf, uint8_t count)
{
PORTB &= ~(1 << PB2);
_zh_spi_transfer(NRF_CMD_R_RX_PAYLOAD);
while (count-- != 0)
{
*buf++ = _zh_spi_transfer(0xFF);
}
PORTB |= (1 << PB2);
}
static uint8_t _zh_nrf24_read_payload_width(void)
{
PORTB &= ~(1 << PB2);
_zh_spi_transfer(NRF_CMD_R_RX_PL_WID);
uint8_t width = _zh_spi_transfer(0xFF);
PORTB |= (1 << PB2);
return width;
}
static void _zh_nrf24_enter_rx(void)
{
PORTB &= ~(1 << PB1);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP | NRF_CONFIG_PRIM_RX);
_delay_ms(2);
PORTB |= (1 << PB1);
_delay_us(130);
}
static void _zh_nrf24_enter_power_down(void)
{
PORTB &= ~(1 << PB1);
_zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_EN_CRC);
}
static void _zh_wdt_start(uint8_t timeout_bits)
{
uint8_t wdp = (timeout_bits & 0x07) | ((timeout_bits & 0x08) ? (1 << WDP3) : 0);
cli();
wdt_reset();
WDTCSR = (1 << WDCE) | (1 << WDE);
WDTCSR = (1 << WDIE) | wdp;
sei();
}
static void _zh_wdt_stop(void)
{
cli();
wdt_reset();
WDTCSR = (1 << WDCE) | (1 << WDE);
WDTCSR = 0;
sei();
}
static uint16_t _zh_get_random_token(void)
{
uint16_t token = MCUSR;
MCUSR = 0;
ADMUX = (1 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
_delay_ms(5);
for (uint8_t i = 0; i < 16; ++i)
{
ADCSRA |= (1 << ADSC);
while ((ADCSRA & (1 << ADSC)) != 0)
{
}
token = (token << 1) ^ (token >> 15) ^ ADC;
}
ADCSRA &= ~(1 << ADEN);
if (token == 0)
{
token = 0xACE1;
}
return token;
}
static uint8_t _zh_do_pairing(void)
{
uint16_t token = _zh_get_random_token();
zh_nrf24_sensor_pairing_data_t pairing_request;
pairing_request.type = (uint8_t)_transmitted_data.type;
pairing_request.magic = ZH_NRF24_PAIRING_REQ_MAGIC;
pairing_request.token_hi = (uint8_t)(token >> 8);
pairing_request.token_lo = (uint8_t)(token & 0xFF);
pairing_request.id = 0;
_zh_nrf24_interrupt_init();
zh_nrf24_sensor_pairing_state_t state = ZH_NRF24_PAIR_SEND;
uint8_t attempts = 0;
for (;;)
{
switch (state)
{
case ZH_NRF24_PAIR_SEND:
_nrf_event = false;
_zh_nrf24_send_no_wait((uint8_t *)&pairing_request, ZH_NRF24_PAIRING_PACKET_SIZE);
state = ZH_NRF24_PAIR_TX_WAIT;
break;
case ZH_NRF24_PAIR_TX_WAIT:
if ((PIND & (1 << PD2)) == 0)
{
_nrf_event = true;
}
else
{
_nrf_event = false;
_zh_wdt_start(WDT_T_250MS);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
_zh_wdt_stop();
sei();
}
if (_nrf_event == true)
{
_nrf_event = false;
uint8_t status = _zh_nrf24_send_command(NRF_CMD_NOP);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT | NRF_STATUS_RX_DR);
if ((status & NRF_STATUS_TX_DS) != 0)
{
_zh_nrf24_enter_rx();
state = ZH_NRF24_PAIR_RX_WAIT;
}
else
{
state = ZH_NRF24_PAIR_BACKOFF;
}
}
else
{
state = ZH_NRF24_PAIR_BACKOFF;
}
break;
case ZH_NRF24_PAIR_RX_WAIT:
if ((PIND & (1 << PD2)) == 0)
{
_nrf_event = true;
}
else
{
_nrf_event = false;
_zh_wdt_start(WDT_T_250MS);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
_zh_wdt_stop();
sei();
}
if (_nrf_event == true)
{
_nrf_event = false;
uint8_t status = _zh_nrf24_send_command(NRF_CMD_NOP);
if ((status & NRF_STATUS_RX_DR) != 0)
{
uint8_t width = _zh_nrf24_read_payload_width();
if (width > ZH_NRF24_MAX_PAYLOAD_SIZE)
{
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR);
state = ZH_NRF24_PAIR_BACKOFF;
break;
}
if (width != ZH_NRF24_PAIRING_PACKET_SIZE)
{
uint8_t dummy[ZH_NRF24_MAX_PAYLOAD_SIZE];
_zh_nrf24_read_payload(dummy, width);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR);
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
state = ZH_NRF24_PAIR_BACKOFF;
break;
}
zh_nrf24_sensor_pairing_data_t pairing_response;
_zh_nrf24_read_payload((uint8_t *)&pairing_response, width);
_zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR | NRF_STATUS_TX_DS | NRF_STATUS_MAX_RT);
_zh_nrf24_send_command(NRF_CMD_FLUSH_RX);
if (pairing_response.magic == ZH_NRF24_PAIRING_ACK_MAGIC && pairing_response.type == pairing_request.type &&
pairing_response.token_hi == pairing_request.token_hi && pairing_response.token_lo == pairing_request.token_lo &&
pairing_response.id != 0 && pairing_response.id != DEVICE_ID_UNASSIGNED)
{
_zh_nrf24_enter_power_down();
EIMSK &= ~(1 << INT0);
_zh_save_device_id(pairing_response.id);
return pairing_response.id;
}
}
state = ZH_NRF24_PAIR_BACKOFF;
}
else
{
state = ZH_NRF24_PAIR_BACKOFF;
}
break;
case ZH_NRF24_PAIR_BACKOFF:
_zh_nrf24_enter_power_down();
_nrf_event = false;
_zh_wdt_start(WDT_T_4S);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
_zh_wdt_stop();
sei();
if (++attempts >= PAIRING_TIMEOUT_ATTEMPTS)
{
_zh_enter_final_sleep();
}
state = ZH_NRF24_PAIR_SEND;
break;
}
}
}
static void _zh_enter_final_sleep(void)
{
cli();
EIMSK = 0;
EIFR = 0xFF;
_zh_wdt_stop();
PORTB &= ~(1 << PB1);
_zh_nrf24_write_register(NRF_REG_CONFIG, 0x00);
ADCSRA = 0;
SPCR = 0;
PRR = (1 << PRTWI) | (1 << PRTIM2) | (1 << PRTIM1) | (1 << PRTIM0) | (1 << PRSPI) | (1 << PRUSART0);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
for (;;)
{
sleep_cpu();
}
}
static float _zh_get_battery_voltage(void)
{
ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
_delay_ms(10);
ADCSRA |= (1 << ADSC);
while ((ADCSRA & (1 << ADSC)) != 0)
{
}
ADCSRA |= (1 << ADSC);
while ((ADCSRA & (1 << ADSC)) != 0)
{
}
uint16_t adc = ADCL;
adc |= (uint16_t)ADCH << 8;
ADCSRA &= ~(1 << ADEN);
if (adc == 0)
{
return 0.0;
}
return (1024.0 * 1.1) / (float)adc;
}
+205 -83
View File
@@ -1,125 +1,186 @@
# Описание компонента zh_avr_touch_pad # Описание прошивки zh_avr_touch_pad
## Общие сведения ## Общие сведения
**zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+. **zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.
Компонент работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. Прошивка работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи. При первом запуске выполняется автоматическое сопряжение (pairing) со шлюзом для получения идентификатора устройства, который сохраняется в EEPROM.
### Основные возможности ### Основные возможности
1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна. 1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна.
2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В. 2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
3. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз. 3. **Автоматическое сопряжение со шлюзом** — получение идентификатора устройства по радиоканалу с сохранением в EEPROM.
4. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB. 4. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
5. **Минимальная обвязка** — установка в стандартный корпус сенсорного выключателя. 5. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
6. **Пробуждение по внешнему прерыванию INT1** — falling edge на пине PD3. 6. **Пробуждение по внешнему прерыванию INT1** — rising edge на пине PD3.
7. **Автономная работа** — не требует внешних подтягивающих резисторов. 7. **Генерация псевдослучайного токена** — на основе шума АЦП и состояния MCUSR для уникализации запроса сопряжения.
8. **Watchdog-таймер в режиме прерывания** — используется для таймаутов при сопряжении без сброса МК.
--- ---
## Внутреннее устройство ## Внутреннее устройство
### Структура данных ### Структура передаваемых данных
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины. Прошивка не использует динамическое выделение памяти. Все данные хранятся в статической области и регистрах микроконтроллера.
#### Формат передаваемого пакета Пакет формируется в глобальной переменной `_transmitted_data` типа `zh_nrf24_sensor_transmitted_data_t` из внешней библиотеки `zh_nrf24_config` и сериализуется функцией `zh_nrf24_sensor_build_payload()` перед передачей.
| Поле | Тип | Описание | | Поле | Тип | Описание |
|------|-----|----------| | ------ | ----- | ---------- |
| `data[0]` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) | | `id` | `int16_t` | Идентификатор устройства, присвоенный шлюзом при сопряжении |
| `data[1]` | `int16_t` | Тип устройства (`DEVICE_TYPE = 4`) | | `type` | `int16_t` | Тип устройства (`ZH_NRF24_SENSOR_TOUCH_SWITCH`) |
| `data[2]` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) | | `battery` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
| `data[3]`–`data[6]` | `int16_t` | Зарезервированы (равны 0) |
### Конфигурационные макросы ### Конфигурационные макросы
| Макрос | Значение | Описание | | Макрос | Значение | Описание |
|--------|----------|----------| | -------- | ---------- | ---------- |
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза | | `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза | | `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства | | `EEPROM_DEVICE_ID_ADDR` | `((uint8_t *)0x00)` | Адрес в EEPROM для хранения идентификатора устройства |
| `DEVICE_TYPE` | `4` | Код типа устройства (touch pad) | | `DEVICE_ID_UNASSIGNED` | `0xFF` | Значение, обозначающее отсутствие сохранённого идентификатора |
| `PAIRING_TIMEOUT_ATTEMPTS` | `7` | Максимальное число попыток сопряжения перед уходом в окончательный сон |
| `WDT_T_250MS` | `0x04` | Таймаут watchdog-таймера 250 мс |
| `WDT_T_4S` | `0x08` | Таймаут watchdog-таймера 4 с |
### Управление памятью ### Управление памятью
Компонент не использует динамическую память. Все данные хранятся в стеке и регистрах микроконтроллера. Флаг `send_request` объявлен как `static volatile uint8_t` для корректного доступа из ISR и главного цикла. Прошивка не использует динамическую память. Глобальная переменная `_transmitted_data` заполняется при старте (идентификатор и тип), в рабочем цикле обновляется только поле `battery`. Флаги `_send_request`, `_nrf_event` объявлены как `static volatile bool` для корректного доступа из ISR и главного цикла.
### Флаги синхронизации
| Флаг | Устанавливается | Используется |
| ------ | ----------------- | -------------- |
| `_send_request` | ISR `INT1_vect` (касание) | Главный цикл — запуск передачи |
| `_nrf_event` | ISR `INT0_vect` (IRQ nRF24) | Конечный автомат сопряжения |
--- ---
## API (интерфейс) ## API (интерфейс)
Все функции являются `static` и доступны только внутри модуля `main.c`. Все функции являются `static` и доступны только внутри модуля `src/zh_avr_touch_pad.c`. Имена функций имеют префикс `_zh_`.
### Инициализация ### Инициализация
| Функция | Описание |
| --------- | ---------- |
| `_zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, rising edge) для пробуждения по касанию |
| `_zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
| `_zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
| `_zh_nrf24_interrupt_init()` | Настройка внешнего прерывания INT0 (PD2, falling edge) для сигнала IRQ nRF24 |
### Работа с EEPROM
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, falling edge) для пробуждения по касанию | | `_zh_load_device_id()` | Чтение идентификатора устройства из EEPROM, возврат `DEVICE_ID_UNASSIGNED` если не сохранён |
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) | | `_zh_save_device_id()` | Сохранение идентификатора устройства в EEPROM |
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
### Измерение ### Измерение
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В | | `_zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В, возврат значения в вольтах |
| `_zh_get_random_token()` | Генерация 16-битного псевдослучайного токена из шума АЦП и MCUSR для запроса сопряжения |
### Передача данных ### Передача данных
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `zh_nrf24_send()` | Передача буфера через nRF24L01+ с полным циклом TX | | `_zh_nrf24_send()` | Блокирующая передача буфера через nRF24L01+ с ожиданием завершения по пину IRQ (таймаут 100 мс) |
| `_zh_nrf24_send_no_wait()` | Неблокирующая передача — загружает буфер, импульс CE 15 мкс, завершение по прерыванию IRQ |
### Управление режимами nRF24L01+
| Функция | Описание |
|---------|----------|
| `_zh_nrf24_enter_rx()` | Переключение nRF24L01+ в режим приёма на трубе 0 |
| `_zh_nrf24_enter_power_down()` | Перевод nRF24L01+ в режим Power Down |
### Низкоуровневые SPI-операции ### Низкоуровневые SPI-операции
| Функция | Описание |
| --------- | ---------- |
| `_zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
| `_zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение регистра статуса |
| `_zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
| `_zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
| `_zh_nrf24_write_buffer()` | Запись буфера данных с указанной SPI-командой |
| `_zh_nrf24_read_payload()` | Чтение принятого пакета из RX FIFO nRF24 в буфер |
| `_zh_nrf24_read_payload_width()` | Чтение длины пакета в RX FIFO nRF24 |
### Watchdog-таймер
| Функция | Описание | | Функция | Описание |
|---------|----------| |---------|----------|
| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) | | `_zh_wdt_start()` | Запуск watchdog-таймера в режиме прерывания с указанным таймаутом |
| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ | | `_zh_wdt_stop()` | Остановка watchdog-таймера |
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой | ### Сон и сопряжение
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
| Функция | Описание |
|---------|----------|
| `_zh_do_pairing()` | Запуск конечного автомата сопряжения со шлюзом для получения идентификатора |
| `_zh_enter_final_sleep()` | Переход в окончательный Power-down сон до сброса: отключение всех прерываний, watchdog, радиомодуля, АЦП, SPI и неиспользуемых периферийных блоков |
--- ---
## Потокобезопасность ## Потокобезопасность
Компонент работает на bare-metal AVR без RTOS. Синхронизация между прерыванием и главным циклом реализуется следующим образом: Прошивка работает на bare-metal AVR без RTOS. Синхронизация между прерываниями и главным циклом реализуется следующим образом:
1. **`volatile` флаг** — переменная `send_request` объявлена как `volatile`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле. 1. **Флаги `volatile`** — переменные `_send_request`, `_nrf_event` объявлены как `static volatile bool`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
2. **Атомарная запись из ISR** — обработка INT1 устанавливает флаг в единицу. Запись одного байта на AVR является атомарной операцией. 2. **Атомарная запись из ISR** — обработка прерывания устанавливает флаг в `true`. Запись одного байта на AVR является атомарной операцией.
3. **Отключение прерываний при инициализации** — `cli()` перед настройкой периферии исключает гонки при старте. 3. **Отключение прерываний при проверке и сбросе флага** — `cli()` перед проверкой и сбросом `_send_request` в главном цикле исключает гонку между ISR и главным циклом.
4. **Отключение прерываний при инициализации** — `cli()` перед настройкой периферии исключает гонки при старте.
--- ---
## Алгоритмы ## Алгоритмы
### Алгоритм сопряжения (pairing)
Конечный автомат `_zh_do_pairing()` работает в четырёх состояниях:
1. **`ZH_NRF24_PAIR_SEND`** — генерируется псевдослучайный токен, формируется пакет запроса (`zh_nrf24_sensor_pairing_data_t` с `magic = ZH_NRF24_PAIRING_REQ_MAGIC`), выполняется неблокирующая передача через `_zh_nrf24_send_no_wait()`.
2. **`ZH_NRF24_PAIR_TX_WAIT`** — ожидание прерывания IRQ (завершение передачи). Если пин IRQ уже низкий — событие считается наступившим. Иначе — переход в сон с watchdog-таймером 250 мс. При флаге `TX_DS` — переход в приём, при `MAX_RT` — в backoff.
3. **`ZH_NRF24_PAIR_RX_WAIT`** — ожидание ответа шлюза. При получении пакета проверяется: длина равна `ZH_NRF24_PAIRING_PACKET_SIZE`, `magic == ZH_NRF24_PAIRING_ACK_MAGIC`, тип совпадает, токен совпадает, `id != 0` и `id != DEVICE_ID_UNASSIGNED`. При успехе — сохранение `id` в EEPROM и возврат.
4. **`ZH_NRF24_PAIR_BACKOFF`** — переход в Power Down, ожидание 4 с через watchdog. После `PAIRING_TIMEOUT_ATTEMPTS` (7) неудачных попыток — переход в `_zh_enter_final_sleep()`.
### Алгоритм измерения напряжения батареи ### Алгоритм измерения напряжения батареи
- ADC конфигурируется с внутренним опорным напряжением 1,1 В на канале AVcc (MUX[3:1] = 111). - АЦП конфигурируется с внутренним опорным напряжением 1,1 В на канале внутреннего источника 1,1 В (`REFS0=1`, `MUX[3:1]=111`), измеряется отношение AVcc к опоре 1,1 В.
- Предделитель частоты: 64 (ADPS[2:0] = 111), что даёт ~125 кГц при 8 МГц. - Предделитель частоты: 64 (`ADPS[2:0] = 111`), что даёт ~125 кГц при 8 МГц.
- Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат. - Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат.
- Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`. - Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`.
- При обрыве датчика (`adc == 0`) возвращается 0.0. - При нулевой выборке (`adc == 0`) возвращается `0.0`.
- После измерения АЦП отключается сбросом бита `ADEN`.
### Алгоритм передачи через nRF24L01+ ### Алгоритм генерации псевдослучайного токена
- Инициальное значение — регистр `MCUSR` (состояние сброса).
- АЦП запускается на 16 конверсий в канале с внутренним опорным напряжением.
- На каждой итерации: `token = (token << 1) ^ (token >> 15) ^ ADC` — сдвиг с переносом и XOR с шумом АЦП.
- Если результат равен 0, возвращается константа `0xACE1`.
### Алгоритм блокирующей передачи через nRF24L01+
- Переход в Power Up (бит `PWR_UP` в CONFIG). - Переход в Power Up (бит `PWR_UP` в CONFIG).
- Ожидание 5 мс (время стартования осциллятора). - Ожидание 5 мс (время стартования осциллятора).
- Очистка TX FIFO командой `FLUSH_TX`. - Очистка TX FIFO командой `FLUSH_TX`.
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`. - Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
- Импульс CE (установка в 1) для запуска передачи. - Установка CE в 1 для запуска передачи.
- Ожидание завершения по пину IRQ (PD2) с таймаутом 50 мс. - Ожидание спада пина IRQ (PD2) с таймаутом 100 мс (проверка каждые 1 мс).
- Сброс флагов статуса, очистка FIFO, переход в Power Down. - Сброс CE, чтение статуса, сброс флагов, очистка FIFO, переход в Power Down.
### Алгоритм сна и пробуждения ### Алгоритм сна и пробуждения
- Главный цикл проверяет флаг `send_request`. - Главный цикл проверяет флаг `_send_request`.
- Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down). - Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down).
- Пробуждение происходит по аппаратному прерыванию INT1 (falling edge на PD3). - Пробуждение происходит по аппаратному прерыванию INT1 (rising edge на PD3).
- ISR устанавливает `send_request = 1`, цикл просыпается и выполняет передачу. - ISR устанавливает `_send_request = true`, цикл просыпается и выполняет передачу.
--- ---
@@ -128,73 +189,118 @@
### Типовой цикл работы (main) ### Типовой цикл работы (main)
```c ```c
#include "avr/io.h"
#include "avr/sleep.h"
#include "avr/interrupt.h"
#include "avr/wdt.h"
#include "math.h"
#include "stdbool.h"
#include "zh_nrf24_config.h"
static volatile bool _send_request = false;
zh_nrf24_sensor_transmitted_data_t _transmitted_data = {0};
ISR(INT1_vect)
{
_send_request = true;
}
int main(void) int main(void)
{ {
// Отключаем прерывания на время инициализации _transmitted_data.type = ZH_NRF24_SENSOR_TOUCH_SWITCH;
MCUSR &= ~(1 << WDRF);
wdt_disable();
cli(); cli();
// Настраиваем периферию _zh_pad_interrupt_init();
zh_pad_interrupt_init(); _zh_spi_init();
zh_spi_init(); _zh_nrf24_init();
zh_nrf24_init();
// Конфигурируем режим сна Power-down
set_sleep_mode(SLEEP_MODE_PWR_DOWN); set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
// Включаем прерывания
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;
for (;;) for (;;)
{ {
if (send_request == 1) if (_send_request == true)
{ {
// Собираем и отправляем пакет cli();
send_request = 0; _send_request = false;
int16_t data[] = {DEVICE_ID, DEVICE_TYPE, sei();
(int16_t)(zh_get_battery_voltage() * 100.0), _transmitted_data.battery = (int16_t)lroundf(_zh_get_battery_voltage() * 100.0);
0, 0, 0, 0}; uint8_t payload[ZH_NRF24_MAX_PAYLOAD_SIZE];
zh_nrf24_send((uint8_t *)data, sizeof(data)); uint8_t size = zh_nrf24_sensor_build_payload(&_transmitted_data, payload, sizeof(payload));
if (size > 0)
{
_zh_nrf24_send(payload, size);
}
} }
else else
{ {
// Спим до следующего прерывания cli();
sleep_cpu(); if (_send_request == false)
{
sleep_enable();
sei();
sleep_cpu();
}
sei();
sleep_disable();
} }
} }
return 0;
} }
``` ```
### Изменение конфигурации под другое устройство ### Назначение пинов AVR для nRF24L01+
```c ```c
// Измените идентификатор и тип для нового устройства // PB2 (SS) -> CSN (выбор кристалла SPI, активный низкий)
#define DEVICE_ID 7 // Уникальный ID в сети // PB3 (MOSI) -> MOSI
#define DEVICE_TYPE 4 // 4 = touch pad // PB4 (MISO) -> MISO
// PB5 (SCK) -> SCK
// PB1 -> CE (запуск передачи/приёма)
// PD2 (INT0) -> IRQ (вход, активный низкий, с подтяжкой к VCC)
// PD3 (INT1) -> Выход сенсорного датчика (rising edge, без подтяжки)
```
### Изменение конфигурации шлюза
```c
// Измените адрес и канал под ваш шлюз
#define GATEWAY_CHANNEL 120 #define GATEWAY_CHANNEL 120
#define GATEWAY_ADDRESS 0xDDEEFF #define GATEWAY_ADDRESS 0xDDEEFF
// Тип устройства задаётся перечислением ZH_NRF24_SENSOR_TOUCH_SWITCH из zh_nrf24_config
``` ```
### Подключение по SPI (схема выводов) ---
```c ## Ошибки
// Назначение пинов AVR для nRF24L01+:
// PB2 (SS) -> CE (через резистор, или напрямую) Прошивка не возвращает коды ошибок в стиле ESP-IDF. Все функции имеют тип `void` или возвращают однобайтовый статус SPI (`uint8_t`) для низкоуровневых операций.
// PB3 (MOSI) -> MOSI
// PB5 (SCK) -> SCK | Условие | Поведение |
// PB1 -> CE (управление передачей) | --------- | ----------- |
// PD2 -> IRQ (вход, активный низкий) | Нулевое показание АЦП | `_zh_get_battery_voltage()` возвращает `0.0` |
// PD3 -> INT1 (вход сенсорного датчика) | Превышение таймаута передачи (100 мс) | Передача прерывается, пакет теряется |
``` | Неверная длина принятого пакета при сопряжении | Пакет отбрасывается, переход в `ZH_NRF24_PAIR_BACKOFF` |
| Превышение числа попыток сопряжения (7) | `_zh_enter_final_sleep()` — сон до сброса |
| `zh_nrf24_sensor_build_payload()` возвращает 0 | Передача пропускается, переход в `_zh_enter_final_sleep()` |
--- ---
## Ограничения ## Ограничения
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения. 1. **Только передача (TX-only) в рабочем цикле** — приём используется исключительно на этапе сопряжения, шлюз не может отправить данные устройству в штатном режиме.
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены. 2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки. 3. **Жёстко зашитый адрес и канал шлюза** — смена `GATEWAY_ADDRESS` или `GATEWAY_CHANNEL` требует перепрошивки.
4. **Таймаут передачи 50 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом). 4. **Таймаут блокирующей передачи 100 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом).
5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг — один бит). 5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг `_send_request` — один бит).
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%. 6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
7. **Нет механизма глубокого сна для периферии** — АЦП полностью отключается после измерения (`ADEN = 0`), но SPI остаётся включённым в режиме сна. 7. **SPI остаётся включённым в режиме сна** — перед окончательным сном SPI отключается в `_zh_enter_final_sleep()`, но в штатном режиме ожидания SPI не отключается между передачами.
--- ---
@@ -202,6 +308,7 @@ int main(void)
- PlatformIO с плагином `platform-atmelavr` - PlatformIO с плагином `platform-atmelavr`
- AVR-GCC (входит в состав PlatformIO) - AVR-GCC (входит в состав PlatformIO)
- Библиотека `zh_nrf24_config` (подключается через `lib_deps` в `platformio.ini`)
- Программатор USBasp (или совместимый) - Программатор USBasp (или совместимый)
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя) - Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
@@ -212,18 +319,33 @@ int main(void)
### Сборка и прошивка через PlatformIO ### Сборка и прошивка через PlatformIO
```bash ```bash
git clone https://github.com/aZholtikov/zh_avr_touch_pad
cd zh_avr_touch_pad
pio run -e ATmega328P -t upload pio run -e ATmega328P -t upload
``` ```
### Подключение в проекте ### Подключение в проекте
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c` и адаптируйте макросы конфигурации. Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/zh_avr_touch_pad.c`, добавьте зависимость `zh_nrf24_config` и адаптируйте макросы конфигурации.
```ini
; platformio.ini
lib_deps =
https://github.com/aZholtikov/zh_nrf24_config
```
### Подключение заголовков в коде
```c ```c
// Зависимости — только стандартные заголовки AVR-libc: // Зависимости: стандартные заголовки AVR-libc и конфигурация протокола
#include "avr/io.h" #include "avr/io.h"
#include "avr/sleep.h" #include "avr/sleep.h"
#include "avr/fuse.h" #include "avr/fuse.h"
#include "avr/interrupt.h" #include "avr/interrupt.h"
#include "avr/eeprom.h"
#include "avr/wdt.h"
#include "util/delay.h" #include "util/delay.h"
#include "math.h"
#include "stdbool.h"
#include "zh_nrf24_config.h"
``` ```