RSS

Posts Tagged ‘Netduino’

Netduino.Foundation – Device Drivers for Netduino

Saturday, February 10th, 2018
Netduino Foundation Banner

Netduino Foundation Banner

It has been a very busy few months both in and out of work as evidenced by the silence here. The last few months has seen a fair amount to time spent working with the Netduino boards developing documentation and libraries.

I was given a Nedtuino board in 2010 as a present and I have never looked back. The orignal Netduino Plus was upgraded to the Netduino Plus 2, the Netduino WiFi and numerous electronics projects have come about all due to that original present.

The attraction of the Netduino is the fact that you can work in C# with Visual Studio. Another big selling point is Visual Studio and the powerful debugging features available to the developer.

In summary, it’s a great prototyping and test environment. If I ever have a problem and want to test a theory quickly I always fall back to these boards.

Netduino.Foundation

One difficulty with Netduino was it was sometimes difficult to find drivers for sensors, displays etc. Support for devices relied upon community members having written a driver and making the source available. In fairness, many community members did just that.

Wilderness Labs have recently taken ownership of the Netduino brand and technology. A new approach has brought some innovations:

  • Support for Visual Studio for Mac
  • New community forum
  • Developer documentation for the Netduino
  • Electronics tutorials
  • Netduino.Foundtion collection of drivers and core functionality
  • Source code is available on GitHub
  • Nuget support

It is still possible to use the Netduino without using Netduino.Foundtion but the library makes a number of tasks easier and quicker.

As well as a number of core peripherals there is support for a library of drivers covering a variety of devices:

Device Description
74595 Shift Registers Everyones favourite output expander
AT24Cxx I2C EEPROM
DS323x Real time clock
Graphics Library General purpose graphics library
LCD MicroLiquidCrystal library for 16×2 and 20×4 LCD displays
Serial LCD Serial LCD backpack
SSD1306 OLED Display (128×32 and 128×64)
BME280 Temperature, pressure and humidity
HIH6130 Temperature and humidity
MPL115A2 Temperature and pressure
MPL3115A2 Temperature and pressue
SHT31D Temperature and humidity
SI7021 Temperature and humidity
TMP102 Temperature
GPS NMEA GPS
ALS-PT19 Analog light sensor
TSL2561 Infra-red compensated light sensor
ADXL335 Triple axis accelerometer (+/- 3g)
ADXL345 Triple axis accelerometer (+/- 16g)
MAG3110 Triple axis magnetometer
PIR Motion detection
Servo Generic servo library
Analog Temperature Analog temperature sensors such as LM35/36/37 etc.

All of the drivers are supplied with source code, sample applications and comprehensive documentation.

Conclusion

It is great to see the Netduino with a new supporter. The new documentation and libraries make the platform easier to use.

STM8S I2C Slave Device

Sunday, May 24th, 2015

Having succeeded at getting a basic I2C master working on the STM8S it is not time to start to look at I2C slave devices on the STM8S.

The project will be a simple one, the STM8S will take a stream of bytes and perform simple addition. When requested, the STM8S will return the total as a 16-bit integer. Each new write request will clear the current total and start the whole process again.

Simple enough so let’s get started.

This post is a fairly long as it contains a lot of code so it might be a good time to grab a beer and settle down.

Simple Slave Adder

The slave adder is a simple device running over I2C. The devices has the following properties:

  1. Acts as an I2C slave device with address 0x50
  2. I2C bus speed is 50 KHz
  3. Upon receiving a valid start condition and address for a write operation the device will clear the current total.
  4. Bytes written to the device will be summed and a running total kept.
  5. A start condition with a valid address for a read operation will return the current total as a 16-bit integer, MSB first.

A Netduino 3 will be used as the I2C bus master device. This runs the .NET Microframework and has an implementation of the I2C protocol built into the framework. This gives a good reference point for the master device i.e. someone else has debugged that so we can assume that the device is working as per the protocol specification.

As mentioned in the previous article on I2C Master devices, there is a really good article about the I2C protocol on Wikipedia. If you want more information about the protocol then I suggest you head over there.

Netduino Code

The initial version of the I2C master device will simply send two bytes of data for the I2C slave device to sum. The code is simple enough:

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

namespace I2CMaster
{
    public class Program
    {
        public static void Main()
        {
            //
            //  Create a new I2C object on address 0x50 with the clock running at 50 KHz.
            //
            I2CDevice i2cBus = new I2CDevice(new I2CDevice.Configuration(0x50, 50));
            //
            //  Create a transaction to write two bytes of data to the I2C bus.
            //
            byte[] buffer = { 1, 2 };
            I2CDevice.I2CTransaction[] transactions = new I2CDevice.I2CTransaction[1];
            transactions[0] = I2CDevice.CreateWriteTransaction(buffer);
            while (true)
            {
                int bytesRead = i2cBus.Execute(transactions, 100);
                Thread.Sleep(1000);
            }
        }
    }
}

The above application creates a new instance of the I2CDevice with a device address of )x50 and a clock frequency of 50 KHz. A single transaction is created and the master writes the same two bytes to the I2C bus every second.

STM8S Code

As with the I2C Master article, much of the action happens in two places:

  1. I2C device initialisation method
  2. I2C Interrupt Service Routine (ISR)

The initialisation method sets up the I2C peripheral on the STM8S and enters the waiting state, waiting for the master to put data onto the I2C bus.

The ISR will deal with the actual data processing and in a full application it will also deal with any error conditions that may arise.

I2C Initialisation

The initialisation method sets up the I2C bus by performing the following tasks:

  1. Setting the expected clock frequency
  2. Setting the device address
  3. Turning the interrupts on

The I2C peripheral must be disabled before configuring certain aspects of the peripheral, namely clock speeds:

I2C_CR1_PE = 0;

The peripheral needs to know the current master clock frequency, the I2C mode (Standard or Fast) and the clock divider values. These must all be configured whilst the peripheral is disabled.

I2C_FREQR = 16;                     //  Set the internal clock frequency (MHz).
I2C_CCRH_F_S = 0;                   //  I2C running is standard mode.
I2C_CCRL = 0xa0;                    //  SCL clock speed is 50 KHz.
I2C_CCRH_CCR = 0x00;

The device assumes that we will be using the standard 16 MHz clock speed which has been used in the other tutorials in this series. This is indicated by the I2C_FREQR register.

