50hz PWM on both pin 9 and 10

Task: Generate two independant variable PWM signals on pin 9 and 10 with the same frequency of 50hz. (For a school project)

Hardware:
Arduino Uno (I have access to a arduino nano if it makes it easier)

These signals would control two servo motors, EMAX ES08MA II analog servo but I've used micro servo sg90 datasheets since I couldn't find any clear data on the EMAX ones. The EMAX have a +/- 90 deg range with a positive duty cycle of 3.3% to 11.8%.

Since I need such a small positive duty cycle I cannot use analogWrite() function nor the library TimerOne. I require a much higher resolution for the servos not to stutter.

So far I've used this code as a starting point (comes from PWM to 50 hz - #15 by afremont - zeppelin3558's comment (second to last).)

// ref: https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
// forum on Arduino PWM: https://forum.arduino.cc/index.php?topic=354160.0
// Schematic: https://content.arduino.cc/assets/UNO-TH_Rev3e_sch.pdf


// Find min and max compare register values to get 1 ms to 2 ms pulse
// Using 16-bit timer with prescale of 8
// Register value should be number of clock cycles before reset - 1 b/c we count up from 0
int OCR1B_min = 1250; //+1=2000 clock cycles to get 1 ms pulse width (off)
int OCR1B_max = 4750; // to get 2 ms pulse width (max speed)

void setup() {

  Serial.begin(9600);
  
  // Set timer output pin data direction
  // From ATMEGA328/P datasheet pgs 13 and 14 and the UNO schematic
  // OC1B -> PB2 -> MCU Pin 16 -> Board Pin 10
  // pg 167: Actual OC1x value will only be visible if data direction for the port pin is set as output (DDR_OC1x)
  pinMode(10, OUTPUT);

  // Create variables to store the values to be written to the TIM1 registers
  // Control registers are 8 bits (char)
  char TCCR1A_pre = 0x00; 
  char TCCR1B_pre = 0x00;

  // Set OC1B to non-inverting mode
  // pg 167: INCORRECT INFO: Non-inverted  PWM output can be generated by writing the COM1x[1:0] to 0x3
  // pg 174-175: looks like it is actually 0x2 in register description
  // Below code was tested working
  TCCR1A_pre |= _BV(COM1B1);

  // Set waveform generation mode
  // pg 165: counter is incremented until counter value matches value in OCR1A (WGM1[3:0]=0xF)
  // pg 175: WGM1[3:2] bits found in TCCR1B register, WGM1[1:0] found in TCCR1A register
  TCCR1B_pre |= _BV(WGM13) | _BV(WGM12); 
  TCCR1A_pre |= _BV(WGM11) | _BV(WGM10);
  //  PORTC |= (_BV(0) | _BV(2) | _BV(7));  // Set bits 0,2,7  

  // Select the prescaled clock to use.  See Excel worksheet "PWMCalcs" to see justification.
  TCCR1B_pre |= _BV(CS11);

  // Write control registers
  TCCR1A = TCCR1A_pre;
  TCCR1B = TCCR1B_pre;

  // Write output compare registers (2 bytes)
  // pg 167: f_OCnxPWM = f_CLK_IO / (N*(1+TOP))
  OCR1A = 39999; // To get 50 Hz frequency
  OCR1B = OCR1B_min; // start at 1 ms pulse width

}

void loop() {
  // Read analog input
  // Voltages are between 0-5V on an UNO per https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/
  int potVal = analogRead(A0);
  Serial.println(potVal);

  // Map the input to the timer output
  // analogRead() values go from 0 to 1023.  We want to change the register from min to max specified above
  // pg 166: OCR1A is double bufferred, updated when TCNT1 matches TOP
  OCR1B = map(potVal, 0, 1023, OCR1B_min, OCR1B_max);
}

This code work flawlessly for pin 10 but I want another output at pin 9 as well. I've tried using ICR1 (see code below) as a compare registry but pin 9 still won't give any form of output.

// actual values after testing with the servo motors for a +/- 90 deg range of motion
int OCR1B_min = 1200;
int OCR1B_max = 4749; 

