Notes and considerations using an Arduino for the WFC Replication project.

The following are inputs/outputs when we would want to make a closed system whereby the exhaust is looped back to the inlet. I don't know if that is feasible, but for the considerations on computer control it might be nice to keep such an option open.

Inputs to measure:

Must have:

  • pressure electrolyzer - A
  • hall sensor exhaust valve - D
  • hall sensor ignition - D
  • ignition retard / delay, A (potmeter) or D (buttons)

Should have:

  • fluid level electrolyzer
  • fluid level mist generator
  • fluid level tank

Nice to have:

  • pressure inlet - A
  • pressure exhaust - A

Must have: 1 analog input and 2 digitale inputs

To control:

Must have:

  • ignition - D
  • electrolyzer on/off - D
  • gas injector inlet - D

Should have:

  • pump or valve to electrolyzer - D
  • pump or valve to mist generator - D
  • liquid injector mist generator - D

Nice to have:

  • puls frequency "gate" electrolyzer
  • gas injector uitlaat -> inlet
  • gas injector lucht inlet
  • gas injector exhaust pressure release

Must have: 3 digitale uitgangen Should have: 6 digitale uitgangen

Arduino pinout


Current hardware

I have two Arduino boards, both of which are based on the ATmega168:

ATmega168 (used on most Arduino Diecimila and early Duemilanove)
Digital I/O Pins14 (of which 6 provide PWM output)
Analog Input Pins6 (DIP) or 8 (SMD)
DC Current per I/O Pin40 mA
Flash Memory16 KB
EEPROM512 bytes

Seeeduino v2.21

This one has an SMD processor, which has two additional analog inputs, as mentioned in the reference:

Analog Pins
In addition to the specific functions listed below, the analog input pins support 10-bit analog-to-digital conversion (ADC) using the analogRead() function. Most of the analog inputs can also be used as digital pins: analog input 0 as digital pin 14 through analog input 5 as digital pin 19. Analog inputs 6 and 7 (present on the Mini and BT) cannot be used as digital pins.

Freeduino v 1.16

This one has a DIP processor, without the extra two analog inputs.

Nu Electronics LCD Shield

Maker: Nu Electronics

Includes a 16x2 HD44780 compatible LCD, white character & blue backlight. Use Arudino LCD4Bit library. Joystick style Keypad with 5 pushbuttons, but only use one ADC pin on Arduino.

LCD shield uses:

  • 7 digital I/O, of which 4 PWM
  • 1 Analog input


  • 7 digital I/O, of which 2 PWM
  • 7 analog inputs.

Sample code:

Zie ook: Seeeduino v2.21 (Atmega 168P)

Mine has an ATMega 168 20AU

The Seeeduino has a double row of connectors, one of which is free and can thus be soldered, etc.

Seeeduino is Arduino compatible board. Its design is based on Diecimila scheme, 100% compatible to its existing program, shield and IDEs. On the hardware part, remarkable changes are taken to improve the flexibility and user experience.

Microcontroller ATmega168 Operating Voltage 5V/3.3V Input Voltage (recommended) 7-12 V Input Voltage (limits) 6-20 V Digital I/O Pins 14 (of which 6 provide PWM output) Analog Input Pins 8 DC Current per I/O Pin 40 mA DC Current for 3.3V Pin 50 mA Flash Memory 16 KB (of which 2 KB used by bootloader) SRAM 1 KB EEPROM 512 bytes Clock Speed 16 MHz

freeduino v1.16 ATmega328 MCU (ATmega168 on older boards).

Mine has a ATmega 168-20

LCD shield

Analog 0 Button (select, up, right, down and left) Digital 4 DB4 Digital 5 DB5 Digital 6 DB6 Digital 7 DB7 Digital 8 RS (Data or Signal Display Selection) Digital 9 Enable Digital 10 Backlit Control

Motor shield is wschl interessant:

Functionpins per Ch. A pins per Ch. B Direction D12 D13 PWM D3 D11 Brake D9 D8 Current Sensing A0 A1

