Embedded HandbookApps
All sections › I. Basics
STM8S103, STM8S105, STM8L151 · level intermediate · 13 min read
Open in the interactive handbook

2в. STM8: the eight-bit relative — what to take and what not

Sections 2–2б are about the 32-bit STM32. ST also has an eight-bit family — the STM8: STM8S for ordinary tasks and STM8L for battery-powered ones. Its peripherals and those of the STM32 were made from the same blocks, so the STM8 looks familiar, but the core is different — eight-bit, with its own instructions. Here: how the STM8 resembles the STM32, how it does not, and what that means in code: one and the same blink and one and the same arithmetic built for both cores and compared by their listings.

Prerequisites: the STM8S105 datasheet (DocID14771), ST note AN2945 on migrating between the STM8S and the STM32, the ST library header for the STM8L15x (stm8l15x.h, V1.5.0), the STM8 description files in OpenOCD, the STM32F103 datasheet (DocID13587) from the Keil pack. The STM8 code is built with the SDCC 4.2 compiler in the Docker image stm-sdcc:1, the F103 code with armclang. There is no STM8 simulator in the image, and no board either: everything is checked against listings and documents.

Terms used in the documentation

Term Meaning
accumulator (A) an eight-bit register for arithmetic
index registers X, Y 16-bit index registers
condition code register (CC) the flags register
SWIM — single wire interface module a single-wire debug and programming interface
ICP / IAP programming via SWIM / from your own program
option bytes configuration bytes (protection, bootloader, pin remapping)
data EEPROM on-chip EEPROM for data
ROP — read-out protection protection against reading the memory

2в.1 The STM8 next to the STM32

AN2945 compares the STM8 core with the Cortex-M3 of the first STM32 generation (table 1):

STM8 Cortex-M3
Data width 8 bits 32 bits
Dhrystone, DMIPS, zero wait states 0.29 1.22
Instructions CISC RISC
Average instruction length 2 bytes 2 bytes
Interrupt entry latency 9 cycles 12 cycles
Debug one wire (SWIM) two wires or JTAG
Frequency up to 24 MHz on the STM8S up to 72 MHz

By Dhrystone the STM8 is 4.2 times weaker, and its maximum frequency is three times lower. On the other hand, the code is just as dense, and an interrupt starts three cycles earlier. The peripherals share their design: in AN2945 the SPI registers of the STM8S and the STM32 almost match in names and bits; what differs is the register width, DMA and details such as the ports. Whoever knows the timers and SPI of the STM32 (sections 13 and 18) will quickly find their way around the STM8 too.

On the left the STM8 registers: A 8 bits, X and Y 16 each, SP 16, PC 24, CC 8; on the right the Cortex-M3: R0–R12 and SP, LR, PC of 32 bits each; toggling a pin takes 2 instructions and 5 bytes on the STM8, 6 instructions and 18 bytes on the M3; multiplying 32 by 32 is a library call of 75 instructions, 149 cycles on the STM8, a single MULS instruction on the M3; the blink image is 89 bytes against 456


2в.2 The core: A, X, Y and a 24-bit program counter

According to the STM8S105 datasheet (section 4.1) the core has six registers:

80 instructions, 20 addressing modes, 8 × 8 multiplication and division 16 by 8 and 16 by 16 bits. For comparison: the Cortex-M3 has sixteen 32-bit registers (section 1.3), and multiplication on the F103, according to its datasheet, takes one cycle.

The SDCC listing shows how the compiler distributes numbers over these registers. A 16-bit sum: the first argument arrives in X, the second on the stack:

_add16:
  000000  72 FB 03  [2]  addw x, (0x03, sp)
  000003  90 85     [2]  popw y
  000005  5B 02     [2]  addw sp, #2
  000007  90 FC     [2]  jp (y)

The result stays in X. The last three instructions are the return: the function itself removes its argument from the stack (it takes the return address into Y, moves SP and jumps via Y). The figures in brackets are the instruction cycles, printed by the SDCC assembler. A 32-bit sum lives in the Y:X pair — the low word in X, the high word in Y; the carry between them is a conditional branch jrnc and the instruction incw y.

