Engine RPM (Part 4)

Part 1, 2, 3, 4, 5

In this version of the arduino stroboscope, the standard micros() function is no longer used. Instead timer 1, an accurate 16 bits counter shall be exclusively used. As a consequence of the disabled timer 0 and timer 2, no interrupts will bother the output signal except the timer 1 interrupts themselves.

Timer 1 settings is extracted from the data acquisition engine from PlainDSP which proved to be very accurate within a wide range of frequencies.

void SetTimer(float rpm) 
/* Set acquisition parameters, full options */
{
	/* Compute frequency */
	float frequency = ((rpm * 16.0) / 60.0);
	/* Disable all interrupts */
	cli();
	/* Disable timers 0 and 2 in order to improve stability */
	TIMSK0 = 0x00; /* Disable timer/counter0 */
	TIMSK2 = 0x00; /* Disable timer/counter2 */
	/* Reset Timer/Counter1 control registers and Interrupt Mask Register */
	TCCR1A = 0x00; 
	TCCR1B = 0x00; 
	TIMSK1 = 0x00; 
	/* Compute clock timer prescaler */
	uint8_t preScalers[] = {0, 3, 6, 8, 10}; /* Log2 of prescalers */	
	uint8_t timerPrescaler = 0;
	uint32_t upperCount;
	do {
		upperCount = uint32_t(F_CPU / (frequency * (1 << preScalers[timerPrescaler]))) - 1 ;
		timerPrescaler += 1;
	} while ((upperCount & 0xFFFF0000) && (timerPrescaler < 5));	
	OCR1A = uint16_t(upperCount);
	TCCR1B |=  (1 << WGM12); /* Set Clear Timer on Compare Match (CTC) Mode (Mode 4) */
	TCCR1B |= timerPrescaler; /* Set timer prescaler */
	TIMSK1 |= (1 << OCIE1B); /* Enable timer1 interrupt  */
	sei(); /* Enable all interrupts */
}

ISR(TIMER1_COMPB_vect)
/* Invoked on completion of counting (Compare mode) */
{
	_counter += 1;
	_counter &= 0x0F;
	/* Flip LED state */
	if (_counter == _onTime) {
		PORTB &= ~_ledPinMask;
	} else if (!_counter) {
		PORTB |= _ledPinMask;
	}
	_synch = 1;
}

Timer 1 provides a stable time base for a sub counter of 4 bits. In other words each flash cycle can be decomposed in 16 basic steps. The reason for this lies in the need for adjusting the phase of the stroboscope. If the rotational speed of the object to monitor is strictly equal to the flash frequency of the arduino stroboscope, chances are that the flash will no spot on the target. You could either move the led in front of the target or leave the LED where it is and adjust the phase until the flash hits the target itself.

Adjusting the phase is pretty simple. It consists in adding an extra count to the secondary counter. In trigonometric words, one more count results in 360/16= +22.5 degrees. If the target is 180° out of phase, 8 clicks on the phase button will be necessary to synchronize the light beam and the target.

Next is code section for the phase adjustment. Note that the counter is updated in a synchronized manner versus the base counter.

	_synch = 0;
	while(!_synch) ;
	if (Button()) {
		_counter += 1;
		_counter &= 0x0F;
		while(Button());
	}

A quick check with the oscilloscope shows a very stable and accurate signal. Ultimately, here comes the whole code

/*

	Accurate stroboscope,
	ATmega328 powered Arduino only
	Copyright (C) 2015 Didier Longueville

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
	
*/

const uint8_t _ledPinMask = ( 1 << PINB5); /* Defaults to digital pin 5 aka the LED pin */
const uint8_t _phaseBtnPinMask = (1 << PINB0); /* Defaults to digital pin 8 */ 
volatile uint8_t _counter = 0;
volatile uint8_t _onTime = 4; /* versus 16 */
volatile uint8_t _synch = 0;

void setup()
{
	InitStrobeLED();
	InitButton();
	SetTimer(4096.0); /* Set strobe frequency */
}

void loop()
{
	_synch = 0;
	while(!_synch) ;
	if (Button()) {
		_counter += 1;
		_counter &= 0x0F;
		while(Button());
	}
}

void SetTimer(float rpm) 
/* Set acquisition parameters, full options */
{
	/* Compute frequency */
	float frequency = ((rpm * 16.0) / 60.0);
	/* Disable all interrupts */
	cli();
	/* Disable timers 0 and 2 in order to improve stability */
	TIMSK0 = 0x00; /* Disable timer/counter0 */
	TIMSK2 = 0x00; /* Disable timer/counter2 */
	/* Reset Timer/Counter1 control registers and Interrupt Mask Register */
	TCCR1A = 0x00; 
	TCCR1B = 0x00; 
	TIMSK1 = 0x00; 
	/* Compute clock timer prescaler */
	uint8_t preScalers[] = {0, 3, 6, 8, 10}; /* Log2 of prescalers */	
	uint8_t timerPrescaler = 0;
	uint32_t upperCount;
	do {
		upperCount = uint32_t(F_CPU / (frequency * (1 << preScalers[timerPrescaler]))) - 1 ;
		timerPrescaler += 1;
	} while ((upperCount & 0xFFFF0000) && (timerPrescaler < 5));	
	OCR1A = uint16_t(upperCount);
	TCCR1B |=  (1 << WGM12); /* Set Clear Timer on Compare Match (CTC) Mode (Mode 4) */
	TCCR1B |= timerPrescaler; /* Set timer prescaler */
	TIMSK1 |= (1 << OCIE1B); /* Enable timer1 interrupt  */
	sei(); /* Enable all interrupts */
}

ISR(TIMER1_COMPB_vect)
/* Invoked on completion of counting (Compare mode) */
{
	_counter += 1;
	_counter &= 0x0F;
	/* Flip LED state */
	if (_counter == _onTime) {
		PORTB &= ~_ledPinMask;
	} else if (!_counter) {
		PORTB |= _ledPinMask;
	}
	_synch = 1;
}

void InitStrobeLED(void) 
{
	/* Make the pin an output pin */
	DDRB |= _ledPinMask;
	/* Turn the pin low */
	PORTB &= ~_ledPinMask;
}

void InitButton(void) 
{
	/* Make the pin an input pin */
	DDRB |= _phaseBtnPinMask;
	/* Bias pin */
	PORTB |= _phaseBtnPinMask;
}

uint8_t Button(void) 
{
	uint8_t res = 0;
	if ((~PINB & _phaseBtnPinMask) == _phaseBtnPinMask) {
		res = 1;
	}
	return(res);
}

 

Next post on same subject

 

Leave a Reply

You must be logged in to post a comment.