STM8S Programming

Thomas edited this page Jan 8, 2019 · 69 revisions

Getting Started with STM8 Programming

This page once started as a scratchpad for the authors first steps towards programming STM8 chips with Open Source Software. Now its purpose is to share information on this subject (and a little bit on STM8 eForth, of course ;-) ).

Most commercial tools for STM8 (e.g. the ST STM8 Assembler) only work on Windows. Linux FOSS tools, however, can be used in Docker containers which is a perfect fit for Continuous Integration - with the free SDCC tool chain STM8 code can not only be built but also tested in a cloud computing environment! Most tools available for Linux support macOS and Windows or run as Linux binaries on Windows10 WSL. Some people use a Raspberry Pi for STM8 development.

If you're a C programmer (like the author) you may find the lightweight STM8 eForth useful for testing STM8 features, for prototyping new ideas (e.g. use Forth as a scripting engine for your C application), or to test your new PCB step by step. In some cases Forth might be the solution you don't need to write. If you're looking for an STM8 breakout board the getting started page might also be interesting.

Flashing the STM8

When soldered on a PCB, STM8 devices can be flashed in one of the following ways:

  • ICP (In Circuit Programming) through the SWIM interface,
  • IAP (In Application Programming) with factory ROM code,
  • or IAP with a user defined method, e.g. with a tool like stm8gal

After the initial programming with ICP, STM8 eForth uses IAP for compiling machine codedirectly to the Flash memory.

Note that in some boards the ICP pin NRST isn't accessible. If the GPIO PD1/SWIM is in input mode, ICP programming works anyway (usually it takes one retry). The low-pin-count device STM8S001J3 doesn't have an NRST pin and relies solely on this method.

Flash can be literally used like RAM: when writing one or more bytes, the Flash memory bridge perform a read-modify-write process and the STM8 core will be stalled until it's complete. That's not fast but it's trivially easy to use! At a time, there was a bug in the STM8 eForth kernel that caused the usage of Flash memory as the scratch-pad in interpreter mode. The bug was discovered because of unexplained core stalling, and not because of wear-out. A development chip survived many thousand write cycles without noticeable effect: this demonstrates that STM8 Flash memory won't wear out easily. Of course, one should never exceed the specified number of write cycles in "production systems", but a development system will be safe, even if the datasheet says "100 write cycles" (e.g. STM8S003 Value Line).

STM8FLASH for Linux Systems

For initial flashing of an STM8 device with ICP you'll need an ST-LINK V2 compatible adapter, e.g. a $2 device like this one:


The open source project stm8flash provides basic access to memory areas of STM8S devices (Flash, option bytes, EEPROM, RAM). STM8FLASH should also work on the Raspberry Pi and on macOS.

STM8FLASH Installation

The following steps for installing stm8flash worked for me:

  • clone stm8flash
  • provide libusb-dev (e.g. sudo apt-get install libusb-1.0-0-dev)
  • in the stm8flash folder, run make and sudo make install
  • create /etc/udev/rules.d/99-stlinkv2.rules with contents SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="0666"

The udev rules file in the last step can be created with the following commands:

echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="0666"' | sudo tee /etc/udev/rules.d/99-stlinkv2.ruleS > /dev/null

For testing, connect an ST-Link adapter (or one any of the many $2 clones, preferably ST-Link V2 compatible) to the SWIM connector of your STM8S board (mind the voltage and the polarity!), and try reading from your STM8 µC, e.g.:

$ stm8flash -c stlinkv2 -p stm8s103f3 -r test.ihx
Determine FLASH area
Reading 8192 bytes at 0x8000... OK
Bytes received: 8192

Running stm8flash without parameters will show the command line options.

Resetting STM8S devices to factory defaults

For unfathomable reasons some manufacturers of STM8SF103F3P6 breakout boards protect their Blinking LED "intellectual property" by setting the Flash protection option bits. The result is the following error message when you try to do anything useful with your board:

stm8flash -c stlinkv2 -p stm8s103f3 -w led.ihx 
Determine FLASH area
Writing Intel hex file 233 bytes at 0x8000... Tries exceeded

The solution is a reset to factory defaults of the µC. The stm8ef Makefile has a make target for this:

$ make defaults
stm8flash -c stlinkv2 -p stm8s103f3 -s opt -w tools/stm8s103FactoryDefaults.bin
Determine OPT area
Writing binary file 11 bytes at 0x4800... OK
Bytes written: 11

