RSS

Archive for November, 2012

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

Monday, November 26th, 2012

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

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

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

  • Chris Walker
  • CW2

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

GoBus 1.0 Protocol

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

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

Enumeration

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

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

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

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

Data Transfer/Module Control

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

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

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

GPIO Interrupt

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

A Simple Module

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

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

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

Netduino Go Module Driver

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

Module ID

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

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

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

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

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

Initialise

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

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

AddFive Method

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

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

	_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
	_writeFrameBuffer[1] = CMD_ADD_FIVE;
	_writeFrameBuffer[2] = value;
	WriteDataToModule();
	while (!responseReceived && (retriesLeft > 0))
	{
		//
		//  We have written the data to the module so wait for a maximum 
		//  of 5 milliseconds to see if the module responds with some 
		//  data for us.
		//
		responseReceived = _irqPortInterruptEvent.WaitOne(5, false);
		if ((responseReceived) && (_readFrameBuffer[1] == GO_BUS10_COMMAND_RESPONSE))
		{
			//
			//  Module has responded so extract the result.  Note we should really
			//  verify the checksum at this point.
			//
			_writeFrameBuffer[0] = GO_BUS10_COMMAND_RESPONSE;
			_writeFrameBuffer[1] = CMD_GET_RESULT;
			WriteDataToModule();
			return(_readFrameBuffer[2]);
		}
		else
		{
			//
			//  No response within the 5ms so lets make another attempt.
			//
			retriesLeft--;
			if (retriesLeft > 0)
			{
				WriteDataToModule();
			}
		}
	}
	throw new Exception("AddFive cannot communicate with the Basic GO! module");
}

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

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

responseReceived = _irqPortInterruptEvent.WaitOne(5, false);


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

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

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

STM8S Module

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

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

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

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

      Function Table

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

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

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

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

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

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

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

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

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

      Buffers and GUIDs

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

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

      GoBus Interrupt

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

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

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

      Adding Functionality to the Module

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

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

      SPI Go Frame

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

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

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

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

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

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

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

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

      SPI Tx/Rx Interrupt Handler

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

      Connecting the Boards

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

      • STM8S103F3 TSSOP20 on a breadboard
      • STM8S Discovery

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

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

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

      Running the Code

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

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

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

      Observations

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

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

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

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

      SPI Timing Diagram

      SPI Timing Diagram

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

      Conclusion

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

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

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

      Source Code Compatibility

      SystemCompatible?
      STM8S103F3 (Breadboard)
      Variable Lab Protomodule
      STM8S Discovery

STM8S SPI Slave (Part 2)

Monday, November 19th, 2012

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

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

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

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

Hardware

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

SPR_SR_OVR – Overflow Indicator

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

SPI_SR_RXNE – Receive Buffer Not Empty

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

SPI_SR_TXE – Transmit Buffer Empty

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

Netduino Plus Software

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

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

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

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

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

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

STM8S SPI Slave

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

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

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

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

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

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

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

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

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

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

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

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

SPI Interrupt Service Routine

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

  • Buffer Overflow
  • Data received
  • Data transmission buffer empty

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

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

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

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

      SPI Slave Buffered output on Logic Analyser

      SPI Slave Buffered output on Logica Analyser

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

      Correctly synchronised SPI buffered output on the Logic Analyser

      Correctly synchronised SPI buffered output on the Logic Analyser

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

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

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

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

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

          Synchronising the Sender and Receiver

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

          Chip Select

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

          void InitialisePorts()
          {
              //
              //  Initialise Port D for debug output.
              //
              PD_ODR = 0;             //  All pins are turned off.
              PD_DDR = 0xff;          //  All pins are outputs.
              PD_CR1 = 0xff;          //  Push-Pull outputs.
              PD_CR2 = 0xff;          //  Output speeds upto 10 MHz.
              //
              //  Initialise Port B for input.
              //
              PB_ODR = 0;             //  Turn the outputs off.
              PB_DDR = 0;             //  All pins are inputs.
              PB_CR1 = 0xff;          //  All inputs have pull-ups enabled.
              PB_CR2 = 0xff;          //  Interrupts enabled on all pins.
              //
              //  Now set up the interrupt behaviour.
              //
              EXTI_CR1_PBIS = 2;      //  Port B interrupt on falling edge (initially).
          }
          

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

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

          This code performs two tasks:

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

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

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

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

          Conclusion

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

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

          Source Code Compatibility

          SystemCompatible?
          STM8S103F3 (Breadboard)
          Variable Lab Protomodule
          STM8S Discovery

