2 Key Concepts of MPLAB Harmony v3 Drivers

Atomicity

Once started, an operation (sequence of instructions) is considered atomic, if it is indivisible and non-interruptible. A data item is considered atomic if it cannot be subdivided as it is read or written.

Atomicity is necessary for shared data or instructions that need to have exclusive access of the CPU. The sequence of instructions that needs to be protected from shared access is called a critical code section, and the data that needs to be protected from shared access is called critical data.

Atomicity is achieved by guarding the critical code or critical data. One of the approaches to guard the critical regions is to use a lock, which is set before accessing the shared resource and then released when done.

Interrupt Safety

A sequence of code is said to be “interrupt-safe” when the occurrence of an interrupts does not alter the output or functional behavior of the code. To be “interrupt-safe”, the sequence of code in consideration must be atomic (indivisible and uninterruptible), and the relevant interrupts must be disabled before entering the sequence.

In MPLAB Harmony v3, interrupts can be disabled globally by using the Interrupt System Service API, that is, SYS_INT_Disable() can be enabled by using the API, SYS_INT_Enable()and can restore the saved state by using the API, SYS_INT_Restore(). However, MPLAB Harmony v3 libraries are modular and by convention they respect the abstractions of other libraries, never attempting to directly access their internal resources. Due to this convention, the only code in the system that should ever attempt to access the internal resources owned by a driver, is the driver code itself.

This means it is not necessary to globally disable interrupts in most cases to guarantee correct and reliable operation of a MPLAB Harmony v3 driver. While performing a non-atomic access to data structures or peripheral hardware, a driver temporarily masks the interrupts of the peripheral it owns. This prevents the driver’s own interrupt-driven tasks functions from potentially corrupting data that is also accessed by the driver’s interface functions.

This can be done using the interrupt system service and it is efficient than globally disabling all the interrupts because it allows higher priority interrupts (which do not affect the driver) to occur, protecting their response time latency. This can be done using the Interrupt System Service, as shown in the following example.

// Disables the interrupt for SPI0.
Bool spiIntStatus; = false;
spiIntStatus = SYS_INT_SourceDisable(SPI0_IRQn);

// Restore the interrupt for SPI0.
SYS_INT_SourceRestore(spiIntStatus, SPI0_IRQn);

These functions can also be used to guard non-atomic accesses by the driver’s interface functions to resources that are shared with the driver’s ISR. This method works to ensure safe access to a shared resource without disabling interrupts globally when using an Asynchronous driver with bare-metal configuration.

If a section of code must be truly atomic (uninterruptible), interrupts can be globally disabled for a short period of time using the Interrupt System Service functions, as shown in the following example.

Bool interruptState;

// Save global interrupt state and disable interrupt
interruptState = SYS_INT_Disable();
// Critical Section

// Restore interrupt state
SYS_INT_Restore(interruptState);

Thread Safety

In an RTOS-based environment, it is possible that a driver and its clients may each run in its own RTOS thread. If the RTOS is preemptive, it is possible that the scheduler may interrupt any of these threads at any time and switch to another. If the other thread happens to access the same shared resource or execute the same critical section of the code, that section of the code must be guarded and made atomic. The sequence of code with such guards is known as “thread-safe” code. In the MPLAB Harmony v3 framework, thread safety is achieved by using the methods provided by the MPLAB Harmony Operating System Abstraction Layer (OSAL).

MPLAB Harmony v3 drivers run efficiently in an interrupt driven system. For an interrupt driven system running in an RTOS-based environment, it is common for the driver’s task functions to be called from an interrupt context. A mutex can be used to guard against simultaneous access to shared resources by different threads. However, this will not prevent an ISR from accessing the shared resources or critical code. If a mutex is used to accomplish thread safety, it must be augmented by temporarily disabling (masking) the associated interrupt source as described for a bare-metal environment in the Interrupt Safety section above.

Callback Functions

In the Non-Blocking method, instead of status polling, the callback mechanism can also be used to check the transfer status. For example, the application registers a callback function with a driver and makes the transfer request. The driver calls back the registered function on the completion of the request submitted by the driver client.

