Stepper Motors (Part 8)

Part 12, 3, 4, 5, 6, 7, 8

This post is about driving multiple stepper motors continuously sweeping various ranges of steps at various speeds. Each motor features a permanent sinusoidal motion (accelerated and decelerated). The complexity of the code lies in the next requirements: Each motor has its own sweeping frequency, its own full swing range (number of steps explored on each cycle). It must be possible to introduce a phase shift between each motor, they must stay synchronized over long periods of time and ultimately you may change the settings at any time.

That was a challenge ! The most confusing part of the project lies in the need for thinking in reverse mode. In this case, we do not have fixed intervals of time so we need to define how much time must elapse between to steps. Let’s put it differently: drawing a sin curve on a spreadsheet usually requires fixed time intervals on the x axis and computed ordinates for the y axis using this sort of formula: y = sin((x * frequency  * 2 * Pi())). Computing x from y requires the use of the inverse of sin function: sin^-1 = asin (arc sine).

The code below encapsulate the Interval function which is used for computing the time after which the next step will be issued by the stepper motor driver. Note that all direction pins are wire together whatever the number of drivers, while pulse must be directed to individual drivers (as per the _vStepPins vector)

#include <math.h>

const uint8_t DIR_CW = LOW;
const uint8_t DIR_CCW = HIGH;
const float _twoPi = 6.283185307179586476925286766559;

/* User settings. See explanations below */
/* _directionPin: this is the arduino pin to which the direction pins from the stepper drivers are
attached. Numbering is as per arduino standard pin namming convention */
const uint8_t _directionPin = 8;
/* _steppers: number of stepper motors. Check next vector contents accordingly */
const uint8_t _steppers = 4;
/* _vSteps: number of steps required to explore the full swing (half the _vBnFSteps) */
const uint16_t _vSteps[_steppers] = {200, 100, 50, 25};
/* _vStepPins: these are the arduino pins to which the step pins from the stepper drivers are 
attached. Numbering is as per arduino standard pin namming convention */
const uint8_t _vStepPins[_steppers] = {9, 10, 11, 12};  
/* _vFrequency; Frequency (in Hz) of sinusoidal motion. Each step interval is computed */
const float _vFrequency[_steppers] = {0.5, 1.0, 2.0, 4.0}; 
/* _vPhaseShift: Phase shift in ms versus the absolute start time which is identical for all tepper 
motors. This value is self constrained. if f = 1 Hz, the phase shift might range from 0 to 1000 ms. 
A 500ms value corresponds to 1 x Pi (so as to say half a cycle) phase shift. */
uint16_t _vPhaseShift[_steppers] = {0, 0, 0, 0}; 
/* Do not change next vectors and variables */
uint16_t _vBnFSteps[_steppers]; /* Twice the steps */
uint16_t _vHalfSteps[_steppers];  /* Half the steps */
int16_t _vStepsCounter[_steppers]; 
uint32_t _vNextStepTime[_steppers];
uint32_t _vLoopsCounter[_steppers];
uint32_t _vStartTime[_steppers];
const uint16_t _startTimeOffset = 100; /* In milli seconds */
const uint8_t _pulseDelay = 2; /* In micro seconds */

void setup(void)
{ 
	/* Console for diag */
	Serial.begin(115200);
	Serial.println("Ready");
	/* Direction pin */
	pinMode(_directionPin, OUTPUT);
	digitalWrite(_directionPin, DIR_CW); /* Set default direction */
	/* Step pins */
	for (uint8_t i = 0; i < _steppers; i++) {
		pinMode(_vStepPins[i], OUTPUT); /* Set direction */
		digitalWrite(_vStepPins[i], LOW); /* Set default state */
	}	
	uint32_t now = millis();
	for (uint8_t i = 0; i < _steppers; i++) {
		_vBnFSteps[i] = (_vSteps[i] << 1); /* Precalculate data */
		_vHalfSteps[i] = (_vSteps[i] >> 1); /* Precalculate data */
		_vStepsCounter[i] = 0; /* Reset steps counter */
		_vPhaseShift[i] %= uint16_t(1000.0 / _vFrequency[i]); /* Constrain phase shift */
		_vStartTime[i] = (now + _vPhaseShift[i]); /* Compute start time */
		_vStartTime[i] +=_startTimeOffset;
		_vNextStepTime[i] = (_vStartTime[i] + Interval(i)); /* Set next step time */
		_vLoopsCounter[i] = 0; /* Reset loop counter */
	}
}

