MicroFAN (Part 3)

Part 1, 2, 3

This part of the subject deals with the code. Nothing exceptional except that it combines multiples libraries: PlainLCD which drives the LCD display, PlainENCi which drives the rotary encoder, PlainTMP for the temperature sensor and PlainEEPROM which handles the erasable memory functions for storing default parameters.

Nota: Using equivalent libraries may not cause major problems for the programmers

There are no major programming complexities in this code, as long as you leave the human interface handling functions untouched. This code is an other example of use of an LCD and a rotary encoder to drive a human interface based on menus. Next are the key components of this specific part of the code:

Declaration of constants:

/* Menu types */
#define MNU_TYP_HEADER 		0x00
#define MNU_TYP_MOD_VALUE 	0x01
#define MNU_TYP_FXD_VALUE 	0x02
#define MNU_TYP_FLD_BACK 	0x03
uint8_t X = 0x00; /* Any value managed by menu driver */
/* Standard captions */
#define CAP_PARAM 		"PARAM."
#define CAP_EXIT 		"EXIT"
#define CAP_RETURN 		"RETURN"
/* Application related captions */
#define CAP_FIRMWARE 	"MicroFAN"
#define CAP_TEMP 		"TEMP."
#define CAP_HIST 		"HISTER."
#define CAP_CTRL 		"CONTROL"
#define CAP_DISPLAY 	"DISPLAY"
#define CAP_STATISTICS 	"STATS"
#define CAP_SETTINGS 	"SET"
#define CAP_MAX_TEMP 	"T MAX"
#define CAP_MIN_TEMP 	"T MIN"
#define CAP_RESET 		"RESET"
#define CAP_FAN 		"FAN"

 

And now the structure corresponding to each line in the architecture of the menu

struct mnuItem { 
	uint8_t menuType; /* One of MNU_TYP_x */
	int16_t minValue; /* min value */
	int16_t maxValue; /* max value */
	uint8_t nextMenuIndex; /* Next menu item */
	uint8_t lastMenuIndex; /* Must be set to 0 as default */
	char *caption;  /* Pointer to menu caption */
};

followed by the architecture of the menu dedicated to MicroFAN:

struct mnuItem vMnuItems[] =	
{ 
/* 	{type, 				min, 		max,		next,	last, 	caption}		*/
	{MNU_TYP_HEADER,	X, 			X, 			X,		X,		CAP_FIRMWARE}, 				
	{MNU_TYP_HEADER,	0, 			3, 			2, 		X,		CAP_PARAM},
	/* Main menu */
	{MNU_TYP_HEADER,	0, 			2, 			6, 		X, 		CAP_SETTINGS},
	{MNU_TYP_HEADER,	0, 			1, 			9,		X, 		CAP_CTRL},
	{MNU_TYP_HEADER,	0, 			3, 			11,		X, 		CAP_STATISTICS},
	{MNU_TYP_FLD_BACK, 	X, 			X, 			0,		X, 		CAP_EXIT},
	/* Settings */
	{MNU_TYP_MOD_VALUE,	MIN_SP,		MAX_SP,		2,		X, 		CAP_TEMP},	
	{MNU_TYP_MOD_VALUE,	MIN_HIST,	MAX_HIST,	2,		X, 		CAP_HIST}, 
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_RETURN},
	/* Control */
	{MNU_TYP_MOD_VALUE, CTRL_OFF,	CTRL_AUTO, 	3,		X, 		CAP_FAN},
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_RETURN},	
	/* Statistics */
	{MNU_TYP_FXD_VALUE, MIN_TEMP, 	MAX_TEMP, 	4,		X, 		CAP_MIN_TEMP},
	{MNU_TYP_FXD_VALUE, MIN_TEMP, 	MAX_TEMP, 	4, 		X, 		CAP_MAX_TEMP},
	{MNU_TYP_MOD_VALUE, NO, 		YES, 		4,		X, 		CAP_RESET},
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_RETURN}	
};

An other critical part of the code is the main loop that I will describe in more details

