RSS

Designspark Feature Tutorial – ModelSource

July 21st, 2013 • ElectronicsComments Off on Designspark Feature Tutorial – ModelSource

I have been a great fan of Designspark for a while. Many hobbyists swear by Eagle. Personally, I find the interface counter intuitive. Designspark on the other hand, feels like a Windows application.

Shortly after Designspark version 5 was released, I noticed a new toolbar in the bottom lefthand corner of the interface. This toolbar was labelled ModelSource. So like all good software users I went to the manual to find out more <COUGH>, OK, I simply started to play with the feature and here’s a summary of what I found.

What Is ModelSource?

ModelSource is Designspark’s free on-line database of component models. The library is by no means complete but it does provide a large on-line library of components to choose from. At the time of writing, there are over 80,000 components in the library. The library is linked to RS Components but this is to be expected as Deisgnspark seems to have been originated/sponsored by RS.

Interestingly, the library is also compatible with other software packages.

Let’s Use It

The quickest way to become familiar with a software feature is to use it. So let’s add a component I’ve never used before to a schematic. For this example we will use the 74HC164.

We start by opening the ModelSource toolbar. This is can be found in the bottom left corner of the application window and looks like this:

ModelSource Toolbar Collapsed

ModelSource Toolbar Collapsed

If you cannot see the ModelSource toolbar then you can check if it is visible. by looking at the View menu. Alternatively, you can use the hotkey Ctrl-M to make it visible.

The first time you make ModelSource visible you will have to log in using your Designspark credentials. Once logged in, you can access ModelSource.

Clicking on ModelSource opens the toolbar. This can appear as required or you can permanently pin this in the open state to the interface. When first opened ModelSource will be ready to accept search requests from the user:

ModelSource

ModelSource

There are really three distinct areas to the interface:

  • Search Parameters (Red Rectangle)
  • Component Detail (Green Rectangle)
  • Component List (Blue Rectangle)

Search Parameters

The Search Parameters section of the interface is in the upper left part of ModelSource and it allows you to search ModelSource by component family or for a specific component using the RS part number.

Component Detail

Component Detail sows the schematic and PCB trace for a selected component along with summary information. This can be found to the right of the Search Parameters. This section also allows you to go to the component data sheet and add the component to your library.

Component List

Component List contains a grid of components which match your search criteria.

Searching For A Component

The component we are searching for is the 74HC164. This is a 8-bit shift register from the 74HC family of components.

The search process requires the user to select from a list of possible family groups/characteristics slowly narrowing the search down to a group of components which you can then browse through. So for the 74HC164 we start by selecting Semiconductors from the first drop down list. The system will then consult the ModelSource database and construct sensible options for the second drop down list which will then be populated.

This component is a shift register and so it probably best fits into the Standard Logic family of components. So we select this in the second drop down list. Again, Designspark will select a new set of options for the third drop down list.

The final list now contains the family of components we are looking for, namely Counters & Shift Registers. Selecting this option now allows Designspark to populate the component list from the ModelSource database and we are presented with the following:

Semiconductor search for Counters and Shift Registers

Semiconductor search for Counters and Shift Registers

As you can see, you can use the drop down lists above each column to filter the information presented in the component list should you require to refine the results further. A quick look down the list and we can see our component, the 74HC164. So let’s select the 74HC164D component in the grid:

74HC164D Ready to load preview

74HC164D Ready to load preview

And now press the Load Preview button. Designspark will then load the component preview information from ModelSource:

74HC164 Preview Complete

74HC164 Preview Complete

At this point we have two options, we can use this component immediately and add it to the schematic or we can view the datasheet. Clicking on View Datasheet takes you to the RS web site and presents the page for the component. Here you can add the component to your basket or scroll down the page to the datasheet links:

74HC164 Web page

74HC164 Web page

By selecting Use Component you make the component available through the library installed on your computer. Whilst in the schematic editor press the Add Component Button

Add Component button

Add Component button

and type in the component name into the Component text box in the Add Component dialog:

Add 74HC164 component to schematic

Add 74HC164 component to schematic

You can now add the component to the schematic:

74HC164 on schematic

74HC164 on schematic

Searching for a Component by Part Number

It is sometimes simpler to use the RS Components web site to locate the part you are looking for and then use the Part Number Search option to locate the component. For example, consider the TLC5940 shift register. It is difficult to work out from the list of search options in the drop down list boxes exactly what you should be selecting.

In comparison, go to the RS Components web site and enter TLC5940 in the search text box on the home page and press the Find button. You will be taken to a page with three components, all variations on the TLC5940 16 channel PWM driver. Now make a note of the RS part number (I have the 389-344 variation of this component).

So go back to ModelSource and press the Clear button. Now enter the component part number into the Part Number Quick Search text box and press the Go button:

TLC5940 search for part number

