2009-12-21

Manufacturing Software

Some say software doesn't have to be manufactured like physical products, it can simply be copied. But this is not entirely true. The first copy is unique and must be manufactured just like physical things.

To manufacture the first copy, we need to know how to do it. This is specified in the most important product document, the manufacturing instruction. This document contains everything you need to know to manufacture the first copy. For very simple software products, this is the only artifact the software engineer must produce during the construction phase.

If the software product is a little bigger, it quickly becomes impractical to have everything in one document, from routines, tools and build scripts to source code. The manufacturing instruction is then reduced to only contain the manufacturing process mixed with references to the source code, the tools to install and the build scripts to run.

From the reasoning above, I come to the conclusion that software construction is the act of turning requirements into a manufacturing instruction, which is then handed to manufacturing for the production of the first copy. For small products it is the constructing engineer himself who is responsible for the manufacturing. For larger products the manufacturing is handled by the system integrator.

2009-06-24

A GPS logger with speedometer

To be able to contribute to the visionary OpenStreetMap project, I built myself a simple GPS logger. I added a speedometer as well, because I like to keep track of my speed when running or biking.



I had a Globalsat EM-411 GPS module already, a leftover from one of my numerous unfinished projects. The EM-411 only does one thing, and that is to output NMEA0183 encoded messages using 4800bps RS-232 with TTL levels. No initialization or control messages necessary.

I used a 5V Arduino Pro Mini as microcontroller, because it is very easy to program with the simple IDE and Java-like language.

A 512Kbit EEPROM with I2C interface is used to store the GPS data. I could not get hold of a PDIP8 version, but I managed to solder a SO8 directly to a DIL socket for easier prototyping. There are a couple of useful references on how to control a I2C EEPROM from the Arduino. Make sure you connect it to ANALOG pins 4 & 5, which I did not do until after many hours of reading and debugging.

The speed is displayed using two seven segment displays. The decimal dots are used to show memory usage. Due to a minor thinking error, I used one with common anode and one with common kathode, but if you decide to use identical displays, this can easily be compensated for in the display() routine.

A push button trigger a memory dump to the serial port on the Arduino, which I connect to a PC using a TTL-232R cable from FTDI. The button can also be used to clear the memory, if pressed during power on.

With a three second log interval, the device can log for about an hour before the memory is full, and that is also the amount of time a 9V, 200mAh rechargeable battery will power it before going empty.

When a $GPGGA message is received from the EM-411, it is stored in the EEPROM unaltered. The memory dump to PC is therefore just a plain playback of NMEA0183 messages. Using this approach, any NMEA0183 compatible software on the PC can read the logs without any conversions. I use GPSBabel to convert between NMEA0183 and GPX, the format required by OpenStreetMap.

When a $GPRMC message is received from the EM-411, the speed in knots is extracted and converted to km/h before being displayed.




#include <Wire.h>
#include <EEPROM.h>

#define LOGDELAY 3000
#define BUFLEN 100
#define PHASEDELAY 10


byte buttonPin = 4;
byte commonPin[2] = {3, 2};
byte segmentPin[8] = {9, 8, 7, 6, 13, 12, 11, 10};

byte matrix[70] = {1, 1, 1, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 1,
1, 1, 0, 1, 1, 1, 0,
1, 1, 0, 1, 0, 1, 1,
1, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1};

int deviceaddress = 0x50;

byte GGA[6] = {'$', 'G', 'P', 'G', 'G', 'A'};
byte RMC[6] = {'$', 'G', 'P', 'R', 'M', 'C'};

byte phase = 0;
long nextphase = 0;
int data = 0;
int state = 0;

unsigned int abase = 0;
unsigned int logend = 0;

byte packet[BUFLEN];
byte length = 0;
byte cache[BUFLEN];
byte cachelength = 0;
byte cachepos = 0;

long time = 0;
long nextlog = 0;

void setup()
{
Serial.begin(4800);
Wire.begin();

pinMode(commonPin[0], OUTPUT);
pinMode(commonPin[1], OUTPUT);
for (byte i = 0; i < 8; i++)
{
pinMode(segmentPin[i], OUTPUT);
}

pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH); // Pull up

// Read memory pointers
abase = EEPROM.read(0);
abase <<= 8;
abase += EEPROM.read(1);
if (abase < 2 || abase > 510)
{
abase = 2;
EEPROM.write(0, abase >> 8);
EEPROM.write(1, abase & 0xFF);
}
logend = EEPROM.read(abase);
logend <<= 8;
logend += EEPROM.read(abase+1);

