Using Timer1 to make my own PWM - OC1A not working properly?

Hello all,

Long story short, I want to write my own code to do PWM on pin 9 of an Arduino Ethernet. I've been reading a lot of the ATmega168/328 datasheet sections that deal with PWM - especially PWM on Timer1. I want to control a serv with a refresh rate of 200 hz (I called the company and they said their servo can refresh anywhere from 50-250 hz and the faster the better). I currently believe I have code written to output at 50 HZ. I'm using the phase and freq corrected PWM with ICR1 as my TOP for PWM.

Registry in Binary

TCCR1A = 1 1 0 0 0 0 0 0
TCCR1B = 0 0 0 1 0 1 0 0

This gives me
WGM1 = 8 - Phase and freq corrected PWM with ICR1 as top
COM1A = 3 - inverted PWM
CS = 256 - prescaler used to give me 50 Hz (Question about this at the bottom of the post).

NOTE: The code below is a blend of code I found online and what I've written. the stuff commented out is stuff I found online but I keep it for reference. I also haven't changed the first few lines to re describe what the code does.

//Code example for Arduino Uno by Bart Borghuis
//This program drives a servo motor using timer0 interrupts at 50kHz.
//Receipt of character '0' through the serial port sets the motor in the 'closed' position
//defined by motorstate0. Receipt of '1' sets the motor in the 'open' position defined by motorstate1.

#include <avr/interrupt.h> 
//storage variables
boolean PWM_State=0;
int startbyte, PWM_On=5, PWM_Count=0, PWM_Max=10;
byte ledPin=13;


   
void setup(){
  Serial.begin(9600); 
  //set pins as outputs
  pinMode(9, OUTPUT); 

  cli();//stop interrupts
 
  //set timer0 interrupt at 2kHz
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 50khz increments
  ICR1 = 0x0271;
  // turn on phase and freq corect PWM mode
  TCCR1A = 0xC0;
  // Set CS11 and CS10 bits for 64 prescaler
  TCCR1B = 0x14;   
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  OCR1A=0;
  sei();//allow interrupts

}//end setup
 
void loop(){
//  if (Serial.available() > 0) {
//      startbyte = Serial.read();
//      switch (startbyte){
//          case 48:
//            motorenabled=1;
//            digitalWrite(ledPin, LOW);
//            motorstate=0;
//            break;  
//          case 49: 
//            motorenabled=1;
//            digitalWrite(ledPin, HIGH);
//            motorstate=1;
//            break;
//     }     
//   }
}
 
 
//ISR(TIMER1_COMPA_vect){
////timer1 interrupt 50Hz to drive servo motor connect to pin 9
//  PWM_Count++;
//  if(PWM_Count < PWM_On){
//    digitalWrite(9,LOW);
//  }
//  if(PWM_Count>=PWM_On){
//    digitalWrite(9,HIGH);
//  }
//  if(PWM_Count>=PWM_Max){
//    PWM_Count=0;
//  }
//}

When I hooked up an Oscope to pin 9, I saw a pulse width of ~150us at ~50Hz. I then set OCR1A = to 100 and I noticed that my freqency of said pulses increased from 50 Hz to 100 Hz but the pulse width did not grow larger - it was still ~150 us. I thought that pin 9 was supposed to go high when TCNT1=OCR1A on the upcount and go low when TCNT1=OCR1A on the downcount, thus giving me PWM. I was going to vary OCR1A to create my PWM.

Everything that I've read says the COM1A will determine what happens on the OC1A pin - pin 9 - and the PWM isn't showing up like I thought it would. What exactly am I missing here?

The code that is commented out at the bottom was trying to keep tract of how often the interrupt happens when OCR1 = TCNT1 and if pin 9 is high, go low, and vice versa.

Prescaler question:
So I know I can set ICR to top, but what is the most I can set ICR to? I ask because right did some math and found out that if my prescaler is 256, then the top will be 0x0271 (625 decimal). However, I believe ICR1 is a 16bit registry, so does that mean it can go up to 0xFFFF (65535 dec)? If so, I could use a smaler prescaler, which gives me better resolution...? If so, I could use a prescaler of 8 and set ICR1 to 0x4E20 (20000 dec) for 50 Hz.

