STC8G1K08A tiny 349 byte UART hello world demo

Also see:

In our previous post STC8G1K08A minimal UART hello world demo we showed how to use FwLib_STC8 to write a simple UART hello world application for the STC8G1K08A that is just over 2.2 KB in size.

While that example is very simple, it suffers from a huge issue related to the SDCC feature set: As of this moment, SDCC does not support pruning unused functions from the linked output. This means that if you use any function from the UART library, you end up linking the entire library, which adds a lot of code size overhead.

In order to fix this, we can inline and somewhat optimize the code.

main.c
#include "fw_uart.h"

/* Keep the baud rate in one place so the Timer1 reload formula stays obvious. */
#define UART1_BAUD_RATE 115200UL

/* Reproduce the library's SYSCLK calculation without calling any runtime code. */
#define UART1_SYSCLK_HZ (__CONF_FOSC / ((__CONF_CLKDIV == 0) ? 1UL : (unsigned long)__CONF_CLKDIV))

/* UART1 mode 1 uses Timer1 overflow divided by four as its baud-rate clock. */
#define UART1_TIMER1_RELOAD (65536UL - (UART1_SYSCLK_HZ / (4UL * UART1_BAUD_RATE)))

/* Keep the entire message in one memory area so the library can stream it out. */
static unsigned char uart_message[] = "Hello, world from UART1!\r\n";

static void UART1_SendString(unsigned char *str)
{
    while (*str)
    {
        SBUF = *str++;
        TI = 0;
        while (!TI)
        {
        }
    }
}

#define TICKS_MS (__CONF_FOSC / ((__CONF_CLKDIV == 0) ? 1UL : (unsigned long)__CONF_CLKDIV) / 9000UL)

static void delay_ms(unsigned int ms)
{
    unsigned int i;

    do
    {
        i = TICKS_MS;
        while (--i)
        {
        }
    } while (--ms);
}

static void UART1_Init(void)
{
    /*
     * Open the extended SFR bank so we can write CLKDIV before bringing UART1 up.
     * B7 = 1: enable access to the extended SFR region.
     * B6..B0 = 0: leave the other bank-switching and peripheral-routing controls unchanged.
     */
    P_SW2 = 0x80;

    /* Apply the configured clock divider with a direct whole-register write. */
    CLKDIV = __CONF_CLKDIV;

    /*
     * Return to the normal SFR bank now that the clock divider is programmed.
     * B7 = 0: disable extended SFR access again.
     * B6..B0 = 0: keep the remaining P_SW2 controls inactive in this minimal demo.
     */
    P_SW2 = 0x00;

    /*
     * Select the high-speed internal RC oscillator band that the trim values were tuned for.
     * 0x03 keeps only IRCBAND[1:0], which are the band-select bits.
     * Any upper bits in the configuration value are discarded so we only touch the documented band field.
     */
    IRCBAND = (__CONF_IRCBAND & 0x03);

    /* Load the matching voltage trim byte for that RC band. */
    VRTRIM = __CONF_VRTRIM;

    /* Load the high-speed RC fine-trim byte. */
    IRTRIM = __CONF_IRTRIM;

    /*
     * Load the low-speed RC trim field even though this demo runs from the fast internal clock.
     * 0x03 keeps only LIRTRIM[1:0], which are the valid low-speed trim bits.
     * Any upper bits are forced low so we do not write undefined values into that register.
     */
    LIRTRIM = (__CONF_LIRTRIM & 0x03);

    /*
     * Put UART1 into the standard 8-bit asynchronous mode and allow the receiver to run.
     * 0x50 = 0101 0000b.
     * SM0 = 0, SM1 = 1: select UART mode 1, the usual 8-bit UART with variable baud rate.
     * SM2 = 0: disable multiprocessor address filtering, so every received byte is accepted.
     * REN = 1: enable the UART receiver.
     * TB8 = 0, RB8 = 0: ninth-bit transmit/receive state starts cleared because mode 1 does not use it.
     * TI = 0, RI = 0: clear both transmit and receive status flags before the first character.
     */
    SCON = 0x50;

    /*
     * Configure Timer1 as the baud-rate generator that UART1 will count from.
     * 0x40 = 0100 0000b.
     * B6 = 1: run Timer1 in 1T mode so it increments every system clock instead of every 12 clocks.
     * B0 = 0: keep UART1's baud source on Timer1 rather than switching it to Timer2.
     * B5..B1 = 0: leave the unrelated AUXR features disabled in this demo.
     */
    AUXR = 0x40;

    /*
     * Put Timer1 into the reload mode expected by the FwLib UART code path.
     * 0x00 = 0000 0000b.
     * GATE1 = 0: Timer1 starts and stops only from TR1, not from the external INT1 pin.
     * C/T1 = 0: Timer1 counts clock ticks, not external events.
     * M1_1 = 0, M0_1 = 0: select the Timer1 mode that this STC8 library uses for UART baud generation.
     * The Timer0 half of TMOD is also cleared because this demo does not use Timer0.
     */
    TMOD = 0x00;

    /* Load the high byte of the compile-time Timer1 reload value. */
    TH1 = (unsigned char)(UART1_TIMER1_RELOAD >> 8);

    /* Load the low byte so the very first overflow already uses the right baud rate. */
    TL1 = (unsigned char)(UART1_TIMER1_RELOAD & 0xFF);

    /* Start Timer1 so UART1 can derive its bit timing. */
    TR1 = 1;
}

void main(void)
{
    /* A zero-initialized unsigned loop variable gives a compact full-range delay. */
    unsigned int settle = 0;

    UART1_Init();
    
    /* Hand the whole NUL-terminated message buffer to the local string transmit helper. */
    while (1)
    {
        UART1_SendString(uart_message);
        delay_ms(1000);
    }
}

How to flash

See our post How to flash STC8G1K08A using stcgal

How to check the serial output

Check the output using

monitor.sh
picocom -b 115200 /dev/ttyUSB0

Memory usage

memory_usage.txt
===== Memory usage summary for uart_minimal_size_demo =====
Internal RAM layout:
      0 1 2 3 4 5 6 7 8 9 A B C D E F
0x00:|0|0|0|0|0|0|0|0|S|S|S|S|S|S|S|S|
0x10:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x20:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x30:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x40:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x50:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x60:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x70:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x80:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0x90:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xa0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xb0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xc0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xd0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xe0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0xf0:|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|S|
0-3:Reg Banks, T:Bit regs, a-z:Data, B:Bits, Q:Overlay, I:iData, S:Stack, A:Absolute

Stack starts at: 0x08 (sp set to 0x07) with 248 bytes available.
No spare internal RAM space left.

Other memory:
   Name             Start    End      Size     Max     
   ---------------- -------- -------- -------- --------
   PAGED EXT. RAM                         0      256   
   EXTERNAL RAM     0x0001   0x0020      32     1024   
   ROM/EPROM/FLASH  0x0000   0x015c     349     8192   
==========================================================

with SDCC 4.2.0 #13081 - 349 bytes of flash is a vast improvement over the 2.2 KB of the previous example.

Note that you can individually choose to just copy FwLib functions into your project (the liberal Apache License terms of FwLib_STC8 allow that, but you might need to retain the copyright statement), or “inline” them altogether to compress the code even more. In our code above, we chose to do a mixture of both.


Check out similar posts by category: STC8, Embedded, C/C++