Last active
April 13, 2018 22:13
-
-
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/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// **************** 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