RSS

Posts Tagged ‘Software Development’

Nikon D70 remote Control PCB Assembly

Sunday, November 10th, 2013

A few days ago the bare PCBs arrived from China:

Nikon D70 Bare Boards

Nikon D70 Bare Boards

Work commitments meant that assembly has had to wait a while. I finally managed to get to put the boards together today. There is certainly a difference in size between the proto-board and the final (well nearly) PCB.

Proto-board and Assembled Board With Ruler

Proto-board and Assembled Board With Ruler

I’ve documented the assembly process of SMD boards in the past so in this post I’ll just be documenting the lessons learned from this assembly

Measure Twice, Cut Once

A carpenter friend of mine passed on this advice and it certainly rings true on this build. If you look at the back of the board there are a couple of options for connecting power to the board. The intention was to allow the board to be powered by 2 x AA batteries or a CR2032 coin cell. The connection points were supposed to be placed to allow the use of two battery holders I had purchased from Bitsbox. The measurements are a classic "off by one" case. The connections are both 2.54mm off.

Next time I’ll be double checking the footprint of the components.

Solder Paste is Opaque

I originally tried to apply solder paste to the pads for the STM8S and then position the chip on the pads. The theory is great but in practice the positioning is difficult as you cannot see the pads through the paste. Instead I found it easier to apply flux and then tack down one pin on he STM8S. This allowed the positioning of the pads and legs of the STM8S to be checked. Once I was happy with the alignment of the two I applied solder paste to the top of the legs on the chip and then heated the pins.

0402 Components are Small

A few of the components are small, very small. Most of these do not require any orientation but the LED indicating that the power is applied is small and does require a particular orientation. I found a cheap USB microscope useful to help ensure the orientation was correct.

Conclusion

I always forget how small some of these components are but with a little practice you can work with surface mount components. The current board looks like this:

Assembled Board With Ruler

Assembled Board With Ruler

The Nikon D70 infra-red remote control prototype still triggers the camera using the trigger button. The next stage is to work on the software allowing the trigger of the remote control using the UART either by the FTDI and the RedBearBLE mini.

Nikon D70 Remote Control with a Button and EEPROM

Thursday, September 12th, 2013

A remote control which can only generate a signal when initially powered up is of limited use. The addition of a switch will allow the user to determine when the camera is triggered.

Adding a Button

The initial versions of the infra-red remote control for the Nikon D70 which have been discussed so far are limited in that the control sequence is transmitted when the STM8S starts and never again until the next time the STM8S is restarted/reset. Adding a button to the remote control will allow the user to select when the control sequence is transmitted.

One of the main problems with switches is that they are subject to switch bounce. You can see an example of this in the post regarding External Interrupts on the STM8S. There are two approaches to switch debouncing, software and hardware. The button on the infra-red remote control will be triggering a sequence of pulses which will typically last several milliseconds. This means that it lends itself to software debouncing using a simple state machine.

The state machine will put the infra-red remote control into two states, waiting for user input and running. When waiting for user input the interrupt handler for the switch will accept user input. When in the running mode the system will be generating the infra-red output for the camera and it will ignore any input from the switch. At the end of the infra-red sequence the switch input will be re-enabled.

So much for the theory, lets have a look at the code. The first thing which the application will need to do is to setup the appropriate pin as an interrupt port:

//--------------------------------------------------------------------------------
//
//  Now set up the ports.
//
//  PD3 - IR Pulse signal.
//  PD4 - Input pin indicating that the user wishes to trigger the camera.
//
void SetupPorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    //
    //  PD3 is the output for the IR control.
    //
    PD_DDR_DDR3 = 1;
    PD_CR1_C13 = 1;
    PD_CR2_C23 = 1;
    //
    //  Now configure the input pin.
    //
    PD_DDR_DDR4 = 0;        //  PD4 is input.
    PD_CR1_C14 = 1;         //  PD4 is floating input.
    PD_CR2_C24 = 1;
    //
    //  Set up the interrupt.
    //
    EXTI_CR1_PDIS = 1;      //  Interrupt on rising edge.
    EXTI_CR2_TLIS = 1;      //  Rising edge only.
}

