Tilt Sensor (Part 9)

Part 12345678, 9

RIP my first Adafruit Industries ADXL335 based accelerometer! I used it so much and in so many agressive situations that I managed to break it down! Now, why bother designing a PCB, wasting my time and burning my fingers? I ordered a brand new accelerometer from Adardfruit, via Snootlab.

 

The new version comes with a built in 3.3 V regulator which will avoid overvoltatge conditions for those who play with multiple chips like the ADXL103-203 which is 5 Volts compliant. The PCB is small, has a rich pin out. However, I am wondering why Adafruit Industries decided to leave only 2 holes, which are small, no to say, too small. Strange.

After preparing a new extension cable, I decided to revamp a little bit the original code. I also decided to make an extensive use of the Serial Monitor from Arduino in order to make it usable for most users, without he need for LCD display and rotary encoder.

NB: The wiring from the above schematics corresponds to the use of a bare ADXL335 which must be fed by 3.3 V. The Adafruit board shall wired to 5 V.

The only additional component that you will have to request is  PlainEEPROM library which contains pre-programmed functions for reading and writing all sorts of data types (e.g. bytes, integers, vectors)

Here is the revised code, fully tested, that I am happy to share with you. HTH

 

/*

	MicroTLT, a three axis tilt angle measurement system
	Use the serial monitor to display data
	Copyright (C) 2012 Didier Longueville

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

/* 
Hardware in use: ADXL335 3 axis accelerometer
The output of the ADXL335 is wired to ground via 15K resistors 
which create a 1/3 bridge divider with the internal 32K resistor
The output is now around 110mV/g @ VSS = 3.3 V 
In this way the full dynamic of adc can be used, using the internal 1.1 V reference
*/

#include <PlainEEPROM.h>
#include <math.h>

#define MNU_CAP_CAL_FIXED "FIXED"  
#define MNU_CAP_CAL_MANUAL "MANUAL"  
#define MNU_CAP_DSP_ACCELERATION "ACCELERATION" 
#define MNU_CAP_DSP_ANGLES "ANGLES"  
#define MNU_CAP_DSP_BIA_DATA "BIASED DATA"  
#define MNU_CAP_DSP_OFFSET "OFFSET"  
#define MNU_CAP_DSP_RAW_DATA "RAW DATA" 
#define MNU_CAP_DSP_GAIN "GAIN"  
#define MNU_CAP_DSP_MAX_TLT_ANGLES "MAX TILT ANGLES"  
#define MNU_CAP_DSP_MIN_TLT_ANGLES "MIN TILT ANGLES" 
#define MNU_CAP_DSP_TILTS "TILTS" 

#define MNU_CAP_SELECT "PARAMETERS" 
#define MNU_CAP_TLT_DETECTION "TILT DETECTION"  
char *vCAP_NO_YES[] = {"NO", "YES"};
char *vCAP_CHANNELS[] = {"X", "Y", "Z"};

#define MSG_CALIBRATING "CALIBRATING..."
#define MSG_COMPUTING "COMPUTING..."
#define MSG_DONE "DONE"
#define MSG_TLT_NONE "NO TILT"
#define MSG_TLT_X "TILT IN X DIR."
#define MSG_TLT_Y "TILT IN Y DIR."
#define MSG_UPLOADING "UPLOADING..." 
#define MSG_CAL_X_DIRECTION "X DIRECTION" 
#define MSG_CAL_Y_DIRECTION "Y DIRECTION" 
#define MSG_CAL_Z_DIRECTION "Z DIRECTION" 
#define MSG_CAL_MEASURING "MEASURING..." 
#define MSG_CAL_RESETING "RESETING..." 
#define MSG_CAL_CMD_UNKNOWN "UNKNOWN CMD..." 

#define _channelX 0
#define _channelY 1
#define _channelZ 2
#define _channels 3
#define _offsetDef 530.0 /* Default value in Mv */
#define _gainDef 110.0 /* Default value in mV / G */

