RSS

Posts Tagged ‘Electronics’

STM32 – Simple GPIO and Some Flashing LEDs

Saturday, March 23rd, 2013

When starting with a new development board I always revisit the fundamentals like GPIO. Doing this helps establish that the development tools are working and gives a basic library for future development. So with this post we will be looking at GPIO on the STM32 Discovery board. When Rediscovering the STM32 Discovery Board I mentioned that I would need to look at using either the standard peripheral library provided by ST or to use a lower level method of controlling the board. After due consideration I have decided to continue with the lower level access. Whilst this may be more effort it does lead to a better understanding of what is actually happening at the low level.

Our objective today will be to toggle a GPIO pin (Port D, pin 0) generating a square wave. The result will be something similar to the Simple GPIO post for the STM8S.

STM32F4 Discovery Board

The STM32F4 Discovery board is a low cost development board for hobbyists, beginners and experienced users. The board uses the STM32F407VGT6 and hosts a number of sensors including digital accelerometer, push buttons, microphone and DAC. It also contains four built in LEDs which can be used to give feedback to the developer and breakout pins for the GPIO ports.

STM32F4 Discovery Board

STM32F4 Discovery Board

Each of the GPIO pins can be used for simple input/output of digital signals. The majority of the pins are also mapped to one or more alternative functions. For instance, Port C, pin 3 is also mapped to the following alternative functions:

  • SPI2 MOSI
  • I2S2 SD
  • OTG HS ULPI NXT
  • ETH MI TX Clk
  • EVENTOUT

For the remainder of this post we will restrict ourselves to the GPIO functionality of the chip and demonstrate the following:

  • Output a digital signal on a single GPIO pin
  • Flash one of the LEDs on the board

The alternative functionality will be considered in future posts.

The functionality of the 16 pins on each of the GPIO ports is controlled by a number of registers. The description of each of the registers can be found in RM0090 Reference Manual for the STM32 chip. This manual can be downloaded from STs web site.

GPIO Registers

Each STM32 chip has a number of ports each of which has 16 pins. As already noted, each of these pins has a number of functions, the most basic of which is digital input/output. The functionality and state of each port is controlled by a number of GPIO registers:

  • Four configuration registers
  • Two data registers
  • One set/reset register
  • One locking register
  • Two alternative function registers

An overview of the registers follows; a fuller description of the registers can be found in section 7 of RM0090 Reference Manual (Revision 4).

When referring to the registers, the reference manual uses the following notation:

GPIOx_Function

Where x is replaced by the port to be used. For instance, GPIOx_MODER refers to the generic mode register for a GPIO port. A specific instance of this would be GPIOD_MODER. This refers to the mode register for port D. Normally, x is in the range A to I and is defined by the specific version of the STM32 being used in the project.

Configuration Registers

The STM32 has four configuration registers for each of the ports.

  • Port mode register – GPIOx_MODER
  • Output type register – GPIOx_OTYPER
  • Speed register – GPIOx_OSPEEDR
  • Pull-up/Pull-down register – GPIOx_PUPDR

Each of these registers is 32-bits wide although not all of the bits are used in all of the registers.

Port Mode Register – GPIOx_MODER

This 32 bit register holds 2 bit values which defines the operation mode. The modes allowed are:

Bit ValuesDescription
00Input
01General purpose output
10Alternate function
11Analog

In general, the default reset value for this register is 0. Some of the ports are reset to non-zero values as the pins in the port have special meanings (debugging pins etc). The rest values are:

PortReset Value
A0xA800 0000
B0x0000 0280
All others0x0000 0000

The bits in the register are read/write and are in consecutive pairs. So bits 0 & 1 define the mode for pin 0, bits 2 & 3 define the mode for pin 1 etc.

Output Type Register – GPIOx_OTYPER

This is a 32-bit register but only the lower 16 bits are used. The top 16 bits are reserved and should not be used. This register uses a single bit for each pin to define the output type of the pins in the port. So, bit 0 defines the output type for pin 0, bit 1 for pin 1 etc.

Bit ValueDescription
0Push/pull output
1Open drain output

The default reset value for all of the GPIOx_TYPER registers is 0x0000 0000 meaning that all ports are configured for push/pull output.

Speed Register – GPIOx_OSPEEDR

This 32-bit register uses all of the bits in the register in pairs (similar to the port mode register). The bits define the output speed of the port. The bit pairs have the following meaning:

Bit ValuesDescription
002 MHz Low speed
0125 MHz Medium speed
1050 MHz Fast speed
11100 MHz High speed on 30pF
80 MHz High speed on 15 pF

The reset value for these registers is 0x0000 00C0 for port B and 0x0000 0000 for all other registers.

Pull-up/Pull-down register – GPIOx_PUPDR

This 32-bit register uses all of the bits in the register in pairs (similar to the port mode register). The bits define pull-up / pull-down resistor mode for the pins on a port. The bit pairs have the following meaning:

Bit ValuesDescription
00No pull-up or pull-down resistor
01Pull-up resistor
10Pull-down resistor
11Reserved

The reset values for the registers are:

PortReset Value
A0x6400 0000
B0x0000 0100
All others0x0000 0000

Data Registers

The two data registers for each port indicate the input/output state of the port. Each of the registers are 32-bit registers but only the lower 16 bits are used. The top 16 bits are reserved and should not be used.

  • Input data register – GPIOx_IDR
  • Output data register – GPIOx_ODR

Input data register – GPIOx_IDR

The input data register indicates if a high or low signal has been applied to the pin within a port. The register is read-only and the whole word should be read.

Bit ValueDescription
0High logic value
1Low logic value

As with the output type register – GPIOx_OTYPER, the bits in the register map onto the pins in the port (bit 0 maps to pin 0, bit 1 to pin 1 etc.).

The reset value for all of these registers is 0x0000 XXXX where X indicates that the bits are unknown as they will be defined by the input signals applied to the pins are reset.

Output data register – GPIOx_ODR

The output data register allows the program to change the output state of a pin within the port. This is a 32-bit register and only the lower 16 bits are used. The top 16 bits are reserved and should not be used. The output states are defined as:

Bit ValueDescription
0High logic value
1Low logic value

