I have a model Stirling engine (Böhm Stirling Technik HB13) and a steam engine (Wilesco D10), and I have been curious about how fast they run (rpm). After some not so successful attempts, I managed to build a solution that works very well.
The Sensor
The idea is to use an IR-diode and an IR-transistor (it basically lets electric current through when being exposed to IR light), and mount these on opposite sides of some part of the engine that moves. First a device especially built for the Stirling engine:
Here is a picture of a sensor designed to work with the steam engine:
It works just fine with the Stirling engine too:
The electronic part is quite simple. I am going to power the sensor and pick up the signal with an Arduino so I design for 5V:
- The Diode is connected in series with a 330 Ohm resistor. Anode (long) to 5V and cathode (short) to ground.
- The IR Transistor has two connectors: Collector and Emitter. I connected Emitter (long) to ground. The Collector (short) was connected to 5V via a 10kOhm resistor.
Now the collector can be used as an IR detector: LOW (close to 0V) means there is an IR source present and HIGH (close to 5V) means there is no IR source.
5V --------+------------------+----------------+ | | | 330 Ohm 10k Ohm 330 Ohm | | | | | Indicator LED | | | | +----------------+---- measure here | | IR Diode ~IR~> IR Transistor | | Ground ----+------------------+
The Indicator LED (and its 330 Ohm resistor) is optional. It will be ON when the IR light from the Diode reaches the IR transistor. It will be OFF when IR beam is blocked.
The Arduino
I decided to use an Arduino to pick up the signal and display the speed. I bought the official Arduino Start kit which contained everything I needed (except the IR Diode and IR Transistor):
There is no indicator LED in this design. What you see in the picture from the Starter kit:
- Arduino UNO
- breadboard
- 16×2 Character Display
- 1 potentiometer (to adjust contrast of display)
- 2 buttons (for a little menu)
- cables (some are not from the starter kit)
- resistors
- a little wooden board to mount the UNO and breadboard on
When everything worked I wanted to make my device more permanent and make my prototyping Arduino UNO available for other projects. I bought an Arduino Nano clone and other things from DealExtreme and put it all together in a more compact design:
This design has two input ports for the two different sensors I built (each having their own indicator LED).
Signal Noise
Based on earlier experiences (see Failed designs below) my primary concern was signal noise. Basically (the sensor is HIGH when IR beam is blocked):
Ideal signal HIGH: ------- ------ LOW: ------------------- ------------------- ------------------ My fear HIGH: - -- --- - - - - LOW: ------------------ - - - ----------------- - ---- -----------------
For this reason I made a configurable max-rpm-value (like 2000 rpm), which in turn could be turned into a silent/numb period during which I ignore any signal:
=WAITING============== ! =NUMB===== =WAITING===== ! =NUMB===== =WAITING====
It turned out however, that for this (IR Diode/Transistor) design, the signal was very close to perfect.
Analog, Digital and Interrupt
The analog reality is of course that my input signal was not going to be perfectly 0 or 5 volts. I was not really sure that Digital or Interrupt input would work fine. Analog (10 bit) input would be in the range [0,1023].
Actual ranges were not completely consistent but could be [16,680] or [180,890] or [200,825]. In the last case 825 was with a paper between the Diode/Transistor. Hiding the Transistor inside a box raised the value from 825 to 1015.
Background IR radiation and other factors clearly play in here, but not enough to cause any problems. It turns out in practice both Digital and Interrupt work just fine.
I anyway built my device and program in such a way that I could choose between Digital, Analog and Interrupt input.
Sensor Performance
A rotation speed of 1200 rpm equals 20Hz and 50ms per revolution. If the HIGH is short (5ms) compared to the LOW (45ms) a sampling interval less than 5ms is required, to not fail to detect a revolution (unless I rely on interrupts).
The Steam engine flywheel has five spokes, each being much smaller than the space between them (see picture above). To measure five beats per revolution would require at approximately 50 samples per revolution, and at 1800 rpm this equals 1500Hz or 600 microseconds (us) per sample.
The transistor itself is fast. Arduino indicates that the speed of AnalogRead is 100us, and digital read is supposed to be faster (about 30us faster, it seems). The main loop must (worst case) complete in ~500us:
loop() { readInput(); processInput(); outputResultToLcd(); }
At 16Mhz and an 8bit CPU, this does not allow for a lot of wasteful input analysis.
Display Performance
The worst performance problem turned out to be the 16×2 character display. Initially I updated it a few times per second with code like:
LCD.clear(); LCD.setCursor(0,0); LCD.print("rpm="); LCD.print(rpmval); LCD.setCursor(0,1); // first character, second row LCD.print("something else");
This typically takes 12ms. Even with the spoke-less Stirling engine this design broke down at 1500rpm.
I ended up having two character arrays (16 bytes each). Whenever I wanted to update the output I just wrote to these two arrays. In each loop() iteration, I then called LCD.write() (at most) once in the end (writing just one character per iteration). This method still updates the display much faster than it is capable to turn the pixels on/off, but avoiding the LCD.clear() improves the visual impression. This is clearly not Arduino Best Practice.
There are I2C-compatible 16×2 displays: it would be interesting to know if they are faster or slower than the display I got with the Starter kit.
I am also thinking about sending output via serial. This will clearly require some thinking at 9600bps (unless the Arduino has some serial buffer working in the background).
Optimization
Optimizing for the Arduino is a bit different from optimization on Linux or Windows: since there is no context switching and no other processes, wasting CPU cycles does not matter: minimizing the maximum loop() time is everything, even if it means a lot of unnecessary operations are performed during the quicker loops.
In the end my loops (analog input) took from ~30us to ~600us. A bit simplified, the loop:
- Reads sensor input (~100us, but not when “numb”)
- Reads button 1 input
- Reads button 2 input
- Analyzes sensor inputer
- Analyzes button input do decide how to respond to user action
- Changes state
- Reformats LCD output based on input and state
- Outputs LCD output (~300us)
- Updates the internal LED (#13)
My overall optimization strategy was to perform as few of these steps as possible every iteration, to minimize the maximum delay between reading sensor input.
Apart from that, performance is of course about datastructures and algorithms (as usual). For the Arduino, also the datatype matters: 32 bit division is not for free on an 8 bit micro processor (and finding time intervals, average speeds and things like that requires division). No FPU anywhere.
I first wrote my program in C style rather than C style, as in:
C style: timer_update(&timer); C++ style: timer.update();
I later rewrote my program creating proper C++ classes. This had essentially no cost whatsoever, neither on memory usage nor on performance, but I have to agree that the C++ got cleaner and easier to read.
Source Code
I uploaded the source code to DropBox. I would of course have wanted to clean it up, document and comment it better before publishing it. But this project has taken some time already, and I doubt it will happen, so I publish it as is instead. Feel free to drop comments or questions below.
Power consumption
My device seems to use about 45mA when powered with a 9V battery. With the sensor plugged out it was down to 25mA.
45mA at 9V gives 400mW and 200 Ohms.
Rotation Speeds
The Stirling engine starts slowly at about 300rpm. It runs for about 30 minutes, and can reach maximum speeds of just over 2000rpm (that typically happens after 20-25 minutes). A little extra heating (just burning a match in the flame) makes a big difference. I don’t know why it runs faster and faster with time and if this is normal.
The steam engine quickly reaches 2000rpm or a bit more, and then slows down during the 10 minutes it typically runs.
Both engines could probably be pushed a bit more, but I don’t want a catastrophic failure, especially not with the steam engine.
Failed designs
I have failed to measure rotation speed of my Stirling engine before.
Mobile applications blinking quickly can be used to determine rotation speed (with the right setting, a rotating wheel will look like it is still). I was not satisfied with this.
I recorded the sound (noise) of the Stirling engine and spent a few days writing a program trying to analyze the signal do determine the frequency of the engine. I should have:
- used a much better microphone to reduce noise
- used FFT or something like that, instead of trying to invent some heuristics like I did
In the end, it kind of gave me the speed, or I could interpret the speed from the output I got. One uncertainty here is that it is not entirely clear if the engine makes one, two (or more) noise-bursts per revolution.
I built an electrical sensor, basically connecting the engine to ground, and every revolution letting a moving part of the engine touch a piece of metal, connected to an Arduino input. This was not entirely unsuccessful, but the design had its disadvantages:
- Noise, and sometimes no signal at all
- Interferes with engine mechanically
- At high speeds, sensor has to be moving (back) fast enough
I did not try to use a photoresistor because they are clearly too slow for my purposes.