Setting timer1 for PWM

Hi,

I'm using an Arduino Leonardo (Atmega32U) to make a 3 seconds pwm with a duty cycle of 66,66 %. For this I want to use timer1.
My problem is that when I set the OCR1A register to 15624 only the low byte gets set. In the avr data sheet I see that OCR1A is a 16 bit register. How do I set all 16 bits?

Here is my code:

void setup() { 
 //Initialize serial and wait for port to open:
  Serial.begin(9600); 
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  
  // prints title with ending line break 
  
  
  cli();
  
  
  TCCR1B=0;
  TCCR1C=0;
  TCNT1=0;
  OCR1A = 15624;
  //OCR1AL = 8;
  TCCR1A= 0b11000000;
  //TCCR1A= 0b00000000;
  TCCR1B = 0b00010101;
  ICR1 = 23438;
  TCNT1 = 0;
  TIMSK1 |= (1 << TOIE1) | (1 << OCIE1A); //| (1 << ICIE1);
  pinMode(9, OUTPUT);

  sei();
  
  Serial.println("test"); 
} 

int inter1 = 0;
int inter2 = 0;

ISR(TIMER1_OVF_vect)
{

}

ISR(TIMER1_COMPA_vect)
{
  //Serial.print("Bau\n");
  Serial.print(OCR1A);
  Serial.print("\n");
  
  //sei();
  inter2++;
}

void loop() { 
  delay(2000);
  
//  Serial.print(inter2);
//  Serial.print("\n");
}

TIMER1_COMPA_vect always prints 8 which is the low byte of 15624. How can I set this value right to OCR1A ?

Have you read the datasheet ?
Atmel furnishes explanations and exemples how to adress 16 bits register in assembler and in C on a 8bit microcontroler.

this is an example from the data sheet:

unsigned int i;
...
/* Set TCNTn to 0x01FF */
TCNTn = 0x1FF;
/* Read TCNTn into i */
i = TCNTn;
...

As far as I can see this is a simple assignment Register = value;
Which is what I do also in the code.
Setting ICR1 register works fine because the overflow interrupt is generated every 3 seconds.
What is the problem with setting OCR1A ?
How can I get the assembly code of my sketch? If I try to disassemble the .elf file I can't find any place where the OCR1A register is set.

I'm trying to set OCR1A using asm. This is the code:

 __asm__("ldi r17,0x3D\n\t");
  __asm__("ldi r16,0x08\n\t");
  __asm__("out 0x89,r17\n\t");
  __asm__("out 0x88, r16\n\t");

But I get this error:
/tmp/ccjceTpG.s: Assembler messages:
/tmp/ccjceTpG.s:207: Error: number must be positive and less than 64
/tmp/ccjceTpG.s:213: Error: number must be positive and less than 64

Do you know why ?

The 'out' instruction only works on the lower 64 registers.
You will have to use the 'sts' instruction instead.

You can also write it like this (to make it more readable)

  __asm__ (
    "ldi r17,hi8(%2)  \n\t"
    "ldi r16,lo8(%2)  \n\t"
    "sts  %0,r17      \n\t"
    "sts  %1,r16      \n\t"
    :
    : "M" (_SFR_IO_ADDR(OCR1AH)), "M" (_SFR_IO_ADDR(OCR1AL)), "i"(15624)
    : "r17", "r16" //tell the compiler you have used these (and they are not inputs/outputs)
  );

Where:

  • %0, %1, and %2 are replaced by three values on the input list in order (i.e. %0 is the address of OCR1AH, etc.).
  • The hi8() and lo8() get the upper 8 bits and lower 8 bits of a 16bit integer constant (supplied using the "i" type).
  • "sts" is used as the address of the register is >=64.
  • _SFR_IO_ADDR() gets the address of a register
  • You have 'clobbered' two registers here, so you must tell the compiler otherwise it might use them to store a number either side of your block and a calculation could get corrupted.

Alternatively, you can do this:

  unsigned int value = 15624; //let the compiler pick the registers and generate the ldi instructions
  __asm__ (
    "sts  %0,%B2      \n\t" //%B2 is the upper register
    "sts  %1,%A2      \n\t"  //%A2 is the lower register
    :
    : "M" (_SFR_IO_ADDR(OCR1AH)), "M" (_SFR_IO_ADDR(OCR1AL)), "r"(value) //then pass in the value using an "r"()
    : 
  );

Or even just this:

  OCR1A = 15624;

I think I found something. My code was this:

OCR1A = 15624;
//OCR1AL = 8;
TCCR1A= 0b11000000;
//TCCR1A= 0b00000000;
TCCR1B = 0b00010101;

If I set OCR1A after I set TCCR1A then I get the correct value when I print OCCR1A:

//OCR1AL = 8;
  TCCR1A= 0b11000000;
  //TCCR1A= 0b00000000;
  OCR1A = 15624;
  TCCR1B = 0b00010101;

Any idea why this is happening ?

The OCR registers on Timer 1 are double buffered to avoid glitches in the PWM waveform when changing them. You need to set the PWM mode before updating the OCR registers otherwise in certain circumstances the value you wrote to OCR gets lost (some modes use double buffering, others don't).

bogdanul2003:
Hi,

I'm using an Arduino Leonardo (Atmega32U) to make a 3 seconds pwm with a duty cycle of 66,66 %. For this I want to use timer1.
My problem is that when I set the OCR1A register to 15624 only the low byte gets set. In the avr data sheet I see that OCR1A is a 16 bit register. How do I set all 16 bits?

I had this problem once, it's nothing to do with how the register is accessed.

The timer has PWM modes that use less bits (see the table in "TCCR1A"). If one of those is selected then writing to OCR1A doesn't affect all the bits in OCR1A.

Guess what the Arduino library does at startup...?

To write all 16 bits you have to first set TCCR1A to 0 (or any 16-bit mode).