As with the output type register – GPIOx_OTYPER, the bits in the register map onto the pins in the port (bit 0 maps to pin 0, bit 1 to pin 1 etc.). The bits in the lower 16 bits of the register are all read/write bits.

The reset value for all of these registers is 0x0000 0000.

A more effective way of setting/resetting a bit in the output register is to use the bit set/reset register.

Set/reset register

The set/reset register sets or resets a single bit in the output data register.

Bit Set/Reset Register – GPIOx_BSSR

This is a 32-bit register and it is split into two halves, the lower 16 bits are used for setting a bit and the upper 16 bits are used for resetting a bit. This is a write only register; reading this register will return 0x0000.

Setting a bit in the lower 16-bits of this register will cause the corresponding bit in the output data register to be set to 1.

Similarly, setting a bit in the upper 16-bits of this register will cause the corresponding bit in the output data register to be set to 0.

For the lower 16-bits the mapping is simple, bit 0 in GPIOx_BSRR maps to bit 0 in GPIOx_ODR etc. For the upper 16-bits, bit 16 of the GPIOx_BSRR maps to bit 0 of the GPIOx_ODR; bit 17 of GPIOx_BSRR maps to bit 1 of GPIOx_ODR etc.

So why provide the set/reset functionality when you have the output data registers? The set/reset register provide an atomic method of setting or resetting an output pin on a port. When using the output data registers, the compiler will have to generate a number of statements in order to set or reset a pin. This can allow an interrupt to be processed when part way through the set/reset operation. By using this register it is possible to set or reset a pin using a single instruction. This means that the operation will complete before the interrupt can be processed.

Configuration Lock Register

This register is used to lock the configuration of the port until the next reset of the chip.

Configuration Lock Register – GPIOx_LCKR

The register is a 32-bit register but only 17 bits are actually used. The lower 16 bits indicate which bit of the port configuration is locked. So setting bit 0 of the register will lock the configuration of pin 0 etc.

The final bit used is bit 16 in the register (known as the Lock Key – LCKK); this bit determines the lock status of the port; a zero indicates that no lock is applied whilst a 1 indicates that the port configuration is locked.

In order to lock the configuration of a port the configuration lock write sequence must be followed. This will prevent accidental locking of the port configuration. The algorithm for locking the configuration is as follows:

  • Set bits 0 through 15 of the register indicating which pins should have their configuration locked
  • Set LCKK to 1 and write the value into the register
  • Set LCKK to 0 and write the value into the register
  • Set LCKK to 1 and write the value into the register
  • Read the lock bit of the port

It is important to note that the values in bits 0 through 15 should not change whilst following the above sequence. Should they change or any error occur then the lock will be aborted.

The final step, reading the lock bit, confirms the success or failure of the operation.

Alternative function registers

The alternative function registers allow the configuration of the port to change from simple GPIO to an alternative function such as SPI MOSI. These registers are noted here but will not be discussed further as they will be covered in subsequent posts.

C Definitions and Some Typedefs

The register in the STM32 microcontroller have been mapped onto specific memory locations within the valid address space of the STM32. These memory locations are defined in the various header files for the microcontroller. For instance, the following type definition has been taken from the header file stm32f4xx.h:

