Simple command parser (Part 1)

Part 1

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.

 

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

 

 

DC motors (Part 3)

Part 1, 2, 3

Now that we managed to get rid of (most of) the spike generated by the opening the supply line of the motor, let’s look at this plateau which happens just after the spike. From the pictures in the previous post, we have been able to observe that the voltage of this plateau looks proportional to the load applied to the shaft of the motor. Well, in fact this signal is related to the back-EMF (ElectroMotive Force) aka counter-EMF or CEMF. You probably remember that a DC-Motor can be reversed and act as a generator. So, when you interrupt the power supply to the motor, the motor will deliver its accumulated energy under the form of a voltage of the same sign than the power supply voltage. As the motor is hooked to VCC, the faster the revolution, the higher the CEMF, the lower the plateau ! And vice versa.

This is a very interesting property that we may exploit in order to build a rotational speed controller without additional sensors !

The ripple from the top picture or from unprotected power supplies looks exactly like the one observable at the output of a DC-motor used a generator. Using subtle DSP we might even calculate the rotational speed based on the analysis of this ripple signal. We will see that later.

Now that we managed to build a safe PWM power supply which is able to provide a clean feedback signal, let’s try to use the CEMF in order to control the speed of the motor. Firstly, we need to create a feedback line to Arduino in order to measure the CEMF, convert it, compare it to a set-point and adjust the PWM ratio accordingly.

pwm_schematics_5

The CEMF signal is taken from the switched power supply output through R2. Although this resistor may not be mandatory, it has some interesting advantages: using the D1 general purpose diode (1N4148 or so) wired to arduino +5V, it protects the analog input from excessive signal as R2 will absorb the generated current. In combination with the optional C1 capacitor it will act as a RC filter which will improve somewhat the filtering of the ripple.

Once the hardware ready, You may want to use the following code:

const uint8_t _outputPinMask = (1 << PINB5);
volatile uint8_t *_outputPort = &PORTB;

const int16_t _cycleTime = 10000; /* in micro seconds */
int16_t _onTime = 0; /* in micro seconds */
uint32_t _lastCycleTime; /* in micro seconds */
uint32_t _lastOnTime; /* in micro seconds */
const uint8_t _analogPin = 0;
/* user defined variable */
const int16_t _targetCEMF = 950; /* From 0 to 1024, high CEMF value = low speed */

void setup(void)
{
	/* Init pwm pin */
	*(_outputPort - 1) |= _outputPinMask; 
	/* Init analog pin */
	analogReference(DEFAULT);
	/* Init cycle time */
	_lastCycleTime = (micros() - _cycleTime);
}


void loop(void)
{
	uint32_t now = micros();
	if (((now - _lastCycleTime) >= _cycleTime)) {
		_lastCycleTime = now;
		_lastOnTime = now;
		/* Before switching the motor drive off, perform a one shot measurement 
		of the counter-electromotive force (abbreviated counter EMF, or CEMF) */
		int16_t val = analogRead(_analogPin);
		/* Compute the difference between the setpoint and the measured value */
		int16_t diff = (val - _targetCEMF);
		/* Apply a correction factor: damping (< 1) or accelerating (> 1) */
		diff *= 0.5;
		/* Update the on time and constrain it */
		_onTime += diff;
		if (_onTime < 0) {
			_onTime = 0;
		} else if (_onTime > _cycleTime) {
			_onTime = _cycleTime;
		}		
		/* Turn motor ON (MOFET gate high) */
		*(_outputPort) |= _outputPinMask; 
	} else if (((now - _lastOnTime) >= _onTime)) {
		/* Turn motor OFF (MOFET gate low) */
		*(_outputPort) &= ~_outputPinMask; 
	}
}

Now you understand why I decided to bit bang the PWM. The CEMF is measured just before resuming the ON state, far away from the spike (Tr in the following diagram).

cemf_sampling

(Very) serious games

From my primary school in the 60’s, I remember that we flew hundreds of paper planes in the school yard. Depending upon the way paper was folded, these planes would be fast or fly long distance or be able to perform some sorts of acrobatics figures (Mostly unexpected I must say).

paper_plane_a

At the same time clubs were flying the early model planes, powered by micro fuel engines and controlled by cables.

These archaic model planes became real serious games thanks to advanced technology and composite materials. the even get larger, heavier in order to match their sometimes deadly tasks.