/* Single character commands */
enum {
	CMD_RAW_DATA = 'r',
	CMD_BIA_DATA = 'b',
	CMD_ACCELERATION = 'g',
	CMD_TLT_ANGLES = 'a',
	CMD_OFFSET = 'O',
	CMD_GAIN = 'G',
	CMD_MAX_TLT_ANGLES = 'M',
	CMD_MIN_TLT_ANGLES = 'm',
	CMD_TILTS = 't',
	CMD_CALIBRATE = 'C',
	CMD_RESET_PARAMETERS = 'R',
	CMD_SAMPLES = 'S',
	CMD_PAUSE = 'P',
	CMD_HELP = '?'
};

/* Display modes */
enum {
	DSP_RAW_DATA = 0,
	DSP_BIA_DATA,
	DSP_ACCELERATION,
	DSP_TLT_ANGLES,
	DSP_OFFSET,
	DSP_GAIN,
	DSP_MAX_TLT_ANGLES,
	DSP_MIN_TLT_ANGLES,
	DSP_TILTS
};

/* Set default display mode */
uint8_t _dispMode = DSP_TLT_ANGLES;
/* Tilt detcteion */
#define TLT_DET_NO 0
#define TLT_DET_YES 1
uint8_t _tiltDetection = TLT_DET_NO;
uint8_t _tiltDetectThreshold = 5;
uint8_t _tiltDetectRequest;
uint8_t vTiltDetected[_channels]; /* For Hysteresis management*/
/* Global variables */
/* Dafault values */
uint16_t _samplesDef = 64;
uint16_t _pauseDef= 500;
/* */
uint16_t _samples = _samplesDef;
uint16_t _pause = _pauseDef; /* In ms */
uint32_t _nextTick;

uint8_t vAdcPins[_channels] = {0, 1, 2}; /* Any from the 6 analog pins */
double vOffsets[_channels]; /* Reference values in millivolts */
double vGains[_channels]; /* In mV/g : depends on sensor and VSS*/
double vRawData[_channels]; /* mV values */
double vOffData[_channels]; /* mV values */
double vAccData[_channels]; /* G values in Gs */
double vTiltAng[_channels]; /* Tilt angles in degrees */
double vMaxTiltAng[_channels]; /* Max tilt angles in degrees */
double vMinTiltAng[_channels]; /* Min tilt angles in degrees */
double vActTiltAng[_channels]; /* Actual tilt angles in degrees */
double vTilts[_channels]; /* Changes */
/* Memory locations for default parameters */
const uint8_t MEM_LOC_OFFSET = 0; /* 3 times 4 bytes */
const uint8_t MEM_LOC_GAIN = 12; /* 3 times 4 bytes */
const uint8_t MEM_LOC_TLT_DET_THRESHOLD = 24; /* 1 time 1 byte */
const uint8_t MEM_LOC_SAMPLES = 28; /* 1 time 2 bytes */
const uint8_t MEM_LOC_PAUSE = 32; /* 1 time 2 bytes */
/* Next memory location is 40 */

void setup() 
{
	Serial.begin(115200);
	analogReference(INTERNAL); /* 1.1V on Arduino UNO */
	ReadDefParameters();
	Serial.println("Ready");
	BlinkLed(3, 150);
};

void loop() 
{
	uint32_t now = millis();
	if (now >= _nextTick) {
		_nextTick = (now + _pause);
		AcquireData(_samples);
		Display(_dispMode);
	}	
	ParseCommands();
};