typedef struct
{
  __IO uint32_t MODER;    /*!> GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!> GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!> GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!> GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!> GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!> GPIO port output data register,        Address offset: 0x14      */
  __IO uint16_t BSRRL;    /*!> GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!> GPIO port bit set/reset high register, Address offset: 0x1A      */
  __IO uint32_t LCKR;     /*!> GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!> GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

This header file can be found in the Standard Peripheral Library as supplied by ST Microelectronics.

A careful examination of the members of the struct within the typedef reveals that with one exception, the members of the struct all map directly onto the registers we have been discussing above. The one exception is the BSSR register. This has been split into two components BSSRL and BSSRH. This split nicely maps to the difference in functionality of the two halves of the register, namely Setting and resetting of individual bits in the output data register.

Locating A GPIO in Memory

Once we have the layout of the registers (as defined in the struct above), we simply need to map the structure onto the right location in memory. A little further digging in the header file leads us to the following #define statements:

#define GPIOD             ((GPIO_TypeDef *) GPIOD_BASE)

#define GPIOD_BASE        AHB1PERIPH_BASE + 0x0C00)

#define APB1PERIPH_BASE   PERIPH_BASE

#define PERIPH_BASE       ((uint32_t) 0x40000000) /*!< Peripheral base address in the alias region                                */

Following the chain of #define statements leads us to the the conclusion that the registers for GPIOD are located at memory location 0x40000C000. These definitions mean that we should be able to access individual registers with statements such as:

GPIOD->MODER = 0x01;    // Set Pin 0 of Port D to input.

A Practical Demonstration

After all this theory we must be ready for some coding. In this section we will look at creating two small applications:

  • GPIO Toggle – toggling a single pin on a GPIO port
  • Blinky – Flashing a single LED

The first of the two applications will simply toggle a single GPIO pin as fast as possible using the default reset state of the microcontroller. Note that an oscilloscope will be required in order to prove that the application is working correctly

The second application will make use of the on board LEDs. This will require a little more coding in order to provide a delay between turning the LED on and off in order to make the flashing visible to the naked eye.

Application 1 – Toggling A GPIO Pin

This application is simple and consists of two parts:

  • Initialisation
  • A loop which toggles the GPIO pin

The full application looks like this:

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

int main()
{
    //
    //  Initialise the peripheral clock.
    //
    RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;
    //
    //  Initilaise the GPIO port.
    //
    GPIOD->MODER |= GPIO_Mode_OUT;
    GPIOD->OSPEEDR |= GPIO_Speed_25MHz;
    GPIOD->OTYPER |= GPIO_OType_PP;
    GPIOD->PUPDR |= GPIO_PuPd_NOPULL;
    //
    //  Toggle Port D, pin 0 indefinitely.
    //
    while (1)
    {
        GPIOD->BSRRL = GPIO_Pin_0;
        GPIOD->BSRRH = GPIO_Pin_0;
    }
}

One thing to note from the above code is that we need to initialise the peripheral clocks (this should be taken as read for the moment and will be covered in future posts). When initialising the GPIO port we should take into consideration the default clock speed of the Discovery board after reset. By default this is 8 MHz and so we should set the speed of the output port accordingly. This leads to a configuration for pin 0 of output with a maximum speed of 25 MHz, push pull with not pull-up or pull-down resistors.

Once initialised we enter the main program loop. Here the application uses the set/rest registers to toggle pin 0. This shows how the 32-bit register is split into two, one for the set and one for the reset operation.

If we create a simple STM32 project using IAR, compile and then deploy this application we get the following output on the oscilloscope:

STM32 Scope Output

STM32 Scope Output

And there we have it, our first application deployed to the STM32 Discovery board.

Blinky – Flashing A Single LED

This second example is really aimed at those who do not have access to an oscilloscope. Here we will slow down the output of the GPIO port and flash one of the built in LEDs.

The STM32F4 Discovery board has four built in LEDs which can be accessed by setting/resetting pin 12 through 15 on port D. The LED colours are:

Pin NumberColour
12Green
13Orange
14Red
5Blue

Using the code in the previous example as a starting point we find ourselves with an application which looks something like this:

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

#define LED_GREEN      12
#define LED_ORANGE     13
#define LED_RED        14
#define LED_BLUE       15

int main()
{
    int pin = LED_RED;
    uint32_t mode = GPIO_Mode_OUT << (pin * 2);
    uint32_t speed = GPIO_Speed_100MHz << (pin * 2);
    uint32_t type = GPIO_OType_PP << pin;
    uint32_t pullup = GPIO_PuPd_NOPULL << (pin * 2);
    //
    //  Initialise the peripheral clock.
    //
    RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;
    //
    //  Initilaise the GPIO port.
    //
    GPIOD->MODER |= mode;
    GPIOD->OSPEEDR |= speed;
    GPIOD->OTYPER |= type;
    GPIOD->PUPDR |= pullup;
    //
    //  Toggle the selected LED indefinitely.
    //
    int index;
    while (1)
    {
        GPIOD->BSRRL = (1 << pin);
        for (index = 0; index < 500000; index++);
        GPIOD->BSRRH = (1 << pin);
        for (index = 0; index < 500000; index++);
    }
}

Deploying this application should flash the selected LED just below the STM32 microcontroller on the STM32F4 Discovery board.

Conclusion

This post presented two simple applications which toggle GPIO lines on Port D. Using these techniques and the additional information in the register descriptions you should be able to extend the application above to control digital devices such as shift registers etc.

Setting up GCC for the STM32F4 Discovery Board

Friday, March 8th, 2013

A few days ago I wrote this post about the STM32 Discovery board and my experiences setting this board up with the IAR Embedded Workbench Kickstarter for ARM. In that post I mentioned that there are some free development tools available for this board, namely the GCC tool chain. I was going to leave these tools until I reached the point where I needed to remove the code size restriction which is in place for the IAR toolset.

Well I could not resist investigating this tool chain further.

Installing the Software

A quick visit to Google lead me to Hussam Al-Hertani’s blog where there are a number of articles which discuss setting up the GCC tool chain for the STM32F0 Discovery board. These three articles provide a comprehensive overview of the steps you need to follow:

Also included on the blog is an overview of the GCC compilation process.

The two boards (STM32F0 and STM32F4) should be similar and so I decided to give the above a go and see if I could use the GCC tools with the STM32F4 Discovery board.

Installing the Tools

The installation process went reasonably well with only a couple of issues.

Part 1 – Setting up the GCC ARM Toolchain

The installation of the tools went according to the blog until we reached the instructions to compile the sample application. The main problem here was that the application would not compile due to missing header files. This problem was caused by the differences in the directory names and structure of the firmware libraries. The only changes required were to the include and library variables in the makefile.

Part 2 – Setting up the Eclipse IDE

Setting up Eclipse was completed without any issues.

Part 3 – Setting up Debugging with the Eclipse IDE (OpenOCD 0.6.x)

Setting up OpenOCD and connecting this to Eclipse was also straightforward.

Creating A New Example

Remember this code:

GPIO_InitTypeDef  GPIO_InitStructure;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
int flag = 0;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &GPIO_InitStructure);
while (1)
{
    if (flag)
    {
        GPIO_SetBits(GPIOD, GPIO_Pin_0);
        flag = 0;
    }
    else
    {
        GPIO_ResetBits(GPIOD, GPIO_Pin_0);
        flag = 1;
    }
}

In order to generate a new example we start with the code we have compiled and working. There are very few changes required. Theses are main made in the make file. We remove the files which are not required and then verify that the libraries and include files can be accessed by the compiler.

The final change is to replace the code in main.c with our code above.

Compilation and deployment from within Eclipse went as expected and the application ran with no issues.

How About Debugging?

With Eclipse open and the above project loaded I set a breakpoint on the first line of the application. Starting the debugger stopped the application at the first line of code – things are looking good. Single stepping also appeared to look as though it was working well at first. A few lines further on and this is where the issues started. The debugger and the line of source code started to get out of step. The IDE would skip a line of code in a consistent manner. It looks like the debugger information is slightly out of sync with the breakpoints. After spending a little time looking at this I found that the original makefile I have been using had the optimisation level set to -Os. The debugger/IDE started to behave as expected when I changed this to -O0.

Conclusion

The installation of the GCC development environment was simple enough and the instructions on Hertaville are comprehensive and well worth visiting. It is always good to have a free unlimited development environment.

Change Log:

  • 10th March 2013: Added additional information about the optimisation flag in the build file.

Rediscovering the STM32 Discovery Board

Tuesday, March 5th, 2013

About a year ago I started to look at alternatives to the Netduino boards and came across the STM32 Discovery board. I spent a little time with this board and wrote a simple application which toggled a GPIO pin – the electronic equivalent of Hello, world. I have had very little time to spend with this board since then but now I feel it is time to revisit the STM32 having spent some time cutting my teeth on the STM8S.

Development Tools

I’m a hobbyist and this means I do not have a vast amount of money to spend on development tools and equipment. A quick survey of the market gives a few options for development tools (software). The prices put the majority if these out of the question for the home developer who does not intend to recover the costs. There are still a number of options open to us by using the restricted versions of the professional tools. Several of the major players have released limited editions of the full development suite. The normal seems to be restricting the amount of code the development environment can deal with before you are required to purchase the software. I have had a look at the following tools (presented in no particular order) over the past few days:

  • Atollic TRUEStudio
    Free Lite edition with a code restriction of 32K on the Cortex-M4
  • IAR Embedded Workbench
    Free kick starter edition available with a code limit of 32K
  • Keil MDK
    No free version seems to be available and the site has a Quote button but no pricing.
  • GNU C/C++ with Eclipse IDE
    Free with no code restriction
  • TASKING
    No free edition available

