STM8S003 C Example Program Reference

STM8S003 Example Program Reference

STM8S003 C Example Program Reference

The STM8S003 (typically STM8S003F3 or STM8S003K3) is an 8-bit microcontroller from STMicroelectronics with a maximum CPU frequency of 16 MHz, 8 KB Flash, 1 KB RAM, 128 bytes EEPROM, 10-bit ADC, three timers, UART, SPI, I²C, and up to 28 GPIOs depending on package.

The examples below are written in C using SDCC (Small Devices C Compiler), the most common open-source toolchain for STM8. They use direct register access (bare-metal style) rather than the deprecated STM8 Standard Peripheral Library (SPL), as this is more transparent and widely used in modern STM8 projects.

Common setup notes:

  • Compile with SDCC: sdcc -mstm8 --std-c99 main.c -o output.ihx
  • Flash using stm8flash or openOCD with an ST-LINK.
  • No built-in filesystem exists → "append to file" is not possible without external storage (e.g., SD card via SPI, not covered here).
  • UART output appears on PD5 (TX) / PD6 (RX) at 115200 baud (common default).

This toggles PD0 every 1 ms using a busy-wait loop calibrated approximately for 16 MHz.

#include <stm8s.h>

#define LED_PORT    GPIOD
#define LED_PIN     GPIO_PIN_0

void delay_ms(uint16_t ms)
{
    while (ms--)
    {
        uint16_t i = 16000;         // Approx 1 ms @ 16 MHz (tuned empirically)
        while (i--);
    }
}

void main(void)
{
    CLK->CKDIVR = 0x00;             // No division → CPU @ 16 MHz (HSI)

    // Configure PD0 as push-pull output, fast mode
    LED_PORT->DDR |= LED_PIN;       // Direction: output
    LED_PORT->CR1 |= LED_PIN;       // Push-pull
    LED_PORT->CR2 |= LED_PIN;       // Fast mode (up to 10 MHz)

    while (1)
    {
        LED_PORT->ODR ^= LED_PIN;   // Toggle
        delay_ms(1);
    }
}

Note: Busy-wait is imprecise; use TIM4 or TIM2 for better accuracy in production code.

2. Read ADC 20 times per second

The STM8S003 has a 10-bit successive approximation ADC (ADC1) with up to 10 multiplexed channels (e.g., AIN2 on PD2). No filesystem is available natively.

This example reads channel 2 (PD2 / AIN2) at 20 Hz and prints the value (0–1023) over UART.

#include <stm8s.h>
#include <stdio.h>                  // For printf (requires retargeting, see UART section)

void delay_ms(uint16_t ms);         // Same delay function as above

void adc_init(void)
{
    ADC1->CR1 = 0x00;               // ADC off, single conversion, fADC = f/2
    ADC1->CR2 = 0x08;               // Right aligned, no external trigger
    ADC1->CR1 |= ADC1_CR1_ADON;     // Enable ADC (first write powers on)
    for (volatile uint8_t i = 0; i < 10; i++);  // Short delay after power-on
    ADC1->CR1 |= ADC1_CR1_ADON;     // Second write starts calibration
    while (ADC1->CR1 & ADC1_CR1_ADON); // Wait for calibration end
}

uint16_t adc_read(uint8_t channel)
{
    ADC1->CSR = (channel & 0x0F);   // Select channel
    ADC1->CR1 |= ADC1_CR1_ADON;     // Start conversion
    while (!(ADC1->CSR & ADC1_CSR_EOC));
    ADC1->CSR &= ~ADC1_CSR_EOC;
    return (uint16_t)((ADC1->DRH << 8) | ADC1->DRL);
}

void main(void)
{
    CLK->CKDIVR = 0x00;             // 16 MHz

    adc_init();

    // UART init here (see section 4)

    while (1)
    {
        uint16_t value = adc_read(2);   // Channel 2 (PD2)
        printf("ADC: %u\n", value);
        delay_ms(50);                   // 20 Hz
    }
}

Hardware note: Connect analog signal to PD2 (AIN2). VREF+ = VDD, VREF- = VSS.

3. PWM example with 5 different duty cycles

TIM2 (16-bit general-purpose timer) supports PWM on up to three channels. This example uses TIM2_CH1 on PD4 at ~1 kHz with 10-bit resolution (ARR=999).

#include <stm8s.h>

void tim2_pwm_init(void)
{
    TIM2->PSCR = 0x04;              // Prescaler = 16 → timer clock = 1 MHz
    TIM2->ARRH = 0x03;              // ARR = 999 (period = 1 ms → 1 kHz)
    TIM2->ARRL = 0xE7;

    TIM2->CCMR1 = 0x60 | 0x08;      // PWM mode 1 + OC1PE (preload enable)
    TIM2->CCER1 = 0x01;             // CC1E = 1 (enable output)

    TIM2->CR1 = 0x80 | 0x01;        // ARPE + CEN (auto-reload preload + enable)
}

