Skip to content

Instantly share code, notes, and snippets.

@bigjosh
Last active April 13, 2018 22:13
Show Gist options
  • Save bigjosh/f5beb32e919ec17e157eeee7d8bc1545 to your computer and use it in GitHub Desktop.
Save bigjosh/f5beb32e919ec17e157eeee7d8bc1545 to your computer and use it in GitHub Desktop.
Code to generate a one-shot pulse on AVR Timer1A by Nevell Greenough, N2GX. More info about this program is here... http://wp.josh.com/2015/03/05/the-perfect-pulse-some-tricks-for-generating-precise-one-shots-on-avr8/
// **************** TimerShot for Timer 1A *********************
// More info about this program is here...
// http://wp.josh.com/2015/03/05/the-perfect-pulse-some-tricks-for-generating-precise-one-shots-on-avr8/
// Demo of a technique to generate various precise one shot pulses using
// timer 1 module on an AVR. This demo code version is writen for an Arduino Uno or Mega2560 for the
// the Timer1 moudule, but this technique should would on other 16-bit AVR timers on Mega2560.
// Original code by Josh Levine, hack by N2GX 8/30/2016.
// Long-pulse working solution for TIMER 1A One-Shot, edited from Josh's Timer2 code and a partial Timer3 solution
// by Chris Hahn. Tested on Uno and Mega2560, should work on other '328P boards as well.
// The one shot pulses are output from OC1A on pin D9 on Uno; pin D11 on Mega2560.
// This long-pulse solution is required when using the prescaler with Timer 1.
// To make this work first choose your prescaler value,
// then pick your board: Mega328P-type or Mega2560-type
// then choose a "wait" definition matching your prescaler value.
// For prescaler values > 1, call OSP_SET_AND_FIRE_LONG(o) instead of OSP_SET_AND_FIRE(o).
// or use OSP_SET_WIDTH(o); wait; OSP_FIRE() sequence as in the code example.
// The "wait" code can also be replaced by other code that uses up similar amounts of time.
// Timer1 needs at least one prescaler output pulse between OCR1A loading and TCNT1 loading (?!).
// This version uses Timer1 Mode 14 instead of Mode 15. Mode 14 uses ICR1 for TOP instead of OCR1A.
// Mode 14 frees up OCR1A for its use as compare match for Timer1A.
#define OSP_SET_WIDTH(cycles) (OCR1A = 0xffff-(cycles-1))
// Setup the one-shot pulse generator and initialize with a pulse width that is (cycles) clock counts long
// "Clock" counts are prescaler counts.
void osp_setup(uint16_t cycles) {
TCCR1B = 0; // Halt counter by setting clock select bits to 0 (No clock source).
// This keeps anything from happening while we get set up
TCNT1 = 0x0000; // Start counting at bottom.
ICR1 = 0;// Set TOP to 0, Mode 14. This effectively keeps us from counting becuase the counter just keeps reseting back to 0.
// We break out of this by manually setting the TCNT higher than 0, in which case it will count all the way up to MAX
// and then overflow back to 0 and get locked up again.
OSP_SET_WIDTH(cycles); // This also makes new OCR values get loaded from the buffer on every clock cycle.
TCCR1A = (1<<COM1A0) | (1<<COM1A1) | (1<<WGM11); // OC1A=Set on Match, clear on BOTTOM. Mode 14 Fast PWM. p.131
// (using Chris Hahn's notation here)
// Prescaler Setup - Choose one of these, then choose a matching "wait" delay statement below.
//TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10); // Prescaler = 1; Start counting now. Max ~4mS
//TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS11); // Prescaler = 8; Start counting now. Max ~32mS, starts in ~10uS or better
//TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10) | (1<<CS11); // Prescaler = 64; Start counting now. Max ~.26 sec, starts in ~20uS or better
//TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS12); // Prescaler = 256; Start counting now. Max ~1.05 sec, starts in ~64uS or better
TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10) | (1<<CS12); // Prescaler = 1024; Start counting now. Max ~4 sec, starts in ~180uS or better
// Set OC1A to output, pick your board- Uno vs 2560
DDRB = (1<<1); // Set pin to output (Note that OC1A = GPIO port PB1 = Arduino Digital Pin D9 Uno)
// DDRB = (1<<5); // Set pin to output (Note that OC1A = GPIO port PB5 = Arduino Digital Pin D11 Mega2560)
}
void osp_setup() {
osp_setup(1);
}
// Fire a one-shot pulse. Use the most recently set width.
#define OSP_FIRE() (TCNT1 = OCR1A - 1)
// Test there is currently a pulse still in progress
#define OSP_INPROGRESS() (TCNT1>0)
// Fire a one-shot pusle with the specififed width.
// Order of operations in calculating m must avoid overflow of the unint8_t.
// TCNT1 starts one count lower than the match value becuase the chip will block any compare on the cycle after setting a TCNT.
// LONG PULSE WARNING! Tweaked code is needed to make it work.
// DO NOT USE OSP_SET_AND_FIRE(cycles), or OSP_SET_WIDTH(cycles) and OSP_FIRE() WITH PRESCALER > 1!
// Erratic behavoir results! Long startup delays and intermittent missing triggers!
// "wait" Definition:
// For prescaler = 8, 64, 256, 1024 use OSP_SET_AND_FIRE_LONG(cycles) instead. The "wait" time-waster makes it work!
//#define wait {delayMicroseconds(2);} // Un-comment this for prescaler = 8
//#define wait {delayMicroseconds(5);} // ...for prescaler = 64, make sure we get at least one clock
//#define wait {delayMicroseconds(17);} // ...for prescaler = 256
#define wait {delayMicroseconds(65);} // ...for prescaler = 1024
#define OSP_SET_AND_FIRE_LONG(cycles) {uint16_t m=0xffff-(cycles-1); OCR1A=m; wait; TCNT1 = m-1;} // for prescaler > 1
// For prescaler = 1, the original code will work for Timer 1:
#define OSP_SET_AND_FIRE(cycles) {uint16_t m=0xffff-(cycles-1); OCR1A=m; TCNT1 = m-1;} // Prescaler = 1 only
void setup()
{
osp_setup();
pinMode(13, OUTPUT); // Use LED pin for a scope trigger
}
// The following code is a WWVB test-pulse-generator. WWVB transmits time codes in the U.S.
// at a 1-second rate per bit on a 60kHz carrier. A 0 is 200mS "off", 800mS on; 1 is 500mS "off", 500mS on;
// and a 10sec marker is 800mS "off", 200mS ON. "off" is a 17dB reduction of carrier strength.
// In the real project this pulse will modulate a 60kHz carrier. The coding and the
// 1PPS rate are obtained from a GPS receiver. To observe behavoir/misbehavoir of Timer 1,
// stuff an LED and resistor on pin 9 on Uno or pin 11 on Mega2560; or
// set up a 2-channel oscilloscope with chan 1 on pin 13 and chan 2 on pin 9 (Uno). Trigger on chan 1.
// To make timer 1 misbehave, reduce the delayMicroseconds value above. Note missing and late pulses on pin 10.
void loop()
{
// Step though 3 cycle long pulses for demo purposes:
// A WWVB "0", a "1" and a 10-second marker.
uint16_t o;
for (int b=0; b < 3; b++) {
switch(b) {
case 0:
o = 3125; // =200mS @ prescaler = 1024
break;
case 1:
o = 7812; // =500mS @ prescaler = 1024
break;
case 2:
o = 12500; // =800mS @ prescaler = 1024
break;
}
// You can use either of the two following possibilities
// with the proper "wait" #define macro chosen above to match the
// chosen prescaler value. Either use:
digitalWrite(13, 1); // Fire a pin for an oscilloscope
digitalWrite(13, 0); // Fire a pin... OSP_SET_AND_FIRE_LONG starts at trailing edge
OSP_SET_AND_FIRE_LONG(o); // Use this for prescaler > 1!
// Or use the following 3 statements with the proper "wait".
// OSP_SET_WIDTH(o);
// wait; // Macro defined above to match chosen prescaler value
// OSP_FIRE();
digitalWrite(13, 1); // Fire a pin a second time...
delay(20*b+20); // for a
digitalWrite(13, 0); // 'Scope with a signature
delay(999-(20*b+20)); // Makes up about 1 Hz total timing
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment