Making a Netduino GO! Module – Stage 5 – Enhancing the Drivers
Saturday, April 27th, 2013This series of posts follows the steps required in order to make your own Netduino GO! module. To do this we are using a simple idea, an Output Expander (yes, it seems to have a name now) for the Netduino GO!. Using only a hand full of simple components, the Output Expander will add 32 digital outputs to the Netduino Go!.
The Story So Far…
The module has so far completed the following steps:
- Concept and prototype on breadboard
- Basic drivers
- PCB design and layout
- Generation of PCB manufacturing files
The first batch of 10 boards were ordered on 20th April 2013 using iTeads prototyping service. I am expecting the manufacturing process to take about one week with a further two weeks for shipping as I used the cheap, slow courier service.
In the meantime we still have the breadboard prototype to work with. This may only have two shift registers attached to it but by carefully parametrising the code we should be able to develop a driver which only needs to be recompiled when the final hardware becomes available.
Features
The driver for this module should be relatively simple as we are merely setting outputs from the shift registers to be either on or off. We shall start with the following list of desired features:
- Default initialisation to set up for one board
- Set the bytes for the digital outputs
- Array like access for the bits
- Configurable latching mode (automatic or manual)
- Clear the output
- Turn outputs from the registers on or off
The only remaining task is to decide where the features are to be implemented. We have two options:
- On the Netduino GO! in C#
- On the STM8S in C
Some of the code is better left on the Netduino GO! with the base features (setting all of the register values etc) being implemented on the STM8S.
Initialisation
Initialisation should allow for the use of cascaded boards. If you look back at the schematic you will notice a connector called CascadeConn. This connector allows the addition of a simpler board as an expansion module. This board only need to supply additional shift registers leaving the first, main board supplying the logic to communicate with the Netduino GO!. The concept is that if you want 64 outputs then you would have a single Output Expander module with a single, cheaper daughter board.
In order to support the addition of the daughter board the initialisation will need to support the specification of the number of shift registers in the cascade.
In supporting the cascading of these boards we will also need to provide some sensible default values. The basic case is a single board which contains four shift registers.
We should also consider a maximum value for the number of shift registers. In this case I am going to set this to 12 for two reasons:
- Power considerations – all of the power for the shift registers is being provided by the Netduino GO!
- Data packet size – the data packets used in the GoBus are fixed size. Keeping the number of shift registers to a value where only one data packet is required simplifies the communication between the Netduino GO! and the module as a single packet can be used for all messages.
In order to facilitate this we will need code on both the Netduino GO! and the module.
Module Code
The code on the module should allow for the number of shift registers to be set up (assuming a default value of four registers) and then clear the registers. The default code should be called when the module is re-initialised.
//--------------------------------------------------------------------------------
//
// GO! function 3 - Set up the module.
//
void SetUp()
{
U8 n = _rxBuffer[2];
if ((n < 0) && (n < MAX_REGISTERS))
{
_numberOfShiftRegisters = n;
free(_registers);
_registers = (U8 *) malloc(n);
ClearRegisters();
OutputData();
NotifyGOBoard();
}
}
Netduino GO! Code
The initialisation code on the Netduino GO! will assume that the startup code on the module will initialise itself to four shift registers. This will reduce the communications overhead between the Netduino GO! and the module. The following when added to the Initialise method should set up the module driver on the Netduino GO! for a variable number of shift registers in the sequence:
//
// Next, set up the space to store the data.
//
_shiftRegisterData = new byte[numberOfShiftRegisters];
for (int index = 0; index < numberOfShiftRegisters; index++)
{
_shiftRegisterData[index] = 0;
}
LatchMode = LatchingMode.Automatic;
if (numberOfShiftRegisters != 4)
{
_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
_writeFrameBuffer[1] = CMD_SETUP;
_writeFrameBuffer[2] = (byte) (numberOfShiftRegisters & 0xff);
WriteDataToModule("Initialise: cannot setup the OutputExpander module");
}
Set Outputs
Setting the outputs is simply a case of sending a number of bytes, one for each shift register to the module.
/// <summary>
/// Set the shift registers using the values in the byte array.
/// "/summary>
/// "param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
if (registers.Length != _shiftRegisterData.Length)
{
throw new ArgumentException("registers: length mismatch");
}
for (int index = 0; index < registers.Length; index++)
{
_shiftRegisterData[index] = registers[index];
}
Latch();
}
The module code needs a slight adjustment to transfer the correct number of incoming bytes to the register store:
//--------------------------------------------------------------------------------
//
// GO! function 2 - Output the specified bytes to the shift registers.
// Tx buffer.
//
void SetShiftRegisters()
{
for (int index = 0; index < _numberOfShiftRegisters; index++)
{
_registers[index] = _rxBuffer[2 + index];
}
OutputData();
NotifyGOBoard();
}
Array of Bits
From a software point of view, a shift register is nothing more than an array of boolean values. Internally it makes sense for the driver to allow this abstraction and use the indexing operator to set a single bit at a time. The code for this operator looks something like this:
/// <summary>
/// Overload the index operator to allow the user to get/set a particular
/// bit in the shift register.
/// </summary>
/// <param name="bit">Bit number to get/set.</param>
/// <returns>Value in the specified bit.</returns>
public bool this[int bit]
{
get
{
if ((bit >= 0) && (bit < (_shiftRegisterData.Length * 8)))
{
int register = bit >> 3;
byte mask = (byte) (bit & 0x07);
return ((_shiftRegisterData[register] & mask) == 1);
}
throw new IndexOutOfRangeException("OutputExpander: Bit index out of range.");
}
set
{
if ((bit >= 0) && (bit < (_shiftRegisterData.Length * 8)))
{
int register = bit >> 3;
byte mask = (byte) ((1 << (bit & 0x07)) & 0xff);
if (value)
{
_shiftRegisterData[register] |= mask;
}
else
{
mask = (byte) ~mask;
_shiftRegisterData[register] &= mask;
}
if (LatchMode == LatchingMode.Automatic)
{
Latch();
}
}
else
{
throw new IndexOutOfRangeException("OutputExpander: Bit index out of range.");
}
}
}
Adding the above code allows the programmer to use constructs such as:
OutputExpander outputs = new OutputExpander();
outputs[2] = true;
instead of the more obscure:
OutputExpander outputs = new OutputExpander();
byte[] data = new byte[4];
data[0] = 0x04;
SetOutputs(data);
Not only is the former example more elegant but it is also more concise.
Clear All Registers
This method will simply clear the shift registers and set the outputs to 0;
/// <summary>
/// This method calls the ClearRegister method on the GO! module and then waits for the
/// module to indicate that it has received and executed the command.
/// </summary>
public void Clear()
{
_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
_writeFrameBuffer[1] = CMD_CLEAR_REGISTERS;
for (int index = 0; index < _shiftRegisterData.Length; index++)
{
_shiftRegisterData[index] = 0;
}
WriteDataToModule("Clear cannot communicate with the Output Expander module");
}
Latch Mode
The introduction of the array indexing operators does introduce on complication, namely that we cannot set all of the outputs to a specified value at the same time without delaying the latching of the registers. Consider the following case:
OutputExpander outputs = new OutputExpander();
outputs[2] = true;
outputs[3] = true;
In this case we would set bit 2 of the lower shift register followed by bit 3 of the same shift register. Because of the speed of .NETMF there would be a slight delay between the two outputs of the shift register being set high. In order to allow for this we introduce the ability to delay the latching of the data from the internal shift register into the output register.
/// <summary>
/// Determine when the data should be sent to the module.
/// </summary>
public enum LatchingMode
{
/// <summary>
/// Automtically send the data to the module as soon as there are any changes.
/// </summary>
Automatic,
/// <summary>
/// Manually latch the data.
/// </summary>
Manual
}
/// <summary>
/// Backing variable for the LatchMode property.
/// </summary>
private LatchingMode _latchMode;
/// <summary>
/// Determine how the data will be send to the module. The default is to
/// automatically send data as soon as there are any changes.
/// </summary>
public LatchingMode LatchMode
{
get { return (_latchMode); }
set { _latchMode = value; }
}
The initialisation of the class would also need to be modified in order to set the mode to automatic:
LatchMode = LatchingMode.Automatic;
The most lightweight method of using the LatchMode is to simply not send the data to the shift registers until the mode is either reset or until the controlling program explicitly latches the data. The Set method will therefore need some adjustment to take into account the two modes:
/// <summary>
/// Set the shift registers using the values in the byte array.
/// </summary>
/// <param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
if (registers.Length != _shiftRegisterData.Length)
{
throw new ArgumentException("registers: length mismatch");
}
for (int index = 0; index < registers.Length; index++)
{
_shiftRegisterData[index] = registers[index];
}
if (LatchMode == LatchingMode.Automatic)
{
Latch();
}
}
Latch Operation
The introduction of the LatchMode means that we also need to allow for the data to be latched into the shift registers.
/// <summary>
/// Call the Set command on the module to set the outputs of the shift registers.
/// </summary>
private void Set()
{
_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
_writeFrameBuffer[1] = CMD_SET_REGISTERS;
for (int index = 0; index < _shiftRegisterData.Length; index++)
{
_writeFrameBuffer[2 + index] = _shiftRegisterData[index];
}
WriteDataToModule("Latch cannot communicate with the Output Expander module");
}
The above method simply sends the data to the module.
Testing
We can perform some simple testing of the software while the prototype boards are being made by using the breadboard test environment build in the first post. This board only has two shift registers but it should be possible to test the majority of functionality using this board.
In the previous posts we have cycled through the bits one at a time either from 0 to 15 or down from 15 to 0. In this example we will perform some counting and use the LEDs to display the number in binary. Our test code becomes:
output = new OutputExpander(2);
short cycleCount = 0;
byte[] registers = new byte[2];
output.LatchMode = OutputExpander.LatchingMode.Manual;
while (true)
{
Debug.Print("Cycle: " + ++cycleCount);
short mask = 1;
output.Clear();
for (int index = 0; index <= 15; index++)
{
if ((cycleCount & mask) != 0)
{
output[index] = true;
}
mask <<= 1;
}
output.Latch();
Thread.Sleep(200);
}
Deploying this application should result in the Netduino GO! counting up from 0 and the binary representation of cycleCount being output on the shift registers. The following video shows this in action:
Conclusion
The above minor modifications to the STM8S module code and the Netduino GO! driver has added the following functionality:
- Default initialisation to set up for one board
- Set the bytes for the digital outputs
- Array like access for the bits
- Configurable latching mode (automatic or manual)
- Clear the output
- Turn outputs from the registers on or off
The code has been carefully written so that we should only need to change two parameters when the final PCBs arrive in order to change the drivers from two shift registers to four shift registers.
A quick test has shown that the main functionality appears to be working on the breadboard prototype as demonstrated by the above video. The prototype PCBs have completed manufacture and are currently with the Hong Kong postal service (as of 27th April 2013). Delivery should take another 7-10 days so there is plenty of time to complete the test suite.