x7890 发表于 2024-2-24 00:31:49

使用SDCC开发51单片机的极简VSCode插件

前言
想要使用开源的SDCC来编译一些简单的51单片机程序,但不想建立繁多的工程文件夹?在线工具对网络环境要求高?头文件用了Keil的关键字?这个自制的VsCode插件或许可以解决你的问题。
功能介绍

[*]文件标签右键菜单“使用SDCC编译(Ctrl+F9)”,快捷编译

[*]编辑区域右键菜单“Keil语法转SDCC”,快速转换语法
[*]编辑区域右键菜单“转到汇编代码”,快速查看指定语句编译后的汇编码与机器码
[*]展示SDCC编译得到的空间占用分布情况(.mem文件)
安装与使用

[*]安装SDCC编译器,并添加到PATH。
[*]安装VSCode,在VSCode中搜索安装“C/C++”(微软官方)、“51forSDCC”插件(自制)。
[*]在VSCode中打开想要存放代码的文件夹,建立头文件、以“main”开头的.c文件等文件,开始编码。所有代码需要是GBK编码格式。
[*]编码完成后,保存文件并编译,使用STC-ISP烧录hex文件。由于STC-ISP只有Windows版本,本插件也因此仅考虑Windows兼容性,并且要求VSCode的终端使用默认的PowerShell,而不是cmd。
说明

[*]SDCC的帮助文档位于:C:\Program Files\SDCC\doc\sdccman.pdf(默认安装位置)
[*]VSCode的工作文件夹中,文件名以“main”或“test”开头的.c文件视为主程序(需要含有main函数),其他.c文件视为库文件。本插件将必要的的文件编译、链接在一起,在“build”文件夹下生成与主程序文件同名的hex文件。因此,共享相同库的简单主程序可以放在一个文件夹下,不需要建立工程,尤其适合单文件的简单代码。

[*]在扩展设置中,可以添加额外的编译命令行。SDCC默认将变量储存在data区中(即“--model-small”),可改用“--model-medium”将变量默认储存到pdata区,或改用“--model-large”将变量默认储存到xdata区。一般不需要修改,因为可以手动修饰较大的变量,将其放到pdata或xdata区域,而剩余的变量保持在默认的data区,获得较快的速度和较短的代码。

[*]SDCC的变量修饰关键字为:__code、__data、__idata、__pdata、__xdata、__bit,可根据实际需求修饰,如将不频繁使用、较大的变量放在pdata或xdata区。内存占用与代码长度会在编译成功后显示。
[*]只要一个.c文件中实现的函数被用到至少一个,则SDCC就会将该.c文件中所有实现的函数链接到最终二进制文件中,造成代码长度增大。因此,编写库文件时应尽量将可能不会同时用到的函数拆分到不同.c文件中,极端情况下甚至可以让一个函数对应一个.c文件。

[*]SDCC默认使用较新的C语言标准,不支持C++,变量不强制定义在函数开头,无参函数需要使用(void)修饰(如main函数)。
[*]SDCC的中断函数需要在main函数所在文件中声明,格式为:void 名称(void) __interrupt(中断号)
[*]SDCC只支持float浮点数,不支持double,书写浮点常量时注意以f结尾。

[*]SDCC可使用__asm__("汇编代码")实现内联汇编,如__asm__("nop\nnop");
[*]如果找不到头文件,可添加包含文件目录“C:\Program Files\SDCC\include”。某些关键字可能会有下划线报错,不理睬即可。
[*]SDCC的printf对于小容量单片机来说太过笨重,可以使用功能较少的printf_fast_f(带浮点)、printf_fast甚至printf_tiny代替。需要包含stdio.h,并自行提供一个标准的putchar函数,原型为:int putchar(int ch)。不同printf的对比表格:



这里我以STC8G1K08为例,给出一个初始化串口输出以使用printf的例子。定时器1既用作波特率发生器也兼用作嘀嗒计时器,因此无需计算指令周期数即可实现延时。
#include "STC8G.H" // 直接由STC-ISP提供的头文件经过“Keil语法转SDCC”转换得到
#include <stdint.h>
#include <stdio.h>

#define FOSC 11059200UL
#define BAUD 115200UL
#define LOAD (65536U - FOSC / BAUD / 4)
void UART_Init(void) {
    SCON = 0x50;
    TMOD = 0x00, AUXR = 0x40;
    TL1 = LOAD & 0xff, TH1 = LOAD >> 8;
    TR1 = 1, ET1 = 1, EA = 1;
}
int putchar(int ch) {
    SBUF = ch;
    while (!TI)
      ;
    TI = 0;
    return ch;
}
uint16_t systick_10ms = 0, systick = 0;
void Timer1_ISR(void) __interrupt(3) {
    if (++systick == BAUD * 4 / 100) {
      ++systick_10ms;
      systick = 0;
    }
}
void delay(uint16_t ms) {
    uint16_t end = systick_10ms + ms / 10;
    while (systick_10ms < end)
      ;
}
void main(void) {
    P3M1 = 0b00000000, P3M0 = 0b00000000; // 准双向口
    UART_Init();
    delay(1000);

    int i = 0;
    while (1) {
      printf_tiny("%d\n", i++);
      delay(1000);
    }
}
以上面的代码为例,编译后,在“TL1 = LOAD & 0xff, TH1 = LOAD >> 8;”这句代码上右击,选择“转到汇编代码”,可以看到以下几行汇编码:                                          636 ;    main_simple_printf.c:12: TL1 = LOAD & 0xff, TH1 = LOAD >> 8;
      000009 75 8B E8         637   mov    _TL1,#0xe8
      00000C 75 8D FF         638   mov    _TH1,#0xff
这说明SDCC可以自动展开简单的常量表达式,可以通过宏定义自动适配IRC频率和波特率,而不需要手动重新计算或在运行时计算定时器装载值。

编译后显示的内存分布为:
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|a|a|a|a|b|Q|Q|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: 0x0f (sp set to 0x0e) with 241 bytes available.
No spare internal RAM space left.

Other memory:
   Name             Start    End      Size   Max
   ---------------- -------- -------- -------- --------   
   PAGED EXT. RAM                         0      256      
   EXTERNAL RAM                           0    65536      
   ROM/EPROM/FLASH0x0000   0x028a   651    65536
其中,16×16的矩阵表示了idata区域中每个字节的作用,最后表格的Size列分别展示了PDATA、XDATA、FLASH占用的空间。由于编译速度很快,可以对比不同写法代码的空间占用,SDCC提供的该编译信息使得优化空间占用变得更加容易。


希望该VSCode插件对大家有所帮助!


国学芯用 发表于 2024-2-28 08:58:42

优秀如你{:5_332:}
页: [1]
查看完整版本: 使用SDCC开发51单片机的极简VSCode插件