User Interface (Part 5)

Part 1234, 5

I brought several improvements since the introduction of the early design of a simplified however yet powerful human interface. Among them the possibility to display variables that the user can change and variables or constants that the user cannot change. Thus the introduction of a new set of menu types:

/* Menu types */
const uint8_t MNU_TYP_HEADER = 0x00;
const uint8_t MNU_TYP_MOD_VALUE = 0x01;
const uint8_t MNU_TYP_FXD_VALUE = 0x02;
const uint8_t MNU_TYP_FLD_BACK = 0x03;

These new data types are the MNU_TYP_MOD_VALUE type  and the MNU_TYP_FXD_VALUE type.

The way the menuItem structure has been changed too in order to ease the readability of the whole menu structure. The new structure format looks like:

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 */
	const char *ptrCap;  /* Pointer to menu ptrCap */
}; 

where:

/* 
menuType: 	one of MNU_TYP_xxx,
minValue: 	min value (Base0), 
maxValue: 	max value (Base0),
nextMenuIndex: 	next sub menu index (Base0),
lastMenuIndex:	last menu index, for returning to previous level (Base0),
ptrCap: 	pointer to caption 
*/

But the most significant change applies to the storage of arrays of characters (so as to say, strings). These memory consuming data are stored in the program memory instead of a storage in the SRAM. As their content keeps constant along the firmware usage, they are eligible to program memory storage. For your records, the SRAM memory is 2k wide in the UNO boards (featuring ATMEGA 328P micro-controllers) while the program memory is 32k wide, which is x16 times larger !

Storing and reading constant data in the program memory requires that the following steps are completed:

Declare pgmspace in the header section of the main code

#include <avr/pgmspace.h> /* Progmem */

Declare the arrays of characters in the header section: these are extracted from the unreleased MicroHTR application (heating element controller)

const char CAP_P_FIRMWARE[] PROGMEM =		"**  MicroHTR  **";
const char CAP_P_VERSION[] PROGMEM =		"    rev. 1a";
/* Application related constants */
const char CAP_P_SP[] PROGMEM = 		"SET POINT";
const char CAP_P_KP[] PROGMEM = 		"KP";
const char CAP_P_KI[] PROGMEM = 		"KI";
const char CAP_P_KD[] PROGMEM = 		"KD";
const char CAP_P_INT[] PROGMEM = 		"INTERVALS";
const char CAP_P_STATISTICS[] PROGMEM = 	"STATISTICS";
const char CAP_P_MISCELLANEOUS[] PROGMEM =	"MISCELLANEOUS";
const char CAP_P_TEMPERATURE[] PROGMEM = 	"TEMPERATURE";
const char CAP_P_MAX_TEMP[] PROGMEM = 		"TEMP. MAX.";
const char CAP_P_MIN_TEMP[] PROGMEM = 		"TEMP. MIN.";
const char CAP_P_RESET[] PROGMEM = 		"RESET";
const char CAP_P_SETTINGS[] PROGMEM = 		"SETTINGS";
const char CAP_P_PRN_DATA[] PROGMEM = 		"PRINT DATA";
/* Captions, reserved words for menu driven interface (do not change) */
const char CAP_P_PARAM[] PROGMEM = 		"PARAMETERS";
const char CAP_P_EXIT[] PROGMEM = 		"EXIT";
const char CAP_P_RETURN[] PROGMEM = 		"RETURN";

 

and here is the structure for the menu:

struct mnuItem vMnuItems[] =	
{ 
/* 	{type, 				min, 		max,		next,	last, 	caption}*/
	{MNU_TYP_HEADER,	X, 			X, 			X,		X,		CAP_P_FIRMWARE}, 				
	{MNU_TYP_HEADER,	0, 			3, 			2, 		X,		CAP_P_PARAM},
	{MNU_TYP_HEADER,	0, 			5, 			6, 		X, 		CAP_P_SETTINGS}, /* Main menu */
	{MNU_TYP_HEADER,	0, 			3, 			12,		X, 		CAP_P_STATISTICS},
	{MNU_TYP_HEADER,	0, 			1, 			16,		X, 		CAP_P_MISCELLANEOUS},
	{MNU_TYP_FLD_BACK, 	X, 			X, 			0,		X, 		CAP_P_EXIT},
	{MNU_TYP_MOD_VALUE,	MIN_SP,		MAX_SP,		2,		X, 		CAP_P_SP}, /* Settings */
	{MNU_TYP_MOD_VALUE,	MIN_KP,		MAX_KP,		2,		X, 		CAP_P_KP}, 
	{MNU_TYP_MOD_VALUE,	MIN_KI,		MAX_KI,		2,		X, 		CAP_P_KI}, 
	{MNU_TYP_MOD_VALUE,	MIN_KD,		MAX_KD,		2,		X, 		CAP_P_KD}, 
	{MNU_TYP_MOD_VALUE,	MIN_INT, 	MAX_INT,	2, 		X, 		CAP_P_INT}, 
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_P_RETURN},
	{MNU_TYP_FXD_VALUE, MAX_TEMP, 	MIN_TEMP, 	3,		X, 		CAP_P_MIN_TEMP}, /* Statistics */
	{MNU_TYP_FXD_VALUE, MAX_TEMP, 	MIN_TEMP, 	3, 		X, 		CAP_P_MAX_TEMP},
	{MNU_TYP_MOD_VALUE, NO, 		YES, 		3,		X, 		CAP_P_RESET},
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_P_RETURN},	
	{MNU_TYP_MOD_VALUE, NO, 		YES,		4,		X, 		CAP_P_PRN_DATA}, /* Miscellaneous */	
	{MNU_TYP_FLD_BACK,	X, 			X, 			1, 		X, 		CAP_P_RETURN}	
};

Within the menu handler routine, the lines

LCD.PrintArrayOfChar(vMnuItems[menuIndex].ptrCap, 1); /* Display menu ptrCap on first line */

LCD.PrintArrayOfChar(vMnuItems[vMnuItems[menuIndex].nextMenuIndex + counts].ptrCap, 2); /* Display menu caption */

become

LCD.PrintArrayOfChar(P(vMnuItems[menuIndex].ptrCap), 1); /* Display menu ptrCap on first line */

LCD.PrintArrayOfChar(P(vMnuItems[vMnuItems[menuIndex].nextMenuIndex + counts].ptrCap), 2); /* Display menu caption */

thanks to this little function:

char* P(const char* ptr) 
{	
	strcpy_P(vCapBuffer, ptr);
	return(vCapBuffer);
}

In this way, you will be able to save hundreds of bytes from the SRAM which is good news to the Arduino’s developers.

Problem solving

/* No comments */

problem_solving

Tips and Tricks (Part 27)

Previous T&T

This T&T deals with the compression of numerical data. Using the proposed algorithm, you will be able to compress a 16 bits unsigned integer (so as to say an uint16_t or an unsigned int data type). into an 8 bits unsigned integer (so as to say an uint8_t or an unsigned char data type). How come ?

Well, this algorithm takes advantage of the principle of operation of integer to float conversion. On completion of the compression algorithm, we get an 8 bits integer from which the 4 most significant bits (msb) contain the exponent and the 4 least significant bits (lsb) contain the mantissa. This arrangement has a drawback: the loss of precision. Next figure illustrates the errors which result from of a compression/decompression cycle:

error_plot

These errors (up to 6%) are the price to pay for compressing data ranging from 0 to 65.535 down to a small compact integer ranging from 0 to 255 that uses only 2 bytes of memory…

Next diagram illustrates the way compression works

compress

and this one illustrates the way decompression works

decompress

These diagrams are inspired by the Application Note AN498 from Silicon Labs, related to the Si114X light sensor.

Next is the code containing the compress and the decompress functions:

/* Compress an unsigned 16 bits integer into an unsigned 8 bits integer */
uint8_t Compress(uint16_t data)
{
	uint8_t res;
	if (data == 0) {
		res = 0x00;
	} else if (data == 1) {
		res = 0x0F;
	} else {
		uint8_t exponent = 15;
		while((data & 0x8000) != 0x8000){
			data <<= 1; /* Shift all bits one step to the left */
			exponent -= 1;
		}
		uint8_t msb = exponent;
		/* Right align and mask the 4 fraction bits  */
		uint8_t lsb = ((data >> 11) & 0x0F);	
		/* Compute result */
		res = ((msb << 4) | lsb);
	}
	/* Returned value */
	return(res);	
}