A 32K code restriction does not seem unreasonable for the hobbyist so I decided to start with the IAR Kickstarter edition as I had a really good experience with the STM8S Kickstarter edition of the same development environment. I suppose the major driver was the fact that I will not have to learn a whole new series of key strokes in order to compile/debug as the IDE is the same as the STM8S version of the software which I have been using for nearly a year now.

Installation

Installation of the development environment was simple. The setup weighs in at a hefty 913 MBytes. Running the setup was painless and took only a few minutes.

Testing the installation

To test the deployment I decided to compile one of ST’s own example applications. These can be found in the firmware package which can be downloaded from ST’s web site. At the time of writing the latest version of the firmware was version 1.1.0. When you unzip the firmware you will find a Project directory which contains a number of samples including a Demonstration example. If you open this directory you will find a number of sub directories containing the projects for the various development tools available. We are interested in the EWARM directory as this contains the IAR project.

Opening the workspace STM32F4-Discovery_Demo.eww, compiling and deploying the application was simple. Just make sure that you have the STM32 Discovery board plugged into your computer using a mini USB connector through CN1 on the board.

Once deployed the IDE will halt program execution just before the main method is called. Pressing F5 will start the application running. At this point you should see the LEDs on the main board light up in a chase style pattern.

Template Application

The demonstration example shows how to use many of the development board features including the sensors, sound generation and the USB capabilities. The application therefore contains a number of features which we will/may not be using in our applications. I decided to strip the code down to a very basic application which can be used as a starting point for future application development. In order to prove that the template work and that we have not removed too much code we will recreate the previous application which toggled a GPIO pin.

There is a certain amount of startup code required in order to configure the STM32 and put it into a state where is can run an application. The code for this appears to be in the following files within the project:

  • startup_stm32f4xx.s
  • stm32f4xx_it.c
  • system_stm32f4xx.c

The code in these file set up the interrupt tables and system clocks before passing control the main.

In order to ensure that I took as much of the configuration as possible over from the demonstration program I started with a copy of the contents of the entire directory and then started to remove files. Doing this would ensure that the project options were preserved.

To see some output we will need to add some code to output a signal on the scope. This application will toggle pin 0 on port D in a loop. The main program becomes:

GPIO_InitTypeDef  GPIO_InitStructure;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
int flag = 0;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOD, &amp;GPIO_InitStructure);
while (1)
{
    if (flag)
    {
        GPIO_SetBits(GPIOD, GPIO_Pin_0);
        flag = 0;
    }
    else
    {
        GPIO_ResetBits(GPIOD, GPIO_Pin_0);
        flag = 1;
    }
}

In order to use the GPIO pins we need to compile in the GPIO standard library. Specifically stm32f4xx_gpio.c and stm32f4xx_rcc.c. These are probably better added as a group so we add a group and then the files. This will leave the project structure looking something like the following:

ProjectStructureInIAR

Compiling the application gave a missing method/preprocessor directive, namely assert_param. I have come across this in the past. This method validates the parameters passed into the methods in the standard library methods. A quick addition of the following to main.h should make the code compile:

void assert_param(void *x) {}

Compile this and we get a whole lot of errors but at least it compiles. Strictly speaking this needs resolving but I still have to decide if long term I will be using the standard library or if I will be following another path. I remember getting burned by the STM8S version of the library. A decision for another day, for now I’ll continue with the template and the test application.

Saving all of this and then running the program with the scope hooked up to Port D, Pin 0 gives the following output on the scope:

STM32GPIOToggleUsingSTDLibrary

My only concern was the frequency of the output. The processor is supposed to be running at 168 MHz. I would have expected something higher than the 417 Khz I was getting.

A quick delve into the standard library and we find that the code which sets and resets the GPIO stat is actually simple. There are the mandatory calls to the assert_param method/preprocessor directive followed by a simple setting of a value. We can substitute the calls in the main loop with the actual code. The main loop now looks something like this:

while (1)
{
    if (flag)
    {
        GPIOD->BSRRL = GPIO_Pin_0;
        flag = 0;
    }
    else
    {
        GPIOD->BSRRH = GPIO_Pin_0;
        flag = 1;
    }
}

Deploying and running this application gives a lot more respectable output:

STM32GPIOToggle

As you can see, we are getting a little over 4.1 MHz.

Conclusion

I now have a small template STM32 project which I can call upon at any time to give me a starting point for development with the STM32 Discovery board. I still need to work on the assert_param issue but only if I am to carry on working with the standard library.

If you are interested in using this template then feel free to download the code. You may need to make some adjustments to the default paths for the include directories. To do this:

  • Right click on the project name (Test STM32 – Debug)
  • Select Options…
  • Now select C/C++ Compiler
  • Scroll to the right until you find the Preprocessor tab
  • Enter the paths for your include directories

A new year, a new journey. Let’s see where this takes us.

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

Monday, November 26th, 2012

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

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

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

  • Chris Walker
  • CW2

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

GoBus 1.0 Protocol

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

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

Enumeration

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

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

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

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

Data Transfer/Module Control

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

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

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

GPIO Interrupt

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

A Simple Module

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

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

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

Netduino Go Module Driver

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

Module ID

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

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

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

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

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

Initialise

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

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

AddFive Method

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

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

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

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

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

responseReceived = _irqPortInterruptEvent.WaitOne(5, false);


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

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

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

STM8S Module

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

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

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

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

      Function Table

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

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

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

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

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

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

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

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

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

      Buffers and GUIDs

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

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

      GoBus Interrupt

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

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

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

      Adding Functionality to the Module

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

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

      SPI Go Frame

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

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

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

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

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

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

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

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

      SPI Tx/Rx Interrupt Handler

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

      Connecting the Boards

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

      • STM8S103F3 TSSOP20 on a breadboard
      • STM8S Discovery

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

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

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

      Running the Code

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

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

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

      Observations

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

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

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

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

      SPI Timing Diagram

      SPI Timing Diagram

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

      Conclusion

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

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

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

      Source Code Compatibility

      SystemCompatible?
      STM8S103F3 (Breadboard)
      Variable Lab Protomodule
      STM8S Discovery