void loop(void)
{
	uint32_t now = millis();
	for (int8_t i = 0; i < _steppers; i++) {
		if (now > _vNextStepTime[i]) {
			/* Set direction */
			int8_t dir;
			if (_vStepsCounter[i] < _vSteps[i]) {
				dir = DIR_CW;
			} else {
				dir = DIR_CCW;
			}
			digitalWrite(_directionPin, dir);
			/* Generate pulse */
			digitalWrite(_vStepPins[i], HIGH);
			delayMicroseconds(_pulseDelay); /* Minimal pulse delay */
			digitalWrite(_vStepPins[i], LOW);
			/* Increment step and constrain computed step value */
			_vStepsCounter[i] += 1; 
			if (_vStepsCounter[i] >= _vBnFSteps[i]) { 
				_vLoopsCounter[i] += 1; /* Increment loop counter */
				_vStepsCounter[i] = 0; /* Reset steps counter */
				_vNextStepTime[i] = (_vStartTime[i] + int32_t(_vLoopsCounter[i] * (1000.0 / _vFrequency[i])));
			} else {
				_vNextStepTime[i] = (now + Interval(i));
			}
		}
	}	
}

uint16_t Interval(uint16_t stepper)
{
	/* Compute next stepper time */
	int16_t i0 = ((_vStepsCounter[stepper] % _vSteps[stepper]) - _vHalfSteps[stepper]);
	int16_t i1 = (i0 + 1);
	float y0 = ((float)i0 / _vHalfSteps[stepper]);
	float y1 = ((float)i1 / _vHalfSteps[stepper]);
	int16_t result = (int16_t)(((asin(y1) - asin(y0)) * 1000.0) / (_twoPi * _vFrequency[stepper]));
	/* Returned value */
	return(result);
}

 

What a shame !

I recently reactivated my Facebook account in order to express my opinions during the last campaign for the election of the French Republic President. Well, I am not naive and I know by experience (I have been myself a candidate for the house of Parliament) that a campaign may be (very) trash. However, what struck me this time is how stupidity can spam the information shared by people from this “social” network. I deactivated my account right after reading and absolutely disgusting mess of slurs poured on Cedric Villani, one of the most brilliant minds of this time. How come absolute nerds write such bullshit on someone they do not really know, probably never heard and that will surely never ever read ? How come citizen can blame a scientist for his passion for spiders just because he pretends to represent the people from his place and share his vision on developing scientific culture  ?

I would not be surprise that none of these persons have the least idea about how computer protection works and how thankful they ought to be to the mathematicians who’s science is right behind our encryption algorithms. It is likely that without their help, we would all be naked in front of hackers these days.

This episode should remind us about the nasty declaration “Whenever I hear the word ‘culture’…I release the safety on my pistol!.”. How far are we from adding the word ‘science’ to the dictionary of rude men and women ?

Simple command parser (Part 2)

Part 1, 2

In the previous version, the command parser is using one single alpha character as an opcode while the argument is computed on the fly using simple arithmetic. In this version, the opcode is extended to (almost) any number of characters and the argument is stored in a vector which is then converted into integers or floats using the iota and itof functions. Please note that the use of itof leads to the same limitations than in the previous version. Also, because the opcode has no more fixed length, a separator must be used in between the opcode and the argument. The example given below makes use of either the ‘:’ character or a space character.

Next is a new set of opcodes. They are more explicit however they require more typing efforts.

const char OPC_NONE[] = "NONE";
const char OPC_BLINKS[] = "BLK";
const char OPC_ACTIVATED[] = "ACT";
const char OPC_ON_RATIO[] = "ONR";
const char OPC_PERIOD[] = "PER";
const char OPC_LST_PARAM[] = "LST";
const char OPC_RST_PARAM[] = "RST";
const char OPC_SAV_PARAM[] = "SAV";
const char OPC_ERROR[] = "ERR";