TLC5940 search for part number

Selecting the part and pressing Load Preview shows the part information:

TLC5940 ModelSource Preview

TLC5940 ModelSource Preview

Conclusion

ModelSource is not a complete database but with 80,000 components it offers a very good chance of finding the component you need for the circuit you are building. I have used this on several occasions to find models for components which do not exist in the standard installation.

There is no question that this could be improved. For instance, why can you not search by component name as you can in the web interface?

Overall this is a good product feature and whilst it is linked through to the RS Components web site it does not necessarily mean you have to order from there.

Temperature and Humidity Sensor Module for the Netduino GO!

June 25th, 2013 • Electronics, Netduino, Software Development, STM8Comments Off on Temperature and Humidity Sensor Module for the Netduino GO!

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

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

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

DHT22 Temperature and Humidity Sensor

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

The four pins should be connected as follows:

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

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

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

CondensedTrace

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

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

StartSignal

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

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

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

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

DHT22FullDataPacket

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

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

Schematic

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

DHT22Schematic

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

Breadboard and PCB Prototypes

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

TemperatureSensorBarePC

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

TemperatureModulePCBPopulated

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

Software

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

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

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

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

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

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

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

Functionality

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

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

STM8S Module

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Netduino GO!

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

DHT22ModuleNETMFClassDiagram

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

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

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

The code which gets the current readings is relatively trivial:

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

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

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

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

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

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

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

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

Testing

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

TemperatureModuleConnectedToGOAndSevenSegment

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

private static DHT22 module;

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

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

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

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

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

Conclusion

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

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

Transmitting Data Using the STM8S SPI Master Mode

June 23rd, 2013 • Electronics, STM8Comments Off on Transmitting Data Using the STM8S SPI Master Mode

So far in The Way of the Register series we have only looked at SPI from a slave device point of view as we have been working towards creating a Netduino GO! module. For every slave device there must be a master, here we will look at configuring the STM8S to operate in SPI master mode.

The project will look at controlling a TLC5940 in order to emulate the work described in the post TLC5940 16 Channel PWM Driver. We could simply bit-bang the data out to the chip but instead we will use the SPI interface to achieve this.

The project breaks down into the following steps:

  1. Generate the grey scale clock and blank signals
  2. Bit-Bang data out over GPIO pins to create an operational circuit
  3. Convert the data transmission to SPI

See the quoted post for a description of how this chip works and for an explanation of the terminology used.

Generating the Grey Scale Clock and Blank Signals

The TLC5940 generated 4,096 grey scale values by using a PWM counter. Once the counter reaches 4096 pulses it stops until it is told to restart. The Blank pulse acts as a restart signal. This project will be controlling LEDs and so will want to continuously keep the counter running. If we did not keep the counter in the TLC5940 running then the LEDs would light for a short while and then simply turn off and remain off.

The greyscale clock is generated by using the Configurable Clock Output (CCO) pin on the STM8S. This pin simply outputs the clock pulses used to drive the STM8S. Reviewing the data sheet we find that the maximum value for the grey scale clock is 30MHz. Using out standard clock initialisation generates a clock with a frequency of 16MHz (approximately). This is well within the tolerances of the TLC5940. To output this we need to make a simple modification to our standard code, name change the line:

CLK_CCOR = 0;   //  Turn off CCO.

to:

CLK_CCOR = 1;   //  Turn on CCO.

The starting point for our application becomes:

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

//
//  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 = 1;                       //  Turn on 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.
}

//
//  Main program loop.
//
void main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    __enable_interrupt();
    while (1)
    {
        __wait_for_interrupt();
    }
}

Wiring up the STM8S and connecting the scope to PC4 (CCO output pin) gives the following trace on the scope:

CCO On Scope

CCO On Scope

The trace on the scope has a minimum value of around 680mV and a maximum of 2.48V. In an ideal world this signal should range from 0 to 3.3V (based upon a 3.3V supply). Adding an inverter from a 74HC04 and feeding the signal through one of the gates gives the following trace:

Invertor output on the scope

Inverter output on the scope

This is starting to look a lot better. The next task is to create the Blank signal. There are several ways of doing this. The most automatic way of doing this is to generate a very short PWM pulse using one of the timers in the STM8S. One drawback of this method is that it is more difficult to generate a Blank pulse on demand. Instead we will use the interrupt method described in the same article. Whilst not automatic it is still a trivial task to complete. We simply modify the code from the method to load the counters with 4,096. The code for the GPIO port, timer and interrupt becomes:

//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    PD_ODR_ODR4 = 1;
    PD_ODR_ODR4 = 0;
    TIM2_SR1_UIF = 0;       //  Reset the interrupt otherwise it will fire again straight away.
}

//
//  Setup Timer 2 to generate an interrupt every 4096 clock ticks.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x00;       //  Prescaler = 1.
    TIM2_ARRH = 0x10;       //  High byte of 4096.
    TIM2_ARRL = 0x00;       //  Low byte of 4096.
    TIM2_IER_UIE = 1;       //  Turn on the interrupts.
    TIM2_CR1_CEN = 1;       //  Finally enable the timer.
}

//
//  Setup the output ports used to control the TLC5940.
//
void SetupOutputPorts()
{
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR_DDR4 = 1;        //  Port D, pin 4 is used for the Blank signal.
    PD_CR1_C14 = 1;         //  Port D, pin 4 is Push-Pull
    PD_CR2_C24 = 1;         //  Port D, Pin 4 is generating a pulse under 2 MHz.
}

Hooking up the scope to PD4 gives the following trace:

Blanking pulses

Blanking pulses

The single pulses are being generated at a frequency of approximately 3.9kHz. A little mental arithmetic dividing the 16MHz clock by 4,096 comes out to about 3,900.

Zooming in on the signal we see:

Single Blanking Pulse

Single Blanking Pulse

This shows that the signal is 125nS wide. This is acceptable as the minimum pulse width given in the data sheet is 20nS.

So at this point we have the 16MHz grey scale clock signal and the Blank pulse being generated every 4,096 clock pulses.

Connecting the TLC5940

The next task is to connect the STM8S to the TLC5940. You should refer to the article TLC5940 16 Channel PWM Driver for more information on the pins and their meaning. For this exercise we will use the following mapping:

STM8S PinTLC5940 Pin
PD4Blank (pin 23)
PD3XLAT (pin 24)
PD2VPRG (pin 27)
PD5Serial data (pin 26)
PD6Serial clock (pin 25)
PC4 (via inverter)GSCLK (pin 18)

You will note that the serial data and clock are currently connected to PD5 and PD6 respectively. Whilst the eventual aim is to communicate with the TLC5940 via SPI, the initial communication will be using Bit-Banging. We will move on to using SPI once the operation of the circuit has been proven using tested technology.

The first changes we will have to make create some #define statements to make the code a little more readable. We also add some storage space for the grey scale and dot correction data.

//
//  Define which pins on Port D will be used as control signals for the TLC5940.
//
//  BLANK - A pulse from low to high causes the TLC5940 to restart the counter
//  XLAT - A high pulse causes the data to be transferred into the DC or GS registers.
//  VPRG - Determines which registers are being programmed, High = DC, Low = GS.
//
#define PIN_BLANK                   PD_ODR_ODR4
#define PIN_XLAT                    PD_ODR_ODR3
#define PIN_VPRG                    PD_ODR_ODR2
//
//  Bit bang pins.
//
#define PIN_BB_DATA                 PD_ODR_ODR5
#define PIN_BB_CLK                  PD_ODR_ODR6
//
//  Values representing the modes for the VPRG pin.
//
#define PROGRAMME_DC                1
#define PROGRAMME_GS                0

//
//  TLC5940 related definitions.
//
#define TLC_NUMBER                  1
#define TLC_DC_BYTES_PER_CHIP       12
#define TLC_DC_BYTES                TLC_NUMBER * TLC_DC_BYTES_PER_CHIP
#define TLC_GS_BYTES_PER_CHIP       24
#define TLC_GS_BYTES                TLC_NUMBER * TLC_GS_BYTES_PER_CHIP

//
//  Next we need somewhere to hold the data.
//
unsigned char _greyScaleData[TLC_GS_BYTES];
unsigned char _dotCorrectionData[TLC_DC_BYTES];

The Bit-Banging methods should look familiar to anyone who has been reading any of the posts in The Way of the Register series.

//--------------------------------------------------------------------------------
//
//  Bit bang data.
//
//  TLC5940 expects the data to be shifted MSB first.  The data
//  is shifted in on the rising edge of the clock.
//
void BitBang(unsigned char byte)
{
    for (short bit = 7; bit >= 0; bit--)
    {
        if (byte &amp; (1 << bit))
        {
            PIN_BB_DATA = 1;
        }
        else
        {
            PIN_BB_DATA = 0;
        }
        PIN_BB_CLK = 1;
        PIN_BB_CLK = 0;
    }
    PIN_BB_DATA = 0;
}

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

Related to the Bit-Banging methods are the two methods which will send the grey scale and dot correction data:

//--------------------------------------------------------------------------------
//
//  Send the grey scale data to the TLC5940.
//
void SendGreyScaleData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_GS;
    BitBangBuffer(buffer, length);
    PulseXLAT();
    PulseBlank();
}

//--------------------------------------------------------------------------------
//
//  Send the dot correction buffer to the TLC5940.
//
void SendDotCorrectionData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_DC;
    BitBangBuffer(buffer, length);
    PulseXLAT();
    PulseBlank();
}

We also need a few methods to make the TLC5940 latch the data and also restart the counters:

//--------------------------------------------------------------------------------
//
//  Pulse the Blank pin in order to make the TLC5940 reload the counters and
//  restart timer.
//
void PulseBlank()
{
    PIN_BLANK = 1;
    PIN_BLANK = 0;
}

//--------------------------------------------------------------------------------
//
//  Pulse the XLAT pin in order to make the TLC5940 transfer the
//  data from the latches into the appropriate registers.
//
void PulseXLAT()
{
    PIN_XLAT = 1;
    PIN_XLAT = 0;
}

Next we need to set the initial condition. For this we set the TLC dot correction off and also turn all of the LEDs off:

//--------------------------------------------------------------------------------
//
//  Initialise the TLC5940.
//
void InitialiseTLC5940()
{
    for (int index = 0; index < TLC_DC_BYTES; index++)
    {
        _dotCorrectionData[index] = 0xff;
    }
    for (int index = 0; index < TLC_GS_BYTES; index++)
    {
        _greyScaleData[index] = 0;
    }
    SendDotCorrectionData(_dotCorrectionData, TLC_DC_BYTES);
    SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
}

The final support method we need to add is the method which sets the brightness of an LED. The brightness is a 12-bit value (0-4095). This means each LED uses 1.5 bytes for the brightness value. The following methods breaks down the value and ensures that the correct bits are set in the grey scale buffer depending upon which LED is being changed:

//--------------------------------------------------------------------------------
//
//  Set the brightness of an LED.
//
void SetLEDBrightness(int ledNumber, unsigned short brightness)
{
    int offset = (ledNumber >> 1) * 3;
    if (ledNumber &amp; 0x01)
    {
        _greyScaleData[offset + 1] = (unsigned char) (_greyScaleData[offset + 1] &amp; 0xf0) | ((brightness & 0x0f00) >> 8);
        _greyScaleData[offset + 2] = (unsigned char) (brightness & 0xff);
    }
    else
    {
        _greyScaleData[offset] = (unsigned char) ((brightness &amp; 0x0ff0) >> 4) &amp; 0xff;
        _greyScaleData[offset + 1] = (unsigned char) ((brightness & 0x0f) >> 4) | (_greyScaleData[offset + 1] & 0x0f);
    }
}

We should also create a similar method for changing the dot correction value for an LED. This is left as an exercise for the reader as we will not be changing this value in this code.

Proving the concept

If we have connected the TLC5940 correctly and our code works we should be able to connect up some LEDs (common anode) to the TLC5940 and change the brightness under program control.

This main program loop slowly increases the brightness of the LEDs. When they are at full brightness they are turned off and the process starts again:

//--------------------------------------------------------------------------------
//
//  Main program loop.
//
void main()
{
    //
    //  Initialise the system.
    //
    __disable_interrupt();
    InitialiseSystemClock();
    SetupOutputPorts();
    SetupTimer2();
    
    InitialiseTLC5940();
    __enable_interrupt();
    //
    //  Main program loop.
    //
    int brightness = 0;
    int counter = 0;
    while (1)
    {
        __wait_for_interrupt();
        counter++;
        if (counter == 20)
        {
            TIM2_CR1_CEN = 0;
            counter = 0;
            for (int index = 0; index < 16; index++)
            {
                SetLEDBrightness(index, brightness);
            }
            SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
            brightness++;
            if (brightness == 4096)
            {
                brightness = 0;
            }
            TIM2_CR1_CEN = 1;       //  Finally re-enable the timer.
        }
    }
}

If you connect a scope to the cathode of one of the LEDs you will see that the wave form slowly changes over time. At the start, the LED is fully on and the trace on the scope shows a horizontal line, i.e. a constant voltage. As time moves on and the value in the dot correction buffer changes you start to see a PWM signal similar to the following:

PWM Output On Scope 1

PWM Output On Scope 1

This trace shows the signal when the LEDs are a little brighter:

PWM Output On Scope 2

PWM Output On Scope 2

Having arrived here we now know that the circuit has been connected correctly and that the control logic in the main method works. We can now move on to considering what we need to do in order to use SPI in master mode. The aim will be to simply remove the Bit-Banging methods and replace these with an interrupt driven SPI master algorithm.

SPI Master

So now we have a working circuit we need to look at SPI on the STM8S. Firstly let’s remind ourselves of the serial communication parameters for the TLC5940. This chip reads the data on the leading clock edge (CPHA = 1). We have also set the clock idle state to low (CPOL = 0).

It is also advisable to start off using the lowest clock speed for SPI in order to confirm correct operation of the software and circuit. Lower speed are less likely to be subject to interference.

SPI Registers

You should review the previous articles on SPI communication if you are not already familiar with the SPI registers we have used so far. In this post we will only discuss the new settings required to switch from being a SPI slave device to a SPI master device.

SPI_CR1_BR – Baud Rate Control

The SPI baud rate is determined by the master clock frequency and the value in this register. The divisor used to set the baud rate according to the following table:

SPI_CR1_BRDivisor
0002
0014
0108
01116
10032
10164
110128
111256

The SPI baud rate is calculated as fmaster / divisor. So for our master clock speed of 16MHz we get the lowest clock speed of 16,000,000 / 256, or 62,500Hz.

SPI_CR1_MSTR – Master Selection

Setting this bit switches SPI into master mode (see also SPI_CR1_SPE).

Note that the reference for the STM8S also states that this bit (and SPI_CR1_SPE) will only remain set whilst NSS is high. It this therefore essential to connect NSS to Vcc if this device is not being used as a slave device.

Implementing SPI

Using SPI presents us with a small problem, namely the program will have to start to operate in a more asynchronous way. The code presented so far has only one interrupt to be concerned with, namely the timer used to control the Blank signal. Adding SPI to the mix means that we will have to also consider the SPI interrupt as well. It also adds the complication of sending dot correction data followed by grey scale data. This last problem will not be covered here and is left as an exercise for the reader.

The initialisation code merely sets things up for us:

//--------------------------------------------------------------------------------
//
//  Initialise SPI to be SPI master.
//
void SetupSPIAsMaster()
{
    SPI_CR1_SPE = 0;                    //  Disable SPI.
    SPI_CR1_CPOL = 0;                   //  Clock is low when idle.
    SPI_CR1_CPHA = 0;                   //  Capture MSB on first edge.
    SPI_ICR_TXIE = 1;                   //  Enable the SPI TXE interrupt.
    SPI_CR1_BR = 7;                     //  fmaster / 256 (62,500 baud).
    SPI_CR1_MSTR = 1;                   //  Master device.
}

Much of the code should be familiar as it has been used in previous posts discussing SPI slave devices. Not however that we do not enable SPI at this point. We simply set the scene for us to use SPI later.

The SPI data transfers will be controlled by using an interrupt service routine:

//--------------------------------------------------------------------------------
//
//  SPI Interrupt service routine.
//
#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.
        return;
    }
    if (SPI_SR_TXE)
    {
        //
        //  Check if we have more data to send.
        //
        if (_txBufferIndex == _txBufferSize)
        {
            while (SPI_SR_BSY);
            SPI_CR1_SPE = 0;
            _txBuffer = 0;
            PulseXLAT();
            PulseBlank();
            TIM2_CR1_CEN = 1;
        }
        else
        {
            SPI_DR = _txBuffer[_txBufferIndex++];
        }
    }
}

The main works starts when we have established that the transmit buffer is empty (SPI_SR_TXE is set). If we have more data then we put the byte into the data register (SPI_DR). If we have transmitted all the data we have then we wait for the last byte to complete transmission (SPI_SR_BSY becomes false) before we start to terminate the end of the SPI communication.

In order to send some data we really just need to setup the pointers and counters correctly and then enable SPI. So the SendGreyScaleData method becomes:

//--------------------------------------------------------------------------------
//
//  Send the grey scale data to the TLC5940.
//
void SendGreyScaleData(unsigned char *buffer, int length)
{
    PIN_VPRG = PROGRAMME_GS;
    _txBuffer = buffer;
    _txBufferIndex = 0;
    _txBufferSize = length;
    TIM2_CR1_CEN = 0;
    SPI_CR1_SPE = 1;
}

We also need to have a look at the main program loop as we use the __wait_for_interrupt() method in order to determine when we should start to process the next LED brightness value. We now need to ignore the interrupts when SPI is enabled otherwise the brightness will increase each time the transmit buffer is empty. A crude implementation eliminating the SPI interrupts is:

int brightness = 0;
int counter = 0;
while (1)
{
    __wait_for_interrupt();
    if (!SPI_CR1_SPE)
    {
        counter++;
        if (counter == 20)
        {
            TIM2_CR1_CEN = 0;
            counter = 0;
            for (int index = 0; index < 16; index++)
            {
                SetLEDBrightness(index, brightness);
            }
            SendGreyScaleData(_greyScaleData, TLC_GS_BYTES);
            brightness++;
            if (brightness == 4096)
            {
                brightness = 0;
            }
            TIM2_CR1_CEN = 1;       //  Finally re-enable the timer.
        }
    }
}

Making these changes and running the code shows that the system operated as before.

Increasing the Baud Rate

As noted earlier, the baud rate has been set low in order to reduce the chance of any problems being experienced due to interference. Now we have established that using SPI communication is possible and the circuit works as before we can start to increase the baud rate. Using our 16MHz clock we find we have the following baud rates which are theoretically possible:

SPI_CR1_BRDivisorSPI Frequency
00028 MHz
00144 MHz
01082 MHz
011161 MHz
10032500 KHz
10164250 KHz
110128125 KHz
11125662.5 KHz

A little experimentation is called for. Being ambitious I started with a clock frequency of 1MHz. This resulted in a flickering effect on the LED display. So, 1MHz is too ambitious, let’s start to reduce the frequency. I finally settled on 250 KHz as this allowed the circuit to function as before.

Conclusion

Using SPI master for data transmission was not as difficult as I originally thought. To make this application complete there are a few tasks to follow up on, namely:

  1. Receiving data over SPI
  2. Create the method to allow setting the dot correction values
  3. Transmitting buffers from a queue
  4. Minor tidying up of the timer control

The use of SPI here actually increased the time taken (597uS Bit-Banging c.f. 795uS for 250 KHz SPI) to reliably send the grey scale data to the TLC5940. I suspect that the time can be decreased if the circuit was taken from breadboard and put onto a PCB manufactured for the purpose. The breadboard for this circuit currently looks like this:

Bread Board And Flying Leads

Bread Board And Flying Leads

As you can see, there is a lot of opportunity for interference with all those flying leads.

While the time taken might have increased, the load on the microcontroller will have decreased as the SPI method is interrupt driven. The actual transmission is off-loaded to the microcontrollers dedicated circuitry.

As usual, the source code for this project is available to download.

How much would you pay?

June 3rd, 2013 • GeneralComments Off on How much would you pay?

One of the joys of being a hobbyist is the fact that you can take a project as far as you want to. Something really fun nearly always remains fun and does not make any money. As long as it’s a hobby it does not really matter.

So what do you do when you want to make money from your hobby. This is the question which Michael Ciuffo of ch00ftech found himself addressing with his QR Clock project earlier this year.

I’d check out The Slowest $380 I’ll Ever Make post for advice on the pros and cons of taking a hobby idea from concept to production.

Regular readers will know that I rarely post links to other peoples work but I feel that I should share this post as much of the post resonates with me. The Output Expander project was for me a great exercise as I had never taken a project from concept through to professional PCB manufacture. Having done this I was left with a simple question – could I make some extra money from this?

I think Michael’s post answers this question and it’s well worth reading.

C# SerialPort on the Raspberry Pi

May 19th, 2013 • Electronics, Software DevelopmentComments Off on C# SerialPort on the Raspberry Pi

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

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

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

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

Raspberry Pi Setup

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

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

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

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

Testing the Application

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

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

Raspberry PiFTDI Cable
GroundGround
RxTx
TxRx

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

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

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

mcs serial.cs
mono serial.exe

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

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

Serial Output in Putty

Serial Output in Putty

Conclusion

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

Making a Netduino GO! Module – Conclusion

May 7th, 2013 • Electronics, Netduino, Software Development, STM8Comments Off on Making a Netduino GO! Module – Conclusion

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

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

Completed Board

Completed Board


Not looking too bad if I say so myself.

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

Lessons Learned

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

Designing the Board

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

Prototyping

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

