Friday, March 31, 2017

Arduino (ATmega328) Direct Register Manipulation for Custom PWM Output (ex: 1us HIGH followed by 99us LOW --> 1% Duty Cycle at 10kHz)

Subscribe by Email!
Here's a quick example of how to make a hardware-based 1us HIGH pulse followed by a 99us LOW pulse (ie: 100us period, or 10kHz, PWM with a 1% duty cycle), via direct timer register manipulation. I've left ample comments for you to read and follow. This example below is therefore quite easy to follow and learn from, but just as it took me dozens of hours originally to learn how to do all this stuff from scratch--and to learn about all the different PWM modes possible as described in the datasheet and elsewhere, expect to spend at least a few hours if you are truly going to read the references and dig into it enough to understand it yourself. Good luck and have fun!

References to Study:

Here's some oscilloscope screenshots of the output on Pin 3 produced by an Arduino Nano running the code below.

 Rising edge to rising edge: Δx = 100us

 Zoomed-in view of high pulse. Rising edge to falling edge = 1us

Here's the code. Don't be put off by it. Literally, it's only 5 key lines of code, surrounded by a little fluff and lots of comments. The 5 key lines are setting the pinMode, TCCR2A, TCCR2B, OCR2A, and OCR2B. Take a look:

```/*
pulse_1us_100us
- use timer2 to make a 1us HIGH pulse followed by 99us LOW, for a duty cycle of
1us/(99+1)us = 1us/100us = 1%, and a period of 100us

By Gabriel Staples
- for my email address, click "Contact Me" link at top of my website here:
http://www.ElectricRCAircraftGuy.com/

References:
- Secrets of Arduino PWM: http://www.righto.com/2009/07/secrets-of-arduino-pwm.html
- ATmega328 Datasheet (660 pg version from 11/2015): http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf
- https://www.arduino.cc/en/Hacking/PinMapping168

*/

void setup()
{
//set 1us HIGH pulse followed by 99us LOW pulse (1% duty cycle) on pin 3
pinMode(3, OUTPUT); //Note: pin 3 is OC2B <--so use port B, not A; see https://www.arduino.cc/en/Hacking/PinMapping168
//Set timer2 clock with prescaler 8 (0.5us per count, so 256 counts --> 128us); datasheet pg. 157: CS22/CS21/CS20 = 0/1/0
//Set to Fast PWM with Top==OCRA; datasheet p. 155: Mode 7; WGM22/WGM21/WGM20 = 1/1/1
//Set OC2B to non-inverting mode; Table 18-6, pg. 154: COM2B1/COM2B0 = 1/0
//TCCR2A: pg. 153; TCCR2B: pg. 156
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(WGM22) | _BV(CS21);

//Set period to 100us and high pulse time to 1us
//-Note that the high time you set here is OCR2B + 1 clock cycles, so OCR2B + 1 in this case
// is 2, which is 1us, since each clock count is 0.5us
//-To get clock cycles we must multiply desired microseconds x 2 (again, with a prescaler
// of 8 and a 16MHz clock, each clock count is 0.5us)
//-For this Fast PWM mode, note that the period is OCR2A + 1 also
//-For reasons for the above, see "off-by-one" here: http://www.righto.com/2009/07/secrets-of-arduino-pwm.html
//--Ex: Notice where Output B duty cycle is calculated in his example here:
//  "Output B duty cycle: (50+1) / (180+1) = 28.2%"
//--So, for the reasons above, we must subtract 1 below to get the right period and duty cycles
byte pd = 100; //us; period
byte highPulseTime = 1; //us
OCR2A = pd*2 - 1; //set period
OCR2B = highPulseTime*2 - 1; //set high time
}

void loop()
{

}

```
By Gabriel Staples
Written: 31 Mar 2017
Last Updated: 1 Apr 2017
20170401 - minor wording additions for clarity and emphasis that the code is only 5 key lines

Other Articles:
END

***Subscribe by Email!***