And here is parsing code

void ParseUartData(void)
{
	if (Serial.available() > 0) {
		/* As long as data is available from the com port */
		while (Serial.available() > 0) {
			/* Set variables to their default values */
			bool delimiterDetected = false;
			char vOpcode[8];
			uint8_t opcodeChars = 0;
			vOpcode[0] = '\0';
			char vArgument[12];
			uint8_t argumentChars = 0;
			/* While cr or nl characters are not met */
			while (true) {
				/* Read next char */
				char dataIn = Serial.read();
				/* Interpret character */
				if ((dataIn == 13) || (dataIn == 10)) {
					break;
				} else  if ((dataIn == ':') || (dataIn == ' ')) {
					/* Delimiter */
					delimiterDetected = true;
				} else if ((dataIn >= 'A') && (dataIn <= 'Z')) {
					/* Parse opcode */
					if (!delimiterDetected) {
						vOpcode[opcodeChars] = dataIn;
						opcodeChars += 1;
					} else {
						vOpcode[0] = '\0';
						break;						
					}
				} else if (((dataIn >= '0') && (dataIn <= '9')) || (dataIn == '.')) {
					/* Parse argument */
					if (delimiterDetected) {
						vArgument[argumentChars] = dataIn;
						argumentChars += 1;
					} else {
						vOpcode[0] = '\0';
						break;						
					}
				} else {
					/* Unexpected character */
					vOpcode[0] = '\0';
					break;					
				}
				delay(1);
			}
			/* Append termination characters */
			vArgument[argumentChars] = '\0';
			vOpcode[opcodeChars] = '\0';
			/* Convert vector */
			/* 
			If the vector contains a decimal separator, only the integer part 
			of the floating point number is returned
			*/
			int32_t intArgument = atol(vArgument);
			/* The vector may not contain a decimal separator */
			float fltArgument = atof(vArgument);
			/* Parse opcodes */
			if (vOpcode[0] != '\0') {
				/* Uncomment for Debug */
				// Serial.print("opcode: ");
				// Serial.print(vOpcode);
				// Serial.print(", argument: ");
				// Serial.print(intArgument);
				// Serial.print(" (");
				// Serial.print(fltArgument, 6);
				// Serial.print(")");
				// Serial.println();	
				if (strcmp(OPC_ACTIVATED, vOpcode) == 0) {
					Serial.print("Turn led ");
					if (intArgument == 1) {
						*(_ledPort) |= _ledPinMask; 
						Serial.println("on");
					} else if (intArgument == 0) {
						*(_ledPort) &= ~_ledPinMask; 					
						Serial.println("off");
					}
					_blinks = 0;		
				} else if (strcmp(OPC_BLINKS, vOpcode) == 0) {
					Serial.print("Blinks = ");
					Serial.println(intArgument);
					_blinks = intArgument;
					/* Reset blink counter */
					_blinkCounter = 0;
				} else if (strcmp(OPC_ON_RATIO, vOpcode) == 0) {
					Serial.print("On/off ratio = ");
					Serial.println(fltArgument, 2);
					_onRatio = fltArgument;	
				} else if (strcmp(OPC_PERIOD, vOpcode) == 0) {
					Serial.print("Period = ");
					Serial.println(intArgument);
					_period = intArgument;
				} else if (strcmp(OPC_LST_PARAM, vOpcode) == 0) {
					PrintParameters();
				} else if (strcmp(OPC_RST_PARAM, vOpcode) == 0) {
					Serial.print("Reading parameters... ");
					GetDefaultParameters(bool(intArgument));
					Serial.println("done");
				} else if (strcmp(OPC_SAV_PARAM, vOpcode) == 0) {
					Serial.print("Saving parameters... ");
					SetDefaultParameters();
					Serial.println("done");
				} else if (strcmp(OPC_ERROR, vOpcode) == 0) {
					/* Report error */
				}
				/* Update subsidiary variables */
				_onDuration = (_period * _onRatio);
				_offDuration = (_period - _onDuration);
			}
		}
	}
}

