Arduino-based fan tachometer using IR, with video, code and schematics

In 2009 I uploaded a video to YouTube showing an Arduino measuring the rotational speed of a basic computer fan. It was one of a number of YouTube videos I did as I was studying for a Masters in Computer Science. This particular module was an embedded systems programming course, with a bit of electronics thrown in. Here’s the video:

I was recently asked if the schematics were available for this video. Unfortunately I was unable to locate the schematics as shown in the video, however I do have the source code. This is a simple Arduino Sketch that uses an open source ‘Timer’ library. You can download the fan-rpm-sketch source code. When reading this source code please keeping in mind that there are three processes going on:

a) setup/loop Sketch methods – responsible for attaching interrupts, configuring the Timer, and writing the RPM count out to the serial port

b) IR ISR – this interrupt routine is configured to be triggered by the falling edge of the IR input – i.e. whenever the leading (or falling, I forget which)  fan blade <em>edge</em> passes the sensor

c) A Timer routine that is attached to a high speed timer and called every second to calculate the RPM value extrapolated from the number of fins seen that second.

I also have schematics and documentation for the newer application – a Temperature Controlled Fan:

The difference between the two is the addition of two modules to the layout – the thermistor temperature sensor, and the opto-isolated pulse-width-modulated speed controller. On the software side, we now use FreeRTOS on Arduino.

To edit the schematic, you may download the free Schematic Capture package, Bsch3V.

As a guideline to the above, this block diagram shows the main parts of the design:

Block diagram for the fan controller circuit

Parts List

a)    Thermistor – taken from a Lithium ion battery pack, not even sure the specs on it !
b)    IR LED and IR phototransistor – Radioshack 276-0142
c)    Fan – standard 80mm 12V two-lead (no tach) computer fan
d)    7.5V power – 1A AC-DC adapter used for Arduino and Fan power instead of a battery pack
e)    CNY 75B opto-coupler

Except for a few edits, the text that follows was mostly written in 2009.

Hardware Discussion

The use of an opto-coupler is separates the low voltage Arduino digital IO from the slightly higher voltage used to power the Arduino board and the Fan. Potentially any voltage is supported up to the limits of the CNY 75B part.

Use of this component required a low current limiting resistor to generate an acceptable voltage drop over the thermistor, so a 4.7k was chosen.

IR LED and Transistor
The IR transistor from radio shack required a large bias resistance to place it in ‘switching’ mode rather than amplification mode. A 100K ohm resistor was chosen. An approximate 100 ohm current limiting resistance for the IR LED was provided through two 47ohm resistors in serial.

Fan and Power choice
Using the 12V fan in conjunction with a 7.5V power source is not optimal but it works. The current required to feed the base of the NPN transistor is obviously changed by the lowered voltage, so the resistances used needed to be different. A 4.7k ohm resistor was placed in parallel with a 10k ohm resistor which appears to provide a faster response from the fan ( I assume due to lowered switch time of the NPN transistor ).


System Software Block Diagram
Free RTOS software block diagram for a fan controller Design Discussion

The system software is built upon FreeRTOS but is wrapped in a C++ layer. All tasks inherit from a task class that provides wrappers around functions applicable to tasks,  including creation, stack and priority configuration, and the ability to invoke a derived (virtual) method that is the actual task code provided by the implementor of the derived class.

In addition to tasks, there is one other class to wrap the configuration of timer 2 as a PWM output. The combination of timer 2 and output compare B means that we are driving pin 3 as the PWM output.

The C++ layer follows the arduino sketch model in three regards:
a)    The use of a setup and loop function for task code
b)    The provision of implicitly available functions – basically anything wrapped by the task base class
c)    The code is built entirely within the sketch tool

However, it needed to implement a global override of the new operator to call pvPortMalloc – the FreeRTOS implementation. In addition I had to place dummy overrides for pure virtual and guard C++ runtime implementation functions.

The original setup method is used to both initialize all tasks in the system – configure the hardware to some base state, and then invoke the FreeRTOS Scheduler. If something goes wrong, it falls through to the original arduino sketch loop method, which is implemented to provide a distinct LED pattern so that this error condition can be seen.

After implementing the C++ layer, however, it became readily apparent that something was wrong, as the stability of the software was variable depending on the order in which tasks were created, whether they were created in FreeRTOS.  What is provided is a working version, but only after much trial and error. Changes to stack sizes, queue sizes, serial debug messages, adding of tasks, or changing the order of tasks and hardware initialization is not recommended! I suspect but cannot be sure that the combination of heap, stack, and static allocations conflict in some non-trivial way. (edit: based on later experiences, I have to see it is actually quite trivial. If you run out of stack of heap memory, you’ll have trouble. If the stack expands onto the heap, you’ll have trouble. Also, you don’t have much to start with, and working in C++ you can easily be fooled that you don’t have to worry about this. You do.)

Were I to implement this project again, I would either go with Eclipse or WinAVR as the build tool and eliminate the use of Arduino functions like the serial engine, or I would strip the C++ layer out to minimize overhead on the Arduino.

RPM Counter

Internally the software is using an interrupt triggered by the IR phototransistor on digital pin 2 to monitor the fan RPM count. A task converts the count to an RPM measure and writes it to the serial every second. A semaphore is used to  synchronize the interrupt ISR with the RPM task.

Originally I tried to go with a counting semaphore, but the FreeRTOS download I am using complained that it does not exist. I did not want to resort to a mutex semaphore on an integer so instead I went with a recursive semaphore. The recursive semaphore allowed itself to be taken as many times as the interrupt is fired, so the task keeps taking the semaphore until it blocks. It waits a maximum of 1 second updated each time the semaphore is taken, so the task is able to block and yet still sum up the counts at the end of each second.

Analog / Thermal / Sense task

This variously named task reads the analog reading on analog port 0 which is hooked up to the thermistor. This value is scaled, inverted, and biased back from a 0-1023 range, of which 900+ is ice and 673 is room temperature back to a zero to 255 value that can be used to set the PWM duty period.

timer2pwm object

This singleton object is used to manage the timer2 output compare B configuration which drives the PWM period on digital pin 3, which drives the fan. Use of timer 2 does not conflict with analog readings or with the FreeRTOS timer tick interrupt.

Serial  task/class

The serial task exposes functions that are intended to be called from other tasks. They place values onto internally managed FreeRTOS queues which are processed once a second by the serial task code. This centralizes access to the serial bus.

Download the source code: arduino-fan-temperature-controller-freertos

Update; added source code for a Sketch version using TimerOne: