RSS

Posts Tagged ‘STM8’

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

Monday, May 6th, 2013

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

Bubble Wrapped Boards From China

Bubble Wrapped Boards From China

Could not wait to unwrap them:

OutputExpander Bare Boards

OutputExpander Bare Boards

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

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

    Component List

    The board requires the following components:

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

    When these arrive be prepared, they are small!

    Adding the Microcontroller

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

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

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

    STM8SPadsShiftRegisterPads
    STM8S75HC595

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

    STM8S Mounted On Board

    STM8S Mounted On Board

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

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

    Capacitor and Tools

    Capacitor and Tools

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

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

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

    Programming the STM8S

    Programming the STM8S

    No deployment errors!

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

    Add a Shift Register

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

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

    Netduino Go! Connected to OutputExpander

    Netduino Go! Connected to OutputExpander

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

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

    But All Was Not Well…

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

    Original Output From the OutputExpander

    Original Output From the OutputExpander

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

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

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

    A New Board

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

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

    //--------------------------------------------------------------------------------
    //
    //  GO! function 2 - Output the specified bytes to the shift registers.
    //  Tx buffer.
    //
    void SetShiftRegisters()
    {
        for (int index = 0; index < _numberOfShiftRegisters; index++)
        {
            _registers[index] = _rxBuffer[5 - index];
        }
        OutputData();
        NotifyGOBoard();
    }
    

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

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

    Testing

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

    Netduino Go OutputExpander and Some LEDs

    Netduino Go OutputExpander and Some LEDs

    And here’s a video of it working:

    Conclusion

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

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

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

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

Saturday, April 27th, 2013

This series of posts follows the steps required in order to make your own Netduino GO! module. To do this we are using a simple idea, an Output Expander (yes, it seems to have a name now) for the Netduino GO!. Using only a hand full of simple components, the Output Expander will add 32 digital outputs to the Netduino Go!.

The Story So Far…

The module has so far completed the following steps:

  • Concept and prototype on breadboard
  • Basic drivers
  • PCB design and layout
  • Generation of PCB manufacturing files

The first batch of 10 boards were ordered on 20th April 2013 using iTeads prototyping service. I am expecting the manufacturing process to take about one week with a further two weeks for shipping as I used the cheap, slow courier service.

In the meantime we still have the breadboard prototype to work with. This may only have two shift registers attached to it but by carefully parametrising the code we should be able to develop a driver which only needs to be recompiled when the final hardware becomes available.

Features

The driver for this module should be relatively simple as we are merely setting outputs from the shift registers to be either on or off. We shall start with the following list of desired features:

  • Default initialisation to set up for one board
  • Set the bytes for the digital outputs
  • Array like access for the bits
  • Configurable latching mode (automatic or manual)
  • Clear the output
  • Turn outputs from the registers on or off

The only remaining task is to decide where the features are to be implemented. We have two options:

  • On the Netduino GO! in C#
  • On the STM8S in C

Some of the code is better left on the Netduino GO! with the base features (setting all of the register values etc) being implemented on the STM8S.

Initialisation

Initialisation should allow for the use of cascaded boards. If you look back at the schematic you will notice a connector called CascadeConn. This connector allows the addition of a simpler board as an expansion module. This board only need to supply additional shift registers leaving the first, main board supplying the logic to communicate with the Netduino GO!. The concept is that if you want 64 outputs then you would have a single Output Expander module with a single, cheaper daughter board.

In order to support the addition of the daughter board the initialisation will need to support the specification of the number of shift registers in the cascade.

In supporting the cascading of these boards we will also need to provide some sensible default values. The basic case is a single board which contains four shift registers.

We should also consider a maximum value for the number of shift registers. In this case I am going to set this to 12 for two reasons:

  • Power considerations – all of the power for the shift registers is being provided by the Netduino GO!
  • Data packet size – the data packets used in the GoBus are fixed size. Keeping the number of shift registers to a value where only one data packet is required simplifies the communication between the Netduino GO! and the module as a single packet can be used for all messages.

In order to facilitate this we will need code on both the Netduino GO! and the module.

Module Code

The code on the module should allow for the number of shift registers to be set up (assuming a default value of four registers) and then clear the registers. The default code should be called when the module is re-initialised.

//--------------------------------------------------------------------------------
//
//  GO! function 3 - Set up the module.
//
void SetUp()
{
    U8 n = _rxBuffer[2];
    if ((n < 0) && (n < MAX_REGISTERS))
    {
        _numberOfShiftRegisters = n;
        free(_registers);
        _registers = (U8 *) malloc(n);
        ClearRegisters();
        OutputData();
        NotifyGOBoard();
    }
}

Netduino GO! Code

The initialisation code on the Netduino GO! will assume that the startup code on the module will initialise itself to four shift registers. This will reduce the communications overhead between the Netduino GO! and the module. The following when added to the Initialise method should set up the module driver on the Netduino GO! for a variable number of shift registers in the sequence:

//
//  Next, set up the space to store the data.
//
_shiftRegisterData = new byte[numberOfShiftRegisters];
for (int index = 0; index < numberOfShiftRegisters; index++)
{
    _shiftRegisterData[index] = 0;
}
LatchMode = LatchingMode.Automatic;
if (numberOfShiftRegisters != 4)
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_SETUP;
    _writeFrameBuffer[2] = (byte) (numberOfShiftRegisters & 0xff);
    WriteDataToModule("Initialise: cannot setup the OutputExpander module");
}

Set Outputs

Setting the outputs is simply a case of sending a number of bytes, one for each shift register to the module.

/// <summary>
/// Set the shift registers using the values in the byte array.
/// "/summary>
/// "param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
    if (registers.Length != _shiftRegisterData.Length)
    {
        throw new ArgumentException("registers: length mismatch");
    }
    for (int index = 0; index < registers.Length; index++)
    {
        _shiftRegisterData[index] = registers[index];
    }
    Latch();
}

The module code needs a slight adjustment to transfer the correct number of incoming bytes to the register store:

//--------------------------------------------------------------------------------
//
//  GO! function 2 - Output the specified bytes to the shift registers.
//  Tx buffer.
//
void SetShiftRegisters()
{
    for (int index = 0; index < _numberOfShiftRegisters; index++)
    {
        _registers[index] = _rxBuffer[2 + index];
    }
    OutputData();
    NotifyGOBoard();
}

Array of Bits

From a software point of view, a shift register is nothing more than an array of boolean values. Internally it makes sense for the driver to allow this abstraction and use the indexing operator to set a single bit at a time. The code for this operator looks something like this:

/// <summary>
/// Overload the index operator to allow the user to get/set a particular 
/// bit in the shift register.
/// </summary>
/// <param name="bit">Bit number to get/set.</param>
/// <returns>Value in the specified bit.</returns>
public bool this[int bit]
{
    get
    {
        if ((bit >= 0) && (bit < (_shiftRegisterData.Length * 8)))
        {
            int register = bit >> 3;
            byte mask = (byte) (bit & 0x07);
            return ((_shiftRegisterData[register] & mask) == 1);
        }
        throw new IndexOutOfRangeException("OutputExpander: Bit index out of range.");
    }
    set
    {
        if ((bit >= 0) && (bit < (_shiftRegisterData.Length * 8)))
        {
            int register = bit >> 3;
            byte mask = (byte) ((1 << (bit & 0x07)) & 0xff);
            if (value)
            {
                _shiftRegisterData[register] |= mask;
            }
            else
            {
                mask = (byte) ~mask;
                _shiftRegisterData[register] &= mask;
            }
            if (LatchMode == LatchingMode.Automatic)
            {
                Latch();
            }
        }
        else
        {
            throw new IndexOutOfRangeException("OutputExpander: Bit index out of range.");
        }
    }
}

Adding the above code allows the programmer to use constructs such as:

OutputExpander outputs = new OutputExpander();
outputs[2] = true;

instead of the more obscure:

OutputExpander outputs = new OutputExpander();
byte[] data = new byte[4];
data[0] = 0x04;
SetOutputs(data);

Not only is the former example more elegant but it is also more concise.

Clear All Registers

This method will simply clear the shift registers and set the outputs to 0;

/// <summary>
/// This method calls the ClearRegister method on the GO! module and then waits for the
/// module to indicate that it has received and executed the command.
/// </summary>
public void Clear()
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_CLEAR_REGISTERS;
    for (int index = 0; index < _shiftRegisterData.Length; index++)
    {
        _shiftRegisterData[index] = 0;
    }
    WriteDataToModule("Clear cannot communicate with the Output Expander module");
}

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

Latch Mode

The introduction of the array indexing operators does introduce on complication, namely that we cannot set all of the outputs to a specified value at the same time without delaying the latching of the registers. Consider the following case:

OutputExpander outputs = new OutputExpander();
outputs[2] = true;
outputs[3] = true;

In this case we would set bit 2 of the lower shift register followed by bit 3 of the same shift register. Because of the speed of .NETMF there would be a slight delay between the two outputs of the shift register being set high. In order to allow for this we introduce the ability to delay the latching of the data from the internal shift register into the output register.

/// <summary>
/// Determine when the data should be sent to the module.
/// </summary>
public enum LatchingMode
{
    /// <summary>
    /// Automtically send the data to the module as soon as there are any changes.
    /// </summary>
    Automatic,
    /// <summary>
    /// Manually latch the data.
    /// </summary>
    Manual
}

/// <summary>
/// Backing variable for the LatchMode property.
/// </summary>
private LatchingMode _latchMode;

/// <summary>
/// Determine how the data will be send to the module.  The default is to 
/// automatically send data as soon as there are any changes.
/// </summary>
public LatchingMode LatchMode
{
    get { return (_latchMode); }
    set { _latchMode = value; }
}

The initialisation of the class would also need to be modified in order to set the mode to automatic:

LatchMode = LatchingMode.Automatic;

The most lightweight method of using the LatchMode is to simply not send the data to the shift registers until the mode is either reset or until the controlling program explicitly latches the data. The Set method will therefore need some adjustment to take into account the two modes:

/// <summary>
/// Set the shift registers using the values in the byte array.
/// </summary>
/// <param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
    if (registers.Length != _shiftRegisterData.Length)
    {
        throw new ArgumentException("registers: length mismatch");
    }
    for (int index = 0; index < registers.Length; index++)
    {
        _shiftRegisterData[index] = registers[index];
    }
    if (LatchMode == LatchingMode.Automatic)
    {
        Latch();
    }
}

Latch Operation

The introduction of the LatchMode means that we also need to allow for the data to be latched into the shift registers.

/// <summary>
/// Call the Set command on the module to set the outputs of the shift registers.
/// </summary>
private void Set()
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_SET_REGISTERS;
    for (int index = 0; index < _shiftRegisterData.Length; index++)
    {
        _writeFrameBuffer[2 + index] = _shiftRegisterData[index];
    }
    WriteDataToModule("Latch cannot communicate with the Output Expander module");
}

The above method simply sends the data to the module.

Testing

We can perform some simple testing of the software while the prototype boards are being made by using the breadboard test environment build in the first post. This board only has two shift registers but it should be possible to test the majority of functionality using this board.

In the previous posts we have cycled through the bits one at a time either from 0 to 15 or down from 15 to 0. In this example we will perform some counting and use the LEDs to display the number in binary. Our test code becomes:

output = new OutputExpander(2);
short cycleCount = 0;
byte[] registers = new byte[2];
output.LatchMode = OutputExpander.LatchingMode.Manual;
while (true)
{
    Debug.Print("Cycle: " + ++cycleCount);
    short mask = 1;
    output.Clear();
    for (int index = 0; index <= 15; index++)
    {
        if ((cycleCount & mask) != 0)
        {
            output[index] = true;
        }
        mask <<= 1;
    }
    output.Latch();
    Thread.Sleep(200);
}

Deploying this application should result in the Netduino GO! counting up from 0 and the binary representation of cycleCount being output on the shift registers. The following video shows this in action:

Conclusion

The above minor modifications to the STM8S module code and the Netduino GO! driver has added the following functionality:

  • Default initialisation to set up for one board
  • Set the bytes for the digital outputs
  • Array like access for the bits
  • Configurable latching mode (automatic or manual)
  • Clear the output
  • Turn outputs from the registers on or off

The code has been carefully written so that we should only need to change two parameters when the final PCBs arrive in order to change the drivers from two shift registers to four shift registers.

A quick test has shown that the main functionality appears to be working on the breadboard prototype as demonstrated by the above video. The prototype PCBs have completed manufacture and are currently with the Hong Kong postal service (as of 27th April 2013). Delivery should take another 7-10 days so there is plenty of time to complete the test suite.

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

Friday, April 12th, 2013

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

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

Design Criteria

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

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

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

In summary:

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

PCB Design Software

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

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

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

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

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

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

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

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

Schematic

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

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

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

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

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


    STM8S Showing Pin Labels

    STM8S Showing Pin Labels

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

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

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

    Shift Register Showing Net Names

    Shift Register Showing Net Names

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

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

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

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


    Net Selection

    Net Selection

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

    The final schematic for the module looks like this:

    Schematic

    Schematic

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

    Conclusion

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

    Output Expander - 3D View

    Output Expander – 3D View

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

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

Friday, April 12th, 2013

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

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

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

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

STM8S Application

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

Start a New Project

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

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

Remove Dummy Functionality

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

Add New Functionality

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

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

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

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

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

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

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

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

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

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

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

//--------------------------------------------------------------------------------
//
//  Clear the shift registers.
//
void ClearRegisters()
{
    for (U8 index = 0; index < NUMBER_OF_REGISTERS; index++)
    {
        _registers[index] = 0;
    }
}

//--------------------------------------------------------------------------------
//
//  GO! Function 1 - Clear the shift registers.
//
void ClearShiftRegisters()
{
    ClearRegisters();
    OutputData();
    NotifyGOBoard();
}

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

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

Netduino Go! Driver

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

Command Constants

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

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

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

Modify the Supporting Methods

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

/// <summary>
/// Write the data in the _writeFrameBuffer to the module.  Make several
/// attempts to write the data before throwing an exception.
/// </summary>
/// <param name="exceptionMessage">Exception message to the used in the constructor if the write attempt fails.</param>
private void WriteDataToModule(string exceptionMessage)
{
    int retriesLeft = 10;
    bool responseReceived = false;

    WriteDataToSPIBus();
    while (!responseReceived && (retriesLeft > 0))
    {
        //
        //  We have written the data to the module so wait for a maximum 
        //  of 5 milliseconds to see if the module responds with some 
        //  data for us.
        //
        responseReceived = _irqPortInterruptEvent.WaitOne(5, false);
        if ((responseReceived) && (_readFrameBuffer[1] == GO_BUS10_COMMAND_RESPONSE))
        {
            //
            //  Assume good result, it is up to the calling method to determine if
            //  the command has been executed correctly.
            //
            return;
        }
        else
        {
            //
            //  No response within the 5ms so lets make another attempt.
            //
            retriesLeft--;
            if (retriesLeft > 0)
            {
                WriteDataToSPIBus();
            }
        }
    }
    throw new Exception(exceptionMessage);
}

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

Add New Functionality

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

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

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

This functionality is provided by the following code:

/// <summary>
/// This method calls the ClearRegister method on the GO! module and then waits for the
/// module to indicate that it has received and executed the command.
/// </summary>
public void Clear()
{
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_CLEAR_REGISTERS;
    WriteDataToModule("Clear cannot communicate with the Output Expander module");
}

/// <summary>
/// Set the shift registers using the values in the byte array.
/// </summary>
/// <param name="registers">Bytes containing the shift register values.</param>
public void Set(byte[] registers)
{
    if (registers.Length != 2)
    {
        throw new ArgumentException("registers: length should be 2 bytes");
    }
    _writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
    _writeFrameBuffer[1] = CMD_SET_REGISTERS;
    for (int index = 0; index < registers.Length; index++)
    {
        _writeFrameBuffer[2 + index] = registers[index];
    }
    WriteDataToModule("Clear cannot communicate with the Output Expander module");
}

Testing

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

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

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

Output Expander Connected to Netdunio GO!

Output Expander Connected to Netdunio GO!

And here is a video of this working:

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

Conclusion

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

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

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

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

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.

STM8S SPI Slave (Part 3) – Making a Go Module

Monday, November 26th, 2012

In this, the last of the series of posts regarding implementing SPI Slave devices using the STM8S, we will look at building a module for the Netduino Go. This post builds upon the two previous posts:

Here we will build upon the buffering and overlay the the Netduino Go 1.0 protocol in order to allow the two devices to communicate. We will also extend the STM8S application to add a simple function table to allow the simple addition of extra functionality to the system.

The makers of the Netduino Go, Secret Labs, have not formally released the GoBus 1.0 specification as a document. They have however release the source code to some of their modules and this can be found in the Wiki. The code found in the Wiki posts along with discussions on various forums has been used in the production of the code presented here. Credit for help is due to Secret Labs for releasing the code and also to the following Netduino forum members:

  • Chris Walker
  • CW2

These forum members have given assistance in one form or another over the past few months and without their help this post would not have been possible.

GoBus 1.0 Protocol

The early GoBus protocol uses an 18 byte data packet which is exchanged by the Netduino Go and the module. This packet of data contains a one byte header, 16 bytes of data and a one byte checksum with the data packets being exchanged over SPI. With the exception of the header and the checksum it appears that meaning of the data within the 16 byte payload is determined by the module developer.

I would also point the reader to the blog post A Developers Introduction to GoBus by Matt Isenhower on the Komodex System web site.

Enumeration

When the Netduino Go is first powered it will look at each of the Go Sockets in turn and attempt to interrogate the modules which are connected to the main board. It does this by sending out a packet with a single byte header 0xfe followed by 16 data bytes and a checksum. From experience, the data bytes are normally set to 0xff.

The module attached to the socket is then required to respond with a header byte of 0x2a followed by the 16 byte GUID of the module and the checksum byte.

The end result of this exchange is that the Netduino Go will have built up a list of modules attached to the main board along with the corresponding socket numbers.

This process then allows the .NET code to connect to a module using the GUID or the GUID and socket number. Successful connection is indicated by the blue LED by the side of the socket being illuminated. A failed connection normally results in an exception being thrown.

Data Transfer/Module Control

When the code running on the Netduino Go has successfully attached to the module on a socket it can start to control/communicate with the module. At this point it appears that the protocol uses the header 0x80 to indicate transfer between the module and the main board. So our data packets remain 18 bytes with the following format:

  • 0x80 – Header
  • 16 byte payload
  • 1 byte CRC

It appears that the meaning of the 16 byte payload is determined by the module developer.

GPIO Interrupt

The protocol also allows for the use of a single GPIO. This can be used as a signalling line to let either side know that an action is pending. Convention appears to be to use this to allow the module to let the code on the main board know that there is some data/action pending.

A Simple Module

We will be creating a simple module to illustrate how the STM8S and the Netduino code work together. In order to use the least hardware possible the module will perform a simple calculation and return the result. Our module will need to perform the following:

  • Enumerate using a GUID allowing the Netduino Go to detect the module
  • Receive a number from the Netduino Go
  • Perform a simple calculation and notify the Netduino Go that the result is ready.
  • Send the result back to the Netduino Go when requested.

This simple module illustrates the key types of communication which may be required of a simple module. It is of course possible to use these to perform other tasks such as controlling a LED or receiving input from a button or keypad.

Netduino Go Module Driver

The Netduino Go code derived from the C# code published by Secret Labs in their Wiki. The major changes which have been made for this post are really concerned with improving clarity (adding comments at each stage to expand on the key points etc.).

Module ID

Modules are identified using a GUID. This ID allows the GoBus to connect to a module by scanning the bus for the specified ID. It also allows the Netduino Go to verify that when connecting to a module on a specific socket that the module is of the correct type. So the first thing we will need to do is obtain a new GUID. There are various ways in which we can do this and the simplest way to do this is to use the Create GUID menu option in Visual Studio. You can find this in the Tools menu.

Once you have your GUID you need to break this down into an array of bytes. You can then enter this in the both the Netduino code and the STM8S code. You will find the appropriate line in the file BasicModule.cs. The code looks something like this:

private Guid _moduleGuid = new Guid(new byte[] { 0x80, 0x39, 0xe8, 0x2b, 0x55, 0x58, 0xeb, 0x48, 0xab, 0x9e, 0x48, 0xd3, 0xfd, 0xae, 0x8c, 0xee });

REMEMBER: It is critical that you generate your own GUID as each module type will need to have distinct ID.

Scanning down the file a little way you will find the two constructors for the class. One takes a socket and attempts to bind to the specified module on the requested socket. The other will attach to the first available module on the GoBus.

Initialise

This method is key to allowing the Netduino Go to connect to the module. The method binds to the module (assuming the IDs match) and retrieves a list of resources which the driver can use to communicate with the module. In this case, the SPI information and the pin used as an interrupt port. The remainder of the method configures the module driver to use these resources.

One key point to note is the use of the AutoResetEvent object. This is used to allow the interrupt handler to communicate the fact that an event has occurred to the methods we will write. This can be done in a manner which is non-blocking.

AddFive Method

This is the first of our methods implementing the functionality which our module will provide. In our case, this method actually implements the simple arithmetic we will be asking the module to perform. We will be sending a byte to the module, the module will add five to the number passed and then make this available to the Netduino Go. The code looks like this:

public byte AddFive(byte value)
{
	int retriesLeft = 10;
	bool responseReceived = false;

	_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
	_writeFrameBuffer[1] = CMD_ADD_FIVE;
	_writeFrameBuffer[2] = value;
	WriteDataToModule();
	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))
		{
			//
			//  Module has responded so extract the result.  Note we should really
			//  verify the checksum at this point.
			//
			_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
			_writeFrameBuffer[1] = CMD_GET_RESULT;
			WriteDataToModule();
			return(_readFrameBuffer[2]);
		}
		else
		{
			//
			//  No response within the 5ms so lets make another attempt.
			//
			retriesLeft--;
			if (retriesLeft > 0)
			{
				WriteDataToModule();
			}
		}
	}
	throw new Exception("AddFive cannot communicate with the Basic GO! module");
}

The first thing this the method does is to set up the _writeFrameBuffer with the header, the command number and the data we will be sending. The data is then written to the module using SPI.

Next we will wait a while for the module to indicate via the GPIO pin that it has processed the data and the result is ready. As we shall see later, the module has already put the result in the transmission buffer ready for retrieval. This will have been performed before the interrupt was generated. The following line performs the non-blocking check to see if the interrupt has been generated:

responseReceived = _irqPortInterruptEvent.WaitOne(5, false);


responseReceived will be true if the interrupt has been generated and the C# module code has received the event.

The final task is to retrieve the result from the module by sending a retrieve command. This is performed by the following code:

_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
_writeFrameBuffer[1] = CMD_GET_RESULT;
WriteDataToModule();
return(_readFrameBuffer[2]);

STM8S Module

Much of the code required here has already been covered in the previous post, STM8S SPI Slave (Part 2). The protocol uses a small buffer to allow messages to be transferred between the STM8S and the Netduino Go. In order to make this work as a Netduino Go module we need to overlay the GoBus protocol onto the message buffers and provide a mechanism for interpreting these messages. The mechanism we adopted is as follows:

  • All messages will be restricted to 18 bytes (one byte header, 16 bytes payload, one byte CRC)
  • The request header (from the Netduino to the module) will be 0x80 allowing a 16 byte payload
  • The response header (from the module to the Netduino) will be 0x2a followed by 0x80. This restricts the return payload to 15 bytes.
  • The final byte will be a CRC calculated on the header and the payload
    • The way in which the protocol has been implemented here also places a restriction upon on the application. Firstly, the module must receive a request as a full payload. Only then can the module respond. This is where the GPIO interrupt discussed earlier comes into play.

      The final part of the problem is to work out how to dispatch the messages received by the module. To do this we will use a function table.

      For the remainder of this article we will restrict ourselves to looking at the new functionality we will be adding on top of the previous post in order to allow the creation of a module.

      Function Table

      A function table in C is a simple list of function pointers. We will add to this by allowing a variable function identifier to be used to associate a byte ID with a particular method within the module. The following code allows the table to be setup:

      //
      //  Function table structure.
      //
      typedef struct
      {
          unsigned char command;          //  Command number.
          void (*functionPointer)();      //  Pointer to the function to be executed.
      } FunctionTableEntry;
      //
      //  Forward function declarations for the function table.
      //
      void AddFive();
      void GetValue();
      //
      //  Table of pointers to functions which implement the specified commands.
      //
      FunctionTableEntry _functionTable[] = { { 0x01, AddFive }, { 0x02, GetValue } };
      //
      //  Number of functions in the function table.
      //
      const int _numberOfFunctions = sizeof(_functionTable) / sizeof(FunctionTableEntry);
      

      Here we define a function table entry which has a byte ID and a pointer to a function (taking a void parameter list) associated with the ID. We then declare an array of these objects and associate functions with the IDs.

      The final line of code simply determines the number of entries in the function table.

      Using the above table we can work out which function to call using the following code:

      if (_numberOfFunctions > 0)
      {
      	for (int index = 0; index < _numberOfFunctions; index++)
      	{
      		if (_functionTable[index].command == _rxBuffer[1])
      		{
      			(*(_functionTable[index].functionPointer))();
      			break;
      		}
      	}
      }
      

      The function table method presented here allows the functionality of the module to be expanded with relative ease. In order to add a new piece of functionality you simply need to do the following:

      • Create a new method in the STM8S code to implement the new functionality
      • Determine the ID to be used for the functionality and add a new entry to the function table
      • Create a method in the Netduino Go driver to call the method and retrieve any results as necessary

      By performing these three simple steps you can add one or more functions with ease. The communication protocol will continue to work as is with no modification. The only exception to this rule will be cases where more than one payload of data needs to be transferred in order to achieve a specified piece of functionality (say a network driver etc.).

      Buffers and GUIDs

      We will need to make a slight modification to the Rx buffer in order to account for the checksum byte. We will also need to add somewhere to store the GUID which acts as the identifier for this module. This results in the following small change to the global variable code:

      //
      //  Application global variables.
      //
      unsigned char _rxBuffer[GO_BUFFER_SIZE + 1];    // Buffer holding the received data plus a CRC.
      unsigned char _txBuffer[GO_BUFFER_SIZE];        // Buffer holding the data to send.
      unsigned char *_rx;                             // Place to put the next byte received.
      unsigned char *_tx;                             // Next byte to send.
      int _rxCount;                                   // Number of characters received.
      int _txCount;                                   // Number of characters sent.
      volatile int _status;                           // Application status code.
      //
      //  GUID which identifies this module.
      //
      unsigned char _moduleID[] = { 0x80, 0x39, 0xe8, 0x2b, 0x55, 0x58, 0xeb, 0x48,
                                    0xab, 0x9e, 0x48, 0xd3, 0xfd, 0xae, 0x8c, 0xee };
      

      GoBus Interrupt

      The discussion of the code on the Netduino Go driver (on the main board) mentioned the fact that the module can raise an interrupt to signal the fact that an operation has completed and that data is ready for retrieval. In order to do this we raise an interrupt on one of the pins when we have processed the data. This code is trivial:

      //
      //  Raise an interrupt to the GO! main board to indicate that there is some data
      //  ready for collection.  The IRQ on the GO! board is configured as follows:
      //
      //  _irqPort = new InterruptPort((Cpu.Pin) socketGpioPin, false, Port.ResistorMode.PullUp,
      //                               Port.InterruptMode.InterruptEdgeLow);
      //
      void NotifyGOBoard()
      {
          PIN_GOBUS_INTERRUPT = 0;
          __no_operation();
          PIN_GOBUS_INTERRUPT = 1;
      }
      

      This method is simple and really just toggles which is connected to GPIO pin on the Netduino Go socket.

      Adding Functionality to the Module

      In our simple case we need to add two pieces of functionality, the ability to add five to a number and then to allow the caller to retrieve the result. This results in the following two methods:

      //
      //  GO! function 1 - add 5 to byte 2 in the Rx buffer and put the answer into the
      //  Tx buffer.
      //
      void AddFive()
      {
          _txBuffer[1] = _rxBuffer[2] + 5;
          NotifyGOBoard();
      }
      
      //--------------------------------------------------------------------------------
      //
      //  GO! Function 2 - return the Tx buffer back to the GO! board.
      //
      void GetValue()
      {
          NotifyGOBoard();
      }
      

      SPI Go Frame

      The implementation of the SPI processing here is interrupt driven. As such we will need to allow a method of synchronising the payloads we receive. This application will do this using the rising edge of the chip select signal which is generated by the Netduino Go main board. This allows us for cater for the many scenarios (synchronisation, underflow and overflow).

      In the case of underflow and synchronisation, the chip select signal will rise before we have enough data. In this case we have either a corrupt packet or we have started to recei8ve data part way through the packet. In this case we cannot sensibly process the data so we should throw away the packet and wait for the next one.

      An overflow situation can occur when the Netduino Go sends more than 18 bytes in one packet of data. In this case we should detect this and prevent the buffers from overflowing.

      In order to allow for these cases we reset the Go frame pointers when the chip select signal changes from low to high:

      //
      //  This method resets SPI ready for the next transmission/reception of data
      //  on the GO! bus.
      //
      //  Do not call this method whilst SPI is enabled as it will have no effect.
      //
      void ResetGoFrame()
      {
          if (!SPI_CR1_SPE)
          {
              (void) SPI_DR;                          //  Reset any error conditions.
              (void) SPI_SR;
              SPI_DR = GO_FRAME_PREFIX;               //  First byte of the response.
              _txBuffer[0] = _moduleID[0];            //  Second byte in the response.
              //
              //  Now reset the buffer pointers and counters ready for data transfer.
              //
              _rx = _rxBuffer;
              _tx = _txBuffer;
              _rxCount = 0;
              _txCount = 0;
              //
              //  Note the documentation states this should be SPI_CR2_CRCEN
              //  but the header files have SPI_CR_CECEN defined.
              //
              SPI_CR2_CECEN = 0;                      //  Reset the CRC calculation.
              SPI_CR2_CRCNEXT = 0;
              SPI_CR2_CECEN = 1;
              SPI_SR_CRCERR = 0;
          }
      }
      

      As we shall see later, the end of the SPI transmission with result in one of the following cases:

      • Too little data received correctly. The rising chip select line will reset the buffer pointers and the data will be discarded.
      • The correct amount of data received. In this case the buffer will be processed correctly.
      • Too much data is received. The excess data will be discarded to prevent a buffer overflow.

      The ResetGoFrame method is key in ensuring that the buffers are reset at the end of the SPI transmission indicated by the rising chip select line.

      SPI Tx/Rx Interrupt Handler

      This method is responsible for ensuring that the data is transmitted and received correctly. It works in much the same way as the previous buffered SPI example. The main difference between this module and the previous example is what happens when the first byte of the data received is equal to 0xfe. In this case the Tx buffer pointer is moved to point to the module ID. This ensures that the Netduino Go receives the correct response to the enumeration request.

      Connecting the Boards

      The application code contains a number of #if statements to take into account the differing pin layouts of the microcontrollers used. The following have been tested so far:

      • STM8S103F3 TSSOP20 on a breadboard
      • STM8S Discovery

      The Protomodule has also been wired up for one particular module but at the time of writing the definitions have not been added to the sources.

      In order to connect the Netduino Go main board to a module in development you will probably need to purchase some form of breakout such as the Komodex breakout board (Straight connectors or 90-Degree connectors).

      Connecting the two boards should be a simple case of ensuring that the SPI pins are connected MOSI to MOSI, MISO to MISO, CS to CS and Clock to Clock. In the case of the Discovery board I used PB0 for the CS line and for the STM8S103 setup I used the standard pin PA3.

      Running the Code

      Running the code should be a simple case of loading the STM8S code into the IAR development environment first and the deploying the code to the chip. Hot F5 to run the code.

      Next, load the visual studio code and deploy this to the Netduino Go main board. Hit F5 to run the code.

      The C# code running in Visual Studio should start to print some diagnostic information to the debug window. You should see a series of statements of the form Adding 5 to 6 to give 11. The 6 is the parameter which has been sent to the module for processing and the 11 is the result.

      Observations

      I have seen differing behaviours to the way in which the debugger in IAR works with the code in the module. Occasionally the debugger will actually prevent the module from enumerating. This will result in an exception in Visual Studio. To date I have only seen this behaviour with the STM8S103 setup. The STM8S Discovery board seems to work correctly. If you have problems with this then the only suggestion is to detach IAR from the board and rely upon diagnostic information being dumped to a logic analyser. You will note that the test application which runs on the Netduino Go has the instantiation of the module wrapped in a while loop and a try block. This allows the test code to make several attempts at creating a new module instance. This should not be necessary in the final production code as this has not yet failed in a none debug environment.

      This code has been tested with the simple module example here and also with a temperature and humidity sensor. The application enumerated OK and has been soak tested in two environments over a period of hours. The code seems to be stable and works well with the Netduino Go.

      I originally tried to be ambitious with the interrupt service routine dealing with the chip select line. This gave me code which was simpler but lead to a timing issue. As it stands at the moment, dropping the chip select line from high to low starts the SPI processing. The time between this happening and the first clock transition is only 3.25us as measured on my logic analyser. This means that all of the preparation must be completed in 3.25us.

      If we look at the diagram below you can see the timings at the start of the SPI communication:

      SPI Timing Diagram

      SPI Timing Diagram

      The two markers 1 & 2 indicate the time we have between the start of the comms indicated by CS falling to the first clock pulse. The Status Code trace is a debugging signal generated by the application. The rising edge indicates when the first line of the interrupt service routine for the CS line starts and the falling edge indicates the point where we have completed enough processing to allow SPI to be enabled.

      Conclusion

      This post shows how we to create a Netduino Go module using a standard communication protocol. Additional module functionality can simply be added by adding to the function table.

      As noted at the start, this article is the combination of information provided by Netduino community members along with the module code which can be found in the Wiki.

      As usual, the source code for this application is available for download (STM8S Go Module and Netduino Go – Basic Module Driver).

      Source Code Compatibility

      SystemCompatible?
      STM8S103F3 (Breadboard)
      Variable Lab Protomodule
      STM8S Discovery

STM8S SPI Slave (Part 2)

Monday, November 19th, 2012

In the previous post we looked at exchanging single bytes using SPI with a Netduino Plus acting as the SPI master device and the STM8S acting as a slave device. The code presented suffered from a few deficiencies:

  • We could only exchange one byte and that was mirrored back to the master device
  • The mirroring assumed that a byte received meant the STM8S was ready to send a byte back to the Netduino

In this post we will deal with both of these issues and also look at a new problem which can arise, namely synchronisation.

The aim of the code we will be developing is to receive a buffer of data and at the same time send a different buffer of data back to the master device.

Hardware

The hardware we will be using is identical to the initial SPI post. We will be using a few more bits from the registers in order to allow the STM8S application to determine the action we should be taking.

SPR_SR_OVR – Overflow Indicator

This bit will be set when the chip detects an overflow condition. This can happen if the bus speed is too high and the data is arriving at a rate which is faster than the Interrupt Service Routine (ISR) can process it.

SPI_SR_RXNE – Receive Buffer Not Empty

This bit indicates that the receive buffer is not empty and that data is ready to be read.

SPI_SR_TXE – Transmit Buffer Empty

This indicates that the SPI transmit buffer is empty and ready to receive another byte of data.

Netduino Plus Software

The software running on the Netduino Plus requires a small modification to allow it to send a buffer of data rather than a single byte. We will also take the opportunity to increase the SPI bus speed to 500KHz. The code running on the Netduino Plus becomes:

public class Program
{
	/// <summary>
	/// SPI object.
	/// </summary>
	private static SPI spi = null;

	/// <summary>
	/// Configuration of the SPI port.
	/// </summary>
	private static SPI.Configuration config = null;

	public static void Main()
	{
		config = new SPI.Configuration(SPI_mod: SPI.SPI_module.SPI1,        // Which SPI module to use?
									   ChipSelect_Port: Pins.GPIO_PIN_D10,  // Chip select pin.
									   ChipSelect_ActiveState: false,       // Chip select is low when SPI is active.
									   ChipSelect_SetupTime: 0,
									   ChipSelect_HoldTime: 0,
									   Clock_IdleState: false,              // Clock is active low.
									   Clock_Edge: true,                    // Sample on the rising edge.
									   Clock_RateKHz: 500);
		spi = new SPI(config);

		byte[] buffer = new byte[17];
		for (byte index = 0; index < 17; index++)
		{
			buffer[index] = index;
		}
		while (true)
		{
			for (byte counter = 0; counter < 255; counter++)
			{
				buffer[0] = counter;
				spi.Write(buffer);
				Thread.Sleep(200);
			}
		}
	}
}

As you can see, much of the code is the same as that presented in the previous post. This application will now transmit a 17 byte buffer to the SPI slave device. The first byte in the buffer will be a sequence number which will cycle through the values 0 to 254. The remaining bytes in the buffer will remain unchanged.

STM8S SPI Slave

The main changes we will be making are in the application running on the STM8S. In this case we need to deal with the following additional issues:

  • Possible overflows due to the increased speed of the SPI bus
  • Treating the receive and transmit scenarios as distinct cases
  • Buffer overflows

The first thing we are going to need is somewhere to store the data. Looking at the Netduino Code we have defined the buffer size as 17 bytes. The corresponding declaration in the STM8S code look like this:

//--------------------------------------------------------------------------------
//
//  Miscellaneous constants
//
#define BUFFER_SIZE             17

//--------------------------------------------------------------------------------
//
//  Application global variables.
//
unsigned char _rxBuffer[BUFFER_SIZE];       // Buffer holding the received data.
unsigned char _txBuffer[BUFFER_SIZE];       // Buffer holding the data to send.
unsigned char *_rx;                         // Place to put the next byte received.
unsigned char *_tx;                         // Next byte to send.
int _rxCount;                               // Number of characters received.
int _txCount;                               // Number of characters sent.

We will also need to provide a mechanism to reset the SPI buffer pointers back to a default state ready to receive data:

//--------------------------------------------------------------------------------
//
//  Reset the SPI buffers and pointers to their default values.
//
void ResetSPIBuffers()
{
    SPI_DR = 0xff;
    _rxCount = 0;
    _txCount = 0;
    _rx = _rxBuffer;
    _tx = _txBuffer;
}

We also no longer have a single byte of data to output on the diagnostic pins. We therefore need to add a new diagnostic method to output the data we are receiving.

//--------------------------------------------------------------------------------
//
//  Bit bang a buffer of data on the diagnostic pins.
//
void BitBangBuffer(unsigned char *buffer, int size)
{
    for (int index = 0; index < size; index++)
    {
        BitBang(buffer[index]);
    }
}

The main method needs to be modified to take into account the changes we have made. The code becomes:

int main(void)
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    InitialiseSPIAsSlave();
    ResetSPIBuffers();
    for (unsigned char index = 0; index < BUFFER_SIZE; index++)
    {
        _txBuffer[index] = index + 100;
    }
    InitialiseOutputPorts();
    _status = SC_UNKNOWN;
    __enable_interrupt();
    //
    //  Main program loop.
    //
    while (1)
    {
        __wait_for_interrupt();
        if (_status == SC_RX_BUFFER_FULL)
        {
            BitBangBuffer(_rxBuffer, BUFFER_SIZE);
        }
        _status = SC_UNKNOWN;
    }
}

So far all of the code changes have been to support the initialisation and configuration of the system. The one area we have not touched upon is processing of the data which is being transmitted / received, namely the SPI ISR.

SPI Interrupt Service Routine

For the application we have built so far, the ISR must take into account three possible scenarios:

  • Buffer Overflow
  • Data received
  • Data transmission buffer empty

The code will utilise the three status we identified earlier in order to determine which action to take. In each case we will do the following:

  • SPI Overflow (SPI_SR_OVR is set)
    Use the status codes to indicate an overflow has occurred and exit the ISR
  • Data Received (SPI_SR_RXNE is set)
    Add the byte received to the buffer and update the buffer pointers. Set the status code to indicate that we have received some data.
  • Data transmission buffer empty (SPI_SR_TXNE is set)
    Grab the next byte from the transmit buffer and send it. Update the transmit buffer pointers accordingly.
    • We will be adopting a naïve buffering solution for this application. The buffers will be circular. The ISR can assume that there is space to save the next byte (i.e. we never overflow) as when we reach the end of the buffer we simply set the pointer back to the start again. The code for the ISR becomes:

      #pragma vector = SPI_TXE_vector
      __interrupt void SPI_IRQHandler(void)
      {
          //
          //  Check for an overflow error.
          //
          if (SPI_SR_OVR)
          {
              (void) SPI_DR;                      // These two reads clear the overflow
              (void) SPI_SR;                      // error.
              _status = SC_OVERFLOW;
              OutputStatusCode(_status);
              return;
          }
          //
          //  Looks like we have a valid transmit/receive interrupt.
          //
          if (SPI_SR_RXNE)
          {
              //
              //  We have received some data.
              //
              *_rx = SPI_DR;              //  Read the byte we have received.
              _rx++;
              _rxCount++;
              if (_rxCount == BUFFER_SIZE)
              {
                  _status = SC_RX_BUFFER_FULL;
                  OutputStatusCode(_status);
                  _rx = _rxBuffer;
                  _rxCount = 0;
              }
          }
          if (SPI_SR_TXE)
          {
              //
              //  The master is ready to receive another byte.
              //
              SPI_DR = *_tx;
              _tx++;
              _txCount++;
              if (_txCount == BUFFER_SIZE)
              {
                  OutputStatusCode(SC_TX_BUFFER_EMPTY);
                  _tx = _txBuffer;
                  _txCount = 0;
              }
          }
      }
      

      If we run these two applications and connect the logic analyser we are likely to see traces similar to the following:

      SPI Slave Buffered output on Logic Analyser

      SPI Slave Buffered output on Logica Analyser

      This is not what we expected. In fact we expect to see something like the following:

      Correctly synchronised SPI buffered output on the Logic Analyser

      Correctly synchronised SPI buffered output on the Logic Analyser

      The reason for this is the simple buffering and we have used and the fact that there we have not implemented a method for synchronising the two systems (Netduino and STM8S). The trace can be understood if we follow the deployment and startup cycles for each application. The sequence of events will proceed something like the following:

      • Deploy code to the Netduino Plus
        At this point the application will start to run. We will be outputting a sequence of bytes followed by a 200ms pause.
      • Deploy the code to the STM8S
        The application on the STM8S starts and waits for data to be received on the SPI bus.
        • It is highly possible that when the application on the STM8S starts we will be part way through the transmission of a sequence of bytes by the Netduino. Let us make the assumption that this is the case and the Netduino is transmitting byte 16.

          • Byte 16 transmitted by Netduino
            The byte is received by the STM8S and put into the buffer at position 0. The buffer pointers are moved on to point to position 1.
          • Byte 17 is transmitted by the Netduino
            The byte is received by the STM8S and put into the buffer at position 1. The buffer pointers are moved on to point to position 2.
          • Netduino enters the 200ms pause
            The STM8S waits for the next byte
          • Byte 0 transmitted by Netduino
            The byte is received by the STM8S and put into the buffer at position 2. The buffer pointers are moved on to point to position 3.

          This sequence of events continues until the buffer on the STM8S is full. As you can see, the buffers started out unsynchronised and continue in this manner ad infinitum.

          Interestingly, if you power down the two boards and then power them up simultaneously (or power up the STM8S and then the Netduino Plus) you will see the synchronised trace. This happens because the STM8S has been allowed to enter the receive mode before the Netduino Plus could start to send data.

          Synchronising the Sender and Receiver

          The key to the synchronisation is this case is to consider using an external signal to indicate the start of transmission of the first byte of the buffer. In theory this is what the NSS signal (chip select) is for. The STM8S does not provide a mechanism to detect the state change for the NSS line when operating in hardware mode (which is how the application has been operating so far). In order to resolve this we should consider converting the application to use software chip select mode.

          Chip Select

          The first thing to be considered is the port we will be using to detect the chip select signal. In this case we will be using Port B, pin 0. This port will need to be configured as an input with the interrupts enabled. The InitialisePorts method becomes:

          void InitialisePorts()
          {
              //
              //  Initialise Port D for debug output.
              //
              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 upto 10 MHz.
              //
              //  Initialise Port B for input.
              //
              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.
              //
              //  Now set up the interrupt behaviour.
              //
              EXTI_CR1_PBIS = 2;      //  Port B interrupt on falling edge (initially).
          }
          

          One point to note about the above method is that we initially only detect the falling edge of the chip select signal. My first attempt at this code had both falling and rising edge detection in place. With this method enabled I found it difficult to detect which edge was causing the interrupt to be triggered. I therefore decided to initially detect only the falling edge. I would then add code to change the edge being detected to the ISR controlling the chip select. The code which detects the change of state for the chip select pin is as follows:

          #pragma vector = 6
          __interrupt void EXTI_PORTB_IRQHandler(void)
          {
              if (EXTI_CR1_PBIS == 1)
              {
                  //
                  //  Transition from low to high disables SPI
                  //
                  SPI_CR1_SPE = 0;                        //  Disable SPI.
                  SPI_CR2_SSI = 1;
                  EXTI_CR1_PBIS = 2;                      //  Waiting for falling edge next.
                  OutputStatusCode(SC_CS_RISING_EDGE);
              }
              else
              {
                  //
                  //  Transition from high to low selects this slave device.
                  //
                  EXTI_CR1_PBIS = 1;                      //  Waiting for rising edge next.
                  ResetSPIBuffers();
                  (void) SPI_DR;
                  (void) SPI_SR;
                  SPI_DR = *_tx++;                        //  Load the transmit with first byte.
                  _txCount++;
                  SPI_CR2_SSI = 0;
                  SPI_CR1_MSTR = 0;
                  SPI_CR1_SPE = 1;                        // Enable SPI.
                  OutputStatusCode(SC_CS_FALLING_EDGE);
              }
          }
          

          This code performs two tasks:

          • Falling Edge – Enable SPI
            Resets the SPI buffers and the SPI registers ready for data transmission> Next, enable SPI. Finally, setup the chip select to detect a rising edge.
          • Rising Edge – Disable SPI
            Disables SPI and sets the chip select to look for a falling edge.

          You will also note a few lines outputting status information. These should be removed in production code but are left in here in order to aid debugging.

          The final thing we need to do is to modify the initialisation of the SPI registers. These are small changes and merely change the system from hardware to software chip select. One key change is that we do not enable SPI here. This is left to the chip select interrupt handler. The new version of the InitialiseSPIAsSlave method becomes:

          void InitialiseSPIAsSlave()
          {
              SPI_CR1_SPE = 0;                    //  Disable SPI.
              SPI_CR1_CPOL = 0;                   //  Clock is low when idle.
              SPI_CR1_CPHA = 0;                   //  Sample the data on the rising edge.
              SPI_ICR_TXIE = 1;                   //  Enable the SPI TXE interrupt.
              SPI_ICR_RXIE = 1;                   //  Enable the SPI RXE interrupt.
              SPI_CR2_SSI = 0;                    //  This is SPI slave device.
              SPI_CR2_SSM = 1;                    //  Slave management performed by software.
          }
          

          Conclusion

          This post shows how we can overcome the naïve data transmission method presented by the previous post and add the ability to buffer data and to store a buffered response. Running the final version of the code overcomes the synchronisation problem we encountered at the expense of performing out own chip select handling in software.

          As usual, the source code for this application is available for download (STM8S SPI Slave and Netduino SPI Master).

          Source Code Compatibility

          SystemCompatible?
          STM8S103F3 (Breadboard)
          Variable Lab Protomodule
          STM8S Discovery

The Way of the Register Source Code Update

Sunday, October 7th, 2012

I have recently been discussing a problem running one of the examples in this series on the STM8S Discovery board. After what seems like an eternity the problem was finally traced to the channel I was using in one of the timer examples. It turns out that Timer 1, channel 3 is connected to the touch sensor on the STM8S Discovery board. This means that the code does not generate the expected output. Credit for discovering this goes to Netduino Forum members Fabien and Gutworks.

This discussion also highlighted the fact that these samples were being used on two common development platforms, namely the Variable Labs Protomodule and the STM8S Discovery board. I have therefore modified the samples in order to support these platforms (where possible) as well as the development platform I am using. I will also be adding a compatibility table at the end of each post in the series to show which platforms on which the code has been tested.

Source Code

The following shows the current status of the sample code for the first nine articles in this series:

You can download the latest sources in a single zip file.

In making the changes to make the programs run on as many of the platforms as possible I also standardised the outputs to make them as compatible across the platforms where possible. So the following changes have been made:

  • Port D pin 4 has been used where possible for all programs with a single output.
  • Timer 1, Channel 3 has been changed to Timer 1, Channel 4 as the pin used for this output channel as Timer 1, Channel 3 is connected to the touch sensor on the STM8S Discovery board.
  • The UART example uses UART1 on the STM8s130F3 and Protomodule but UART2 on the STM8S Discovery board.
  • The ADC example uses AIN4 on the STM8S103F3 and STM8S Discovery board but AIN3 on the Protomodule.

Directory Layout

All of the projects use a similar directory structure. Let’s look at the first article in the series (Simple GPIO) as an example.

Unzip the file and navigate to the main directory, if you have used the default setting when extracting the files it should be 1 – Simple GPIO.

The main directory should contain three subdirectories (Discovery, Protomodule and STM8S103F3) and a single file (main.c).

main.c

This file contains the source code for this example and it is shared by all of the projects. Future examples may contain more files here in which case each file will also be a common file to all of the projects.

Discovery, Protomodule and STM8S130F3 Directories

These directories contain the workspaces and projects for each of the target platforms. They will also contain any code which is specific to that platform. At the time of writing the following platforms are supported:

  • STM8S103F3 – STM8S103F3 TSSOP20 platform (my reference platform)
  • Protomodule – Variable Labs Protomodule
  • Discovery – STM8S Discovery board

Single Conversion ADC on the STM8S

Monday, September 17th, 2012

In this post we will have a look at the Analog to Digital Converter (ADC) on the STM8S microcontroller. The number of ADCs available will depend upon the STM8S you are using. We will be using ADC1 which should be present on all STM8S microcontrollers.

In order to show how the ADC works we will be using the STM8S as a dimmer switch for an LED. This simple example will demonstrate how we can read an analog value and use a PWM signal to control the brightness of an LED.

In order to do this we will need the following:

We will also be using Timer 1, Channel 4 to generate a PWM signal to control the brightness of the LED (see Generating PWM Signals using the STM8S.

The algorithm we will be using is as follows:

  1. Configure the system
  2. Read the value from the ADC
  3. Set the PWM output based upon the analog reading
  4. Pause for 1/10th second
  5. Repeat from step 2

We will achieve this by using interrupts from the following resources:

  • Timer 3 – Generates the PWM signal which will be used to control the LED
  • Timer 2 – 1/10th second interrupt which triggers the ADC process
  • ADC – Conversion is completed, adjust the PWM output

ADC Features

The ADC has several modes of operations. We will be using the simplest, namely single conversion mode. As the name suggests, this mode performs a conversion on a specific channel. We will also instruct the microcontroller to generate an interrupt once the conversion is complete.

Amongst the other features and modes on the STM8S are the following:

  • Single scan mode – Perform a single conversion on a number of channels.
  • Continuous and Buffered Continuous – Perform continuous conversions. New conversions start as soon as the current conversion has completed.
  • Continuous Scan – Similar to the Single Scan but operating on a number of channels. Conversion restarts from channel 0 when the last channel has been converted.
  • Watchdog – Set upper and lower limits for the conversion. An interrupt can be generated if a conversion is above the upper or below the lower values.
  • External Trigger – An external trigger is used to start a conversion.

The conversion takes 14uS after a stabilisation period. Once the stabilisation is complete, readings are available without any further pauses.

The Registers

So let’s have a look at the registers we will be using in order to control the ADC:

  • ADC_CR2_ALIGN – Data alignment
  • ADC_CSR_CH – Channel selection
  • ADC_DRH/L – Analog conversion result
  • ADC_CR1_ADON – Turn ADC on / off, trigger conversion
  • ADC_CR3_DBUF – Data buffer Availability
  • ADC_CSR_EOCIE- Enable ADC interrupts
  • ADC_CSR_EOC – End of Conversion

ADC_CR1_ADON – ADC On/Off

The ADON flag determines of the ADC is on or off. It also determines if a conversion has been triggered. Setting ADON to 0 turns the ADC off. Setting ADON to 1 the first time turns the ADC on. Setting this value a second (or subsequent time) starts a conversion.

ADC_CSR_CH – Channel Selection

The CH flag determines the channel which should be converted.

ADC_CR2_ALIGN – Data Alignment

The ALIGN flag determines the type of alignment in the result registers. We will be setting this to right align (set to 1) the data in the registers.

ADC_DRH/L – Conversion Result

This pair of registers holds the result of the conversion. The order the registers should be read is dependent upon the alignment of the data in the registers. For right aligned data we need to read the DRL before DRH.

ADC_CSR_EOCIE – Enable ADC Interrupts

Turn the ADC interrupts on / off.

ADC_CSR_EOC – End of Conversion

This bit is set by the hardware when the conversion has completed. It should be reset by the software in the Interrupt Service Routine (ISR).

Unused Registers

The application we are going to be writing is simple and only performs a conversion once every 1/10th second. This is plenty of time to perform a conversion and process the data before the next conversion starts. As such, we do not need to check the overrun register. This register indicates if the data generated in the continuous mode was overwritten before it was used.

Hardware

This post requires some additional hardware to be added to the circuit containing the STM8S:

  • LED which is being controller through a transistor configured as a switch
  • Potentiometer to provide an analog signal for conversion

LED Output

Use the LED Output circuit in the post External Interrupts on the STM8S. Connect the base of the transistor to the output of Timer 1, Channel 3.

Potentiometer

Connect the potentiometer so that one pin is connected to ground and one to 3.3V. The output (the wiper) should be connected to AIN4 on the STM8S. I used a 10K potentiometer for this example.

Software

As we have already noted, we will be driving the application by using interrupts. We will also use a few techniques/methods from previous posts. So let’s look at each of the elements we will be using.

Timer 2 – Start Conversions

Timer 2 is used to generate 10 interrupts per second. Each interrupt will trigger a new conversion. Setting up the timer should look familiar:

void SetupTimer2()
{
    TIM2_PSCR = 0x05;       //  Prescaler = 32.
    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;       //  Finally enable the timer.
}

The ISR is a simple method, it has only one main function, namely to start the conversion.

#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    PD_ODR_ODR5 = !PD_ODR_ODR5;        //  Indicate that the ADC has completed.

    ADC_CR1_ADON = 1;       //  Second write starts the conversion.

    TIM2_SR1_UIF = 0;       //  Reset the interrupt otherwise it will fire again straight away.
}

Note the comment on the ADC register Second write starts the conversion. This method assumes that we have set ADC_CR1_ADON at least once previously. As you will see later, we set this register in the setup method for the ADC.

In addition to the starting of the conversion, we have also added a line of code to toggle PD5. This will show us when the ISR has been triggered and is really only there for debugging.

Timer 1, Channel 4 – PWM Signal

The ADC generates a 10 bit value. We will therefore set up Timer 1 to generate a PWM signal which is 1024 (210) clock signals in width. We can therefore use the value from the conversion to directly drive the PWM duty cycle.

void SetupTimer1()
{
    TIM1_ARRH = 0x03;       //  Reload counter = 1023 (10 bits)
    TIM1_ARRL = 0xff;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    TIM1_CR1_DIR = 1;       //  Down counter.
    TIM1_CR1_CMS = 0;       //  Edge aligned counter.
    TIM1_RCR = 0;           //  Repetition count.
    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 = 0x03;      //  Start with the PWM signal off.
    TIM1_CCR4L = 0xff;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
    TIM1_CR1_CEN = 1;
}

Note that the Auto-reload registers is set to 1023 (0x3ff). We also set the capture compare registers to 1023 at the start. This will turn the LED off when the program starts.

System Clock

The program will be generating a PWM signal with a reasonably high clock frequency. In order to do this we will set the clock to use the internal oscillator running at 16MHz.

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

GPIO – Debug Signals

As with previous examples, we will configure some of the output ports so that we can generate debug signals:

void SetupOutputPorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    //
    //  PD5 indicates when the ADC is triggered.
    //
    PD_DDR_DDR5 = 1;
    PD_CR1_C15 = 1;
    PD_CR2_C25 = 1;
    //
    //  PD4 indicated when the ADC has completed.
    //
    PD_DDR_DDR4 = 1;
    PD_CR1_C14 = 1;
    PD_CR2_C24 = 1;
}

ADC

The setup method for the ADC is relatively simple as many of the settings we will be using are the defaults after a reset. This method is as follows:

void SetupADC()
{
    ADC_CR1_ADON = 1;       //  Turn ADC on, note a second set is required to start the conversion.

#if defined PROTOMODULE
    ADC_CSR_CH = 0x03;      //  Protomodule uses STM8S105 - no AIN4.
#else
    ADC_CSR_CH = 0x04;      //  ADC on AIN4 only.
#endif

    ADC_CR3_DBUF = 0;
    ADC_CR2_ALIGN = 1;      //  Data is right aligned.
    ADC_CSR_EOCIE = 1;      //  Enable the interrupt after conversion completed.
}

After calling this method, the ADC should be powered on and ready to perform a conversion. Note that the ADC will not perform a conversion until ADC_CR1_ADON is set for a second time. This will be performed by the Timer 2 interrupt.

Another point to note is that on the Protomodule board the version of the STM8S does not have the AIN4 channel and so we use AIN3 instead.

The next method we will consider is the ADC ISR. This is where the real work of changing the values for the PWM signal takes place.

#pragma vector = ADC1_EOC_vector
__interrupt void ADC1_EOC_IRQHandler()
{
    unsigned char low, high;
    int reading;

    ADC_CR1_ADON = 0;       //  Disable the ADC.
    TIM1_CR1_CEN = 0;       //  Disable Timer 1.
    ADC_CSR_EOC = 0;        //     Indicate that ADC conversion is complete.

    low = ADC_DRL;            //    Extract the ADC reading.
    high = ADC_DRH;
    //
    //  Calculate the values for the capture compare register and restart Timer 1.
    //
    reading = 1023 - ((high * 256) + low);
    low = reading &amp; 0xff;
    high = (reading >> 8) &amp; 0xff;
    TIM1_CCR3H = high;      //  Reset the PWM counters.
    TIM1_CCR3L = low;
    TIM1_CR1_CEN = 1;       //  Restart Timer 1.

    PD_ODR_ODR4 = !PD_ODR_ODR4;     //  Indicate we have processed an ADC interrupt.
}

Note that we once again use a GPIO port to indicate when the ISR has been called.

Main Program Loop

The main program loop looks pretty much like the programs we have written in previous examples:

void main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    SetupTimer1();
    SetupTimer2();
    SetupOutputPorts();
    SetupADC();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

Results

If we put all of this together we can do the following:

The LED indicates the duty cycle of the PWM signal. The output on the oscilloscope confirms the changes being made to the PWM signal (the wider the high component of the signal, the brighter the LED should be).

By adjusting the trimmer potentiometer to ground (turning to the right) the LED becomes dimmer as the duty cycle becomes biased towards ground (off more than on). Turning to the left does the reverse, the PWM signal becomes biased to +3.3V (more on than off).

