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:
  1. Secrets of Arduino PWM, by Ken Shirriff
  2. ATmega328 Datasheet (660 pg version from 11/2015)
  3. ATmega168/328-Arduino Pin Mapping

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:

- 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:

- Secrets of Arduino PWM:
- ATmega328 Datasheet (660 pg version from 11/2015): 


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
  //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: 
  //--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
History (newest on top):
20170401 - minor wording additions for clarity and emphasis that the code is only 5 key lines

Other Articles:

***Subscribe by Email!***

No comments:

Post a Comment

Thanks for your comment or question! If it is a question, I will try to get back to you quickly. Notice to spammers: I personally remove all spam promptly and report spammers to Google, so please don't do it.

Note: some HTML tags are allowed in your comments. To learn how to add bold (<b>...</b>), italics (<i>...</i>), or hyperlinks (<a href="URL">NAME</a>) to your comments, read here.

~Sincerely, Gabriel Staples.

P.S. Yo hablo español también. Je parle français aussi. (I speak Spanish & French too).