The modifications to the SetupPorts method keeps the infra-red output on PD3 but adds an input on PD4 with a rising edge interrupt.

The next step is to deal with the interrupt on PD4. Here we need to work out if we are waiting for an interrupt and if we are then the application needs to start the generation of the infra-red signal. If we are not waiting for a button press then we should ignore the user request as it is likely to be a result of switch bounce. The code starts to look like this:

//--------------------------------------------------------------------------------
//
//  Process the interrupt generated by the pressing of the button.
//
//  This ISR makes the assumption that we only have on incoming interrupt on Port D.
//
#pragma vector = 8
__interrupt void EXTI_PORTD_IRQHandler(void)
{
    if (_currentState != STATE_RUNNING)
    {
        //
        //  Set everything up ready for the timers.
        //
        
        //  TODO!
        
        //
        //  Now we have everything ready we need to force the Timer 2 counters to
        //  reload and enable Timers 1 & 2.
        //
        TIM2_CR1_URS = 1;
        TIM2_EGR_UG = 1;
        TIM1_CR1_CEN = 1;
        TIM2_CR1_CEN = 1;
    }
}

The exact code required for setting up the timer will be revealed following the EEPROM section of this post.

Using EEPROM

In the post Storing Data in the EEPROM on the STM8S we saw how we could save data into the EEPROM of the STM8S for later retrieval. The data used in the example should look familiar if you have been following this series on the Nikon D70 Remote Control as it is the timing and signal data which triggers the Nikon D70.

The first thing we should do is to modify the order in which the data is written into the EEPROM. In the above post, the timing data is written into the EEPROM low byte followed by high byte. By swapping the order we can store the data in the order required by the interrupt for Timer 2. So the first task is to modify the application which wrote and verified the timing information to the following:

//
//  Write a series of bytes to the EEPROM of the STM8S105C6 and then
//  verify that the data has been written correctly.
//
//  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

//
//  Data to write into the EEPROM.
//
unsigned int _pulseLength[] = { 2000U, 27830U, 400U, 1580U, 400U, 3580U, 400U };
unsigned char _onOrOff[] =    {   1,      0,     1,     0,    1,     0,    1 };
char numberOfValues = 7;

//--------------------------------------------------------------------------------
//
//  Write the default values into EEPROM.
//
void SetDefaultValues()
{
    //
    //  Check if the EEPROM is write-protected.  If it is then unlock the EEPROM.
    //
    if (FLASH_IAPSR_DUL == 0)
    {
        FLASH_DUKR = 0xae;
        FLASH_DUKR = 0x56;
    }
    //
    //  Write the data to the EEPROM.
    //
    char *address = (char *) 0x4000;        //  EEPROM base address.
    *address++ = (char) numberOfValues;
    for (int index = 0; index < numberOfValues; index++)
    {
        *address++ = (char) ((_pulseLength[index] >> 8) & 0xff);
        *address++ = (char) (_pulseLength[index] & 0xff);
        *address++ = _onOrOff[index];
    }
    //
    //  Now write protect the EEPROM.
    //
    FLASH_IAPSR_DUL = 0;
}

//--------------------------------------------------------------------------------
//
//  Verify that the data in the EEPROM is the same as the data we
//  wrote originally.
//
void VerifyEEPROMData()
{
    PD_ODR_ODR2 = 1;            //  Checking the data
    PD_ODR_ODR3 = 0;            //  No errors.
    //
    char *address = (char *) 0x4000;        //  EEPROM base address.
    if (*address++ != numberOfValues)
    {
        PD_ODR_ODR3 = 1;
    }
    else
    {
        for (int index = 0; index < numberOfValues; index++)
        {
            unsigned int value = (*address++ << 8);
            value += *address++;
            if (value != _pulseLength[index])
            {
                PD_ODR_ODR3 = 1;
            }
            if (*address++ != _onOrOff[index])
            {
                PD_ODR_ODR3 = 1;
            }
        }
    }
    PD_ODR_ODR2 = 0;        // Finished processing.
}

//--------------------------------------------------------------------------------
//
//  Setup port D for data output.
//
void SetupPorts()
{
    //
    //  Initialise Port D.
    //
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR = 0xff;          //  All bits are output.
    PD_CR1 = 0xff;          //  All pins are Push-Pull mode.
    PD_CR2 = 0xff;          //  Pins can run up to 10 MHz.
}

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
void main()
{
    SetupPorts();
    SetDefaultValues();
    VerifyEEPROMData();
}

Create a new project for the above and execute the program on the STM8S Discovery board. This should set up the timing data ready for use to use.

Using the EEPROM Data

The above application has been tailored to write the byte data into the EEPROM in the order in which the bytes are required by the remote control application. Using this data should be a simple case of setting a pointer to the first byte and then consuming the bytes one after another. To do this we will need some pointers and a counter:

//
//  Define where we will be working in the EEPROM.
//
#define EEPROM_BASE_ADDRESS         0x4000
#define EEPROM_INITIAL_OFFSET       0x0000
#define EEPROM_DATA_START           (EEPROM_BASE_ADDRESS + EEPROM_INITIAL_OFFSET)

//
//  Data ready for the pulse timer ISR's to use.
//
int _numberOfPulses = 0;
int _currentPulse = 0;
char *_pulseDataAddress = NULL;

As part of the initialisation process we will need to set the pointer and also the number of pulses we have data for:

_pulseDataAddress = (char *) EEPROM_DATA_START;
_numberOfPulses = *_pulseDataAddress++;

So now we should have the initial state configured and we should revisited the button handler method. This method kicks off the timers by consuming the first three bytes of data:

//--------------------------------------------------------------------------------
//
//  Process the interrupt generated by the pressing of the button.
//
//  This ISR makes the assumption that we only have on incoming interrupt on Port D.
//
#pragma vector = 8
__interrupt void EXTI_PORTD_IRQHandler(void)
{
    if (_currentState != STATE_RUNNING)
    {
        //
        //  Set everything up ready for the timers.
        //
        _currentState = STATE_RUNNING;
        _currentPulse = 0;
        _pulseDataAddress = (char *) (EEPROM_DATA_START + 1);
        TIM2_ARRH = *_pulseDataAddress++;
        TIM2_ARRL = *_pulseDataAddress++;
        PD_ODR_ODR3 = *_pulseDataAddress++;
        //
        //  Now we have everything ready we need to force the Timer 2 counters to
        //  reload and enable Timers 1 & 2.
        //
        TIM2_CR1_URS = 1;
        TIM2_EGR_UG = 1;
        TIM1_CR1_CEN = 1;
        TIM2_CR1_CEN = 1;
    }
}

Finally, the interrupt for the Timer 2 interrupt needs to be modified in order to continue to process the data three bytes at a time until we have reached the total number of pulses:

//--------------------------------------------------------------------------------
//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    _currentPulse++;
    if (_currentPulse == _numberOfPulses)
    {
        //
        //  We have processed the pulse data so stop now.
        //
        PD_ODR_ODR3 = 0;
        TIM2_CR1_CEN = 0;
        TIM1_CR1_CEN = 0;           //  Stop Timer 1.
        _currentState = STATE_WAITING_FOR_USER;
    }
    else
    {
        TIM2_ARRH = *_pulseDataAddress++;
        TIM2_ARRL = *_pulseDataAddress++;
        PD_ODR_ODR3 = *_pulseDataAddress++;
        TIM2_CR1_URS = 1;
        TIM2_EGR_UG = 1;
    }
    TIM2_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

Conclusion

The addition of the button certainly makes the remote control more usable as the user can elect when to trigger the camera. At this stage, the EEPROM does not offer too many advantages over the use of the static data but it can allow the remote control to be fine-tuned at a later stage.

The acid test, does it still trigger the camera – Yes it does.

Modulated Nikon D70 Remote Control Signal

Sunday, September 8th, 2013

A quiet Sunday here in the North of England so I decided to add the 38.4KHz modulation to the Nikon D70 Infra-red Remote Control.

I considered two methods of modulating the signal:

  • Software implementation using a timer
  • Hardware implementation using PWM