MPLAB Harmony v3 provides driver specific APIs to register the callback functions. For example, the Asynchronous SPI driver provides API, DRV_SPI_TransferEventHandlerSet(), this allows a client to set a transfer event handling function for the driver to call back when a queued transfer has finished.

Note: Because callbacks are called from the interrupt context, the following guidelines must be followed while implementing a callback function:
  • Must be treated like an ISR
  • Must be short
  • Do not call application functions that are not interrupt safe
  • Do not call other driver’s interface functions

Blocking API

A blocking API hangs up execution flow until it has performed its function and returns a result.

For example, the following code shows the I2C driver Synchronous mode API, DRV_I2C_WriteTransfer.

DRV_I2C_WriteTransfer(app_eepromData.i2cHandle, APP_EEPROM_I2C_SLAVE_ADDR, (void *)app_eepromData.i2cTxBuffer, 2);

The execution flow blocks when the API, DRV_I2C_WriteTransfer() is called until the requested bytes are transferred to the EEPROM.

Non-Blocking API

A non-blocking API receives the application request and returns immediately without providing the result. The result of the non-blocking API call is provided separately through an Asynchronous event. The application verifies the event to take further action.

The following code example shows the usage of the I2C driver Asynchronous mode API, DRV_I2C_WriteReadTransferAdd(), which is a non-blocking API.


void APP_EEPROM_I2CEventHandler ( DRV_I2C_TRANSFER_EVENT event, DRV_I2C_TRANSFER_HANDLE transferHandle, uintptr_t context)
{
    switch(event)
    {
        case DRV_I2C_TRANSFER_EVENT_COMPLETE:
            /* I2C Transfer Complete. */
            app_eepromData.reqStatus = APP_EEPROM_REQ_STATUS_DONE;
        break;

        case DRV_I2C_TRANSFER_EVENT_ERROR:
            app_eepromData.reqStatus = APP_EEPROM_REQ_STATUS_ERROR;
        break;

        default:
        break;
    }
}

int main ( void )
{
    /* Initialize all modules */
     ……

    /* Open the I2C Driver */
    app_eepromData.i2cHandle = DRV_I2C_Open( DRV_I2C_INDEX_0, DRV_IO_INTENT_READWRITE );
    if(app_eepromData.i2cHandle == DRV_HANDLE_INVALID)
    {
        app_eepromData.state = APP_STATE_ERROR;
    }
    else
    {
        /* Register I2C transfer complete Event Handler for EEPROM. */
        DRV_I2C_TransferEventHandlerSet(app_eepromData.i2cHandle, APP_EEPROM_I2CEventHandler, 0);

        /* Submit I2C transfer to read stored temperature values from EEPROM. */ 
        DRV_I2C_WriteReadTransferAdd(app_eepromData.i2cHandle,
                APP_EEPROM_I2C_SLAVE_ADDR, app_eepromData.i2cTxBuffer, 1,
                app_eepromData.i2cRxBuffer, 5, &app_eepromData.transferHandle);

        /* Display the read temperature on console */
    }

     ……
}

In the main function, the application registers a callback function (event handler “APP_EEPROM_I2CEventHandler”) with the I2C driver. The control immediately returns after the submission of the I2C request, calling the API DRV_I2C_WriteReadTransferAdd(). The application is notified by the driver on the completion of the request through an event to the callback function (event handler).

Synchronization

MPLAB Harmony v3 provides driver interface functions having both a blocked and non-blocking usage model. There are situations when developers expect a blocking model. For example, the file system style read and write functions block and not return until the entire transfer has completed.

The requirement to block is challenging to accomplish in a bare-metal environment. The RTOS-based design provides flexibility to implement the blocking requirement. MPLAB Harmony v3 provides the synchronous drivers to implement the blocking requirement of application. The Synchronous drivers are blocking in nature. Although the APIs are blocking, the data transfer still takes place from the interrupt context.
Note: MPLAB Harmony v3 OSAL provides a consistent interface to MPLAB Harmony v3 framework components (drivers, middleware, and so on). It takes care of the underlying differences between the available or supported RTOS Kernels, and ensures correct operation in the bare-metal and RTOS environment.