void ParseCommands(void)
/* Simple command paser */
{
	if (Serial.available()){
		while (Serial.available()){
			uint8_t command = Serial.read();
			switch (command){
			case CMD_RAW_DATA:
				Serial.println(MNU_CAP_DSP_RAW_DATA);
				_dispMode = DSP_RAW_DATA;
				break;
			case CMD_BIA_DATA:
				Serial.println(MNU_CAP_DSP_BIA_DATA);
				_dispMode = DSP_BIA_DATA;
				break;
			case CMD_ACCELERATION:
				Serial.println(MNU_CAP_DSP_ACCELERATION);
				_dispMode = DSP_ACCELERATION;
				break;
			case CMD_TLT_ANGLES:
				Serial.println(MNU_CAP_DSP_ANGLES);
				_dispMode = DSP_TLT_ANGLES;
				break;
			case CMD_OFFSET:
				Serial.println(MNU_CAP_DSP_OFFSET);
				_dispMode = DSP_OFFSET;
				break;
			case CMD_GAIN:
				Serial.println(MNU_CAP_DSP_GAIN);
				_dispMode = DSP_GAIN;
				break;
			case CMD_MAX_TLT_ANGLES:
				Serial.println(MNU_CAP_DSP_MAX_TLT_ANGLES);
				_dispMode = DSP_MAX_TLT_ANGLES;
				break;
			case CMD_MIN_TLT_ANGLES:
				Serial.println(MNU_CAP_DSP_MIN_TLT_ANGLES);
				_dispMode = DSP_MIN_TLT_ANGLES;
				break;
			case CMD_TILTS:
				Serial.println(MNU_CAP_DSP_TILTS);
				_dispMode = DSP_TILTS;
				break;
			case CMD_CALIBRATE:		
				Calibrate();
				break;
			case CMD_RESET_PARAMETERS:
				Serial.println(MSG_CAL_RESETING);
				ResetParameters();
				Serial.println(MSG_DONE);
				break;
			case CMD_SAMPLES:
				Serial.println(_samples);
				break;	
			case CMD_PAUSE:
				Serial.println(_pause);
				break;
			case CMD_HELP:
				DisplayCommandsList();
				break;
			default:
				Serial.println(MSG_CAL_CMD_UNKNOWN);
			}
		}
	}
};

double AnalogReadMv(uint8_t channel, uint16_t samples) 
/* Read analog values, compute mean and convert digital value in mV value*/
{
	uint32_t sumOfADCs = 0;
	for (uint8_t i = 0; i < samples; i++) {
		sumOfADCs += analogRead(*(vAdcPins + channel)); /* Sum measurements */
	}
	double meanValue = (double(sumOfADCs) / samples);
	meanValue *= 1100.0; /* Convert to millivolts */
	meanValue /= 1024.0;
	/* Retuned value */
	return(meanValue);
};

void AcquireData(uint16_t samples) 
{
	/* Acquire raw data */
	for (uint8_t i = 0; i < _channels; i++) {	
		*(vRawData + i) = AnalogReadMv(i, samples);
		/* Offset data */
		*(vOffData + i) = (*(vRawData + i) - *(vOffsets + i));
	}	
	/* Compute acceleration in Gs */
	double maxAcc = 0.0; /* Reset max acceleration */
	for (uint8_t i = 0; i < _channels; i++) {
		*(vAccData + i) = *(vOffData + i) / *(vGains + i);
		maxAcc += square(*(vAccData + i)); /* Sum squared accelerations */
	}
	/* Compute the resulting maximum G value which should be one */
	maxAcc = sqrt(maxAcc); 
	/* Normalize acceleration */
	for (uint8_t i = 0; i < _channels; i++) {
		*(vAccData + i) /= maxAcc ;
	}
	/* Compute angles */
	for (uint8_t i = 0; i < _channels; i++) {
		/* Compute angles in radians */
		switch (i) {
		case _channelX: 
			*(vTiltAng + i) = atan(*(vAccData + _channelX) / (sqrt(square(*(vAccData + _channelY)) + square(*(vAccData + _channelZ)))));
			break;
		case _channelY: 
			*(vTiltAng + i) = atan(*(vAccData + _channelY) / (sqrt(square(*(vAccData + _channelX)) + square(*(vAccData + _channelZ)))));
			break;
		case _channelZ:
			*(vTiltAng + i) = atan(sqrt(square(*(vAccData + _channelX)) + square(*(vAccData + _channelY))) / *(vAccData + _channelZ));
			break;
		}
		/* Convert radians in degrees */
		*(vTiltAng + i) *= 180.00;
		*(vTiltAng + i) /= M_PI;
	}
	/* Record min max */
	for (uint8_t i = 0; i < _channels; i++) {
		if (*(vTiltAng + i) < *(vMinTiltAng + i)) {
			*(vMinTiltAng + i) = *(vTiltAng + i);
		}
		if (*(vTiltAng + i) > *(vMaxTiltAng + i)) {
			*(vMaxTiltAng + i) = *(vTiltAng + i);
		}
	}
	/* Detect changes */
	if (_tiltDetection == TLT_DET_YES) {
		for (uint8_t i = 0; i < _channels; i++) {
			if ((*(vTiltDetected + i) == 0x00) && (abs(*(vActTiltAng + i) - *(vTiltAng + i)) > double(_tiltDetectThreshold))) {
				*(vTiltDetected + i) = 0x01;
				*(vTilts + i) += 1.0;
			} 
			else if ((*(vTiltDetected + i) == 0x01) && (abs(*(vActTiltAng + i) - *(vTiltAng + i)) < double(_tiltDetectThreshold))) {
				*(vTiltDetected + i) = 0x00;
			}
		}
	}
};

