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:
-
Is there a way to do this at all (like the above example) or am I misunderstanding how OCR1A works?
-
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.
-
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!