Embedded HandbookПриложения
Все разделы › I. Основы
STM8S103, STM8S105, STM8L151 · уровень средний · 13 мин чтения
Открыть в интерактивном справочнике

2в. STM8: восьмибитный родственник — что взять, а что нет

Разделы 2–2б — о 32-битных STM32. У ST есть и восьмибитное семейство — STM8: STM8S для обычных задач и STM8L для батарейных. Периферию у него и у STM32 делали из одних блоков, поэтому STM8 выглядит знакомо, но ядро другое — восьмибитное, со своими командами. Здесь — чем STM8 похож на STM32, чем нет, и что это значит в коде: одно и то же мигание и одна и та же арифметика собраны для обоих ядер и сравнены по листингам.

Опоры: даташит STM8S105 (DocID14771), заметка ST AN2945 о переходе между STM8S и STM32, заголовок библиотеки ST для STM8L15x (stm8l15x.h, V1.5.0), файлы описания STM8 в OpenOCD, даташит STM32F103 (DocID13587) из пака Keil. STM8 собран компилятором SDCC 4.2 в Docker-образе stm-sdcc:1, F103 — armclang. Симулятора STM8 в образе нет, платы тоже: всё сверено по листингам и документам.

Как это называется в документации

Оригинал Перевод
accumulator (A) аккумулятор: восьмибитный регистр для арифметики
index registers X, Y индексные регистры по 16 бит
condition code register (CC) регистр флагов
SWIM — single wire interface module однопроводной интерфейс отладки и прошивки
ICP / IAP прошивка через SWIM / из своей программы
option bytes байты настроек (защита, загрузчик, переназначение выводов)
data EEPROM EEPROM для данных внутри кристалла
ROP — read-out protection защита от чтения

2в.1 STM8 рядом с STM32

AN2945 сравнивает ядро STM8 с Cortex-M3 в первом поколении STM32 (таблица 1):

STM8 Cortex-M3
Ширина данных 8 бит 32 бита
Dhrystone, DMIPS, без тактов ожидания 0,29 1,22
Команды CISC RISC
Средняя длина команды 2 байта 2 байта
Задержка входа в прерывание 9 тактов 12 тактов
Отладка один провод (SWIM) два провода или JTAG
Частота до 24 МГц у STM8S до 72 МГц

По Dhrystone STM8 в 4,2 раза слабее, предельная частота — втрое ниже. Зато код такой же плотный, а прерывание начинается на три такта раньше. Периферия общая по устройству: в AN2945 регистры SPI у STM8S и STM32 почти совпадают по именам и битам, отличаются ширина регистров, DMA и частности вроде портов. Кто знает таймеры и SPI у STM32 (разделы 13 и 18), быстро разберётся и в STM8.

Слева регистры STM8: A 8 бит, X и Y по 16, SP 16, PC 24, CC 8; справа Cortex-M3: R0–R12 и SP, LR, PC по 32 бита; переключить вывод — у STM8 2 команды и 5 байт, у M3 6 команд и 18 байт; умножить 32 на 32 — у STM8 вызов библиотеки на 75 команд, 149 тактов, у M3 одна команда MULS; образ мигания — 89 байт против 456


2в.2 Ядро: A, X, Y и 24-битный счётчик команд

По даташиту STM8S105 (п. 4.1) у ядра шесть регистров:

80 команд, 20 способов адресации, 8 × 8 умножение и деление 16 на 8 и 16 на 16 бит. Для сравнения: у Cortex-M3 шестнадцать 32-битных регистров (раздел 1.3), а умножение у F103, по его даташиту, занимает один такт.

В листинге SDCC видно, как компилятор раскладывает числа по этим регистрам. 16-битная сумма: первый аргумент приходит в X, второй — в стеке:

_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)

Результат остаётся в X. Три последние команды — возврат: функция сама снимает свой аргумент со стека (достаёт адрес возврата в Y, сдвигает SP и прыгает по Y). В скобках — такты команды, их печатает ассемблер SDCC. 32-битная сумма лежит в паре Y:X — младшее слово в X, старшее в Y; перенос между ними — условный переход jrnc и команда incw y.

У SDCC для STM8 int — 2 байта, long — 4, указатель — 2 байта; это подтверждает _Static_assert в опыте. Как у AVR (раздел 80): выражение 1000 * 60 в int уже переполняется.


2в.3 Память и такт после сброса

Карта памяти STM8S105 (даташит, рисунок карты и таблица 7):

Адреса Что там
0x0000–0x07FF ОЗУ, 2 КБ; вершина стека — конец ОЗУ
0x4000–0x43FF EEPROM для данных, 1 КБ
0x4800–0x487F байты настроек
0x5000–0x57FF порты и периферия
0x6000–0x67FF загрузочное ПЗУ, 2 КБ
0x7F00… регистры ядра, SWIM, отладки и прерываний
0x8000–0x807F 32 вектора прерываний
0x8000–0xFFFF Flash программы, до 32 КБ

Регистры ядра видны отладчику по адресам: A — 0x7F00, SP — 0x7F08 и 0x7F09. После сброса SP = 0x07FF — вершина ОЗУ, поэтому стартовому коду не нужно ставить стек. EEPROM выдерживает 300 тыс. циклов записи (даташит) — у F103 отдельной EEPROM нет, в описании пака у F103C8 только Flash и ОЗУ.

Другие камни — по файлам на диске:

Flash EEPROM Источник
STM8S103 0x8000–0x9FFF, 8 КБ 0x4000–0x427F, 640 байт конфигурация OpenOCD
STM8L15x малой ёмкости 0x8000–0x9FFF, 8 КБ 0x1000–0x10FF, 256 байт stm8l15x_flash.h
STM8L15x средней ёмкости 0x8000–0xFFFF, 32 КБ 0x1000–0x13FF, 1 КБ stm8l15x_flash.h
STM8L15x высокой ёмкости 0x8000–0x17FFF, 64 КБ 0x1000–0x17FF, 2 КБ stm8l15x_flash.h

У старших STM8L Flash выходит за 0xFFFF — вот зачем 24-битный счётчик команд. Указатель в SDCC при этом 2 байта: данные за 64 КБ так не достать.

Такт после сброса у STM8S и STM8L одинаковый — HSI 16 МГц, делённый на 8, то есть 2 МГц, — но регистр делителя в разных местах:

STM8S105 (даташит) STM8L15x (stm8l15x.h)
CLK_CKDIVR 0x50C6, после сброса 0x18 0x50C0, после сброса 0x03
Такты периферии CLK_PCKENR1 после сброса 0xFF — всё включено 0x00 — всё выключено
Бит такта для портов нет нет
Медленный внутренний генератор 128 кГц 38 кГц

Порты у обоих без отдельного такта: включать нечего, в отличие от STM32 (раздел 8.4). Адреса портов совпадают — порт B с 0x5005 и там и там.


2в.4 Мигание по регистрам: SDCC против armclang

Библиотека ST для STM8 к SDCC не подходит: stm8l15x.h знает только компиляторы Cosmic, Raisonance и IAR, а на остальных останавливает сборку строкой #error "Unsupported Compiler!". Поэтому адреса — свои, из даташита STM8S105 (таблицы 8 и 9). Светодиод — на 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;
}

Сборка — sdcc -mstm8 --std-c11 blink_stm8.c, без предупреждений. Листинг .rst после компоновки:

_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

Каждое |=, &= ~ и ^= стало одной командой над битом прямо в памяти: bset, bres, bcpl. Прерывание не может вклиниться между чтением и записью — это одна команда. У F103 для того же нужен BSRR или bit-banding (разделы 8.2 и 1.5). Тот же led_toggle у 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

Два 32-битных слова уходят только на то, чтобы собрать адрес 0x4001 0C0C (GPIOB->ODR), дальше — чтение, «исключающее или», запись. Итог по функциям и образу целиком:

STM8S, SDCC STM32F103, armclang -Os
led_init 4 команды, 13 байт 12 команд, 36 байт
led_toggle 2 команды, 5 байт 6 команд, 18 байт
Образ мигания 89 байт 456 байт
Из них векторы 4 байта (только сброс) 236 байт (59 слов)