void main(void)
{
    CLK->CKDIVR = 0x00;

    // PD4 as alternate function (TIM2_CH1)
    GPIOD->DDR |= GPIO_PIN_4;
    GPIOD->CR1 |= GPIO_PIN_4;
    GPIOD->CR2 |= GPIO_PIN_4;       // Fast mode

    tim2_pwm_init();

    uint16_t duties[] = {0, 250, 500, 750, 999};  // ≈0%, 25%, 50%, 75%, 100%

    while (1)
    {
        for (uint8_t i = 0; i < 5; i++)
        {
            TIM2->CCR1H = duties[i] >> 8;
            TIM2->CCR1L = duties[i] & 0xFF;
            // printf("Duty: %u / 999\n", duties[i]);  // optional
            for (volatile uint32_t d = 0; d < 800000; d++); // ~2 s delay
        }
    }
}

Alternative channels: TIM2_CH2 (PD3), TIM2_CH3 (PA3 / remapped).

4. Output to serial port (UART1)

STM8S003 has one UART (UART1) with TX on PD5, RX on PD6. No native USB interface exists.

This example prints a counter at 115200 baud.

#include <stm8s.h>
#include <stdio.h>

// Minimal putchar retargeting for printf
int putchar(int c)
{
    while (!(UART1->SR & UART1_SR_TXE));
    UART1->DR = (uint8_t)c;
    return c;
}

void uart_init(void)
{
    // 16 MHz → 115200 baud
    UART1->BRR2 = 0x03;
    UART1->BRR1 = 0x01;             // Mantissa + fraction

    UART1->CR2 = UART1_CR2_TEN;     // Enable TX only

    // PD5 (TX) as alternate function
    GPIOD->DDR |= GPIO_PIN_5;
    GPIOD->CR1 |= GPIO_PIN_5;
    GPIOD->CR2 |= GPIO_PIN_5;
}

void main(void)
{
    CLK->CKDIVR = 0x00;
    uart_init();

    uint32_t counter = 0;

    while (1)
    {
        printf("Counter: %lu\n", counter++);
        for (volatile uint32_t d = 0; d < 1600000; d++); // ≈1 s
    }
}

Compile with -lstm8 if using SDCC's minimal libc, or provide your own putchar.

5. Clocks on the STM8S003 and how to read them

The STM8S003 clock tree includes:

  • HSI — Internal 16 MHz RC oscillator (factory calibrated, ±1% at 25 °C)
  • HSE — External crystal/ceramic resonator (1–16 MHz)
  • LSI — Internal 128 kHz RC (for watchdog or AWU)
  • fMASTER — Master clock (HSI, HSE, or LSI; prescaled by 1/2/4/8/16/32/64/128 via CLK_CKDIVR)
  • fCPU — CPU clock = fMASTER (can be further divided in low-power modes)
  • Peripheral clocks — Gated individually (e.g., TIM2, ADC, UART)

No direct frequency measurement API exists. Read via:

  • CLK->CKDIVR — Current prescaler
  • CLK->SWCR & CLK->SWIM — Clock source status / switching
  • CLK->ICKCR — HSI/LSI ready & trim
  • CLK->ECKCR — HSE ready & bypass

Example program printing clock configuration:

#include <stm8s.h>
#include <stdio.h>      // Assumes UART init from section 4

void uart_init(void);   // From section 4

void main(void)
{
    CLK->CKDIVR = 0x00; // Max speed (16 MHz, no division)
    uart_init();

    while (1)
    {
        uint8_t div = CLK->CKDIVR & 0x07;
        uint32_t f_master = 16000000UL >> div;

        printf("fMASTER ≈ %lu Hz  (CKDIVR=0x%02X)\n", f_master, CLK->CKDIVR);

        if (CLK->SWCR & CLK_SWCR_SWIF)
            printf("HSI ready\n");
        if (CLK->ECKCR & CLK_ECKCR_HSERDY)
            printf("HSE ready\n");
        if (CLK->ICKCR & CLK_ICKCR_LSIRDY)
            printf("LSI ready\n");

        for (volatile uint32_t d = 0; d < 3200000; d++); // ≈2 s
    }
}

Typical startup values:

  • fMASTER = 16 MHz (HSI, CKDIVR = 0x00)
  • To use HSE: enable in CLK_ECKCR, switch via CLK_SWR / CLK_SWCR

These examples are compatible with SDCC and bare-metal STM8 development. For production use, calibrate delays/timing with timers and consider enabling interrupts where appropriate. If you require examples using sduino (Arduino-like layer for STM8) or Cosmic compiler syntax, please specify.

Linux Rocks Every Day