// Clear log if button pressed
if (digitalRead(buttonPin) == 0)
{
abase++;
if (abase > 510)
{
abase = 2;
}
EEPROM.write(0, abase >> 8);
EEPROM.write(1, abase & 0xFF);
logend = 0;
EEPROM.write(abase, logend >> 8);
EEPROM.write(abase+1, logend & 0xFF);

while (digitalRead(buttonPin) == 0);
}
}

void loop()
{
time = millis();

// Display driver
if (time > nextphase) {
display();
nextphase = time + PHASEDELAY;
}

// Dump log to serial port
if (digitalRead(buttonPin) == 0)
{
unsigned int address;
for (address = 0; address < logend; address++)
{
Serial.print(i2c_eeprom_read_byte(deviceaddress, address), BYTE);
}
if (digitalRead(buttonPin) == 0)
{
for (; address < 65535; address++)
{
Serial.print(i2c_eeprom_read_byte(deviceaddress, address), BYTE);
}
while (digitalRead(buttonPin) == 0);
}
}

// Serial receive
if (Serial.available() > 0)
{
int serialByte = Serial.read();
packet[length++] = serialByte;
if (serialByte == 10 || length >= BUFLEN)
{
parsePacket();
length = 0;
}
}

// EEPROM write
if (cachepos > 0)
{
i2c_eeprom_write_byte(deviceaddress, logend+cachepos, cache[cachepos]);
cachepos++;

if (cachepos == cachelength)
{
logend += cachelength;
EEPROM.write(abase, logend >> 8);
EEPROM.write(abase+1, logend & 0xFF);
cachepos = 0;
cachelength = 0;
}
}
}

void parsePacket()
{
if (isGGA() && time > nextlog && cachepos == 0 && length > 50)
{
nextlog = time + LOGDELAY;

if (logend > 65535-length)
{
state = 3;
return;
}
else if (logend > 32768)
{
state = 2;
}
else
{
state = 1;
}

byte pos;
for (pos = 0; pos < length; pos++)
{
cache[pos] = packet[pos];
}
cachelength = length;
i2c_eeprom_write_byte(deviceaddress, logend+cachepos, cache[cachepos]);
cachepos++;
}
else if (isRMC())
{
byte pos = find(0, ',', 7);
float speed = parseFloat(pos);
data = speed * 1.852 + 0.5;
}
}

int parseInt(byte pos)
{
int result = 0;
while (packet[pos] >= '0' && packet[pos] <= '9')
{
result = result*10 + packet[pos] - '0';
pos++;
}
return result;
}

float parseFloat(byte pos)
{
float result = parseInt(pos);
pos = find(pos, '.', 1);
float decimals = parseInt(pos);
byte end = find(pos, ',', 1);
pos++;
while (pos < end)
{
decimals /= 10;
pos++;
}
return result + decimals;
}

byte find(byte pos, char sign, byte ncomma)
{
while (ncomma > 0)
{
if (packet[pos] == sign)
{
ncomma--;
}
pos++;
}
return pos;
}

boolean isGGA()
{
for (byte i = 0; i < 6; i++)
{
if (GGA[i] != packet[i])
{
return false;
}
}
return true;
}

boolean isRMC()
{
for (byte i = 0; i < 6; i++)
{
if (RMC[i] != packet[i])
{
return false;
}
}
return true;
}

void display()
{
byte digit = 0;

digitalWrite(commonPin[0], 0);
digitalWrite(commonPin[1], 1);

if (phase == 0)
{
if (state & 0x02)
{
digitalWrite(segmentPin[7], 1);
}
else
{
digitalWrite(segmentPin[7], 0);
}

digit = (data % 100) / 10;
}
else
{
if (state & 0x01)
{
digitalWrite(segmentPin[7], 0);
}
else
{
digitalWrite(segmentPin[7], 1);
}

digit = data % 10;
}
for (byte i = 0; i < 7; i++)
{
digitalWrite(segmentPin[i], (phase ^ matrix[digit * 7 + i]) & 1);
}

if (phase == 0)
{
digitalWrite(commonPin[1], 0);
phase = 1;
}
else
{
digitalWrite(commonPin[0], 1);
phase = 0;
}
}

void i2c_eeprom_write_byte( int deviceaddress, unsigned int eeaddress, byte data ) {
Wire.beginTransmission(deviceaddress);
Wire.send((int)(eeaddress >> 8)); // MSB
Wire.send((int)(eeaddress & 0xFF)); // LSB
Wire.send((int)data);
Wire.endTransmission();
delay(10);
}

