Martin's corner on the web

Crazy high frequency PWM with ATMega32U4

Timer 4 on the ATMega32U4 is a high speed one, but I have never tried it out before. Now that I have a scope, I decided to toy with it. It can work from the internal PLL, meaning some really high frequencies can be achieved. I managed to get up to 8Mhz, but probably more can be achieved with setting the PLL to even higher frequency. I tried out frquencies from 10Hz to 8Mhz. Anything above 700Khz is less accurate (or at least I couldn’t make it better), below that the frequency generated is pretty well matched. Here are couple tests with 1.712Mhz 33%Duty:

NewFile4

123456hz, 50% Duty:

NewFile5

So overall interesting experiment, I am not sure if it has any practical value other than testing my o-scope. Here is my code, if someone wants to try it out, I tested it on a Funky v2:

#define TIMER4_RESOLUTION 1023UL
#define PLL_FREQ 48000000UL

unsigned long pwmPeriod = 0;
ISR(TIMER4_OVF_vect) {}

void enable_intr(){
    TIMSK4 = _BV(TOIE4);
}

void disable_intr(){
    TIMSK4 = 0;
}

void initialize(unsigned long freq) {

    /* Init the internal PLL */
   PLLFRQ = _BV(PDIV2);
   PLLCSR = _BV(PLLE);
   while(!(PLLCSR & _BV(PLOCK)));
   PLLFRQ |= _BV(PLLTM0); /* PCK 48MHz */

   TCCR4A = (1<<PWM4A);  
   TCCR4E = (1<<ENHC4); 
   TCCR4D = (1<<WGM40); //set it to phase and frequency correct mode    
   TCCR4C = 0;     
   setPeriod(freq);    
} 
void setPwmDuty(unsigned int duty) { 	
   unsigned long dutyCycle = pwmPeriod; 	
   dutyCycle *= duty; 	
   dutyCycle >>= 9;
   TC4H = (dutyCycle) >> 8; 
   OCR4A = (dutyCycle) & 255; 
}

void start() {
  TCCR4A |= _BV(COM4A1);
}
void stop()  {
  TCCR4A &= ~(_BV(COM4A1));
}

void setPeriod(unsigned long freq)  {
unsigned long cycles = PLL_FREQ / 2 / freq;
unsigned char clockSelectBits = 0;

        if (cycles < TIMER4_RESOLUTION) {
		clockSelectBits = _BV(CS40);
		pwmPeriod = cycles;
	} else
	if (cycles < TIMER4_RESOLUTION * 2) {
		clockSelectBits = _BV(CS41);
		pwmPeriod = cycles / 2;
	} else
	if (cycles < TIMER4_RESOLUTION * 4) {
		clockSelectBits = _BV(CS41) | _BV(CS40);
		pwmPeriod = cycles / 4;
	} else
	if (cycles < TIMER4_RESOLUTION * 8) {
		clockSelectBits = _BV(CS42);
		pwmPeriod = cycles / 8;
	} else
	if (cycles < TIMER4_RESOLUTION * 16) {
		clockSelectBits = _BV(CS42) | _BV(CS40);
		pwmPeriod = cycles / 16;
	} else 
        if (cycles < TIMER4_RESOLUTION * 32) {
		clockSelectBits = _BV(CS42) | _BV(CS41);
		pwmPeriod = cycles / 32;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 64) {
		clockSelectBits = _BV(CS42) | _BV(CS41) | _BV(CS40);
		pwmPeriod = cycles / 64;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 128) {
		clockSelectBits = _BV(CS43);
		pwmPeriod = cycles / 128;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 256) {
		clockSelectBits = _BV(CS43) | _BV(CS40);
		pwmPeriod = cycles / 256;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 512) {
		clockSelectBits = _BV(CS43) | _BV(CS41);
		pwmPeriod = cycles / 512;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 1024) {
		clockSelectBits = _BV(CS43) | _BV(CS41) | _BV(CS40);
		pwmPeriod = cycles / 1024;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 2048) {
		clockSelectBits = _BV(CS43) | _BV(CS42);
		pwmPeriod = cycles / 2048;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 4096) {
		clockSelectBits = _BV(CS43) | _BV(CS42) | _BV(CS40);
		pwmPeriod = cycles / 4096;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 8192) {
		clockSelectBits = _BV(CS43) | _BV(CS42) | _BV(CS41);
		pwmPeriod = cycles / 8192;
	} else 	
        if (cycles < TIMER4_RESOLUTION * 16384) { 		clockSelectBits = _BV(CS43) | _BV(CS42) | _BV(CS41) | _BV(CS40); 		pwmPeriod = cycles / 16384; 	}  /*else    		clockSelectBits = _BV(CS43) | _BV(CS42) | _BV(CS41) | _BV(CS40); 		pwmPeriod = TIMER4_RESOLUTION - 1; */	         TCCR4B = clockSelectBits;             TC4H = pwmPeriod >> 8; 
        OCR4C = pwmPeriod; 

    }

void setup() {

pinMode(13, OUTPUT);
digitalWrite(13,LOW);
initialize(123456);  // Frequency to generate in Hz
setPwmDuty(512);     // Duty cycle 0-1024, 1024 is 100%, 512 is 50%
start();

}
void loop(){
}

6 thoughts on “Crazy high frequency PWM with ATMega32U4

  1. Rednalexa

    What pin should this output on? I tried it on a teensy 2.0 (atmega32u4) and can’t find any output on any pin.

    If I can get this working it will be very useful for having the atmega clock an external sound chip.

    Thanks!

      1. Rednalexa

        Ah I think you mean pin 13 on your actual Funky v2 board, right? OC4A (PC7) is what you have pin 13 on your board connected to. On the teensy 2.0 it is C7 or Arduino 10. I will look for the signal there.

        Thanks!

  2. Timi

    Hi i tried the code you posted on arduino leonardo. It compiles well but i lose connection to leonardo and it is not recognized by windows. I need to upload a new empty sketch via reset to get arduino back. Please can you help.

  3. Gaston

    I try this on Pro Micro (SparkFun) and transforms my device into a brick, as mentioned before by Timi above.
    To restore my Pro Micro, I had to make the procedure listed here

    https://learn.sparkfun.com/tutorials/pro-micro–fio-v3-hookup-guide/troubleshooting-and-faq#ts-reset

    That is the port assignment and reset with a regular code with more steps so it could help.
    Do you know why this code will not work with ProMicro? It is very compatible with everything,

    The piout could be found on

    https://learn.sparkfun.com/tutorials/pro-micro–fio-v3-hookup-guide/hardware-overview-pro-micro

    Maybe if I reassign the pin to pin10 (PWM) ? I try that and still bricks my board. Any other suggestion?

    Thank you,

    Gaston