If you need different settings for the option bytes, here is how to do it:

echo "00 00 ff 00 ff 00 ff 00 ff 00 ff" | xxd -r -p > factory_defaults.bin
stm8flash -c stlinkv2 -p stm8s103f3 -s opt -w factory_defaults.bin

ST-LINK Utility for Windows

For Windows please follow the instructions on the STMicroelectronics STVP-STM32 page (STVP also supports ST 8bit µC families).

Using SDCC for STM8

Most examples for the STM8 controller family are based on a commercial C compiler. The FOSS SDCC tool chain offers support for STM8 and other 8-bit µCs and the generated code is quite good. The SDCC documentation for STM8 is rather thin, but what's not documented in the docs can be found by browsing the source code.

Notable gaps in the documentation for STM8 include linker, the assembly-C-interface (but see here), and the hard-coded startup code (function stm8_genInitStartup in /sdcc/src/stm8/main.c). SDCC won't allocate RAM address 0x0000 to avoid that a test for NULL accidentally goes wrong. When you use the RAM location 0x000000 in Assembly or Forth be aware that SDCC's RAM initialization starts at address 0x0001, too.

SDCC: using the Assembler

The SDCC tool chain uses a fork of ASxxxx Cross Assemblers V1.7x as an integral part of the code generator chain. The ASxxxx assembler suite supports many µCs and µPs, and it offers powerful features (e.g. macros, paging support). However, the set of directives differs a lot from the standard ST assembler, and hence the original stm8ef.asm code had to be converted to ASxxxx syntax with the help of an AWK script.

Due to licensing concerns the ASXXXX fork has been out of sync with the upstream development for a long time, and new features from recent versions of ASxxxx were added selectively (e.g. the macro feature). However, it's not documented in the SDCC project which of the later ASxxxx 4.0 or 5.0 features were added, and which are still missing. Furthermore, the integration of SDASSTM8 into SDCC is rather rough, and the existing documentation of the SDCC assembler interface concentrates on the MCS51 architecture.

From the outset, I had planned to enable mixing Forth with C which meant that I had to work with the ASxxxx fork used in SDCC. Since little is documented, figuring out how to work with STM8 assembly code, and how to work with mixed sources in the Makefile, required reading the SDCC sources and to experimenting with generated C code.

In SDAS V2.0.0 of release SDCC V3.6.0, and development version SDCC V3.6.6 (rev. 9930), I observed the following:

  • SDASSTM8 doesn't pass all tests in the ASXXXX 4.x.x tool chain's *tst8.asm test file (the issues are minor, e.g. RLWA X,A must be replaced with RLWA X and the feature Base STM8 Instructions with External symbols is unsupported)
  • the patched version supports "r,(offs,SP)" addressing for ADC, ADCW, SUB and SUBW (added to ASXXXX only in version 5.3)
  • the new directive .optsdcc passes the SDCC target parameter (e.g. -mstm8) to the linker
  • conditional assembly with boolean expressions doesn't always work as expected. As a work-around I used arithmetic expressions, e.g. multiplication instead of AND or addition instead of OR.
  • comparing stm8pst.c shows that the following ASxxxx V5.20 are missing in SDCC:
    • .ifdef, .ifndef, .ifb, .ifnb, .ifidn, and .ifdif
    • .iifdef, .iifndef .iifb, .iifnb, .iifidn, and .iifdif
    • .define, and .undefine
    • .4byte, .quad, .blk4, and .bank
    • .msg, .assume, and .error
    • .msb, .lohi, .hilo, .8bit, .16bit, .24bit, and .32bit
  • startup code, and linker features for STM8 are hard coded, and depend on main.c
  • in SDCC V3.6.0 interrupts can only be defined in main.c (this is maybe about to change in development version SDCC V3.6.6). Defining interrupts in Forth works in a different way.
  • in recent SDCC development revisions (e.g. 9933) start-up code may be placed in the unused interrupt vectors 0x806C .. 0x807f
  • the frequent use of assembler macros in STM8 eForth exposed some bugs in the macro expansion code: some things just don't work for unfathomable reasons, so I don't use them. One bug, related to expansion of macros with strings that contain assembler deliminator characters ("," and ";") triggered a segfault. The bug and a patch I proposed is described here.

In SDCC it's not possible to define symbols on the command line (at least up to SDCC V3.6.6). A work-around is putting configuration files with defines in folders, and setting the directory search path with the "-I" option (that's how TG9541/STM8EF board-support folders work).