void loop(void) 
{
	/* Manage user interfce */
	MenuDriver();
	/* Periodical temperature measurement and fan control */
	_now = millis();
	if ((_now - _lastTime) >= _interval) {
		_lastTime = _now;
		/* Run measurement */
		_currentTemperature = (TMP.Temperature() * 10);
		/* Record min and max temperatures */
		if (_currentTemperature < _minTemp) {
			_minTemp = _currentTemperature;
		}
		if (_currentTemperature > _maxTemp) {
			_maxTemp = _currentTemperature;
		}	
		/* Control fan */
		if (_fanControl == CTRL_AUTO) {
			if (_currentTemperature > _tempSetPoint) {
				Fan(CTRL_ON);
			} else if (_currentTemperature < (_tempSetPoint - _hysteresis)) {
				Fan(CTRL_OFF);
			}
		} else {
			Fan(_fanControl);
		}
		LCD.PrintFloat((_currentTemperature / 10.0), 1, 1); 
		LCD.ClearBuffer();
		LCD.InsertString("FAN", 1, LCD_ALI_LEFT);
		LCD.InsertString(vOffOnAuto[_fanState], 5, LCD_ALI_LEFT);
		if (_fanControl != CTRL_AUTO) {
			LCD.InsertString("*", 8, LCD_ALI_LEFT);
		}
		LCD.PrintBuffer(2);
	}
}

On top of the list comes the management of the human interface: as often as possible the code looks for an request from the user. Then comes the management of periodical events, written in a way which prevents millis() rollover problems.

If it is time to run the temperature control, read the sensor temperature and record statistics (min and max, just for fun). Instead of directly applying the temperature to a test box, I added the possibility of forcing the fan. In CTRL_AUTO mode, the fan is ON if the temperature is higher than the set-point and vice versa. However, if the fan is in CTRL_OFF or CTRL_ON mode, it will follow the order whatever the temperature.

Then come a couple of lines for displaying the current temperature and the fan state (a star in the right bottom of the screen means that the fan is in CTRL_AUTO mode).

An other interesting part of the code deals with storage and reading of parameters in EEPROM:

/* Write data */
void WriteDefaultParameters(void)
{
	EEPROM.Address(0);
	EEPROM.WriteInt16(_tempSetPoint);
	EEPROM.WriteInt16(_hysteresis);
	EEPROM.WriteUInt8(_fanControl);
}      

/* Read data */
void ReadDefaultParameters(void)
{
	/* Read data */
	EEPROM.Address(0);
	_tempSetPoint = EEPROM.ReadInt16();
	/* Check data */
	if (_tempSetPoint < MIN_SP) {
		_tempSetPoint = MIN_SP;
	} else if (_tempSetPoint > MAX_SP) {
		_tempSetPoint = MAX_SP;
	}
	_hysteresis = EEPROM.ReadInt16();
	/* Check data */
	if (_hysteresis < MIN_HIST) {
		_hysteresis = MIN_HIST;
	} else if (_hysteresis > MAX_HIST) {
		_hysteresis = MAX_HIST;
	}
	_fanControl = EEPROM.ReadUInt8();
	/* Check data */
	if (_fanControl < CTRL_OFF) {
		_fanControl = CTRL_OFF;
	} else if (_fanControl > CTRL_AUTO) {
		_fanControl = CTRL_AUTO;
	}
}

Again, nothing exceptional, but some new comers may get some ideas on what to do when reading from the EEPROM, as on the first attempt to read from EEPROM, unexpected data may corrupt their code.

If you like the code, you may get it as per my code request policy

Make of the day

Back to the 3D prints again with a spectacular tourbillon !

tourbillon

A must see for all fans of clocks… and 3D prints

And that’s not all, Christoph LAIMER put all the stl files on thingiverse !

 

MicroFAN (Part 2)

Part 1, 2, 3

