3 Generate Two Fixed Frequency PWM Signals with Variable Duty Cycle

Use case description: Configure the TCF to generate an overflow event on the compare registers at a fixed frequency while increasing the waveform duty cycle to all available steps.

Result: The TCF will generate an output on the Channel 0 and Channel 1 at a fixed frequency, while the waveform duty cycle will increase.

Embedded Bare Metal Implementation

  1. Configuring the Output Signals

    Enable Waveform Output 0 and Waveform Output 1.

    Figure 3-1. CTRLC Register
    The CTRLC register is double buffered. It is recommended to wait in a loop for bit CTRLCBUSY in the Status register to be cleared every time the CTRLC register is written. 
    Create a function called TCF0_OutputsSet that returns void and takes an argument of type uint8_t.
    void TCF0_OutputsSet(uint8_t value)     
        {      
            while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0){};
            TCF0.CTRLC = value;
        }
  2. Configuring the TCF Clock

    The AVR16EB32 board runs default with a system clock of 3.33 MHz. For the TCF to run at 20 MHz , the system-clock prescaler must be disabled.

    Create a function take returns void and take an argument of type void.
    void CLOCK_Initialize(void)
    {
        _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x0);
    }
    Figure 3-2. CTRLB Register
    Figure 3-3. Clock Select Bit Mask

    The TCF provides a range of clock options to choose from. For this application select the default option, which is the Peripheral Clock running at 20 MHz. The CTRLB register controls the clock selection.

    Create a function that returns void and takes an argument of type TCF_CLKSEL_t.
    void TCF0_ClockSet(TCF_CLKSEL_t config)
    {
        TCF0.CTRLB |= config;    
    }

    The Prescaler is disabled in this mode.

  3. Configuring the Waveform Generation Mode

    The mode in which the timer works is selected using the CTRLB register. NCOPF will be selected.

    Figure 3-4. Waveform Generation Mode Bit Mask
    Create a function that returns void and takes an argument of type TCF_WGMODE_t.
    void TCF0_ModeSet(TCF_WGMODE_t mode)
    {    
        TCF0.CTRLB |= mode;
    }
  4. Setting the Frequency

    After configuring the TCF clock, choose the frequency at which the timer will operate - 125 kHz using the CMP register.

    Figure 3-5. CMP Register
    Using Equation 1, the Increment can be calculated as follows: I n c r e m e n t =    f F R Q ( H z ) ×    2 S I Z E _ C N T    T C F c l o c k ( H z )

    Using the desired values results in:

    I n c r e m e n t =    125.000 × 16.777.216    20.000.000 = 104 , 857.6

    The hexadecimal result is 0x0001999A.

    The above formula cand be translated into the formula:
    #define TCF0_NCOPL_HZ_TO_INCREMENT(HZ, F_CLOCK)   (uint32_t)(((float)(HZ) * 16777216.0) / (float)(F_CLOCK) + 0.5)

    The CMP register is double buffered. It is recommended to wait in a loop for bit CMP0BUSY in the Status register to be cleared every time the CMP register is written.
    void TCF0_CompareSet(uint32_t value)
    {
        while((TCF0.STATUS & TCF_CMP0BUSY_bm) != 0){};
        TCF0.CMP = value; 
    }
    void TCF0_CounterSet(uint32_t value)
    {
        while((TCF0.STATUS & TCF_CNTBUSY_bm) != 0){};   
        TCF0.CNT0 = (uint8_t)value;  
    }
  5. Configuring the Waveform Generation Pulse Length
    Figure 3-6. CTRLC Register
    Figure 3-7. Waveform Generation Pulse Length Bit Mask

    Set the pulse length to be a single clock increment, which is 50 ns. 1 clock cycle takes 50 ns beacuse the timer is running at 20 MHz. 1 divided by 20 MHz equals 50 ns.

    void TCF0_NCO_PulseLengthSet(TCF_WGPULSE_t config)
    {
        uint8_t temp; 
        while((TCF0.STATUS & TCF_CTRLCBUSY_bm) != 0){};       
        temp = (TCF0.CTRLC & ~TCF_WGPULSE_gm) |
                   (  config   &  TCF_WGPULSE_gm); 
        TCF0.CTRLC = temp;
    }
  6. Start and Stop the Timer
    Figure 3-8. CTRLA Register
    void TCF0_Start(void)
    {
        while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0){};
        TCF0.CTRLA |=  TCF_ENABLE_bm;
    }
    void TCF0_Stop(void)
    {
        while((TCF0.STATUS & TCF_CTRLABUSY_bm) != 0){};
        TCF0.CTRLA &= ~TCF_ENABLE_bm;   
    }

    The TCF will be initialized with the following settings:

    • Both outputs enabled
    • Peripheral clock selected
    • NCO Pulse-Length mode set
    • Set a frequency of 125 kHz
    • Set a waveform generation pulse length of 1 clock cycle
    • Set the timer to start counting from 0
    Create a function that returns void and takes an argument of type void.
    void TCF0_Initialize(void)
    {   
       TCF0_OutputsSet(TCF_WO0EN_bm | TCF_WO1EN_bm);
       TCF0_ClockSet(TCF_CLKSEL_CLKPER_gc);
       TCF0_ModeSet(TCF_WGMODE_NCOPF_gc);
       TCF0_CompareSet(TCF0_NCOPL_HZ_TO_INCREMENT(125000, 20000000)); 
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK1_gc);
       TCF0_CounterSet(0);
    }
    The util/delay.h header file requires to define the F_CPU frequency.
    #define F_CPU 20000000UL
    Include the util/delay.h header file
    #include <util/delay.h>
    Create a function that returns void and takes an argument of type void. Use the TCF0_NCO_Pulse_Length_Demo function to change the pulse-length with a delay in-between the changes. 
    void NCO_Pulse_Length_Demo(void)
    {
        /* Configure the TCF to start counting from 0 */
        TCF0_CounterSet(0);
        
        /* Enable the TCF */
        TCF0_Start(); 
        
        /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 2 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK2_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 4 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK4_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 8 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK8_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 6 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK16_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 32 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK32_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 64 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK64_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 128 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK128_gc);
       
       /* Delay for 25 us */
       _delay_us(25);
       
       /* Stop the timer */
       TCF0_Stop();
       
       /* Configure the pulse-length to 1 clock cycle */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK1_gc);
    }
    The main function looks like this:
    void main(void) 
    {  
        CLOCK_Initialize();
        TCF0_Initialize();
        while(1)
        {
            NCO_Pulse_Length_Demo();
            _delay_ms(20);
        }    
    }