The register settings look correct to me, although I would not enable the interrupt is I wasn't going to use it. You don't show the code where you changed OCR1A. If you changed ICR1 (TOP) accidentally, that would change your frequency

"Prescaler question:
So I know I can set ICR to top, but what is the most I can set ICR to? I ask because right did some math and found out that if my prescaler is 256, then the top will be 0x0271 (625 decimal). However, I believe ICR1 is a 16bit registry, so does that mean it can go up to 0xFFFF (65535 dec)? If so, I could use a smaler prescaler, which gives me better resolution...? If so, I could use a prescaler of 8 and set ICR1 to 0x4E20 (20000 dec) for 50 Hz. "

Yes. For the 16-bit Timer/Counter you can set TOP to 65545 (0xFFFF). If you use the smaller pre-scale you will get more PWM resolution.

So to change OCR1A, i just wrote it in the code. The first image attached will be OCR1A = 0

 TIMSK1 |= (1 << OCIE1A);
  OCR1A=0;
  sei();//allow interrupts

The second will be OCR1A = 100.

 TIMSK1 |= (1 << OCIE1A);
  OCR1A=100;
  sei();//allow interrupts

I would not enable the interrupt is I wasn't going to use it

That is TIMSK1 correct? My other thought was to somehow get a pin to work off of that interrupt some home by setting it to 50 Hz then make a fancy ISR for it. Something like

//ISR(TIMER1_COMPA_vect){
////timer1 interrupt 50Hz to drive servo motor connect to pin 9
//  PWM_Count++;
//  if(PWM_Count < PWM_On){
//    digitalWrite(9,LOW);
//  }
//  if(PWM_Count>=PWM_On){
//    digitalWrite(9,HIGH);
//  }
//  if(PWM_Count>=PWM_Max){
//    PWM_Count=0;
//  }
//}

Instead that just made my pin 5V the entire time.

EDIT: Forgot to add my follow up prescaler question.
So does that mean that I can also set OCR1A anywhere from 0x0-0xFFFF too?

Another side note, whats the short hand for dec and binary notation? I know hex is 0x. I'm guessing dec has no prefix and binary would be something like BV(101010100). I was just wondering.

So I changed where Im updating OCR1A. Originally it was in the setup mode, but that gave me my problems. I didn't have interrupts allowed when I was updating. Does OCR1A require an interrupt to update? I want to say yes, but I'm not posative. Anyways, I added this as my loop and I'm getting somewhere now :smiley:

void loop(){
//  if (Serial.available() > 0) {
//      startbyte = Serial.read();
//      switch (startbyte){
//          case 48:
//            motorenabled=1;
//            digitalWrite(ledPin, LOW);
//            motorstate=0;
//            break;  
//          case 49: 
//            motorenabled=1;
//            digitalWrite(ledPin, HIGH);
//            motorstate=1;
//            break;
//     }     
//   }
  now = millis();

  time_diff = now - lastTime;

  if( time_diff > 100 ) // 500 should be 1/2 second
  {
    Serial.print("OCR1A is: ");
    Serial.println(OCR1A);
    if (OCR1A_direction = 1)
    {
      OCR1A += 10;
      if (OCR1A >= 499) 
      {
        OCR1A_direction = 0;
      }
    }
    if (OCR1A_direction = 0)
    {
      OCR1A -= 10;
      if (OCR1A <= 99) 
      {
        OCR1A_direction = 1;
      }
    } 
   lastTime = now; 
}
}

The code for the set up of registries was

cli();//stop interrupts
 
  //set timer0 interrupt at 2kHz
  TCCR1A = 0;// set entire TCCR2A register to 0
  TCCR1B = 0;// same for TCCR2B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 50khz increments
  ICR1 = 0x0271;
  // turn on phase and freq corect PWM mode
  TCCR1A = 0xC0;
  // Set CS11 and CS10 bits for 64 prescaler
  TCCR1B = 0x14;   
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  OCR1A=0;
  sei();//allow interrupts

Matt

I presume this is connected to your Thread about servo jitter Servo jtter - found a low level workaround - do i understand this? - Project Guidance - Arduino Forum

