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