// WARNING: address is a page address, 6-bit end will wrap around
// also, data can be maximum of about 30 bytes, because the Wire library has a buffer of 32 bytes
void i2c_eeprom_write_page( int deviceaddress, unsigned int eeaddresspage, byte* data, byte length ) {
Wire.beginTransmission(deviceaddress);
Wire.send((int)(eeaddresspage >> 8)); // MSB
Wire.send((int)(eeaddresspage & 0xFF)); // LSB
byte c;
for ( c = 0; c < length; c++)
Wire.send(data[c]);
Wire.endTransmission();
delay(10);
}

byte i2c_eeprom_read_byte( int deviceaddress, unsigned int eeaddress ) {
byte rdata = 0x0F;
Wire.beginTransmission(deviceaddress);
Wire.send((int)(eeaddress >> 8)); // MSB
Wire.send((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(deviceaddress,1);
if (Wire.available()) rdata = Wire.receive();
return rdata;
}

// maybe let's not read more than 30 or 32 bytes at a time!
void i2c_eeprom_read_buffer( int deviceaddress, unsigned int eeaddress, byte *buffer, int length ) {
Wire.beginTransmission(deviceaddress);
Wire.send((int)(eeaddress >> 8)); // MSB
Wire.send((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(deviceaddress,length);
int c = 0;
for ( c = 0; c < length; c++ )
if (Wire.available()) buffer[c] = Wire.receive();
}


From the code we can deduce that the two displays should be connected in parallel to digital I/O 6, 7, 8, 9, 10, 11, 12 and 13. Digital 2 and 3 are display select signals, connected to the anode and kathode on the displays. The push button is connected to digital 4, with the internal pull up resistor enabled. EM-411 TX (pin 3) is connected to Arduino RxD.

2009-03-13

Properties of version numbers

A software product is usually identified with a name and a version number.

The most fundamental requirement is for the version number to be unique, or it will fail to identify the product. The easiest way to achieve this is to assign the next available positive natural number to a new version.

1, 2, 3, ...

Besides being unique, the version number is also used to communicate various properties of the product. The most common are

* Compatibility with other versions
* Promise of support
* Interface stability
* Quality
* Market expectations

If a commitment is made to support old versions with bug fixes, we need a way to branch the version number, because the next natural number may already be taken when the need for a bug fix arrives. The convention is to use a point to start a branch.

2 => 2.1, 2.2, 2.3, ...

This simple branch pattern has one major drawback. It is not possible to create more than one branch from each version. This can be overcome by inserting a branch name.

2 => 2.bf.1, 2.bf.2, 2.bf.3, ...
2 => 2.customerX_special.1, 2.customerX_special.2,...

You may recognize this naming pattern from ClearCase, which also follows the convention that the zero version in every new branch is identical to the parent, e.g. version 2, 2.bf.0, and 2.customerX_special.0 are all identical.

By assigning special meaning to branches, we can also communicate promises about compatibility and stability of interfaces. For example, a .stable.* branch could only contain modifications that don't change public interfaces. A .bugfix.* branch could also keep the target environment intact, to guarantee drop in compatibility.

Note that a version number is chosen by looking at the relations the new product version has with earlier versions. We do not make any special considerations about which number to assign to any future version of the product. The freedom to assign future version numbers is secured by an open numbering scheme.

Many developers also insist on encoding the quality of the product in the version number. This is a bad idea for several reasons. The quality of the product is established through tests and extended use. This necessarily comes after the fixation of the product gestalt and identity, which includes the version number. Therefore, it is impossible to know anything about the product quality at the time of manufacture, when the version number is assigned. Consider this. After much hesitation, the developer decides to put the stamp of approval on an untested product anyway, by giving it a version number which communicates high quality. All is fine until the first serious bug is found, requiring a bug fix release. Now we have a product version with an identity indicating high quality, which it obviously doesn't have.

Quality based numbering schemes also has a tendency to close the available name space for versions that are actually built and delivered, by assigning numbers to future versions ahead of time, versions which may never actually be produced.

The sound way to communicate the quality of a specific product version is via statements made about the product in reviews, test protocols and bug track records.

Marketing considerations can also put pressure on developers to assign version numbers which do not conform to established technical standards. The solution is to treat any numbers coming from marketing as part of the product name, e.g. Webspunk 7.0 (version 257.bf.17).

More information about version numbers.