void setup() {

  Serial.begin(9600);
  
  pinMode(10, OUTPUT);
  pinMode(9, OUTPUT);


  char TCCR1A_pre = 0x00; 
  char TCCR1B_pre = 0x00;

  TCCR1A_pre |= _BV(COM1B1);

  TCCR1B_pre |= _BV(WGM13) | _BV(WGM12); 
  TCCR1A_pre |= _BV(WGM11); // | _BV(WGM10); //remove _BV(WGM10) to set ICR1 as comp value instead of OCR1A

  TCCR1B_pre |= _BV(CS11);

  TCCR1A = TCCR1A_pre;
  TCCR1B = TCCR1B_pre;

  ICR1 = 39999;// To get 50 Hz frequency
  OCR1A = OCR1B_min; 
  OCR1B = OCR1B_min; 

}

void loop() {
  int potValX = analogRead(A0);
  int potValY = analogRead(A1);

  // int degreeX = map(potValX, 0, 1023, 80, -90);
  // int degreeY = map(potValX, 0, 1023, 80, -90);
  
  OCR1B = map(potValX, 0, 1023, OCR1B_min, OCR1B_max);
  OCR1A = map(potValY, 0, 1023, OCR1B_min, OCR1B_max);
}

Now what I'm wondering is this:

  1. Is there a way to do this at all (like the above example) or am I misunderstanding how OCR1A works?

  2. I've been having a hard time finding somewhere to read up on COM1B1 and how it functions/alters the timer, if you could give me a link for further reading that would be greatly appreciated.

  3. I'm guessing the servos would function on a ~64Hz signal (which could be obtained from simple clock division), is there any way to get a higher resolution from analogWrite() (other than analogWriteResolution()
    since that is for Zero, Due & MKR Family)?

Please let me know if I need to alter something in the post or if you need more information, I'll try to monitor the thread daily!

Have you considered using the Servo library?

Why not just use the servo library? If you have to write the program completely on your own, you can see how the library itself works.

P.S. @anon73444976 was a little ahead of me.

1 Like

EMAX ES08MA II : https://emaxmodel.com/collections/wholeoff/products/emax-es08ma-ii-12g-mini-metal-gear-analog-servo-for-rc-model-robot-pwm-servo
Other timing that is not mentioned is probably the default servo timing.

Use a different WGM that frees up both OC1A and B. For example

const uint8_t pinRC1 = 9;   //OC1A output
const uint8_t pinRC2 = 10;  //OC1B output

void setup() 
{
    TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);    //COM1x clear on match, set on bottom
    TCCR1B = _BV(WGM13) | _BV(WGM12)| _BV(CS11);        //WGM=14, /8 prescaler
    ICR1 = 39999;   //20mS period for timer 1
    OCR1A = 2999;   //1.5mS pulse on pin 9
    OCR1B = 3999;   //2mS pulse on pin 10

    pinMode( pinRC1, OUTPUT );
    pinMode( pinRC2, OUTPUT );
    
}//setup

void loop() 
{
}//loop
1 Like

Sketch by @Blackfin of Reply #5 in Wokwi: https://wokwi.com/arduino/projects/316702102141272640

@Blackfin Hi, I encountered a problem in the Wokwi simulator. The 39999 is correct for 20ms interval, but the value for OCR1A and OCR1B do not seem to have that "-1" correction and can be 3000 and 4000. Do you know something about that or is there a bug in Wokwi ?

Without thinking too much about it I'd say the difference lies in how the timer works vs how the compare works.

The TOP value in the ICR1register is itself a valid timer count; we need to go one more than that to cycle back to 0 (BOTTOM) giving us a total of ICR1 + 1 counts for a complete cycle.

The OC1x pins clear (using this compare mode) when the count equals the value in the OCR1x register. If we set the OCR1x register to, say, 10 then from zero we need 1-2-3-4-5-6-7-8-9-10 (i.e. 10) ticks; on that tenth tick the compare matches and the output goes low. At 2MHz clock rate then, if we extend that out to 4000 ticks to get to the pin-clearing value (2mS).

So my values of N-1 are incorrect for the compare values but correct for the TOP value.

1 Like

This worked fantastic, I have no idea how I managed to miss this library, I think I just went overboard trying to learn how timers/clocks work.

Thanks alot, and thank you everyone else as well, I'll def look into the other answers to learn more about how the timers work!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.