Initialization

First, initialize the peripherals to be used: TCD, CCL, AC and OPAMP.

Start with setting up the OPAMP as two cascaded noninverting Programmable Gain Amplifiers (PGA), with a total gain of 60. This gain is chosen based on the input signal produced by the current draw of the fan motor used during development. Depending on the current draw, a higher or lower gain can be chosen to fit your application. To lower noise, the first op amp in the cascade is set to maximum, that is 16x, while the second op amp can be adjusted to application needs, preferably to a gain that results in a 1-2V range output for ease of detection. If a different setup is used, please refer to the AVR128DB48 Data Sheet for gain calculation. The following table shows possible gain settings for OPAMP.OP2RESMUX.

Table 1. Gain Settings for OPAMP.OP2RESMUX
Group Configuration Resulting Total Gain
OPAMP_OP2RESMUX_MUXWIP_WIP0_gc ~17
OPAMP_OP2RESMUX_MUXWIP_WIP1_gc ~18
OPAMP_OP2RESMUX_MUXWIP_WIP2_gc ~21
OPAMP_OP2RESMUX_MUXWIP_WIP3_gc ~32
OPAMP_OP2RESMUX_MUXWIP_WIP4_gc ~43
OPAMP_OP2RESMUX_MUXWIP_WIP5_gc ~64
OPAMP_OP2RESMUX_MUXWIP_WIP6_gc ~128
OPAMP_OP2RESMUX_MUXWIP_WIP7_gc ~256
void opamp_init(void)
{
	/*Disable input on op amp output pin*/
	PORTD.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc;
       PORTE.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc;
	
	/*Set up op amp*/
	OPAMP.CTRLA = OPAMP_ENABLE_bm;
	OPAMP.TIMEBASE = (uint8_t) ceil(CLK_PER*0.000001)-1; /*Number of peripheral clock cycles that amounts to 1us*/
	
    //OP2 setup
    OPAMP.OP2CTRLA = OPAMP_RUNSTBY_bm | OPAMP_ALWAYSON_bm | OPAMP_OP2CTRLA_OUTMODE_NORMAL_gc;
    OPAMP.OP2SETTLE = OPAMP_MAX_SETTLE_TIME; //As the settle time is unknown, the maximum should be set
    OPAMP.OP2INMUX = OPAMP_OP2INMUX_MUXNEG_WIP_gc | OPAMP_OP2INMUX_MUXPOS_LINKOUT_gc;
    OPAMP.OP2RESMUX = OPAMP_OP2RESMUX_MUXWIP_WIP5_gc | OPAMP_OP2RESMUX_MUXBOT_GND_gc|OPAMP_OP2RESMUX_MUXTOP_OUT_gc;
    
    //OP1 setup
    OPAMP.OP1CTRLA = OPAMP_RUNSTBY_bm | OPAMP_ALWAYSON_bm | OPAMP_OP1CTRLA_OUTMODE_NORMAL_gc;
    OPAMP.OP1SETTLE = OPAMP_MAX_SETTLE_TIME; //As the settle time is unknown, the maximums should be set
    OPAMP.OP1INMUX = OPAMP_OP1INMUX_MUXNEG_WIP_gc | OPAMP_OP1INMUX_MUXPOS_INP_gc;
    OPAMP.OP1RESMUX = OPAMP_OP1RESMUX_MUXWIP_WIP7_gc | OPAMP_OP1RESMUX_MUXBOT_INN_gc | OPAMP_OP1RESMUX_MUXTOP_OUT_gc;
}

Then, set up the TCD to drive the PF0 pin, and to shut down on a failure event. Currently, the TCD is not enabled. However, it will be started later on.

