Compare commits
3
Commits
5b52fbb2a3
...
cb4411b40e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb4411b40e | ||
|
|
a85f4c9bc8 | ||
|
|
04a96b837a |
No files matched your search
@@ -1,11 +1,33 @@
|
|||||||
# Touch pad on ATMega 8/48/168/328 + nRF24L01
|
# AVR firmware for wireless touch pad (nRF24L01+)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
1. Power consumption in the sleep mode approximately 2 μA.
|
1. Wireless touch pad sensor for ATMega 168/328 microcontrollers with nRF24L01+ transceiver.
|
||||||
2. When triggered transmits battery level charge.
|
2. Power consumption in the sleep mode approximately 2 uA.
|
||||||
3. Easy installation into any standard touch pad enclosure.
|
3. Transmits device identifier and battery voltage level on each touch event.
|
||||||
|
4. Battery measurement through internal ADC with 1.1 V bandgap reference.
|
||||||
|
5. Wake-up from power-down mode by external interrupt on falling edge.
|
||||||
|
6. Supports ATMega168, ATMega168P, ATMega168PB, ATMega328, ATMega328P, ATMega328PB.
|
||||||
|
7. Easy installation into any standard touch pad enclosure.
|
||||||
|
|
||||||
## Note
|
## Attention
|
||||||
|
|
||||||
A gateway is required. For details see [RF Gateway](http://git.zh.com.ru/alexey.zholtikov/RF-Gateway).
|
1. A compatible RF Gateway is required to receive and process transmitted packets.
|
||||||
|
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.
|
||||||
|
4. Changing gateway address or channel requires reflashing the microcontroller.
|
||||||
|
|
||||||
|
## 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_touch_pad
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `platformio.ini`, select the target microcontroller environment and upload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run -e ATmega328P -t upload
|
||||||
|
```
|
||||||
Binary file not shown.
@@ -0,0 +1,83 @@
|
|||||||
|
[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
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
[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
|
||||||
+210
-45
@@ -4,23 +4,165 @@
|
|||||||
#include "avr/interrupt.h"
|
#include "avr/interrupt.h"
|
||||||
#include "util/delay.h"
|
#include "util/delay.h"
|
||||||
|
|
||||||
FUSES = {0xE2, 0xDF, 0x07};
|
#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_CHANNEL 120
|
||||||
#define GATEWAY_ADDRESS 0xDDEEFF
|
#define GATEWAY_ADDRESS 0xDDEEFF
|
||||||
#define DEVICE_ID 6
|
#define DEVICE_ID 6
|
||||||
#define DEVICE_TYPE 4
|
#define DEVICE_TYPE 4
|
||||||
|
|
||||||
|
#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 falling-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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
static uint8_t zh_nrf24_send_command(uint8_t cmd);
|
||||||
static float zh_get_battery_level_charge(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 uint8_t send_request = 0;
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
@@ -32,31 +174,41 @@ int main(void)
|
|||||||
sleep_enable();
|
sleep_enable();
|
||||||
sei();
|
sei();
|
||||||
for (;;)
|
for (;;)
|
||||||
|
{
|
||||||
|
if (send_request == 1)
|
||||||
|
{
|
||||||
|
send_request = 0;
|
||||||
|
int16_t data[] = {DEVICE_ID, DEVICE_TYPE, (int16_t)(zh_get_battery_voltage() * 100.0), 0, 0, 0, 0};
|
||||||
|
zh_nrf24_send((uint8_t *)data, sizeof(data));
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
sleep_cpu();
|
sleep_cpu();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ISR(INT1_vect)
|
ISR(INT1_vect)
|
||||||
{
|
{
|
||||||
cli();
|
send_request = 1;
|
||||||
int16_t data[] = {DEVICE_ID, DEVICE_TYPE, (zh_get_battery_level_charge() * 100), 0, 0, 0, 0};
|
|
||||||
zh_nrf24_send((uint8_t *)&data, sizeof(data));
|
|
||||||
sei();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zh_pad_interrupt_init(void)
|
static void zh_pad_interrupt_init(void)
|
||||||
{
|
{
|
||||||
EICRA |= ((1 << ISC11) | (1 << ISC10));
|
DDRD &= ~(1 << PD3);
|
||||||
|
PORTD &= ~(1 << PD3);
|
||||||
|
EICRA &= ~((1 << ISC11) | (1 << ISC10));
|
||||||
|
EICRA |= (1 << ISC11) | (1 << ISC10);
|
||||||
|
EIFR |= (1 << INTF1);
|
||||||
EIMSK |= (1 << INT1);
|
EIMSK |= (1 << INT1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zh_spi_init(void)
|
static void zh_spi_init(void)
|
||||||
{
|
{
|
||||||
DDRB |= ((1 << PORTB2) | (1 << PORTB3) | (1 << PORTB5));
|
DDRB |= (1 << PB2) | (1 << PB3) | (1 << PB5);
|
||||||
PORTB |= (1 << PORTB2);
|
PORTB |= (1 << PB2);
|
||||||
SPCR |= ((1 << SPE) | (1 << MSTR));
|
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t zh_spi_transfer(uint8_t data)
|
static uint8_t zh_spi_transfer(uint8_t data)
|
||||||
@@ -70,86 +222,99 @@ static uint8_t zh_spi_transfer(uint8_t data)
|
|||||||
|
|
||||||
static void zh_nrf24_init(void)
|
static void zh_nrf24_init(void)
|
||||||
{
|
{
|
||||||
|
_delay_ms(100);
|
||||||
|
DDRB |= (1 << PB1);
|
||||||
|
PORTB &= ~(1 << PB1);
|
||||||
uint64_t gateway_address = GATEWAY_ADDRESS;
|
uint64_t gateway_address = GATEWAY_ADDRESS;
|
||||||
DDRB |= (1 << PORTB1);
|
zh_nrf24_write_register(NRF_REG_CONFIG, 0x00);
|
||||||
PORTB &= ~(1 << PORTB1);
|
zh_nrf24_write_register(NRF_REG_EN_AA, 0x01);
|
||||||
zh_nrf24_write_register(0x00, 0x48);
|
zh_nrf24_write_register(NRF_REG_EN_RXADDR, 0x01);
|
||||||
zh_nrf24_write_register(0x01, 0x01);
|
zh_nrf24_write_register(NRF_REG_SETUP_AW, 0x01);
|
||||||
zh_nrf24_write_register(0x02, 0x01);
|
zh_nrf24_write_register(NRF_REG_SETUP_RETR, 0xFF);
|
||||||
zh_nrf24_write_register(0x03, 0x01);
|
zh_nrf24_write_register(NRF_REG_RF_CH, GATEWAY_CHANNEL);
|
||||||
zh_nrf24_write_register(0x04, 0xFF);
|
zh_nrf24_write_register(NRF_REG_RF_SETUP, 0x26);
|
||||||
zh_nrf24_write_register(0x05, GATEWAY_CHANNEL);
|
zh_nrf24_write_buff_register(NRF_REG_TX_ADDR, (uint8_t *)&gateway_address, 3);
|
||||||
zh_nrf24_write_register(0x06, 0x26);
|
zh_nrf24_write_buff_register(NRF_REG_RX_ADDR_P0, (uint8_t *)&gateway_address, 3);
|
||||||
zh_nrf24_write_buff_register(0x0A, (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_buff_register(0x10, (uint8_t *)&gateway_address, 3);
|
zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC);
|
||||||
PORTB &= ~(1 << PORTB2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zh_nrf24_send(uint8_t *buf, uint8_t size)
|
static void zh_nrf24_send(uint8_t *buf, uint8_t size)
|
||||||
{
|
{
|
||||||
zh_nrf24_write_register(0x00, 0x4A);
|
zh_nrf24_write_register(NRF_REG_CONFIG, NRF_CONFIG_MASK_RX_DR | NRF_CONFIG_EN_CRC | NRF_CONFIG_PWR_UP);
|
||||||
_delay_ms(5);
|
_delay_ms(5);
|
||||||
zh_nrf24_send_command(0xE1);
|
zh_nrf24_send_command(NRF_CMD_FLUSH_TX);
|
||||||
zh_nrf24_write_buffer(0xA0, buf, size);
|
zh_nrf24_write_buffer(NRF_CMD_W_TX_PAYLOAD, buf, size);
|
||||||
PORTB |= (1 << PORTB1);
|
PORTB |= (1 << PB1);
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
while ((PIND & (1 << PIND2)) != 0)
|
while ((PIND & (1 << PD2)) != 0)
|
||||||
{
|
{
|
||||||
if (++count > 100)
|
if (++count > 50)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_delay_ms(1);
|
_delay_ms(1);
|
||||||
}
|
}
|
||||||
zh_nrf24_write_register(0x07, zh_nrf24_send_command(0xFF));
|
PORTB &= ~(1 << PB1);
|
||||||
zh_nrf24_send_command(0xE1);
|
uint8_t status = zh_nrf24_send_command(NRF_CMD_NOP);
|
||||||
zh_nrf24_write_register(0x00, 0x48);
|
zh_nrf24_write_register(NRF_REG_STATUS, status);
|
||||||
PORTB &= ~((1 << PORTB1) | (1 << PORTB2));
|
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)
|
static uint8_t zh_nrf24_write_buffer(uint8_t cmd, uint8_t *buf, uint8_t count)
|
||||||
{
|
{
|
||||||
PORTB &= ~(1 << PORTB2);
|
PORTB &= ~(1 << PB2);
|
||||||
uint8_t status = zh_spi_transfer(cmd);
|
uint8_t status = zh_spi_transfer(cmd);
|
||||||
while (count-- != 0)
|
while (count-- != 0)
|
||||||
{
|
{
|
||||||
zh_spi_transfer(*(buf++));
|
zh_spi_transfer(*(buf++));
|
||||||
}
|
}
|
||||||
PORTB |= (1 << PORTB2);
|
PORTB |= (1 << PB2);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val)
|
static uint8_t zh_nrf24_write_register(uint8_t reg, uint8_t val)
|
||||||
{
|
{
|
||||||
PORTB &= ~(1 << PORTB2);
|
PORTB &= ~(1 << PB2);
|
||||||
uint8_t status = zh_spi_transfer(reg | 0x20);
|
uint8_t status = zh_spi_transfer(reg | NRF_CMD_W_REGISTER);
|
||||||
zh_spi_transfer(val);
|
zh_spi_transfer(val);
|
||||||
PORTB |= (1 << PORTB2);
|
PORTB |= (1 << PB2);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count)
|
static uint8_t zh_nrf24_write_buff_register(uint8_t reg, uint8_t *buf, uint8_t count)
|
||||||
{
|
{
|
||||||
return zh_nrf24_write_buffer((reg | 0x20), buf, count);
|
return zh_nrf24_write_buffer((reg | NRF_CMD_W_REGISTER), buf, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t zh_nrf24_send_command(uint8_t cmd)
|
static uint8_t zh_nrf24_send_command(uint8_t cmd)
|
||||||
{
|
{
|
||||||
PORTB &= ~(1 << PORTB2);
|
PORTB &= ~(1 << PB2);
|
||||||
uint8_t status = zh_spi_transfer(cmd);
|
uint8_t status = zh_spi_transfer(cmd);
|
||||||
PORTB |= (1 << PORTB2);
|
PORTB |= (1 << PB2);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float zh_get_battery_level_charge(void)
|
static float zh_get_battery_voltage(void)
|
||||||
{
|
{
|
||||||
ADMUX = ((1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1));
|
ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
|
||||||
ADCSRA |= (1 << ADEN);
|
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
|
||||||
_delay_ms(10);
|
_delay_ms(10);
|
||||||
ADCSRA |= (1 << ADSC);
|
ADCSRA |= (1 << ADSC);
|
||||||
while ((ADCSRA & (1 << ADSC)) != 0)
|
while (ADCSRA & (1 << ADSC))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
ADCSRA |= (1 << ADSC);
|
||||||
|
while (ADCSRA & (1 << ADSC))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
uint16_t adc = ADCL;
|
||||||
|
adc |= (uint16_t)ADCH << 8;
|
||||||
ADCSRA &= ~(1 << ADEN);
|
ADCSRA &= ~(1 << ADEN);
|
||||||
return ((1024 * 1.1) / (ADCL + (ADCH * 256)));
|
if (adc == 0)
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return (1024.0 * 1.1) / (float)adc;
|
||||||
}
|
}
|
||||||
+229
@@ -0,0 +1,229 @@
|
|||||||
|
# Описание компонента zh_avr_touch_pad
|
||||||
|
|
||||||
|
## Общие сведения
|
||||||
|
|
||||||
|
**zh_avr_touch_pad** — прошивка для AVR-микроконтроллеров ATMega168/328, реализующая беспроводную сенсорную кнопку на базе трансивера nRF24L01+.
|
||||||
|
|
||||||
|
Компонент работает в режиме глубокого сна (Power-down), пробуждается по внешнему прерыванию от сенсорного датчика и передаёт на шлюз пакет с идентификатором устройства и уровнем заряда батареи.
|
||||||
|
|
||||||
|
### Основные возможности
|
||||||
|
|
||||||
|
1. **Сверхнизкое энергопотребление** — около 2 мкА в режиме сна.
|
||||||
|
2. **Передача уровня заряда батареи** — измерение напряжения через внутренний АЦП с опорным напряжением 1,1 В.
|
||||||
|
3. **Работа по протоколу nRF24L01+** — передача через SPI, адресная доставка на шлюз.
|
||||||
|
4. **Поддержка нескольких МК** — ATMega168/168P/168PB/328/328P/328PB.
|
||||||
|
5. **Минимальная обвязка** — установка в стандартный корпус сенсорного выключателя.
|
||||||
|
6. **Пробуждение по внешнему прерыванию INT1** — falling edge на пине PD3.
|
||||||
|
7. **Автономная работа** — не требует внешних подтягивающих резисторов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Внутреннее устройство
|
||||||
|
|
||||||
|
### Структура данных
|
||||||
|
|
||||||
|
Компонент не использует динамическое выделение памяти и сложные структуры данных. Все данные передаются как массив `int16_t` фиксированной длины.
|
||||||
|
|
||||||
|
#### Формат передаваемого пакета
|
||||||
|
|
||||||
|
| Поле | Тип | Описание |
|
||||||
|
|------|-----|----------|
|
||||||
|
| `data[0]` | `int16_t` | Идентификатор устройства (`DEVICE_ID = 6`) |
|
||||||
|
| `data[1]` | `int16_t` | Тип устройства (`DEVICE_TYPE = 4`) |
|
||||||
|
| `data[2]` | `int16_t` | Напряжение батареи × 100 (например, 330 = 3.30 В) |
|
||||||
|
| `data[3]`–`data[6]` | `int16_t` | Зарезервированы (равны 0) |
|
||||||
|
|
||||||
|
### Конфигурационные макросы
|
||||||
|
|
||||||
|
| Макрос | Значение | Описание |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `GATEWAY_CHANNEL` | `120` | Радиоканал шлюза |
|
||||||
|
| `GATEWAY_ADDRESS` | `0xDDEEFF` | 3-байтовый адрес шлюза |
|
||||||
|
| `DEVICE_ID` | `6` | Уникальный идентификатор устройства |
|
||||||
|
| `DEVICE_TYPE` | `4` | Код типа устройства (touch pad) |
|
||||||
|
|
||||||
|
### Управление памятью
|
||||||
|
|
||||||
|
Компонент не использует динамическую память. Все данные хранятся в стеке и регистрах микроконтроллера. Флаг `send_request` объявлен как `static volatile uint8_t` для корректного доступа из ISR и главного цикла.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API (интерфейс)
|
||||||
|
|
||||||
|
Все функции являются `static` и доступны только внутри модуля `main.c`.
|
||||||
|
|
||||||
|
### Инициализация
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `zh_pad_interrupt_init()` | Настройка внешнего прерывания INT1 (PD3, falling edge) для пробуждения по касанию |
|
||||||
|
| `zh_spi_init()` | Инициализация аппаратного SPI в режиме мастера (Fosc/16) |
|
||||||
|
| `zh_nrf24_init()` | Инициализация трансивера nRF24L01+ в режим передачи |
|
||||||
|
|
||||||
|
### Измерение
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `zh_get_battery_voltage()` | Измерение напряжения батареи через внутренний АЦП с опорой 1,1 В |
|
||||||
|
|
||||||
|
### Передача данных
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `zh_nrf24_send()` | Передача буфера через nRF24L01+ с полным циклом TX |
|
||||||
|
|
||||||
|
### Низкоуровневые SPI-операции
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `zh_spi_transfer()` | Обмен одного байта по SPI (полный дуплекс) |
|
||||||
|
| `zh_nrf24_write_register()` | Запись одного байта в регистр nRF24L01+ |
|
||||||
|
| `zh_nrf24_write_buff_register()` | Запись многобайтового буфера в регистр nRF24L01+ |
|
||||||
|
| `zh_nrf24_write_buffer()` | Запись буфера данных с указанной командой |
|
||||||
|
| `zh_nrf24_send_command()` | Отправка однобайтовой команды и чтение статуса |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Потокобезопасность
|
||||||
|
|
||||||
|
Компонент работает на bare-metal AVR без RTOS. Синхронизация между прерыванием и главным циклом реализуется следующим образом:
|
||||||
|
|
||||||
|
1. **`volatile` флаг** — переменная `send_request` объявлена как `volatile`, что гарантирует чтение актуального значения из памяти при каждой проверке в главном цикле.
|
||||||
|
2. **Атомарная запись из ISR** — обработка INT1 устанавливает флаг в единицу. Запись одного байта на AVR является атомарной операцией.
|
||||||
|
3. **Отключение прерываний при инициализации** — `cli()` перед настройкой периферии исключает гонки при старте.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Алгоритмы
|
||||||
|
|
||||||
|
### Алгоритм измерения напряжения батареи
|
||||||
|
|
||||||
|
- ADC конфигурируется с внутренним опорным напряжением 1,1 В на канале AVcc (MUX[3:1] = 111).
|
||||||
|
- Предделитель частоты: 64 (ADPS[2:0] = 111), что даёт ~125 кГц при 8 МГц.
|
||||||
|
- Выполняются **две последовательные конверсии**: первая стабилизирует выборку, вторая даёт результат.
|
||||||
|
- Формула расчёта: `Vbat = (1024.0 × 1.1) / adc_value`.
|
||||||
|
- При обрыве датчика (`adc == 0`) возвращается 0.0.
|
||||||
|
|
||||||
|
### Алгоритм передачи через nRF24L01+
|
||||||
|
|
||||||
|
- Переход в Power Up (бит `PWR_UP` в CONFIG).
|
||||||
|
- Ожидание 5 мс (время стартования осциллятора).
|
||||||
|
- Очистка TX FIFO командой `FLUSH_TX`.
|
||||||
|
- Загрузка полезной нагрузки командой `W_TX_PAYLOAD`.
|
||||||
|
- Импульс CE (установка в 1) для запуска передачи.
|
||||||
|
- Ожидание завершения по пину IRQ (PD2) с таймаутом 50 мс.
|
||||||
|
- Сброс флагов статуса, очистка FIFO, переход в Power Down.
|
||||||
|
|
||||||
|
### Алгоритм сна и пробуждения
|
||||||
|
|
||||||
|
- Главный цикл проверяет флаг `send_request`.
|
||||||
|
- Если флаг не установлен — вызывается `sleep_cpu()` (режим Power-down).
|
||||||
|
- Пробуждение происходит по аппаратному прерыванию INT1 (falling edge на PD3).
|
||||||
|
- ISR устанавливает `send_request = 1`, цикл просыпается и выполняет передачу.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Примеры использования
|
||||||
|
|
||||||
|
### Типовой цикл работы (main)
|
||||||
|
|
||||||
|
```c
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
// Отключаем прерывания на время инициализации
|
||||||
|
cli();
|
||||||
|
// Настраиваем периферию
|
||||||
|
zh_pad_interrupt_init();
|
||||||
|
zh_spi_init();
|
||||||
|
zh_nrf24_init();
|
||||||
|
// Конфигурируем режим сна Power-down
|
||||||
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
|
sleep_enable();
|
||||||
|
// Включаем прерывания
|
||||||
|
sei();
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (send_request == 1)
|
||||||
|
{
|
||||||
|
// Собираем и отправляем пакет
|
||||||
|
send_request = 0;
|
||||||
|
int16_t data[] = {DEVICE_ID, DEVICE_TYPE,
|
||||||
|
(int16_t)(zh_get_battery_voltage() * 100.0),
|
||||||
|
0, 0, 0, 0};
|
||||||
|
zh_nrf24_send((uint8_t *)data, sizeof(data));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Спим до следующего прерывания
|
||||||
|
sleep_cpu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Изменение конфигурации под другое устройство
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Измените идентификатор и тип для нового устройства
|
||||||
|
#define DEVICE_ID 7 // Уникальный ID в сети
|
||||||
|
#define DEVICE_TYPE 4 // 4 = touch pad
|
||||||
|
#define GATEWAY_CHANNEL 120
|
||||||
|
#define GATEWAY_ADDRESS 0xDDEEFF
|
||||||
|
```
|
||||||
|
|
||||||
|
### Подключение по SPI (схема выводов)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Назначение пинов AVR для nRF24L01+:
|
||||||
|
// PB2 (SS) -> CE (через резистор, или напрямую)
|
||||||
|
// PB3 (MOSI) -> MOSI
|
||||||
|
// PB5 (SCK) -> SCK
|
||||||
|
// PB1 -> CE (управление передачей)
|
||||||
|
// PD2 -> IRQ (вход, активный низкий)
|
||||||
|
// PD3 -> INT1 (вход сенсорного датчика)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
1. **Только передача (TX-only)** — компонент не принимает данные, шлюз не может отправить подтверждение на уровне приложения.
|
||||||
|
2. **Отсутствие шифрования** — CRC включён, но AES-шифрование не используется. Пакеты могут быть перехвачены.
|
||||||
|
3. **Жёстко зашитый адрес шлюза** — смена адреса требует перепрошивки.
|
||||||
|
4. **Таймаут передачи 50 мс** — если шлюз недоступен, пакет теряется без повторной попытки на уровне приложения (аппаратные ретрансмиты настроены через `SETUP_RETR = 0xFF`: 15 попыток с максимальным интервалом).
|
||||||
|
5. **Одно сенсорное событие за цикл** — несколько касаний во время передачи будут потеряны (флаг — один бит).
|
||||||
|
6. **Точность измерения батареи** — зависит от стабильности внутреннего опорного напряжения 1,1 В и может отличаться от реального напряжения на ±3%.
|
||||||
|
7. **Нет механизма глубокого сна для периферии** — АЦП полностью отключается после измерения (`ADEN = 0`), но SPI остаётся включённым в режиме сна.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Требования для сборки
|
||||||
|
|
||||||
|
- PlatformIO с плагином `platform-atmelavr`
|
||||||
|
- AVR-GCC (входит в состав PlatformIO)
|
||||||
|
- Программатор USBasp (или совместимый)
|
||||||
|
- Фьюзы: LFUSE=`0xE2`, HFUSE=`0xDF`, EFUSE=`0x07` (8 МГц, без делителя)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
### Сборка и прошивка через PlatformIO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run -e ATmega328P -t upload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Подключение в проекте
|
||||||
|
|
||||||
|
Компонент является самодостаточной прошивкой — не подключается как библиотека. Для интеграции в другой проект скопируйте `src/main.c` и адаптируйте макросы конфигурации.
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Зависимости — только стандартные заголовки AVR-libc:
|
||||||
|
#include "avr/io.h"
|
||||||
|
#include "avr/sleep.h"
|
||||||
|
#include "avr/fuse.h"
|
||||||
|
#include "avr/interrupt.h"
|
||||||
|
#include "util/delay.h"
|
||||||
|
```
|
||||||
Reference in new issue
Block a user