#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_rf_config.h" #if defined(__AVR_ATmega328PB__) #ifndef SPCR #define SPCR SPCR0 #endif #ifndef SPDR #define SPDR SPDR0 #endif #ifndef SPSR #define SPSR SPSR0 #endif #ifndef SPE #define SPE SPE0 #endif #ifndef MSTR #define MSTR MSTR0 #endif #ifndef SPR0 #define SPR0 SPR00 #endif #ifndef SPIF #define SPIF SPIF0 #endif #ifndef 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 uint8_t send_request = false; static volatile uint8_t nrf_event = false; zh_rf_sensor_transmitted_data_t transmitted_data = {0}; int main(void) { transmitted_data.type = ZH_RF_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_RF_MAX_PAYLOAD_SIZE]; if (zh_rf_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_RF_MAX_PAYLOAD_SIZE]; uint8_t size = zh_rf_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--) { *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_rf_sensor_pairing_data_t pairing_request; pairing_request.type = (uint8_t)transmitted_data.type; pairing_request.magic = ZH_RF_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_rf_sensor_pairing_state_t state = ZH_RF_PAIR_SEND; uint8_t attempts = 0; for (;;) { switch (state) { case ZH_RF_PAIR_SEND: nrf_event = false; zh_nrf24_send_no_wait((uint8_t *)&pairing_request, ZH_RF_PAIRING_PACKET_SIZE); state = ZH_RF_PAIR_TX_WAIT; break; case ZH_RF_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_RF_PAIR_RX_WAIT; } else { state = ZH_RF_PAIR_BACKOFF; } } else { state = ZH_RF_PAIR_BACKOFF; } break; case ZH_RF_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_RF_MAX_PAYLOAD_SIZE) { zh_nrf24_send_command(NRF_CMD_FLUSH_RX); zh_nrf24_write_register(NRF_REG_STATUS, NRF_STATUS_RX_DR); state = ZH_RF_PAIR_BACKOFF; break; } if (width != ZH_RF_PAIRING_PACKET_SIZE) { uint8_t dummy[ZH_RF_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_RF_PAIR_BACKOFF; break; } zh_rf_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_RF_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_RF_PAIR_BACKOFF; } else { state = ZH_RF_PAIR_BACKOFF; } break; case ZH_RF_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_RF_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; }