Low Power Application on SAM D21 Using Harmony v3 Peripheral Libraries: Step 6

Last modified by Microchip on 2023/11/09 09:07

Add Application Code to the Project

The application is already partially developed and is available in the main_d21.c file under <your unzip folder>/samd21_low_power/dev_files/sam_d21_xpro. The main_d21.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 samd21_low_power/dev_files/sam_d21_xpro folder and copy the pre-developed main_d21.c file.
  • Replace (over-write) the main_d21.c file of your project available at <Your project folder>/samd21_low_power/firmware/src with the copied file.
  • Open main_d21.c in MPLAB® X IDE and add the application code by following the steps below:

Under the main_d21.c file, in the main() function, notice the call to the SYS_Initialize function. The generated SYS_Initialize function initializes all the peripheral modules used in the application, configured through MPLAB Harmony Configurator (MHC).

Tip: Press the CTRL key and left-click on the SYS_Initialize function. The click will open the implementation for the SYS_Initialize function as shown in the following image.

Code Example 1​​​​

Note: The NVIC_Initialize is a system-specific initialization function necessary to run the device. MHC adds this module by default to the project graph and generates code. This module will be initialized to user configurations if the user configures them explicitly.


In the int main (void) function, below the SYS_Initialize() function call, add the following lines of code to register callback event handlers, start the Real-Time Clock (RTC) timer, and display the demo title message:

/* Initialize the EIC callback register */
EIC_CallbackRegister (EIC_PIN_15, EIC_User_Handler, 0);

/* Initialize the SERCOM 2 I²C callback register */
SERCOM2_I2C_CallbackRegister (i2cEventHandler, 0);

/* Initialize the AC callback register */
AC_CallbackRegister(acEventHandler, comparator_context);

/* Start RTC counter */
RTC_Timer32Start ();

/* Display example title */
printf ("\n\n\r-----------------------------------------------------------");
printf ("\n\r           Low-power Application on SAM D21 Xpro           ");
printf ("\n\r-----------------------------------------------------------");

Code Example 2​​​

a. The EIC_CallbackRegister function call registers an External Interrupt Controller (EIC) callback interrupt handler with the EIC Peripheral Library (PLIB). The interrupt handler is called by the EIC PLIB when switch button SW0 is pressed to enter into Idle mode.

b. The SERCOM2_I2C_CallbackRegister function call registers a callback interrupt handler with the I²C PLIB. The interrupt handler is called by the I²C PLIB when the I²C reads the temperature value from the temperature sensor.

c. The AC_CallbackRegister function call registers an AC callback interrupt handler with the AC PLIB. The interrupt handler is called by the AC PLIB when the AC comparator output is rising. The AC comparator interrupt occurs when comparing the light sensor input with reference to VDD Scaler voltage.

d. The RTC_Timer32Start function call starts the RTC timer. The event output from the RTC is fed as input to the Event System and is used to trigger the AC comparator start conversion.


After the code above, add the following lines of code to read the user input and run the application either in Power Measurement mode or Wake-Up Time Measurement mode.

for( ; ; )
{
    display_menu();

   if(cmd == 'a')
    {
        printf("\n\rEntering into Power Measurement Mode");
       break;
    }
   else if(cmd == 'b')
    {
        printf("\n\rEntering into Wakeup Time Measurement Mode");

       /* To run EIC using CPU clock through GCLK0, set the DFLLCTRL to run in standby mode and set the EIC peripheral clock source as GLCK2 */
       /* Configure DFLL    */
        SYSCTRL_REGS->SYSCTRL_DFLLCTRL = SYSCTRL_DFLLCTRL_ENABLE_Msk | SYSCTRL_DFLLCTRL_MODE_Msk | SYSCTRL_DFLLCTRL_RUNSTDBY_Msk ;
       /* Selection of the Generator and write Lock for EIC */
        GCLK_REGS->GCLK_CLKCTRL = GCLK_CLKCTRL_ID(5) | GCLK_CLKCTRL_GEN(0x0)  | GCLK_CLKCTRL_CLKEN_Msk;

       break;
    }
   else
    {
        printf("\n\rInvalid choice");
    }
}

After the code above, add the following lines of code to keep the device in Standby Sleep mode after power on reset.

printf ("\n\rEntering into Standby sleep mode");
/* Put device in Standby mode */
ac_comparison_done = false;
sleepMode = STANDBY_SLEEP_MODE;
PM_StandbyModeEnter ();

Code Example 3​​​ 

Note:
For CPU fast wake-up on EIC interrupt, run the EIC on the CPU clock. To achieve this, run the DFLL in Standby mode by configuring the DFLLCTRL and change the EIC peripheral clock source to GLCK0. This configuration is needed only when the user is not configured in MHC.

As this application can measure both the power and CPU wake-up time, the DFLL and EIC peripheral clock configurations are needed to change the run time. Add the DFLL and EIC peripheral clock configuration code snippet for fast CPU wake-up time.

You can also configure Run DFLL in Standby Sleep Mode and the EIC peripheral source clock as the CPU clock through GCLK0 in MHC.

Peripheral Clock Configuration


Implement the registered callback interrupt handlers for I²C, AC, and EIC PLIBs by adding the following code before the int main (void) function in main_d21.c.