STM8S SPI Slave (Part 2)

Monday, November 19th, 2012

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

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

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

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

Hardware

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

SPR_SR_OVR – Overflow Indicator

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

SPI_SR_RXNE – Receive Buffer Not Empty

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

SPI_SR_TXE – Transmit Buffer Empty

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

Netduino Plus Software

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

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

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

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

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

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

STM8S SPI Slave

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

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

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

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

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

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

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

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

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

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

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

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

SPI Interrupt Service Routine

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

  • Buffer Overflow
  • Data received
  • Data transmission buffer empty

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

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

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

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

      SPI Slave Buffered output on Logic Analyser

      SPI Slave Buffered output on Logica Analyser

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

      Correctly synchronised SPI buffered output on the Logic Analyser

      Correctly synchronised SPI buffered output on the Logic Analyser

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

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

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

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

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

          Synchronising the Sender and Receiver

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

          Chip Select

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

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

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

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

          This code performs two tasks:

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

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

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

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

          Conclusion

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

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

          Source Code Compatibility

          SystemCompatible?
          STM8S103F3 (Breadboard)
          Variable Lab Protomodule
          STM8S Discovery

The Way of the Register Source Code Update

Sunday, October 7th, 2012

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

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

Source Code

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

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

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

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

Directory Layout

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

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

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

main.c

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

Discovery, Protomodule and STM8S130F3 Directories

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

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

Single Conversion ADC on the STM8S

Monday, September 17th, 2012

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

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

In order to do this we will need the following:

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

The algorithm we will be using is as follows:

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

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

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

ADC Features

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

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

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

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

The Registers

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

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

ADC_CR1_ADON – ADC On/Off

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

ADC_CSR_CH – Channel Selection

The CH flag determines the channel which should be converted.

ADC_CR2_ALIGN – Data Alignment

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

ADC_DRH/L – Conversion Result

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

ADC_CSR_EOCIE – Enable ADC Interrupts

Turn the ADC interrupts on / off.

ADC_CSR_EOC – End of Conversion

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

Unused Registers

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

Hardware

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

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

LED Output

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

Potentiometer

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

Software

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

Timer 2 – Start Conversions

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

void SetupTimer2()
{
    TIM2_PSCR = 0x05;       //  Prescaler = 32.
    TIM2_ARRH = 0xc3;       //  High byte of 50,000.
    TIM2_ARRL = 0x50;       //  Low byte of 50,000.
    TIM2_IER_UIE = 1;       //  Enable the update interrupts.
    TIM2_CR1_CEN = 1;       //  Finally enable the timer.
}

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

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

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

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

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

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

Timer 1, Channel 4 – PWM Signal

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

void SetupTimer1()
{
    TIM1_ARRH = 0x03;       //  Reload counter = 1023 (10 bits)
    TIM1_ARRL = 0xff;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    TIM1_CR1_DIR = 1;       //  Down counter.
    TIM1_CR1_CMS = 0;       //  Edge aligned counter.
    TIM1_RCR = 0;           //  Repetition count.
    TIM1_CCMR4_OC4M = 7;    //  Set up to use PWM mode 2.
    TIM1_CCER2_CC4E = 1;    //  Output is enabled.
    TIM1_CCER2_CC4P = 0;    //  Active is defined as high.
    TIM1_CCR4H = 0x03;      //  Start with the PWM signal off.
    TIM1_CCR4L = 0xff;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
    TIM1_CR1_CEN = 1;
}

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

System Clock

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

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

GPIO – Debug Signals

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

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

ADC

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

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

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

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

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

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

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

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

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

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

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

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

Main Program Loop

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

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

Results

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

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

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

Conclusion

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

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

Source Code Compatibility

SystemCompatible?
STM8S103F3 (Breadboard)
Variable Lab Protomodule
STM8S Discovery

Timer 1 Counting Modes

Friday, September 14th, 2012

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

  • counting modes
  • repetition counter
  • update/overflow events

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

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

Test Environment

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

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

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

Logic Analyser Output

Logic Analyser Output

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

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

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

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

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

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

The Registers

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

  • TIM1_CR1_DIR – Counter Direction
  • TIM1_RCR – Repetition Counter

TIM1_CR1_DIR – Counter Direction

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

TIM1_RCR – Repetition Counter

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

Software

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

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

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

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

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

//--------------------------------------------------------------------------------
//
//  Setup Timer 2 to generate a 40 Hz interrupt based upon a 2 MHz timer.  This
//	will result in a signal with a frequency of 20Hz.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x00;       //  Prescaler = 1.
    TIM2_ARRH = 0xc3;       //  High byte of 50,000.
    TIM2_ARRL = 0x50;       //  Low byte of 50,000.
    TIM2_IER_UIE = 1;       //  Enable the update interrupts.
    TIM2_CR1_CEN = 1;       //  Finally enable the timer.
}

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

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

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

Five Pulses

Five Pulses

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

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

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

Timer 1 Overflow Handler

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

Timer 2 Overflow Handler

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

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

Conclusion

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

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

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

As always, the source code is available for download.

Source Code Compatibility

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

Single Pulse Generation with the STM8S

Monday, September 3rd, 2012

I have recently been looking at using a sensor which uses a one-wire communication protocol. The protocol uses a single pulse of a defined length to trigger the unit to send the sensor reading back down the same wire. This lead me on to thinking about how I could achieve this, the results of which are documented here.

Whilst the main purpose of the code we will be developing in this post remains the same, i.e. to produce a single pulse of a defined length, I felt it important to show the two fundamental ways in which this can be achieved:

  • Interrupts and GPIO
  • Timers

Much of the first method, using interrupts and GPIO signals is a relatively straight forward case of modifying one of the previous examples, namely Using Timers on the STM8S.

The second method is more interesting as we look at using Timers to solve this problem. This will start us looking at using Timer 1. This is probably the most flexible and powerful of the Timers on the STM8S. This power and flexibility comes with a price, it is also the most complex of the timers we have at our disposal.

As an aside, we will look at measuring the length of the pulses we can generate with the aim of defining the minimum pulse length we can create using each of the methods.

So let’s start with a common problem definition. We will use both methods to generate a single pulse lasting 30 uS.

Method 1 – Interrupts and GPIO

This method requires only slight modifications to the code presented in Using Timers on the STM8S. So let’s start by downloading the example and modifying the code.

The first thing we will need to do is to modify the duration of the timer in order to generate and interrupt every 30 uS. In the original program we setup Timer 2 as follows:

//
//  Setup Timer 2 to generate a 20 Hz interrupt based upon a 16 MHz timer.
//
void SetupTimer2()
{
    TIM2_PSCR = 0x03;       //  Prescaler = 8.
    TIM2_ARRH = 0xc3;       //  High byte of 50,000.
    TIM2_ARRL = 0x50;       //  Low byte of 50,000.
    TIM2_IER_UIE = 1;       //  Enable the update interrupts.
    TIM2_CR1_CEN = 1;       //  Finally enable the timer.
}

From the previous article we know that the following formula applies:

(2TIM2_PSCR * counter) = fmaster / finterrupt

Now we are looking at generating a high frequency (low duration) pulse and so it is not unreasonable to set the prescalar to 1 (i.e. TIM2_PSCR = 0). This simplifies the formula to:

counter = fmaster / finterrupt

We also know that finterrupt is given by the following formula:

finterrupt = 1 / pulse duration

Putting the two together gives:

counter = fmaster * pulse duration

counter = 16,000,000 * 30 * 10-6

counter = 480 (0x1e0)

So our code becomes:

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

If you hook up oscilloscope and deploy the code you should find that the STM8S is generating square wave on Post D, Pin 5. The frequency of the signal should be 60 uS (see the previous article for an explanation where this comes from) with a duty cycle of 50%. Each of the components should have a width of 30 uS.

The next thing we need to do is to make the system generate a single pulse instead of a square wave. The solution is shockingly simple; in this case we turn off the timer interrupt after the first pulse has been generated.

The code in the Interrupt Service Routine (ISR) currently looks like this:

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

When we initialise the GPIO port we start with the output set to low. The timer interrupt code then toggles the GPIO port. So the first time this ISR is called the GPIO port goes high, the second time the GPIO port goes low etc. This means we need to turn off the interrupt when we transition from high to low for the first time. This results in the following code:

//
//  Timer 2 Overflow handler.
//
#pragma vector = TIM2_OVR_UIF_vector
__interrupt void TIM2_UPD_OVF_IRQHandler(void)
{
    unsigned char data;

    data = PD_ODR_ODR4;
    PD_ODR_ODR4 = !data;            //  Toggle Port D, pin 5.
    if (data == 1)
    {
        TIM2_IER_UIE = 0;           //  Only allow the pulse to happen once.
    }
    TIM2_SR1_UIF = 0;               //  Reset the interrupt otherwise it will fire again straight away.
}

This method turns off the Timer 2 interrupt and only the Timer 2 interrupt but resetting the interrupt enable flag for Timer 2 (TIM2_IER_UIE = 0). We could have called __disable_interrupt() here but this would have turned off all interrupts.

Deploying this code results in the following output on the oscilloscope:

And just to prove that the application generated a single pulse I hooked up the logic analyser and set this up to capture over 10 seconds worth of data. This resulted in the following output:

As you can see, we have what looks like a single pulse (see the logic analyser output). Zooming in on the pulse on the logic analyser output confirmed that there is indeed only a single pulse. A quick check of the oscilloscope output confirmed that the duration of the pulse is 30 uS.

Method 2 – Timers and PWM

In Generating PWM Signals using the STM8S we saw how we can generate a PWM signal without having to use interrupts. Here we will extend the principle to generating a single pulse using the One Pulse Mode (OPM) feature of Timer 1 (note that OPM is not available on Timer 2). As with the above, we will do this is two stages, namely to generate a PWM signal and then to restrict the output to a single pulse.

So let’s start by looking at Timer 1 and what we will need for this example.

TIM1_ARRH & TIM1_ARRL – Timer 1 Auto Reload Registers

As with Timer 2, these are two 8-bit registers which when combined make up the 16-bit counter value. To reset the 16-bit value we need to write to TIM1_ARRH before writing to TIM_ARRL as writing to TIM1_ARRL triggers the update of the registers.

TIM1_PSCRH & TIM1_PSCRL – Timer 1 Prescalar

This is a 16-bit register and allows finer control over the prescalar than we had with Timer 2. In this case the value can be any value from 0 to 65535. The frequency of the counter (fcounter) is given by the following frequency:

fcounter = fmaster / (Prescalar + 1)

This means that the range of the divisor used is actually 1 to 65536.

As with the auto-reload register, we should load the high bits before the low bits (i.e. TIM1_PSCRH before TIM1_PSCRL).

TIM1_RCR – Timer 1 Repetition Counter

The repetition counter allows for the timer to generate update events only when a number of repetitions of the counter underflow and overflow have occurred. This is a topic which is outside of the scope of this example and so we will set this to 0 for the moment and return to this topic in future examples.

TIM1_CR1 – Timer 1 Control Register 1

We will be ensuring that two bits in this register are set; namely TIM1_CR1_DIR and TIM1_CR1_CMS.

TIM1_CR1_DIR controls the direction of the counter as counter 1 can count from 0 upwards or from TIM_ARR down to 0. Setting this value to 0 means count upwards whilst 1 means count downwards.

TIM1_CR1_CMS determines the counter alignment. For this example we will be using edge aligned counting and will be setting this to 0. Note that this value is a two bit value and the meaning of the remaining values is left for a future discussion.

TIM1_CCRM4 – Timer 1 Capture/Compare Mode Register 4

As with Timer 2, we can control the PWM mode setting this to either mode 1 or mode 2. We will configure this channel to be operating in PWM mode 2. In this mode OC3 will be inactive as long as the counter < TIM1_CCR3.

TIM1_CCR4H & TIM1_CCR4L – Timer 1 Capture Compare Register 4

These registers together form a 16-bit value for use in Capture/Compare/PWM mode. In PWM mode, these values coupled with the TIM1_ARR registers will allow control of the duty cycle of a PWM signal.