You get the point now: variable length opcodes allow an almost unlimited choice of explicit commands which might be very useful in complex applications.

However, and once again, the aim of this series of post is to provide you with a simple yet powerful starting point with command parsing. Advanced application may require much more rugged transactions, including checksum or CRC (cyclic redundancy check) and data descriptors for example.

HTH

Simple command parser (Part 1)

Part 1, 2

Many applications require some interaction with the user. And one of the most basic manner to interact with an application is to use the serial communication port. Using Serial.print command is probably one the earliest command we all used to debug applications. The proposed command parser uses Serial.available() and Serial.read() functions and few lines of code.

As usual, I tried to make the code as simple and explicit as possible. It does not use additional libraries or tricky functions. This principle of operation is illustrated by the following code, which might be called “The ultimate blink sketch” or “Why make things simple when you can make them complex?”. The idea is to interact with the led blinking process by setting the blink period and the on/off ratio. Additional commands will blink the led, turn it steady on or off.

Instructions are passed to the application using opcodes and arguments. As it is easier to memorize a letter – typically the initial of an instruction – opcodes are single characters as proposed below:

const uint8_t OPC_NONE = 0x00;
const uint8_t OPC_BLINKS = 'B';
const uint8_t OPC_ACTIVATED = 'A';
const uint8_t OPC_ON_RATIO = 'O';
const uint8_t OPC_PERIOD = 'P';
const uint8_t OPC_LST_PARAM = 'L';
const uint8_t OPC_RST_PARAM = 'R';
const uint8_t OPC_SAV_PARAM = 'S';
const uint8_t OPC_ERROR = 'E';

The argument can be a signed integer or a floating point value. So that setting the blink duration for 1000 ms will result from the D1000 instruction. On the other hand, setting the on/off ratio to 10/90% will result from the O0.1 instruction. Note that D 1000 or O 0.1 will work too.

Parsing the instructions is performed as per the following sequence:

  • Read available characters up to the carriage return and/or line feed characters. Alphabetical characters are recognized as opcodes while numerical characters are recognized as numerical arguments.
  • Apply some basic math to format the argument properly.
  • Parse opcodes and set variables using the formatted argument values

The whole code sits in one single routine which is to be periodically executed.