void tcd_init(void)
{
    PORTMUX.TCDROUTEA = PORTMUX_TCD0_ALT2_gc;
    PORTF.DIRSET=PIN0_bm;

    TCD0.CTRLB = TCD_WGMODE_TWORAMP_gc;
    TCD0.CMPASET = 0; /* Compare A Set: 0 */
    TCD0.CMPACLR = 1001; /* Compare A Clear: 1001 */
    TCD0.CMPBSET = 0; /* Compare B Set: 0 */
    TCD0.CMPBCLR = 1000; /* Compare B Clear: 1000 */

    ccp_write_io((void*)&(TCD0.FAULTCTRL),1 << TCD_CMPAEN_bp /* Compare A enable: enabled */
    	 | 0 << TCD_CMPA_bp /* Compare A value: disabled */
    	 | 0 << TCD_CMPB_bp /* Compare B value: disabled */
    	 | 0 << TCD_CMPBEN_bp /* Compare B enable: disabled */
    	 | 0 << TCD_CMPC_bp /* Compare C value: disabled */
    	 | 0 << TCD_CMPCEN_bp /* Compare C enable: disabled */
    	 | 0 << TCD_CMPD_bp /* Compare D vaule: disabled */
    	 | 0 << TCD_CMPDEN_bp /* Compare D enable: disabled */);

    TCD0.EVCTRLA = TCD_CFG_ASYNC_gc     /* Neither Filter nor Asynchronous Event is enabled */
	| TCD_ACTION_FAULT_gc  /* Event trigger a fault */
	| TCD_EDGE_RISE_HIGH_gc /* The falling edge or low level of event generates retrigger or fault action */
	| 1 << TCD_TRIGEI_bp;  /* Trigger event enable: enabled */

	TCD0.INPUTCTRLA=TCD_INPUTMODE_WAITSW_gc; //Wait for a reset command
	TCD0.INPUTCTRLB=TCD_INPUTMODE_WAITSW_gc; //Wait for a reset command
    
	while ((TCD0.STATUS & TCD_ENRDY_bm) == 0); // Wait for Enable Ready to be high.

	TCD0.CTRLA = 0 << TCD_ENABLE_bp      /* Enable: disabled */
	| TCD_CLKSEL_OSCHF_gc   /*  */
	| TCD_CNTPRES_DIV1_gc   /* Sync clock divided by 1 */
	| TCD_SYNCPRES_DIV1_gc;
	

}

The AC needs to be set up to trigger at a suitable level. First, set it to trigger at some low level, here 1.1V. This level will later be automatically calibrated to suit the motor at hand.

void ac_init(void)
{
	PORTA.DIRSET=PIN7_bm;
	VREF.ACREF = VREF_REFSEL_4V096_gc;

	AC0.MUXCTRL = 0 << AC_INVERT_bp     /* Invert AC Output: disabled */
	| AC_MUXNEG_DACREF_gc /* DAC Reference */
	| AC_MUXPOS_AINP2_gc; /* Positive Pin 0 */


	AC0.DACREF = ac_calculate_trigger_voltage(AC_TRIGGER_VOLTAGE_MV_INIT); /* DAC Voltage Reference: 0x64 */

	AC0.CTRLA = 1 << AC_ENABLE_bp /* Enable: enabled */
	| AC_HYSMODE_NONE_gc   /* No hysteresis */
	| AC_POWER_PROFILE0_gc /* Power profile 0, lowest consumption and highest response time. */
	| 1 << AC_OUTEN_bp     /* Output Buffer Enable: enabled */
	| 0 << AC_RUNSTDBY_bp; /* Run in Standby Mode: disabled */
}

The helper function ac_calculate_trigger_voltage(uint16_t mV) is used to set the AC trigger. It calculates the DACREF value corresponding to a trigger level in millivolt.

uint8_t ac_calculate_trigger_voltage(uint16_t mV)
{
	uint8_t triggerVoltage = (((uint32_t)mV*256)/VREF_AC_MV);
	return triggerVoltage;
}

To keep the overcurrent signal active until reset, the CCL LUT0 and LUT1 form an RS-latch with the AC0 output as S input and Software Event A as R input as shown in Figure 1.

void ccl_init(void)
{
    CCL.SEQCTRL0 = CCL_SEQSEL_RS_gc; // Create a RS latch
    /*Set up LUT0*/
    CCL.LUT0CTRLB = CCL_INSEL0_AC0_gc; // AC0 and mask all other bits
    CCL.TRUTH0 = 0x02; // When AC is hight hight output, otherwise low output
    CCL.LUT0CTRLA = CCL_ENABLE_bm | CCL_OUTEN_bm;
    PORTA.DIRSET = PIN3_bm;
    
    /*Set up LUT1*/
    EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL1_gc;
    CCL.LUT1CTRLB = CCL_INSEL0_EVENTA_gc; // Event A as input and mask all other bits
    CCL.TRUTH1 = 0x02; // When Event A is high, we have a high output, otherwise low output
    CCL.LUT1CTRLA = CCL_ENABLE_bm;
    
    CCL.CTRLA = CCL_ENABLE_bm;
}

Lastly, the button and debug signals showing the AC event level are set up.

void button_init(void)
{
	PORTB.DIRCLR=PIN2_bm;
	PORTB.PIN2CTRL=PORT_ISC_FALLING_gc | PORT_PULLUPEN_bm;
	sei();
}
	button_init();
    PORTC.DIRSET = PIN4_bm | PIN5_bm;
    PORTC.OUTCLR = PIN4_bm | PIN5_bm;
    PORTD.DIRSET = PIN2_bm;