TIM1_CCER2 – Timer 1 Capture/Compare Register 2

This register determines the output polarity and availability of Timer 1, channel 4 (amongst other things). The bits we are really interested in are the availability and the polarity of the output.

TIM1_CCER2_CC4E determines if the output is enabled or disabled; 0 is disabled, 1 is enabled.

TIM2_CCER2_CC4P determines the polarity of the active stage of the output. A polarity of 0 means that the active stage gives a high (logic 1) output, whilst a polarity of 1 gives a low (logic 0) output.

Software

So if we put all of this together we get an application which looks something like this:

//
//  This program shows how you can generate a single pulse using
//  timers on the STM8S microcontroller.
//
//  This software is provided under the CC BY-SA 3.0 licence.  A
//  copy of this licence can be found at:
//
//  http://creativecommons.org/licenses/by-sa/3.0/legalcode
//
#if defined DISCOVERY
    #include <iostm8S105c6.h>
#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 = 0;                       //  Turn off CCO.
    CLK_HSITRIMR = 0;                   //  Turn off any HSIU trimming.
    CLK_SWIMCCR = 0;                    //  Set SWIM to run at clock / 2.
    CLK_SWR = 0xe1;                     //  Use HSI as the clock source.
    CLK_SWCR = 0;                       //  Reset the clock switch control register.
    CLK_SWCR_SWEN = 1;                  //  Enable switching.
    while (CLK_SWCR_SWBSY != 0);        //  Pause while the clock switch is busy.
}

//
//  Set up Timer 1, channel 4 to output a single pulse lasting 30 uS.
//
void SetupTimer1()
{
    TIM1_ARRH = 0x03;       //  Reload counter = 960
    TIM1_ARRL = 0xc0;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    TIM1_CR1_DIR = 0;       //  Up counter.
    TIM1_CR1_CMS = 0;       //  Edge aligned counter.
    TIM1_RCR = 0;           //  No repetition.
    //
    //  Now configure Timer 1, channel 4.
    //
    TIM1_CCMR4_OC4M = 7;    //  Set up to use PWM mode 2.
    TIM1_CCER2_CC4E = 1;    //  Output is enabled.
    TIM1_CCER2_CC4P = 0;    //  Active is defined as high.
    TIM1_CCR4H = 0x01;      //  480 = 50% duty cycle (based on TIM1_ARR).
    TIM1_CCR4L = 0xe0;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
	//
	//	Uncomment the following line to produce a single pulse.
	//
//    TIM1_CR1_OPM = 1;
    TIM1_CR1_CEN = 1;
}

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

If we run this application and hook up the oscilloscope to Timer 1, channel 4 (Pin 13 on the STM8S103F3 TSSOP20 package) we should find we get a PWM signal with a 60 uS period and a 50% duty cycle.

Timers and One Pulse Mode

Now that we have PWM functioning as expected we really only have to make one minor code modification, namely to set the timer generating a single pulse. For this we only need to add one line of code to the above application, SetupTimer1 becomes:

//
//  Set up Timer 1, channel 4 to output a single pulse lasting 30 uS.
//
void SetupTimer1()
{
    TIM1_ARRH = 0x03;       //  Reload counter = 960
    TIM1_ARRL = 0xc0;
    TIM1_PSCRH = 0;         //  Prescalar = 0 (i.e. 1)
    TIM1_PSCRL = 0;
    TIM1_CR1_DIR = 0;       //  Up counter.
    TIM1_CR1_CMS = 0;       //  Edge aligned counter.
    TIM1_RCR = 0;           //  No repetition.
    //
    //  Now configure Timer 1, channel 4.
    //
    TIM1_CCMR3_OC3M = 7;    //  Set up to use PWM mode 2.
    TIM1_CCER2_CC3E = 1;    //  Output is enabled.
    TIM1_CCER2_CC3P = 1;    //  Active is defined as high.
    TIM1_CCR3H = 0x01;      //  480 = 50% duty cycle (based on TIM1_ARR).
    TIM1_CCR3L = 0xe0;
    TIM1_BKR_MOE = 1;       //  Enable the main output.
    TIM1_CR1_OPM = 1;		//	Enable single pulse mode.
    TIM1_CR1_CEN = 1;
}

How Fast Can We Go?

Each of the above programs has their limitations. Assuming the same clock speed, the interrupt method is restricted by the number of instructions which must be executed in order to toggle the GPIO pin and work out if this is the first or seconded invocation of the ISR. The second is really only restricted by the speed of the system clock. This does not mean we cannot experiment to determine which is faster.

In both cases the programs were modified changing the timer auto-reload registers and the capture compare registers. The auto-reload register was always set to a value twice that of the capture compare register. For the interrupt method the fastest pulse which could be achieved was in the order of 2.5 uS (ARR = 0x0004) whilst the OPM method resulted in a pulse width of 146 nS (TIM1_ARR = 0x0002).

Conclusion

In this article we have looked at two methods which we can use to generate a single pulse. I am not offering advice on which is better, I’ll leave this to you as the application developer to decide.

Hopefully you will have gained an appreciation of the power of Timer 1. You should also have realised that using Timer 1 is not as simple as using Timer 2. There are a number of features we have not touched upon including (but not restricted to):

  • Capture/Compare
  • PWM Modes
  • Timer synchronisation

I am sure that we shall return to Timer 1 in future posts.

As always, the source code is available for download.

Source Code Compatibility

SystemCompatible?
STM8S103F3 (Breadboard)
Variable Lab Protomodule
STM8S Discovery

Converting The Way of The Register Examples

Friday, August 31st, 2012

A few days ago I was discussing a post from this series in the Netduino chat room with someone who is following the posts and is keen to learn about the STM8S. It became apparent that there are a few things I am taking for granted; namely:

  • Assuming you all have the same hardware set up as me
  • You are familiar with the development environment I am using

For the hardware case this is certainly unlikely especially with the availability and low pricing of the STM8S Discovery boards. As for the software, well there are at least two environments available and a number of compilers and assemblers.

The objective of this post is to describe the environment I am using and how you can convert the hardware and software setup to work with the STM8S Discovery board. By converting the application to this board we will cover the principle steps which should be followed in order to convert the application to run on any of the STM8S family of microcontrollers. We will also cover some of the shortcut keys in the development environment in order to help the novice/infrequent user of the environment become a little more productive.