/* Uncompress an unsigned 8 bits integer into an unsigned 16 bits integer */
uint16_t Uncompress(uint8_t data)
{
	/* Extract bits */
	uint8_t lsb = (data & 0x0F);
	uint8_t msb = ((data >> 4) & 0x0F);
	/* Compute value */
	float fraction = ((float)lsb / 16.0);
	if (msb != 0) {
		/* Applies to compressed 0s and 1s */
		fraction += 1.0;
	}
	float exponent = (1UL << msb);
	uint16_t res = round(exponent * fraction);
	/* Returned value */
	return(res);
}

Note: Please check carefully the way the compressor manages the “0” and “1” values which have special behaviors in the domain of multiplication.

Enjoy !

Cool stuff of the day

Time-Sandglass-icon

Although very handy for basic applications, you may soon or later want to get rid of the basic timing functions from Arduino and start using advanced timing featuring better accuracy and avoid time consuming waiting loops.

There are several approaches to achieve this goal. Firstly, you may explore the web and try to find some information of interest. I you are lucky, you may find pages such as this one: Secrets of Arduino PWM or this one Les Timers. If you are patient and used to read advanced technical publications, you may just get a copy of the ATMEGA 328 microcontroller data sheet and read all about timers.

Alternatively, you may learn about timers by doing ! This on line code generator does that very well. All you need to do is to set the base frequency of your micro-controller (typically 16 MHz for UNO boards, 8 MHz for some low power boards) and the requested frequency. As a result of the calculations, the page displays the code necessary to drive any of the three timers from the ATMEGA 328.

Nota: Keep in mind that timer0 is used by Arduino for the timing functions such as millis() or micros().

And here is the link to this nice web site: Arduino Timer Interrupts Calculator

HTH

Tips and Tricks (Part 26)

Previous T&T

Building a tracking device is no big deal for the Arduino enthusiasts. All you need is an Arduino board (e.g. a UNO), a GPS shield and a GSM shield.

P1170993

Too bad, we have a problem here because of the pin-out of the shields. The GSM shield uses pin 2 and 3 for the Rx and Tx connexions with the mode and pin 7 is used as the “Reset pin” which is responsible for remotely switching on and off the modem. Note that the “Power” button next to the SIM card does the same… manually.

On the other hand, the Adafruit “Data logger” shield features multiple functions such as an RTC, a SD card reader and a GPS chip. And this GPS shield uses pin 8 and 7 for Rx and Tx connexions. Blam ! Pin 7 is now overburdened.

Here is a simple turnaround which will allow you to plug both shields with very little modifications.

Hardware

P1170997

Remove the solder strap next to pin 7 and solder a wire between any other available pin (e.g. pin 6) and the live side of the conductive layer.

Software

Get to the GSM library folder (typically: C:\Program Files (x86)\arduino\libraries\GSM\src) and apply two modes on files GSM3IO.h and GSM3ShieldV1.cpp. Both initially feature a line which looks like:

#define __RESETPIN__ 7

that you will change a comment like that

#define __RESETPIN__ 6 /* Reset pin changed from 7 to 6 by DL 20160809 */

Save both files and compile your code

Done.

HTH

Next Tip and Trick

analogRead alternative (Part2)

Part 1, 2

Starting from the presentation of the parametric version of an alternate analogRead function, here is a lean version of the whole code. The header section of the code

/* Sampling parameters */
uint32_t _interval = 200; /* Interval between two consecutive ADC reading cycles */
uint32_t _lastTime;

Next comes the ‘setup’ routine

void setup(void)
{
	/* Initialize the ADC */
	InitADC(0);
	/* Console */
	Serial.begin(115200);
	/* Set the strating time */
	_lastTime = 0;
}

It contains the initialization parts of the ADC, console and timer. This section followed by the ‘loop’ routine

void loop(void)
{
	uint32_t _now = millis();
	/* Check elapsed time. This method handles millis overflow properly */
	if ((_now - _lastTime) >= _interval) {
		_lastTime = _now;  
		Serial.print(_now / 1000.0, 1);
		Serial.print(';');
		Serial.print(ADCRead());
		Serial.println();
	}
}