void PresetTiltDetection(void) 
{
	/* Enable tilt detection */
	for (uint8_t channel = 0; channel < _channels; channel++) {
		*(vTilts + channel) = 0.0; /* Reset tilt counts */
		*(vActTiltAng + channel) = *(vTiltAng + channel); /* Set actual angle value */
		*(vMinTiltAng + channel) = *(vActTiltAng + channel); /* Set default max tilt angle */
		*(vMaxTiltAng + channel) = *(vActTiltAng + channel); /* Set default min tilt angle */
		*(vTiltDetected + channel) = 0x00;
	}	
};

void Calibrate(void) 
/* Calibrate inputs */
{
	uint16_t readingTime = 3000;
	double vMax[_channels];
	/* Clear previous data */
	for (uint8_t i = 0; i < _channels; i++) {	
		*(vOffsets + i) = 0.0;
	}
	Serial.println(MSG_CALIBRATING);
	delay(readingTime);
	/*  */
	Serial.println(MSG_CAL_X_DIRECTION);
	delay(readingTime);
	Serial.println(MSG_CAL_MEASURING);
	*(vMax + 0) = AnalogReadMv(0, _samples);
	*(vOffsets + 1) +=  AnalogReadMv(1, _samples);
	*(vOffsets + 2) +=  AnalogReadMv(2, _samples);
	/*  */
	Serial.println(MSG_CAL_Y_DIRECTION);
	delay(readingTime);
	Serial.println(MSG_CAL_MEASURING);
	*(vOffsets + 0) += AnalogReadMv(0, _samples);
	*(vMax + 1) =  AnalogReadMv(1, _samples);
	*(vOffsets + 2) +=  AnalogReadMv(2, _samples);
	/*  */
	Serial.println(MSG_CAL_Z_DIRECTION);
	delay(readingTime);
	Serial.println(MSG_CAL_MEASURING);
	*(vOffsets + 0) += AnalogReadMv(0, _samples);
	*(vOffsets + 1) +=  AnalogReadMv(1, _samples);
	*(vMax + 2) =  AnalogReadMv(2, _samples);
	/*  */
	Serial.println(MSG_DONE);
	delay(readingTime);
	for (uint8_t i = 0; i < _channels; i++) {
		*(vOffsets + i) /= 2.0;
		*(vGains + i) = (*(vMax + i) - *(vOffsets + i));
	}
	free(vMax);
	/* Save calibration data */
	WriteDefParameters();
};

/* Display */

void Display(uint8_t type) 
/* Display selected data type */
{
	switch(type) {
	case DSP_RAW_DATA: DisplayData(vRawData, 0); break;
	case DSP_BIA_DATA: DisplayData(vOffData, 0); break;
	case DSP_ACCELERATION: DisplayData(vAccData, 2); break;
	case DSP_TLT_ANGLES: DisplayData(vTiltAng, 0); break;
	case DSP_OFFSET: DisplayData(vOffsets, 0); break;
	case DSP_GAIN: DisplayData(vGains, 0); break;	
	case DSP_MAX_TLT_ANGLES: DisplayData(vMaxTiltAng, 0); break;	
	case DSP_MIN_TLT_ANGLES: DisplayData(vMinTiltAng, 0); break;		
	case DSP_TILTS: DisplayData(vTilts, 0); break;	
	}
};