For the purposes of this exercise we will look at the Simple GPIO example as this application is small and simple.

Hardware

The first thing we will do is look at the hardware I am using and then compare this to the STM8S Discovery board.

My Hardware Configuration

I am using the STM8S103F3P3 in a TSSOP20 package. This has been mounted on a TSSOP20 to DIP board to allow me to insert the chip into a breadboard circuit. When mounting this on a breadboard you need the following components:

  • 2 x 1uF capacitors (you can get away with only one for simple circuits)
  • 1 x 100nF capacitor
  • 3.3V regulated power supply
  • ST-Link/V2 programmer
  • Some wire to wire it all up.

To put this together, place one of the 1uF capacitors between VSS and VCAP and a 100 nF capacitor is placed between VDD and VSS. An additional (this is the optional capacitor) 1uF capacitor is also placed across the +3.3V and ground of the power supply.

The ST-Link/V2 should be connected to 3.3V and ground with the SWIM and NRST lines connected to the appropriate pins on the STM8S.

When you have all this put together you will have something like the following:

STM8S103 set up on Breadboard

STM8S103 set up on Breadboard

Now compare this to the STM8S Discovery board:

STM8S Discovery Board

STM8S Discovery Board

As you can see, all of the work setting up the hardware has been done for you :). You will also note that the chip used is the STM8S105C6T6. This chip is a different series to the one I am targeting. It is also a larger package giving the developer access to more ports etc. This board also has the ST-Link programmer built into the board. The only thing we need in order to use this board is a USB cable.

Development Environment

The development environment we are using is the IAR Kickstarter environment. This allows the creation of small applications (8 KBytes) for the STM8S. At the time of writing, the licence allowed the creation of fully functional applications with no commercial restrictions on the applications you create. The only requirement for the developer is that you register for a licence key.

We will not cover setting up the environment as this is a standard Windows installer.

If you have decided to use this environment then you might want to check out Custom IAR Templates for STM8S Projects. Arron Chapman has also put together some additional files and templates together. These are available in his posting Custome IAR STM8S Template.

Running the Application

We should now have the hardware and software set up so let’s take a look at the application we will be working with:

#include <iostm8S103f3.h>

int main( void )
{
    //
    //  Initialise Port D.
    //
    PD_ODR = 0;             //  All pins are turned off.
    PD_DDR_DDR5 = 1;        //  Port D, bit 5 is output.
    PD_CR1_C15 = 1;         //  Pin is set to Push-Pull mode.
    PD_CR2_C25 = 1;         //  Pin can run up to 10 MHz.
    //
    //  Now lets toggle to IO line.
    //
    while (1)
    {
        PD_ODR_ODR5 = 1;    // Turn Port D, Pin 5 on.
        PD_ODR_ODR5 = 0;    // Turn Port D, Pin 5 off.
    }
}

If you are using the STM8S103F3 chip then the application should compile and deploy OK. So now let’s consider what we need to do to make this run on the STM8S Discovery board.

The first thing to note is the include file we are using. This should match the chip you are using in your environment. You can find a full list of the include files supplied with the development environment in the inc directory of the installation. On my machine these can be found in C:Program Files (x86)IAR SystemsEmbedded Workbench 6.0 Kickstartstm8inc. If you browse through this directory you will find a file iostm8s105c6.h. This is the file you should be using for the STM8S Discovery board.

So make the change to the include file and we can now compile the application (press F7). The application should compile without any warnings or errors.

If you were to try to deploy the application now you would receive an error from the development environment. This is because the development environment and the programmer (ST-Link/V2) are targeting the wrong chip. To correct this we need to change the project options. Assuming you have your development environment open you should be looking at something like this:

Simple GPIO Project In IAR

Simple GPIO Project In IAR

Right click on the project name in the left hand panel (Simple GPIO – Debug) and select Options. Look for the Device text on the dialog which appears and click on the button to the right of the text box which contains the text STM8S103F3P3 and follow the popups which appear and select the STM8S105C6 chip.

Using IAR Options to Change the Target Device

Using IAR Options to Change the Target Device

At this point we should be ready to try the application out. So make sure that the STM8S Discovery board is connected to your PC and compile and deploy the application (Ctrl-D). At this point the application compiles and deploys after which the debugger should stop at the default breakpoint which has been set for you on the first line of code. Your display should look something like the following:

Breakpoint on the first line of code in the IAR environment

Breakpoint on the first line of code in the IAR environment

The green arrow and the highlighted line indicate the next line of code to be executed. From here we have a few options open to us:

  • Run the application with no breakpoints
  • Single step through the application
  • Set some breakpoints and then run the application

These options should be familiar to any software engineer.

Let’s hook the STM8S Discovery board up to an oscilloscope and run the application (press F5). You should now see the following output on the oscilloscope:

Simple GPIO Running on the STM8S Discovery Board Oscilloscope Output

Simple GPIO Running on the STM8S Discovery Board Oscilloscope Output

IAR Shortcut Keys

Throughout this post we have looked at some of the shortcut keys which I commonly use. The following table shows the key and the description of its function. This is not meant to be a comprehensive list of the keys used but it covers a few of the basics which I use regularly:

KeyFunction
Ctrl-DCompile the code and deploy to the device.
Shift-Ctrl-DTerminate the debugging session.
F7Make the project, do not deploy.
F5In debug mode, causes the application to run until the next breakpoint is reached or the application terminates.
F10In debug mode, execute the currently selected statement and then break at the next line of code. This will step over any method calls.
F11As with F10 but this time step into any method calls.
Shift-F11Step out of the current method being debugged and return to the calling method with a breakpoint set accordingly.
Shift-Ctrl-RReset the debugging environment setting a breakpoint on the first line of code and restart the application.
Ctrl-KComment out the currently selected lines of code.
Shift-Ctrl-KUncomment the selected lines of code.

Conclusion

As we have shown, a few simple changes to the example code in The Way of the Register series are all we need to make to run these examples on other STM8S chips other than the STM8S103F3. In summary we need to do the following:

  • Check the include file and make sure it matches the chip you are using
  • Check the chip the development environment is targeting

Hope you have found this useful and continue to enjoy The Way of the Register series.