Library for generating pulsepatterns

Last Modified: November 13, 2013, at 01:52 PM
By: robtillaart

remarks & comments

Intro

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.

PulsePattern library

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!).

Usage

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);
}

Notes

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.

Todo

  • test test test test and ...
  • see .cpp file

Disclaimer

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

PulsePattern.h

#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

PulsePattern.cpp

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

Share