Let’s talk about some basic electronics. MicroFAN is really easy and requires few affordable electronic components: a 12V fan, Arduino (Uno, Nano, etc.), a 8×2 LCD (a 16×2 LCD will do the job too), a rotary encoder, typically a 30 pulse 15 detents per round (so as to say 1 cycle per detent) will be great, a N-Channel MOSFET: an IRF540 or equivalent will definitely be overrated but it’s OK because you  obviously have one in your scrap box and it will allow you to drive a  dozen of fans like a charm. Plus some extra components.

 

g2522

Connecting the LCD is almost trivial. You may skip the potentiometer and wire VEE to ground. Wiring the rotary encoder is trivial. The fan driver consists in a hot spot (Vin, or +12V in our case) connected to the positive pole of the fan. The negative pole is switched (or not) by the MOSFET transistor. Although the 1K resistor in serial with the transistor gate is not mandatory, it may prevent cries in case of wiring errors. The power may come from any wall plug adapter feeding MicroFAN with a stable +12V under a few hundred milliamps (250 or more will be just fine for driving one fan).

For your records, here is the pin-out for the TMP04 temperature sensor:

tmp04

What else ?

The best is probably to use an Arduino proto-shield to hard-wire components. I use this principle a lot so that I can swap arduino boards from one application to an other without having to rewire the required components. I suggest that you choose the bare board which is cheap compared to the assembled one (x4 more expensive):

caa0b3efac4b6ffb7e70a85e5c0ed350.image.538x354

and a 36 male pins header (2.54 mm) like this:

header

that you can get for less than 1€ each in quantities.

Next post on same subject

 

Web site of the day

Music has always been part of my life. And the means to listen to music has been a long way. From the 3 transistors amplifier taken from a broken toy record player to the A&R Cambridge amplifier. From the early shoe box home made speaker covered with wood looking adhesive up to the Magneplanar speakers.

So that when my youngest daughter told me that she was looking for a pair of speakers for her new apartment, I started thinking about something special. What about starting from a white page, right from the fundamentals of acoustics ? Ideally the best way to install a speaker is to mount it on a wall separating the volume where the listener seats and an equivalent dead volume. The problem is that the architecture of few houses comply with this type of configuration… The good news is that it is possible to compromise this requirement as long as we can prevent acoustic short circuiting using a large enough baffle around the driver.

Hum getting lost with these terms ? Never mind. There is great web site describing and explaining in a very scientific manner all about “Design and tuning of loud speakers“. It is written in french by a French audio enthusiast.

Quick and dirty glossary of terms:

  • Driver: So as to say the pressure generator. This pressure may be generated by the linear displacement of a cone membrane, a flat membrane, a compressed ribbon, air suppression (ionization), etc.
  • Acoustic short circuit: Isolating the front and the back of the driver by means of baffle or enclosure prevents the high pressure generated in front of speaker during positive activation to equilibrate with the low pressure conditions at the back of the driver
  • Baffle mounting surface of the driver separating the front and the back of the driver.

Here are some examples of implementation:

Starting point

IMG_20151025_094752_598

Cute little ones

28bd3660dba72846a9ba69caed31baa8

Basic and strong looking

open_baffle_-_better_1

Real large baffle aren’t they ?

T2WlRkXlBXXXXXXXXX_!!101631887

Real large boomers !

bac14e5020c2c0c13aef206a26d04ae5

Incremental rotary encoders (Part 9)

Part 1234567, 8, 9

Next diagrams show the signal patterns from the A and B switches. The “d” sign shows the rest position of the encoder after a detent. As mentioned above, the diagrams deal with rotary encoders  featuring one cycle per detent. Under the phase plots are the bit values for A and B switches. Just under is the horizontal time table (e.g. BA BA BA BA) and the corresponding binary ( e.g.  10 00 01 11) and decimal (e.g. 2 0 1 3) value coded by the A and B switches.

CLOCKWISE

    --> Time
B                d               d
    ---         -------         -------           
       |       |       |       |       |          
        -------         -------         -------   
A                d               d
            -------         -------         --- 
           |       |       |       |       |    
    -------         -------         -------     
    -------------d---------------d------------- 