Schematic

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

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

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

    Schematic to Manufacture

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

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

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

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

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

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

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

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

    Improvised Test Point

    Improvised Test Point

    A good test point would have made this easier.

    How Long Did it Take

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

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

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

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

    Conclusion

    Well, that was a hectic few weeks.

    Did I enjoy it – YES!

    Would I recommend that you try it – YES!

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

    I suppose you may be interested in some downloads…

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

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

    May 6th, 2013 • Electronics, Netduino, STM83 Comments »

    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

    April 27th, 2013 • Electronics, Netduino, Software Development, STM8Comments Off on Making a Netduino GO! Module – Stage 5 – Enhancing the Drivers

    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 4 – Lay Out the Board

    April 20th, 2013 • Electronics, Netduino, STM8Comments Off on Making a Netduino GO! Module – Stage 4 – Lay Out the Board

    This series of posts will examine the activities required to take the concept of a module for the Netduino GO! through to production. So far in this series we have completed the following tasks:

    • Created a breadboard prototype
    • Linked the prototype on breadboard to the Netduino GO! with proof of concept software
    • Generated a schematic for a prototype PCB

    The next stage of the process is to convert the schematic into a PCB.

    This part of the process is the one which is totally new for me and so is the one which has the greatest chance of going wrong. This is where we will find out if my research is good.

    The Schematic

    Looking back at the previous post we note that we have the following schematic:

    Schematic

    Schematic

    PCB Layout

    The PCB layout process converts the schematic into a representation of the board which can be edited using the PCB layout tools. The layout can appear confusing as it is a layered version of the final board. Different colours map to the layers / artefacts on the board. It is this layering which can appear confusing at first.

    The layout process requires the following tasks to be completed:

    1. Conversion of the schematic into the ratsnest
    2. Reorganising the components in the ratsnest to their “final” position on the board
    3. Routing the tracks between the components
    4. Addition of additional artefacts such as text
    5. Design Rule Checks (DRC) and verification of the layout
    6. Production of the manufacturing files

    It is suggested that the above process is completed in order as each step adds new artefacts to the board.

    As noted earlier, the schematic and related files are being created using DesignSpark PCB. The processes being discussed are relevant to PCB manufacture in general.

    Making the Ratsnest

    The first step in the process is to convert the schematic into a PCB layout. Often this process creates a ratsnest of components and connections with no real layout:

    The ratsnest in DesignSpark’s PCB editor shows each component in the footprint it will occupy on the final board. The logical connections between the components are also shown as simple lines between the pads which will be used to mount the components. The lack of structure of the output gives the representation it’s name as it looks like a disorganised rats nest.

    Placing the Components

    The next step is to lay out the components out on the board. DesignSpark PCB converts the Schematic into a disorganised layout. The Output Expander board is a simple board and reorganising the layout should be simple. The layout can be broken down into the following functional units as follows:

    1. Connectors
    2. Shift registers
    3. Microcontroller

    The connectors should near to the edge of the board to allow the board to be connected to the Netduino GO! and external circuitry with ease.

    The shift registers and microcontroller can be placed anywhere on the board as we are looking at a low speed, simple board. Placement is more critical for more complex boards. For this board it is logical to place the shift registers near the output from the board as they will used to provide the 32 outputs for the board. It is also logical to place the microcontroller near the Netduino GO! connector as it is receiving instructions from the Netduino GO!.

    There are also a number of passive components on the board. These provide some signal filtering and power stabilisation. These components should be place as close to the chip they are supporting. For instance, each of the shift registers has a 100nf capacitor between power and ground. This capacitor provides a buffer for power spikes and it should be placed a close to the chip as possible. There are similar capacitors near the STM8S microcontroller.

    So the first step is to take the components and break them down into the logical groups. You can then deal with each group in turn.

    The following shows the start of the component placement:

    Components on PCB

    Components on PCB

    The major components are shown along with the connections between the components. This appears a little disorganised at the start as the yellow lines showing the connections between the components run in a straight line taking the most direct route.

    Routing the Tracks

    Routing is the process of placing copper connections between the components. So at this point you should have the components in their final resting place. If you have to move them later then you will disturb the routing. It is not too big a problem as you can always break the connection and then re-establish it with a new copper track.

    The next thing to consider is that for this simple board there are two types of tracks, power and signal. In DesignSpark PCB these are also broken down into two subcategories, nominal and minimum. Where possible I have always selected the nominal connection for both power and signal. You should also remember that it is possible it define your own track type.

    When connecting components DesignSpark PCB is reasonably intelligent and will take the connection type from the schematic and apply this to the PCB layout. By default this will be the nominal connection for either power or signal.

    Like many PCB layout packages, DesignSpark PCB provides an auto-router. In my experience these take a long time to run and does not always provide a complete board (i.e. they fail to completely route the board) and they also need a reasonable amount of computing power to complete. For a small and simple board like this one I have always found that manually routing the board is the preferred option.

    Routing also requires a change in the way of thinking about the board. PCB manufacture is a multi-layer process. The simplest for home manufacture is a single layer. This board can use two layers as this is relatively standard for low cost. Two and four layers are becoming common in the low cost prototype market. Larger numbers of layers are also possible but are currently too expensive and complex for the hobbyist. The simplicity of this board only merits a two layer board which also helps to keep the production cost lower.

    Time to start routing…

    After a while the board started to come together:

    Partially Routed

    Partially Routed

    The above image shows the partially routed board with some additional artefacts to give an indication how the final board will look. Some important points to note:

    1. Red traces are tracks which will appear on the top layer of the board.
    2. Cyan traces are tracks which are on the lower layer of the board.
    3. Thick tracks are used to carry the ground and power signals.
    4. Thin tracks carry signals between components.
    5. Small yellow circles in the tracks are vias (interconnections between the top and bottom layer of the board).
    6. Component outlines are shown in yellow. These represent the physical size of the component when it is mounted on the board. This will also appear on the top layer silkscreen.
    7. The green parts are represent the board outline and the mounting holes for the board.
    8. All of the changes in direction of a track are mitred rather than simple right angles.

    You can see that the board still contains some very thin yellow lines from the original ratsnest. These lines represent the connections which have not been routed yet. A quick check of the connections shows that all of these pads are connected to ground. These pads will become connected when the ground plane is added to the board.

    The final routing task is to add a ground plane to the top and bottom of the board.

    Top Layer With Ground PlaneTopLayerWithGroundPlane

    Top Layer With Ground PlaneTopLayerWithGroundPlane

    Adding Additional Artefacts

    Now the routing is complete we can return to the additional artefacts on the board. The first and possibly the most important is the board outline. The original outline in the image above was used to give an indication of the maximum ideal board size (10cm x 5cm). Most of the Netduino GO! modules released so far have had rounded corners. So the board outline was replaced by a board with rounded corners.

    The next check was to look at the silkscreen layers. These already contain the component outlines along with some names. Some useful additional information includes pin names for the connectors along with some information about the board – a name perhaps.

    A final note about the mounting holes. These should be placed on a 5mm x 5mm grid in order to achieve GO! certification. The holes should also be 3.1mm – 3.3mm in diameter.

    One limitation I found with DesignSpark PCB is the fact that you cannot place a logo/image in the silkscreen layer (or any other layer for that matter). This is a major limitation of the package. It appears that the solution is to create a font containing the image and then add text to the silkscreen using the font which has been created. For a package which is currently at version 5 seems to be a major omission.

    DRC and Verification

    The final step in the design process before going to manufacture is to verify the board and determine if the board can be manufactured successfully.

    Firstly, print out the board. Now double check the connections making sure that all of the pads are connected correctly. A print out is more useful to me as I can take it away from the screen and start to tick off the pads I believe to be connected correctly.

    Now for the DRC check. By default DesignSpark PCB has a set of design rules built in. These rules represent manufacturing parameters such as:

    • Track thickness
    • Minimum spacing between tracks
    • Minimum distance of a component from the board edge

    The list is much larger but you get the idea.

    I found that the values used for some rules was too cautious and would not let me route some tracks correctly. I had to change the default rules using values from the manufacturer I had decided to use. Rerunning the check with the new minimum values for track spacing allowed the board to pass DRC.

    Generate Manufacturing Files

    PCB manufacturers use Gerber files. The exact specification of the files used is determined by the chosen manufacturer. For this board we will need to generate a Gerber file in RS-274x format for the following layers:

    • Top layer: pcbname.gtl
    • Bottom layer: pcbname.gbl
    • Solder Stop Mask top: pcbname.gts
    • Solder Stop Mask Bottom pcbname.gbs
    • Silk Top: pcbname.gto
    • Silk Bottom: pcbname.gbo
    • NC Drill: pcbname.txt

    One thing to note is that there is nothing to represent the board outline. This has been added to the top silkscreen layer as requested by the manufacturer I have chosen.

    The Gerber files are simple text files which contain instructions for the CNC machines used in the manufacturing process.

    Now we have the Gerber files it is useful to check that they look right. Luckily there is an on-line Gerber viewer. Uploading your files will allow you to check that the image generated matches the design and layout.

    3D View

    A neat little feature of DesignSpark PCB is the ability to generate a 3D view of the board being made and to rotate the view. A quick check gives the following images for the final board:

    Top of Board in 3D

    Top of Board in 3D

    Note that the GND pin to the top right of the board has four tabs connecting it to the ground plane.

    The cube hovering above the board is the 3D model for the STM8S. A better model could be created, more along the lines of the 75HC595 shift registers. A task for another day…

    And rotating the board to view the underside we see the following:

    Bottom of Board in 3D

    Bottom of Board in 3D

    PCB Prototype Manufacturer

    The past 12 months have seen a number of PCB companies offer a low cost prototype PCB service. These services allow the production of a number of board (typically 10) starting as low as $10 for 10 5cm x 5cm boards. Some services even give you extra boards if you open source the hardware and hence allow them to add the board to their online shop.

    Companies offering this type of service include:

    The number of PCB options may be limited (i.e. silkscreen colour etc.) but this certainly makes the cost of production viable. For instance, a 5cm x 10 cm board costs less than $30 including shipping to the UK for 10 boards. Our module has been designed to just fit within the 5cm x 10 cm footprint.

    Another service for the hobbyist or prototype developer is that offered by Batch PCB. This company collects the production files from customers and creates a single panelised board containing one or more customer designs. This board is manufactured, the individual boards cut and extracted for shipping to the customer. By doing this the company can offer a low cost production service ($2.50 per square inch at the time of writing) for larger boards.

    There are a number of other companies offering low cost services for small runs. The companies quoted are ones I have noted over the past few months when I have been looking for low cost alternatives.

    Conclusion

    There will now follow a slight pause in the development of the hardware while the boards are produced somewhere in China. According to colleagues it typically takes about one week for manufacture and two weeks for shipping.

    This does not mean that development needs to stop. On the assumption that the prototype will work correctly we still have the following tasks which can be completed:

    • Order the components for mounting on the board
    • Develop the drivers

    In the next post we will look at developing the drivers for the board using the breadboard prototype as our test environment.

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

    April 12th, 2013 • Electronics, Netduino, STM81 Comment »

    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.