Skip to content

RADIO FREQUENCY COMMUNICATION

RF communication refers to the technology of transmitting information through radio waves. It is widely used in radio, television, mobile phones, satellite communications and other fields. The basic principle of RF communication is to modulate information onto RF signals and transmit and receive them through antennas. This project uses nRF24L01 wireless module for RF communication.

The following is the relevant code implementation for the nRF24L01 wireless module:

rf.hpp

#pragma once
#include <Arduino.h>
#include <RF24.h>

#define RF_CHANNEL 108
#define RF_PIPE_BASE 0xF0F0F0F000LL

struct RFMessage
{
    uint8_t from_id;
    uint8_t to_id;
    char payload[24];       
    uint64_t timestamp_ms; 
};


bool rf_init();
bool rf_send(uint8_t to_id, const RFMessage &msg, bool require_ack = false);
bool rf_receive(RFMessage &msg, unsigned long timeout_ms);
bool rf_send_then_receive(const RFMessage &msg, uint8_t to_id, unsigned long timeout_ms, uint8_t retries);

void rf_stop_listening();
void rf_start_listening();
void rf_set_rx_address(uint8_t id);
String rf_format_address(uint16_t node_id);

rf.cpp

#include "rf.hpp"
#include "config.hpp"
#include <SPI.h>

RF24 radio(9, 8);

String rf_format_address(uint16_t node_id)
{
    char buf[6];
    snprintf(buf, sizeof(buf), "N%03d", node_id);
    return String(buf);
}

void rf_set_rx_address(uint8_t id)
{
    uint64_t address = RF_PIPE_BASE | id;
    radio.openReadingPipe(1, address);
    radio.setAutoAck(true);
}

bool rf_init()
{
    if (!radio.begin())
    {
        Serial.println("[INIT] <RF> Initialization failed.");
        return false;
    }

    radio.setPALevel(RF24_PA_HIGH);
    radio.setDataRate(RF24_250KBPS);
    radio.setChannel(RF_CHANNEL);
    radio.setRetries(5, 15);
    radio.enableDynamicPayloads();
    radio.setCRCLength(RF24_CRC_16);

    rf_set_rx_address(NODE_ID);
    radio.startListening();

    Serial.print("[INIT] <RF> Initialized. Listening on ");
    Serial.println(rf_format_address(NODE_ID));
    return true;
}

void rf_stop_listening() { radio.stopListening(); }
void rf_start_listening() { radio.startListening(); }

bool rf_send(uint8_t to_id, const RFMessage &msg, bool require_ack)
{
    uint64_t tx_address = RF_PIPE_BASE | to_id;
    radio.openWritingPipe(tx_address);
    return radio.write(&msg, sizeof(RFMessage), require_ack);
}

bool rf_receive(RFMessage &msg, unsigned long timeout_ms)
{
    unsigned long start_time = millis();
    while (millis() - start_time < timeout_ms)
    {
        if (radio.available())
        {
            radio.read(&msg, sizeof(RFMessage));
            return true;
        }
    }
    return false;
}

bool rf_send_then_receive(const RFMessage &msg, uint8_t to_id, unsigned long timeout_ms, uint8_t retries)
{
    for (uint8_t attempt = 0; attempt < retries; ++attempt)
    {
        rf_stop_listening();
        bool sent = rf_send(to_id, msg);
        rf_start_listening();

        if (!sent)
        {
            Serial.print("[RF] Send failed (attempt ");
            Serial.print(attempt + 1);
            Serial.println(")");
            continue;
        }

        RFMessage response;
        if (rf_receive(response, timeout_ms) && response.to_id == NODE_ID)
            return true;

        Serial.print("[RF] No response received (attempt ");
        Serial.print(attempt + 1);
        Serial.println(")");
    }
    return false;
}

Key Functions

Function Name Description
rf_init() Initializes the nRF24L01 module, sets the channel, data rate, and other parameters, and begins listening.
rf_send(uint8_t to_id, const RFMessage &msg, bool require_ack = false) Sends an RFMessage to the specified node ID, with optional acknowledgment requirement.
rf_receive(RFMessage &msg, unsigned long timeout_ms) Receives an RFMessage until a timeout occurs or a message is received.
rf_send_then_receive(const RFMessage &msg, uint8_t to_id, unsigned long timeout_ms, uint8_t retries) Sends a message and waits for a response, with a configurable retry mechanism.
rf_stop_listening() Stops listening and switches to transmission mode.
rf_start_listening() Starts listening and switches to receiving mode.
rf_set_rx_address(uint8_t id) Sets the receiving address to listen to messages from a specific node ID.
rf_format_address(uint16_t node_id) Formats the node ID into a human-readable string for logging/debugging.

RFMessage Structure

The RFMessage structure defines the format of messages exchanged over RF communication. It includes the following fields:

  • from_id: The sender's node ID.
  • to_id: The receiver's node ID.
  • payload: The actual data payload of the message (max 24 bytes).
  • timestamp_ms: The timestamp (in milliseconds) when the message is sent.

rf_init() Function

The rf_init() function initializes the nRF24L01 module. It attempts to start the RF module and returns false if initialization fails. If successful, it sets transmission power level, data rate, channel, retry count, and enables features such as dynamic payloads and CRC checking. It then calls rf_set_rx_address(NODE_ID) to set the current node’s receiving address and starts listening. If successful, the function prints the current node’s address and returns true.


rf_set_rx_address() Function

The rf_set_rx_address(uint8_t id) function sets the receiving address for the nRF24L01 module. It combines the input node ID with a predefined base address (RF_PIPE_BASE) to form a full address. It then calls radio.openReadingPipe(1, address) to open a reading pipe and enables auto-acknowledgment.


rf_format_address() Function

The rf_format_address(uint16_t node_id) function formats a node ID into a readable string using snprintf, producing a string in the format "Nxxx" (e.g., N001, N100). Useful for printing and debugging.


rf_send() Function

The rf_send(uint8_t to_id, const RFMessage &msg, bool require_ack) function sends an RFMessage to the specified to_id. It generates the target write pipe address and transmits the message to that address. It returns true on success, false on failure. If require_ack is true, the function waits for an acknowledgment from the receiver.


rf_receive() Function

The rf_receive(RFMessage &msg, unsigned long timeout_ms) function attempts to receive an RFMessage within the given timeout. It continuously checks if data is available and reads the message into msg upon success. Returns true if a message is received before timeout, otherwise returns false.


rf_send_then_receive() Function

The rf_send_then_receive(const RFMessage &msg, uint8_t to_id, unsigned long timeout_ms, uint8_t retries) function sends a message and waits for a response. It attempts to send the message up to retries times. After each send, it switches to listening mode and waits for a valid response. If a response is received and its to_id matches the current node’s ID, it returns true. Returns false if all attempts fail.


rf_stop_listening() and rf_start_listening() Functions

  • rf_stop_listening() stops the nRF24L01 from listening mode and switches to transmit mode using radio.stopListening().

  • rf_start_listening() starts listening mode using radio.startListening() to receive incoming messages.