Read more from WIRED post: Watch Darpa’s Creepy ‘Project SideArm’ Pluck a Drone Out of the Air

 

 

DC motors (Part 2)

Part 1, 2, 3

Time for code ! Let’s write a few lines of trivial code in order to drive our controller (Check previous post). You probably heard about the analogWrite() function which is very convenient for generating PWM. Well, we will not use it ! Is it because it is too easy ? It is because there is a limited number of pins capable of generating a PWM signal ? Or is it because of the limited choice of base frequency ? All these arguments must be taken into account. However on top of that, the reason why we will use bit bang code relates to the need for having full and easy access to all timed events from each cycle. Although this choice may not look obvious to you now, it will in the next posts.

So here is the basic code that we will use as a staring point:

/* set pwm pin */
const uint8_t _outputPinMask = (1 << PINB5);
volatile uint8_t *_outputPort = &PORTB;
/* set timing variables */
const uint16_t _cycleTime = 10000; /* in micro seconds */
uint16_t _onTime = 0; /* in micro seconds */
uint32_t _lastCycleTime; /* in micro seconds */
uint32_t _lastOnTime; /* in micro seconds */
/* User defined pwm ratio */
uint8_t _pwmRatio = 50; /* from 0 to 100 */

void setup(void)
{
	/* Init pwm pin */
	*(_outputPort - 1) |= _outputPinMask; 
	/* Constrain pwm ratio */
	if (_pwmRatio < 0) {
		_pwmRatio = 0;
	} else if (_pwmRatio > 100) {
		_pwmRatio = 100;
	}	
	/* Compute on-time */
	_onTime = (((uint32_t)_cycleTime * _pwmRatio) / 100);
	/* Init cycle time */
	_lastCycleTime = (micros() - _cycleTime);
}


void loop(void)
{
	uint32_t now = micros();
	if (((now - _lastCycleTime) >= _cycleTime)) {
		/* cycle-time has elapsed */
		/* Reset timing variables */
		_lastCycleTime = now;
		_lastOnTime = now;
		/* turn motor ON (MOFET gate high) */
		*(_outputPort) |= _outputPinMask; 
	} else if (((now - _lastOnTime) >= _onTime)) {
		/* on-time has elapsed */
		/* turn motor OFF (MOFET gate low) */
		*(_outputPort) &= ~_outputPinMask; 
	}
}

If you are unsure about your electronic skills, do not connect anything thing to your Arduino board and just observe the built in LED. Using various PWM ratio will dim the LED accordingly. Once you are ready, it might be a good idea to see what’s going on using an oscilloscope. Connect the probe A to TP1 (as per the schematic below) and probe B to TP2. The clean signal from channel A (yellow trace) will be used for synchronization, while we will pay attention to the signal on channel B (blue trace).

pwm_driver

Set a PWM ratio of 50 (50% on 50% off), upload your code and will get these kind of plots on your screen. This is the plot of the signal at TP2 when the motor in not loaded:

pwm_signal_1

Under a moderate load, this what you’ll get

pwm_signal_2

Ultimately, this is the resulting plot when the rotor is blocked

pwm_signal_3

Let’s discuss these plots starting from the ON state (5 V applied to the gate of the MOSFET as per channel A): there’s nothing much we can say about it. The MOSFET does the job perfectly and switches one of the motor ends to the ground or almost to the ground. The OFF state shows a more confusing signal. The pattern of the signal looks the same under various load conditions: a strong spike which exceed the supply voltage followed by a plateau which is equal or lower than the supply voltage. On the other hand, we observe that the higher the load, the more intense is the signal.

The spike is no big surprise as you may already know that opening a circuit loaded with an inductance will generate an intense reverse voltage. This is the principle of boost voltage converters or spark plug ignition coils. As this pulse may severely impact other components, we will get rid of it using a clamp diode in parallel with the motor which will absorb the reverse voltage. Once again, we will use a slightly different option by using an other MOSFET which features a built in protective diode… See why in the next pictures, starting with a zoom in the spike area

Protective diode (1N4007), blocked motor shaft

NewFile0

Protective MOSFET, blocked motor shaft

NewFile1

Are you convinced by my choice? Note that a Schottky diode would do the job as well. However, if you have one MOSFET, it is very likely that you also have its little brother !

Next post on same subject