If you don't need the Brake and the Current Sensing and you also need more pins for your application you can disable this features by cutting the respective jumpers on the back side of the shield.

The additional sockets on the shield are described as follow:

Screw terminal to connect the motors and their power supply. 2 TinkerKit connectors for two Analog Inputs (in white), connected to A2 and A3. 2 TinkerKit connectors for two Aanlog Outputs (in orange in the middle), connected to PWM outputs on pins D5 and D6. 2 TinkerKit connectors for the TWI interface (in white with 4 pins), one for input and the other one for output.

Arduino Tinkerkit sensor shield module


Met de "TinkerKit Sensor Shield" sluit u gemakkelijk TinkerKit sensoren en schakelaars aan. Geen breadboard of soldeerpunten nodig, de 3-draads TinkerKit kabels zijn voldoende om de verschillende sensors en schakelaars uit de TinkerKit-serie te voeden en te gebruiken. Het shield is voorzien van 12 aansluitingen, 3 aansluitpunten voor TinkerKit randapparatuur. De 6 aansluitingen met nummers I0 tot I5 zijn analoge ingangen en de 6 aansluitingen O0 tot 05 zijn analoge uitgangen op pinnen waarmee u PWM-uitgangen voor de Arduino kunt maken. U kunt de toewijzing van de uitgangsaansluitingen O0 tot 05 wijzigen naar digitale ingangen om de sensoren voor alles of niets-waarden te gebruiken (hoge of lage stand), maar niet voor sensoren die puur analoog zijn. Een groene LED geeft de juiste werking van de shield aan en u reset de Arduino-kaart met een drukknop. Daarnaast is de kaart voorzien van een TWI 4-puntsaansluiting zodat u er een randapparaat met I2C-verbinding op kunt aansluiten. De aansluiting levert een voeding van 5V en gebruikt de analoge pinnen 4 en 5 voor de i2C-interface. Het gebruik van de TWI-verbinding gaat dus ten koste van de analoge ingangen 4 en 5. Een 4-punts serieelstekker verzorgt de serile aansluiting van andere randapparatuur en is ook voorzien van een 5V voeding. Dit gaat ten koste van de standaard serieverbinding van de Arduino. De twee bevestigingsgaten op de TinkerKit Sensor Shield maken bevestiging op de Arduino-kaart mogelijk. Het derde gat laat u de status van de L13-led van de Arduino zien.


De mapping van de pinnen op de Arduino ziet er als volgt uit: Pin 11 op de Arduino is bekabeld op O0 op het shield - Pin 10 op de Arduino is bekabeld op O1 op het shield - Pin 9 op de Arduino is bekabeld op O2 op het shield - Pin 6 op de Arduino is bekabeld op O3 op het shield - Pin 5 op de Arduino is bekabeld op O4 op het shield Pin 3 op de Arduino is bekabeld op O5 op het shield.

i.c.m. LCD shield dus analoog in O1-O5 bruikbaar en outputs pin 3 en 11, PWM uitgangen.

Arduino I/O Shield Velleman KA05

Documentation: pdf

General purpose INPUT - OUTPUT shield for Arduino


  • For use with Arduino Due, Arduino Uno, Arduino Mega
  • 6 analog inputs
  • 6 digital input
  • 6 relay contact outputs
  • Indicator leds for relay outputs and digital inputs


  • Analog inputs: 0..+5VDC
  • Digital inputs: dry contact or open collector
  • Relays: 12V
  • Relay contacts: NO/NC 24VDC/1A max.
  • Dimensions: 68 x 53mm / 2.67 x 2.08

The relay outputs on this board are wired like this:

This can be easily converted to an open collector output or a digital output by replacing the diode with a pull up resistor.

The led output can also be used to drive a mosfet trough an optocoupler like this:

And of course one can also drive an ignition coil with a MOSFET, when we protect the MOSFET with a diode. Something like this:

Serial communication -:- Used for communication between the Arduino board and a computer or other devices. All Arduino boards have at least one serial port (also known as a UART or USART): Serial. It communicates on digital pins 0 (RX) and 1 (TX) as well as with the computer via USB. Thus, if you use these functions, you cannot also use pins 0 and 1 for digital input or output. -:-

Pin use overview

From the Board reference:

Digital Pins

In addition to the specific functions listed below, the digital pins on an Arduino board can be used for general purpose input and output via the pinMode(), digitalRead(), and digitalWrite() commands. Each pin has an internal pull-up resistor which can be turned on and off using digitalWrite() (w/ a value of HIGH or LOW, respectively) when the pin is configured as an input. The maximum current per pin is 40 mA.

  • Serial: 0 (RX) and 1 (TX). Used to receive (RX) and transmit (TX) TTL serial data. On the Arduino Diecimila, these pins are connected to the corresponding pins of the FTDI USB-to-TTL Serial chip. On the Arduino BT, they are connected to the corresponding pins of the WT11 Bluetooth module. On the Arduino Mini and LilyPad Arduino, they are intended for use with an external TTL serial module (e.g. the Mini-USB Adapter).
  • External Interrupts: 2 and 3. These pins can be configured to trigger an interrupt on a low value, a rising or falling edge, or a change in value. See the attachInterrupt() function for details.
  • PWM: 3, 5, 6, 9, 10, and 11. Provide 8-bit PWM output with the analogWrite() function. On boards with an ATmega8, PWM output is available only on pins 9, 10, and 11.

Analog Pins

In addition to the specific functions listed below, the analog input pins support 10-bit analog-to-digital conversion (ADC) using the analogRead() function. Most of the analog inputs can also be used as digital [input] pins: analog input 0 as digital pin 14 through analog input 5 as digital pin 19. Analog inputs 6 and 7 (present on the Mini and BT) cannot be used as digital pins.

Pin use plan

PinoutLCD ShieldVellemanTinkerkitUseWith 2nd Arduino
DI/O_0 (rx)   serial
DI/O_1 (tx)   serial
DI/O_2 (ir) DI1 exhaust hall sensor 
DI/O_3 (PWM,ir) DI2O5ignition hall sensor 
DI/O_4 (T0I)XDI3   
DI/O_5 (PWM,T1I)XDI4O4  
DI/O_6 (PWM)XDI5O3  
DI/O_8XRY1 vacuum relief gas injector
DI/O_9 (PWM)XRY2O2liquid mist injector 
DI/O_10(PWM) RY3O1water pump cell / gas injector loopback
DI/O_11(PWM) RY4O0cell on/off 
DI/O_12 RY5 ignition triggerwater pump mist
DI/O_13 RY6 gas injector inlet 
AI_0 / DI_14XAI1I0buttons (ignition ctrl) 
AI_1 / DI_15 AI2I1cell pressure sensor 
AI_2 / DI_16 AI3I2inlet pressure sensor / fluid level mist gen
AI_3 / DI_17 AI4I3exhaust pressure sensor / fluid level main
AI_4 / DI_18 AI5I4fluid level cell 
AI_5 / DI_19 AI6I5potmeter ignition ctrl 
AI_6 (SMD)     
AI_7 (SMD)     
  • AI = analog in
  • DI = digitial in
  • DI/O = digital input or output
  • ir : external interrupt.
  • SMD: only on SMD type boards.

Timer 1 controls digital I/O Pin 9 and 10. Timer 2 controls digital I/O Pin 3 and 11. Avoid using these.


Saving settings

Settings can be saved in the internal EEPROM in the processor, in my case 512 bytes.


A sketch that demonstrates how to load settings for your code from the EEPROM (if there are any) and how to store them.
The constant CONFIG_VERSION provides a way to determine if you ever wrote settings to the EEPROM and if it was this piece of code that did it (otherwise the code will use the default values).
When using this algorithm, please remember the following to ensure data integrity and some reliability:
    CONFIG_VERSION must be unique between different sketches;
    Also change it if you do any changes to the "StoreStruct" struct, or you will be in trouble! 

