3.2 Configure the CFD to Detect Failures on the Main Clock

Todo:
  • Edit the CLOCK_CFD_CLKMAIN_init() function so that the CFD is set up to monitor the main clock and generates an NMI upon clock failure
  • Set up the NMI handler
  • Create a main clock failure using the software test
  1. Enable the CFD and set the main clock as the source by adding:
    ccp_write_io((uint8_t *) &CLKCTRL.MCLKCTRLC, CLKCTRL_CFDSRC_CLKMAIN_gc
    	             | CLKCTRL_CFDEN_bm);
    
    to CLOCK_CFD_CLKMAIN_init().
  2. Enable the CFD interrupt and set the interrupt type to NMI by writing:
    ccp_write_io((uint8_t *) &CLKCTRL.MCLKINTCTRL, CLKCTRL_INTTYPE_bm
    	             | CLKCTRL_CFD_bm);
    Info: The CFD can create a normal interrupt or non-maskable interrupt depending on the setting in the Interrupt Type (INTTYPE) bit in the Main Clock Interrupt Control (CLKCTRL.MCLKINTCTRL) register. The advantage with an NMI is that the interrupt will trigger even if the microcontroller is in an interrupt or the global interrupt is disabled. This ensures that the interrupt will be executed straight away, and the error can be handled straight away. The only way to exit an NMI is by a device reset.
  3. In the ISR(NMI_vect), add the code to check if a CFD triggered the NMI and to blink the LED:
    if(CLKCTRL.MCLKINTFLAGS & CLKCTRL_CFD_bm)
    	{
        /* This interrupt will trigger if the source for the main clock fails
    	 * and the CFD is able to switch to a different working clock.
    	 * In this case that means XOSCHF has failed and is replaced by OSCHF.
    	 * The main clock is therefore reduced to 4 MHz.
    	 */
    
    	/* Toggle the LED forever */
    	while(1)
    	{
    		LED0_toggle();
    		_delay_ms(200); // 200 ms calculated from 16 MHz == 800 ms
    	}
    
    	}
    	else
    	{
    		/* A different NMI has been triggered */
    	}
    Info: There is only one interrupt vector for all the NMIs, so to be sure which NMI caused the interrupt, check the respective interrupt flags.
  4. In the ISR(PORTB_PORT_vect), add the code to trigger a clock failure when SW0 is pressed:
    ccp_write_io((uint8_t *) &CLKCTRL.MCLKCTRLC, CLKCTRL.MCLKCTRLC | CLKCTRL_CFDTST_bm);
    Info: When setting the Clock Failure Detection Test (CFDTST) bit in the Main Clock Control C (MCLKCTRL) register, hardware simulates a clock failure to test the CFD feature.
  5. Verify that the solution/project builds by selecting the BuildBuild Solution from the top menu bare in Atmel Studio or by pressing the F7 key.
  6. Flash the device by selecting the DebugStart without debugging from the top menu bar in Atmel Studio or by pressing the Ctrl+Alt+F5 keys.
Result: After pressing SW0, the LED0 will start blinking with a lower frequency. This because the main clock source is switched to the start-up clock source, and the Main Clock Control B (CLKCTRL.MCLKCTRLB) register will be written to the reset value when a clock failure is detected. The number of clock ticks needed to create the delay_ms(200) is calculated using the XOSCHF clock frequency of 16 MHz, resulting in the delay_ms being four times as slow when the clock source is switched to the 4 MHz internal clock.
Info: The start-up clock source is decided by the Clock Select (CLKSEL) bit field in the Oscillator Configuration (OSCCFG) fuse.
Warning: As the MCLKCTRLB register is written to its default values by the CFD, the clock will not be available on the CLKOUT pin until it is set by the user again.