The software implementation is attractive as it does not require the addition of any additional components to the circuit and hence reduces the cost of the remote control. On the downside, this requires a slightly more complex implementation and may cause some issues due to the timing of the interrupts.

Using PWM is a much simpler software solution as it only requires that the timers are setup correctly and turned on at the right time.

How Does Modulation Work?

Modulation works by combining a clock frequency (in the case of infra-red this is normally around 38KHz – 40 KHz) with a digital signal. When the digital signal is supposed to be at logic 1 then the clock signal is output rather than a stable logic level 1. When the signal is at logic level 0 then no signal is generated. The following illustrates this:

Digital signal:

Digital Signal

Digital Signal

Clock signal:

38.4KHz Clock Signal

38.4KHz Clock Signal

Combined output:

Digital Signal And Clock

Digital Signal And Clock

In the final image above, the top trace shows the clock signal, the middle trace shows the digital signal we wish to generate and the lower trace shows the signal which should be output by the circuit.

Hardware Changes

The hardware solution requires the combination of a digital signal with a PWM signal. The easiest way to do this is to use a single AND gate taking input from the digital output required and a clock signal.

Nikon Remote Circuit With Added Modulation

Nikon Remote Circuit With Added Modulation

Searching the RS Components web site lead to a single and gate component for a small price. This would be ideal for solving this problem.

Software Changes

The software changes are minimal as we simply need to configure a timer and turning it on and off as required. We will be using Timer 1, Channel 4 configured to generate a 38.4KHz PWM signal.

Setup

A 38.4KHz signal has a peak to peak duration of 26uS The system is running at 2MHz and so we would need a count value of 52 clock pulses (with no prescalar applied). Using Timer 2, Channel 4 results in the following setup code:

//--------------------------------------------------------------------------------
//
//  Set up Timer 1, channel 4 to output a single pulse lasting 240 uS.
//
void SetupTimer1()
{
    TIM1_ARRH = 0x00;       //  Reload counter = 51
    TIM1_ARRL = 0x33;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    //
    //  Now configure Timer 1, channel 4.
    //
    TIM1_CCMR4_OC4M = 7;    //  Set up to use PWM mode 2.
    TIM1_CCER2_CC4E = 1;    //  Output is enabled.
    TIM1_CCER2_CC4P = 0;    //  Active is defined as high.
    TIM1_CCR4H = 0x00;      //  26 = 50% duty cycle (based on TIM1_ARR).
    TIM1_CCR4L = 0x1a;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
}

Timer 2 Interrupt Handler

A minor change to the Timer 2 interrupt handler is required to turn off Timer 1 when the signal is no longer being generated:

//--------------------------------------------------------------------------------
//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    _currentPulse++;
    if (_currentPulse == _numberOfPulses)
    {
        //
        //  We have processed the pulse data so stop now.
        //
        PD_ODR_ODR3 = 0;
        TIM2_CR1_CEN = 0;
        TIM1_CR1_CEN = 0;           //  Stop Timer 1.
    }
    else
    {
        TIM2_ARRH = _counterHighBytes[_currentPulse];
        TIM2_ARRL = _counterLowBytes[_currentPulse];
        PD_ODR_ODR3 = _outputValue[_currentPulse];
        TIM2_CR1_URS = 1;
        TIM2_EGR_UG = 1;
    }
    TIM2_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

Main Loop

The final change is to the main program loop. This needs to start Timer 1 when the application starts to output data:

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
void main()
{
    unsigned int pulseLength[] = { 2000U, 27830U, 400U, 1580U, 400U, 3580U, 400U };
    unsigned char onOrOff[] =    {   1,      0,     1,     0,    1,     0,    1 };
    PrepareCounterData(pulseLength, onOrOff, 7);
    __disable_interrupt();
    SetupTimer2();
    SetupTimer1();
    SetupOutputPorts();
    __enable_interrupt();
    PD_ODR_ODR3 = _outputValue[0];
    //
    //  Now we have everything ready we need to force the Timer 2 counters to
    //  reload and enable Timer 2.
    //
    TIM2_CR1_URS = 1;
    TIM2_EGR_UG = 1;
    TIM2_CR1_CEN = 1;
    TIM1_CR1_CEN = 1;       // Start Timer 1
    while (1)
    {
        __wait_for_interrupt();
    }
}