Every time the millis() is equal or more than the interval time, the loop routine prints the time (in seconds) and the raw ADC counts. And here are the two ADC related functions for acquiring analog signals in the fastest possible mode. Keep in mind that under these conditions, the output of the AnalogRead function ranges from 0 to 256 (@ 5 V)

/* Initilalize analog read in fast mode and low resolution */
uint8_t InitADC(uint8_t channel)
{
	/* Reset register contents */
	ADCSRA = 0;
	ADCSRB = 0; 
	ADMUX = 0;
	/* Set voltage reference */
	ADMUX |= (1 << REFS0); /* DEFAULT VCC, 5 V */
	/* left align ADC value to 8 bits from ADCH register */
	ADMUX |= (1 << ADLAR);
	/* Set channel */ 
	ADMUX |= channel;
	/* Set pre-scaler */
	ADCSRA |= (1 << ADPS1);
	/* Enable ADC */
	ADCSRA |= (1 << ADEN);
	/* Start first conversion */
	ADCSRA |= (1 << ADSC); 
	/* Wait for conversion */
	while (ADCSRA & (1 << ADSC));
}


/* Simple analog to digital conversion */
uint16_t ADCRead(void)
{
	/* Start conversion */
	ADCSRA |= (1 << ADSC); 
	/* Wait for conversion */
	while (ADCSRA & (1 << ADSC));
	/* Returned ADC value */
	return(ADCH);
}

Ultimately, if you really want to stick to the original analogRead format, here is the ‘advanced” ADCRead function:

/* Simple analog to digital conversion with analog channel parameter */
uint16_t ADCRead(uint8_t channel)
{
	/* Clear any lower 4 bits from the ADMUX register */
	ADMUX &= ~(0x0F)
	/* Set channel */
	ADMUX |= channel;
	/* Start conversion */
	ADCSRA |= (1 << ADSC); 
	/* Wait for conversion */
	while (ADCSRA & (1 << ADSC));
	/* Returned ADC value */
	return(ADCH);
}

 

Amazing 3D printing

As a big fan of 3D printing, I had a lot of fun watching this video

In the next future, this is what we will hear from NASA records:

  • “Hello Houston, we have a problem”
  • “How can we help ?”
  • “Could you email us a cable clip so that we can tidy up this harness”
  • “Yep, you’ll get in within the next five minutes”
  • “In white please”

analogRead alternatives (Part1)

Part 1, 2

Long story short: I never met wizards before Valerio knocked on quai-lab‘s door. And we all committed to help him to ‘augment’ the experience of his magic puppets. However, it’s long road from puppets to bytes and bits. But Valerio is not this sort of person who gives up easily and this post is a quite exhaustive answer to a question that I recently rose. Enjoy !

The analogRead is a very popular function among the basic standard functions from the Arduino programming environment. In just one command and one parameter it allows data acquisition from any from the 6 (or more) analog ports from an Arduino board. Fair enough for most applications. However, some applications may require some tweaks or fine tuning that analogRead cannot stand. This post describes what is behind the scene of analog to digital conversion on Arduino UNOs. Using the nuts and bolts from this post, you will be able to tailor your own analog readings using lower resolution and lower latency. These tunings may be very useful in applications where reading accuracy is not issue while reading time is an issue: e.g. reading a potentiometer position within a loop.

Achieving the ultimate performances has a price, and the price to pay is few lines of code. I decided to split the ADC reading operation in two distinctive parts: ADC initialization and ADC reading. Initializing the ADC is requires careful programming of 3 registers:  ADCSRA,  ADCSRB and ADMUX. Next are the few, almost self explanatory, lines of code that you may insert in your ADC initialization routine:

uint8_t InitADC(uint8_t channel, float reference, uint8_t resolution, uint8_t prescaler)
{
 /* Reset register contents */
 ADCSRA = 0; /* Consequently clears ADEN */
 ADCSRB = 0; 
 ADMUX = 0;
 /* Set voltage reference */
 if (reference == REF_VCC) {
 ADMUX |= (1 << REFS0); /* DEFAULT 5 V */
 } else if (reference == REF_INTERNAL) {
 ADMUX |= (1 << REFS1) | (1 << REFS0); /* REF_INTERNAL 1.1 V */
 }
 _reference = reference;
 _resolution = resolution;
 if (_resolution == RES_LOW) {
 /* left align ADC value to 8 bits from ADCH register */
 ADMUX |= (1 << ADLAR);
 }
 /* Set channel */ 
 ADMUX |= channel;
 /* Set pre-scaler */
 if (prescaler < 2) {
 prescaler = 2;
 } else if(prescaler > 7) {
 prescaler = 7; 
 }
 _prescaler = prescaler;
 ADCSRA |= _prescaler;
 /* Enable ADC */
 ADCSRA |= (1 << ADEN);
 /* Start first conversion */
 ADCSRA |= (1 << ADSC); 
 /* Wait for conversion */
 while (ADCSRA & (1 << ADSC));
}

Firstly, we will reset the content of the 3 registers by writing ‘0x00’s in each of them. Doing so we disable the ADC by clearing the ADEN bit. Then we will set ADMUX content, starting with the reference voltage setting. As most of you know, you may use Vcc (so as to say 5V) or a builtin reference voltage (1.1V) or read the voltage at the Vref input. As this post is intended for people with intermediate knowledge, I skipped the Vref option as it requires a good understanding of ADC in order to prevent irrecoverable mistakes. However it is easy from the table below to adapt the code to your needs:

adc_02

The reference setting is recorded in a global variable (_reference) as it will be needed in future code. In the same register, we will set the bits alignement. Let’s keep it simple. If you require a 0 to 256 range of ADC counts, use this option which I gave the constant name ‘RES_LOW’ for resolution low. Conversly, if you need a 0 to 1024 range of ADC counts, use the ‘RES_HIGH’ value in the resolution parameter. Once again, we will need this parameter for future code so it is recorded in the ‘_resolution’ global variable. Ultimately, we want to decide from which analog port we want to acquire data, that we do through the simplistic ADMUX |= channel; command.

A little bit more sophisticated is the prescaler setting within the ADCSRA register. Prescaling means applying a dividing factor to a reference clock, presently the CPU clock which runs at 16 MHz on an Arduino UNO.

adc_01

As it takes 13 ADC clock cycles per conversion in one shot mode, taking into account the 16 MHz CPU clock, it takes between 1.625 µs up to 104 µs to perform an ADC conversion depending on the prescaler value (from 1 to 7, which turns to a division factor ranging from 2 to 128 as per the above table). This highlights the fact that under the 16 MHz conditions, the choice of a division factor of 2 leads to a sampling frequency of 600+ kHz, which is far beyond the ADC potential. I experimentally determined that for a CPU clock rate of 16 MHz, the lowest permitted prescaler is 2 leading to a division factor of 4 and an ADC sampling rate of 300+ kHz.

You may wonder why we would not use the lowest prescaler as a default value. The reason is that the longer the sampling time from the ADC, the better the signal to noise ratio. In other words, the longer the time spent acquiring signal, the least the influence of noise, and ultimately the better the precision of measurements. So that if we need high precision measurements, we may want to use prescalers up to 7, to the cost of longer sampling times.

Then we want to enabled the ADC and launch a first conversion. In this way, all later conversion will all last 13 ADC clock cycles, while the first ‘blank’ conversion will require 25 ADC clock cycles.

Acquiring data is much simpler in comparison

inline uint16_t ADCRead(void)
{
	uint16_t result;
	/* Start conversion */
	ADCSRA |= (1 << ADSC); 
	/* Wait for conversion */
	while (ADCSRA & (1 << ADSC));
	/* Read digital value */
	if (_resolution == RES_LOW) {
		result = ADCH;
	} else {
		result = ADCL | (ADCH << 8);
	}
	/* Returned value */
	return(result);
}

Writing ADSC bit to one starts the one shot ADC conversion and then we wait until this bit is set back to ‘0’, meaning that the ADC conversion is fully completed. Then we read the ADCx registers according to the previously selected data format (left aligned or not). Just keep in mind that LSBs must be read priori to MSBs. Additionally, we may want to oversample data in order to improve the signal to noise ratio. Here is a complementary function which runs the ADCRead function a predetermined number of times:

uint16_t ADCRead(uint8_t samples)
{
	uint32_t result = 0;
	for (uint8_t i = 0; i < samples; i++) {
		result += ADCRead();
	}
	result /= samples;
	return(result);
}

Next post on same subject

Stepper Motors (Part 6)

Part 12, 3, 4, 5, 6

This post is about an other driver module which is very popular in the world of robots and 3D printers. These modules features the DRV8825 Stepper Motor Controller integrated circuit made by Texas Instrument (Datasheet available from >here<). Although many clones exist, it looks like Pololu was at the origin of this module.

drv8825

The DRV8825 is a micro-stepping bipolar stepper motor driver which features adjustable current limiting, over-current and over-temperature protection, and six micro-step resolutions (down to 1/32-step). It operates from 8.2 V to 45 V and can deliver up to approximately 1.5 A per phase without a heat sink or forced air flow (rated for up to 2.2 A per coil with sufficient additional cooling). The driver has a pin-out and interface that are nearly identical to those of our A4988 stepper motor driver carriers, so it can be used as a higher-performance drop-in replacement.

drv8825_1

The VELLEMANN Vertex 3D printer (aka K8400) features 4 to 5 (2 extruder configuration) of these drivers. They proved to be very reliable in spite of poor power dissipation. The amazing thing about these drivers is that most suppliers suggest the use of an heat aluminum heat dissipator mounted on top of the plastic enclosure of the driver chip itself, although most of you know that plastic has poor thermal transmission properties. The striking thing about it all is that the designers of most DRV8825 driver modules managed to create a strong thermal bounding between the bottom of the DRV8825 chip (which by the way feature a conductive metal pad) and the opposite side of the PCB thanks to through hole thermal bridges.

drv8825_2

So that the heat dissipator should be installed beneath the module meaning restricted exposure to air flows…Strange. Some argued that installing the chip upside down would restrict access to the current limiting trim pot. Quite true. But how often did you need to adjust this pot really ?

 

3D Printing (Part 10)

Part 1234567, 8, 9, 10

Almost shocking ! As I was not very satisfied with the quality of my prints, and after experiencing a strange awful printing incident (Wires every where, big melted material blob on top), I started having some sort of concern about temperature readings.

I used a very thin thermocouple connected to a pro grade thermometer (Testo 945). Then I managed to  lock the tip of the thermocouple firmly on the heated block and started performing measurements. And the readings where alarming. Although the set point for ABS was set to 245°C, the reading showed a good 280°C ! Some posts from the Vertex forum rang my bell, but none reported real data. Now it’s done. The original sensor is very badly reporting the print head temperature.

I quickly found on the Internet some vendors of thermistors (100k Ohm), some of them offering for a very cheap price assembled thermistor. So, why bother ? Here is the one I chose.

sensor_4

For less than 3€, the thermistor comes insulated and nicely wired

sensor_5

The I used a Faston connector adapter

sensor_1

that I modified in order to fit the print head profile and sensor size

Note: Watch out, the bead is fragile do not over tighten it

sensor_2

Once you are happy with the mechanical arrangement, remove the sensor, add some thermal compound around the bead and put it back. Remove the original thermistor wires from the print head PCB and place the new ones after adjusting their length.

  sensor_3

sensor_6

And now the readings are alright, no more than 5°C in difference between the thermocouple and the thermistor. The benefit from this modification is that you can retrofit it in case you find a better option.

Short discussion about the original sensor: Firstly, it is pretty risky to install it as specified, “feeling the bump” from the insulated tube. A minimal improvement would consist in removing the sleeve, marking the wire length in order to make sure that the glass bulb is properly fitted in its hole. In my opinion, what makes this big difference in readings is that the hole is too wide and the insulating tube is … insulating too much. I am afraid that using thermal compound would not help bridging the thermal gap. Am I the only one with this problem ? Didn’t it occurred during testing at VELLEMAN labs ? That’s strange ! And dangerous for the “plastic” parts all around.

And by the way, using 230°C for extruding ABS is just fine.

HTH