void DisplayCommandsList(void)
{
	Serial.print("Display raw data: ");
	Serial.println(CMD_RAW_DATA);
	Serial.print("Display biased data: ");
	Serial.println(CMD_BIA_DATA);
	Serial.print("Display accelerations: ");
	Serial.println(CMD_ACCELERATION);
	Serial.print("Display angles: ");
	Serial.println(CMD_TLT_ANGLES);
	Serial.print("Display offsets: ");
	Serial.println(CMD_OFFSET);
	Serial.print("Display gains: ");
	Serial.println(CMD_GAIN);
	Serial.print("Display max tilt angles: ");
	Serial.println(CMD_MAX_TLT_ANGLES);
	Serial.print("Display min tilt angles: ");
	Serial.println(CMD_MIN_TLT_ANGLES);
	Serial.print("Display nuber of tilts: ");
	Serial.println(CMD_TILTS);
	Serial.print("Calibrate: ");
	Serial.println(CMD_CALIBRATE);
	Serial.print("Reset parameters: ");
	Serial.println(CMD_RESET_PARAMETERS);
	Serial.print("Samples: ");
	Serial.println(CMD_SAMPLES);
	Serial.print("Pause: ");
	Serial.println(CMD_PAUSE);
	Serial.print("Help: ");
	Serial.println(CMD_HELP);
};

void DisplayData(double *ptrData, uint8_t decimals) 
/* Display data from channel vectors */
{
	for (uint8_t i = 0; i < _channels; i++) {
		Serial.print(*(vCAP_CHANNELS + i));
		Serial.print(": ");
		Serial.print(*(ptrData + i), decimals);
		Serial.print("  ");
	}
	Serial.println();
};

void ReadDefParameters() 
/* Read Default parameters */
{
	for (uint8_t i = 0; i < _channels; i++) {
		/* Get offsets from previous calibration*/
		*(vOffsets + i) = EEPROM.ReadDbl32((MEM_LOC_OFFSET + (i * sizeof(double))));
		// *(vOffsets + i) = constrain(*(vOffsets + i), 0.0, 1100.0);
		/* Get gains from previous calibration*/
		*(vGains + i) = EEPROM.ReadDbl32((MEM_LOC_GAIN + (i * sizeof(double))));
		// *(vGains + i) = constrain(*(vGains + i), 0.0, 10000.0);
	}
	/* Tilt detection threshold */
	_tiltDetectThreshold = EEPROM.ReadUInt8(MEM_LOC_TLT_DET_THRESHOLD);
	_tiltDetectThreshold = constrain(_tiltDetectThreshold, 0, 90);
	/* Default pause */
	_pause = EEPROM.ReadUInt16(MEM_LOC_PAUSE);
	_pause = constrain(_pause, 10, 2000); /* Keep read value inside allowed range */
	/* Default samples */
	_samples = EEPROM.ReadUInt16(MEM_LOC_SAMPLES);
	_samples = constrain(_samples, 1, 128); /* Keep read value inside allowed range */
};

void WriteDefParameters() 
/* Save default parameters */
{
	/* Gains and ooffsets */
	for (uint8_t i = 0; i < _channels; i++) {
		EEPROM.WriteDbl32((MEM_LOC_OFFSET + (i * sizeof(double))), *(vOffsets + i));
		EEPROM.WriteDbl32((MEM_LOC_GAIN + (i * sizeof(double))), *(vGains + i));
	}
	/* Tilt detection threshold */
	EEPROM.WriteUInt8(MEM_LOC_TLT_DET_THRESHOLD, _tiltDetectThreshold);
	/* Default pause */
	EEPROM.WriteUInt16(MEM_LOC_PAUSE, _pause);
	/* Default samples */
	EEPROM.WriteUInt16(MEM_LOC_SAMPLES, _samples);
};

void ResetParameters(void)
{
	for (uint8_t i = 0; i < _channels; i++) {
		*(vOffsets + i) = _offsetDef;
		*(vGains + i) = _gainDef;
		*(vMaxTiltAng + i) = 0.0;
		*(vMinTiltAng + i) = 0.0;
		*(vTilts + i) = 0.0;
	}
	_samples = _samplesDef;
	_pause = _pauseDef;
}

void BlinkLed(uint8_t blinks, uint16_t interval) 
{
	DDRB |= (1 << PINB5);
	PORTB &= ~(1 << PINB5);
	for (uint8_t i = 0; i < (blinks << 1); i++) {
		PORTB ^= (1 << PINB5); 
	}
};

 

 

Leave a Reply

You must be logged in to post a comment.