Conclusion

Connecting the logic analyser to the circuit will allow the examination of the three signals, namely, the digital signal required (centre trace), the clock (upper trace) and the modulated output (lower trace):

Modulated IR Signal

Modulated IR Signal

The solid white blocks in the clock and modulated traces show a high density of signals. Zooming in on the right hand side of the capture shows the following:

Modulated IR Signal Zoom View

Modulated IR Signal Zoom View

As you can see, the modulated output is composed of a series of 38.4KHz clock pulses which are only generated when the digital signal should be high (logic 1). The remainder of the time the trace shows no output.

The final test is to see if this will trigger the camera, and yes, it still does.

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.

C# SerialPort on the Raspberry Pi

Sunday, May 19th, 2013

Sometimes you have to do something just because you can. Today was no exception. Take one Raspberry Pi board (Linux) and give it a dose of .NET loving…

I can hear people screaming NO! but it’s too late, I’ve done it.

To be honest, I’m not the first person to use .NET on the Raspberry Pi. In fact I used this installation guide to install Mono on the Raspberry Pi.

So what interested me? Simply put, hardware interfacing. Could the .NET hardware classes (i.e. SerialPort) be used on the Raspberry Pi? There was only one way to find out.

Raspberry Pi Setup

The first thing to do was to set up the Raspberry Pi using the instructions in the blog mentioned above. This was not difficult although it was time consuming as it took a while to compile.

When this is complete a simple program should demonstrate if the serial ports are available to us. The following code should show this:

namespace ConsoleApplication1
{
	class Program
	{
		public static void Main(string[] args)
		{
			SerialPort sp = new SerialPort("/dev/ttyAMA0", 115200, Parity.None, 8, StopBits.One);
			sp.Open();
			for (int index = 0; index < 10; index++)
			{
				string result = string.Format("{0} Testing", index);
				sp.Write(result);
			}
			sp.Close();
		}
	}
}

As you can see, this code is not too complex. It merely opens a serial port and then starts to output data to the port. The only tricky part of the job was to locate the name to be used for the serial port. It’s been too long since I used Unix in anger.

Testing the Application

Testing is not too complex as the test application is simply outputting a text message. We can see the output by hooking up PuTTY to a serial port on a computer.

The hardware is also simple to hook up. The FTDI cable I have allows me to connect a 3.3V TTL serial device to my PC over USB. This USB device then appears as a standard serial device on the PC. Three connections are required:

Raspberry PiFTDI Cable
GroundGround
RxTx
TxRx

Strictly speaking, we do not have to connect the Tx on the FTDI cable to the Rx on the Raspberry Pi as we will not be transmitting anything from the PC to the Raspberry Pi in this example.

Once the hardware is connected you will need to fire up PuTTY and connect it to the serial port on your PC. Ensure that the settings used match those in the application code above.

Assuming that you now have Mono installed and the application typed in ready to go, type the following commands into a shell on the Raspberry Pi:

mcs serial.cs
mono serial.exe

The first command invokes the Mono C# compiler. The second tells mono to execute the application which has been compiled.

If all has gone well then you will see something like this in the PuTTY window running on the PC:

Serial Output in Putty

Serial Output in Putty

Conclusion

I was rather interested to see that the C# skills acquired on the PC could also be used on the Raspberry Pi. The only thing missing on the Pi was a good development environment.

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 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.

Making a Netduino GO! Module

Saturday, April 6th, 2013

This series of posts will examine the activities required to take the concept of a module for the Netduino GO! through to production.

Project Definition

A simple project will allow the series of posts to concentrate on the principles required for the production of a Netduino GO! module without being too distracted by the functional aspects of the module. The following project definition should meet this brief:

  • Provide 16 or more digital outputs
  • Work with the Netduino GO!
  • Low cost manufacture
  • Use simple tried and tested components/techniques

Meeting the Objectives

