nRF5 SDK v13.0.0
TWI slave

The two-wire interface slave with EasyDMA (TWIS) driver includes two layers: the hardware access layer (HAL) and the driver layer (DRV).

The hardware access layer provides basic APIs for accessing the registers of the TWI. See the API documentation for the TWIS driver for details.

The driver layer provides APIs on a higher level than the HAL. See the API documentation for the TWIS driver for details.

Key features include:

Driver configuration

The following configuration options are available in sdk_config.h:

Modes of operation

The TWIS driver supports two different modes: synchronous mode and asynchronous mode.

Synchronous mode

In synchronous mode, the driver works completely without interrupts. To configure the driver to use synchronous mode, pass a NULL pointer as the pointer to the event handler during driver initialization.

In synchronous mode, events are pooled every time a state function is called. Pooling and state machine processing can be entered only once at the same time. Therefore, if a function is called from the main thread and, while it is processed, an interrupt occurs that tries to process a TWIS event again, the checking function in the interrupt is skipped.

The TWIS driver is a slave driver. Therefore, the reasons for configuring this driver to use synchronous mode are different than for a master driver. For a slave driver, it does not make sense to support sending data from the main thread and from interrupts at the same time. State testing functions like nrf_drv_twis_is_busy are not guaranteed to process the current state in the interrupts if the same functions are processed in the main thread. Interrupts should not wait for a state change. Otherwise, they might be locked.

The following example code shows how to use synchronous mode:

int main(void)
{
// Initialization
bool waitingRx = false;
nrf_drv_twis_init(&instance, NULL, NULL);
nrf_drv_twis_enable(&instance);
// Main loop
while(1)
{
// It is important to process it first
if(waitingRx && !nrf_drv_twis_is_pending_rx(&instance))
{
waitingRx = false;
// Check of errors
uint32_t err = nrf_drv_twis_error_get(&instance);
if(err)
{
// ... process errors ....
}
else
{
// ... process received data ...
}
}
{
// Prepare TX buffer (response for READ command)
nrf_drv_twis_tx_prepare(&instance, txbuffer, sizeof(txbuffer));
}
{
// Prepare RX buffer (response for WRITE command)
nrf_drv_twis_rx_prepare(&instance, rxbuffer, sizeof(rxbuffer));
waitingRx = true;
}
// ... The rest of the main lop tasks ...
}
}

Asynchronous mode

In asynchronous mode, all events are processed in internal interrupt service runtime. When anything requires processing, the event handler is called. See nrf_drv_twis_evt_type_t for a list of defined events.

The event handler is always called in the following pattern: REQ -> DONE or ERROR. This means that if we get a TWIS_EVT_READ_REQ message, the answer will always be TWIS_EVT_READ_DONE or TWIS_EVT_READ_ERROR. The answer will be sent even if there is no STOP condition on the bus, but reading finishes for example by a repeated START condition.

There are different ways to prepare buffers for receiving or sending data:

The following pseudocode shows how to implement an event handler:

void twis_event_handler(nrf_drv_twis_evt_t const * const p_event)
{
switch(p_event->type)
{
if(p_event->data.buf_req)
{
// Function size_t get_buffer_to_send(char const * * const rptr)
// needs to be implemented
const char * rptr;
size_t size = get_buffer_to_send(&rptr);
nrf_drv_twis_tx_prepare(&m_slave_inst, rptr, size);
}
break;
break;
if(p_event->data.buf_req)
{
// Function size_t get_buffer_to_write(char * * const wptr)
// needs to be implemented
char *wptr;
size_t size = get_buffer_to_write(&wptr);
nrf_drv_twis_rx_prepare(&m_slave_inst, wptr, size);
}
break;
// Function void mark_buffer_ready(size_t amount)
// needs to be implemented
mark_buffer_ready(p_event->data.rx_amount);
break;
default:
break;
}
}

Documentation feedback | Developer Zone | Subscribe | Updated