Search code examples
arduinoavratmegaatmel

Arduino timer4 custom PWM issue


I made a nice code which generates fast PWM with 50% duty cycle and I can change the frequency with a potentiometer. It outputs straight and inverted channels with some dead time. I am using Arduino Micro aka ATmega32U4. The code is actually "Atmel" code. Code is working fine until I power Arduino Micro off and then on again.

I have programmed the code and registers so that the frequency is changeable from 10kHz to 100kHz. But after power on/off the frequency changes from 5kHz to 50kHz. After this has happened I have to program the board again using Arduino IDE, to make it work correctly. Again after power on/off it has changed. I am quite sure that one of the registers is overwritten by the "Arduino hardware abstraction layer" or however we should name it. I have not yet read out all the registers so I do not know which one is overwritten. I guess it's the prescaler. How do I prevent this from happening? Should I write the register contents somewhere else? Or should I write it few times to be sure? Why or how this is happening anyway?

Here's the code:

#define OSC1  5
#define OSC2  13

uint8_t read_reg1;
uint8_t read_reg2;
int pot, freq; 

void setup() {
  pinMode(OSC1, OUTPUT); 
  pinMode(OSC2, OUTPUT); 
  Serial.begin(9600);

  cli();  // disable global interrupts

  TCCR4A=0; // clear register
  TCCR4B=0x06; // configure prescaler to 64 (CK = CLK / 64 = 1.5 MHz)
  TCCR4C=0;
  TCCR4D=0; // select Fast PWM operation (0 << WGM41)|(0 << WGM40)
  PLLFRQ=(PLLFRQ&0xCF)|0x30; // select clock source and frequency
  OCR4C=150; // select PWM frequency
  OCR4A=150/2; // set duty cycle
  DT4 = 0x55; // set dead times. DT = (1 / 48Mhz) * 0...15

  // enable interrupt on timer4 overflow
  TIMSK4|=(1 << TOIE4);

  // This register write has to be after others. Otherwise the PWM generation will not work. I do not know why. 
  TCCR4A=0x42; // COM4A1..0 = 01, OC4A and !OC4A connected. PWM4A = 1 (activate channel A PWM output)

  sei(); // enable global interrupts
}

void loop() {
  //cli();
  pot = analogRead(A0);
  freq = map(pot, 0, 1023, 14, 166);
  //sei();
  /*
  Serial.print("Pot value: ");
  Serial.print(pot);
  Serial.print("\tFreq value: ");  
  Serial.println(1500000/freq);
  */

}

ISR(TIMER4_OVF_vect){
  OCR4C = freq;
  OCR4A = freq / 2; 
}

Solution

  • I am not sure exactly why you got different behavior right after programming, but the bootloader that the Arduino Micro uses (Caterina) does not perform a full reset after it runs, so changes that the bootloader made to the AVR's registers are often visible to the user's sketch.

    I was able to fix the problem by removing the line that modifies PLLFRQ. Here is a simplified version of your code that always produces 3.31 kHz PWM:

    void setup()
    {
      pinMode(5, OUTPUT); 
      pinMode(13, OUTPUT);
    
      TCCR4A = 0;
      TCCR4B = 0x06; // configure prescaler to 64 (CK = CLK / 64 = 1.5 MHz)
      TCCR4C = 0;
      TCCR4D = 0; // select Fast PWM operation (0 << WGM41)|(0 << WGM40)
      OCR4C = 150; // select PWM frequency
      OCR4A = 150 / 2; // set duty cycle
      DT4 = 0x55; // set dead times. DT = (1 / 48Mhz) * 0...15
    
      // This register write has to be after others.
      // Otherwise the PWM generation will not work. I do not know why. 
      // COM4A1..0 = 01, OC4A and !OC4A connected.
      // PWM4A = 1 (activate channel A PWM output)
      TCCR4A = 0x42;
    }
    
    void loop()
    {
    }
    

    It's not a great idea to mess with the PLL postscaler since it will probably affect every other Arduino library that uses timers, including the USB stack.