As you can no doubt see, this is a reasonably simple project and with the exception of cost, we should have no major problems reaching the objectives. The most obvious solution to this problem is to look at using 74HC595 serial to parallel chips. These are cheap components and the techniques needed to solve this type of problem are tried and tested. The project definition looks like the counting example which is discussed in the Counting Using 74HC595 Shift Registers post with the addition of the Netduino Go! functionality.

Project Plan

Initial assessment of the project indicates that the following steps are required in order to take the project from concept to manufacture:

  1. Build a hardware prototype using the STM8S103F3 to control two shift registers. This will have some form of visual output to prove that we can control the digital lines (probably some LEDs)
  2. Write the software for the STM8S which will control the output of the 74HC595 chips
  3. Generate the schematic for the board
  4. Layout a prototype board and send to manufacture
  5. Write the software for the Netduino GO! while waiting for the manufactured boards to turn up
  6. Assemble a prototype on one of the prototype boards
  7. Conclusion

The first post in the series will build the breadboard prototype and start to control the LEDs using the STM8S.

STM32F4 Clocks

Thursday, March 28th, 2013

The STM32 contains several clocks each of which can be turned on or off when not used. In this post we will examine the clocks available and how they can be configured. To do this we will modify the simple GPIO example to change the clock parameters. Any changes to the system clock speed will increase or decrease the period of the delay. The results can be seen using an oscilloscope. We will look at the following topics:

At the end of this post you should be able to configure the system clock and introduce a known delay subject to the tolerances of the system clocks.

Clock Sources

The STM32 Reference Manual (RM0090) states that there are five clocks available:

  • High Speed Internal (HSI) oscillator
  • High Speed External (HSE) oscillator
  • Phase Locked Loop (PLL)
  • Low Speed Internal (LSI) clock
  • Low Speed External (LSE) clock

As already noted, each clock can be turned on/off as required. Turning an unused clock off reduces the power consumption of the microcontroller.

The first three clocks are used to drive the system clock for the microcontroller. The final two are low speed clocks and are primarily used to drive the watchdogs.

HSI

After reset, the STM32 enables the HSI oscillator. This has a relatively low accuracy, only 1%, but is suitable for most applications. Using the HSI oscillator eliminates the need for an external clock in the final circuit design. On the STM32F4, the HSI oscillator has a clock speed of 16 MHz.

HSE

The STM32 can operate using an external clock circuit. It is possible to design an external clock to run with a greater accuracy than the internal HSI clock enabling finer control of the operating parameters of the final circuit. The exact specification of the external clock frequency varies but is typically 4-16 MHz.

The STM32F4 Discovery Board has a built in external oscillator circuit fitted with a 8 MHz crystal.

PLL

The PLL is used to multiply it’s input clock source by a factor varying between 2 to 16. The input of the PLL is one of HSI, HSE or HSE/2.

It is important to note that the configuration of the PLL cannot be changed once it has been enabled.

LSI

The LSI is a low power clock used for the watchdog timers.

LSE

The LSE is powered by an external 32.768 KHz clock. This provides a method of providing a low speed accurate clock for the real time clock.

System and Peripheral Clocks

Once the clock source has been selected it is necessary to configure the internal system and peripheral clocks. The internal clocks are:

  • System Clock
  • Advanced High Performance Bus (AHB)
  • Low speed Advanced Peripheral Bus (APB1)
  • High speed Advanced Peripheral Bus (APB2)

Each of these clocks can be scaled using prescalers. A fuller picture of the types of clocks and their relationships can be found in the STM32 Reference Manual (RM0090).

System Clock

The system clock is used to determine the speed at which instructions are executed and has a maximum speed of 168MHz.

Advanced High Performance Bus (AHB)

Derived from the system clock, this bus has a maximum speed of 168MHz.

Low speed Advanced Peripheral Bus (APB1)

Derived from AHB, this bus has a maximum speed of 42MHz.

High speed Advanced Peripheral Bus (APB2)

Derived from AHB, this clock has a maximum frequency of 84MHz.

Non-System Clock Peripherals

A number of the peripheral clocks are not derived from the system clock but have their own independent source.

