Skip to content

Concurrent Arbitrary Protocol

The rf-slots API is used to share the radio between Mira and custom protocols.

The API has two important concepts; rf-slot-processes and slots. A rf-slot-process or process for short, is a special kind of function/thread that contain the code accessing the radio functions and it runs in a high level interrupt priority.

A slot, rf_slots_slot_t, is a variable to hold internal state about the slot process.

A process can be scheduled to run multiple times for different slots, but a slot can only be used again when the previous slot has completed. Scheduling the same slot before it is free works fine though, but it is undefined which time or priority that will be used.

The API has two parts, there are functions starting with mira_radio_timeslots_ that schedule a process and a slot to run. There are also functions and macros defined in rf_slots.h to implement the processes.

A scheduled slot has a time length and a priority. When the time is up, the slot will be aborted if it hasn't already completed. When two slots are scheduled to run at overlapping times, the priority of the slots will decide which slot gets to run.

Processes

A process looks like this:

RF_SLOTS_PROCESS(process_name, storage) {
    RF_SLOTS_PROCESS_BEGIN();
    // Radio code
    RF_SLOTS_PROCESS_BEGIN_CLEANUP();
    // Cleanup code, never use the radio or buffers here.

    if (RF_SLOTS_PROCESS_STATUS() != RF_SLOTS_STATUS_SUCCESS) {
        // The radio code failed/wasn't run.
        // ...
    }
    // ...
    RF_SLOTS_PROCESS_END();
}

A slot may not always get its "radio code" to run, but the "cleanup code" will always get to run. Here it can tell a normal process that it completed, that the slot is free for use once more.

One can also define sub-processes with RF_SLOTS_SUBPROCESS. They should also use RF_SLOTS_PROCESS_-BEGIN/BEGIN_CLEANUP/END in their bodies.

In the radio code section there are a lot of macros starting with RF_SLOTS_, they are described in the section for the API for rf-slots processes.

Time

A slot is scheduled to start as soon as possible or at a specific time. It is also scheduled to be run for a certain time. It may be in conflict with other slots and mira will decide which slot gets to run in that case. If, during the life of the slot, it needs more time, the slot may attempt to extend the allocated time. That can be done many times.

There are two clocks in the system, a low frequency, high precision 32kHz clock and a high frequency 1MHz clock. Mira uses both and they are synchronized at the start of the slot. The longer the slot runs, the further the clocks may drift apart. That affects the precision of the timestamp of packets and when packets will be sent. RF_SLOTS_PROCESS_SYNC() can be used to synchronize them again, but it takes a little time so it is a tradeoff against needed precision.

The time resolution is 1 us.

Transmitting a packet

To send a packet, one does something like this:

static rf_slots_radio_config_t radio_conf = {
    // ...
};
static miracore_timer_time_t start_time = rf_slots_get_time_now();

memcpy(rf_slots_get_packet_header_ptr(), s0_s1_header, header_length);
memcpy(rf_slots_get_packet_ptr(), payload, payload_length);
rf_slots_set_packet_length(payload_length);

RF_SLOTS_PROCESS_TX(start_time + 5000, &radio_conf)

The format of the packet need to be defined in radio_conf. One can select BLE frames or 802.15.4 frames.

The header buffer is used for BLE frames to contain the S0 and/or S1 parts of the packet. The s0_bits and s1_bits fields says how many bytes are read from the header_ptr buffer. S1 starts on an even byte after S0. If the length is less than 8, the least significant bites are used.

For 802.15.4 frames, the packet_ptr buffer should contain the whole MPDU, except the FCS/MFR (checksum) and the payload_length should be its whole size (without FCS). Its content will be FCF, Seq, Addressing fields, MAC Payload.

For 802.15.4 frames, clear channel assessment (CCA) can be turned on. If the channel isn't free when the slot tries to send it, it will not send the packet. rf_slots_get_cca() can be used to see if the CCA was successful.

Receiving a packet

Receiving a packet works almost like transmitting a packet.

RF_SLOTS_PROCESS_RX_WIN(expected_time, window_size, &radio_conf);
if (rf_slots_get_packet_length() > 0) {
    // Handle received packet
}

The expected_time is at the exact time the packet is supposed to be received. The radio starts to listen window_size/2 before that time and continues to listen for window_size/2 after, unless a packet is being received, then it continues to receive the whole packet.

