MPLAB® Harmony v3 Peripheral Libraries on SAM E70/S70/V70/V71: Step 5
Add application code to the project
The application is already developed and is available in the main_e70.c file under <your unzip folder>/same70_getting_started/dev_files/sam_e70_xult. The main_e70.c file contains the application logic. It also contains placeholders that you will populate with the necessary code in the next step.
- Go to the same70_getting_started/dev_files/sam_e70_xult folder and copy the pre-developed main_e70.c file.
- Replace (over-write) the main_e70.c file of your project available at <Your project folder>/same70_getting_started/firmware/src with the copied file.
- Open main_e70.c in MPLAB® X IDE and add application code by following the steps below:
Under main_e70.c file, in function main, notice the call to function SYS_Initialize. The generated function SYS_Initialize initializes all the peripheral modules used in the application (configured through MPLAB® Code Configurator (MCC)).
In the main_e70.c function, below SYS_Initialize(), add the following code to register callback event handlers.
XDMAC_ChannelCallbackRegister(XDMAC_CHANNEL_0, usartDmaChannelHandler, 0);
TC0_CH0_TimerCallbackRegister(tc0EventHandler, 0);
PIO_PinInterruptCallbackRegister(SWITCH_PIN, PIO_User_Handler, 0);
PIO_PinInterruptEnable(SWITCH_PIN);
Following the addition of the code above, add the function call.
Implement the registered callback event handlers for TC, I²C, Universal Synchronous Asynchronous Receiver Transmitter (USART) and Parallel Input/Output (PIO) PLIBs by adding the following code before
main() function in main_e70.c.
{
changeTempSamplingRate = true;
}
static void tc0EventHandler (TC_TIMER_STATUS status, uintptr_t context)
{
isTC0TimerExpired = true;
}
static void i2cEventHandler(uintptr_t contextHandle)
{
if (TWIHS0_ErrorGet() == TWIHS_ERROR_NONE)
{
isTemperatureRead = true;
}
}
static void usartDmaChannelHandler(XDMAC_TRANSFER_EVENT event, uintptr_t contextHandle)
{
if (event == XDMAC_TRANSFER_COMPLETE)
{
isUSARTTxComplete = true;
}
}
Add the code below to submit an I²C transfer to read temperature sensor value when the configured period (default 500 milliseconds) has elapsed. The I²C PLIB calls back the callback event handler (registered in Step 2 above) when the submitted request is complete.
TWIHS0_WriteRead(TEMP_SENSOR_SLAVE_ADDR, &i2cWrData, 1, i2cRdData, 2);
Add the code below to prepare the received temperature value from the sensor to be prepared on the serial terminal.
sprintf((char*)uartTxBuffer, "Temperature = %02d F\r\n", temperatureVal);
LED_Toggle();
Add the code below to implement the change of sampling rate and prepare a message for the same on the serial terminal when the user presses the switch SW0.
if(tempSampleRate == TEMP_SAMPLING_RATE_500MS)
{
tempSampleRate = TEMP_SAMPLING_RATE_1S;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 1 second \r\n");
TC0_CH0_TimerPeriodSet(PERIOD_1S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_1S)
{
tempSampleRate = TEMP_SAMPLING_RATE_2S;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 2 seconds \r\n");
TC0_CH0_TimerPeriodSet(PERIOD_2S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_2S)
{
tempSampleRate = TEMP_SAMPLING_RATE_4S;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 4 seconds \r\n");
TC0_CH0_TimerPeriodSet(PERIOD_4S);
}
else if(tempSampleRate == TEMP_SAMPLING_RATE_4S)
{
tempSampleRate = TEMP_SAMPLING_RATE_500MS;
sprintf((char*)uartTxBuffer, "Sampling Temperature every 500 ms \r\n");
TC0_CH0_TimerPeriodSet(PERIOD_500MS);
}
else
{
;
}
Add code to transfer the buffer containing either:
- the latest temperature value in the format “Temperature = XX F\r\n”, over or
- the message mentioning the change of sampling rate over USART using DMA.
In the above code snippet, the Application Programming Interface (API) SCB_CleanDCache_by_Addr, is called to address the cache coherency issue (due to the default Write Back – Write Allocate cache policy) on SAM E70/S70/V70/V71 MCUs.
The cache coherency issue is inevitable on applications running on MCUs that have cacheable memory regions and using DMA for data transfer operations. This is because the CPU may perform read/write from the cache while DMA, on the other hand, transfers data between the peripheral and physical memory.
The cache coherency issue observed in this application is shown in the accompanying image.
Here, the above example is a memory to peripheral transfer (DMA reads from SRAM and writes to the peripheral). In this example, CPU first populates the DMA write buffer with the data to be written (ABCDEF) to the peripheral. However, depending on the cache policy (Write Back and Write Allocate), the DMA write buffer may be available in the data cache. Hence, the DMA write buffer in the main memory still contains the old data (123456). When the DMA is triggered, DMA reads the DMA write buffer from the main memory (123456). As a result, the DMA might end up transferring stale data to the peripheral.
One of the ways to address the cache coherency issue is to use the cache maintenance APIs provided by Cortex Microcontroller Software Interface Standard (CMSIS)
The application uses SCB_CleanDCache_by_Addr API to flush the write buffer from cache to the SRAM so that the DMA gets the latest data to be transferred to the USART.
In the above example, to resolve the cache coherency issue, the CPU first populates the DMA write buffer and then issues a cache clean command to flush the contents of the data cache (ABCDEF) into the main memory. The API SCB_CleanDCache_by_Addr can be used to perform a cache clean operation. When the DMA is triggered, DMA reads the DMA write buffer from the main memory, which now contains the updated data (ABCDEF).
The following screenshot shows how the cache coherency issues are addressed by using the cache maintenance APIs
You are now ready to build the code!