void ParseUartData(void)
{
	if (Serial.available() > 0) {
		/* As long as data is available from the com port */
		while (Serial.available() > 0) {
			/* Set variables to their default values */
			uint8_t opcode = 0;
			bool opcodeDetected = false;
			int32_t intArgument = 0;
			float fltArgument = 0.0;
			bool negative = false;
			uint32_t argumentDivisor = 1;
			bool decSepDetected = false;
			/* While cr or nl charaters are not met */
			while (true) {
				/* Read next char */
				uint8_t dataIn = Serial.read();
				/* Interpret character */
				if ((dataIn == ' ')) {
					/* Spaces are accepted */
				} else if ((dataIn == 13) || (dataIn == 10)) {
					break;
				} else if ((dataIn >= 'A') && (dataIn <= 'Z')) {
					/* Parse opcode */
					if (!opcodeDetected) {
						opcode = dataIn;
						opcodeDetected = true;
					}
				} else if ((dataIn == '-')) {
					/* Parse sign */
					if (opcodeDetected) {
						if (intArgument == 0) {
							negative = true; /* Set sign */
						} else {
							/* Improper sign location */
							opcode = OPC_ERROR;
							break;
						}
					}
				} else if ((dataIn == '.')) {
					/* Parse decimals */
					if (opcodeDetected) {
						if (!decSepDetected) {
							decSepDetected = true;
						} else {
							/* Improper decimal separator location */
							opcode = OPC_ERROR;
							break;
						}
					}
				} else if ((dataIn >= '0') && (dataIn <= '9')) {
					/* Parse argument */
					if (opcodeDetected) {
						/* Shift previous value */
						intArgument *= 10;
						/* Add data to previous value */
						intArgument += (dataIn - '0'); 
						if (decSepDetected) {
							argumentDivisor *= 10;
						}
					}
				} else {
					/* Unexpected character */
					opcode = OPC_ERROR;
					break;					
				}
				delay(1);
			}
			/* Apply argument divisor and sign */
			if (negative) {
				intArgument = -intArgument;
			}
			fltArgument = (float(intArgument) / float(argumentDivisor));
			/* Parse opcodes */
			if (opcode != OPC_NONE) {
				/* Uncomment next lines for debug */
				// Serial.print("opcode: ");
				// Serial.print(char(opcode));
				// Serial.print(", argument: ");
				// Serial.print(intArgument);
				// Serial.print(" (");
				// Serial.print(fltArgument, 4);
				// Serial.print(")");
				// Serial.println();			
				switch(opcode) {
				case OPC_ACTIVATED:
					Serial.print("Turn led ");
					if (intArgument == 1) {
						*(_ledPort) |= _ledPinMask; 
						Serial.println("on");
					} else if (intArgument == 0) {
						*(_ledPort) &= ~_ledPinMask; 					
						Serial.println("off");
					}
					_blinks = 0;
					break;			
				case OPC_BLINKS:
					Serial.print("Blinks = ");
					Serial.println(intArgument);
					_blinks = intArgument;
					/* Reset blink counter */
					_blinkCounter = 0;
					break;
				case OPC_ON_RATIO:
					Serial.print("On/off ratio = ");
					Serial.println(fltArgument, 2);
					_onRatio = fltArgument;
					break;
				case OPC_PERIOD:
					Serial.print("Period = ");
					Serial.println(intArgument);
					_period = intArgument;
					break;
				case OPC_LST_PARAM: /* List parameters */
					PrintParameters();
					break;
				case OPC_RST_PARAM: /* Reset parameters */
					Serial.print("Reset parameters... ");
					GetDefaultParameters(bool(intArgument));
					Serial.println("done");
					break;
				case OPC_SAV_PARAM: /* Save parameters */
					Serial.print("Save parameters... ");
					SetDefaultParameters();
					Serial.println("done");
					break;
				case OPC_ERROR: /* Error */
					/* Report error */
					break;
				}
				/* Update subsidiary variables */
				_onDuration = (_period * _onRatio);
				_offDuration = (_period - _onDuration);
			}
		}
	}
}

Check the few tricks within the code which care about the sign, and the recognition of floats. The ‘-‘ should always lead the argument, and one  single decimal point is accepted. The .1 notation is accepted. These are basic protections and one could easily improve the ruggedness of this code. This code has few limitations such as numerical capacity, however it will work fine in 99% cases. Integers and floating point values must fit the −2147483648 to 2147483647 range and floats are limited to the same range. The precision of floating point values is affected by the loss of precision during floating point arithmetic. Spaces are not illegal, however all other characters generate an error.

Next post on same subject

 

Wire as you think

I was used to build my prototypes on strip-boards, burning my fingers, loosing my nerves and making many wiring mistakes because of the mirroring effect. And then came the illumination ! As I was visiting the R&D plant of my former division in Palo-Alto I observed engineers using breadboards to design yet pretty sophisticated devices. It did not take long before I drove to Fry’s and bought a couple of 3M branded mid-sized breadboards. Since them, I enriched my collection of breadboards with larger and smaller ones in order to fit the scale of my projects.

I very quickly developed some sort of standardization for the wire straps and later for dedicated components. At this time I used single core copper wires which were perfect for the job. My sourcing was pairs of telephone wire. The draw backs of this abundant source of wires was the lack of colors and the oxidizing of the bare copper wires. In the long term, oxide creates contact problem and leads to erratic signals.

A better option consists in getting plated copper wires in various insulating material colors. The optimal wire gauge is 0.6 mm, which translates to 22/23 AWG. This size of wire fits the bread boards as well as the headers (such as Arduino headers).

I cut these wires in standardized lengths: 1/2 inch, 1 inch and 1 1/2 inches.

These lengths are very convenient for most applications. Anyway, save some wire for specific needs. From my experience, multiple core wires are better for longer wire lengths.

And now is the time for something special. Instead of wiring passive components with bare terminals, which I find suicidal for any type of wiring…

I standardized sets of very commonly used components as shown below