Conclusion

This example may be trivial as we could have easily just connected the LED and the potentiometer together. However, it does show how we can take a reading from an ADC and change the output of the microcontroller based upon the value.

As always, the source code is available for download. This application is compatible with my reference platform, the Variable Labs Protomodule and the STM8S Discovery board.

Source Code Compatibility

SystemCompatible?
STM8S103F3 (Breadboard)
Variable Lab Protomodule
STM8S Discovery

Timer 1 Counting Modes

Friday, September 14th, 2012

In this article we will continue to look at Timer 1, specifically:

  • counting modes
  • repetition counter
  • update/overflow events

This article will assume some knowledge from the following two posts published previously:

Unlike previous posts we will not be resetting the system clock but will instead leave this running using the default 2 MHz internal HSI oscillator.

Test Environment

In order to demonstrate the features of the timer I will be using my Saleae Logic Analyser as we will be observing events which occur many times a second. I have the following connections set up:

Saleae STM8S Pin Description
Ground N/A Ground
Black PD5 Indicate the state of the Timer 2 overflow interrupt
Brown Timer 1, Channel 4 PWM signal from Timer 1
Red PD4 Indicate state of Timer 1 (running or halted)

This set up will result in a series of charts from the Logic software which look like this:

Logic Analyser Output

Logic Analyser Output

The top portion of the display (labelled TIM2 Overflow) indicates when an overflow event has occurred on Timer 2. This timer is used to control the application. A change from low to high starts Timer 1 and the timer window for the observation runs to the next transition from high back to low. There is then a pause and then the whole cycle starts again.

The centre portion (labelled TIM1 PWM) shows the PWM output of Timer 1, channel 4. This is used to demonstrate what happens as we change the various values in the timer registers.

The lower portion (labelled TIM1 Overflow) shows when the Timer 1 overflow event occurs.

One thing that you can do to make the capture of these traces easier is to set a sample trigger on one of the traces. If you look at the trace labelled TIM2 Overflow you will see that one of the four square buttons is highlighted. This is showing that capture of data will begin when the logic analyser detects a signal which changes from low to high. You can make use of this by deploying and starting the application as follows:

  1. Compile and deploy the application
  2. Start data capture on the logic analyser
  3. Start the application

When you click the Start button on the logic analyser (step 2) a window will appear which indicates that the software is monitoring the data looking for a trigger (in this case rising edge on PD5) before it will start to capture data.

The Registers

The example code we will use shows how we can change the properties of the output and also the frequency of the interrupts generated by using the following registers:

  • TIM1_CR1_DIR – Counter Direction
  • TIM1_RCR – Repetition Counter

TIM1_CR1_DIR – Counter Direction

This register determines the direction of the counter, either up from 0 to the auto-reload values (set to 0) or down from the auto-reload value (set to 1).

TIM1_RCR – Repetition Counter

This counter can be used to determine the frequency of the overflow interrupts. Normally (i.e. by setting this register to 0) an overflow/underflow interrupt is generated every time the counter is reloaded from the auto-reload registers. By setting this register to any number other than zero (i.e. n), the overflow/underflow interrupt will only be generated after n + 1 overflow/underflows have been detected.

Software

We will use the registers described above to generate a series of 5 pulses every 50 mS. The code to do this is as follows:

#if defined DISCOVERY
    #include <iostm8S105c6.h>
#elif defined PROTOMODULE
    #include <iostm8s103k3.h>
#else
    #include <iostm8s103f3.h>
#endif
#include <intrinsics.h>

//--------------------------------------------------------------------------------
//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    if (PD_ODR_ODR5 == 1)
    {
        TIM1_CR1_CEN = 0;
        PD_ODR_ODR5 = 0;
        PD_ODR_ODR4 = 0;
    }
    else
    {
        //
        //  Force Timer 1 to update without generating an interrupt.
        //  This is necessary to makes sure we start off with the correct
        //  number of PWM pulses for the first instance only.
        //
        TIM1_CR1_URS = 1;
        TIM1_EGR_UG = 1;
        //
        //  Reset the indicators.
        //
        PD_ODR_ODR5 = 1;
        PD_ODR_ODR4 = 1;
        //
        //  Enable Timer 1
        //
        TIM1_CR1_CEN = 1;           //  Start Timer 1.
    }
    TIM2_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

//--------------------------------------------------------------------------------
//
//  Timer 1 Overflow handler.
//
#pragma vector = TIM1_OVR_UIF_vector
__interrupt void TIM1_UPD_OVF_IRQHandler(void)
{
    PD_ODR_ODR4 = !PD_ODR_ODR4; //0;                //  Signal to the user that Timer 1 has stopped.
    TIM1_CR1_CEN = 0;               //  Stop Timer 1.
    TIM1_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

//--------------------------------------------------------------------------------
//
//  Set up Timer 1, channel 3 to output a single pulse lasting 240 uS.
//
void SetupTimer1()
{
    TIM1_ARRH = 0x03;       //  Reload counter = 960
    TIM1_ARRL = 0xc0;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    //
    //  Select 0 for up counting or 1 for down counting.
    //
    TIM1_CR1_DIR = 0;       //  Up counter.
    //
    //  Select 0 for edge aligned, 1 for mode 1 centre aligned,
    //  2 for mode 2 centre aligned or 3 for mode 3 centre aligned.
    //
    TIM1_CR1_CMS = 0;       //  Edge aligned counter.
    //
    //  Set the following depending upon the number of PWM pulses, note
    //  n + 1 pulses will be generated before the interrupt.
    //
    TIM1_RCR = 4;           //  Repetition count.
    //
    //  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 = 0x01;      //  480 = 50% duty cycle (based on TIM1_ARR).
    TIM1_CCR4L = 0xe0;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
    TIM1_IER_UIE = 1;       //  Turn interrupts on.
}

//--------------------------------------------------------------------------------
//
//  Setup Timer 2 to generate a 40 Hz interrupt based upon a 2 MHz timer.  This
//	will result in a signal with a frequency of 20Hz.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x00;       //  Prescaler = 1.
    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;       //  Finally enable the timer.
}

//--------------------------------------------------------------------------------
//
//  Now set up the output ports.
//
//  Setup the port used to signal to the outside world that a timer event has
//  been generated.
//
void SetupOutputPorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    //
    //  PD5 is used to indicate the firing of the update/overflow event for Timer 2
    //
    PD_DDR_DDR5 = 1;
    PD_CR1_C15 = 1;
    PD_CR2_C25 = 1;
    //
    //  PD4 is used to indicate the firing of the update/overflow event for Timer 1
    //
    PD_DDR_DDR4 = 1;
    PD_CR1_C14 = 1;
    PD_CR2_C24 = 1;
}

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
void main()
{
    __disable_interrupt();
    SetupTimer1();
    SetupTimer2();
    SetupOutputPorts();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

If we now have a look at the output on the Logic Analyser we can see how the various signals indicate what is happening with the Timers.

Five Pulses

Five Pulses

The top trace of the output shows us when Timer 2 has overflowed (i.e. every 25mS). The code above goes through a cycle of enabling Timer 1 and 25mS later disabling Timer 1. You can see the output from Timer 1, Channel 3 in the middle of the three traces. The final (bottom) trace shows when Timer 1 overflows.

We have used much of the code presented above in previous examples in the series. We are setting up the two timers (Timer 1 and Timer 2, Channel 3) and adding interrupt handlers for the counter overflows. We are also setting up Port D as an output port to give us some diagnostic traces.

One difference from previous examples is that we are running this code at a slower clock frequency. By not setting up the system clock we are running at the default clock rate (2 MHz clock from the internal clock source).

Timer 1 Overflow Handler

This handler is really simple and does nothing more than use PD4 to indicate it has been called and then turns the timer off.

Timer 2 Overflow Handler

This handler is a little more complex. The first things to note is that it uses PD5 and works out if we are starting timers or pausing (PD5 = 1).

The next thing to note is the use of two more registers TIM1_CR1_URS and TIM1_EGR_UG. These two registers are used together to force the counter register to be reloaded without generating an interrupt. This is necessary to ensure that we start from a known value.

Conclusion

We have seen how we can use the logic analyser and some output pins (PD4 and PD5) to give us information about the sequence of events (interrupts). This is a very useful diagnostic tool and often the only one which is available to you when operating in this sort of environment.

I would also suggest trying some of the following and viewing the output on a logic analyser:

  • Changing the counting mode
  • Removing the update of TIM1_CR1_URS and/or TIM1_EGR_UG

As always, the source code is available for download.

Source Code Compatibility

System Compatible?
STM8S103F3 (Breadboard)
Variable Lab Protomodule
STM8S Discovery