B    1   0   0   1   1   0   0   1   1   0   0  
A    0   0   1   1   0   0   1   1   0   0   1  

--> Time
BA BA BA BA
10 00 01 11
2  0  1  3 


COUNTER-CLOCKWISE

    --> Time
B        d               d               d
    -------         -------         -------     
           |       |       |       |       |    
            -------         -------         --- 
A        d               d               d      
        -------         -------         ------- 
       |       |       |       |       |        
    ---         -------         -------         
    -----d---------------d---------------d----- 
B    1   1   0   0   1   1   0   0   1   1   0  
A    0   1   1   0   0   1   1   0   0   1   1  

--> Time
BA BA BA BA
01 00 10 11 
1  0  2  3

In the previous versions of PlainENCi, the successive states are buffered and the content of the buffer is compared to the reference pattern: e.g. in CW mode, the code was comparing the buffer content with 0x87 (10000111 in binary).

But !

In the real life, the readings from the encoder are more likely to look like… 10 00 10 00 10 00 01 00 01 00 01 11 01 11 ! The slower the rotational speed of the encoder the higher the probability of such bits stammer. This is the consequence of severe bouncing which prevents the code from achieving proper match.

Note: Adding RC filters (R=10 kΩ, C=10 nF) attached to the A and B outputs and ground as specified by some rotary encoder makers do not help .

alps_encoder_1

As I was unhappy with the lack of ruggedness from my last algorithm, I reworked the code. The major difference lies in the decoding of the successive encoder states. Taking the previous example, the code will expect 10 and then 00 and then 01 and ultimately 11 whatever comes in between each expected state. The CW/CCW decision is made while reading the first state: 10 means CW, 01 means CCW. Next lines of code constitute the heart of the decoding process.

	switch(encoderState) {
	case 0x00:
		if (stateCounts == 1) {
			stateCounts = 2;
		}
		break;
	case 0x01:
		if (stateCounts == 0) {
			step = -1;
			stateCounts = 1;
		} else if (stateCounts == 2) {
			stateCounts = 3;
		}
		break;
	case 0x02:
		if (stateCounts == 0) {
			step = 1;
			stateCounts = 1;
		} else if (stateCounts == 2) {
			stateCounts = 3;
		}
		break;
	case 0x03:
		if (stateCounts == 3) {
			stateCounts = 4; 
		} else {
			stateCounts = 0;
		}
		break;
	}

Once 4 consecutive valid states are read, the algorithm runs the boost function which increases the number of counts per detent depending upon the rotational speed of the encoder. This part also contains some code which prevent the counter to override the min and max limits.

	if (stateCounts == 4) {
		uint32_t now = millis();
		uint32_t elapsedTime = (now - lastTime);
		lastTime = now;
		if (elapsedTime < _encBoostCutOff) {
			step *= (1 + (((_encBoostFactor - 1) * (_encBoostCutOff - elapsedTime)) / _encBoostCutOff));
		}
		_encCounts += step;
		if (_encCounts > _encMaxCounts) {
			_encCounts = _encMaxCounts;
		} else if  (_encCounts < _encMinCounts) {
			_encCounts = _encMinCounts;
		}
		stateCounts = 0;		
	}

Next plot illustrates the counts per detent or clicks if you like while rotating the encoder using a x100 boosting factor. As you can see, it takes only 360 detents (12 turns) to raise the counter from 0 to 10000 while still being able to count 1 by 1 when turning the knob slowly. You can clearly see the acceleration/deceleration of the rotational speed from the encoder shaft.

alps_encoder_2

The result of this rework is fully satisfactory: whatever the rotational speed, the encoder gives the expected result. And the code remains pretty light and simple. The sad news is that I tried to transpose this principle to the “half cycle per detent” rotary encoders and I did not manage to achieve full reliability. So that I made the brutal however understandable decision to release this version which is only compatible with “one cycle per detent” encoders. This library was tested using Alps and Bourns encoders.

Next are some links to rotary encoder datasheets:

For two weeks, you can download the new version of the library directly from > here <

MicroFAN (Part 1)

Part 1, 2, 3

We gained quite a lot of experience after printing few kilometers of ABS on our Makerbot 2x. Controlling the temperature inside the cabinet solved a lot of problems. After few attempts, we decided to modify the printer by adding a fan on top of the transparent cover. The fan is turned on when a certain temperature is read by a good’ol TMP04 digital sensor. Nothing spectacular and a simple Arduino Uno + a dedicated shield made the job for some months. Then I decided that this prototype was ugly and was lacking few nice functions such as the possibility to change the temperature setting, the hysteresis and a forced mode.

As many visitors are interested in rotary encoders, I decided to build a new device which would feature a LCD, a rotary encoder all packed together in the smallest and cutest enclosure. A few hundred clicks and a few grams of ABS later the box was sketched and printed.

micro_fan_3

As you can see, MicroFAN features a small however very convenient LCD with 2 lines of 8 characters. May be you can distinguish the vertical fins on the cover for the dissipation of heat.

Note: Before buying a LCD, check if the extra fonts match your language !

micro_fan_2

A apertures gives way to Arduino connectors and to the wires connected to the shield,

micro_fan_1

The enclosure features 4 ABS parts (Bottom, cover, front and button) and only 1 screw ! This is a really minimalist design. I am happy to share the plans that you can download >here<

micro_fan_4

Next post on same subject

 

Hacky new year, makyier vœux !

Oops, bubbles of champagne are still burbling in my brain. I wish you good luck and full achievement of your projects in 2016.

french_tech

At the present time, a team of 4 members of HL2 group (The company that I co-funded 3 years ago) is attending the CES in las VEGAS (Hall G, booth 82122) along with the large delegation of the French Tech.

Arduinoos will keep on being a place for sharing information, ideas, code, schematics, news, tricks, etc. I will also give you some feedback about Quai-Lab, a local FabLab that I also co-funded along with Sébastien Bonnifet (Head of Cevad and Creatic which is managing the logistics of the PlainDSP kits) and some other techno enthusiast. You may meet Quai-Lab during the now famous Gamer Assembly next Easter in Poitiers / France. As a side effect of my Presidency of the Calibra-Classic car club, you may hear about the DOCbox project (Diagnstic Opel Calibra box) a nice little autonomous tool which will read the specific early ODB 1 (aka ALDL) ECUs from GM…

Enough to keep a man busy 😉

 

Xmas presents

Well I hope all of you received at least a little present from their relatives.

40 years ago I got a very special Xmas present. Packed in a slim box was a bunch of strange pieces of metal and plastic (looking) nicely presented in a carved polystyrene shell.

0930

“Of course, Phlips wouldn’t leave you with a bunch of components and a good luck!”, if I may paraphrase Limor Fried ! A well written book was providing the young apprentice with some theory and clean plans to build your own applications. The radio receiver was probably number 1 on my wish list at this time.

Building the circuit was pretty easy thanks to a very clever design. You would just put the diagram on a perforated wooden plate and use special springs and clips to attach components legs.

 

ee1003th

After building an optical barrier, a buzzer and some other fancy applications came the wild, risky time for experimentation. I noticed that glass packed diodes where looking like lamp bulbs and I wondered if they would react the same. Well, the diode did, but once and in a flash ! Ooooops, this was my first experience at burning an electronic component . The problem was that I had no idea on how to replace it: where would I get a spare one, at which cost and above all, which reference should I ask for ? This is how I learned about the BOMs !

Because I could not get help from home and because I wanted to be a clock maker at this time, layers of dust soon covered the box which probably disappeared many years ago when mother managed to tidy up my old stuff (she would have called that m…) when I left home for university. However this Philips kit gave me a first taste of what electronics was about and how components where working together.

40 years later, Arduino took over the lack of kits and proposes its lovely stater kit which in many ways matches the spirit of the Philips EE kits. A good idea for a present isn’t it ?

