Hi,
I am working on a project with two nRF52840 devices (nRF Connect SDK 3.2.4 / Zephyr RTOS) that are already connected via
BLE. On top of the BLE link I need a low-latency out-of-band signalling channel using the MPSL Timeslot API.
Setup:
- Sensor (peripheral): when an application event fires, it requests a single EARLIEST timeslot (NORMAL priority, ~22
ms long) and transmits 10 short beacons back-to-back inside it using a TIMER0 loop, without chaining separate NORMAL
slots.
- Dongle (central, USB host): requests an EARLIEST slot at HIGH priority and keeps extending it indefinitely via
ACTION_EXTEND until the beacon is received. It also enables RADIO_INTENSET_CRCOK_Msk to get SIGNAL_RADIO immediately
on reception and toggles a GPIO directly inside the SWI1 ISR, before any k_work is involved. Before starting, the
dongle widens the BLE connection interval to 1000 ms to give MPSL more room.
I am measuring the time between a GPIO toggle on the sensor (inside the timer callback that triggers the TX) and a
GPIO toggle on the dongle (inside the SWI1 ISR on beacon reception) with an oscilloscope. I consistently get ~8 ms.
I will attach the full source of both timeslot_radio.c files below.
Is there anything wrong or suboptimal in the approach that is causing this 8 ms? Is it possible to get below that,
given that both devices must maintain the BLE connection?
Thanks.
dongle:
#include "timeslot_radio.h"
#include "system_logging.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/bluetooth/conn.h>
#include <mpsl_timeslot.h>
#include <nrfx.h>
#include <string.h>
LOG_MODULE_REGISTER( timeslot_radio );
/*******************************************************************************
* Costanti
******************************************************************************/
/* Slot da 100ms (massimo MPSL per singola richiesta).
* L'approccio con ESTENSIONE prolunga il timeslot corrente invece di richiedere
* slot NORMAL separati, evitando il SIGNAL_BLOCKED tipico dei slot periodici.
* Se l'estensione fallisce (evento BLE urgente) si richiede subito un nuovo EARLIEST. */
#define RX_SLOT_LENGTH_US 100000U /* 100ms – MPSL_TIMESLOT_LENGTH_MAX_US */
#define RX_SLOT_TIMER_MARGIN_US 2000U /* TIMER0 scatta 2ms prima della fine slot */
#define RX_SLOT_EXTEND_US 100000U /* incremento di ogni estensione (100ms) */
#define RX_TIMEOUT_MS ( 30U * 1000U ) /* 30 secondi */
/* Parametri CI durante timeslot: 800 × 1.25ms = 1000ms, supervision 60s.
* Il dongle (central) invia LL_CONNECTION_UPDATE_IND; il sensore deve accettare. */
#define RX_CI_TIMESLOT_MIN 800U
#define RX_CI_TIMESLOT_MAX 800U
#define RX_CI_TIMESLOT_LATENCY 0U
#define RX_CI_TIMESLOT_TIMEOUT 3200U /* 3200 × 10ms = 32s (massimo BLE spec) */
/* Parametri CI idle (ripristino post-timeslot): 40 × 1.25ms = 50ms */
#define RX_CI_IDLE_MIN 40U
#define RX_CI_IDLE_MAX 40U
#define RX_CI_IDLE_LATENCY 0U
#define RX_CI_IDLE_TIMEOUT 400U /* 400 × 10ms = 4s */
#define RADIO_FREQ_OFFSET 80U /* 2480 MHz */
/* AA custom – NON usare 0x8E89BED6 (BLE advertising AA): causerebbe la
* ricezione di ogni beacon BLE pubblicitario su ch.39, disabilitando la
* radio prima del nostro beacon. */
#define RADIO_ACCESS_ADDRESS 0xDEADBEEFUL
#define BEACON_MAGIC 0xBEAC0001UL
#define MPSL_THREAD_STACKSIZE 1024U
#define MPSL_MSGQ_SIZE 4U
#define SLOT_RING_BUF_SIZE 16U
/*******************************************************************************
* Tipo beacon ricevuto
******************************************************************************/
/* Payload fisso 8 byte: niente campo LENGTH in testa (STATLEN=8 in PCNF1). */
typedef struct __attribute__(( packed ))
{
uint32_t magic;
uint8_t serial[ 4 ];
} timeslot_radio_beacon_t;
/*******************************************************************************
* Stato macchina RX
******************************************************************************/
typedef enum
{
RX_STATE_IDLE = 0,
RX_STATE_REQUESTING,
RX_STATE_POLLING,
RX_STATE_BEACON_RECEIVED,
} rx_state_t;
typedef enum
{
MPSL_MSG_OPEN_SESSION = 0,
MPSL_MSG_MAKE_REQUEST,
MPSL_MSG_CLOSE_SESSION,
} mpsl_msg_t;
typedef enum
{
SLOT_EVT_SESSION_IDLE = 0,
SLOT_EVT_BEACON_RECEIVED,
SLOT_EVT_SLOT_ENTERED,
SLOT_EVT_TIMER0_FIRED,
SLOT_EVT_TIMER0_CRCOK_MAGIC_OK,
SLOT_EVT_TIMER0_CRCOK_MAGIC_FAIL,
SLOT_EVT_EXTENDED, /* EXTEND_SUCCEEDED: slot prolungato */
SLOT_EVT_EXTEND_FAILED, /* EXTEND_FAILED: nuovo EARLIEST richiesto */
SLOT_EVT_BLOCKED, /* BLOCKED: richiesta bloccata, retry EARLIEST */
SLOT_EVT_CANCELLED, /* CANCELLED: slot revocato, retry EARLIEST */
} slot_evt_t;
/*******************************************************************************
* Parametri connessione BLE
******************************************************************************/
static const struct bt_le_conn_param *timeslotConnParam =
BT_LE_CONN_PARAM( RX_CI_TIMESLOT_MIN, RX_CI_TIMESLOT_MAX,
RX_CI_TIMESLOT_LATENCY, RX_CI_TIMESLOT_TIMEOUT );
static const struct bt_le_conn_param *idleConnParam =
BT_LE_CONN_PARAM( RX_CI_IDLE_MIN, RX_CI_IDLE_MAX,
RX_CI_IDLE_LATENCY, RX_CI_IDLE_TIMEOUT );
/*******************************************************************************
* Variabili globali
******************************************************************************/
static volatile rx_state_t rxState = RX_STATE_IDLE;
static uint8_t targetDeviceId;
static struct bt_conn *targetConn;
static timeslot_radio_beacon_cb_t beaconCallback;
/* Fine slot corrente in µs (da start slot): aggiornato ad ogni estensione */
static volatile uint32_t currentSlotEndUs;
/* Contatori debug – aggiornati dalla callback MPSL (ISR), letti da SWI1/work */
static volatile uint32_t dbgSlotCount;
static volatile uint32_t dbgTimer0Count; /* SIGNAL_TIMER0 ricevuti */
static volatile uint32_t dbgExtendCount; /* estensioni concesse da MPSL */
static volatile uint32_t dbgCrcOkCount;
static volatile uint32_t dbgMagicFailCount;
static volatile uint32_t dbgLastMagic;
static volatile uint32_t dbgAddressCount; /* AA match rilevati (EVENTS_ADDRESS) */
static volatile uint32_t dbgCrcErrorCount; /* pacchetti con CRC errato */
static volatile uint32_t dbgStateAtStart; /* NRF_RADIO->STATE all'ingresso SIGNAL_START */
static volatile uint32_t dbgLastState; /* NRF_RADIO->STATE all'ingresso SIGNAL_TIMER0 */
static timeslot_radio_beacon_t rxBuffer;
static mpsl_timeslot_session_id_t sessionId;
/* Strutture MPSL */
static mpsl_timeslot_request_t firstRequest;
static mpsl_timeslot_request_t nextRequest;
static mpsl_timeslot_signal_return_param_t returnParam;
/* Ring buffer ISR → SWI */
static uint8_t slotRingBufData[ SLOT_RING_BUF_SIZE ];
static struct ring_buf slotEventRingBuf;
/* Timer timeout RX */
static struct k_timer rxTimeoutTimer;
/* Timer ritardo prima della prima request MPSL: aspetta che il CI update
* (50ms → 1000ms) sia applicato prima che MPSL cerchi lo slot da 100ms. */
static struct k_timer mpslRequestDelayTimer;
/* Thread MPSL non-preemptible */
static struct k_thread mpslThread;
static K_THREAD_STACK_DEFINE( mpslStackArea, MPSL_THREAD_STACKSIZE );
static k_tid_t mpslThreadId;
/* Message queue */
K_MSGQ_DEFINE( mpslMsgq, sizeof( mpsl_msg_t ), MPSL_MSGQ_SIZE, 4 );
/* Work handlers */
static struct k_work sessionIdleWork;
static struct k_work beaconReceivedWork;
static struct k_work restoreConnParamWork;
static const struct gpio_dt_spec signalSwitchButton =
GPIO_DT_SPEC_GET( DT_NODELABEL( button0 ), gpios );
/*******************************************************************************
* Forward declarations
******************************************************************************/
static mpsl_timeslot_signal_return_param_t *Timeslot_Callback(
mpsl_timeslot_session_id_t session_id, uint32_t signal_type );
static void Mpsl_Thread( void *p1, void *p2, void *p3 );
static void Session_Idle_Work_Handler( struct k_work *work );
static void Beacon_Received_Work_Handler( struct k_work *work );
static void Restore_Conn_Param_Work_Handler( struct k_work *work );
static void Rx_Timeout_Expiry( struct k_timer *timer );
static void Mpsl_Request_Delay_Expiry( struct k_timer *timer );
/*******************************************************************************
* Configurazione radio RX
******************************************************************************/
static void Radio_Configure_Rx( void )
{
/* Assicura che la radio sia alimentata. */
NRF_RADIO->POWER = 1U;
NRF_RADIO->FREQUENCY = RADIO_FREQ_OFFSET;
NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_1Mbit;
/* Access address 0x8E89BED6 con BALEN=3 */
NRF_RADIO->BASE0 = ( RADIO_ACCESS_ADDRESS << 8U ) & 0xFFFFFF00UL;
NRF_RADIO->PREFIX0 = ( RADIO_ACCESS_ADDRESS >> 24U ) & 0xFFUL;
NRF_RADIO->RXADDRESSES = 1U;
/* Nessun header (S0/LENGTH/S1 = 0): payload fisso 8 byte via STATLEN. */
NRF_RADIO->PCNF0 = 0UL;
/* PCNF1: MAXLEN=8, STATLEN=8, BALEN=3, little-endian */
NRF_RADIO->PCNF1 =
( 8UL << RADIO_PCNF1_MAXLEN_Pos ) |
( 8UL << RADIO_PCNF1_STATLEN_Pos ) |
( 3UL << RADIO_PCNF1_BALEN_Pos ) |
( RADIO_PCNF1_ENDIAN_Little << RADIO_PCNF1_ENDIAN_Pos );
/* CRC: 3 byte, skip addr, poly 0x100065B, init 0x555555 */
NRF_RADIO->CRCCNF =
( RADIO_CRCCNF_LEN_Three << RADIO_CRCCNF_LEN_Pos ) |
( RADIO_CRCCNF_SKIPADDR_Skip << RADIO_CRCCNF_SKIPADDR_Pos );
NRF_RADIO->CRCPOLY = 0x100065BUL;
NRF_RADIO->CRCINIT = 0x555555UL;
/* Shorts: READY→START, END→DISABLE */
NRF_RADIO->SHORTS =
RADIO_SHORTS_READY_START_Msk |
RADIO_SHORTS_END_DISABLE_Msk;
/* Resetta tutti gli eventi rilevanti per questo slot */
NRF_RADIO->EVENTS_CRCOK = 0U;
NRF_RADIO->EVENTS_CRCERROR = 0U;
NRF_RADIO->EVENTS_DISABLED = 0U;
NRF_RADIO->EVENTS_ADDRESS = 0U;
NRF_RADIO->EVENTS_END = 0U;
NRF_RADIO->PACKETPTR = ( uint32_t )&rxBuffer;
/* Abilita interrupt CRCOK: genera SIGNAL_RADIO appena un pacchetto valido è ricevuto.
* Permette rilevamento immediato del beacon (latenza ~µs invece di ~100ms). */
NRF_RADIO->INTENSET = RADIO_INTENSET_CRCOK_Msk;
/* Avvia ricezione */
NRF_RADIO->TASKS_RXEN = 1U;
}
/*******************************************************************************
* Helper interno: prepara nextRequest come EARLIEST per recovery
******************************************************************************/
static void Prepare_Earliest_Recovery( void )
{
nextRequest.request_type = MPSL_TIMESLOT_REQ_TYPE_EARLIEST;
nextRequest.params.earliest.hfclk = MPSL_TIMESLOT_HFCLK_CFG_XTAL_GUARANTEED;
nextRequest.params.earliest.priority = MPSL_TIMESLOT_PRIORITY_HIGH;
nextRequest.params.earliest.length_us = RX_SLOT_LENGTH_US;
nextRequest.params.earliest.timeout_us = 4000000U; /* timeout 4s */
}
/*******************************************************************************
* MPSL callback – ISR di massima priorità
*
* Strategia: estensione slot invece di slot NORMAL separati.
* - SIGNAL_TIMER0 scatta 2ms prima della fine: se nessun beacon → ACTION_EXTEND
* - EXTEND_SUCCEEDED: ri-arma CC[0] per il prossimo allarme
* - EXTEND_FAILED: disabilita radio, richiede nuovo EARLIEST
* - BLOCKED / CANCELLED: richiede nuovo EARLIEST (retry immediato)
* - Beacon trovato: ACTION_END → SESSION_IDLE → session_close
******************************************************************************/
static mpsl_timeslot_signal_return_param_t *Timeslot_Callback(
mpsl_timeslot_session_id_t session_id, uint32_t signal_type )
{
ARG_UNUSED( session_id );
switch ( signal_type )
{
case MPSL_TIMESLOT_SIGNAL_START:
{
rxState = RX_STATE_POLLING;
dbgSlotCount++;
/* Cattura stato radio PRIMA di configurarla */
dbgStateAtStart = NRF_RADIO->STATE;
/* Configura radio e avvia RX */
Radio_Configure_Rx();
/* Arma TIMER0: scatta 2ms prima della fine per richiedere l'estensione.
* TIMER0 parte da 0 all'inizio dello slot e gira a 1 MHz. */
currentSlotEndUs = RX_SLOT_LENGTH_US;
NRF_TIMER0->TASKS_CLEAR = 1U;
NRF_TIMER0->CC[ 0 ] = RX_SLOT_LENGTH_US - RX_SLOT_TIMER_MARGIN_US;
NRF_TIMER0->EVENTS_COMPARE[0] = 0U;
NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
NRF_TIMER0->TASKS_START = 1U;
{
uint8_t evt = ( uint8_t )SLOT_EVT_SLOT_ENTERED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
case MPSL_TIMESLOT_SIGNAL_RADIO:
{
/* Scatta appena la radio genera EVENTS_CRCOK: rilevamento immediato del beacon.
* Latenza: ~µs dal completamento del pacchetto RX. */
if ( NRF_RADIO->EVENTS_CRCOK == 1U )
{
NRF_RADIO->EVENTS_CRCOK = 0U;
dbgCrcOkCount++;
bool magicOk = ( rxBuffer.magic == BEACON_MAGIC );
if ( magicOk )
{
/* Beacon valido: attendi DISABLED (END→DISABLE short in corso) e termina. */
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
/* Disabilita ulteriori interrupt radio e TIMER0 (non più necessari) */
NRF_RADIO->INTENCLR = RADIO_INTENCLR_CRCOK_Msk;
NRF_TIMER0->INTENCLR = TIMER_INTENCLR_COMPARE0_Msk;
rxState = RX_STATE_BEACON_RECEIVED;
uint8_t evt = ( uint8_t )SLOT_EVT_BEACON_RECEIVED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
evt = ( uint8_t )SLOT_EVT_TIMER0_CRCOK_MAGIC_OK;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_END;
}
else
{
/* Magic errato: attendi DISABLED e riavvia RX immediatamente. */
dbgMagicFailCount++;
dbgLastMagic = rxBuffer.magic;
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
NRF_RADIO->EVENTS_CRCOK = 0U;
NRF_RADIO->EVENTS_CRCERROR = 0U;
NRF_RADIO->EVENTS_ADDRESS = 0U;
NRF_RADIO->EVENTS_END = 0U;
NRF_RADIO->TASKS_RXEN = 1U;
uint8_t evt = ( uint8_t )SLOT_EVT_TIMER0_CRCOK_MAGIC_FAIL;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
}
}
else
{
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
}
break;
}
case MPSL_TIMESLOT_SIGNAL_TIMER0:
{
/* SIGNAL_RADIO gestisce il rilevamento rapido; SIGNAL_TIMER0 si occupa di:
* a) chiudere il slot se il beacon è già stato rilevato (race con SIGNAL_RADIO)
* b) richiedere l'estensione se ancora nessun beacon */
dbgTimer0Count++;
dbgLastState = NRF_RADIO->STATE;
if ( NRF_RADIO->EVENTS_ADDRESS == 1U ) { dbgAddressCount++; }
if ( NRF_RADIO->EVENTS_CRCERROR == 1U ) { dbgCrcErrorCount++; }
{
uint8_t evt = ( uint8_t )SLOT_EVT_TIMER0_FIRED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
if ( rxState == RX_STATE_BEACON_RECEIVED )
{
/* Beacon già rilevato da SIGNAL_RADIO: termina il slot. */
if ( NRF_RADIO->STATE != 0U )
{
NRF_RADIO->TASKS_DISABLE = 1U;
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
}
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_END;
}
else
{
/* Nessun beacon: richiedi estensione. */
/* Fallback: controlla CRCOK nel caso raro in cui SIGNAL_RADIO
* non sia ancora stato consegnato (race CRCOK/TIMER0). */
bool crcOk = ( NRF_RADIO->EVENTS_CRCOK == 1U );
bool magicOk = ( rxBuffer.magic == BEACON_MAGIC );
if ( crcOk && magicOk )
{
if ( NRF_RADIO->STATE != 0U )
{
NRF_RADIO->TASKS_DISABLE = 1U;
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
}
dbgCrcOkCount++;
rxState = RX_STATE_BEACON_RECEIVED;
uint8_t evt = ( uint8_t )SLOT_EVT_BEACON_RECEIVED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
evt = ( uint8_t )SLOT_EVT_TIMER0_CRCOK_MAGIC_OK;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_END;
}
else
{
if ( crcOk && !magicOk )
{
/* Magic errato: radio DISABLED, riavvia per l'estensione */
dbgMagicFailCount++;
dbgLastMagic = rxBuffer.magic;
NRF_RADIO->EVENTS_CRCOK = 0U;
NRF_RADIO->EVENTS_CRCERROR = 0U;
NRF_RADIO->EVENTS_DISABLED = 0U;
NRF_RADIO->EVENTS_ADDRESS = 0U;
NRF_RADIO->EVENTS_END = 0U;
NRF_RADIO->TASKS_RXEN = 1U;
}
/* Richiedi estensione del timeslot corrente */
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_EXTEND;
returnParam.params.extend.length_us = RX_SLOT_EXTEND_US;
}
}
break;
}
case MPSL_TIMESLOT_SIGNAL_EXTEND_SUCCEEDED:
{
/* Estensione concessa: aggiorna la fine slot e ri-arma CC[0].
* TIMER0 continua a girare dal T=0 dello slot originale. */
dbgExtendCount++;
currentSlotEndUs += RX_SLOT_EXTEND_US;
NRF_TIMER0->EVENTS_COMPARE[0] = 0U;
NRF_TIMER0->CC[ 0 ] = currentSlotEndUs - RX_SLOT_TIMER_MARGIN_US;
{
uint8_t evt = ( uint8_t )SLOT_EVT_EXTENDED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
case MPSL_TIMESLOT_SIGNAL_EXTEND_FAILED:
{
/* Estensione negata (evento BLE urgente imminente).
* Disabilita radio e richiedi subito un nuovo slot EARLIEST. */
if ( NRF_RADIO->STATE != 0U )
{
NRF_RADIO->TASKS_DISABLE = 1U;
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
}
{
uint8_t evt = ( uint8_t )SLOT_EVT_EXTEND_FAILED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
Prepare_Earliest_Recovery();
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
returnParam.params.request.p_next = &nextRequest;
break;
}
case MPSL_TIMESLOT_SIGNAL_BLOCKED:
{
/* Richiesta bloccata da evento di priorità ≥ già schedulato.
* Retry immediato con EARLIEST: MPSL trova il primo slot libero. */
{
uint8_t evt = ( uint8_t )SLOT_EVT_BLOCKED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
Prepare_Earliest_Recovery();
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
returnParam.params.request.p_next = &nextRequest;
break;
}
case MPSL_TIMESLOT_SIGNAL_CANCELLED:
{
/* Slot già schedulato ma revocato da evento a priorità superiore.
* Retry immediato con EARLIEST. */
{
uint8_t evt = ( uint8_t )SLOT_EVT_CANCELLED;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
Prepare_Earliest_Recovery();
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_REQUEST;
returnParam.params.request.p_next = &nextRequest;
break;
}
case MPSL_TIMESLOT_SIGNAL_SESSION_IDLE:
{
uint8_t evt = ( uint8_t )SLOT_EVT_SESSION_IDLE;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
default:
{
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
}
return &returnParam;
}
/*******************************************************************************
* ISR SWI1_EGU1 – lettura ring buffer e submit work
******************************************************************************/
ISR_DIRECT_DECLARE( Timeslot_Swi1_Isr )
{
uint8_t evt;
while ( ring_buf_get( &slotEventRingBuf, &evt, 1 ) == 1 )
{
switch ( ( slot_evt_t )evt )
{
case SLOT_EVT_SESSION_IDLE:
k_work_submit( &sessionIdleWork );
break;
case SLOT_EVT_BEACON_RECEIVED:
gpio_pin_toggle_dt( &signalSwitchButton );
k_work_submit( &beaconReceivedWork );
break;
case SLOT_EVT_SLOT_ENTERED:
/* stateAtStart: stato radio PRIMA di Radio_Configure_Rx()
* 0=DISABLED 1=RXRU 2=RXIDLE 3=RX 9=TXRU 11=TX */
LOG_INF( "slot #%u ENTRATO stateAtStart=%u",
dbgSlotCount, dbgStateAtStart );
break;
case SLOT_EVT_TIMER0_FIRED:
/* dbgLastState: stato radio al SIGNAL_TIMER0 (dopo 99ms di RX)
* 0=DISABLED (END→DISABLE short: pkt ricevuto o CRC error)
* 3=RX (nessun AA match – radio ancora in ascolto)
* altri (transizione in corso) */
LOG_INF( "slot #%u TIMER0 state=%u addr=%u crcOk=%u crcErr=%u magicFail=%u",
dbgTimer0Count, dbgLastState,
dbgAddressCount, dbgCrcOkCount,
dbgCrcErrorCount, dbgMagicFailCount );
break;
case SLOT_EVT_TIMER0_CRCOK_MAGIC_OK:
LOG_INF( "CRCOK + magic OK -> beacon valido" );
break;
case SLOT_EVT_TIMER0_CRCOK_MAGIC_FAIL:
LOG_WRN( "CRCOK ma magic errato 0x%08X (atteso 0x%08X) – riavvio RX",
dbgLastMagic, BEACON_MAGIC );
break;
case SLOT_EVT_EXTENDED:
LOG_DBG( "ext #%u: slotEnd=%ums",
dbgExtendCount, ( unsigned )( currentSlotEndUs / 1000U ) );
break;
case SLOT_EVT_EXTEND_FAILED:
LOG_WRN( "EXTEND_FAILED dopo %ums – nuovo EARLIEST (ext=%u slot=%u)",
( unsigned )( currentSlotEndUs / 1000U ),
dbgExtendCount, dbgSlotCount );
break;
case SLOT_EVT_BLOCKED:
LOG_WRN( "BLOCKED – retry EARLIEST (slot=%u)", dbgSlotCount );
break;
case SLOT_EVT_CANCELLED:
LOG_WRN( "CANCELLED – retry EARLIEST (slot=%u)", dbgSlotCount );
break;
default:
break;
}
}
ISR_DIRECT_PM();
return 1;
}
/*******************************************************************************
* Work handler: sessione idle → chiude sessione e resetta stato
******************************************************************************/
static void Session_Idle_Work_Handler( struct k_work *work )
{
ARG_UNUSED( work );
LOG_INF( "session idle – slot=%u STATE=%u addr=%u crcOk=%u crcErr=%u magicFail=%u stato=%d",
dbgSlotCount, dbgLastState,
dbgAddressCount, dbgCrcOkCount,
dbgCrcErrorCount, dbgMagicFailCount, ( int )rxState );
mpsl_msg_t msg = MPSL_MSG_CLOSE_SESSION;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
/* Resetta stato e ferma il timer: la sessione è terminata */
k_timer_stop( &rxTimeoutTimer );
k_work_submit( &restoreConnParamWork );
rxState = RX_STATE_IDLE;
}
/*******************************************************************************
* Work handler: beacon ricevuto → callback applicativa
******************************************************************************/
static void Beacon_Received_Work_Handler( struct k_work *work )
{
ARG_UNUSED( work );
k_timer_stop( &rxTimeoutTimer );
/* La sessione MPSL viene chiusa da Session_Idle_Work_Handler quando arriva
* SIGNAL_SESSION_IDLE in risposta all'ACTION_END restituito da SIGNAL_TIMER0.
* Non chiamare session_close qui per evitare il doppio close (-EBUSY). */
k_work_submit( &restoreConnParamWork );
uint8_t devId = targetDeviceId;
rxState = RX_STATE_IDLE;
LOG_INF( "RADIO BEACON RICEVUTO: device %d", devId );
if ( beaconCallback != NULL )
{
beaconCallback( devId );
}
}
/*******************************************************************************
* Work handler: ripristina la CI BLE a idle dopo il timeslot
******************************************************************************/
static void Restore_Conn_Param_Work_Handler( struct k_work *work )
{
ARG_UNUSED( work );
if ( targetConn != NULL )
{
bt_conn_le_param_update( targetConn, idleConnParam );
targetConn = NULL;
}
}
/*******************************************************************************
* Timer timeout RX
******************************************************************************/
static void Rx_Timeout_Expiry( struct k_timer *timer )
{
ARG_UNUSED( timer );
if ( rxState == RX_STATE_IDLE )
{
return;
}
mpsl_msg_t msg = MPSL_MSG_CLOSE_SESSION;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
/* Ripristina CI BLE a idle */
k_work_submit( &restoreConnParamWork );
rxState = RX_STATE_IDLE;
LOG_WRN( "timeout 2min, beacon non ricevuto per device %d", targetDeviceId );
}
/*******************************************************************************
* Thread MPSL non-preemptible
******************************************************************************/
static void Mpsl_Thread( void *p1, void *p2, void *p3 )
{
ARG_UNUSED( p1 );
ARG_UNUSED( p2 );
ARG_UNUSED( p3 );
mpsl_msg_t msg;
while ( 1 )
{
k_msgq_get( &mpslMsgq, &msg, K_FOREVER );
switch ( msg )
{
case MPSL_MSG_OPEN_SESSION:
{
int err = mpsl_timeslot_session_open( Timeslot_Callback, &sessionId );
if ( err != 0 )
{
LOG_ERR( "session_open fallita: %d", err );
rxState = RX_STATE_IDLE;
}
else
{
LOG_INF( "session_open OK (id=%u)", ( unsigned )sessionId );
}
break;
}
case MPSL_MSG_MAKE_REQUEST:
{
int err = mpsl_timeslot_request( sessionId, &firstRequest );
if ( err != 0 )
{
LOG_ERR( "timeslot_request fallita: %d (sessione=%u)", err, ( unsigned )sessionId );
rxState = RX_STATE_IDLE;
}
else
{
LOG_INF( "timeslot_request OK, in attesa SIGNAL_START..." );
}
break;
}
case MPSL_MSG_CLOSE_SESSION:
{
int err = mpsl_timeslot_session_close( sessionId );
if ( err != 0 )
{
LOG_ERR( "session_close fallita: %d", err );
}
break;
}
default:
break;
}
}
}
/*******************************************************************************
* Timer ritardo request MPSL – scatta dopo 1s dal CI update
******************************************************************************/
static void Mpsl_Request_Delay_Expiry( struct k_timer *timer )
{
ARG_UNUSED( timer );
LOG_INF( "CI update atteso, invio MAKE_REQUEST a MPSL..." );
mpsl_msg_t msg = MPSL_MSG_MAKE_REQUEST;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
}
/*******************************************************************************
* API pubblica
******************************************************************************/
error_mng_t Timeslot_Radio_Initialize( void )
{
rxState = RX_STATE_IDLE;
targetConn = NULL;
beaconCallback = NULL;
currentSlotEndUs = 0U;
dbgSlotCount = 0U;
dbgTimer0Count = 0U;
dbgExtendCount = 0U;
dbgCrcOkCount = 0U;
dbgMagicFailCount = 0U;
dbgLastMagic = 0U;
dbgAddressCount = 0U;
dbgCrcErrorCount = 0U;
dbgStateAtStart = 0U;
dbgLastState = 0U;
ring_buf_init( &slotEventRingBuf, sizeof( slotRingBufData ), slotRingBufData );
k_work_init( &sessionIdleWork, Session_Idle_Work_Handler );
k_work_init( &beaconReceivedWork, Beacon_Received_Work_Handler );
k_work_init( &restoreConnParamWork, Restore_Conn_Param_Work_Handler );
k_timer_init( &rxTimeoutTimer, Rx_Timeout_Expiry, NULL );
k_timer_init( &mpslRequestDelayTimer, Mpsl_Request_Delay_Expiry, NULL );
/* Collega ISR SWI1_EGU1 */
IRQ_DIRECT_CONNECT( SWI1_EGU1_IRQn, 1, Timeslot_Swi1_Isr, 0 );
irq_enable( SWI1_EGU1_IRQn );
/* Crea il thread MPSL non-preemptible */
mpslThreadId = k_thread_create(
&mpslThread,
mpslStackArea,
K_THREAD_STACK_SIZEOF( mpslStackArea ),
Mpsl_Thread,
NULL, NULL, NULL,
K_PRIO_COOP( CONFIG_MPSL_THREAD_COOP_PRIO ),
0,
K_NO_WAIT );
if ( mpslThreadId == NULL )
{
LOG_ERR( "creazione thread MPSL fallita" );
return SYSTEM_ERROR;
}
k_thread_name_set( mpslThreadId, "TH_MPSL_RX" );
LOG_INF( "inizializzato" );
return SYSTEM_NO_ERROR;
}
void Timeslot_Radio_Register_Beacon_Callback( timeslot_radio_beacon_cb_t callback )
{
beaconCallback = callback;
}
error_mng_t Timeslot_Radio_Rx_Start( uint8_t deviceId, struct bt_conn *conn )
{
int err;
if ( rxState != RX_STATE_IDLE )
{
LOG_WRN( "gia in corso (stato %d)", ( int )rxState );
return SYSTEM_ERROR;
}
targetDeviceId = deviceId;
targetConn = conn;
rxState = RX_STATE_REQUESTING;
/* Aumenta la CI BLE a 1000ms per massimizzare le finestre MPSL (~90% duty cycle).
* Nessun trasferimento BLE durante il timeslot, quindi nessun impatto applicativo. */
if ( conn != NULL )
{
err = bt_conn_le_param_update( conn, timeslotConnParam );
if ( err != 0 )
{
LOG_ERR( "impossibile aggiornare parametri connessione (%d)", err );
}
}
else
{
LOG_WRN( "connessione NULL, impossibile aggiornare parametri connessione" );
}
/* Prima richiesta EARLIEST */
firstRequest.request_type = MPSL_TIMESLOT_REQ_TYPE_EARLIEST;
firstRequest.params.earliest.hfclk = MPSL_TIMESLOT_HFCLK_CFG_XTAL_GUARANTEED;
/* HIGH: MPSL può preemptare gli eventi BLE connection, necessario perché
* uno slot da 100ms non entra mai in un gap BLE da 48ms (CI=50ms).
* Nessun dato BLE viene trasmesso durante il timeslot → OK. */
firstRequest.params.earliest.priority = MPSL_TIMESLOT_PRIORITY_HIGH;
firstRequest.params.earliest.length_us = RX_SLOT_LENGTH_US;
firstRequest.params.earliest.timeout_us = 4000000U;
/* Apre la sessione immediatamente; la MAKE_REQUEST viene ritardata di 1 secondo
* tramite mpslRequestDelayTimer per dare tempo al CI update di applicarsi.
* Senza il ritardo, MPSL non trova uno slot da 100ms nell'intervallo BLE da 50ms. */
mpsl_msg_t msg = MPSL_MSG_OPEN_SESSION;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
k_timer_start( &mpslRequestDelayTimer, K_MSEC( 1000 ), K_NO_WAIT );
/* Avvia timer timeout 2 minuti */
k_timer_start( &rxTimeoutTimer, K_MSEC( RX_TIMEOUT_MS ), K_NO_WAIT );
gpio_pin_configure_dt( &signalSwitchButton, GPIO_OUTPUT_INACTIVE );
LOG_INF( "avviato per device %d (CI -> 1000ms, request tra 1s)", deviceId );
return SYSTEM_NO_ERROR;
}
sensor:
#include "timeslot_radio.h"
#include "memory.h"
#include <zephyr/kernel.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/drivers/gpio.h>
#include <mpsl_timeslot.h>
#include <nrfx.h>
#include <string.h>
LOG_MODULE_REGISTER( timeslot_radio );
/*******************************************************************************
* Costanti
******************************************************************************/
/* Slot unico contenente tutti i beacon: la radio TX rimane allocata per tutta
* la finestra, eliminando i gap inter-slot dovuti a BLOCKED/CANCELLED.
* TX_SLOT_DISTANCE_US: intervallo tra inizio beacon N e inizio beacon N+1.
* TX_SLOT_LENGTH_US: deve coprire l'ultimo TIMER0 + margine.
* = TX_TIMER_EXPIRY_US + (TX_BEACON_COUNT-1)*TX_SLOT_DISTANCE_US + margine */
#define TX_SLOT_LENGTH_US 22000U /* 22ms: copre 10 beacon ogni 2ms */
#define TX_TIMER_EXPIRY_US 350U /* TIMER0 primo beacon: ~40µs ramp + ~110µs TX + margine */
#define TX_SLOT_DISTANCE_US 2000U /* 2ms tra beacon: latenza max dongle ~2ms */
#define TX_BEACON_COUNT 10U /* beacon nel singolo slot */
#define RADIO_FREQ_OFFSET 80U /* 2480 MHz */
#define RADIO_ACCESS_ADDRESS 0xDEADBEEFUL
#define BEACON_MAGIC 0xBEAC0001UL
#define TX_ARM_DELAY_MS 10000U /* 10s – simula evento applicativo random */
#define MPSL_THREAD_STACKSIZE 1024U
#define MPSL_MSGQ_SIZE 4U
#define SLOT_RING_BUF_SIZE 8U
/*******************************************************************************
* Tipo beacon
******************************************************************************/
/* Payload fisso 8 byte: niente campo LENGTH in testa (STATLEN=8 in PCNF1).
* S0/LFLEN/S1 a zero → la radio non legge/scrive alcun header prima del payload. */
typedef struct __attribute__(( packed ))
{
uint32_t magic; /* = BEACON_MAGIC */
uint8_t serial[ 4 ]; /* primi 4 byte del seriale NVS */
} timeslot_radio_beacon_t;
/*******************************************************************************
* Stato macchina TX
******************************************************************************/
typedef enum
{
TX_STATE_IDLE = 0,
TX_STATE_ARMED,
TX_STATE_REQUESTING,
TX_STATE_IN_SLOT,
} tx_state_t;
typedef enum
{
MPSL_MSG_OPEN_SESSION = 0,
MPSL_MSG_MAKE_REQUEST,
MPSL_MSG_CLOSE_SESSION,
} mpsl_msg_t;
typedef enum
{
SLOT_EVT_SESSION_IDLE = 0,
SLOT_EVT_TX_TIMER0, /* SIGNAL_TIMER0 scattato: conferma per ogni beacon TX */
} slot_evt_t;
/*******************************************************************************
* Variabili globali
******************************************************************************/
static volatile tx_state_t txState = TX_STATE_IDLE;
static volatile uint8_t txCount;
static volatile uint32_t dbgTxEndCount; /* EVENTS_END=1 al TIMER0: TX completata */
static volatile uint32_t dbgTxStateAtT0; /* NRF_RADIO->STATE al TIMER0 (11=TX,0=DISABLED) */
static timeslot_radio_beacon_t txBeacon;
static mpsl_timeslot_session_id_t sessionId;
static mpsl_timeslot_request_t firstRequest;
static mpsl_timeslot_signal_return_param_t returnParam;
static uint8_t slotRingBufData[ SLOT_RING_BUF_SIZE ];
static struct ring_buf slotEventRingBuf;
static struct k_thread mpslThread;
static K_THREAD_STACK_DEFINE( mpslStackArea, MPSL_THREAD_STACKSIZE );
static k_tid_t mpslThreadId;
K_MSGQ_DEFINE( mpslMsgq, sizeof( mpsl_msg_t ), MPSL_MSGQ_SIZE, 4 );
static struct k_work sessionIdleWork;
static struct k_timer txArmTimer;
extern memory_nvs_settings_t nvsSettings;
static const struct gpio_dt_spec signalChargeWireless =
GPIO_DT_SPEC_GET( DT_NODELABEL( chg_wls_mcu ), gpios );
/*******************************************************************************
* Forward declarations
******************************************************************************/
static mpsl_timeslot_signal_return_param_t *Timeslot_Callback(
mpsl_timeslot_session_id_t session_id, uint32_t signal_type );
static void Mpsl_Thread( void *p1, void *p2, void *p3 );
static void Session_Idle_Work_Handler( struct k_work *work );
static void Tx_Arm_Timer_Expiry( struct k_timer *timer );
/*******************************************************************************
* Configurazione radio TX
******************************************************************************/
static void Radio_Configure_Tx( void )
{
/* Assicura che la radio sia alimentata. */
NRF_RADIO->POWER = 1U;
NRF_RADIO->FREQUENCY = RADIO_FREQ_OFFSET;
NRF_RADIO->MODE = RADIO_MODE_MODE_Nrf_1Mbit;
NRF_RADIO->BASE0 = ( RADIO_ACCESS_ADDRESS << 8U ) & 0xFFFFFF00UL;
NRF_RADIO->PREFIX0 = ( RADIO_ACCESS_ADDRESS >> 24U ) & 0xFFUL;
NRF_RADIO->TXADDRESS = 0U;
NRF_RADIO->RXADDRESSES = 1U;
/* Nessun header (S0/LENGTH/S1 = 0): payload fisso 8 byte via STATLEN. */
NRF_RADIO->PCNF0 = 0UL;
NRF_RADIO->PCNF1 =
( 8UL << RADIO_PCNF1_MAXLEN_Pos ) |
( 8UL << RADIO_PCNF1_STATLEN_Pos ) |
( 3UL << RADIO_PCNF1_BALEN_Pos ) |
( RADIO_PCNF1_ENDIAN_Little << RADIO_PCNF1_ENDIAN_Pos );
NRF_RADIO->CRCCNF =
( RADIO_CRCCNF_LEN_Three << RADIO_CRCCNF_LEN_Pos ) |
( RADIO_CRCCNF_SKIPADDR_Skip << RADIO_CRCCNF_SKIPADDR_Pos );
NRF_RADIO->CRCPOLY = 0x100065BUL;
NRF_RADIO->CRCINIT = 0x555555UL;
NRF_RADIO->SHORTS =
RADIO_SHORTS_READY_START_Msk |
RADIO_SHORTS_END_DISABLE_Msk;
NRF_RADIO->PACKETPTR = ( uint32_t )&txBeacon;
NRF_RADIO->EVENTS_DISABLED = 0U;
NRF_RADIO->TASKS_TXEN = 1U;
}
/*******************************************************************************
* MPSL callback – ISR massima priorità
******************************************************************************/
static mpsl_timeslot_signal_return_param_t *Timeslot_Callback(
mpsl_timeslot_session_id_t session_id, uint32_t signal_type )
{
ARG_UNUSED( session_id );
switch ( signal_type )
{
case MPSL_TIMESLOT_SIGNAL_START:
{
txState = TX_STATE_IN_SLOT;
Radio_Configure_Tx();
NRF_TIMER0->TASKS_CLEAR = 1U;
NRF_TIMER0->CC[ 0 ] = TX_TIMER_EXPIRY_US;
NRF_TIMER0->EVENTS_COMPARE[0] = 0U;
NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
NRF_TIMER0->TASKS_START = 1U;
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
case MPSL_TIMESLOT_SIGNAL_TIMER0:
{
/* Cattura stato prima di disabilitare:
* 0=DISABLED → TX completata (END→DISABLE short scattato)
* 11=TX → TX ancora in corso (improbabile a 350µs) */
dbgTxStateAtT0 = NRF_RADIO->STATE;
if ( NRF_RADIO->EVENTS_END == 1U ) { dbgTxEndCount++; }
/* Forza DISABLE in ogni caso (sicurezza se lo short non è ancora scattato) */
NRF_RADIO->TASKS_DISABLE = 1U;
while ( NRF_RADIO->EVENTS_DISABLED == 0U ) {}
NRF_RADIO->EVENTS_DISABLED = 0U;
txCount++;
/* Segnala TIMER0 per log di conferma TX */
{
uint8_t evt = ( uint8_t )SLOT_EVT_TX_TIMER0;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
}
if ( txCount < TX_BEACON_COUNT )
{
/* Beacon successivo nello stesso slot: ri-arma CC[0] e rilancia TX.
* TIMER0 continua a girare dal T=0 dello SIGNAL_START: avanziamo
* CC[0] di TX_SLOT_DISTANCE_US rispetto al valore corrente. */
NRF_RADIO->EVENTS_END = 0U;
NRF_RADIO->EVENTS_DISABLED = 0U;
NRF_TIMER0->CC[ 0 ] += TX_SLOT_DISTANCE_US;
NRF_TIMER0->EVENTS_COMPARE[0] = 0U;
NRF_RADIO->TASKS_TXEN = 1U;
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
}
else
{
/* Tutti i beacon trasmessi: termina lo slot. */
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_END;
}
break;
}
case MPSL_TIMESLOT_SIGNAL_SESSION_IDLE:
{
uint8_t evt = ( uint8_t )SLOT_EVT_SESSION_IDLE;
ring_buf_put( &slotEventRingBuf, &evt, 1 );
NVIC_SetPendingIRQ( SWI1_EGU1_IRQn );
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
case MPSL_TIMESLOT_SIGNAL_CANCELLED:
default:
returnParam.callback_action = MPSL_TIMESLOT_SIGNAL_ACTION_NONE;
break;
}
return &returnParam;
}
/*******************************************************************************
* ISR SWI1_EGU1
******************************************************************************/
ISR_DIRECT_DECLARE( Timeslot_Swi1_Isr )
{
uint8_t evt;
while ( ring_buf_get( &slotEventRingBuf, &evt, 1 ) == 1 )
{
switch ( ( slot_evt_t )evt )
{
case SLOT_EVT_SESSION_IDLE:
k_work_submit( &sessionIdleWork );
break;
case SLOT_EVT_TX_TIMER0:
/* STATE: 0=DISABLED(TX ok, short END→DISABLE) 11=TX(ancora in corso) */
LOG_INF( "TX beacon #%u state=%u END=%u",
( unsigned )txCount, dbgTxStateAtT0, dbgTxEndCount );
break;
default:
break;
}
}
ISR_DIRECT_PM();
return 1;
}
/*******************************************************************************
* Work handler
******************************************************************************/
static void Session_Idle_Work_Handler( struct k_work *work )
{
ARG_UNUSED( work );
mpsl_msg_t msg = MPSL_MSG_CLOSE_SESSION;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
txState = TX_STATE_IDLE;
txCount = 0;
/* dbgTxEndCount==TX_BEACON_COUNT → tutte le TX completate (EVENTS_END).
* dbgTxStateAtT0==0 (DISABLED) → radio si era già auto-disabilitata via END→DISABLE. */
LOG_INF( "Timeslot TX: %d beacon trasmessi, END_count=%u STATE_last=%u",
TX_BEACON_COUNT, dbgTxEndCount, dbgTxStateAtT0 );
}
/*******************************************************************************
* Timer expiry – scatta dopo TX_ARM_DELAY_MS e avvia la sessione MPSL
******************************************************************************/
static void Tx_Arm_Timer_Expiry( struct k_timer *timer )
{
ARG_UNUSED( timer );
gpio_pin_toggle_dt( &signalChargeWireless );
txState = TX_STATE_REQUESTING;
txCount = 0;
mpsl_msg_t msg = MPSL_MSG_OPEN_SESSION;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
msg = MPSL_MSG_MAKE_REQUEST;
k_msgq_put( &mpslMsgq, &msg, K_NO_WAIT );
LOG_INF( "Timeslot TX: trasmissione beacon avviata" );
}
/*******************************************************************************
* Thread MPSL non-preemptible
******************************************************************************/
static void Mpsl_Thread( void *p1, void *p2, void *p3 )
{
ARG_UNUSED( p1 );
ARG_UNUSED( p2 );
ARG_UNUSED( p3 );
mpsl_msg_t msg;
while ( 1 )
{
k_msgq_get( &mpslMsgq, &msg, K_FOREVER );
switch ( msg )
{
case MPSL_MSG_OPEN_SESSION:
{
int err = mpsl_timeslot_session_open( Timeslot_Callback, &sessionId );
if ( err != 0 )
{
LOG_ERR( "session_open fallita: %d", err );
txState = TX_STATE_IDLE;
}
else
{
LOG_INF( "session_open OK (id=%u)", ( unsigned )sessionId );
}
break;
}
case MPSL_MSG_MAKE_REQUEST:
{
int err = mpsl_timeslot_request( sessionId, &firstRequest );
if ( err != 0 )
{
LOG_ERR( "timeslot_request fallita: %d (sessione=%u)", err, ( unsigned )sessionId );
txState = TX_STATE_IDLE;
}
else
{
LOG_INF( "timeslot_request OK, in attesa SIGNAL_START..." );
}
break;
}
case MPSL_MSG_CLOSE_SESSION:
{
int err = mpsl_timeslot_session_close( sessionId );
if ( err != 0 )
{
LOG_ERR( "session_close fallita: %d", err );
}
break;
}
default:
break;
}
}
}
/*******************************************************************************
* API pubblica
******************************************************************************/
error_mng_t Timeslot_Radio_Initialize( void )
{
txState = TX_STATE_IDLE;
txCount = 0;
dbgTxEndCount = 0U;
dbgTxStateAtT0 = 0U;
gpio_pin_configure_dt( &signalChargeWireless, GPIO_OUTPUT_INACTIVE );
ring_buf_init( &slotEventRingBuf, sizeof( slotRingBufData ), slotRingBufData );
k_work_init( &sessionIdleWork, Session_Idle_Work_Handler );
k_timer_init( &txArmTimer, Tx_Arm_Timer_Expiry, NULL );
IRQ_DIRECT_CONNECT( SWI1_EGU1_IRQn, 1, Timeslot_Swi1_Isr, 0 );
irq_enable( SWI1_EGU1_IRQn );
mpslThreadId = k_thread_create(
&mpslThread, mpslStackArea,
K_THREAD_STACK_SIZEOF( mpslStackArea ),
Mpsl_Thread, NULL, NULL, NULL,
K_PRIO_COOP( CONFIG_MPSL_THREAD_COOP_PRIO ), 0, K_NO_WAIT );
if ( mpslThreadId == NULL )
{
LOG_ERR( "Timeslot TX: creazione thread MPSL fallita" );
return SYSTEM_ERROR;
}
k_thread_name_set( mpslThreadId, "TH_MPSL_TX" );
LOG_INF( "Timeslot TX: inizializzato" );
return SYSTEM_NO_ERROR;
}
error_mng_t Timeslot_Radio_Tx_Arm( void )
{
if ( txState != TX_STATE_IDLE )
{
LOG_WRN( "Timeslot TX: già in corso (stato %d)", ( int )txState );
return SYSTEM_ERROR;
}
/* Popola il beacon ora, prima del delay, così è pronto all'avvio MPSL */
txBeacon.magic = BEACON_MAGIC;
memcpy( txBeacon.serial, &nvsSettings.serial[ MEMORY_NVS_HEADER_SIZE ], 4 );
/* Prepara la prima richiesta EARLIEST */
firstRequest.request_type = MPSL_TIMESLOT_REQ_TYPE_EARLIEST;
firstRequest.params.earliest.hfclk = MPSL_TIMESLOT_HFCLK_CFG_XTAL_GUARANTEED;
firstRequest.params.earliest.priority = MPSL_TIMESLOT_PRIORITY_NORMAL;
firstRequest.params.earliest.length_us = TX_SLOT_LENGTH_US; /* 22ms: tutti i beacon in un unico slot */
firstRequest.params.earliest.timeout_us = 1000000U;
txState = TX_STATE_ARMED;
/* Avvia il timer: la trasmissione parte dopo TX_ARM_DELAY_MS */
k_timer_start( &txArmTimer, K_MSEC( TX_ARM_DELAY_MS ), K_NO_WAIT );
LOG_INF( "Timeslot TX: armato, trasmissione tra %u ms", TX_ARM_DELAY_MS );
return SYSTEM_NO_ERROR;
}