Have you tried the servoTimer2 library that I suggested in Reply #6 in the other Thread (which has now got to 30 Replies)?

I suspect it would be easier to modify that (if it really needs modifying) rather than starting from scratch.

...R

Robin2,

Yes, it is related to the other thread, but I figured it would be best to devote this to just my questions about the actual registry of timer 1. I looked at the ServoTimer2 library and it was basically the same as the servo library from what I could tell. I don't fully understand what those libraries say, so instead of trying to learn them in and out, I just worked in the registries which I know how to control.

EDIT:
I already spent a few hours learning the registry method, so writing the code was pretty easy.

Matt

This is the code I used to control the servo. I haven't put it into my main sketch, but its the proof of concept that it works - control pin 9 via registry commands. I commented as much as I could so hopefully other people who are having this problem can use it for their own benefit.

//Code example for Arduino Uno by Ukeri 
//Bart Borghuis and Zoomkat wrote the majority of the code that I used, I just changed it to what I needed.
//Zoomkat's "servo-test-22-dual-input"'s void loop() is my void loop() code
//This program drives a servo motor using timer1 interrupts at 200Hz.

#include <avr/interrupt.h> 
#include <Serial.h>

//storage variables
String readString;
unsigned long time_diff, OCR1A_value;

void setup(){
  //allows serial communication with the Arduino
  Serial.begin(9600); 
  //set pins as outputs
  pinMode(9, OUTPUT); 

  cli();//stop interrupts
 
  //Initialize the registries for 200 Hz
  //TOP=16Mhz/(2*200*N) where N = 1,8,64,256
  TCCR1A = 0;// set entire TCC12A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 200khz increments
  ICR1 = 0x9C40;  //Top was determined to be 40000 with N = 1
  // turn on phase and freq corect PWM mode
  TCCR1A = 0x80;  // TRRC1A = BV(1 1  0 0 0 0 0 0)
  // Set CS1 for 1 prescaler - 15.28 Resolution and standard PWM
  TCCR1B = 0x11;   //TRRC1B = BV(0 0 0 1 0 0 0 1)
  sei();//allow interrupts
  
  // sets to 15000 - 8 OCR1A value/ usec - 1500us*8 OCR1A value/sec = 12000 OCR1A value
  OCR1A=12000;

  Serial.println("servo registry control"); // so I can keep track of what is loaded  
}//end setup


 
void loop(){
//
//  now = millis();
//
//  time_diff = now - lastTime;

  while (Serial.available()) {
    char c = Serial.read();  //gets one byte from serial buffer
    readString += c; //makes the string readString
    delay(2);  //slow looping to allow buffer to fill with next character
  }

  if (readString.length() >0) 
  {
    Serial.println(readString);  //so you can see the captured string 
    int n = readString.toInt();  //convert readString into a number

    // determines if the input is within the range of the servo.
    if(n >= 900 && n <=2100) // input is within the range of the servo
    {
      OCR1A_value = n * 8;  // takes input pulse width and converts it to a value for OCR1A
      Serial.print("writing Microseconds: ");  // 
      Serial.print(n);
      Serial.print("   OCR1A_value: ");
      Serial.println(OCR1A_value);
      OCR1A = OCR1A_value;
    }
    else if(n < 900)  // input is too small
    {
      n=900;
      OCR1A_value = n * 8; // takes input pulse width and converts it to a value for OCR1A
      Serial.print("writing Microseconds: ");
      Serial.print(n);
      Serial.print("   OCR1A_value: ");
      Serial.println(OCR1A_value);
      OCR1A = OCR1A_value;
    }
    else if(n > 2100)  // input is too large
    {
      n = 2100;
      OCR1A_value = n * 8; // takes input pulse width and converts it to a value for OCR1A
      Serial.print("writing Microseconds: ");
      Serial.print(n);
      Serial.print("   OCR1A_value: ");
      Serial.println(OCR1A_value);
      OCR1A = OCR1A_value;
    }
    
    Serial.print("Last servo command position: ");    
    Serial.println(OCR1A_value/8);
    readString=""; //empty for next input
    }
}

Matt