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.