/* Handler for I²C interrupt */
static void i2cEventHandler (uintptr_t contextHandle)
{
   /* Check that there were no error during SERCOM3_I2C read for temperature */
   if(SERCOM2_I2C_ErrorGet() == SERCOM_I2C_ERROR_NONE)
    {
        isTemperatureRead = true;
    }

   else
    {
       /* Display message on terminal */
        printf ("\n\rThere were an error during I2C Transmit. Please ensure that the I/O1 Xplained Pro is connected to the board.");
    }
}

/* Handler for AC interrupt */
void acEventHandler(uint8_t int_flag, uintptr_t ac_context)
{
    ac_comparison_done = true;

   /* Indication that a comparison is done */
    LED0_Toggle();
}

/* Handler for button switch interrupt using EIC peripheral */
static void EIC_User_Handler (uintptr_t context)
{
   /* Set sleepmode flag after interrupt from button pressure */
   if(cmd == 'a')
    {
        sleepMode = IDLE_SLEEP_MODE;
    }
}

Code Example 4


Inside the while loop, add the following lines of code to ensure that the selection is Power Measurement mode or Wake-up Time Measurement.

if(cmd == 'a')               /* Power Measurement Mode */
{
}
else                             /* Wakeup Time Measurement */
{
}

Code Example 5


Inside the power measurement mode condition, add the following code to submit an I²C transfer to read the temperature sensor value when the AC triggers an interrupt (i.e., when the user covers the light sensor). The I²C PLIB calls back the callback event handler (registered in Step 2 above) when the submitted request is complete.

if(ac_comparison_done)
{
    printf ("\n\n\n\rPA04 voltage is above detect level, Wake-up from sleep mode.");
    ac_comparison_done = false;
   /* Switch on LED0 */
    LED0_Clear ();
   /* Read the temperature sensor value from I/O1 Xplained Pro through SERCOM3 I²C */
    SERCOM2_I2C_WriteRead (TEMP_SENSOR_SLAVE_ADDR, &i2cWrData, 1, i2cRdData, 2);
    TemperatureReadInitiated = true;
}
else if (sleepMode == IDLE_SLEEP_MODE)
{
    printf ("\n\n\n\rSW0 Pressed, Wake-up from sleep mode......");
}

After the code above, add the following lines of code to prepare the received temperature value from the sensor to be printed on the serial terminal and code to transfer the buffer containing the latest temperature value in the format Temperature = XX F\r\n.

if (isTemperatureRead == true)
{
    isTemperatureRead = false;
   /* Store the temperature sensor value on temperatureVal */
    temperatureVal = getTemperature (i2cRdData);
   /* Fill uartTxBuffer variable with message containing temperature value to display on terminal */
    sprintf ((char*)uartTxBuffer, "\n\rTemperature = %02d F", temperatureVal);
   /* Check that the SERCOM3 USART is ready for new data */
   while (!SERCOM3_USART_TransmitterIsReady());
   /* Transmit the uartTxBuffer variable content to terminal through SERCOM3 USART peripheral */
    printf("%s", (char *)uartTxBuffer);
    TemperatureReadInitiated = false;
}

After the code above, add the following code to enter into Standby mode once temperature data is printed on the serial terminal and add the code snippet to enter into Idle Sleep mode when the user presses switch SW0.

if(TemperatureReadInitiated == false)
{
   /* Switch off LED0 */
    LED0_Set ();
   if (sleepMode == IDLE_SLEEP_MODE)
    {
        sleepMode = STANDBY_SLEEP_MODE;
       /* Display message on terminal */
        printf ("\n\rEntering into Idle sleep mode.");
       /* Put the device in Idle mode */
        PM_IdleModeEnter ();
    }
   else
    {
        printf ("\n\rEntering into Standby sleep mode");
       /* Put device in Standby mode */
        PM_StandbyModeEnter ();
    }
}

Code Example 6


Inside the wake-up time measurement mode condition, add the following lines of code to put the device back into Sleep mode based on the wake-up input and sleepMode flag.

if(ac_comparison_done)
{
    ac_comparison_done = false;
    printf ("\n\n\n\rPA04 voltage is above detect level, Wake-up from sleep mode......");
}
else
{
    printf ("\n\n\n\rSW0 Pressed, Wake-up from sleep mode......");
    sleepMode = ((sleepMode == IDLE_SLEEP_MODE) ? STANDBY_SLEEP_MODE : IDLE_SLEEP_MODE);
}
/* Switch off LED0 */
LED0_Set ();
if (sleepMode == IDLE_SLEEP_MODE)
{
   /* Display message on terminal */
    printf ("\n\rEntering into Idle sleep mode.");
   /* Put the device in Idle mode */
    PM_IdleModeEnter ();
}
else
{
    printf ("\n\rEntering into Standby sleep mode");
   /* Put device in Standby mode */
    PM_StandbyModeEnter ();
}

Code Example 7


In the plib_eic.c file, add #include "definitions.h" at the top. Scroll down to the EIC_InterruptHandler function and add the following lines of code at the top. This GPIO is toggled in the Interrupt Service Routine (ISR) of the SW0 press interrupt to measure wake-up time. The time between the switch press and the GPIO toggle in the ISR is the wake-up time.

WAKEUP_TEST_Toggle();

 Code Example 8
You are now ready to build the code and observe the results!

I²C and USART transfers can also be configured to run in Sleep mode using the Event System and Run in Standby options available in the peripheral.