The packet's data has been stored in the same format in the buffers returned by rf_slots_get_packet_ptr and rf_slots_get_packet_header_ptr.

rf_slots_get_packet_time returns the time of reception of the packet. rf_slots_get_rssi returns the RSSI value.

For 802.15.4 packets, rf_slots_get_packet_lqi returns the LQI value.

nRF52 specifics

One can not call softdevice functions from processes since they run at IRQ level 0. It is possible to run a callback at a lower priority via the RF_SLOTS_PROCESS_CALL function and thus get access to the softdevice' encryption functions etc.

mkw41z specifics

802.15.4 support is not yet implemented.

Example

There is an example of using rf-slots in examples/miraos/rf_slots_ble_beacon.

Performance

Using another protocol concurrently with Mira will lower the performance of Mira. That leads to a lower PDR/more missing packets. The actual impact depends on the network rate and how often the other protocol's rf-slots usage. By using the low priority slots, the impact on the mira mesh can be lower at the expense of the other protocol's performance.

Scheduling API

The API to schedule rf-slots processes to run.

Functions

mira_radio_timeslot_schedule_at

void mira_radio_timeslot_schedule_at(rf_slots_slot_t *slot, uint8_t prio, miracore_timer_time_t start_time, miracore_timer_interval_t expected_duration, rf_slots_slot_process_t process, void *storage);

Schedule a rf_slots_slot_process_t to start at a given time.

The start_time can be calculated from the result of rf_slots_get_time_now or a previously received packet's timestamp.

Parameters

Name Description
slot pointer to a rf_slots_slot_t that needs to be valid until the handler is done/aborted.
prio what priority the slot has, lower number means higher priority.
start_time when the radio is to be used.
expected_duration how long the radio is scheduled.
process The rf-slots process that is to be run.
storage A pointer passed on to the handler.

mira_radio_timeslot_schedule_immediatly

void mira_radio_timeslot_schedule_immediatly(rf_slots_slot_t *slot, uint8_t prio, miracore_timer_interval_t latency, miracore_timer_interval_t expected_duration, rf_slots_slot_process_t process, void *storage);

Schedule a rf_slots_slot_process_t to start at now + latency.

The slot is starting as soon as possible after a delay of latency

Parameters

Name Description
slot pointer to a rf_slots_slot_t that needs to be valid until the handler is done/aborted.
prio what priority the slot has, lower number means higher priority.
latency how long the start can wait and still be relevant.
expected_duration how long the radio is scheduled.
process The rf-slots process that is to be run.
storage A pointer passed on to the handler.

API for rf-slots processes

The API to implement rf-slots processes with.

Defines

RF_SLOTS_SUBPROCESS

#define RF_SLOTS_SUBPROCESS(_NAME, ...)

Define a RF_SLOTS subprocess.

RF_SLOTS_PROCESS

#define RF_SLOTS_PROCESS(_NAME, _STORAGE)

Define a RF_SLOTS process.

RF_SLOTS_SUBPROCESS_CALL

#define RF_SLOTS_SUBPROCESS_CALL(_NAME, ...)

Call a subprocess from a process.

Example

// Define subprocess:
RF_SLOTS_SUBPROCESS(my_tx_process, uint8_t *packet, uint16_r packet_len);

// ...

RF_SLOTS_SUBPROCESS_CALL(my_tx_process, current_packet, current_packet_len);

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_BEGIN

#define RF_SLOTS_PROCESS_BEGIN()

Starts the rf-slots radio handling inside the rf-slots (sub)process.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_BEGIN_CLEANUP

#define RF_SLOTS_PROCESS_BEGIN_CLEANUP()

Ends the rf-slots radio handling inside the rf-slots (sub)process.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_END

#define RF_SLOTS_PROCESS_END()

Ends the rf-slots cleanup code inside the rf-slots (sub)process.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_EXIT

#define RF_SLOTS_PROCESS_EXIT()

Exit the rf-slots process.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_STATUS

#define RF_SLOTS_PROCESS_STATUS()

Get status from previous call

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_SET_PRIORITY

#define RF_SLOTS_SET_PRIORITY(PRIO)

Change the priority of the running slot.

Note

Should only be used in a rf-slots (sub)process's cleanup section.

RF_SLOTS_PROCESS_EXTEND

#define RF_SLOTS_PROCESS_EXTEND(_TARGET_TIME)