There are a number of versions at the source, but the version with the EEPROMex library appears to be the most attractive:

2012-07-30, Thijs Elenbaas
Using the EEPROMex library, the code would simplify to the following:

#include <EEPROMex.h>

// ID of the settings block
#define CONFIG_VERSION "ls1"

// Tell it where to store your config data in EEPROM
#define memoryBase 32

bool ok  = true;
int configAdress=0;

// Example settings structure
struct StoreStruct {
    char version[4];   // This is for mere detection if they are your settings
    int a, b;          // The variables of your settings
    char c;
    long d;
    float e[6];
} storage = { 
    220, 1884,
    {4.5, 5.5, 7, 8.5, 10, 12}

void setup() {
  EEPROM.setMemPool(memoryBase, EEPROMSizeUno); //Set memorypool base to 32, assume Arduino Uno board
  configAdress  = EEPROM.getAddress(sizeof(StoreStruct)); // Size of config object 
  ok = loadConfig();

void loop() {
  // [...]
  int i = storage.c - 'a';
  // [...]
  storage.c = 'a';
  if (ok)
  // [...]

bool loadConfig() {
  EEPROM.readBlock(configAdress, storage);
  return (storage.version == CONFIG_VERSION);

void saveConfig() {
   EEPROM.writeBlock(configAdress, storage);

It would also limit the number of writes in the loop statement to stop burning out the EEPROM.

The library by default has a #define for debugging and thus prevents more than 100 writes.

For my ongoing clock project, I want to persistently store some data. That is, store data that is retained after turning off the Arduino. The processor on the Arduino board comes with on-board EEPROM. In the case of the Arduino Uno, the processor is the Atmega328, equipped with 1 glorious KByte of EEPROM memory.
The AVR libraries that come with the ATmega implements a relatively broad set of functions for reading, writing and management of the EEPROM (for a description see the AVR user manual). However, the Arduino standard EEPROM library exposes only functionality for reading and writing a single byte, as described here.
This is why I wrote the EEPROMex library, an extension of the standard Arduino EEPROM library. It writes and reads basic types like bytes, longs, ints, floats & doubles. It can also read/write single bits, arbitrary data-formats and arrays. It adds debug functionality to identify and stop writing outside of the EEPROM memory size and excessive writing to prevent memory wear.
The library can be downloaded at:


Serial communication / debugging


volatile is a keyword known as a variable qualifier, it is usually used before the datatype of a variable, to modify the way in which the compiler and subsequent program treats the variable.

Declaring a variable volatile is a directive to the compiler. The compiler is software which translates your C/C++ code into the machine code, which are the real instructions for the Atmega chip in the Arduino.

Specifically, it directs the compiler to load the variable from RAM and not from a storage register, which is a temporary memory location where program variables are stored and manipulated. Under certain conditions, the value for a variable stored in registers can be inaccurate.

A variable should be declared volatile whenever its value can be changed by something beyond the control of the code section in which it appears, such as a concurrently executing thread. In the Arduino, the only place that this is likely to occur is in sections of code associated with interrupts, called an interrupt service routine.


Notice that the total period for the LED is 26.60usec. That means the off time for the LED is 23.165usec, which is 3.165usec longer than the delay statement request. Lets now look at what happens under the hood.

It may be worth addressing the ports/pins of the ATMEL chip directly instead of using the digitalWrite() and digitalRead() functions:

Avaiable interrupts

 1  Reset 
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

This also includes an example for firing sparks for an ignition system:

How long does it take to execute an ISR?

According to the datasheet, the minimal amount of time to service an interrupt is 4 clock cycles (to push the current program counter onto the stack) followed by the code now executing at the interrupt vector location. This normally contains a jump to where the interrupt routine really is, which is another 3 cycles.
Then an ISR routine (declared with the ISR define) does something like this:


That's another 16 cycles (the cycles are in brackets). So from the moment the interrupt occurs, to the first line of code being executed, would be 16 + 7 cycles (23 cycles), at 62.5 nS per clock cycle, that would be 1.4375 S. That's assuming a 16 MHz clock.
Then to leave the ISR we have this code:


That's another 19 clock cycles (1.1875 S). So in total, an ISR using the ISR define will take you 2.625 S to execute, plus whatever the code itself does.
However the external interrupts (where you use attachInterrupt) do a bit more, as follows:


I count 82 cycles there (5.125 S in total at 16 MHz) as overhead plus whatever is actually done in the supplied interrupt routine. That is, 2.9375 S before entering your interrupt handler, and another 2.1875 S after it returns.

When I count correctly, I count 47 cycles for entering and 35 for leaving, making 82 indeed.

So, there are two interrupt mechanisms:

  1. ISR define, which uses one of the system interrupts, using the name of the relevant interrupt vector (from the table earlier on), 23 cycles or 1.4375 S to enter;
  2. "External pin" interrupt, using attachInterrupt(), 47 cycles or 2.9375 S to enter.

So, these delays are totally predictable and can thus be compensated for. With a prescaler of 8, we have a delay of 23/8 = 2.875 ticks for an ISR define and 47/8 = 5.875 ticks for an external pin interrupt. However, with a prescaler of 8 and below 1000 RPM, we have to face (multiple) overflows before we can set the final ignition firing interrupt.



This tutorial shows the use of timers and interrupts for Arduino boards. As Arduino programmer you will have used timers and interrupts without knowledge, bcause all the low level hardware stuff is hidden by the Arduino API. Many Arduino functions uses timers, for example the time functions: delay(), millis() and micros() and delayMicroseconds(). The PWM functions analogWrite() uses timers, as the tone() and the noTone() function does. Even the Servo library uses timers and interrupts.


The controller of the Arduino is the Atmel AVR ATmega168 or the ATmega328. These chips are pin compatible and only differ in the size of internal memory. Both have 3 timers, called timer0, timer1 and timer2. Timer0 and timer2 are 8bit timer, where timer1 is a 16bit timer. The most important difference between 8bit and 16bit timer is the timer resolution. 8bits means 256 values where 16bit means 65536 values for higher resolution.

This library is a collection of routines for configuring the 16 bit hardware timer called Timer1 on the ATmega168/328. There are 3 hardware timers available on the chip, and they can be configured in a variety of ways to achieve different functionality. The development of this library began with the need for a way to quickly and easily set the PWM period or frequency, but has grown to include timer overflow interrupt handling and other features. It could easily be expanded upon or ported to work with the other timers.

The accuracy of the timer depends on your processor speed and the frequency. Timer1's clock speed is defined by setting the prescaler, or divisor. This prescale can be set to 1, 8, 64, 256 or 1024.

Here some nice examples, including a one-shot timer:

Below are some namespaces for easily setting up timers. They can be a bit tedious to get the various bit combinations right for the various modes.

The sketch has three "namespaces" (Timer0, Timer1, Timer2) which inside have a table of modes, and some enums for the various settings).

So for example, to set Timer 1 into mode 4 (CTC, top = OCR1A) with a prescaler of 1 (ie. no prescaler) and clearing timer output port 1A on compare you would do this:

  Timer1::setMode (4, Timer1::PRESCALE_1, Timer1::CLEAR_A_ON_COMPARE);

That is a lot easier than setting up a lot of bit patterns.

 Timer Helpers library.

Devised and written by Nick Gammon.
Date: 21 March 2012
Version: 1.0

Licence: Released for public use.



 // set up Timer 1
 TCNT1 = 0;         // reset counter
 OCR1A =  999;       // compare A register value (1000 * clock speed)

 // Mode 4: CTC, top = OCR1A
 Timer1::setMode (4, Timer1::PRESCALE_1, Timer1::CLEAR_A_ON_COMPARE);

 TIFR1 |= bit (OCF1A);    // clear interrupt flag
 TIMSK1 = bit (OCIE1A);   // interrupt on Compare A Match  


The above can be downloaded from:

Just unzip and put the TimerHelpers folder into your libraries folder.


The example code above demonstrates a one-shot timer. This sets up Timer 1 to activate a camera shutter for 62.5 uS (1000 x the clock speed of 62.5 nS), and then the interrupt service routine cancels the timer, so the shutter is only activated once.

This is more or less what we need (from the link just above):

Timer "input capture"

From the same source:

This one uses the "input capture" input to capture the value in Timer 1 at the moment an event occurs. Since the hardware captures the timer value in a separate register, this eliminates the delay caused by entering the interrupt routine (itself around 2 S).

The same source also has a table with timer connections:

Timer 0

input     T0     pin  6  (D4)

output    OC0A   pin 12  (D6)
output    OC0B   pin 11  (D5)

Timer 1

input     T1     pin 11  (D5)

output    OC1A   pin 15  (D9)
output    OC1B   pin 16  (D10)

Timer 2

output    OC2A   pin 17  (D11)
output    OC2B   pin  5  (D3)

The timer 1 capture pin is pin 11, D5, which is used by our LCD shield. :(

The first parameter I'll discuss is the speed at which the timer increments the counter. The Arduino clock runs at 16MHz, this is the fastest speed that the timers can increment their counters. At 16MHz each tick of the counter represents 1/16,000,000 of a second (~63ns), so a counter will take 10/16,000,000 seconds to reach a value of 9 (counters are 0 indexed), and 100/16,000,000 seconds to reach a value of 99.

In many situations, you will find that setting the counter speed to 16MHz is too fast. Timer0 and timer2 are 8 bit timers, meaning they can store a maximum counter value of 255. Timer1 is a 16 bit timer, meaning it can store a maximum counter value of 65535. Once a counter reaches its maximum, it will tick back to zero (this is called overflow). This means at 16MHz, even if we set the compare match register to the max counter value, interrupts will occur every 256/16,000,000 seconds (~16us) for the 8 bit counters, and every 65,536/16,000,000 (~4 ms) seconds for the 16 bit counter. Clearly, this is not very useful if you only want to interrupt once a second.

Example using timer2

In the comments an example using timer 2 for blinking led 13:

unsigned int toggle = 0; //used to keep the state of the LED
unsigned int count = 0; //used to keep count of how many interrupts were fired
byte ledpin = 13; //for testing - onboard pin
unsigned int blinkms = 0; //duration of blink

ISR(TIMER2_OVF_vect) //Timer2 Overflow Interrupt Vector, called every blinkms
  count++; //Increments the interrupt counter
  if(count > (blinkms - 1))
    toggle = !toggle; //toggles the LED state
    count = 0; //Resets the interrupt counter
  TCNT2 = 130; //Reset Timer to 130 out of 255 - 130 1 sec - 192.5 0.5 sec
  TIFR2 = 0x00; //Timer2 INT Flag Reg: Clear Timer Overflow Flag

void setup() 
  TIMSK2 = 0x00; //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  TCCR2A = 0x00; //Timer2 Control Reg A: Normal port operation, Wave Gen Mode normal

void loop() 
  if( Serial.available() )
    byte c =;
    if (c==65) //A
      Serial.println("Blink 500 ms");
    if (c==66) //B
      Serial.println("Blink 1 sec.");
    if (c==67) //C
      TIMSK2 = 0x00; //Timer2 INT Reg: Timer2 Overflow Interrupt Disable
    if (c==68) //D
      TIMSK2 = 0x00;
    if (c==69) //E
      for (unsigned int i = 0; i<1000; i++) Serial.println("TESTTESTTEST");

void blinkled(byte setpin,unsigned int microseconds)
  ledpin = setpin;
  blinkms = microseconds;
  TIMSK2 = 0x01; //Timer2 INT Reg: Timer2 Overflow Interrupt Enable

Outline of timer use.

We can practically only use timer one and two. Timer0 is been used for the timer functions, like delay(), millis() and micros(), so we don't want to use that one, unless absolutely necessary.

Timer two is an 8 bit timer, which has such a limited resolution that it cannot be used for timing the engine itself, but appears to suffice for use as "one shot" timer for firing the ignition coil for a certain period.

Timer One

This is a 16 bit timer, with can be tuned to the application with a number of pre-scalers. Our application has rather demanding and conflicting demands. For example, during starting of the engine, we may be running with a speed as low as one rotation per second, or 60 RPM. Since we have a 4 stroke engine, we need to cover at least 2 seconds during "starting". This is possible using a prescaler of 1024, but then the resolution of the timer is limited to 64 us. At 2600 RPM we already have 1 degree of rotation of the shaft within that time. At 3750 RPM, the current unloaded RPM of my engine, we only need 44 us for rotating 1 degree of the shaft. When we want to support 10,000 RPM with the board, we are looking at 17 us for 1 degree of rotation. If we want to be able to time the ignition more accurately, I would say we need a resolution of minimal 2 us for the higher speeds.

In other words: the timer needs to "shift gears" during operation. When starting, we need a big prescaler of 1024 if we want to be able to time a whole 4-stroke cycle, while running at maximum speed, we need a small prescaler because we need a higher resolution.

The idea is to use timer one for measuring the valve posisitions and then, once we know when to fire the ignition, program the comparator to give an interrupt when the ignition needs to fire, without stopping the counter, because it is very handy to measure the RPM the next time the exhaust valve is opened. Because the interrupt handler also adds a delay, we may need to account for that and have the interrupt come a bit early to compensate for the overhead of calling the interrupt handler.

It may be possible to also use timer1 for firing the ignition, but that might need a considerable amount of book-keeping and installing multiple comparator interrupt values on a running counter....

Engine timing.

There are essentially two blocks of signals that need to be timed:

  1. the inlet stroke
  2. the ignition

The timing for the inlet stroke needs to be calculated around the exhaust stroke, of which we get a signal.

Once again, there are two possibilities for calculating and setting the interrupts:

  1. at the rising edge of the exhaust valve
  2. at the falling edge of the exhaust valve

The calculations are best performed in the main loop. When performed at the rising edge of the exhaust valve, we have all the time in the world to do so, but in that case we need the RPM to be pretty stable, although the injection of the gas (and/or water fog) is not too critical in it's timing. When performed at the falling edge of the exhaust valve, we might run into trouble at high RPM.

When starting, we don't necessarily have any data regarding the current RPM. The rising edge of the exhaust valve might be the first signal we get, while we do want to be able to start the engine in that case. Fortunately, at low (starting) RPM, timing is much less critical

At 3750 RPM, the ignition signal requires about 45 degrees of rotation.

Whatever happens, the last interrupt is the one actually firing the ignition. The rising edge of the ignition might come when an injector still needs to be closed, but the edge triggering the ignition is the last interrupt we need.

Since the amount of interrupts in one cycle is fixed and we can calculate/estimate all of them on the exaust falling edge, the only one we may have to adjust at the ignition rising or falling edge, is the actual firing of the ignition itself. We can manage the interrupts by simply counting which one we already got and set a flag in the ISR that we have been interrupted. Based on that, we can set up the next one in the main loop. And if we use a buffer of size 1, we can setup the next interrupt within the ISR already.

Frequencies and data engine

Period running engine exhaust valve: 6,5 * 5 ms = 32 ms.

Period valve open: 1.5 * 5 ms = 7.5 ms.

Period main shaft thus: 16 ms.

One degree of rotation thus: 44 us.

So, the exaust valve is open for 360 * 7.5/16 = 164 degrees.

Shaft frequency: 1/16e-3 = 62.5 Hz, or 3750 RPM.


Net Power Output* 4.8 HP (3.6 kW) @ 3,600 rpm