The return on (time) investment is massive: wire gauges will always fit your prototyping tools, no risk of short circuits or loose contacts, and restricted risks of improper biasing. For achieving this last objective, I use black wires on the negative sides of polarized components. For diodes, I normalized the fact the tip of the diode is the cathode.

Ultimately, this sort of standardization helps a lot in tidying things up a lot as shown from the next picture. You may start with the most used series such as 1, 1.5, 2.2, 3.3, 4.7 in the x10, x100, x1k, x100k  ranges for resistors and capacitors and widen the range of values as you need them.

Well, this is it ! Once all things in place, it takes minutes to safely and quickly wire most schematics; again, and again, and again…

HTH

April fool… two days ahead

These guys at TI and have a great sense of humor !

Are they serious  ?

News and Moods

Some time ago, I suggested to append the keyword Arduino to any search for some advanced techno oriented questions. Arduino acts as an amplifying or magnifying factor as you look for the latest developments, the smartest solution in any kind of technology. It sounds like a password whispered at the entrance of a secret society building: “arduino” and the door opens on a noisy, busy, crowdy open space full of makers, hackers, designers, helpers, etc…

As I worked on DC/DC converters, for an urgent project, I decided to speed up its development by addressing directly the support team of one of top suppliers in converting chips. And I found…

LinDuino ! Amazing ! Read more about it in this publication. LT is surely not pioneering an unprecedented trend in R&D, however they address it properly.

Last not least, I had a laugh at reading this on Mouser’s web pages:

Well, you made it great Limor, and you deserve once again my great consideration. I imagine that it is quite a change compared to the early times…

Mon p’tit python ! (Part 1)

I had a laugh when I started tumbling around on the web, trying to get a proper starting point for learning Python ! I heard about the famous Pink Floyd rock band funding the no less famous “Python and the Holy Grail” with a part of their substantial earning from “Dark Side of the Moon” album. But I was far from imagining that a developer would name a programming language after the hilarious Monty Python gang…

However, learning a new code is less fun than the Monty Python, and generates more stress than listening to Clare Torry solo… Anyway, I wanted to get into this language which is growing fast in popularity among many developers from institutional organisations down to apprentice developers. And it is interesting to get back to learning and how it feels to get into something (almost) new. Some years ago, my dear colleagues gently convinced me to volunteer to go back to university and obtain a degree in nuclear physics in order to get the accreditation for running the analysis of radio active compounds: at 50, it’s quite a challenge ! After all, I like challenges thus my constant interest in new tech and this blog.

python

Let’s get back to Python for now. Getting Pyhton installed is pretty easy, just get to the official page and download the package which fits your configuration. If you are new at Python, it is suggested that you go for the latest version which is 3.x.x. I tumbled through the web looking at a convenient IDE ad found that notepad++ is definitely a fine editor for Python. In addition, you can verify and launch your code from inside Notepad++ which is very handy. As for any new language, starting programming is a mix of easy successes and fast achievements as well as long boring full stops because of these tiny little #%$#?@& changes in structure or parameters definition. At least two (among few) things confused me quite a lot: the indentation of code and the programming of GUI (Graphic User interface) !

I am happy to share my first application which is a plain simple although handy console which reads data from serial communication ports.

from tkinter import *
from time import sleep
import serial

# Instanciate object
ser = serial.Serial()

_applicationName = "ComWithMe"
_btnWidth = 10

# Functions
def ControlComPort(*args):
	if (ser.isOpen()):
		# Close port
		try:
			ser.close()
			varPortOpenText.set("Open")
		except serial.Exception as e1:
			exit()
	else:
		# Set port parameters
		# Extract parameters from entry field
		vParams = varComPortParam.get()
		param = vParams.split(',', 4)
		ser.port = param[0]
		ser.baudrate = int(param[1]) #115200
		ser.bytesize = int(param[2]) #serial.EIGHTBITS
		ser.parity = param[3] #serial.PARITY_NONE
		ser.stopbits =  int(param[4]) #serial.STOPBITS_ONE
		# Open port
		try:
			ser.open()
			varPortOpenText.set("Close")
		except serial.Exception as e1:
			exit()