Extend the requested time slot.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_SYNC

#define RF_SLOTS_PROCESS_SYNC()

Sync the rf-slots process and the long term time source.

rf-slots uses a high resolution clock for scheduling slot events, but the clock does not have many bits so it wraps around after a short while.

That clock is synchronized with a low resolution clock at the start of a slot. For long running slots, the clock needs to be resynchronized once in a while before it wraps around. This function does that.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_DELAY

#define RF_SLOTS_PROCESS_DELAY(_TARGET_TIME)

Delay further code until _TARGET_TIME has happened.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_CALL

#define RF_SLOTS_PROCESS_CALL(_TARGET_TIME, _CALLBACK, _STORAGE)

Call a function on a lower irq level.

On NRF with SoftDevice the rf-slots process runs from a SoftDevice callback which prevents calling sd_-functions. Code using sd_* functions must be run at a lower irq priority and that is done with this function.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_TX

#define RF_SLOTS_PROCESS_TX(_TARGET_TIME, _CONFIG)

Transmit the radio packet.

The packet should already have been placed in the buffer returned by rf_slots_get_packet_ptr() and rf_slots_set_packet_length() should have been called.

If CCA is used, get its status via the rf_slots_get_cca();

  • _CONFIG is a rf_slots_radio_config_t struct.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_RX_WIN

#define RF_SLOTS_PROCESS_RX_WIN(_CENTER_TIME, _WINDOW_LENGTH, _CONFIG)

Tell the radio to listen for a packet during the time window.

If a packet is being received when the window ends, the listening window is extended to allow the reception of the whole packet.

  • _CENTER_TIME when the packet is expected to be received.
  • _WINDOW_LENGTH the receive window of the packet.
  • _CONFIG is a rf_slots_radio_config_t struct.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_RX_IMM

#define RF_SLOTS_PROCESS_RX_IMM(_END_TIME, _CONFIG)

Tell the radio to listen for a packet, for at most _END_TIME.

  • _CONFIG is a rf_slots_radio_config_t struct.

Note

Should only be used in a rf-slots (sub)process

RF_SLOTS_PROCESS_RX_POWER

#define RF_SLOTS_PROCESS_RX_POWER(DEST, CONFIG)

Get channel RF power.

For 802.15.4 packets, Energy Detection is used. The returned value is in cDb and compensated for the frontend's gain.

For BLE packets, RSSI is sampled for 20us and the peak value is stored in DEST.

  • DEST A rf_slots_radio_power_t *, where the measurement value is stored.
  • CONFIG is a rf_slots_radio_config_t struct.

Note

should only be used in a rf-slots (sub)process

RF_SLOTS_PRIO_MIRA_LOWEST

#define RF_SLOTS_PRIO_MIRA_LOWEST

The lowest priority for MIRA RF-Slots.

RF_SLOTS_PRIO_MIRA_HIGHEST

#define RF_SLOTS_PRIO_MIRA_HIGHEST

The highest priority for MIRA RF-Slots.

RF_SLOTS_PRIO_BLE_BEACONS

#define RF_SLOTS_PRIO_BLE_BEACONS

The priority for BLE Beacon RF-Slots.

RF_SLOTS_RADIO_POWER_MAX

#define RF_SLOTS_RADIO_POWER_MAX

Maximum configurable radio power

This value is not the maximum output power, but the maximum value to put in the output power field

RF_SLOTS_RADIO_POWER_MIN

#define RF_SLOTS_RADIO_POWER_MIN

Maximum configurable radio power

This value is not the minimum output power, but the minimum value to put in the output power field

Types

rf_slots_radio_power_t

typedef int16_t rf_slots_radio_power_t;

Power in unit of cBm, which is 1/10 of dBm

rf_slots_radio_antenna_t

typedef uint8_t rf_slots_radio_antenna_t;

Antenna index

rf_slots_slot_t

typedef struct rf_slots_slot rf_slots_slot_t;

Storage for a scheduled slot

Enums

rf_slots_status_t

Status of previous operation within an rf slot. Return value of RF_SLOTS_PROCESS_STATUS()

Name Description
RF_SLOTS_STATUS_SUCCESS
RF_SLOTS_STATUS_FAIL
RF_SLOTS_STATUS_ABORT

rf_slots_radio_mode_t

The radio mode used to send/receive packets.

Used as mode in rf_slots_radio_config_t