Образ STM8: вектор сброса и переход на main — 7 байт, обнуление и копирование данных — 26, переход к запуску — 3, код — 53. Вектор сброса — 82 00 80 07: команда int и 24-битный адрес. SDCC пишет в таблицу только используемые векторы. Образ F103 собран со стартовым файлом пака Keil и microlib; в нём 236 байт таблицы векторов. На 8 КБ STM8S103 мигание — это 89 байт, на 64 КБ F103 — 456: разница заметна только на самых маленьких камнях.


2в.5 Арифметика по листингам

Один исходник arith.c собран обоими компиляторами. Модель на ПК (stm8_2c.py) считает по листингам команды и байты, а у STM8 — и сумму тактов из квадратных скобок. У fromelf тактов в листинге нет. Всё вместе с возвратом из функции; где STM8 зовёт библиотеку, её подпрограмма собрана из исходников SDCC и прибавлена:

Операция STM8: команд байт тактов Cortex-M3: команд байт
16 + 16 бит 4 9 8 3 6
32 + 32 бита 7 15 14 2 4
8 × 8 бит 6 9 11 2 4
8 × 8 с приведением к uint16_t 32 51 56 2 4
16 × 16 бит 25 42 50 3 6
32 × 32 бита 86 142 149 2 4

Вывод: на STM8 держите данные в 8 и 16 битах. Каждый uint32_t и тем более float — вызов библиотеки.


2в.6 Отладка, прошивка и когда STM8 уместен

SWIM. Один провод данных вместо двух у SWD: у STM8S105 это вывод PD1. По даташиту SWIM работает во всех режимах камня, передаёт до 145 байт в миллисекунду; модуль отладки даёт программные точки останова на любой команде и две сложные. Протокол описан в UM0470. Программатор — ST-LINK: в OpenOCD STM8 подключается только через SWIM (transport select swim), а сетевой ST-Link-сервер SWIM не поддерживает (руководство OpenOCD). Для STM8S103 в OpenOCD готов файл: Flash 8 КБ, блок записи 64 байта.

Защита и загрузчик. Защита чтения включается байтом ROP = 0xAA и меняется только через SWIM; любая попытка её снять стирает Flash и EEPROM. Вывода BOOT0, как у STM32 (раздел 4.9), нет: загрузочное ПЗУ после сброса смотрит байты 0x487E, 0x487F и вектор сброса и само решает, идти ли в загрузчик.

Среда. ST даёт STVD и программатор STVP; в даташите 2010 года бесплатные Cosmic и Raisonance ограничены 16 КБ кода. SDCC — без ограничений, но без библиотеки ST: регистры описываете сами, как в 2в.4. CubeMX, HAL и LL для STM8 нет — всё это только у STM32.

Когда STM8 уместен:

Когда нет:

Цен на диске нет, поэтому сравнения по цене здесь нет: её смотрят у поставщика на момент заказа (раздел 2.5).


2в.7 Грабли

Код с STM8S не заводит такт на STM8L. Адреса портов общие, а CLK_CKDIVR у S — 0x50C6, у L — 0x50C0. Запись по чужому адресу попадает в другой регистр.

На STM8L молчит таймер или UART. После сброса CLK_PCKENR1 = 0x00 — такт периферии выключен, в отличие от STM8S (0xFF). Порты при этом работают: у них отдельного такта нет.

Библиотека ST не собирается в SDCC. stm8l15x.h понимает только Cosmic, Raisonance и IAR: #error "Unsupported Compiler!". Для SDCC — свои определения регистров из даташита.

Лишнее приведение типа. (uint16_t)a * b для двух uint8_t — вызов __mulint вместо одной команды mul: 32 команды вместо 6.

int на 16 бит. 1000 * 60 переполняется, счётчики миллисекунд в int заканчиваются на 32 767. Берите uint32_t там, где это правда нужно, и помните, что это вызов библиотеки при умножении.

Защита чтения стирает всё. Снять ROP без стирания Flash и EEPROM нельзя — данные в EEPROM тоже пропадут.


2в.8 Итоги

Полные исходники

blink_stm8.c — мигание светодиодом на STM8S по регистрам, сборка 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 — то же мигание на STM32F103 через CMSIS, сборка 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);
    }
}

Дальше: раздел 2г «Миландр К1986ВЕ92: отечественный Cortex-M3 рядом с STM32» — чем отечественный камень на знакомом ядре отличается от F103 и что переносится без правок.

Открыть в интерактивном справочнике