The values for the clock control register (I2C_CCRL and I2C_CCRH_CCR were simply taken from the STM8S programming reference manual. There is no coincidence that a bus speed of 50 KHz was chose, it is simply one of the reference values in the table. No point in creating work if you do not have to. If you want different speeds then you can either use one of the defined values in the remainder of the table or use the formulae provided.

The next step is to define the device address and addressing mode. I2C allows both 7 and 10 bit addressing modes. For simplicity this device will use a 7-bit device address:

I2C_OARH_ADDMODE = 0;               //  7 bit address mode.
I2C_OARH_ADD = 0;                   //  Set this device address to be 0x50.
I2C_OARL_ADD = 0x50;
I2C_OARH_ADDCONF = 1;               //  Docs say this must always be 1.

The device also allows for the maximum rise time to be configured. This example uses 17uS as the maximum time:

I2C_TRISER = 17;

The I2C peripheral allows for three different interrupt conditions to be defined, buffer interrupts, event interrupts (start condition etc.) and error interrupts. For convenience we will turn all of these on:

I2C_ITR_ITBUFEN = 1;                //  Buffer interrupt enabled.
I2C_ITR_ITEVTEN = 1;                //  Event interrupt enabled.
I2C_ITR_ITERREN = 1;

Now that the peripheral is configured we need to re-enable it:

I2C_CR1_PE = 1;

The last bit of initialisation is to configure the device to return an ACK after each byte:

I2C_CR2_ACK = 1;

At this point the I2C peripheral should be listening to the I2C bus for a start condition and the address 0x50.

I2C Interrupt Service Routine (ISR)

The ISR contains the code which processes the data and error conditions for the I2C peripheral. All of the I2C events share the same ISR and the ISR will need to interrogate the status registers in order to determine the exact reason for the interrupt.

Starting with an empty ISR we have the following code:

#pragma vector = I2C_RXNE_vector
__interrupt void I2C_IRQHandler()
{
}

There are several other vector names we could have chosen, they all contain the same value and the value I2C_RXNE_vector was chosen arbitrarily.

Assuming that there are no errors on the bus, the I2C master code above will cause the following events to be generated:

  1. Address detection
  2. Receive buffer not empty

This initial simple implementation can use these two events to clear the total when a new Start condition is received and add the current byte to the total when data is received.

if (I2C_SR1_ADDR)
{
    //
    //  In master mode, the address has been sent to the slave.
    //  Clear the status registers and wait for some data from the salve.
    //
    reg = I2C_SR1;
    reg = I2C_SR3;
    _total = 0;                 // New addition so clear the total.
    return;
}

I2C_SR1_ADDR should be set when an address is detected on the bus which matches the address currently in the address registers. As a starter application the code can simply assume that any address is going to be a write condition. The code can clear the totals and get ready to receive data. Note that this will be expanded later to take into consideration the fact that the master can perform both read and write operations.

The next event we need to consider is the receipt of data from the master. This will trigger the Receive Buffer Not Empty interrupt. This simple application should just read the buffer and add the current byte to the running total:

if (I2C_SR1_RXNE)
{
    //
    //  Received a new byte of data so add to the running total.
    //
    _total += I2C_DR;
    return;
}

As indicated earlier, the I2C ISR is a generic ISRT and is triggered for all I2C events. The initialisation code above has turned on the error interrupts as well as the data capture interrupts. Whilst none of the code in this article will handle error conditions, the ISR will output the status registers for diagnostic purposes:

PIN_ERROR = 1;
__no_operation();
PIN_ERROR = 0;
reg = I2C_SR1;
BitBang(reg);
reg = I2C_SR3;
BitBang(reg);

This will of course require suitable definitions and support methods.

Putting this all together gives an initial implementation of a slave device as:

//
//  This application demonstrates the principles behind developing an
//  I2C slave device on the STM8S microcontroller.  The application
//  will total the byte values written to it and then send the total
//  to the master when the device is read.
//
//  This software is provided under the CC BY-SA 3.0 licence.  A
//  copy of this licence can be found at:
//
//  http://creativecommons.org/licenses/by-sa/3.0/legalcode
//
#if defined DISCOVERY
    #include <iostm8S105c6.h>
#else
    #include <iostm8s103f3.h>
#endif
#include <intrinsics.h>

//
//  Define some pins to output diagnostic data.
//
#define PIN_BIT_BANG_DATA       PD_ODR_ODR4
#define PIN_BIT_BANG_CLOCK      PD_ODR_ODR5
#define PIN_ERROR               PD_ODR_ODR6

//
//  Somewhere to hold the sum.
//
int _total;

//
//  Bit bang data on the diagnostic pins.
//
void BitBang(unsigned char byte)
{
    for (short bit = 7; bit >= 0; bit--)
    {
        if (byte & (1 << bit))
        {
            PIN_BIT_BANG_DATA = 1;
        }
        else
        {
            PIN_BIT_BANG_DATA = 0;
        }
        PIN_BIT_BANG_CLOCK = 1;
        __no_operation();
        PIN_BIT_BANG_CLOCK = 0;
    }
    PIN_BIT_BANG_DATA = 0;
}

//
//  Set up the system clock to run at 16MHz using the internal oscillator.
//
void InitialiseSystemClock()
{
    CLK_ICKR = 0;                       //  Reset the Internal Clock Register.
    CLK_ICKR_HSIEN = 1;                 //  Enable the HSI.
    CLK_ECKR = 0;                       //  Disable the external clock.
    while (CLK_ICKR_HSIRDY == 0);       //  Wait for the HSI to be ready for use.
    CLK_CKDIVR = 0;                     //  Ensure the clocks are running at full speed.
    CLK_PCKENR1 = 0xff;                 //  Enable all peripheral clocks.
    CLK_PCKENR2 = 0xff;                 //  Ditto.
    CLK_CCOR = 0;                       //  Turn off CCO.
    CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
    CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
    CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
    CLK_SWCR = 0;                       //  Reset the clock switch control register.
    CLK_SWCR_SWEN = 1;                  //  Enable switching.
    while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
}

//
//  Initialise the I2C system.
//
void InitialiseI2C()
{
    I2C_CR1_PE = 0;                     //  Disable I2C before configuration starts.
    //
    //  Set up the clock information.
    //
    I2C_FREQR = 16;                     //  Set the internal clock frequency (MHz).
    I2C_CCRH_F_S = 0;                   //  I2C running is standard mode.
    I2C_CCRL = 0xa0;                    //  SCL clock speed is 50 KHz.
    I2C_CCRH_CCR = 0x00;
    //
    //  Set the address of this device.
    //
    I2C_OARH_ADDMODE = 0;               //  7 bit address mode.
    I2C_OARH_ADD = 0;                   //  Set this device address to be 0x50.
    I2C_OARL_ADD = 0x50;
    I2C_OARH_ADDCONF = 1;               //  Docs say this must always be 1.
    //
    //  Set up the bus characteristics.
    //
    I2C_TRISER = 17;
    //
    //  Turn on the interrupts.
    //
    I2C_ITR_ITBUFEN = 1;                //  Buffer interrupt enabled.
    I2C_ITR_ITEVTEN = 1;                //  Event interrupt enabled.
    I2C_ITR_ITERREN = 1;
    //
    //  Configuration complete so turn the peripheral on.
    //
    I2C_CR1_PE = 1;
    //
    //  Acknowledge each byte with an ACK signal.
    //
    I2C_CR2_ACK = 1;
}

//
//  I2C interrupts all share the same handler.
//
#pragma vector = I2C_RXNE_vector
__interrupt void I2C_IRQHandler()
{
    unsigned char reg;

    if (I2C_SR1_ADDR)
    {
        //
        //  Clear the status registers and wait for some data from the salve.
        //
        reg = I2C_SR1;
        reg = I2C_SR3;
        _total = 0;                 // New addition so clear the total.
        return;
    }
    if (I2C_SR1_RXNE)
    {
        //
        //  Received a new byte of data so add to the running total.
        //
        _total += I2C_DR;
        return;
    }
    //
    //  Send a diagnostic signal to indicate we have cleared
    //  the error condition.
    //
    PIN_ERROR = 1;
    __no_operation();
    PIN_ERROR = 0;
    //
    //  If we get here then we have an error so clear
    //  the error, output the status registers and continue.
    //
    reg = I2C_SR1;
    BitBang(reg);
    reg = I2C_SR3;
    BitBang(reg);
}

//
//  Main program loop.
//
int main()
{
    _total = 0;
    __disable_interrupt();
    //
    //  Initialise Port D.
    //
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR_DDR4 = 1;        //  Port D, bit 4 is output.
    PD_CR1_C14 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C24 = 1;         //  Pin can run up to 10 MHz.
    //
    PD_DDR_DDR5 = 1;        //  Port D, bit 5 is output.
    PD_CR1_C15 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C25 = 1;         //  Pin can run up to 10 MHz.
    //
    PD_DDR_DDR6 = 1;        //  Port D, bit 6 is output.
    PD_CR1_C16 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C26 = 1;         //  Pin can run up to 10 MHz.
    //
    InitialiseSystemClock();
    InitialiseI2C();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

Executing the Applications

At this stage a quick test with the two devices connected and the logic analyser will show the master device outputting data to the I2C bus. The correct operation of the I2C salve device can be verified by setting break points with in the two if statements in the ISR. Note that this will generate some errors but it is good enough to verify that the ISRs are being triggered correctly.

STM8 I2C Write Condition

STM8 I2C Write Condition

The above shows the output from the Saleae Logic Analyser when the write transaction is sent to the bus by the Netduino.

Reading and Writing with I2C Slaves

At this point the above code should be accepting data from the Netduino 3. It is now time to expand the code to take into account the requirement to read back data from the slave device.

Netduino Code

The Netduino code will need to be modified to generate both a write transaction to send data to the I2C slave device and a read transaction to retrieve the results from the calculation.

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

namespace I2CMaster
{
    public class Program
    {
        public static void Main()
        {
            //
            //  Create a new I2C object on address 0x50 with the clock running at 50 KHz.
            //
            I2CDevice i2cBus = new I2CDevice(new I2CDevice.Configuration(0x50, 50));
            //
            //  Create a transaction to write two bytes of data to the I2C bus.
            //
            byte[] buffer = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            byte[] resultBuffer = new byte[2];
            I2CDevice.I2CTransaction[] transactions = new I2CDevice.I2CTransaction[2];
            transactions[0] = I2CDevice.CreateWriteTransaction(buffer);
            transactions[1] = I2CDevice.CreateReadTransaction(resultBuffer);
            while (true)
            {
                int bytesRead = i2cBus.Execute(transactions, 100);
                Thread.Sleep(1000);
            }
        }
    }
}

The transaction array has been expanded to contain a second transaction to read the data from the device. The data to be summed has also been expanded to include a further eight elements.

STM8S Code

A state machine will be used to allow the STM8S to work out what action should be taken within the ISR. This is required as the i”c peripheral will generate an event for the address detection for both the write transaction and the read transaction. The application will need to be able to differentiate between the two conditions as one requires the total to be cleared, the other requires that the total is sent back to the master device.

The state machine contains the following states:

  1. Waiting for a write condition to start
  2. Adding data to the running total
  3. Sending the MSB of the total
  4. Sending the LSB of the total

This is represented in code by an enum and a global variable:

typedef enum
{
    ISWaiting,                    //  Waiting for comms to start.
    ISAdding,                     //  Adding bytes of data.
    ISSendingMSB,                 //  Sending MSB of total.
    ISSendingLSB                  //  Sending LSB of total.
} I2CStateType;
I2CStateType _i2cState;

The ISR will need to be changed in order to deal with the state machine and also handle the slave transmitting data to the master device. The first change is to the address detection. This assumes that the main program loop initialises the state to ISWaiting:

if (I2C_SR1_ADDR)
{
    //
    //  Slave address received, work out if to expect a read or a write.
    //
    if (_i2cState == ISWaiting)
    {
        _i2cState = ISAdding;
        _total = 0;             // New addition so clear the total.
    }
    reg = I2C_SR1;
    reg = I2C_SR3;
    return;
}

The code above works out if the application is waiting for the first write condition (IsWaiting) or if the address detection has been triggered by a read condition (any other state).

Next, any other data reception is assumed to be data to be added to the total. The state is changed to IsAdding just in case:

if (I2C_SR1_RXNE)
{
    //
    //  Receiving data from the master so we must be adding.
    //
    _total += I2C_DR;
    _i2cState = ISAdding;
    return;
}

Next we have two new conditions we have not considered so far, the first is the fact that we need to transmit the result to the master. The request from the client will trigger a Transmit Buffer Empty event:

if (I2C_SR1_TXE)
{
    if (_i2cState == ISAdding)
    {
        I2C_DR = (_total >> 8) & 0xff;
        _i2cState = ISSendingMSB;
    }
    else
    {
        I2C_DR = _total & 0xff;
        _i2cState = ISSendingLSB;
    }
    return;
}

The master is asking for two bytes and the application needs to track if we are transmitting the most significant byte (MSB) or least significant byte (LSB).

The next new condition is the acknowledge event. This is generated at the end of the master read operation. This will set the system back into a waiting condition:

if (I2C_SR2_AF)
{
    I2C_SR2_AF = 0;             //  End of slave transmission.
    _i2cState = ISWaiting;
    return;
}

The full application becomes:

//
//  This application demonstrates the principles behind developing an
//  I2C slave device on the STM8S microcontroller.  The application
//  will total the byte values written to it and then send the total
//  to the master when the device is read.
//
//  This software is provided under the CC BY-SA 3.0 licence.  A
//  copy of this licence can be found at:
//
//  http://creativecommons.org/licenses/by-sa/3.0/legalcode
//
#if defined DISCOVERY
    #include <iostm8S105c6.h>
#else
    #include <iostm8s103f3.h>
#endif
#include <intrinsics.h>

//
//  Define some pins to output diagnostic data.
//
#define PIN_BIT_BANG_DATA       PD_ODR_ODR4
#define PIN_BIT_BANG_CLOCK      PD_ODR_ODR5
#define PIN_ERROR               PD_ODR_ODR6

//
//  State machine for the I2C communications.
//
typedef enum
{
    ISWaiting,                    //  Waiting for comms to start.
    ISAdding,                     //  Adding bytes of data.
    ISSendingMSB,                 //  Sending MSB of total.
    ISSendingLSB                  //  Sending LSB of total.
} I2CStateType;
I2CStateType _i2cState;

//
//  Somewhere to hold the sum.
//
int _total;

//
//  Bit bang data on the diagnostic pins.
//
void BitBang(unsigned char byte)
{
    for (short bit = 7; bit >= 0; bit--)
    {
        if (byte & (1 << bit))
        {
            PIN_BIT_BANG_DATA = 1;
        }
        else
        {
            PIN_BIT_BANG_DATA = 0;
        }
        PIN_BIT_BANG_CLOCK = 1;
        __no_operation();
        PIN_BIT_BANG_CLOCK = 0;
    }
    PIN_BIT_BANG_DATA = 0;
}

//
//  Set up the system clock to run at 16MHz using the internal oscillator.
//
void InitialiseSystemClock()
{
    CLK_ICKR = 0;                       //  Reset the Internal Clock Register.
    CLK_ICKR_HSIEN = 1;                 //  Enable the HSI.
    CLK_ECKR = 0;                       //  Disable the external clock.
    while (CLK_ICKR_HSIRDY == 0);       //  Wait for the HSI to be ready for use.
    CLK_CKDIVR = 0;                     //  Ensure the clocks are running at full speed.
    CLK_PCKENR1 = 0xff;                 //  Enable all peripheral clocks.
    CLK_PCKENR2 = 0xff;                 //  Ditto.
    CLK_CCOR = 0;                       //  Turn off CCO.
    CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
    CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
    CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
    CLK_SWCR = 0;                       //  Reset the clock switch control register.
    CLK_SWCR_SWEN = 1;                  //  Enable switching.
    while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
}

//
//  Set up Port D GPIO for diagnostics.
//
void InitialisePortD()
{
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR_DDR4 = 1;        //  Port D, bit 4 is output.
    PD_CR1_C14 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C24 = 1;         //  Pin can run up to 10 MHz.
    //
    PD_DDR_DDR5 = 1;        //  Port D, bit 5 is output.
    PD_CR1_C15 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C25 = 1;         //  Pin can run up to 10 MHz.
    //
    PD_DDR_DDR6 = 1;        //  Port D, bit 6 is output.
    PD_CR1_C16 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C26 = 1;         //  Pin can run up to 10 MHz.
}

//
//  Initialise the I2C system.
//
void InitialiseI2C()
{
    I2C_CR1_PE = 0;                     //  Disable I2C before configuration starts.
    //
    //  Set up the clock information.
    //
    I2C_FREQR = 16;                     //  Set the internal clock frequency (MHz).
    I2C_CCRH_F_S = 0;                   //  I2C running is standard mode.
    I2C_CCRL = 0xa0;                    //  SCL clock speed is 50 KHz.
    I2C_CCRH_CCR = 0x00;
    //
    //  Set the address of this device.
    //
    I2C_OARH_ADDMODE = 0;               //  7 bit address mode.
    I2C_OARH_ADD = 0;                   //  Set this device address to be 0x50.
    I2C_OARL_ADD = 0x50;
    I2C_OARH_ADDCONF = 1;               //  Docs say this must always be 1.
    //
    //  Set up the bus characteristics.
    //
    I2C_TRISER = 17;
    //
    //  Turn on the interrupts.
    //
    I2C_ITR_ITBUFEN = 1;                //  Buffer interrupt enabled.
    I2C_ITR_ITEVTEN = 1;                //  Event interrupt enabled.
    I2C_ITR_ITERREN = 1;
    //
    //  Configuration complete so turn the peripheral on.
    //
    I2C_CR1_PE = 1;
    //
    //  Set the acknowledge to be ACK.
    //
    I2C_CR2_ACK = 1;
}

//
//  I2C interrupts all share the same handler.
//
#pragma vector = I2C_RXNE_vector
__interrupt void I2C_IRQHandler()
{
    unsigned char reg;

    if (I2C_SR1_ADDR)
    {
        //
        //  Slave address received, work out if to expect a read or a write.
        //
        if (_i2cState == ISWaiting)
        {
            _i2cState = ISAdding;
            _total = 0;             // New addition so clear the total.
        }
        reg = I2C_SR1;
        reg = I2C_SR3;
        return;
    }
    if (I2C_SR1_RXNE)
    {
        //
        //  Receiving data from the master so we must be adding.
        //
        _total += I2C_DR;
        _i2cState = ISAdding;
        return;
    }
    if (I2C_SR1_TXE)
    {
        if (_i2cState == ISAdding)
        {
            I2C_DR = (_total >> 8) & 0xff;
            _i2cState = ISSendingMSB;
        }
        else
        {
            I2C_DR = _total & 0xff;
            _i2cState = ISSendingLSB;
        }
        return;
    }
    if (I2C_SR2_AF)
    {
        I2C_SR2_AF = 0;             //  End of slave transmission.
        _i2cState = ISWaiting;
        return;
    }
    //
    //  Send a diagnostic signal to indicate we have cleared
    //  the error condition.
    //
    PIN_ERROR = 1;
    __no_operation();
    PIN_ERROR = 0;
    //
    //  If we get here then we have an error so clear
    //  the error, output the status registers and continue.
    //
    reg = I2C_SR1;
    BitBang(reg);
    BitBang(I2C_SR2);
    reg = I2C_SR3;
    BitBang(reg);
}

//
//  Main program loop.
//
int main()
{
    _total = 0;
    _i2cState = ISWaiting;
    __disable_interrupt();
    InitialisePortD();
    InitialiseSystemClock();
    InitialiseI2C();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

The above two pieces of code are fuller applications and if we execute these and hook up the logic anayser to the I2C bus we see the following output:

Adding 1 To 10

Adding 1 To 10

Reading From Multiple Devices

The I2C protocol allows for one or more devices with different slave addresses to be connected to the same I2C bus. The previous article used the TMP102 temperature sensor with slave address 0x48 and this article has created a slave device with address 0x50. It should therefore be possible to connect the two devices to the same bus and talk to each selectively.

Netduino Application

As with the previous examples, the Netduino 3 will be used as the I2C bus master. The applicaiton above will need to be merged with the code in the previous article.

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;

namespace I2CMaster
{
    public class Program
    {
        public static void Main()
        {
            //
            //  Create a new I2C object and the configurations for the STM8S and the TMP102.
            //
            I2CDevice.Configuration stm8s = new I2CDevice.Configuration(0x50, 50);
            I2CDevice.Configuration tmp102 = new I2CDevice.Configuration(0x48, 50);
            I2CDevice i2cBus = new I2CDevice(stm8s);
            //
            //  Create a transaction to write several bytes of data to the I2C bus.
            //
            byte[] buffer = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            byte[] resultBuffer = new byte[2];
            I2CDevice.I2CTransaction[] transactions = new I2CDevice.I2CTransaction[2];
            transactions[0] = I2CDevice.CreateWriteTransaction(buffer);
            transactions[1] = I2CDevice.CreateReadTransaction(resultBuffer);
            //
            //  Create a transaction to read two bytes of data from the TMP102 sensor.
            //
            byte[] temperatureBuffer = new byte[2];
            I2CDevice.I2CTransaction[] reading = new I2CDevice.I2CTransaction[1];
            reading[0] = I2CDevice.CreateReadTransaction(temperatureBuffer);
            while (true)
            {
                //
                //  Read data from the I2C bus.
                //
                i2cBus.Config = stm8s;
                int bytesRead = i2cBus.Execute(transactions, 100);
                i2cBus.Config = tmp102;
                bytesRead = i2cBus.Execute(reading, 100);
                //
                //  Convert the reading into Centigrade and Fahrenheit.
                //
                int sensorReading = ((temperatureBuffer[0] << 8) | temperatureBuffer[1]) >> 4;
                double centigrade = sensorReading * 0.0625;
                double fahrenheit = centigrade * 1.8 + 32;
                //
                //  Display the readings in the debug window and pause before repeating.
                //
                Debug.Print(centigrade.ToString() + " C / " + fahrenheit.ToString() + " F");
                //
                //  Now display the results of the addition.
                //
                string message = "";
                for (int index = 0; index < buffer.Length; index++)
                {
                    message += buffer[index].ToString();
                    if (index == (buffer.Length - 1))
                    {
                        message += " = " + ((resultBuffer[0] * 256) + resultBuffer[1]).ToString();
                    }
                    else
                    {
                        message += " + ";
                    }
                }
                Debug.Print(message);
                Thread.Sleep(1000);
            }
        }
    }
}

The above code creates two I2C configuration object, one for the TMP102 and one for the STM8S device. The I2C bus object has the configuration changed depending upon which device is required.

Wiring up the Devices

Firstly, wire up the TMP102 sensor as described in the article on I2C master devices. The SDA and SCK lines should be connected to 3.3V via 4K7 resistors. Next connect the SDA and SCK lines from the STM8S device to the same point as SDA and SCK lines from the TMP102 sensor. Throw in the logic analyser connections and you get something like this:

TMP102 and STM8S I2C slave devices

TMP102 and STM8S I2C slave devices

Running the Application

Running the above two applications and starting the logic analyser generated the following output:

Reading From Two I2C Slaves

Reading From Two I2C Slaves

The write operation to the far left (W0x50) sends the 10 bytes to the STM8S device. This is followed by a read (R0x50) of two bytes from the same device, this can be seen about two thirds of the way from the left-hand side of the trace. The final operation is the read of the current temperature, R0x48, to the right of the trace.

Conclusion

The STM8S slave device above is a simple device. There are still plenty of modification which need to be made:

  1. Add error handling
  2. Allow for trapping conditions such as clock stretching

to name but a few. The code above should allow for simple devices to be put together is a short space of time.

Intel Galileo – First Impressions

Thursday, August 21st, 2014

Yesterday I received my Intel Galileo rev 1 board. I know the rev 2 board is available but recently the Windows on Devices program have release the necessary firmware etc to upgrade a Galileo rev 1 board to enable it to run Windows.

See the end of this article for an update added on 6th Sept 2014.

Upgrading the Firmware

The first step was to check athe firmware and upgrade it if necessary. In my case it was necessary. Intel provide a comprehensive set of instructions on how to do this. The upgrade process took about 10 minutes.

Creating a Windows SD Card

The next step is to write Windows to a micro SD card. This step of the process took the longest to complete, about 25-30 minutes.

Booting to Windows

The next step is to verify that Windows has loaded correctly. Insert the card, power on and then waiting for 2 minutes for Windows to boot. If successful you should be able to telnet to the device. I used PTTYPortable to do this and was presented with the login request.

Testing the Board

One of the first tests I normally perform is to deploy a Blinky application to the board. Once I am happy that I can deploy applications to the board I speed up Blinky by removing any code which would cause a pause. The result should be an indicator of the performance of the board and the software. So let’s give it a go.

The Galileo board offers two methods for deploying Blinky to the board:

  1. Arduino UI
  2. Visual Studio Windows application

The first method uses the board without the Windows SD card image, the second deploys a Windows application to the SD card and runs as a Windows application.

Our starting point for the tests will be the standard Blink application:

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
*/
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup()
{                
    // initialize the digital pin as an output.
    pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop()
{
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);               // wait for a second
}

Arduino UI

Using this application in the Arduino UI for the Galileo is simple. In fact the application is one of the samples (File -> Examples -> 01.Basics -> Blink). Loading this sketch and deploying to the Galileo starts the on board LED blinking at a steadily at 1 Hz.

Visual Studio Windows Application

Microsoft have provided a Wiring API for use in Visual Studio. This allows access to the “Arduino” hardware from a Windows application. An equivalent Windows version of the same application is:

#include "stdafx.h"
#include "arduino.h"

int _tmain(int argc, _TCHAR* argv[])
{
    return RunArduinoSketch();
}

int led = 13;  // This is the pin the LED is attached to.

void setup()
{
    pinMode(led, OUTPUT); // Configure the pin for OUTPUT so you can turn on the LED.
}

// the loop routine runs over and over again forever:
void loop()
{
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    Log(L"LED OFF\n");
    delay(1000);               // wait for a second
    digitalWrite(led, HIGH);    // turn the LED on by making the voltage HIGH
    Log(L"LED ON\n");
    delay(1000);               // wait for a second
}

Much of the application looks the same as the Arduino application. The main differences are the addition of the _tmain method and the Log statement. The _tmain method acts as the entry point for the application and provides a method for running an Arduino sketch (as above) or some other program logic.

The Log statements generate debug information which is displayed in Visual Studio’s Output window.

Deploying this application to the board results in… NOTHING!

Digging a little deeper into the examples section of the web site reveals the On Board LED example:

// Main.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "arduino.h"

int _tmain(int argc, _TCHAR* argv[])
{
    return RunArduinoSketch();
}

//This application flashes the on board LED of the Galileo board by calling GPIO functions directly in the embprpusr.dll instead of using the Arduino layer.

ULONG state = LOW; // keeps track of the state of the on-board LED

void setup()
{
    GpioSetDir(LED_BUILTIN, OUTPUT); // Sets the pin to output
}

void loop()
{
    if (HIGH == state)
    {
	    state = LOW;
	} 
    else
    {
	    state = HIGH;
	}
    GpioWrite(LED_BUILTIN, state); // Writes to the pin, setting its value either HIGH (on) or LOW (off)
    Log(L"LED %s\n", (HIGH == state ? L"ON" : L"OFF"));
    Sleep(1000);
}

Compiling and deploying this application to the board results in the steady flashing of the on board LED.

Reverting to the previous sample and hooking up an oscilloscope reveals that pin 13 is indeed being toggled at 1 Hz it is just not connected directly to the on board LED.

How Fast Can We Go?

Now to find out how fast the board will actually run. Starting with the Arduino example, remove the delay statements, recompile and deploy to the board. Doing this resulted in a square wave with a 50% duty cycle and a frequency of 221Hz. That’s right Hz, not KHz!

Removing the delay and logging statements and deploying the Windows application results in a square wave. This time a 60Hz square wave with a 30% duty cycle is displayed on the oscilloscope.

There must be something wrong. Surely this board with a 400MHz processor should run faster than this.

What About the Netduino?

The Netduino has always had one issue when compared with Arduino and other similar board. Namely it is running interpreted code which is not real time due to the nature of the .NET Microframework and the way the framework runs. I have performed similar tests and I was convinced that it was faster. Only one way to find out, deploy some code to the Netduino Plus 2. This board runs the .NET Microframework on the STM32 family of microcontrollers at 168 MHz. The equivalent code to the two examples above is:

using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace NetduinoPlus2
{
    public class Program
    {
        public static void Main()
        {
            OutputPort dp = new OutputPort(Pins.GPIO_PIN_D13, false);
            while (true)
            {
                dp.Write(!dp.Read());
            }
        }
    }
}

Deploying this to the Netduino PLus 2 and hooking up the oscilloscope results in a square wave with a 50% duty cycle at 17.8 KHz.

Much faster than the Galileo board.

Update 6th Sept 2014

I have been looking through the schematic for the Galileo Rev 1 board and found that not all of the GPIO pins are connected through the CY8C9540A chip but are in fact connected directly top the Quark processor. These GPIOs should be capable of higher speeds. A quick test shows that these pins (Digital 2, 3 & 10) can all generate a 1.16 KHz square wave for an application compiled in debug mode. Compiling the same applications in release more and running the application on the Galileo increases the frequency from 1.16 KHz to 1.27 KHz.

Conclusion

I’ve only had this board for a few hours but I have deployed a few of the examples. The raw GPIO speed appears lower than the interpreted .NET Microframework equivalent. The Galileo has access to a network port with easy access to the Arduino Wiring API but then so does the .NET Microframework.

Some further investigation is required I believe.

Netduino, iPhone and Low Energy Bluetooth

Saturday, August 24th, 2013

A while ago I wrote a small library for the RN-42 Bluetooth module. This small module acted as a serial port when connected to the Netduino. Times have moved on with Bluetooth 4.0 being available on the iPhone I thought I would take another look at Bluetooth.

Bluetooth Module

The Bluetooth module we will be using is the RedBear Bluetooth Mini. This little module is a Bluetooth 4.0 Low Energy (BLE) module. In it’s simplest form it offers the ability to act as a serial port for your project. This is how we will be using this module for this project.

Bluetooth Device

To test this a BLE compatible device is required. Enter the iPhone 5 and the BLE Arduino application which RedBear have thoughtfully provided. The application may be written for Arduino but it is sending simple serial commands to the Arduino. Let’s see what it is doing and if we can use this application.

Serial Port Settings

First job is to figure out the serial port settings. Scanning the source code for the Arduino I came across the following line in the setup method:

BleFirmata.begin(57600);

My guess is that this is setting up the Arduino serial port to run at 57,600 baud using standard data bits, parity and top bit settings. Time to break out the logic analyser.

Connections between the Netduino and the RedBear module is simple:

Netduino PinRedbear BLE Module Pin
D0 (Rx)Tx
D1 (Tx)Rx
3.3VVin
GNDGND

Additionally, the logic analyser is connected to the Rx and Tx pins of the Netduino. An Async Serial analyser was added to the two pins through the Saleae software. This analyser has an Autobaud feature. Turn this on just in case we get the baud rate incorrect.

Next step is to install the BLE Arduino application on the iPhone 5. Start the application:

ReadBear BLE Software Opening Screen

ReadBear BLE Software Opening Screen

Press the Click button to connect to the ReadBear module.

Select Arduino Board

Select Arduino Board

Select the Arduino to connect to. The Arduino Uno has a similar pin out so select the Uno option.

Change a Pin Output Using BLE Software

Change a Pin Output Using BLE Software

So far there has been no output on the logic analyser. Change the state of an output pin (pressing H or L). This generates serial data:

Serial Data On the Logic Analyser

Serial Data On the Logic Analyser

A quick check in the Async Serial analysers properties shows that the data logic analyser thinks that the data is being sent at 60,000 baud, pretty close to the 57,600 which was found in the code earlier.

Netduino Code

The next step is to try to connect some code on the Netduino to the iPhone application. The simplest application we can use simply captures the incoming data on the serial port and displays the bytes received in the debug window of Visual Studio. The following should do the trick:

public class Program
{
    const int BUFFER_SIZE = 1024;

    public static void Main()
    {
        SerialPort sp = new SerialPort("COM1", 57600, Parity.None, 8, StopBits.One);
        sp.DataReceived += sp_DataReceived;
        sp.Open();
        Thread.Sleep(Timeout.Infinite);
    }

    static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string str;
        str = "";
        if (e.EventType == SerialData.Chars)
        {
            int amount;
            byte[] buffer;

            buffer = new byte[BUFFER_SIZE];
            amount = ((SerialPort) sender).Read(buffer, 0, BUFFER_SIZE);
            if (amount > 0)
            {
                for (int index = 0; index < amount; index++)
                {
                    str += buffer[index].ToString();
                    str += " ";
                }
                Debug.Print("Data: " + str);
            }
        }
    }
}

Deploying the above code to my Netduino Plus 2 and running the application gives the following output:

Data: 144 4 0
Data: 144
Data: 12 0
Data: 144
Data: 4 0

Pressing the H at the side of Pin 2 in the application generates the sequence 144 4 0. Following this up by pressing the H at the side of Pin 3 results in the sequence 144 12 0. Checking the Arduino code shows that the digital commands are prefixed by 0x90, i.e. 144.

The iPhone Application

Now that we know the iPhone and the Netduino can communicate (at least from the phone to the Netduino) let’s have a look at creating our own iPhone application. The simplest application would entail toggling a digital pin, so let’s do that. We’ll turn an LED on and off.

Starting XCode we will create a new project (Single View Application) for the iPhone and set the application be targeted at the iPhone only and to use Automatic Reference Counting (ARC):

New iPhone Project Options

New iPhone Project Options

Next we need to add the CoreBluetooth.Framework references to the application. So scroll down the frameworks and click on the + button under the Linked Frameworks section. Type bluetooth in order to search the installed frameworks.

Next, we need to add the RedBear BLE framework. I downloaded this and put it in my stored libraries. This was then added by right clicking on the Frameworks folder of the project, selecting Add Files to… and then browsing to the folder which contained the files.

Next we head over to the Storyboard and add a few controls to our interface:

iPhone Application in XCode

iPhone Application in XCode

ControlDescription
btnConnectToNetduinoButton to connect/disconnect to/from the Netduino.
lblStatusLabel to indicate the status of the application.
lblLEDStatusLabel containing the text LED Status
swLEDSwitch which is used to turn the LED on/off.
aiBusyIndicator which shows when the application is trying to detect the Bluetooth module.

First thing to do is to connect the controls to the code in the header file for the view controller:

#import <UIKit/UIKit.h>
#import "BLE.h"

//
//  Define the various modes for the state machine.
//
#define MODE_DISCONNECTED       0
#define MODE_CONNECTING         1
#define MODE_CONNECTED          2
#define MODE_DISCONNECTING      3

//
//  Timeout values.
//
#define TIMEOUT_BLE_TIMEOUT         2
#define TIMEOUT_BUSY_CONNECTING     3.0

@interface SNCViewController : UIViewController <BLEDelegate>
{
    IBOutlet UILabel *lblStatusMessage;
    IBOutlet UILabel *lblLEDStatus;
    IBOutlet UISwitch *swLED;
}

- (IBAction) btnConnect_TouchUpInside:(UIButton *) sender;
- (IBAction) swLED_Changed:(UISwitch *)sender;

@property (strong, nonatomic) BLE *ble;
@property (strong, nonatomic) UIActivityIndicatorView *aiBusy;
@property (strong, nonatomic) IBOutlet UIButton *btnConnectToNetduino;

@end

Now we have the controls defined in the header file we can start to work on the methods.

Definitions

We need some simple definitions at the head of our implementation file:

#import "SNCViewController.h"
#import "BLE.h"

@interface SNCViewController ()

@end

@implementation SNCViewController

@synthesize ble;
@synthesize btnConnectToNetduino;

//
//  Private local variables.
//
int mode = MODE_DISCONNECTED;

These definitions create the Bluetooth object and he variables required for state control.

Supporting Methods

The Connected and Disconnected methods change the interface and state variables when we have successfully connected or disconnected to/from the Bluetooth module:

//
//  Set up the interface to show that the Bluetooth module is connected
//
- (void) Connected
{
    [lblStatusMessage setText:@"Connected"];
    lblLEDStatus.hidden = false;
    swLED.hidden = false;
    [btnConnectToNetduino setTitle:@"Disconnect from Netduino" forState:UIControlStateNormal];
    btnConnectToNetduino.enabled = true;
    [self.aiBusy stopAnimating];
    mode = MODE_CONNECTED;
}

//
//  Set up the interface to show that the Bluetooth module is disconnected
//
- (void) Disconnected
{
    [lblStatusMessage setText:@"Disconnected"];
    lblLEDStatus.hidden = true;
    swLED.hidden = true;
    [btnConnectToNetduino setTitle:@"Connect to Netduino" forState:UIControlStateNormal];
    btnConnectToNetduino.enabled = true;
    [self.aiBusy stopAnimating];
    mode = MODE_DISCONNECTED;
}

Standard Methods (viewDidLoad and didReceiveMemoryWarning)

These methods are generated by default when the application is first created. For viewDidLoad we will set up the application for initial use. We will not be modifying the didReceiveMemoryWarning method in this application.

//
//  Setup the view.
//
- (void) viewDidLoad
{
    [super viewDidLoad];
    //
    //  Setup the display.
    //
    lblLEDStatus.hidden = true;
    [swLED setOn:NO];
    swLED.hidden = true;
    [lblStatusMessage setText:@"Ready"];
    self.aiBusy.hidesWhenStopped = true;
    //
    //  Create the Bluetooth objects.
    //
    ble = [[BLE alloc] init];
    [ble controlSetup:1];
    ble.delegate = self;
    //
    //  Setp the simple properties.
    //
    mode = MODE_DISCONNECTED;
}

//
//  Process the memory warning event.
//
- (void) didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

The main point to note in the viewDidLoad method is that the Bluetooth object is initialised.

Button Press Event

The btnConnect_TouchUpInside event initiates the connection/disconnection of the application to/from the Bluetooth module.

//
//  User has pressed the Connect button so try to connect/disconnect from the Netduino.
//
- (IBAction) btnConnect_TouchUpInside:(UIButton *) sender
{
    if (ble.activePeripheral)
    {
        if (ble.activePeripheral.isConnected)
        {
            [[ble CM] cancelPeripheralConnection:[ble activePeripheral]];
        }
    }
    ble.peripherals = nil;
    switch (mode)
    {
        case MODE_DISCONNECTED:
            [lblStatusMessage setText:@"Connecting..."];
            [NSTimer scheduledTimerWithTimeInterval:(float) TIMEOUT_BUSY_CONNECTING target:self selector:@selector(connectionTimer:) userInfo:nil repeats:NO];
            [ble findBLEPeripherals:TIMEOUT_BLE_TIMEOUT];
            [self.aiBusy startAnimating];
            mode = MODE_CONNECTING;
            break;
        case MODE_CONNECTED:
            [lblStatusMessage setText:@"Disconnecting..."];
            [self.aiBusy startAnimating];
            mode = MODE_DISCONNECTING;
            break;
        default:
            break;
    }
    btnConnectToNetduino.enabled = false;
}

Timer

The btnConnect_TouchUpInside event starts a timer when it is trying to connect to a Bluetooth module. This timer prevents the application from locking when searching for a Bluetooth module which does not exist. The timer callback stops the search process after 3 seconds:

//
//  The timer started by the Connect button has triggered.  See if we have any
//  Bluetooth modules nearby.  If we have then connect to the first one in the
//  list.
//
-(void) connectionTimer:(NSTimer *) timer
{
    if (ble.peripherals.count > 0)
    {
        [ble connectPeripheral:[ble.peripherals objectAtIndex:0]];
    }
    else
    {
        [lblStatusMessage setText:@"Cannot find Netduino"];
        mode = MODE_DISCONNECTED;
    }
    [self.aiBusy stopAnimating];
}

Switching the LED on/off

The application assumes only one LED is connected to the Netduino. The data packet sent to the Netduino therefore contains a 1 or 0 to indicate the status of the LED:

//
//  User has changed the value of the LED.
//
- (IBAction) swLED_Changed:(UISwitch *) sender
{
    UInt8 buffer[1];
    buffer[0] = sender.isOn == YES ? 1 : 0;
    NSData *data = [[NSData alloc] initWithBytes:buffer length:1];
    [ble write:data];
}

Bluetooth Module Events

The final two events are generated by the Bluetooth library. These are fired when the module connects/disconnects to/from the module:

//
//  Connected to a Bluetooth module successfully.
//
-(void) bleDidConnect
{
    [self Connected];
}

//
//  Application has disconnected from a Bluetooth module.
//
- (void) bleDidDisconnect
{
    [self Disconnected];
}

//
//  RSSI has been updated.
//
- (void) bleDidDisconnect:(NSNumber *) rssi
{
}

Netduino Application

The final task is to modify the Netduino Application to process the data. An OutputPort is added to the application and this is connected to a digital IO pin. This pin is then turned on/off when serial data is received from the Bluetooth module:

public class Program
{
    const int BUFFER_SIZE = 1024;
    private static OutputPort led = new OutputPort(Pins.GPIO_PIN_D8, false);

    public static void Main()
    {
        SerialPort sp = new SerialPort("COM1", 57600, Parity.None, 8, StopBits.One);
        sp.DataReceived += sp_DataReceived;
        sp.Open();
        Thread.Sleep(Timeout.Infinite);
    }

    static void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string str;
        str = "";
        if (e.EventType == SerialData.Chars)
        {
            int amount;
            byte[] buffer;

            buffer = new byte[BUFFER_SIZE];
            amount = ((SerialPort) sender).Read(buffer, 0, BUFFER_SIZE);
            if (amount > 0)
            {
                for (int index = 0; index < amount; index++)
                {
                    str += buffer[index].ToString();
                    str += "";
                }
                if (buffer[0] == 1)
                {
                    led.Write(true);
                }
                else
                {
                    led.Write(false);
                }
                Debug.Print("Data: " + str);
            }
        }
    }
}

Does it Work?

Well of course it does and here’s the video to prove it:

First we connect to the module. Once connected we can start to turn the LED on and off using the iPhone.

Conclusion

The library provided by RedBear is a great starting point for using the iPhone to communicate with a BLE module. It is simple and easy to use for this type of application. This whole experiment only took a few hours to put together an execute.

Let’s see where this takes us 🙂

Temperature and Humidity Sensor Module for the Netduino GO!

Tuesday, June 25th, 2013

A while ago (forgive the pun), Arron Chapman and I started to collaborate on building a temperature and humidity sensor based upon the DHT22 sensor. One of the original posts discussing the module can be found here in the Netduino forums. From the very start we agreed that both the hardware and software would be open source. This post will discuss the basic hardware requirements and the software required to create a Temperature and Humidity Module for the Netduino GO!.

This post has a software bias given the relatively simple nature of the hardware being developed. Here is a flavour of what was achieved:

The design work for this module is the combined effort of Arron Chapman of Variable Labs and myself.

DHT22 Temperature and Humidity Sensor

The DHT22 is a four pin package capable of measuring temperature (+/- 0.5C) and humidity (+/- 5%). The package uses a single wire interface for communication and can be powered by 3.3-5V. The single wire protocol used is not compatible with Dallas single wire protocol.

The four pins should be connected as follows:

PinConnection
1VDD (3.3-5V)
2Data/Signal
3Ground
4Ground

Pin 2 (Data/Signal) should be connected to the microcontroller with a pull-up resistor to VDD.

The microcontroller sends a start signal to the sensor which then responds with the data representing the temperature and humidity. The data is terminated with a check sum. The sensor can only be read at most once every 2 seconds. The trace for a full start, transmit and end signal looks like this:

CondensedTrace

The communication starts with the microcontroller sending the start signal. The microcontroller pulls the signal line low for at least 1-10ms. This ensures that the sensor can detect the microcontrollers signal. The microcontroller then pulls up the signal line and then waits for 20-40us for the sensor to respond.

Zooming in on the start packet we would see something like:

StartSignal

As you can see, in this case the signal line was pulled low by the microcontroller for about 6.25mS.

The sensor then pulls the signal line low for 80us followed by pulling up the signal line for a further 80us. At this point the sensor is ready to start to transmit the temperature and humidity data.

The data is transmitted by varying the length of time the signal pin is held high. Transmission of a single bit starts by pulling the signal line low. A 0 bit is indicated by the sensor pulling the signal line high for 26-28us. pulling the signal high for 70us indicates a 1.

The temperature and humidity data is transmitted in a 40-bit packet. The first 16 bits hold the humidity information, the next 16 bits hold the temperature information and the final 8 bits contains the checksum. The following shows the full data packet from the sensor:

DHT22FullDataPacket

Both the temperature and the humidity are represented as an integer. The actual value is obtained by converting the binary number to decimal and then dividing by 10. If the high bit of the temperature reading is 1 then the value represents a negative temperature.

The final 8 bits of the data packet contain the checksum. The checksum is the result of adding the four bytes of the temperature and humidity data.

Schematic

Aside from the components required to make a basic module, the board really only required two parts, a single pull-up resistor and the DHT22 Temperature and Humidity sensor itself.

DHT22Schematic

The components to the left of the diagram should be familiar if you have read the previous posts on making a module. The only additional parts can be seen to the right of the schematic.

Breadboard and PCB Prototypes

The original work for this module was completed on breadboard with an additional LM35 temperature sensor at the side of the DHT22. This second sensor was used as a reference to confirm the readings being generated by the DHT22. The simplicity of the design meant that moving to a PCB prototype was relatively simple and the iTeadStudio prototyping service made this affordable. A few weeks after ordering the prototype modules arrived:

TemperatureSensorBarePC

One of the tests I do on any PCBs I have made is a connectivity test. I do this when the board is unpopulated and simply walk through the list of connections and verify that there are no problems. I also take the software I have written during prototyping on the breadboard and check that the pins on the STM8S are connected to the correct points on the PCB. It was during this test that I found that a connection had been missed off of the original schematic, namely the connection from the GO! connector and the GPIO pin which is used to signal that data is ready for the GO! to consume. A quick fix once the board had been populated.

TemperatureModulePCBPopulated

Notice the yellow wire – this goes to show the value of prototyping even for the smallest and seemingly simplest of projects.

Software

The one wire protocol means that the microcontroller will be both a master and a slave device as will the DHT22. We also have to allow for the fact that we also have the leave a 2 second gap between readings. The ideal way to implement this is to use a finite state machine. The cycle of events is as follows:

  1. Send the start signal and wait for 1-10ms
  2. Enter read mode and collect data
  3. Pause for at least 2 seconds

The state machine relies upon the timers to change the state based upon the minimum values for the time periods set by the sensor.

For the start signal, the signal line is set low and the timer started.

When the timer interrupt is triggered, the timer is turned off and reset. The signal pin is then switched from being an output pin to input with interrupts enabled. The timer is then restarted with a time period slightly larger than that required to ready all 40 bits of data from the sensor.

In the final stage, the interrupts are turned off, the data is processed and the system put to sleep (from a reading point of view) for more than 2 seconds.

In read mode, the system merely waits for interrupts to be generated by the sensor changing the state of the signal line. When an interrupt occurs, the time stamp (from the currently running timer) is read and recorded. The duration of the signal can then be calculated later (in stage three, pause mode) and the bit stream reconstructed from the timings.

Functionality

From a high level, the temperature and humidity module should provide the ability to read the current temperature and humidity (given the restrictions on the sensors delay of 2 seconds between readings). In addition, it would be desirable to allow the system to generate alarms for readings which are out of range.

As with all Netduino GO! modules, this functionality is split between the module and the module driver running on the Netduino GO!. The code on the module takes care of all the communication with the sensor. It takes this information and then responds to requests from the module driver on the Netdunio GO!.

STM8S Module

The software on the STM8S started life as the basic STM8S module software which has been used in previous posts.

The first modification needed is to add the state machine. The STM8S periodically reads the values from the sensor and store them for later retrieval by the Netduino GO! The regular nature of this update means that there is always a “current” reading available to the Netduiino GO! So the first thing we need is to setup a timer:

//--------------------------------------------------------------------------------
//
//  Setup Timer 2 to pause for 3.2 seconds following power up.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x0a;       //  Prescaler = 1024
    TIM2_ARRH = 0xc3;       //  High byte of 50,000.
    TIM2_ARRL = 0x50;       //  Low byte of 50,000.
    TIM2_IER_UIE = 1;       //  Enable the update interrupts.
    TIM2_CR1_CEN = 1;
}