Name Description
RF_SLOTS_RADIO_MODE_BLE_1MBPS

rf_slots_radio_cca_mode_t

Type of CCA/LBT mode to use.

Name Description
RF_SLOTS_RADIO_CCA_MODE_NONE
RF_SLOTS_RADIO_CCA_MODE_ED Energy Detection
RF_SLOTS_RADIO_CCA_MODE_CS Carrier Sense
RF_SLOTS_RADIO_CCA_MODE_ED_AND_CS
RF_SLOTS_RADIO_CCA_MODE_ED_OR_CS

Structs

rf_slots_radio_config_t

Radio and packet format configuration

Note: To be compatible with future versions of Mira and allow more parameters to be added, make sure all non-used padding bytes in the struct is set to zero.

Recommended to call memset() before initializing the parameters, or changing mode:

memset(&config, 0, sizeof(rf_slots_radio_config_t));
Name Type Description
mode rf_slots_radio_mode_t
access_address uint32_t
frequency uint8_t
white_iv uint8_t
preamble_bits uint8_t
length_bits uint8_t
s0_bits uint8_t
s1_bits uint8_t
ble_1mbps struct rf_slots_radio_config_t::@4::@7
fmt union rf_slots_radio_config_t::@4
radio_power rf_slots_radio_power_t
antenna rf_slots_radio_antenna_t
low_power_mode bool
rf struct rf_slots_radio_config_t::@5
can_wakeup bool If RX should be possible to interrupt from application
ops struct rf_slots_radio_config_t::@6

rf_slots_slot

Storage for a scheduled slot

No parameters in this struct is intended to be directly accessed by the user.

Name Type Description
parameters_hidden int No parameters in this struct should be updated by user directly

Functions

rf_slots_radio_config_is_valid

bool rf_slots_radio_config_is_valid(const rf_slots_radio_config_t *config);

Check if a radio packet configuration is valid.

Checks if a given radio packet configuration is valid and supported by the current radio hardware.

Note

This function is stateless, and can be executed both in IRQ and thread

rf_slots_get_packet_ptr

uint8_t* rf_slots_get_packet_ptr(void);

Get pointer to the packet buffer.

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_packet_header_ptr

uint8_t* rf_slots_get_packet_header_ptr(void);

Get pointer to storage for packet header.

For BLE 1Mbps packets, it's the s0 and s1 fields Both s0 and s1 fields are stored bytes aligned, and padded with 0 so each field fill a whole byte. Content of the field is stored in LSB for each field

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_packet_length

uint16_t rf_slots_get_packet_length(void);

Get length of last received packet.

Note

Should only be used in a rf-slots (sub)process

rf_slots_set_packet_length

void rf_slots_set_packet_length(uint16_t length);

Set length of the packet buffer to send.

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_packet_rssi

rf_slots_radio_power_t rf_slots_get_packet_rssi(void);

Get RSSI of last received packet.

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_packet_lqi

int rf_slots_get_packet_lqi(void);

Get 802.15.4 LQI value of last received packet.

This is only available for 802.15.4 packets.

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_cca

bool rf_slots_get_cca(void);

Get CCA of last transmitted packet.

Note

Should only be used in a rf-slots (sub)process after RF_SLOTS_PROCESS_TX

rf_slots_get_actual_power

rf_slots_radio_power_t rf_slots_get_actual_power(const rf_slots_radio_config_t *config);

Get actual TX power given a radio configuration

This method is safe to run independent of rf-slot state.

If called from interrupt while rf_slots_set_frontend_config is running, the behaviour is undefined

rf_slots_get_max_power

rf_slots_radio_power_t rf_slots_get_max_power(const rf_slots_radio_config_t *config);

Get the maximum TX power given a radio configuration

This method is safe to run independent of rf-slot state.

If called from interrupt while rf_slots_set_frontend_config is running, the behaviour is undefined

rf_slots_get_packet_time

miracore_timer_time_t rf_slots_get_packet_time(void);

Get the time the last packet was received.

Note

Should only be used in a rf-slots (sub)process

rf_slots_get_time_now

miracore_timer_time_t rf_slots_get_time_now(void);

Get the current time in 1us precision.

rf_slots_timer_diff

miracore_timer_interval_t rf_slots_timer_diff(miracore_timer_time_t a, miracore_timer_time_t b);

Get the difference between two miracore_timer_time_t.