SDCC: the Simulator uCsim

The SDCC tool chain includes the multi µC simulator uCsim. The development version SDCC V3.6.6 (from rev. 9923 on) was the first to support running STM8 eForth in the simulator sstm8: thanks to the kind support by the uCsim author, from development version ucsim-0.6-pre29 on, basic Flash control is provided. Running Forth, and compiling Forth code into simulated Flash memory just works.

A more stable support with "lockstep" between UART simulation and the telnet-UART interface was introduced in SDCC R10770 (see details).

Run sstm8 in a first terminal window:

./sstm8 -g -w -tS103 -Suart=1,port=10000 out/MINDEV/MINDEV.ihx

For interacting with the Forth console (or any other application that uses the UART for communication) in the simulated µC start telnet in a second terminal:

telnet localhost 10000

Note that CTRL-h works as backspace.

From STM8 eForth v2.2.13.snapshot on, tools are provided to compile Forth code into a STM8S binary using uCsim using interactive execution (refer to Build and Test Automation).

The SDCC Tool Chain by Example

This chapter contains some results from my first steps with SDCC and the STM8S target.

The SDCC Assembler and Linker

In SDCC 3.6.0 the assembler and linker are a fork of the outdated V2 of the ASSxxxx assembler suite. Both tools are called from with the SDCC compiler main.c as integral part of the optimization & link process. The required parameters and intermediate files find no mention in the manual, and I had to read them from src/stm8/main.c.

The following procedure worked:

  • execute the SDCC led.c test procedure described before, and confirm that all works as expected
  • remove all generated files but led.asm, and
  • store the following file as
sdasstm8 - -plosgffw $1
sdldstm8 -nf $
stm8flash -c stlinkv2 -p stm8s103f3 -w $1.ihx
  • run ./ led, and confirm that the test works as expected


  • contains the .area declarations for HOME = 0x8000, and DATA = 0x0001.
  • the test works fine with DATA = 0x0000. In a talk at Fosdem 2015 the authors states that this is due to concerns about null-pointers.

Combining C and Assembly

The SDCC manual section 3.2.3 Projects with Multiple Source Files describes how a main component can be linked with outer object files (the .rel output files from the assembly component are the object files in SDCC).

Both the old stable SDCC 3.6.0 and the current development branch (e.g. SDCC 3.8.4 R10768) do parameter passing in the following way:

  1. parameters are copied to the return stack in the relevant size, ready for access with "SP indirect addressing", and in the reverse order of the definition
  2. Return values are passed in registers:
  • 8bit: A
  • 16bit: X
  • 24bit: X and YL
  • 32bit: X (MSW) and Y (LSW)
  • 64bit: hidden pointer
  • returning structs isn't implemented (error 54).

There is discussion in Issue #235 about mixing C with Forth (or assembler routines). This interface is now mentioned in development branch of the SDCC documentation and can be considered "somewhat stable".

Minimal SDCC test: led.c

Patched for the cheap STM8S103F3 breakout board without crystal:


#include <stdint.h>

#define CLK_DIVR	(*(volatile uint8_t *)0x50c6)
#define CLK_PCKENR1	(*(volatile uint8_t *)0x50c7)

#define TIM1_CR1	(*(volatile uint8_t *)0x5250)
#define TIM1_CNTRH	(*(volatile uint8_t *)0x525e)
#define TIM1_CNTRL	(*(volatile uint8_t *)0x525f)
#define TIM1_PSCRH	(*(volatile uint8_t *)0x5260)
#define TIM1_PSCRL	(*(volatile uint8_t *)0x5261)

#define PB_ODR	(*(volatile uint8_t *)0x5005)
#define PB_DDR	(*(volatile uint8_t *)0x5007)
#define PB_CR1	(*(volatile uint8_t *)0x5008)

unsigned int clock(void)
	unsigned char h = TIM1_CNTRH;
	unsigned char l = TIM1_CNTRL;
	return((unsigned int)(h) << 8 | l);

void main(void)
	CLK_DIVR = 0x00; // Set the frequency to 16 MHz

	// Configure timer
	// 1000 ticks per second
	TIM1_PSCRH = 0x3e;
	TIM1_PSCRL = 0x80;
	// Enable timer
	TIM1_CR1 = 0x01;

	PB_DDR = 0x20;
	PB_CR1 = 0x20;

		PB_ODR = clock() % 1024 < 256 ? 0x20 : 0;