def ResetComPort(*args):
	# reset port
	if (ser.isOpen()):
		try:
			ser.dtr = 1
			ser.rtscts = 1
			time.sleep(0.05)
			ser.dtr = 0
			ser.rtscts = 0
			if (varClearOnReset.get()):
				txtReadData.delete(1.0, END)
		except serial.Exception as e2:
			exit()

def ReadData():
	if (ser.isOpen()):
		if ser.inWaiting() > 0:
			varDataInLabel.set("Data in  *")
			line = ""
			try:
				line = ser.readline()
			except serial.Exception as e1:
				exit()
			else:
				txtReadData.insert(END, line)
				if (varAutoScroll.get()):
					txtReadData.see(END)
				root.after(250, StatusTimer)
	root.after(10, ReadData) # check serial again soon

def StatusTimer():
	varDataInLabel.set("Data in")

	
root = Tk()
root.title(_applicationName)
# root.iconbitmap(r"C:\Users\dlongueville\Documents\python\icons\CommWithMe.ico")
#Define frame
content = Frame(root)

# Define Tkinter variables
varAutoScroll = BooleanVar()
varClearOnReset = BooleanVar()
varPortOpenText = StringVar()
varComPortParam = StringVar()
varDataInLabel = StringVar()
# Set variables
varAutoScroll.set(True)
varClearOnReset.set(True)
varPortOpenText.set("Open")
# Change default comm port parameters acroding to your configuration
varComPortParam.set("COM35,115200,8,N,1") 
varDataInLabel.set("Data in")
# Define widgets
chkAutoScroll = Checkbutton(content, text = "Auto scroll", variable = varAutoScroll, onvalue = True, width = _btnWidth)
chkClearOnReset = Checkbutton(content, text = " Clr on Rst", variable = varClearOnReset, onvalue = True, width = _btnWidth)
btnComPortCtrl = Button(content, textvariable = varPortOpenText, command = ControlComPort, width = _btnWidth)
btnReset = Button(content, text = "Reset", command = ResetComPort, width = _btnWidth)
btnQuit = Button(content, text = "Quit", command = root.destroy, width = _btnWidth)
entComPortParam = Entry(content, textvariable = varComPortParam, width = _btnWidth * 2)
txtReadData = Text(content)
lblText = Label(content, textvariable = varDataInLabel)
# Define grid
content.pack(fill = BOTH, expand = True)
content.columnconfigure(1, weight = 1)
content.columnconfigure(3, pad = 7)
content.rowconfigure(6, weight = 1)
content.rowconfigure(7, pad = 7)
# https://goo.gl/yUZYWj
# Placewidgets
lblText.grid(sticky = W, pady = 4, padx = 5, row = 0, column = 0)
txtReadData.grid(sticky = E + W + S + N, rowspan = 6, columnspan = 2, padx = 5, row = 1, column = 0)
btnQuit.grid(pady = 4, row = 0, column = 3)
btnComPortCtrl.grid(pady = 4, row = 1, column = 3)
btnReset.grid(pady = 4, row = 2, column = 3)
chkAutoScroll.grid(pady = 4, row = 3, column = 3)
chkClearOnReset.grid(pady = 4, row = 4, column = 3)
entComPortParam.grid(sticky = W, padx = 5, row = 7, column = 0)  
# Set window properties
# width x height + x + y
root.geometry("500x300+50+50")   
root.minsize(width = 500, height = 300)
# root.resizable(width = False, height = True)
btnComPortCtrl.focus()
# https://goo.gl/hzc820
root.after(10, ReadData)

root.mainloop()

It works out of the box and looks like

python_1

All you have to do is to set the communication port according to your configuration by changing the string at the bottom of the window. The default configuration string is “”COM35,115200,8,N,1”, which stands for comm port “COM35”, baudrate 115200, 8 bits of data, No parity check and 1 stop bit. The Reset button will reboot your Arduino and clear the actual “Data in” content if the “Clr on Rst” check box is activated.

Enjoy !

Picture of the day

Could DSP produce some sort of modern art on some occasion ?

harmanics

Do you want to reproduce that ? Well, here is the recipe …