USB OTG FS, Random Number Generator and SDIO Clock

These clocks are all driven by an independent output of the PLL. The maximum speed for these peripherals is 48MHz.

I2S

The I2S peripherals have their own internal clock (PLLI2S). Alternatively, this can be derived by an independent external clock source.

USB OTG HS and Ethernet Clock

These clocks are derived from an external source.

Microcontroller Clock Output

The STM32 provides the ability to output up to two clocks to the external circuit using the Microcontroller Clock Output (MCO) pins. Both of the outputs can have a prescaler applied. This can be in the range 1 to 5 inclusive.

Microcontroller Clock Output 1 (MCO1) is output on pin PA8. MCO1 output can be one of HSI, LSE, HSE or PLL scaled appropriately.

Microcontroller Clock Output 2 (MCO2) is output on pin PC9. MCO2 output can be one of HSE, PLL, System Clock or PLLI2S scaled appropriately.

The clock output is configured by selecting the appropriate alternative function for the GPIO port and is restricted to 100MHz.

Configuring the Clock

The number of features on the STM32F4 analysis and discussion of the registers and their function large. Luckily ST have come to the rescue and provided a configuration tool to help.

ST Clock Configuration Tool in Wizard Mode

ST Clock Configuration Tool in Wizard Mode

This Excel spreadsheet can be run in both Expert and Wizard mode making it suitable for a wide range of users.

ST Clock Configuration Tool in Expert Mode

ST Clock Configuration Tool in Expert Mode

The grey number in the above illustration have been calculated whilst the numbers in black can be changed by the users.

The output from this spreadsheet is a source file (system_stm32f4xx.c) which can be added to your project. This file, system_stm32f4xx.c, contains three methods which can be called to setup the clocks on the microcontroller:

  • SystemInit – Setup the system including the clocks
  • SystemCoreClockUpdate – Setup the SystemCoreClock variable (clock frequency)
  • SetSysClock – Setup the system clock (called from SystemInit)

We can see the effect of calling SystemInit by using the code from the simple GPIO example to the following:

#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "system_stm32f4xx.h"

int main()
{
    //
    //  Uncomment this line to see the effect of
    //  the clock change.
    //
	//SystemInit();
    //
    //  Initialise the peripheral clock.
    //
    RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;
    //
    //  Initialise the GPIO port.
    //
    GPIOD->MODER |= GPIO_Mode_OUT;
    GPIOD->OSPEEDR |= GPIO_Speed_100MHz;
    GPIOD->OTYPER |= GPIO_OType_PP;
    GPIOD->PUPDR |= GPIO_PuPd_NOPULL;
    //
    //  Toggle Port D, pin 0 indefinitely.
    //
    while (1)
    {
        GPIOD->BSRRL = GPIO_Pin_0;
        GPIOD->BSRRH = GPIO_Pin_0;
    }
}

If we run the code above with the SystemInit line commented out and hook an oscilloscope up to pin 0 of port D we get a square wave output with a frequency of about 1.5MHz.

STM32F4 Output (1.46MHz)

STM32F4 Output (1.46MHz)

Now uncomment the SystemInit line of code and re-run the example. This time we see that the output signal has a frequency of nearly 4.9MHz.

STM32F4 Output (5.75MHz)

STM32F4 Output (5.75MHz)

Turning the configuration tool into expert mode and tweaking a few of the parameters allows the frequency of the output signal to go to 8.77MHz.

STM32F4 Output (8.77MHz)

STM32F4 Output (8.77MHz)

Timings are based upon the optimisation level being set to no optimisation. Turning the optimisation on to the maximum speed allows an output signal of nearly 20MHz to be generated.

Conclusion

The STM32 contains a variety of clock sources and configuration using the registers within the microcontroller would be a complex task. The Clock Configuration tool eases the configuration of the internal and peripheral clocks generating the source code for system_stm32f4xx.c automatically.

The Clock Configuration tool and the associated guide can be downloaded from ST’s web site. The tool and document are in the following files:

  • stsm-stm32091 Clock Configuration Tool
  • AN3988 Clock Configuration Tool User Guide