In SDCC for the STM8 an int is 2 bytes, a long 4, a pointer 2 bytes; this is confirmed by _Static_assert in the experiment. As on the AVR (section 80): the expression 1000 * 60 already overflows in an int.


2в.3 Memory and the clock after reset

The STM8S105 memory map (datasheet, the memory map figure and table 7):

Addresses What is there
0x0000–0x07FF RAM, 2 KB; the top of the stack is the end of RAM
0x4000–0x43FF data EEPROM, 1 KB
0x4800–0x487F option bytes
0x5000–0x57FF ports and peripherals
0x6000–0x67FF boot ROM, 2 KB
0x7F00… core, SWIM, debug and interrupt registers
0x8000–0x807F 32 interrupt vectors
0x8000–0xFFFF program Flash, up to 32 KB

The core registers are visible to the debugger at addresses: A at 0x7F00, SP at 0x7F08 and 0x7F09. After reset SP = 0x07FF — the top of RAM, so the startup code does not need to set up the stack. The EEPROM withstands 300 thousand write cycles (datasheet) — the F103 has no separate EEPROM, the pack description of the F103C8 has only Flash and RAM.

Other chips — from the files on disk:

Flash EEPROM Source
STM8S103 0x8000–0x9FFF, 8 KB 0x4000–0x427F, 640 bytes OpenOCD configuration
STM8L15x low density 0x8000–0x9FFF, 8 KB 0x1000–0x10FF, 256 bytes stm8l15x_flash.h
STM8L15x medium density 0x8000–0xFFFF, 32 KB 0x1000–0x13FF, 1 KB stm8l15x_flash.h
STM8L15x high density 0x8000–0x17FFF, 64 KB 0x1000–0x17FF, 2 KB stm8l15x_flash.h

On the larger STM8L the Flash goes beyond 0xFFFF — that is what the 24-bit program counter is for. A pointer in SDCC is still 2 bytes, though: data beyond 64 KB cannot be reached that way.

The clock after reset is the same on the STM8S and the STM8L — HSI 16 MHz divided by 8, that is 2 MHz — but the prescaler register is in different places:

STM8S105 (datasheet) STM8L15x (stm8l15x.h)
CLK_CKDIVR 0x50C6, 0x18 after reset 0x50C0, 0x03 after reset
Peripheral clocks CLK_PCKENR1 after reset 0xFF — all enabled 0x00 — all disabled
Clock bit for the ports none none
Slow internal oscillator 128 kHz 38 kHz

On both the ports have no separate clock: there is nothing to enable, unlike the STM32 (section 8.4). The port addresses match — port B from 0x5005 on both.


2в.4 A register-level blink: SDCC versus armclang

ST's library for the STM8 does not suit SDCC: stm8l15x.h knows only the Cosmic, Raisonance and IAR compilers, and on all others it stops the build with the line #error "Unsupported Compiler!". So the addresses are our own, from the STM8S105 datasheet (tables 8 and 9). The LED is on PB5:

#define PB_ODR     (*(volatile uint8_t *)0x5005)
#define PB_DDR     (*(volatile uint8_t *)0x5007)
#define PB_CR1     (*(volatile uint8_t *)0x5008)
#define PB_CR2     (*(volatile uint8_t *)0x5009)
#define CLK_CKDIVR (*(volatile uint8_t *)0x50C6)

#define LED (1u << 5)                   /* светодиод на PB5 */

void clock_16mhz(void)
{
    CLK_CKDIVR = 0x00;                  /* после сброса 0x18: HSI / 8 = 2 МГц */
}

void led_init(void)
{
    PB_DDR |= LED;                      /* выход */
    PB_CR1 |= LED;                      /* двухтактный */
    PB_CR2 &= (uint8_t)~LED;            /* медленный фронт */
}

void led_toggle(void)
{
    PB_ODR ^= LED;
}

The build is sdcc -mstm8 --std-c11 blink_stm8.c, with no warnings. The .rst listing after linking:

_led_init:
  008029  72 1A 50 07  [1]  bset 0x5007, #5
  00802D  72 1A 50 08  [1]  bset 0x5008, #5
  008031  72 1B 50 09  [1]  bres 0x5009, #5
  008035  81           [4]  ret
_led_toggle:
  008036  90 1A 50 05  [1]  bcpl 0x5005, #5
  00803A  81           [4]  ret

Each |=, &= ~ and ^= became a single instruction on a bit right in memory: bset, bres, bcpl. An interrupt cannot squeeze in between the read and the write — it is one instruction. On the F103 the same needs BSRR or bit-banding (sections 8.2 and 1.5). The same led_toggle from armclang:

led_toggle:
  f640400c  MOV r0,#0xc0c
  f2c40001  MOVT r0,#0x4001
  6801  LDR r1,[r0,#0]
  f0810120  EOR r1,r1,#0x20
  6001  STR r1,[r0,#0]
  4770  BX lr

Two 32-bit words go just to assembling the address 0x4001 0C0C (GPIOB->ODR), then come the read, the "exclusive or", the write. The totals by function and for the whole image:

STM8S, SDCC STM32F103, armclang -Os
led_init 4 instructions, 13 bytes 12 instructions, 36 bytes
led_toggle 2 instructions, 5 bytes 6 instructions, 18 bytes
Blink image 89 bytes 456 bytes
Of which vectors 4 bytes (reset only) 236 bytes (59 words)

The STM8 image: the reset vector and the jump to main — 7 bytes, zeroing and copying data — 26, the jump to start — 3, the code — 53. The reset vector is 82 00 80 07: the int instruction and a 24-bit address. SDCC writes only the vectors used into the table. The F103 image is built with the Keil pack startup file and microlib; it contains 236 bytes of vector table. On the 8 KB STM8S103 a blink is 89 bytes, on the 64 KB F103 it is 456: the difference is noticeable only on the smallest chips.


2в.5 Arithmetic from the listings

One source file arith.c is built by both compilers. A PC model (stm8_2c.py) counts instructions and bytes from the listings, and for the STM8 also the sum of cycles from the square brackets. fromelf has no cycles in its listing. Everything is counted together with the function return; where the STM8 calls a library, its subroutine is built from the SDCC sources and added:

Operation STM8: instructions bytes cycles Cortex-M3: instructions bytes
16 + 16 bits 4 9 8 3 6
32 + 32 bits 7 15 14 2 4
8 × 8 bits 6 9 11 2 4
8 × 8 with a cast to uint16_t 32 51 56 2 4
16 × 16 bits 25 42 50 3 6
32 × 32 bits 86 142 149 2 4

Conclusion: on the STM8 keep data in 8 and 16 bits. Every uint32_t, and even more so every float, is a library call.


2в.6 Debugging, programming and when the STM8 is appropriate

SWIM. One data wire instead of SWD's two: on the STM8S105 it is pin PD1. According to the datasheet SWIM works in all chip modes and transfers up to 145 bytes per millisecond; the debug module provides software breakpoints on any instruction and two complex ones. The protocol is described in UM0470. The programmer is an ST-LINK: in OpenOCD the STM8 connects only via SWIM (transport select swim), and the networked ST-Link server does not support SWIM (OpenOCD manual). For the STM8S103 OpenOCD has a ready file: 8 KB Flash, 64-byte write block.

Protection and the bootloader. Read protection is enabled with the ROP byte = 0xAA and can be changed only via SWIM; any attempt to remove it erases the Flash and the EEPROM. There is no BOOT0 pin as on the STM32 (section 4.9): after reset the boot ROM looks at the bytes 0x487E, 0x487F and the reset vector and decides by itself whether to go into the bootloader.

Environment. ST provides STVD and the STVP programmer; in the 2010 datasheet the free Cosmic and Raisonance are limited to 16 KB of code. SDCC has no limits, but no ST library: you describe the registers yourself, as in 2в.4. There is no CubeMX, HAL or LL for the STM8 — all of this exists only for the STM32.

When the STM8 is appropriate:

When not:

There are no prices on disk, so there is no price comparison here: check it with the supplier at the time of ordering (section 2.5).


2в.7 Pitfalls

STM8S code does not start the clock on an STM8L. The port addresses are shared, but CLK_CKDIVR is at 0x50C6 on the S and at 0x50C0 on the L. A write to the wrong address lands in a different register.

A timer or the UART is silent on an STM8L. After reset CLK_PCKENR1 = 0x00 — the peripheral clock is off, unlike on the STM8S (0xFF). The ports still work: they have no separate clock.

The ST library does not build in SDCC. stm8l15x.h understands only Cosmic, Raisonance and IAR: #error "Unsupported Compiler!". For SDCC — your own register definitions from the datasheet.

An unnecessary type cast. (uint16_t)a * b for two uint8_t is a call to __mulint instead of one mul instruction: 32 instructions instead of 6.

A 16-bit int. 1000 * 60 overflows, millisecond counters in an int end at 32 767. Use uint32_t where it is really needed, and remember that multiplying it is a library call.

Read protection erases everything. ROP cannot be removed without erasing the Flash and the EEPROM — the data in the EEPROM will be lost too.


2в.8 Summary

Full sources

blink_stm8.c — a register-level LED blink on the STM8S, built with SDCC.

/* Раздел 2в: мигание светодиодом на STM8S по регистрам, компилятор SDCC.
   Адреса — даташит STM8S105 (DocID14771): таблица 8 (порты) и таблица 9 (CLK). */
#include <stdint.h>

_Static_assert(sizeof(int) == 2 && sizeof(long) == 4 && sizeof(void *) == 2,
               "SDCC для STM8: int 2 байта, long 4, указатель 2");

#define PB_ODR     (*(volatile uint8_t *)0x5005)
#define PB_DDR     (*(volatile uint8_t *)0x5007)
#define PB_CR1     (*(volatile uint8_t *)0x5008)
#define PB_CR2     (*(volatile uint8_t *)0x5009)
#define CLK_CKDIVR (*(volatile uint8_t *)0x50C6)

#define LED (1u << 5)                   /* светодиод на PB5 */

void clock_16mhz(void)
{
    CLK_CKDIVR = 0x00;                  /* после сброса 0x18: HSI / 8 = 2 МГц */
}

void led_init(void)
{
    PB_DDR |= LED;                      /* выход */
    PB_CR1 |= LED;                      /* двухтактный */
    PB_CR2 &= (uint8_t)~LED;            /* медленный фронт */
}

void led_toggle(void)
{
    PB_ODR ^= LED;
}

static void delay(uint16_t n)
{
    while (n--) {
        __asm__("nop");
    }
}

void main(void)
{
    clock_16mhz();
    led_init();
    for (;;) {
        led_toggle();
        delay(50000u);
    }
}

blink_f103.c — the same blink on the STM32F103 via CMSIS, built with armclang.

/* Раздел 2в: то же мигание на STM32F103 по регистрам CMSIS, компилятор armclang. */
#include "stm32f10x.h"

#define LED (1u << 5)                   /* светодиод на PB5 */

void led_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;                     /* у STM32 порту нужен такт */
    GPIOB->CRL = (GPIOB->CRL & ~(0xFu << 20)) | (0x2u << 20); /* PB5: выход 2 МГц, двухтактный */
}

void led_toggle(void)
{
    GPIOB->ODR ^= LED;
}

static void delay(uint32_t n)
{
    while (n--) {
        __asm volatile("nop");
    }
}

int main(void)
{
    led_init();
    for (;;) {
        led_toggle();
        delay(50000u);
    }
}

Next: section 2г "Milandr K1986VE92: a domestic Cortex-M3 next to the STM32" — how a domestic chip on a familiar core differs from the F103 and what carries over without changes.

Open in the interactive handbook