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

Leave a Reply

You must be logged in to post a comment.