This initialisation code means that a reading is not available immediately. To go with this we will also need a variable to store the current mode along with some definitions:

//
//  Define the various modes for the state machine.
//
#define MODE_PAUSE                  0
#define MODE_SENDING_START_SIGNAL   1
#define MODE_READING_DATA           2
//
//  Current sensing mode.
//
int _mode = MODE_PAUSE;

Now for the critical part of the operation, we need to change to the correct state when the timer is triggered:

//--------------------------------------------------------------------------------
//
//  Timer 2 Overflow handler.
//
//  Note:   Normally we want the ISR to operate as quickly as possible but in
//          this case "as quickly as possible" just needs to be quick enough
//          for this sensor.  This means we have milliseconds for this ISR.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    TIM2_CR1_CEN = 0;
    switch (_mode)
    {
        case MODE_PAUSE:
            //
            //  Any pause has now completed, we need to start the
            //  read process.
            //
            PIN_DHT22_DATA = 0;
            TIM2_ARRH = 0xc3;       //  High byte of 50,000.
            TIM2_ARRL = 0x50;       //  Low byte of 50,000.
            TIM2_PSCR = 1;          //  Prescalar = 2 => count = 100,000 = 6.25mS
            TIM2_EGR_UG = 1;        //  Force counter update.
            _mode = MODE_SENDING_START_SIGNAL;
            break;
        case MODE_SENDING_START_SIGNAL:
            //
            //  At this point the start signal period has elapsed and we
            //  want to start to read data from the sensor.
            //
            PIN_DHT22_DATA = 1;
            PIN_DHT22_DIRECTION = 0;//  DHT22 pin is input.
            PIN_DHT22_MODE = 0;     //  DHT22 pin is floating input.
            EXTI_CR1_PDIS = 1;      //  Interrupt on rising edge.
            //
            //  We will get another interrupt after 5ms.  This should be
            //  enough time for the sensor to have generated the data and
            //  for us to process it.
            //
            TIM2_ARRH = 0x13;       //  High byte of 5,000.
            TIM2_ARRL = 0x88;       //  Low byte of 5,000.
            TIM2_PSCR = 4;          //  Prescalar = 4 => count = 5,000 (5 mS)
            TIM2_EGR_UG = 1;        //  Force counter update.
            _currentTiming = 0;
            _mode = MODE_READING_DATA;
            break;
        case MODE_READING_DATA:
            //
            //  At this point we should have read all of the data.  We
            //  now need to calculate the values and wait for 2 seconds
            //  before reading the next value.
            //
            PIN_DHT22_DATA = 1;     //  Set the output high after the data has been read.
            PIN_DHT22_DIRECTION = 1;//  DHT22 data pin is output.
            //
            //  We cannot read the sensor again for at least 2 seconds (32,800 with a prescalar
            //  of 11 should result in a ~4 seconds delay).
            //
            TIM2_ARRH = 0x80;
            TIM2_ARRL = 0x20;
            TIM2_PSCR = 0x0b;
            TIM2_EGR_UG = 1;        //  Force counter update.
            if (_currentTiming == 42)
            {
                DecodeData();
                if (_alarmsEnabled != 0)
                {
                    CheckAlarms();
                }
            }
            else
            {
                OutputStatusCode(SC_TOO_LITTLE_DATA);
            }
            _mode = MODE_PAUSE;
            break;
    }
    TIM2_CR1_CEN = 1;               //  Re-enable Timer 2.
    TIM2_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