MCC Melody Implementation

To generate this project using MPLAB Code Configurator, MCC Melody (MCC Classic is not supported), follow the next steps:
  1. Create a new MPLAB® X IDE project for AVR16EB32.
  2. Open MCC from the toolbar find more information on installing the MCC plug-in here.
  3. In MCC Content Manager Wizard, select MCC Melody, then click Finish.
  4. From Device Resources, go to System, click the CLCKCTRL window and disable the Prescaler.
  5. From Device Resource, go to Drivers, click the Timer window, add the TCF module, then do the following configuration:

    – Clock Divider: System clock (by default, the divider will be 1 - System clock)

    – Waveform Generation Mode: NCO Pulse-Length mode

    – Waveform Generation Pulse Length: 1 Clock Period

    – Requested Period[s]: 0.000008

    – Waveform Output n: check the boxes from the Enable column for Waveform Output 0 and Waveform Output 1

  6. Select the PA0 and PA1 pins in the Pin Grid View. When the boxes from Enable column and Waveform Output n are checked, the pins are also locked. To change the PORT, click on a pin from another PORT in Pin Grid View.
  7. In the Project Resources window, click the Generate button so that MCC will generate all the specified drivers and configurations.
  8. Edit the main.c file,as following:

    Include the util/delay.h header file.

    #include <util/delay.h>

    Create a function called NCO_Pulse_Length_Demo. Use the TCF0_NCO_PulseLengthSet function to change the pulse length.

    The code is as follows:
    void NCO_Pulse_Length_Demo(void)
    {   
         /* Configure the TCF to start counting from 0 */
        TCF0_CounterSet(0);
        
        /* Enable the TCF */
        TCF0_Start(); 
        
        /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 2 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK2_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 4 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK4_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 8 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK8_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 6 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK16_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 32 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK32_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 64 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK64_gc);
       
       /* Delay for 20 us */
       _delay_us(20);
       
       /* Configure the pulse-length to 128 clock cycles */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK128_gc);
       
       /* Delay for 25 us */
       _delay_us(25);
       
       /* Stop the timer */
       TCF0_Stop();
       
       /* Configure the pulse-length to 1 clock cycle */
       TCF0_NCO_PulseLengthSet(TCF_WGPULSE_CLK1_gc);
    }
  9. The main function looks like this:
    int main(void)
    {
        SYSTEM_Initialize();
    
        while(1)
        {
            NCO_Pulse_Length_Demo(); 
            _delay_ms(20);
        }    
    }
  10. Flash the project.
  11. Result:

TCF generates two identical signals with a frequency of 125 kHz and PWM with a variable duration ranging from 1 to 128 clock cycles. In this case, one clock cycle takes 50 ns.

Figure 3-9. Result