Source from here with hint about used port from here, and missing register addresses from the datasheet.

Compile and flash with:

sdcc -mstm8 --std-c99 led.c 
stm8flash -c stlinkv2 -p stm8s103f3 -w led.ihx 

Problems & Solutions

This section contains some of the problems I encountered, and their solutions.

Code Execution from Different Memory Areas

The STM8 applies Harvard architecture (separate instruction and data buses) although the core's bus interface hides that fact: a bus bridge maps all memory classes into a linear memory layout. However, access to RAM doesn't have the performance advantage of a 32 bit fetch, and the code run from RAM is slower than code from Flash memory.

Contrary to RAM, the bus bridge doesn't allow for code execution from the EEPROM memory:

\ Write RET to the first byte in RAM
$81 0 C! 0 EXECUTE ok
\ Unlock the EEPROM and write RET to the first byte
ULOCK $81 $4000 C! LOCK ok
\ try to execute from EEPROM
STM8eForth 2.2.20 ok
\ execution from the EEPROM caused a reset

As of STM8 eForth v2.2.21 certain Forth dictionary entries can be created in the EEPROM so that some space in the Flash memory can be conserved. Please refer to issue #178.

Time Critical Code

The STM8S architecture has a 3-stage pipeline, and the length of instructions varies from 1 to 5 bytes with an average size of 2 bytes. A fetch from Flash gets 4 bytes (aligned at 32bit boundaries) in one clock cycle filling a pre-fetch buffer. In linear code, this makes the execution time less dependent on the instruction length. However, after a branch, the pipeline is flushed, and code execution time then depends heavily on the location of instructions relative to a 32bit boundary. When moving a group of "spin loop" instructions by just one byte large execution time changes can be observed!

For writing code where the exact execution time is crucial the following approaches can be taken:

  • for coarse granularity (e.g. 100µs) use a timer
  • copy (small) routines to RAM for execution
  • in Flash, unroll loops, or align code carefully on 32bit boundaries

STM8EF uses a timer for optional UART simulation code. There are several examples for virtual USB on the STM8S103F3 which demonstrate that bit-banging on the STM8S is feasible, but so far non is known to be usable. Examples for generating video on the STM8 are rare.

STM8 16 Bit Register Access

The following behavior is all but well documented in the STM8S Reference Manual:

E.g. 17.3.1 Reading and writing to the 16-bit counter:

An 8-bit buffer is implemented for the read. Software must read the MS byte first, after which the LS byte value is buffered automatically ... This buffered value remains unchanged until the 16-bit read sequence is completed. Note: Do not use the LDW instruction to read the 16-bit counter. It reads the LS byte first and returns an incorrect result.

Or also 7.3.2 Write sequence for 16-bit TIM1_ARR register:

16-bit values are loaded in the TIM1_ARR register through preload registers. This must be performed by two write instructions, one for each byte. The MS byte must be written first. The shadow register update is blocked as soon as the MS byte has been written, and stays blocked until the LS byte has been written. Do not use the LDW instruction as this writes the LS byte first which produces incorrect results.

The bottom line is: check the reference manual before coding your hardware register access routines.

Note that TG9541/STM8EF provides the words 2C@ and 2C! for 16 bit reversed order-of-access read- and write operations that can be readily used when the reference manual requires a "MS byte first" 16 bit access.

UART Transmit Interrupt Handling

Sending the contents of a buffer with UART TXE and TC interrupt events turned out not to work as I had expected, and there is little guidance on how to initialize, start, spin down and end a transmission.

It turns out that most has to be done with the interrupt interrupt enable flags TIEN and TCIEN in UART_CR2.

This is what worked for me:

  • both TIEN and TCIEN should be normally disabled
  • after setting up buffers and pointers, enable TIEN to start a transmission
  • in the ISR, during the transmission, writing to UART_DR clears the interrupt event
  • in the ISR, when the buffer is empty, disable TIEN and enable TCIEN to spin down the ISR chain
  • in the ISR, read UART_SR to check if TC is set. If that's the case, clear TCIEN

The advantage of this procedure is that media access control, e.g. for RS485, can be woven into the ISR chain.

Example code (in Forth) is here.

STM8S Programming Primers

Linux Primers

All using the low cost STM8S103F3 breakout board, an ST-LINK V2 clone, SDCC, and stm8flash:

Other Primers

  • Blinking LED: IAR workbench, different STM8S103F3 breakout, same ST-LINK V2 clone
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.