Much of the code in the Interrupt Service Routine (ISR) above is concerned with recording the current state and resetting the timer ready to move into the next state. A key point to notice here is the change of use for the GPIO pin connected to the sensor.

When the ISR is entered and we are in pause mode (case MODE_PAUSE), we reset the timer and drop the signal line connected to the DHT22 (PIN_DHT22_DATA = 0). We then enter the next mode, MODE_SENDING_START_SIGNAL.

The next time the ISR is entered we should have just completed the time needed to keep the signal line low and for the sensor to be ready to send the data on the signal line. So we pull the signal line high and then change the direction of the GPIO line and the line becomes an input line which generates and interrupt. We then record the fact that we have changed to the next mode (MODE_READING_DATA), reset the timers and start to wait again.

In the final state, we should have received a full data packet from the sensor so we decode and process the data and then enter the pause mode once more. At this point the whole cycle repeats.

The state machine is now complete and we now need to start to look at recording the data. A little early experimentation showed that the sensor would generate 42 pulses. It was decided that the best way to record the pulses was to simply record the time at which they occurred. These timings could later be used to workout the pulse duration and hence if we had a 0 or 1. As we only needed the pulse duration then the actual time of the pulse was not needed, simply a reliable way of recording the duration. Timer 2 came to our aid. This timer was already running as it was required to control the state machine. We could simply record the value in this timer whenever an interrupt occurred. So starting with some definitions to support the storage of the data:

//
//  How many readings will we take?
//
#define MAX_READINGS                50
//
//  Somewhere to put the data.
//
int _sensorTimings[MAX_READINGS];
//
//  Which reading are we expecting next?
//
int _currentTiming = 0;
int _reading;

Next we need to capture the value in the timer when an interrupt occurred:

//--------------------------------------------------------------------------------
//
//  Process the interrupt generated by the DHT22.
//
#pragma vector = 8
__interrupt void EXTI_PORTD_IRQHandler(void)
{
    unsigned char high = TIM2_CNTRH;
    unsigned char low = TIM2_CNTRL;
    if (_currentTiming < MAX_READINGS)
    {
        _reading = (high << 8);
        _reading += low;
        _sensorTimings[_currentTiming++] = _reading;
    }
}

Now we have captured the data we need to decode the timings to obtain the temperature, humidity and the checksum. This method is called from the Timer 2 ISR above.

//--------------------------------------------------------------------------------
//
//  Decode the temperature and humidity data.
//
void DecodeData()
{
    unsigned short multiplier;
    //
    //  Extract the humidity.
    //
    unsigned short humidity = 0;
    multiplier = 32768;
    for (int index = 2; index < 18; index++)
    {
        if ((_sensorTimings[index] - _sensorTimings[index - 1]) > LOGIC_BOUNDARY)
        {
            humidity += multiplier;
        }
        multiplier >>= 1;
    }
    //
    //  Extract the temperature.
    //
    unsigned short temperature = 0;
    multiplier = 32768;
    for (int index = 18; index < 34; index++)
    {
        if ((_sensorTimings[index] - _sensorTimings[index - 1]) > LOGIC_BOUNDARY)
        {
            temperature += multiplier;
        }
        multiplier >>= 1;
    }
    //
    //  Extract the checksum.
    //
    unsigned short checksum = 0;
    multiplier = 128;
    for (int index = 34; index < 42; index++)
    {
        if ((_sensorTimings[index] - _sensorTimings[index - 1]) > LOGIC_BOUNDARY)
        {
            checksum += multiplier;
        }
        multiplier >>= 1;
    }
    //
    //  If the checksum is OK then overwrite the data.
    //
    unsigned short calcChecksum = 0;
    calcChecksum += humidity & 0xff;
    calcChecksum += ((humidity >> 8) & 0xff);
    calcChecksum += temperature & 0xff;
    calcChecksum += ((temperature >> 8) & 0xff);
    if ((calcChecksum & 0xff) == checksum)
    {
        _lastTemperature = temperature;
        _lastHumidity = humidity;
        _lastChecksum = checksum;
        OutputStatusCode(SC_OK);
        OutputDebugData(humidity, temperature, checksum);
    }
    else
    {
        OutputStatusCode(SC_CHECKSUM_ERROR);
    }
}

The LOGIC_BOUNDARY definition is simply the number of clock pulses which separates the 0 and the 1. A little empirical research gave this value as 100 clock pulses. This is not exactly as defined in the data sheet but it does give a reasonable working value.

As with all GoBus 1.0 modules, the definition of the data within the data packets (with the exception of the first byte) is left to the implementation. For this module the packets will be formatted as follows:

  • 0x80 – Mandatory first byte
  • ACK or NACK
  • Type of data in the packet (for an ACK packet)
  • Data (for ACK)

The type of data in the packet allows the system to respond to requests for information and also raise alarms.

Responding to the requests for readings is simply a case of copying the last set of readings from the global variable and putting them into the packet. The module then raises an interrupt (via NotifyGOBoard()) to indicate to the Netduino GO! that there is some data ready for processing:

//--------------------------------------------------------------------------------
//
//  Copy the sensor readings into the _txBuffer.
//
void CopySensorReadingsToTxBuffer()
{
    _txBuffer[3] = ((_lastHumidity >> 8) & 0xff);
    _txBuffer[4] = (_lastHumidity & 0xff);
    _txBuffer[5] = ((_lastTemperature >> 8) & 0xff);
    _txBuffer[6] = (_lastTemperature & 0xff);
    _txBuffer[7] = _lastChecksum;
}

//--------------------------------------------------------------------------------
//
//  Notify the GO! main board that there is some data ready for collection.
//
void GetReadings()
{
    _txBuffer[0] = 0x80;
    _txBuffer[1] = DHT22_ACK;
    _txBuffer[2] = DHT22_GET_READINGS;
    CopySensorReadingsToTxBuffer();
    NotifyGOBoard();
}

One desirable feature mentioned earlier was the ability to set alarms and have the module signal to the Netduino GO! that an alarm has been raised. The alarm system allows the user to set an alarm for the following events:

  • Low temperature
  • High temperature
  • Low humidity
  • High humidity

Setting an alarm is a simple operation as it only requires that the module records the alarms which have been set and the values associated with the alarm:

//--------------------------------------------------------------------------------
//
//  Set the alarm thresholds for the temperature and humidity.
//
//  Note that the alarm uses "special" out of range values to indicate that a
//  specified limit is not required.
//
void SetAlarms()
{
    //
    //  Start by turning everything off.
    //
    _alarmsEnabled = 0;
    _lowerTemperatureAlarm = SENSOR_MIN_TEMPERATURE;
    _upperTemperatureAlarm = SENSOR_MAX_TEMPERATURE;
    _lowerHumidityAlarm = SENSOR_MIN_HUMIDITY;
    _upperHumidityAlarm = SENSOR_MAX_HUMIDITY;
    //
    //  Now work out what we are looking at.
    //
    if (_rxBuffer[2] & ALARM_LOW_TEMPERATURE)
    {
       _lowerTemperatureAlarm = (_rxBuffer[3] * 256) + _rxBuffer[4];
       if (_lowerTemperatureAlarm < SENSOR_MIN_TEMPERATURE)
       {
           _alarmsEnabled = 0;
           RaiseNAK();
           return;
       }
       _alarmsEnabled |= ALARM_LOW_TEMPERATURE;
    }
    if (_rxBuffer[2] & ALARM_HIGH_TEMPERATURE)
    {
        _upperTemperatureAlarm = (_rxBuffer[5] * 256) + _rxBuffer[6];
       if (_upperTemperatureAlarm > SENSOR_MAX_TEMPERATURE)
       {
           _alarmsEnabled = 0;
           RaiseNAK();
           return;
       }
        _alarmsEnabled |= ALARM_HIGH_TEMPERATURE;
    }
    if (_rxBuffer[2] & ALARM_LOW_HUMIDITY)
    {
       _lowerHumidityAlarm = (_rxBuffer[7] * 256) + _rxBuffer[8];
       if (_lowerHumidityAlarm < SENSOR_MIN_HUMIDITY)
       {
           _alarmsEnabled = 0;
           RaiseNAK();
           return;
       }
       _alarmsEnabled |= ALARM_LOW_HUMIDITY;
    }
    if (_rxBuffer[2] & ALARM_HIGH_HUMIDITY)
    {
        _upperHumidityAlarm = (_rxBuffer[9] * 256) + _rxBuffer[10];
       if (_upperHumidityAlarm > SENSOR_MAX_HUMIDITY)
       {
           _alarmsEnabled = 0;
           RaiseNAK();
           return;
       }
        _alarmsEnabled |= ALARM_HIGH_HUMIDITY;
    }
    //
    //  Tell the Go board which alarms have been enabled (as an acknowledgement).
    //
    _txBuffer[0] = 0x80;
    _txBuffer[1] = DHT22_ACK;
    _txBuffer[2] = DHT22_SET_ALARMS;
    _txBuffer[3] = _alarmsEnabled;
    NotifyGOBoard();
}

