SPARK Wireless Core
When integrating the SPARK Wireless Core into a project using custom hardware, users need to consider two main actions:
Implement the backend: This involves creating the necessary backend interfaces for the SPARK Wireless Core to interact effectively with the custom hardware.
Provide a context switch mechanism within their application: This mechanism allows for efficient switching between different operational contexts within the application and the Wireless Core, facilitating resource management.
Implement the backend
Users need to create their own backend to successfully port the Wireless Core onto custom hardware. This requires implementing the function prototypes found in core/wireless/facade/swc_hal_facade.h. In addition to this guide, the API’s Doxygen documentation provides guidance on how to implement the interface correctly.
Application-level SPARK Radio setup and access are performed through the Wireless Core public API, swc_api.h. The SPARK Radio PHY is used internally by the Wireless Core and is not exposed as a public application API.
Note
The Wireless Core is designed to interface up to two radio modules with a single MCU, duplicating peripherals requirements. When using two radios, users must ensure that functions for radio 1 and radio 2 are fully implemented. When using one radio, only radio 1 functions need to be implemented. Weak functions have been implemented to the backend to ensure compatibility with the facade in single-radio operation.
The facade functions are organized into groups described below.
Context Switching and Interrupt Management
Functions related to managing context switches and setting interrupt callbacks, crucial for multitasking and efficient response to system events.
/** @brief Manually triggers the radio #1's IRQ pin interrupt.
*
* This function is specifically designed to manually activate the IRQ pin interrupt for radio #1,
* prompting the system to handle an interrupt event as if the radio hardware had issued it. The
* subsequent interrupt handling routine invokes an internal SWC library function tailored to
* process this event.
*
* @note Unlike generic context switch mechanisms, this function does not require the user to set an
* application-level callback for the interrupt. However, users must implement a separate mechanism
* to configure a callback for the radio #1 IRQ to ensure proper handling of the triggered event
* within the SWC library's context.
*/
void swc_hal_radio_1_context_switch(void);
/** @brief Sets the callback function for radio #1's IRQ interrupt.
*
* This function registers a internal SWC library function callback to be invoked when the IRQ pin
* interrupt for radio #1 is triggered.
*
* @param[in] callback A pointer to the callback function that will be executed upon
* an interrupt from radio #1's IRQ pin.
*/
void swc_hal_set_radio_1_irq_callback(void (*callback)(void));
/** @brief Sets the callback function for the DMA or peripheral transfer done (RX or TX) interrupt for radio #1.
*
* This function allows for the registration of a internal SWC library function callback
* to be invoked when the non-blocking memory transfer over DMA for radio #1 completes.
*
* @param[in] callback A pointer to the callback function that will be executed upon the
* completion of a DMA or peripheral transfer done operation for radio #1.
*/
void swc_hal_set_radio_1_non_blocking_transfer_callback(void (*callback)(void));
/** @brief Disables the IRQ external interrupt for radio #1.
*
* This function deactivates the external interrupt request (IRQ) for radio #1, preventing
* the interrupt handler from being invoked in response to radio #1's IRQ events.
*/
void swc_hal_radio_1_disable_irq_it(void);
/** @brief Enables the IRQ external interrupt for radio #1.
*
* This function enables the external interrupt request (IRQ) for radio #1, allowing
* the system to respond to IRQ signals from the radio.
*/
void swc_hal_radio_1_enable_irq_it(void);
/** @brief Disables the non-blocking transfer interrupt for radio #1.
*
* This function deactivates the interrupt request (IRQ) associated with the DMA
* operation for radio #1, preventing DMA or peripheral related interrupt handling.
*/
void swc_hal_radio_1_disable_non_blocking_transfer_irq_it(void);
/** @brief Enables the non-blocking transfer interrupt for radio #1.
*
* This function activates the interrupt request (IRQ) for the DMA operation for radio #1,
* allowing DMA or peripheral related interrupt handling.
*/
void swc_hal_radio_1_enable_non_blocking_transfer_irq_it(void);
SPI/QSPI Communication
Radio transfer functions for data exchange between the MCU and ASIC radios, underpinning wireless communication functionality. It is important to know that when using the SPI version of the wireless core, the full-duplex transfer functions need to be implemented. On the other hand if the QSPI version is used, the half-duplex transfer functions need to be implemented instead. Only the functions required for the selected wireless core version need to be implemented.
Warning
When using two radios, only SPI mode is supported. The QSPI mode of the Wireless Core is only compatible with single radio operation.
/** @brief Set the on-board controller SPI/QSPI chip-select pin of the radio #1.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_radio_1_end_transfer(void);
/** @brief Reset the on-board controller SPI/QSPI chip-select pin of the radio #1.
*/
void swc_hal_radio_1_begin_transfer(void);
/** @brief Read the status of the radio's SPI/QSPI of the radio #1.
*
* @retval true Transfer is busy.
* @retval false Transfer is not busy.
*/
bool swc_hal_radio_1_is_transfer_busy(void);
/** @brief Read and Write data full duplex on the radio #1 in blocking mode.
*
* @note This function is only required when using the SPI peripheral.
*
* @param[in] tx_data Data buffer to write.
* @param[out] rx_data Data received.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_full_duplex_blocking(uint8_t *tx_data, uint8_t *rx_data, uint16_t size);
/** @brief Read and Write data full duplex on the radio #1 in non-blocking mode.
*
* @note This function is only required when using the SPI peripheral.
*
* @param[in] tx_data Data buffer to write.
* @param[out] rx_data Data received.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_full_duplex_non_blocking(uint8_t *tx_data, uint8_t *rx_data, uint16_t size);
/** @brief Read data half duplex on the radio #1 in blocking mode.
*
* @note This function is only required when using the QSPI peripheral.
*
* @param[in] command Command byte of the transfer.
* @param[out] rx_data Data received.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_half_duplex_rx_blocking(uint8_t command, uint8_t *rx_data, uint16_t size);
/** @brief Write data half duplex on the radio #1 in blocking mode.
*
* @note This function is only required when using the QSPI peripheral.
*
* @param[in] command Command byte of the transfer.
* @param[in] tx_data Data buffer to write.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_half_duplex_tx_blocking(uint8_t command, uint8_t *tx_data, uint16_t size);
/** @brief Read data half duplex on the radio #1 in non-blocking mode.
*
* @note This function is only required when using the QSPI peripheral.
*
* @param[in] command Command byte of the transfer.
* @param[out] rx_data Data received.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_half_duplex_rx_non_blocking(uint8_t command, uint8_t *rx_data, uint16_t size);
/** @brief Write data half duplex on the radio #1 in non-blocking mode.
*
* @note This function is only required when using the QSPI peripheral.
*
* @param[in] command Command byte of the transfer.
* @param[in] tx_data Data buffer to write.
* @param[in] size Size of the data.
*/
void swc_hal_radio_1_transfer_half_duplex_tx_non_blocking(uint8_t command, uint8_t *tx_data, uint16_t size);
/** @brief Set the way to communicate to radio #1 to be SPI.
*
* @note This function is only required when using the QSPI peripheral.
*/
void swc_hal_radio_1_set_access_mode_spi(void);
/** @brief Set the way to communicate to radio #1 to be QSPI.
*
* @note This function is only required when using the QSPI peripheral.
*
*/
void swc_hal_radio_1_set_access_mode_qspi(void);
GPIO Controls for Radios
Functions for direct manipulation of GPIO pins connected to the radio ASIC, essential for radio module control and radio IRQ pin state monitoring.
/** @brief Reads the status of radio #1's IRQ pin.
*
* @retval True If the pin is high.
* @retval False If the pin is low.
*/
bool swc_hal_radio_1_read_irq_pin(void);
/** @brief Sets the reset pin of radio #1.
*/
void swc_hal_radio_1_set_reset_pin(void);
/** @brief Resets the reset pin of radio #1.
*/
void swc_hal_radio_1_reset_reset_pin(void);
Timer and Delay Management
Functions for managing a hardware timer.
/** @brief Get the free running timer tick count.
*
* @note This function is ack as a watchdog timer for the Stop and Wait feature, indicating to the Wireless Core when
* to abort re-transmission efforts.
*
* @note Users not using the Stop and Wait feature should implement a pseudo-version of this function that returns
* UINT64_MAX, ensuring backend compatibility with the facade.
*
* @return Tick count.
*/
uint64_t swc_hal_get_tick_free_running_timer(void);
/** @brief Returns the configured tick frequency of the free running timer.
*
* @note Enables the Wireless Core to calculate accurate tick counts for delays and other timing-related operations by
* providing the free running timer's frequency in hertz.
*
* @note Users not using the Stop and Wait feature should implement a pseudo-version of this function that returns 0,
* ensuring backend compatibility with the facade.
*
* @return The free running timer's configured frequency in Hz.
*/
uint32_t swc_hal_get_free_running_timer_frequency_hz(void);
Dual Radio Timer Management
Functions for managing a hardware timer specifically designed for synchronizing dual radio operations. This includes initializing the timer, setting timer periods, handling callbacks, and managing timer interrupts. These functions are only required when using two radio modules.
/** @brief Initializes the timer for dual-radio support.
*
* Configures the multi-radio timer with the following options:
* - Counter Up mode
* - Auto-reload preload disabled
* - Tick frequency set within this range: 18MHz and 22MHz
* - Interrupt generation at the end of each period
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_multi_radio_timer_init(void);
/** @brief Set multi-radio callback.
*
* This function registers a internal SWC library function callback to be invoked when the IRQ pin
* interrupt for multi radio timer is triggered.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*
* @param[in] callback A pointer to the callback function that will be executed.
*/
void swc_hal_set_multi_radio_timer_callback(void (*callback)(void));
/** @brief Starts the multi-radio timer.
*
* Initiates the countdown for the multi-radio timer.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_timer_multi_radio_timer_start(void);
/** @brief Starts the multi-radio timer.
*
* Initiates the countdown for the multi-radio timer.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_timer_multi_radio_timer_start(void);
/** @brief Starts the multi-radio timer.
*
* Halt the countdown for the multi-radio timer.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_timer_multi_radio_timer_stop(void);
/** @brief Sets the period of the multi-radio timer.
*
* Defines the duration of the timer cycle for dual-radio operations, specified in ticks.
* Adjusting the period allows for the synchronization of actions across multiple radio
* units within a defined timeline.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*
*
* @param[in] period The timer period, in ticks.
*/
void swc_hal_timer_multi_radio_timer_set_period(uint16_t period);
/** @brief Set the period of the dual radio timer to the maximum value.
*
* @note This function is part of a dual-radio support system, allowing a single MCU to manage
* two separate radio ASICs. Users implementing support for a single radio ASIC do not have to provide
* an implementation for this function in their backend.
*/
void swc_hal_timer_multi_radio_timer_set_max_period(void);
/** @brief Returns the configured tick frequency of the multi radio timer.
*
* Enables the Wireless Core to calculate accurate tick counts for delays and
* other timing-related operations by providing the multi radio timer's frequency in hertz.
*
* @return The multi radio timer's configured frequency in Hz.
*/
uint32_t swc_hal_get_timer_multi_frequency_hz(void);
Provide a context switch mechanism
Users must provide a mechanism to facilitate context switching via their application layer, as this is not included in the facade/backend architecture due to its hardware-agnostic implementation.
Note
This mechanism must be specified through the swc_init() function via its callback argument.
/** @brief Wireless Core initialization.
*
* This is the first API call that needs to be made when initializing and
* configuring the Wireless Core.
*
* @param[in] cfg Wireless Core network configuration.
* @param[in] node_cfg Wireless Core local node configuration.
* @param[in] callback A function pointer to the logic initiating context switches.
* @param[out] err Wireless Core error code.
*/
void swc_init(swc_cfg_t cfg, swc_node_cfg_t node_cfg, void (*callback)(void), swc_error_t *const err);
The context switch initiated by the Wireless Core allows for the handling of callback functions at a lower priority. The context switch mechanism should use a lower priority than the transceiver IRQ and the SPI’s DMA IRQ, ensuring that the system’s real-time performance operations are not compromised. If the user’s application has any time critical IRQs to handle, they should be set at a higher priority than the context switch mechanism. For more information regarding the IRQ priorities used by SPARK, see IRQ Priority Scheme.
The following functions would be raised in the application within that context switch scope.
swc_connection_set_tx_success_callback
swc_connection_set_tx_fail_callback
swc_connection_set_tx_dropped_callback
swc_connection_set_rx_success_callback
swc_connection_set_event_callback
Refer to the Wireless Core API - Callbacks section for more information about these callback functions.
In a bare-metal environment
Use the
SetPendSV()function to trigger a Pendable Service Interrupt (PendSV).Configure PendSV_IRQ_handler to execute
swc_connection_callbacks_processing_handler()at a safe, lower priority.
Hello_world example
Implement the context_switch trigger in the dedicated backend.
void hw_facade_context_switch_trigger(void)
{
SetPendSV();
}
Implement the PendSV_IRQ_Handler callback register function in the dedicated backend.
void hw_facade_set_context_switch_handler(void (*callback)(void))
{
set_pendsv_IRQ_handler_callback(callback);
}
Register the swc_connection_callbacks_processing_handler to be invoked within the PendSV_IRQ_Handler.
hw_facade_set_context_switch_handler(swc_connection_callbacks_processing_handler);
Register the context_switch trigger via the swc_init() API function.
swc_init(core_cfg, node_cfg, hw_facade_context_switch_trigger, err);
In an RTOS environment
Utilize a semaphore or signal a condition that prompts the RTOS scheduler to lower the task’s priority and execute
swc_connection_callbacks_processing_handler().
Hello_world_RTOS example
Implement the context_switch trigger in the application scope.
/** @brief Callback context switch implementation for the SWC interface.
*/
static void callback_context_trigger(void)
{
osSemaphoreRelease(swc_process_sem);
}
Register the context_switch trigger via the
swc_init()API function.
swc_init(core_cfg, node_cfg, callback_context_trigger, err);
Acquire the semaphore prior to executing the
swc_connection_callbacks_processing_handler()function.
while (1) {
iface_button_handling(reset_stats, NULL);
osSemaphoreAcquire(swc_process_sem, osWaitForever);
swc_connection_callbacks_processing_handler();
}