STM8S SPI Slave Device

Wednesday, November 14th, 2012

For the next few posts I will be taking a look at SPI and how to use this to allow communication between two devices.

For some background reading I suggest that you visit Wikipedia and read the article on SPI. This post will assume that you are familiar with the material in that article.

In the first of the series we are going to be implementing a simple byte transfer between two devices, namely:

  • Netduino Plus
  • STM8S Discovery board

This scenario will require that SPI on the STM8S operates in slave mode as SPI on the Netduino Plus can only operate as a SPI master device. The Netduino family of products was chosen as the master because the SPI implementation is quick to setup and use. This means that we can be sure that any problems which arise during development are highly likely to be related to the STM8S code.

The problem definition is as follows:

  • Configure the Netduino Plus as a SPI master device
  • Send a repeated pattern of bytes over SPI to a listening slave device
  • Configure the STM8S to operate as a SPI slave device
  • Read the bytes from the SPI bus (MOSI) and send them back out on the bus (MISO)

We will also add some debugging code to allow us to connect a logic analyser to the STM8S and verify the data which is being received.

SPI Master – Netduino Plus Code

The initial version of this application will use a low bus speed for the SPI communication. By using a low speed we will reduce the influence of transmission errors and also allow the code to be debugged and logic errors eliminated without worrying too much about errors introduced through timing issues.

Our simple application looks like this:

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

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

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

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

The configuration of the SPI bus is as follows:

  • Chip select is digital pin 10
  • Chip select is low when the bus is active
  • Clock is active low (Clock Polarity – CPOL)
  • Data will be valid on the rising clock edge (Clock Phase – CPHA)
  • Clock frequency is 10Khz

It is important to note that the sampling settings must be duplicated on the slave device.

Once the SPI bus is configured, the application continuously loops outputting the bytes 0 to 254 on the SPI bus with a 200ms pause between each byte.

SPI Slave – STM8S

With the exception of the clock speed, we now need to configure the STM8S as a slave device using the same settings as the SPI master device.

The Registers

SPI_CR1_CPOL – Clock Polarity

The first setting we will consider is the clock polarity (CPOL). This is controlled by the CPOL bit in the CR1 register. This is defined as:

SettingDescription
0Clock is low when idle
1Clock is high when idle

The master has an active low clock.

SPI_CR1_CPHA – Clock Phase

The clock phase determines when the data is ready to be sampled, i.e. on the rising or falling clock edge.

SettingDescription
0Data is ready to be sampled on the rising edge of the clock
1Data is ready to be sampled on the falling edge of the clock

We will be sampling on the first clock transition, on the rising edge.

SPI_CR1_SPE – Enable or Disable SPI

This register determines if SPI is enabled or disabled. Setting this register to 0 disables SPI, setting it to 1 enables SPI.

SPI_ICR_TXIE and SPI_ICR_RXNE – Interrupt Enable/Disable

These two registers determine if the SPI interrupts will be triggered on transmit (TXIE) or receive (RXIE). Setting a bit to 0 will disable the interrupt, setting it to 1 will enable the interrupt.

SPI_DR – Data Register

The data register is used in two contexts, when data has been received and to transmit data. Reading this register will retrieve data from the receive buffer. Setting this register will load the specified value into the transmit buffer.

SPI_SR_RXNE – Receive Buffer Not Empty

This bit in the status register indicates if the receive buffer is empty. You can check this value before you read the SPI_DR register to determine if there is any data waiting to be read.

STM8S Code

The first thing we will need to do is to initialise the SPI bus matching the settings of the SPI master:

void InitialiseSPIAsSlave()
{
    SPI_CR1_SPE = 0;                    //  Disable SPI.
    SPI_CR1_CPOL = 0;                   //  Clock is low when idle.
    SPI_CR1_CPHA = 0;                   //  Sample the data on the rising edge.
    SPI_ICR_TXIE = 1;                   //  Enable the SPI TXE interrupt.
    SPI_ICR_RXIE = 1;                   //  Enable the SPI RXE interrupt.
    SPI_CR1_SPE = 1;                    //  Enable SPI.
}

This code not only matches the master settings but also enables the generation of interrupts for transmit empty and receive not empty. These interrupts are handled by the following code:

#pragma vector = SPI_TXE_vector
__interrupt void SPI_IRQHandler(void)
{
    if (SPI_SR_RXNE)
    {
        unsigned char byte;
        byte = SPI_DR;          //  Read the byte we have received.
        SPI_DR = byte;          //  Now transmit the byte.
        //
        //  Output some debug information.
        //
        OutputStatusCode(SC_OK);
        BitBang(byte);
    }
}

This method checks the SPI_SR_RXNE flag to determine if the receive buffer is not empty. If there is data ready for processing then the data is retrieved and then transmitted back to the master.

Note that this method offers a naive approach to sending and receiving data, something we will overcome in subsequent posts.

We have also provided two methods for debugging, one will output a status code; the second will output a single byte by bit banging the data using two pins on an output port. We will need to use these methods with care when we start to look at higher transmission speeds. The two debug methods need to operate at speeds which allow the interrupt service routine to complete before the next interrupt is ready to be generated.

Hardware Setup

The connections between the two devices are straight forward. The following pins should be connected:

Pin DescriptionNetduino PinSTM8S Discovery Pin
MISOD12PC7
MOSID11PC6
SCLKD13PC5
Chip SelectD10PE5
GNDGNDGND

In addition to the above connections between the two boards we have three pins defined for debugging/diagnostics.

Port D, pin 2 will be used to output a status code. The code will be output as a series of high/low transitions.

Port D, pins 4 (clock) and 5 (data) will output debug/diagnostic data. This will be output in a similar form to the SPI data being transmitted on the SPI bus. This form has been chosen as it allows a logic analyser to be used to interpret the data.

Results

If we connect the two devices and hook up a logic analyser we get output similar to the following:

Logic Analyser Output

Logic Analyser Output

The top four traces represent the data which is being transmitted by the Netduino and the STM8S on the SPI bus along with the clock and select control signals. The labelling on the traces indicate which signal is being shown.

The traces labelled 4 and 5 show the data which has been output from the BitBang method.

The final trace shows the status code.

If we read this trace from left to right we can see that the Netduino master output the byte 213 on MOSI. At the same time, the STM8S is sending the byte 212 back to the master on the MISO line. This difference of one is caused by the fact that the transmission from the STM8S is always one behind the transmission by the master to the slave.

Traces 4 and 5 confirm that the STM8S has in fact received the byte 213.

Trace 6 shows that we have a status code of 1 – a single pulse – showing that the application has not detected an error condition.

We see that the first thing that happens is that the transmission of data starts on the MOSI and MISO lines simultaneously. When the data transmission has completed we have in interrupt generated and the status code of 1 is output. Finally the application copies the data received onto the diagnostic output.

Conclusion

This application represents the first step on the road to building a faster application capable of transmitting and receiving greater amounts of data.

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

Source Code Compatibility

SystemCompatible?
STM8S103F3 (Breadboard)
Variable Lab Protomodule
STM8S Discovery