The actual process of raising the alarm is performed in the Timer 2 ISR when we have decoded the data. Remember this code:

DecodeData();
if (_alarmsEnabled != 0)
{
    CheckAlarms();
}

When checking the alarms the application will compare the values for all of the alarms and then raise a single interrupt back to the Netduino GO! if one or more alarms need to be raised. The data packet sent back to the Netduino GO! also contains the current sensor readings. This means that the Netduino GO! does not have to request the sensor data to find out which alarm has been raised.

//--------------------------------------------------------------------------------
//
//  Check to see if the temperature/humidity is outside of any ranges and raise
//  an alarm if needed.
//
void CheckAlarms()
{
    unsigned char alarm = 0;
    if ((_alarmsEnabled & ALARM_LOW_TEMPERATURE) && (_lastTemperature < _lowerTemperatureAlarm))
    {
        alarm |= ALARM_LOW_TEMPERATURE;
    }
    if ((_alarmsEnabled & ALARM_HIGH_TEMPERATURE) && (_lastTemperature > _upperTemperatureAlarm))
    {
        alarm |= ALARM_HIGH_TEMPERATURE;
    }
    if ((_alarmsEnabled & ALARM_LOW_HUMIDITY) && (_lastHumidity < _lowerHumidityAlarm))
    {
        alarm |= ALARM_LOW_HUMIDITY;
    }
    if ((_alarmsEnabled & ALARM_HIGH_HUMIDITY) && (_lastHumidity > _upperHumidityAlarm))
    {
        alarm |= ALARM_HIGH_HUMIDITY;
    }
    if (alarm != 0)
    {
        _txBuffer[0] = 0x80;
        _txBuffer[1] = DHT22_ACK;
        _txBuffer[2] = DHT22_ALARM;
        CopySensorReadingsToTxBuffer();
        _txBuffer[8] = alarm;
        NotifyGOBoard();
    }
}

At this point the majority of the STM8S code is complete.

Netduino GO!

As with the STM8S code, the Netduino GO! driver is based upon the code developed previously and used as the basis for the Output Expander module. Much of this should be familiar and so we will only be considering the methods and supporting structures which implement specific features in this module. The class diagram for our module driver looks like this:

DHT22ModuleNETMFClassDiagram

As you can see the module driver is not overly complex as it provides a few methods and supports a single interrupt.

The STM8S module returns temperature data on two occasions, the first is the explicit request by the Netduino GO! for the current readings, the second is when an alarm is raised. It therefore makes sense to abstract this code into a method:

/// <summary>
/// Extract the temperature and humidity data from the data which has been
/// generated by the STM8S.
/// </summary>
/// <param name="temperature">Temperature extracted from the buffer.</param>
/// <param name="humidity">Humidity extracted from the buffer.</param>
private void ExtractSensorReadings(out float temperature, out float humidity)
{
    //
    //  Verify the checksum before extracting the data.
    //
    int sum = 0;
    for (int index = 4; index < 8; index++)
    {
        sum += _readFrameBuffer[index];
    }
    if ((sum & 0xff) == _readFrameBuffer[8])
    {
        //
        //  Checksum good so extract the temperature and humidity data.
        //
        humidity = ((float) ((_readFrameBuffer[4] * 256) + _readFrameBuffer[5])) / 10;
        int sign = 1;
        byte highByte = _readFrameBuffer[6];
        if ((highByte & 0x80) == 0x80)
        {
            sign = -1;
            highByte &= (byte) 0x7f;
        }
        temperature = sign * ((float) ((highByte * 256) + _readFrameBuffer[7])) / 10;
    }
    else
    {
        throw new Exception("ExtractSensorData: Checksum error, discarding data.");
    }
}

The code which gets the current readings is relatively trivial:

/// <summary>
/// This method calls the AddOne method on the GO! module and then waits for the
/// module to indicate that there is a response ready.  The response is then read
/// from the module and the resulting value is returned to the caller.
/// </summary>
public void GetReadings()
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = (int) Action.GetReadings;
    WriteDataToModule();
    if (!WaitForResponse())
    {
        throw new Exception("GetReadings: Cannot communicate with the DHT22 module");
    }
}

Setting an alarm is also trivial, although the method is longer, with much of the work involving extracting the bytes of data and storing them in the transmit buffer:

/// <summary>
/// Set the alarms for the low/high temperature/humidity alarms.
/// </summary>
/// <remarks>
/// To turn an alarm off set the value to float.MinValue.
/// 
/// The alarms are triggered when the temperature/humidity goes below the low threshold
/// or above the high threshold.
/// </remarks>
/// <param name="lowTemperature">Low temperature alarm value.</param>
/// <param name="highTemperature">High temperature alarm value.</param>
/// <param name="lowHumidity">Low humidity alarm value.</param>
/// <param name="highHumidity">High humidity alarm value.</param>
public void SetAlarms(float lowTemperature, float highTemperature, float lowHumidity, float highHumidity)
{
    Alarms alarms = 0;

    if (lowTemperature != float.MinValue)
    {
        if ((lowTemperature < MIN_TEMPERATURE) || (lowTemperature > MAX_TEMPERATURE))
        {
            throw new ArgumentOutOfRangeException("SetAlarms: lowTemperature out of range.");
        }
        short lt = (short) (lowTemperature * 10);
        _writeFrameBuffer[3] = (byte) ((lt & 0xff00) >> 8);
        _writeFrameBuffer[4] = (byte) (lt & 0xff);
        alarms |= Alarms.LowTemperature;
    }
    if (highTemperature != float.MinValue)
    {
        if ((highTemperature > MAX_TEMPERATURE) || (highTemperature < MIN_TEMPERATURE))
        {
            throw new ArgumentOutOfRangeException("SetAlarms: highTemperature out of range.");
        }
        short ht = (short) (highTemperature * 10);
        _writeFrameBuffer[5] = (byte) ((ht & 0xff00) >> 8);
        _writeFrameBuffer[6] = (byte) (ht & 0xff);
        alarms |= Alarms.HighTemperature;
    }
    if (lowHumidity != float.MinValue)
    {
        if ((lowHumidity < MIN_HUMIDITY) || (lowHumidity > MAX_HUMIDITY))
        {
            throw new ArgumentOutOfRangeException("SetAlarms: lowHumidity out of range");
        }
        short lh = (short) (lowHumidity * 10);
        _writeFrameBuffer[7] = (byte) ((lh & 0xff00) >> 8);
        _writeFrameBuffer[8] = (byte) (lh & 0xff);
        alarms |= Alarms.LowHumidity;
    }
    if (highHumidity != float.MinValue)
    {
        if ((highHumidity < MIN_HUMIDITY) || (highHumidity > MAX_HUMIDITY))
        {
            throw new ArgumentOutOfRangeException("SetAlarms: highHumidity out of range");
        }
        short hh = (short) (highHumidity * 10);
        _writeFrameBuffer[9] = (byte) ((hh & 0xff00) >> 8);
        _writeFrameBuffer[10] = (byte) (hh & 0xff);
        alarms |= Alarms.HighHumidity;
    }
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = (int) Action.SetAlarms;
    _writeFrameBuffer[2] = (byte) alarms;
    WriteDataToModule();
    if (!WaitForResponse())
    {
        throw new Exception("SetAlarms: Cannot communicate with the DHT22 module");
    }
}

The really interesting work involves the receipt of data from the module. This is triggered by the interrupt being raised on the GPIO pin of the Netduino GO! connector.

/// <summary>
/// Handle the IRQ events generated by the GO! module.
/// </summary>
/// <remarks>
/// The module raises an interrupt when a command has been processed or when there
/// is data ready for the module.  The first task for this method is to retrieve 
/// the buffer from the module and then work out what action should be taken.  The
/// module will have placed any relevant data into the write buffer prior to raising 
/// the interrupt.
/// </remarks>
private void _irqPort_OnInterrupt(uint data1, uint data2, DateTime time)
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = (int) Action.GetBuffer;
    WriteDataToModule();
    if ((_readFrameBuffer[1] == GO_BUS10_COMMAND_RESPONSE) && (_readFrameBuffer[2] == DHT22_ACK) && ReadBufferCRCCheckOK())
    {
        float t, h;

        switch ((Action) _readFrameBuffer[3])
        {
            case Action.GetReadings:
                ExtractSensorReadings(out t, out h);
                Temperature = t;
                Humidity = h;
                _irqPortInterruptEvent.Set();
                break;
            case Action.SetAlarms:
                _irqPortInterruptEvent.Set();
                break;
            case Action.Alarm:
                if ((SensorAlarm != null) && (AlarmInterruptsEnabled == AlarmState.InterruptsEnabled))
                {
                    ExtractSensorReadings(out t, out h);
                    SensorAlarm(this, new AlarmEventArgs(t, h, (Alarms) _readFrameBuffer[9]));
                }
                break;
            default:
                throw new ArgumentException("Interrupt: Unknown action " + _readFrameBuffer[3].ToString());
                break;
        }
    }
}

This event allows the main program to set an alarm and then leave the module to work out when it needs to communicate with the Netduino GO!. This is achieved by the application setting the delegate SensorAlarm

Testing

At this point we have the hardware and software complete. All that is needed now is to put the two together:

TemperatureModuleConnectedToGOAndSevenSegment

A quick test application (I promise, this will be last piece of the code in this article):

private static DHT22 module;

/// <summary>
/// Main program loop.
/// </summary>
public static void Main()
{
    SevenSegmentDisplay display = new SevenSegmentDisplay();
    display.SetBrightness(0.5F);
    display.SetValue("----");
    module = new DHT22();
    module.AlarmInterruptsEnabled = DHT22.AlarmState.InterruptsDisabled;
    module.SensorAlarm += new DHT22.SensorAlarmHandler(module_SensorAlarm);
    module.SetAlarms(35.0f, float.MinValue, float.MinValue, 10.0f);
    module.AlarmInterruptsEnabled = DHT22.AlarmState.InterruptsEnabled;
    Thread.Sleep(5000);
    int counter = 0;
    while (true)
    {
        try
        {
            module.GetReadings();
            Debug.Print("Reading: " + counter.ToString() + ", temperature: " + module.Temperature.ToString("f2") + "C, Humidity: " + module.Humidity.ToString("f2") + "%");
            string text = "Read  Temp  Hum " + (counter.ToString() + "    ").Substring(0, 5) + (module.Temperature.ToString("f1") + "C     ").Substring(0, 6) + (module.Humidity.ToString("f1") + "%     ").Substring(0, 5);
            display.SetValue(module.Temperature, 1);
            Thread.Sleep(2000);
            display.SetValue(module.Humidity, 1);
            Thread.Sleep(2000);
        }
        catch (Exception ex)
        {
            Debug.Print(ex.Message);
            display.SetValue("EEEE");
        }
        counter++;
        if (counter > 9999)
        {
            counter = 0;
        }
    }
}

/// <summary>
/// Catch the interrupt generated when one of the temperature/humidity (or both)
/// exceed the alarm values.
/// </summary>
private static void module_SensorAlarm(object sender, DHT22.AlarmEventArgs args)
{
    string message = "";
    if ((args.AlarmsRaised & DHT22.Alarms.HighHumidity) != 0)
    {
        message += "High humidity,";
    }
    if ((args.AlarmsRaised & DHT22.Alarms.HighTemperature) != 0)
    {
        message += "High Temperature,";
    }
    if ((args.AlarmsRaised & DHT22.Alarms.LowHumidity) != 0)
    {
        message += "Low Humidity, ";
    }
    if ((args.AlarmsRaised & DHT22.Alarms.LowTemperature) != 0)
    {
        message += "Low Temperature,";
    }
    message += " alarm raised: temperature = " + args.Temperature.ToString() + ", humidity = " + args.Humidity.ToString();
    Debug.Print(message);
}

The image above shows the DHT22 module connected to the Netduino GO!. A Komodex Labs Seven Segment Display is used to display the temperature and humidity.

And here is a video showing the module working. The system shows the temperature for a short while (16.8C) followed by the humidity (46.7%).

We were lucky last year as we had a particularly cold winter (lucky for testing). This allowed the module to be test outdoors in a reasonably cold environment. The sensor and the Netduino GO! performed as expected down to -10C when compared to an off the shelf digital thermometer and humidity unit. I would have waited for the temperature to drop further by my desire to prove the module has limits and I discovered that standing around outside with a coffee waiting for the module to read lower and lower temperatures had an interest threshold of about 10 minutes.

Conclusion

This module shows the power of combining microcontrollers to provide a combined system. The Netduino GO! would have found it difficult to have achieved the work completed by the STM8S. Similarly, the Netduino GO! provides the application developer with the simplicity and power of NETMF, something the STM8S could not achieve.

This module has yet to move from prototype into production. Maybe it will someday, just not today.

Making a Netduino GO! Module – Conclusion

Tuesday, May 7th, 2013

Making the OutputExpander module has been an interesting journey. The original drawings started in August 2012 and then sat on the hard drive for about eight months. Much of the time following the original drawings were spent working out how the STM8S worked. You can find out more in The Way of the Register series (something I will pick up again soon, to my mind there are a few missing topics).

For those who do not know, I’m a software engineer and electronics is a hobby. The prospect of designing a board and using SMD components would have been unthinkable to me two years ago. Today I sit here with my first prototype PCB connected to a commercial board and the output looks reasonably professional – well I’ll let you decide.

Completed Board

Completed Board


Not looking too bad if I say so myself.

So let’s look at what I have learned and also how long the project took.

Lessons Learned

With all projects we should look back and learn from the experience, both good and bad. So here are a few things I have learned over the past few months.

