STC8G1K08A 仅 349 字节的 UART hello world 示例

另请参见:

在上一篇文章 STC8G1K08A 最小 UART hello world 示例 中,我们演示了如何使用 FwLib_STC8STC8G1K08A 编写一个简单的 UART hello world 应用,其体积略大于 2.2 KB。

虽然该示例非常简单,但它存在一个与 SDCC 功能集相关的严重问题:截至目前,SDCC 尚不支持从链接输出中裁剪未使用的函数。 这意味着,只要你使用了 UART 库中的任意一个函数,最终就会链接整个库,从而带来大量额外的代码体积开销。

为了解决这一问题,我们可以将代码内联并进行一定程度的优化。

main.c
#include "fw_uart.h"

/* 将波特率集中定义在一处,使 Timer1 重载公式保持直观。 */
#define UART1_BAUD_RATE 115200UL

/* 不调用任何运行时代码,复现库的 SYSCLK 计算。 */
#define UART1_SYSCLK_HZ (__CONF_FOSC / ((__CONF_CLKDIV == 0) ? 1UL : (unsigned long)__CONF_CLKDIV))

/* UART1 模式 1 使用 Timer1 溢出频率除以 4 作为波特率时钟。 */
#define UART1_TIMER1_RELOAD (65536UL - (UART1_SYSCLK_HZ / (4UL * UART1_BAUD_RATE)))

/* 将整条消息存放在同一块内存区域,便于库将其逐字节输出。 */
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)
{
    /*
     * 打开扩展 SFR 区,以便在启用 UART1 之前写入 CLKDIV。
     * B7 = 1:允许访问扩展 SFR 区域。
     * B6..B0 = 0:保持其他区切换与外设路由控制不变。
     */
    P_SW2 = 0x80;

    /* 通过整寄存器写入应用所配置的时钟分频系数。 */
    CLKDIV = __CONF_CLKDIV;

    /*
     * 时钟分频已编程完成,现在返回普通 SFR 区。
     * B7 = 0:再次禁用扩展 SFR 访问。
     * B6..B0 = 0:在此最小示例中保持 P_SW2 其余控制位无效。
     */
    P_SW2 = 0x00;

    /*
     * 选择与已校准微调值匹配的高速内部 RC 振荡器频段。
     * 0x03 仅保留 IRCBAND[1:0],即频段选择位。
     * 配置值中的高位将被丢弃,确保只修改文档所述的频段字段。
     */
    IRCBAND = (__CONF_IRCBAND & 0x03);

    /* 加载与该 RC 频段对应的电压微调字节。 */
    VRTRIM = __CONF_VRTRIM;

    /* 加载高速 RC 微调字节。 */
    IRTRIM = __CONF_IRTRIM;

    /*
     * 即使本示例运行在快速内部时钟下,也加载低速 RC 微调字段。
     * 0x03 仅保留 LIRTRIM[1:0],即有效的低速微调位。
     * 高位强制为低,避免向该寄存器写入未定义值。
     */
    LIRTRIM = (__CONF_LIRTRIM & 0x03);

    /*
     * 将 UART1 置于标准 8 位异步模式,并允许接收器运行。
     * 0x50 = 0101 0000b。
     * SM0 = 0,SM1 = 1:选择 UART 模式 1,即常见的可变波特率 8 位 UART。
     * SM2 = 0:禁用多机地址过滤,因此接收到的每个字节都会被接受。
     * REN = 1:启用 UART 接收器。
     * TB8 = 0,RB8 = 0:第九位发送/接收状态初始清零,因为模式 1 不使用该位。
     * TI = 0,RI = 0:在发送第一个字符前清除发送与接收状态标志。
     */
    SCON = 0x50;

    /*
     * 将 Timer1 配置为 UART1 计数所用的波特率发生器。
     * 0x40 = 0100 0000b。
     * B6 = 1:Timer1 运行于 1T 模式,每个系统时钟周期递增一次,而非每 12 个时钟一次。
     * B0 = 0:保持 UART1 的波特率源为 Timer1,不切换到 Timer2。
     * B5..B1 = 0:在本示例中保持 AUXR 其余无关功能禁用。
     */
    AUXR = 0x40;

    /*
     * 将 Timer1 置于 FwLib UART 代码路径所期望的重载模式。
     * 0x00 = 0000 0000b。
     * GATE1 = 0:Timer1 仅由 TR1 启停,不受外部 INT1 引脚控制。
     * C/T1 = 0:Timer1 计数时钟节拍,而非外部事件。
     * M1_1 = 0,M0_1 = 0:选择本 STC8 库用于 UART 波特率生成的 Timer1 模式。
     * TMOD 中 Timer0 部分也被清零,因为本示例不使用 Timer0。
     */
    TMOD = 0x00;

    /* 加载编译期 Timer1 重载值的高字节。 */
    TH1 = (unsigned char)(UART1_TIMER1_RELOAD >> 8);

    /* 加载低字节,使首次溢出即采用正确的波特率。 */
    TL1 = (unsigned char)(UART1_TIMER1_RELOAD & 0xFF);

    /* 启动 Timer1,使 UART1 能够据此推导位定时。 */
    TR1 = 1;
}

void main(void)
{
    /* 以零初始化的无符号循环变量实现紧凑的全范围延时。 */
    unsigned int settle = 0;

    UART1_Init();
    
    /* 将整个以 NUL 结尾的消息缓冲区交给本地字符串发送辅助函数。 */
    while (1)
    {
        UART1_SendString(uart_message);
        delay_ms(1000);
    }
}

如何烧录

参见我们的文章 如何使用 stcgal 烧录 STC8G1K08A

如何查看串口输出

使用以下命令查看输出:

monitor.sh
picocom -b 115200 /dev/ttyUSB0

内存占用

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   
==========================================================

使用 SDCC 4.2.0 #13081 时,349 字节的 flash 相比上一示例的 2.2 KB 是显著改进

你可以选择只将 FwLib 的部分函数复制到自己的项目中(FwLib_STC8 宽松的 Apache 许可条款允许这样做,但可能需要保留版权声明),或者将它们整体“内联”以进一步压缩代码。在上面的代码中,我们采用了两者结合的方式。


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