Last Modified: | November 13, 2013, at 01:52 PM |
By: | robtillaart |
For a (non disclosed) application I needed to create a repeating pulse pattern. As work progressed it became clear this function was a good candidate for reuse or in other words a library.
For the original application I could have used a "blink without delay" construction but I found it a good opportunity to dive into the working of timers a bit more.
For now I want to label this library as experimental as there are several points to improve, tune and callibrate.
The PulsePattern class has a quite straightforward interface
PulsePatternOut(); // constructor void init(pin, *ar, size, level, prescaler)); // pin that outputs the pattern // array of durations // size of the array // starting level HIGH/LOW // prescaler, one of the 5 defines from .h file void start(); // start the pattern generator void stop(); // stop the pattern generator bool isRunning(); // bool indicator void worker(); // must be public otherwise the ISR cannot call it // some bad understood __vector_11() error // when worker() is private.
The library uses Timer1 for the timing of the pulses. Therefore the class is implemented with a static instance called PPGenerator. Still by calling init() one can change all parameters of the process. One should note that calling init() forces the timer to stop.
The timer code is based upon the website of Nick Gammon which holds quite a lot of good solid material (Thanks Nick!).
The sample sketch below shows how the library can be used to generate an SOS morse signal on pin 13 (LED pin).
Sample sketch
// // FILE: SOS_demo2.pde // AUTHOR: Rob Tillaart // DATE: 2012-11-23 // // PUPROSE: demo of the PulsePattern Library // uses timer1 // #include "PulsePattern.h" // a pattern consists of durations of LOW and HIGH periods // so the first line of the SOSpattern is // 500 units LOW, 500 units HIGH etc // for a dutycycle of 50% LOW and HIGH should have equal periods // max period = 4095. // min period = about 12 uint16_t SOSpattern[] = { 500,500,500,500,500,1500, // SOS in morse 1500,500,1500,500,1500,1500, 500,500,500,500,500,1500 }; uint16_t pattern[] = { 100,100,100,100,100,100, 500,500,500,500,500,500, 1000,1000,1000,1000,1000,1000 }; uint8_t patternSize = 18; uint8_t startLevel = LOW; void setup() { Serial.begin(9600); Serial.println("Start PulsePattern"); // as the prescaler = 1024 the periods of the pattern are a // few percent less than a millisecond PPGenerator.init(13, SOSpattern, patternSize, startLevel, PRESCALE_1024); PPGenerator.start(); } void loop() { // dummy code Serial.println(millis()); delay(1000); }
To use the library, make a folder in your SKETCHBOOKPATH\libaries with the name PulsePattern and put the .h and .cpp there. Optionally make a examples subdirectory to place the sample sketches.
This library is still experimental and it will probably change in the future. So please do not build mission critical SW with it ;)
Enjoy tinkering,
rob.tillaart@removethisgmail.com
#ifndef PulsePattern_h #define PulsePattern_h // // FILE: PulsePattern.h // AUTHOR: Rob dot Tillaart at gmail dot com // PURPOSE: PulsePattern library for Arduino // sends a pulse pattern to a digital pin (continuously) // HISTORY: See PulsePattern.cpp // // Released to the public domain // #include <inttypes.h> #define PULSEPATTERN_LIB_VERSION "0.0.5" #define NOTINIT -1 #define STOPPED 0 #define RUNNING 1 #define NO_CLOCK 0 #define PRESCALE_1 1 #define PRESCALE_8 2 #define PRESCALE_64 3 #define PRESCALE_256 4 #define PRESCALE_1024 5 class PulsePattern { public: PulsePattern(); void init(uint8_t pin, uint16_t * ar, uint8_t size, uint8_t level, uint8_t prescaler); void start(); void stop(); bool isRunning(); void worker(); private: void stopTimer(); void setTimer(uint16_t cc); uint16_t * _ar; uint8_t _size; uint8_t _pin; uint8_t _prescaler; volatile uint8_t _level; volatile int8_t _state; volatile uint8_t _cnt; }; extern PulsePattern PPGenerator; #endif // END OF FILE
// // FILE: PulsePattern.cpp // AUTHOR: Rob dot Tillaart at gmail dot com // VERSION: see PULSEPATTERN_LIB_VERSION in .h // PURPOSE: PulsePattern library for Arduino // // HISTORY: // 0.0.1 - 2012-11-23 initial version // 0.0.2 - 2012-11-23 adapted a static PPO // 0.0.3 - 2012-12-27 renamed to PulsePattern // 0.0.4 - 2012-12-27 code stable(?) enough to publish // 0.0.5 - 2012-12-27 code cleanup+comment // // Released to the public domain // // TODO // - fast function iso array to return the next period? // more adaptive to e.g. sensor values. (investigate) // - test PRE 1.0 backwards compatibility // - move code to .h file so compiler can inline? // - optimize timer code // - adjust timing to more accurate values -> setTimer() // - worker should be private - how??? // - test invalid array periods // #include "PulsePattern.h" #if defined(ARDUINO) && ARDUINO >= 100 #include "Arduino.h" #else #include "WProgram.h" #endif // Predefined generator (singleton) PulsePattern PPGenerator; ISR(TIMER1_COMPA_vect) { PPGenerator.worker(); } PulsePattern::PulsePattern() { _size = 0; _state = NOTINIT; } void PulsePattern::init(uint8_t pin, uint16_t * ar, uint8_t size, uint8_t level, uint8_t prescaler) { stop(); _pin = pin; _ar = ar; _size = size; // TODO: run over the array to test invalid values? // constrain them 10-4095? _level = constrain(level, LOW, HIGH); _prescaler = constrain(prescaler, PRESCALE_1, PRESCALE_1024); _cnt = 0; pinMode(_pin, OUTPUT); digitalWrite(_pin, _level); } void PulsePattern::start() { if (_size == 0) return; if (_state == RUNNING) return; // no restart setTimer(1); // start asap _state = RUNNING; } void PulsePattern::stop() { stopTimer(); _state = STOPPED; _level = LOW; digitalWrite(_pin, _level); } bool PulsePattern::isRunning() { return (_state == RUNNING); } void PulsePattern::worker() { if (_state != RUNNING) return; // set next period & flip signal _level = !_level; digitalWrite(_pin, _level); // TODO: adjustment needed for code overhead when micros?; // + 5.2 usec for digitalWrite // + 3 usec for settimer call OCR1A = (_ar[_cnt]) * (F_CPU/1000000L); _cnt++; if (_cnt >= _size) _cnt = 0; // repeat } // TIMER code based upon - https://www.gammon.com.au/forum/?id=11504 void PulsePattern::stopTimer() { TCCR1A = 0; // reset timer 1 TCCR1B = 0; } // TODO: can be optimized? void PulsePattern::setTimer(uint16_t cc) { TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; // reset counter OCR1A = cc*16; // compare A register value; // *16 makes max period 4095 // min period 12? // 4: CTC mode, top = OCR1A TCCR1A = _BV (COM1A1); // clear on compare TCCR1B = _BV (WGM12) | _prescaler; TIFR1 |= _BV (OCF1A); // clear interrupt flag TIMSK1 = _BV (OCIE1A); // interrupt on Compare A Match } // END OF FILE