trousse-demarrage-arduino_1

Thoughts of the day

Recent news struck some deep thoughts which have been rambling on my mind for a long time. A new tycoon announces that he will join the the gozinerous section from the gozillionaires club. That is great.

And I mean it. Spending money on education, welfare, better life is indisputably knowledgeable.

What questions me is the model. Consume petawatts of power dissipated in the blue sky (for what ?) and then care about the little birds, drill in people minds and then care about their education (did you read 1984 lately ?), benefit from infrastructures and spend $$$ in financial consultancy for not paying taxes. Is this this the model ?

Silently, many, many other highly knowledgeable entrepreneurs run their business with a day to day concern for environment, education and public needs. From the beginning. I prefer this model.

Light a eCandle

Against the darkness of all dangers, fears and sadness, keep a eCandle light.

Take a piece a paper, a resistor, a LED, the following lines of code and light a eCandle. This may be a ePrayer for the victims of the vicious terrorist attacks in Paris.

/* Constants */
const int16_t _maxDutyCycle = 256;
/* Custom variables */
volatile uint8_t *_outputPort = &PORTB;
uint8_t _outputPin = PINB5;
int16_t _absMax = (_maxDutyCycle / 1);
int16_t _absMin = (_maxDutyCycle / 4);
uint16_t _interval = 2;
/* Application variables */
volatile uint8_t _pwmOutputPinMask = (1 << _outputPin);
uint8_t _defTCCR2B;
int16_t _min = _absMin;
int16_t _max = _absMax;
	
void setup(void)
{  
	/* Initialize PWM */
	InitializePWM();
	/* Set default values */

}


/* Fliker LED attached to the output pin */
void loop(void) 
{
	_max = random(_min, _absMax);
	for (int16_t i = _min; i <= _max; i++) {
		DutyCycle(i);
		delay(_interval);
	}
	_min = random(_absMin, _max);
	for (int16_t i = _max; i >= _min; i--) {
		DutyCycle(i);
		delay(_interval);
	}
}


/* Set up output pin and timer2, clockRate at 5 */
void InitializePWM(void)
{
	cli();
	/* Set data direction */
	*(_outputPort - 1) |= _pwmOutputPinMask; 
	/* Set timer 2, Mode 3, aka Fast PWM mode */
	TCCR2A = (1 << WGM20) | (1 << WGM21); 		
	/* Set timer 2 prescaler */
	TCCR2B = 0x05;
	/* Record default value */
	_defTCCR2B = TCCR2B;
	/* Output Compare Match A Interrupt Enable and Overflow Interrupt Enable */
	TIMSK2 = (1 << OCIE2A) | (1 << TOIE2);
	sei();
}

void DutyCycle(int16_t dutyCycle)
{
	if (dutyCycle <= 0) {
		dutyCycle = 0;
		/* Stop clock */
		TCCR2B = 0x00; 
		/* Set output pin to low state */
		*(_outputPort) &= ~_pwmOutputPinMask; 
	} else if (dutyCycle >= _maxDutyCycle) {
		dutyCycle = _maxDutyCycle;
		/* Stop clock */
		TCCR2B = 0x00; 
		/* Set output pin to high state */
		*(_outputPort) |= _pwmOutputPinMask; 
	} else {
		/* Set trigger level for next compare cycle */
		OCR2A = dutyCycle; 
		/* resume clock */
		TCCR2B = _defTCCR2B; 
	}
}

/* Invoked after TCNT2 overflows */
ISR(TIMER2_OVF_vect)
{	
	/* Set output pin to high state */
	*(_outputPort) |= _pwmOutputPinMask;  
}


/* Invoked after TCNT2 equals COMPA */
ISR(TIMER2_COMPA_vect)
{
	/* Set output pin to low state */
	*(_outputPort) &= ~_pwmOutputPinMask;  
}

You may customize your own candle using the custom variables: _outputPort for any port,
_outputPin for any pin from this port, _absMax absolute max intensity, _absMin absolute min intensity and flickering intervals in milliseconds.