Get a function generator and set the signal pattern to ramp, sweep frequencies from 80 to 90 Hz with 0.1 Hz steps every second. Connect the output to PlainDSP* and starting from the example scripts write you own which will: look for the more intense signal in the 50 to 100 Hz window, interpolate the peak apex, search for target peaks corresponding to to the 2d up to the 10th harmonics. Plot the computed apexes for every scan and you will get this fancy drawing !

Converting a ramp signal to a frequency spectrum is a very convenient way of generating a whole series of harmonics (see spectrum sample below):

harmonics

while for example, a square signal will produce only odd levels of harmonics (see spectrum sample below) !

harmonics_b

Once again, graphics allow a quick first pass data analysis and show that the device operates very nicely (see this other example in previous post).

* Some tweaking might be necessary depending upon the input signal, please contact me for specific applications.

Alternate IDE (Part 2)

Part 1, 2

Well Notepad++ is really my editor of choice for coding Arduino. Although I am aware of many other powerful editors, I like the style as well as the price (It is free!). Anyway, I felt like Notepad++ was lacking something handy available elsewhere: running external applications. As my first attempts to use the run (f5) command were not successful and because I am not patient… I left the question aside and kept coding tap tap tap tap tap tap tap etc.

However, having started with new languages, I felt like I should solve this problem. Surprisingly, few posts describe the solution well enough to be understood and run snappy, except this one which was the starting point for this post. Here is an attempt to make it in few minutes.

Firstly, have Notepad++ and Arduino installed.

WARNING: Due to a regression this will not work with Arduino rev. 6.1.2 ! Use rev. 6.1.1 or the latest rev. 6.8.1 instead.

Install NppExec (a plugin for Notepad++) from the (Plugins Menu).

npp_d

Open an Arduino sketch in Notepad++. Then press the f6 key from your keyboard and this form will show:

npp_a

Enter the following code in the Commands text box:

// set full path to arduino debug
set arduino_path = "C:\Program Files (x86)\arduino\rev. 1.6.11\arduino_debug"
// save current sketch file
npp_save  
// gets com port value
inputbox "Enter the COM port of your Arduino (e.g., COM1):"  
// verify, uploads to the specified port
cmd /c $(arduino_path) --port $(input) --upload $(full_current_path)

Note: Change the path to Arduino according to your own configuration

Save the script using any name you like and press OK.

The script prompts you for the serial com port to which your arduino board is connected.

Note: You may want to run Arduino before doing that in order to set all default parameters such as the com port and the board type.

Enter the com port:

npp_b

Click OK and here we go …

npp_c

The console pops at the bottom of Notepad++ and shows the instructions and their results:

SET: arduino_path = "C:\Program Files (x86)\arduino\rev. 1.6.11\arduino-1.6.11\arduino_debug"
$(ARDUINO_PATH) = "C:\Program Files (x86)\arduino\rev. 1.6.11\arduino-1.6.11\arduino_debug"
NPP_SAVE: C:\Users\dlongueville\Documents\Arduino\projects\projects_pending\Blink\Blink.ino
INPUTBOX: "Please enter the COM port of your Arduino (e.g., COM3):"
$(INPUT) = COM3
$(INPUT[1]) = COM3
cmd /c "C:\Program Files (x86)\arduino\rev. 1.6.11\arduino-1.6.11\arduino_debug" --port COM3 --upload C:\Users\dlongueville\Documents\Arduino\projects\projects_pending\Blink\Blink.ino
Process started >>>
Loading configuration...
Initializing packages...
Preparing boards...
Verifying and uploading...

Sketch uses 940 bytes (2%) of program storage space. Maximum is 32,256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2,039 bytes for local variables. Maximum is 2,048 bytes.
<<< Process finished. (Exit code 0)
================ READY ================

If you want to customize your own sketch, pay a visit to the ARDUINO(1) Manual Page in order to get all possible arguments.

FAQ

How do I stop a script ? Press f6 again and press the Terminate button

How can I run the default script without opening the editor form ? Press Ctrl + f6

Is it possible to close the console on completion of uploads ? Yes, append the “npp_console 0” line to the script