Designing the Board

The original design started life in August 2012. I probably should have taken the plunge and developed the board a little quicker than I did although in truth, I did not get the major requirement of the board, namely GoBus 1.0 really sorted out until late November 2012.

Prototyping

This was probably the simplest bit of the project. I have all of the standard components in my toolbox already and I also have the tools required. This was really a case of getting the system working. The hardest part was getting to grips with the STM8S, a story I have documented in The Way of The Register series of posts.

Schematic

During this part of the design phase I tried several different packages. All of them had strengths and weaknesses. I finally settled on DesignSpark. For me this package had three major strengths:

  • It’s free
  • You can use it for commercial projects
  • It feels like a Windows application
  • In fairness it does have a few weaknesses. The most obvious for me was the lack of the ability to add images of any kind to the design. Come on, at version 5 you should have this one!

    Nets – I discovered these when producing the final draft of the schematic. These allowed the separation of the nets into logical groups/functions/areas. It made the schematic a lot cleaner.

    Schematic to Manufacture

    For me this was the where I learned the most. The first thing I learned was that auto-routers are dreadful. They are slow and produce some interesting board layouts. This board is a simple board and yet the auto-router still took a long time to make a half-hearted attempt at routing the board. In the end I did this manually. This was not too much of a problem as the board was simple.

    Next, you have to learn to think in three dimensions. You have two layers so use them.

    The Netduino modules produced by Secret Labs have nice rounded corners – these are a devil to produce in DesignSpark. I think that the module I produced has one corner which is slightly different from the others.

    The cost of prototyping is a lot lower than I thought. Ten boards including shipping costs about £18 and only took 10 days.

    I now know what 0403 means. The ‘0’ stands for Ohhh my goodness that’s small. Seriously, the four digits should be split into two and thy give the dimensions of the component. So for a metric component an 0403 part is 0.4 x 0.3 mm – that’s small.

    The STM8S part selected has a 0.65mm pitch for the pins. I originally found this a little worrying. Don’t be afraid – they are not that bad.

    Get a USB microscope when soldering SMD components. This tools is cheap and allows the examination of joints for shorts. The quality will never be great, mine only runs at 640×480, but a 400x zoom means you can be sure that you have no problems.

    Add test points. There came a point when I was making the board and I needed to see the data going through to the 74HC595’s. I did not have a suitable connection and so I had to solder a piece of wire to the board:

    Improvised Test Point

    Improvised Test Point

    A good test point would have made this easier.

    How Long Did it Take

    The original drawing started in August 2012 and the final board was put together and tested only yesterday. So in elapsed time that’s about nine months. In real working time this broke down as follows:

    Activity Duration (Hours)
    Building Prototype circuit 2
    Prototype software 3
    Schematic 6
    PCB layout 20
    Assembly and testing 5
    Enhanced software 4
    Total 40

    Something to bear in mind is that no production evaluation or component selection has been conducted as part of this project. It was supposed to be the final item on the list. I am still not sure if this should be taken through to manufacture – time will tell.

    Another item to be considered is to achieve the Netduino GO! logo approval. At the time of writing this required the approval of the board by Secret Labs – this activity has not been completed.

    Conclusion

    Well, that was a hectic few weeks.

    Did I enjoy it – YES!

    Would I recommend that you try it – YES!

    As for me, I’ll be taking a few days off of hardware development and blogging. Love doing it but it can take it’s toll.

    I suppose you may be interested in some downloads…

    If you use any of the code or techniques discussed in this series of posts then please let me know as I’m interested in what other people are doing with this work.

    Making a Netduino GO! Module – Stage 6 – Assembling the Prototype

    Monday, May 6th, 2013

    A few days ago I received a package from China, namely my Output Expander prototype boards:

    Bubble Wrapped Boards From China

    Bubble Wrapped Boards From China

    Could not wait to unwrap them:

    OutputExpander Bare Boards

    OutputExpander Bare Boards

    Only one thing left to do, start assembling them. As with all projects this will be broken down into steps:

    • Add the STM8S microcontroller and test
    • Add one 74HC595 shift register and test
    • Complete the board and add connectors and of course, test
    • By using a modular approach it should be easy to detect a problem with the design or assembly.

      Component List

      The board requires the following components:

      ComponentValueQuantity
      STM8S103F3NA1
      IDC socket1.27″ pitch1
      Sr1, SR2, Sr3, SR4SOL / SOP164
      C21uF0403
      C1, C3, C4, C5, C6, C7100nF – 04036
      Connectors0.1″Misc

      When these arrive be prepared, they are small!

      Adding the Microcontroller

      The board will need a microcontroller and some way of programming it. The logical first task is to add the controller, socket and the supporting passive components. Doing this will allow us to programme the controller with the firmware. As a test we can connect the programmed board to the Netduino Go!. If the connections between the board and the Netduino GO! are correct then the blue LED on the socket on the Netduino Go! should light.

      If you are attempting to follow this series and you are making your own board then I recommend you browse the net and have a look for videos on soldering SMD components. I found the tutorials on drag soldering really useful.

      Out with the soldering iron, a magnifier (it was needed). One thing I noticed was the difference between the 74HC595 pads and the pads for the STM8S. The 74HC595 component used was a built in component whilst the STM8S was a component I had created. The most noticeable difference between the two parts was the size of the pads on the PCB compared to the size of the component. The 74HC595 pads were elongated. These make soldering easier.

      STM8SPadsShiftRegisterPads
      STM8S75HC595

      Although the pins on the STM8S are only 0.65mm pitch, soldering is not as difficult as it first appears. A quick first attempt gave the following:

      STM8S Mounted On Board

      STM8S Mounted On Board

      There is only one item of concern and that is the whisker of solder between the fourth and fifth pins down on the right hand side of the image. This was quickly tidied up by dragging the soldering iron between the two pins.

      Next task was to add the passives which supported the STM8S leaving the passives for the shift registers for later. This is where you get some idea of the difference between the size of the components vs the size of the tools you are using:

      Capacitor and Tools

      Capacitor and Tools

      At this point I realised that an 0403 (metric sizing) component is 0.4mm x 0.3mm and the smallest soldering iron bit I has was about 1.5mm. Not to worry, the pads on the board are a reasonable size, simply tin the pads and then slide the capacitor into the molten solder.

      The next job was to add the socket for the GoBus. The sockets are surface mounted 1.27″ pitch IDC sockets. I found the easiest way to add these was to tin one pad and then slide the socket into place. The remaining pads could be soldered by placing the solder at the end of the connector and then applying heat and letting the solder run under the socket. It’s not as difficult as it sounds.

      At this point, the microcontroller should be in place with enough supporting hardware to allow it to be programmed. This was achieved by connecting the ST-Link/V2 programmer to the prototype board using the Komodex Breakout Board. The firmware developed in the previous posts was loaded into the development environment and deployed top the microcontroller.

      Programming the STM8S

      Programming the STM8S

      No deployment errors!

      A good indication that the microcontroller and the supporting hardware are functioning correctly.

      Add a Shift Register

      Next step is to add a single shift register and see if we get some output. Soldering the shift registers was a lot simpler than the STM8S as the pin pitch was greater. These could be soldered more conventionally although the pitch was finer than you may be used to if you have only worked with PTH components.

      Connecting the module to the Netduino GO! acts as a quick check:

      Netduino Go! Connected to OutputExpander

      Netduino Go! Connected to OutputExpander

      The blue LED lights up – the Netduino GO! recognises the OutputExpander as a valid module.

      Adding the single register worked and so the next task is to add the remaining registers and connectors.

      But All Was Not Well…

      During the assembly and testing process I had managed to accidentally short a few pins on the shift registers. This resulted in no output from the OutputExpander module. Breaking out the scope and the logic analyser proved that something was very wrong. The following trace shows the problem:

      Original Output From the OutputExpander

      Original Output From the OutputExpander

      It appears that the latch and clear lines were being triggered at the same time. I was able to establish by disconnecting the module from the circuit that there was not short between the two lines. Something else must be going on. Some further digging into the output from the logic analyser showed that the clear signal was being triggered slightly before the latch signal and that the latch was being released slightly after the clear signal. As a result I would expect no output from the shift registers – this is what I was seeing.

      Not wanting to waste money on components I continued to check the circuit but could not find anything else obviously wrong with the soldering or the software.

      Only one remaining option. Try a putting together a new board. Back to step one.

      A New Board

      Building the new board was a lot quicker than the first. Following the same procedure (one step at a time and test all the way) produced a new board:

      Before testing the board there was a final modification to make. This time to the software. The board has the outputs labelled from left to right with the lower bits being to the right of the board. The prototype module had the shift registers ordered from right to left. A quick change to the C code on the STM8S soon resolved this problem:

      //--------------------------------------------------------------------------------
      //
      //  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[5 - index];
          }
          OutputData();
          NotifyGOBoard();
      }
      

      A small bug had also been noticed in the clock configuration method. The code stated that CCO was turned off but the code actually turned it on. The code should read:

      //--------------------------------------------------------------------------------
      //
      //  Setup the system clock to run at 16MHz using the internal oscillator.
      //
      void InitialiseSystemClock()
      {
          CLK_ICKR = 0;                       //  Reset the Internal Clock Register.
          CLK_ICKR_HSIEN = 1;                 //  Enable the HSI.
          CLK_ECKR = 0;                       //  Disable the external clock.
          while (CLK_ICKR_HSIRDY == 0);       //  Wait for the HSI to be ready for use.
          CLK_CKDIVR = 0;                     //  Ensure the clocks are running at full speed.
          CLK_PCKENR1 = 0xff;                 //  Enable all peripheral clocks.
          CLK_PCKENR2 = 0xff;                 //  Ditto.
          CLK_CCOR = 0;                       //  Turn off CCO.
          CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
          CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
          CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
          CLK_SWCR = 0;                       //  Reset the clock switch control register.
          CLK_SWCR_SWEN = 1;                  //  Enable switching.
          while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
      }
      

      Testing

      The step by step testing process had shown that a single shift register worked, now to prove that four worked. Now it was time to add some more and connect some LEDs:

      Netduino Go OutputExpander and Some LEDs

      Netduino Go OutputExpander and Some LEDs

      And here’s a video of it working:

      Conclusion

      Assembly was not as difficult as it first appears even considering the small size of the components. In fact the STM8S was programmed first time.

      One piece of equipment I did find invaluable was a cheap USB microscope. These don’t give a high resolution image but they do allow you to zoom in on the board and check for problems.

      One final post left – time to reflect on the process.

    Making a Netduino GO! Module – Stage 5 – Enhancing the Drivers

    Saturday, April 27th, 2013

    This 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");
    }
    

    This method could have been rewritten to set the values to 0 and then send the values to the module. However, the prototype already had an implementation of a clear command and so this was left in as is.

    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.

    Making a Netduino GO! Module – Stage 3 – The Schematic

    Friday, April 12th, 2013

    In the previous posts a prototype output expander module was put together on breadboard and connected to the Netduino Go!. A small module driver was developed for the Netduino GO! and the STM8S. This video shows the basic module working:

    The next stage in the process is to make the final design decisions and produce a prototype PCB.

    Design Criteria

    The criteria as defined in the first post stated that the hardware should provide at least 16 digital outputs using low cost components. The breadboard prototype produced shows that we can certainly control 16 digital outputs using the common shift register.

    One possible extension is to use more shift registers than were used in the breadboard prototype. This will produce a module with more outputs. A quick look at a local suppliers web site shows that these can obtained for a relatively modest cost. Adding a further two shift registers will take the outputs from 16 to 32.

    The prototype shows which pin is being activated by a LED. The LED circuit includes a resistor and MOSFET/Transistor to allow the power for the LED to be provided by an external power supply rather than the 74HC595. Doing this allows the use of LEDs which would exceed the maximum power rating of the 74HC595. Although it should be possible to include these components (resistor and MOSFET) in the final design, they will be omitted in order to reduce the production costs.

    In summary:

    1. Use the STM8S as it is low cost and powerful enough for this task
    2. 32 digital output (4 shift registers)
    3. Use 0.1" connectors to the board can be used in breadboard prototyping

    PCB Design Software

    Over the past few years I have spent a fair amount of time looking for the right PCB design software. I have looked at the following:

    There are also several other packages available but these seem to be the three main packages offering free version of their software. Being a hobbyist price is a critical factor in the choice of software to use.

    Eagle PCB is available as a free personal edition. The software and licence are both restricted in some way. I have also found the interface to be exceedingly difficult to use. As a long time user of Windows applications the interface in Eagle PCB is counter-intuitive.

    KiCad is free and looks to be well supported but I found the interface difficult to use.

    DesignSpark looks to be well supported and has gone through several revisions, one major revision in the time in which I have been using the software.

    Of the three packages I have settled on using DesignSpark for the following reasons:

    • The interface is the most "standard Windows" like of the three packages
    • Free licence with no restrictions
    • Eagle parts can be imported into the library
    • Additional DesignSpark libraries are also available including one for Sparkfun’s components and boards

    DesignSpark is not restricted to producing just the PCB design file but can also render a 3D image of the final PCB. This allows you to visualise how the final board will look. The interface allows the board to be rotated and viewed from an infinite number of points of view.

    Schematic

    DesignSpark is fairly intuitive to use and there are a number of tutorials and how to guides available and so I will not go into too much detail regarding using the package. There is one tip I would like to give and that is use nets.

    Using nets allows the design file to be simplified greatly. You can break the design down into a number of logical components. Consider our design, this breaks down into the following three sections:

    1. STM8S and connector for the GoBus
    2. Shift registers
    3. Connectors for the digital outputs
    4. Now before I discovered how the nets feature works I would have dropped the components for the STM8S and connector on the schematic and then wired them together.

      Next I would have dropped the first of the shift registers onto the schematic and started to connect this to the STM8S. I would have then repeated this with the next shift register (connecting it to the first) and so on. The end result would have been a schematic which was difficult to read due to the number of connecting wires.

      This can be simplified by the use of nets and input/output connectors/pins. So lets have a look at how this appears for the first of out logical blocks, the STM8S and the GoBus connector:


      STM8S Showing Pin Labels

      STM8S Showing Pin Labels

      Looking at the above image you should note that the STM8S has two different connections, an un-named wire which goes off to another part of the circuit (i.e. pins PD5, PD6 etc.) and connections which are terminated with a name (i.e. PD3 is connected to something called SRClockOut).

      The connections which go off of the image are standard interconnects between the various components on the board. These interconnects have been restricted to connecting components in our logical function block (STM8S and GoBus connector). This includes any components required to support the STM8S such as capacitors etc.

      The second group of pins which go to named connections go to either an input or output pin. The names represent the function of the signal; so SRClockOut is the Shift Register Clock Output from the STM8S. If we look at the first shift register you will see that it has an input pin SR1ClockIn (Shift Register 1 Clock Input):

      Shift Register Showing Net Names

      Shift Register Showing Net Names

      We have two pins which have two different names but if we were to look at their properties we would find that they have both been connected to the same net, SRClock Shift Register Clock. By doing this the software knows that the two pins are in fact connected.

      This process has been repeated and a number of nets have been created, some have only two pins on them (the data output from the STM8S – SRData – is only connected to the input of shift register 1), others have several (the SRClock net connected the STM8S clock output pin to the clock input of all of the shift registers).

      Using nets simplifies the schematic and makes it easier to read. Using a standard naming convention for the input and output pins means that you should always be clear on which pins should be connected. It does add a new task to the design process. With a number of the pins named rather than connected you will need to verify that the pins are connected correctly.

      For a small schematic like this one the process of checking the connections is relatively simple. The software allows a component (or net) to be selected from a list of all of the components/nets in the circuit. A part/net is then highlighted on the schematic once it has been selected:


      Net Selection

      Net Selection

      The above diagram shows that the net SR4DataOut has been selected; see the blue highlighted name in the list at the right of the image. The green connections on the schematic show which pins are connected to the SR4DataOut net. Checking is then a case of repeating the process for each of the nets and noting which connections are highlighted.

      The final schematic for the module looks like this:

      Schematic

      Schematic

      A PDF version is also available as this may be difficult to read.

      Conclusion

      Now that we have the schematic we can translate this board into a PCB and from there we can get a 3D view of the board. Here is a sneak preview of the board in 3D:

      Output Expander - 3D View

      Output Expander – 3D View

      In the next post we shall have a look at taking the schematic and laying out the PCB.

    Making a Netduino GO! Module – Stage 2 – Connect the GO! to the Breadboard

    Friday, April 12th, 2013

    In the previous post a prototype for the multiple digital outputs was developed and implemented on breadboard. The STM8S code was independent and demonstrated that two chained 74HC595 shift registers could be controlled by bit-banging data through GPIO pins on the STM8S.

    In this post we will continue the development of the module by merging the code from the previous post with the STM8S module code from the post STM8S SPI Slave (Part 3) – Making a Go Module. We will develop both the STM8S code and the NETMF driver to implement the following functionality:

    • Clear the shift registers
    • Set the outputs of the two shift registers

    These two functions are already in the standalone version of the STM8S code and it should be a relatively simple exercise to merge this functionality with the Netduino Go Bus communication protocol.

    STM8S Application

    The first task to be completed for the STM8S code is to create a new project and add the code from the post STM8S SPI Slave (Part 3) – Making a Go Module. The two functions should then be removed and the two new ones we are implementing should be added.

    Start a New Project

    Start a new STM8S project and save the project into a new directory. Now take the source code in main.c from the project STM8S SPI Slave (Part 3) – Making a Go Module and paste over the code in you new project. Ensure that the project is targeting the correct microcontroller and save the project.

    Alternatively, copy the project code from STM8S SPI Slave (Part 3) – Making a Go Module into a new directory.

    Remove Dummy Functionality

    The project code in the STM8S module post contained a couple of dummy functions illustrating the concept of module functionality and communications. This code should be removed and the application stripped down to the basics required for SPI communication with the Netduino Go!.

    Add New Functionality

    The final task is to add the code which implements the new functionality as defined for the basic module (i.e. clear the shift registers and set the outputs of the shift registers).

    The first thing which is required is to add the #define statements to support the bit-banging:

    //--------------------------------------------------------------------------------
    //
    //  Pins which we will be using for output the data to the shift registers.
    //
    #define SR_CLOCK            PD_ODR_ODR3
    #define SR_DATA             PD_ODR_ODR2
    #define SR_CLEAR            PC_ODR_ODR3
    #define SR_OUTPUT_ENABLE    PC_ODR_ODR4
    #define SR_LATCH            PD_ODR_ODR4
    

    The InitialisePorts method will also need to be modified in order to ensure that the ports above are all setup correctly. We need these to be output ports capable of running at up to 10MHz. The code becomes:

    //--------------------------------------------------------------------------------
    //
    //  Initialise the ports.
    //
    void InitialisePorts()
    {
        //
        //  Initialise Port D.
        //
        PD_ODR = 0;             //  All pins are turned off.
        PD_DDR = 0xff;          //  All pins are outputs.
        PD_CR1 = 0xff;          //  Push-Pull outputs.
        PD_CR2 = 0xff;          //  Output speeds up to 10 MHz.
        //
        //  Initialise Port C.
        //
        PC_ODR = 0;             //  Turn port C outputs off.
        PC_DDR = 0x18;          //  PC3 &amp; PC4 initialised for output.
        PC_CR1 = 0x18;          //  PC3 &amp; PC4 Push-Pull outputs
        PC_CR2 = 0x18;          //  PC3 &amp; PC4 can run up to 10MHz.
        //
        //  Initialise the CS port for input and set up the interrupt behaviour.
        //
    #if defined(DISCOVERY)
        PB_ODR = 0;             //  Turn the outputs off.
        PB_DDR = 0;             //  All pins are inputs.
        PB_CR1 = 0xff;          //  All inputs have pull-ups enabled.
        PB_CR2 = 0xff;          //  Interrupts enabled on all pins.
        EXTI_CR1_PBIS = 2;      //  Port B interrupt on falling edge (initially).
    #else
        PA_ODR = 0;             //  Turn the outputs off.
        PA_DDR = 0;             //  All pins are inputs.
        PA_CR1 = 0xff;          //  All inputs have pull-ups enabled.
        PA_CR2 = 0xff;          //  Interrupts enabled on all pins.
        EXTI_CR1_PAIS = 2;      //  Port A interrupt on falling edge (initially).
    #endif
    }
    

    The application will also need some storage space for the data in the shift registers:

    //--------------------------------------------------------------------------------
    //
    //  Number of registers in the chain.
    //
    #define NUMBER_OF_REGISTERS     2
    //
    //  Data area holding the values in the register.
    //
    U8 _registers[NUMBER_OF_REGISTERS];             //  Data in the shift registers.
    

    Note that the variable used to store the data in the registers has been converted into a static array instead of a variable sized array using malloc.

    The function table needs to be modified to hold the references to the methods which will implement the module functionality:

    //
    //  Forward function declarations for the function table.
    //
    void SetShiftRegisters();
    void ClearShiftRegisters();
    //
    //  Table of pointers to functions which implement the specified commands.
    //
    FunctionTableEntry _functionTable[] = { { 0x01, ClearShiftRegisters }, { 0x02, SetShiftRegisters } };
    

    The next step is to add the methods which will implement the functionality:

    //--------------------------------------------------------------------------------
    //
    //  Clear the shift registers.
    //
    void ClearRegisters()
    {
        for (U8 index = 0; index < NUMBER_OF_REGISTERS; index++)
        {
            _registers[index] = 0;
        }
    }
    
    //--------------------------------------------------------------------------------
    //
    //  GO! Function 1 - Clear the shift registers.
    //
    void ClearShiftRegisters()
    {
        ClearRegisters();
        OutputData();
        NotifyGOBoard();
    }
    

    Note that the functionality has been implemented using two methods, the first ClearRegisters is the internal implementation. This is independent of the Netduino Go! communications and allows the functionality to be called in say the initialisation code of the module. The second method, ClearShiftRegisters is the method which is called by the code as determined by the Netduino Go! driver code. This has the additional output and notification methods which actually sets the data in the shift registers and sends a signal back to the Netduino Go! to let the board know that the request has been received and processed. Note that this split is not necessary but is a design decision taken for this particular module.

    The code should be ready to compile and deploy to the STM8S.

    Netduino Go! Driver

    At this point we should have one half of the communications channel ready to test. The second stage is to implement the driver on the Netduino Go!. As our starting point, we will copy the code from the BasicGoModule class in the post STM8S SPI Slave (Part 3) – Making a Go Module into a new class OutputExpander. We will modify this to add create new functionality to clear and set the shift registers.

    Command Constants

    The first this we need to do is to remove the old command constants and add two new ones:

    /// <summary>
    /// Command number for the ClearShiftRegister command.
    /// </summary>
    private const byte CMD_CLEAR_REGISTERS = 1;
    
    /// <summary
    /// Command number for the SetRegister command.
    /// </summary>
    private const byte CMD_SET_REGISTERS = 2;
    

    Modify the Supporting Methods

    The existing code in the module can be optimised for this module. To do this rename the WriteDataToModule method to WriteDataToSPIBus. Now add the following code:

    /// <summary>
    /// Write the data in the _writeFrameBuffer to the module.  Make several
    /// attempts to write the data before throwing an exception.
    /// </summary>
    /// <param name="exceptionMessage">Exception message to the used in the constructor if the write attempt fails.</param>
    private void WriteDataToModule(string exceptionMessage)
    {
        int retriesLeft = 10;
        bool responseReceived = false;
    
        WriteDataToSPIBus();
        while (!responseReceived && (retriesLeft > 0))
        {
            //
            //  We have written the data to the module so wait for a maximum 
            //  of 5 milliseconds to see if the module responds with some 
            //  data for us.
            //
            responseReceived = _irqPortInterruptEvent.WaitOne(5, false);
            if ((responseReceived) && (_readFrameBuffer[1] == GO_BUS10_COMMAND_RESPONSE))
            {
                //
                //  Assume good result, it is up to the calling method to determine if
                //  the command has been executed correctly.
                //
                return;
            }
            else
            {
                //
                //  No response within the 5ms so lets make another attempt.
                //
                retriesLeft--;
                if (retriesLeft > 0)
                {
                    WriteDataToSPIBus();
                }
            }
        }
        throw new Exception(exceptionMessage);
    }
    

    By making this change we are also making the assumption that the signal back from the module (via the interrupt) is always indicating success. This is a decision which is appropriate for this module but may not be appropriate for other applications/modules.

    Add New Functionality

    Before adding new functionality it is necessary to remove the existing AddFive functionality from the BasicGoModule.

    Out OutputExpander module will provide the application with two basic functions:

    • Clear – clear the shift registers setting the output to 0
    • Set – Set the values in the shift registers to those specified by the parameters

    This functionality is provided by the following code:

    /// <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;
        WriteDataToModule("Clear cannot communicate with the Output Expander 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 != 2)
        {
            throw new ArgumentException("registers: length should be 2 bytes");
        }
        _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
        _writeFrameBuffer[1] = CMD_SET_REGISTERS;
        for (int index = 0; index < registers.Length; index++)
        {
            _writeFrameBuffer[2 + index] = registers[index];
        }
        WriteDataToModule("Clear cannot communicate with the Output Expander module");
    }
    

    Testing

    At this point we should be able to add some code the Main method of the application to test the module. We will perform something similar to the code in the previous post, we will move a single LED but this time we will illuminate the LED starting a position 15 and working down to 0. This should ensure that we have deployed new code to both the module and the Netduino Go! board.

    public static void Main()
    {
        OutputExpander output = new OutputExpander();
        int cycleCount = 0;
        byte[] registers = new byte[2];
        while (true)
        {
            Debug.Print("Cycle: " + ++cycleCount);
            for (int index = 15; index >= 0; index--)
            {
                output.Clear();
                if (index < 8)
                {
                    registers[0] = (byte) (1 << index);
                    registers[1] = 0;
                }
                else
                {
                    registers[0] = 0;
                    registers[1] = (byte) (1 << (index - 8));
                }
                output.Set(registers);
                Thread.Sleep(200);
            }
        }
    }
    

    The test breadboard and Netduino Go! were connected through the Komodex GoBus Breakout Module. This allows the Netduino Go! and the ST-Link/2 programmer to be connected to the breadboard:

    Output Expander Connected to Netdunio GO!

    Output Expander Connected to Netdunio GO!

    And here is a video of this working:

    You can just see the Netduino Go! in the bottom left corner of the video.

    Conclusion

    At this point the basic concept has be proven and the application can perform the following:

    1. STM8S code can control one or more LEDs
    2. STM8S can be controlled using SPI
    3. Netduino Go! can pass instructions to the STM8S

    One thing that did become apparent during this part of the process was that both the STM8S code and the Netduino code can be improved by improved grouping the functionality. For instance, in order to add the methods to the function table in the STM8S code I had to jump around the source code a little.

    The next step in the process is to look at the hardware and start to prepare for prototype PCB manufacture.