写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

SDCC是一个小型设备的 C语言编译器,该编译器支持标准 C语言;相对于 GCC编译器来说可能知名度不是很高,但它跟 GCC一样,是跨平台,并且遵循 GPL开源协议。本次实验是使用 nuvoton的 MS51系列单片机来操作(基于 8051内核)


sdcc官方网址:http://sdcc.sourceforge.net/

Wiki主页:https://sourceforge.net/p/sdcc/wiki/Home/


目录

  • 一、关于 SDCC
  • 二、安装及环境配置
  • 三、SDCC规则(仅对于 MCS51说明)
  • 四、SDCC头文件处理
  • 五、工程构建
  • 六、VSCode语法修饰
  • 七、Makefile程序化管理
  • 八、程序编译
  • 九、总结
  • 十、相关链接




一、关于 SDCC

SDCC是可重定目标的、优化的标准 C(ANSI C89,ISO C99,ISO C11)编译器套件,针对的是基于 Intel MCS51的微处理器(8031、8032、8051、8052等),Maxim(以前为达拉斯)DS80C390变体,飞思卡尔( 基于 HC08(hc08,s08),基于 Zilog Z80的 MCU(z80,z180,gbz80,Rabbit 2000/3000,Rabbit 3000A,TLCS-90),Padauk(pdk14,pdk15)和 STMicroelectronics STM8。

在安装了 SDCC后,通过指令查看版本号可以看到它所支持的设备类型:

Emscripten编译sdl并且使用cmake sdcc编译器_sdcc

然后,这里有个帖子有讨论 SDCC的一些相关东西,而且好像(我也不确定)SDCC的开发者也在里面,感兴趣的可以看一下:https://wap.newsmth.net/article/905eb27dddf829f15c81077215d66284?title=%E7%94%B5%E8%B7%AF%E8%AE%BE%E8%AE%A1%E4%B8%8E%E8%B0%83%E8%AF%95&from=search

SDCC较于 Keil来说,它对 C语法的严谨度是很高的,更像一个标准的 C语言编译器,并不会像 Keil那样把一些 warning去除掉,自动帮你优化;前面说了, SDCC是一个好的编译器,可优化方面稍微有点不够完美,以至于代码生成的体积还是比 Keil C51大一些(是不是我还有些优化指令没 get到呢?)。



二、安装及环境配置

1、 SDCC

软件的下载路径在上面的 sdcc主页上有对应的接口,只需要下载相关的平台程序包安装就好了,安装完成后添加系统环境变量这个就不用多说了,最后就 sdcc -v 测试检验。就这么简单。。。

2、 MinGW-w64

下载地址:https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/

安装完成后需要添加系统环境变量,可以利用 cmd命令:gcc -v 测试。

3、 MSYS2或者 Git(只要支持 shell命令的终端控制台就行)

msys2下载 <-- 自戳

Git自行搜索下载。

2、 VSCode

VSCode环境部署可以看之前的 STM32开发之 VS Code + gcc环境编译 的第三节,然后如果你懂得配置 VSCode的配置项的话,那么你可以跳过下面的配置操作自己写。

  • c_cpp_properties.json
{
    "configurations": [
        {
            "name": "C51",
            "includePath": [			// 你的工程中存放 include的文件夹路径
                "${workspaceFolder}/**",
                "${workspaceFolder}/App",
                "${workspaceFolder}/Libraries/Device/Include",
                "${workspaceFolder}/Libraries/StdDriver/inc"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE",
            ],
            "compilerPath": "C:\\Program Files\\SDCC\\bin\\sdcc.exe",	// sdcc bin路径
            "cStandard": "gnu18",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}
  • tasks.json
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build",
            "type": "shell",
            "command": "make",
            "args": [
                "target=${fileBasenameNoExtension}"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}
  • settings.json
{
    "files.encoding": "gb2312",
    "files.autoGuessEncoding": true,

    "C_Cpp.errorSquiggles": "Enabled",     // 语法错误

    "files.associations": {
        "main.h": "c",
    },
    
    /* 终端在Windows上使用的shell的路径 */
    "terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
}


三、SDCC规则(仅对于 MCS51说明)

1、支持的数据类型

Emscripten编译sdl并且使用cmake sdcc编译器_8051_02

2、存储类型

Emscripten编译sdl并且使用cmake sdcc编译器_makefile_03

相对于 Keil,其存储类型关键字加上了前缀 ’ __ ’ 双下划线,这也是 SDCC的特色风格。

__data :这是小内存模式的默认(通用)地址空间,声明的变量将被放在 8051内核的直接寻址 RAM中。

__idata :这个地址空间中的变量将被分配到 8051的内部 RAM的间接可寻址部分。

__pdata :存储类型 pdata用于访问分页的外部数据存储器。

__xdata :这个地址空间中的变量将被放在外部 RAM中。

__code :存放程序代码的内存地址空间。

3、存储器模式

SDCC支持四种存储器模式:(small, medium, large, huge)

采用 SDCC编译时,默认为小模式。如果要强制 SDCC使用特定的存储器模式,可使用以下命令行参数(在手册的 P3.3.6章节可以查询得到):

Emscripten编译sdl并且使用cmake sdcc编译器_sdcc_04

类似于 Keil的这个选项(只不过 Keil是 GUI操作,SDCC是命令操作):

Emscripten编译sdl并且使用cmake sdcc编译器_8051_05

关于不同模式下变量的存储位置不一样,可以查阅手册 P3.13章节;总的来说,对于中(medium)、大(large)、巨大(huge)存储模式来说,所有未指定内部命名地址空间而声明的变量都将分配到外部 RAM中,这包括所有参数和局部变量(用于不可重入函数),中型模式使用 pdata,大型模式使用 xdata;而小内存模式(small)则默认存放在 data。

4、bit 和 sbit关键字

bit和 int、char之类的差不多,只不过 char = 8位,bit = 1位;

sbit是对应可位寻址空间的一个位。

同样的,在 SDCC这里加上了前缀 ’ __ ’ 双下划线,变成 __bit__sbit

5、SFR(特殊功能寄存器)

与 bit关键字类似,表示命名地址空间,用于描述 8051的特殊函数寄存器和特殊位变量。

eg:

    __sfr __at (0x80) P0; /* special function register P0 at location 0x80 */

6、绝对寻址

SDCC支持采用 __at 关键字表示绝对寻址。

7、内嵌汇编

SDCC完全支持内嵌汇编。使用该功能时,汇编代码应嵌在 __asm__endasm 标识符之间。

8、编译生成文件

  • xxx.asm:程序的汇编文件。
  • xxx.lst:程序的列表文件。
  • xxx.rst:被链接器更新的列表文件。
  • xxx.sym:由链接器生成的符号清单。
  • xxx.rel:由汇编器生成的对象文件,提供给链接器使用。
  • xxx.map:被链接器更新的最终存储器映射。
  • xxx.mem:内存的使用情况摘要。
  • xxx.ihx:Intel十六进制格式的加载模块。该文件必须被下载到微控制器中。


四、SDCC头文件处理

前面说了,sdcc的非标关键字是带 ‘ __ ’ 双下滑线的,但是 MS51的官方 SDK包中,寄存器寻址的关键字全是 keil格式的,这就需要转换过来;如果是一些成熟的 8051内核单片机,那么你可以在 sdcc的安装路径 …\SDCC\include\mcs51下找到对应的芯片头文件,若是没有,那么就要自己进行格式转换了。sdcc格式转换,你可以去上网搜一下,这里给一个链接:https://www.amobbs.com/thread-5625040-1-1.html,里面有提供一个转换工具,当然你也可以自己去写一个程序。

Emscripten编译sdl并且使用cmake sdcc编译器_#define_06



五、工程构建

因为是用 VSCode做编辑开发,只要有 .vscode文件夹的配置项就可以了,剩下的编译过程就交给 sdcc编译,所以工程的构建比较简单,文件夹创建以及移植 SDK库都方便,以下是我的工程文件分布(看起来还是比较容易理解的):

Emscripten编译sdl并且使用cmake sdcc编译器_makefile_07

这里 Libraries文件夹的内容是直接移植 SDK库的,其余的看文件名就知道用途了。

另外就是,App文件夹里,除了 lint.h(用来语法解析 mcs51特定代码)和添加了 main.h头文件(往常 main主文件是不带头文件);然后为什么要这两个呢,是为了避免 VSCode的语法错误的,当然你也可以一劳永逸,直接关了 VSCode的语法提示(这个可以看上面的 settings.json配置文件),至于 lint.h是从 sdcc的安装路径 …\SDCC\include\mcs51提取出来的,原滋原味。



六、VSCode语法修饰

上面也讲了,sdcc使用了部分非 ASCII C关键字,所以 VSCode会在程序中凸显语法错误;那么,我们就来解决这个问题(当然,不是用关闭语法检查这种粗暴形式):

1、首先要了解的是,在使用 sdcc进行编译的时候,是会自动在进行编译前预定义 __SDCC 宏的,这样就好办,利用条件编译,区别智能提示运行环境和 SDCC实际编译环境,用空的 define去取代这些关键字,寄存器也都用宏代替,然后在 SDCC实际编译时调用原来 C51语法的寄存器定义。

2、根据上面第一点,然后结合上面的提到的 lint.h(默认是留了 sdcc关键字的空 define),得到这样的一个例子:

#ifdef __SDCC
    __sfr __at (0x80) P0;    //实际有效的寄存器定义

#else
	/* 关键字部分 */
	#define __sfr                    // 空的关键字宏,消除关键字不兼容(在 lint.h上可以获取到相关的关键字)
	...
	...
	/* 寄存器部分 */
	#define P0    (*(char *) (0x80)) // 无实际意义,用于兼容(欺骗)标准 C语法的寄存器符号
	...
	...

#endif

通过以上条件编译,就可以把代码区分到智能提示和实际编译两个环境:

  • 在实际编译时,SDCC 编译器会预定义 __SDCC 宏,因此实际编译时使用实际有效的寄存器定义;
  • 而在智能提示环境,用空的宏取代所有关键字,消除关键字的不兼容,然后用一个宏定义寄存器,保证寄存器名智能提示依然可以使用。这里将寄存器定义为 char* 指针解引用的左值表达式,目的是为迎合语法上对寄存器赋值是合法的,括号里的值可以是任意值,意义不大,当然如果使用寄存器本来的值更合适,但处理起来比较麻烦。

3、对上面的 1、2点总结起来,就可以得到:

#ifndef __MAIN_H__
#define __MAIN_H__


#include <stdint.h>

#ifdef __SDCC
    #include "MS51_16K.h"	// 把 keil格式转换后的 sdcc寄存器头文件

#else
    #include <stdbool.h>	// lint.文件中需要用到 bool关键字定义
    #include "lint.h"		// 关键字处理
    #include "SFR_Macro_MS51_16K.h"

/******************************************************************************/
/*                      Macro define  header files                            */
/******************************************************************************/

#define P0          (*(char *) (0)) //= 0x80;
#define SP          (*(char *) (0)) //= 0x81;
#define DPL         (*(char *) (0)) //= 0x82;
#define DPH         (*(char *) (0)) //= 0x83;
#define RCTRIM0     (*(char *) (0)) //= 0x84;
#define RCTRIM1     (*(char *) (0)) //= 0x85;  
#define RWK         (*(char *) (0)) //= 0x86;
#define PCON        (*(char *) (0)) //= 0x87;

#define TCON        (*(char *) (0)) //= 0x88;
#define TMOD        (*(char *) (0)) //= 0x89;
#define TL0         (*(char *) (0)) //= 0x8A;
#define TL1         (*(char *) (0)) //= 0x8B;
#define TH0         (*(char *) (0)) //= 0x8C;
#define TH1         (*(char *) (0)) //= 0x8D;
#define CKCON       (*(char *) (0)) //= 0x8E;
#define WKCON       (*(char *) (0)) //= 0x8F;

#define P1          (*(char *) (0)) //= 0x90;
#define SFRS        (*(char *) (0)) //= 0x91; //TA Protection
#define CAPCON0     (*(char *) (0)) //= 0x92;
#define CAPCON1     (*(char *) (0)) //= 0x93;
#define CAPCON2     (*(char *) (0)) //= 0x94;
#define CKDIV       (*(char *) (0)) //= 0x95;
#define CKSWT       (*(char *) (0)) //= 0x96; //TA Protection
#define CKEN        (*(char *) (0)) //= 0x97; //TA Protection

#define SCON        (*(char *) (0)) //= 0x98;
#define SBUF        (*(char *) (0)) //= 0x99;
#define SBUF_1      (*(char *) (0)) //= 0x9A;
#define EIE         (*(char *) (0)) //= 0x9B;
#define EIE1        (*(char *) (0)) //= 0x9C;
#define CHPCON      (*(char *) (0)) //= 0x9F; //TA Protection

#define P2          (*(char *) (0)) //= 0xA0;
#define AUXR1       (*(char *) (0)) //= 0xA2;
#define BODCON0     (*(char *) (0)) //= 0xA3; //TA Protection
#define IAPTRG      (*(char *) (0)) //= 0xA4; //TA Protection
#define IAPUEN      (*(char *) (0)) //= 0xA5;  //TA Protection
#define IAPAL       (*(char *) (0)) //= 0xA6;
#define IAPAH       (*(char *) (0)) //= 0xA7;

#define IE          (*(char *) (0)) //= 0xA8;
#define SADDR       (*(char *) (0)) //= 0xA9;
#define WDCON       (*(char *) (0)) //= 0xAA; //TA Protection
#define BODCON1     (*(char *) (0)) //= 0xAB; //TA Protection
#define P3M1        (*(char *) (0)) //= 0xAC;
#define P3S         (*(char *) (0)) //= 0xAC; //Page1
#define P3M2        (*(char *) (0)) //= 0xAD;
#define P3SR        (*(char *) (0)) //= 0xAD; //Page1
#define IAPFD       (*(char *) (0)) //= 0xAE;
#define IAPCN       (*(char *) (0)) //= 0xAF;

#define P3          (*(char *) (0)) //= 0xB0;
#define P0M1        (*(char *) (0)) //= 0xB1;
#define P0S         (*(char *) (0)) //= 0xB1; //Page1
#define P0M2        (*(char *) (0)) //= 0xB2;
#define P0SR        (*(char *) (0)) //= 0xB2; //Page1
#define P1M1        (*(char *) (0)) //= 0xB3;
#define P1S         (*(char *) (0)) //= 0xB3; //Page1
#define P1M2        (*(char *) (0)) //= 0xB4;
#define P1SR        (*(char *) (0)) //= 0xB4; //Page1
#define P2S         (*(char *) (0)) //= 0xB5; 
#define IPH         (*(char *) (0)) //= 0xB7;
#define PWMINTC     (*(char *) (0)) //= 0xB7;  //Page1

#define IP          (*(char *) (0)) //= 0xB8;
#define SADEN       (*(char *) (0)) //= 0xB9;
#define SADEN_1     (*(char *) (0)) //= 0xBA;
#define SADDR_1     (*(char *) (0)) //= 0xBB;
#define I2DAT       (*(char *) (0)) //= 0xBC;
#define I2STAT      (*(char *) (0)) //= 0xBD;
#define I2CLK       (*(char *) (0)) //= 0xBE;
#define I2TOC       (*(char *) (0)) //= 0xBF;

#define I2CON       (*(char *) (0)) //= 0xC0;
#define I2ADDR      (*(char *) (0)) //= 0xC1;
#define ADCRL       (*(char *) (0)) //= 0xC2;
#define ADCRH       (*(char *) (0)) //= 0xC3;
#define T3CON       (*(char *) (0)) //= 0xC4;
#define PWM4H       (*(char *) (0)) //= 0xC4; //Page1
#define RL3         (*(char *) (0)) //= 0xC5;
#define PWM5H       (*(char *) (0)) //= 0xC5;  //Page1
#define RH3         (*(char *) (0)) //= 0xC6;
#define PIOCON1     (*(char *) (0)) //= 0xC6; //Page1
#define TA          (*(char *) (0)) //= 0xC7;

#define T2CON       (*(char *) (0)) //= 0xC8;
#define T2MOD       (*(char *) (0)) //= 0xC9;
#define RCMP2L      (*(char *) (0)) //= 0xCA;
#define RCMP2H      (*(char *) (0)) //= 0xCB;
#define TL2         (*(char *) (0)) //= 0xCC; 
#define PWM4L       (*(char *) (0)) //= 0xCC; //Page1
#define TH2         (*(char *) (0)) //= 0xCD;
#define PWM5L       (*(char *) (0)) //= 0xCD; //Page1
#define ADCMPL      (*(char *) (0)) //= 0xCE;
#define ADCMPH      (*(char *) (0)) //= 0xCF;

#define PSW         (*(char *) (0)) //= 0xD0;
#define PWMPH       (*(char *) (0)) //= 0xD1;
#define PWM0H       (*(char *) (0)) //= 0xD2;
#define PWM1H       (*(char *) (0)) //= 0xD3;
#define PWM2H       (*(char *) (0)) //= 0xD4;
#define PWM3H       (*(char *) (0)) //= 0xD5;
#define PNP         (*(char *) (0)) //= 0xD6;
#define FBD         (*(char *) (0)) //= 0xD7;

#define PWMCON0     (*(char *) (0)) //= 0xD8;
#define PWMPL       (*(char *) (0)) //= 0xD9;
#define PWM0L       (*(char *) (0)) //= 0xDA;
#define PWM1L       (*(char *) (0)) //= 0xDB;
#define PWM2L       (*(char *) (0)) //= 0xDC;
#define PWM3L       (*(char *) (0)) //= 0xDD;
#define PIOCON0     (*(char *) (0)) //= 0xDE;
#define PWMCON1     (*(char *) (0)) //= 0xDF;

#define ACC         (*(char *) (0)) //= 0xE0;
#define ADCCON1     (*(char *) (0)) //= 0xE1;
#define ADCCON2     (*(char *) (0)) //= 0xE2;
#define ADCDLY      (*(char *) (0)) //= 0xE3;
#define C0L         (*(char *) (0)) //= 0xE4;
#define C0H         (*(char *) (0)) //= 0xE5;
#define C1L         (*(char *) (0)) //= 0xE6;
#define C1H         (*(char *) (0)) //= 0xE7;

#define ADCCON0     (*(char *) (0)) //= 0xE8;
#define PICON       (*(char *) (0)) //= 0xE9;
#define PINEN       (*(char *) (0)) //= 0xEA;
#define PIPEN       (*(char *) (0)) //= 0xEB;
#define PIF         (*(char *) (0)) //= 0xEC;
#define C2L         (*(char *) (0)) //= 0xED;
#define C2H         (*(char *) (0)) //= 0xEE;
#define EIP         (*(char *) (0)) //= 0xEF;

#define B           (*(char *) (0)) //= 0xF0;
#define CAPCON3     (*(char *) (0)) //= 0xF1;
#define CAPCON4     (*(char *) (0)) //= 0xF2;
#define SPCR        (*(char *) (0)) //= 0xF3;
#define SPCR2       (*(char *) (0)) //= 0xF3; //Page1
#define SPSR        (*(char *) (0)) //= 0xF4;
#define SPDR        (*(char *) (0)) //= 0xF5;
#define AINDIDS     (*(char *) (0)) //= 0xF6;
#define EIPH        (*(char *) (0)) //= 0xF7;

#define SCON_1      (*(char *) (0)) //= 0xF8;
#define PDTEN       (*(char *) (0)) //= 0xF9; //TA Protection
#define PDTCNT      (*(char *) (0)) //= 0xFA; //TA Protection
#define PMEN        (*(char *) (0)) //= 0xFB;
#define PMD         (*(char *) (0)) //= 0xFC;
#define EIP1        (*(char *) (0)) //= 0xFE;
#define EIPH1       (*(char *) (0)) //= 0xFF;

/*  BIT Registers  */
/*  SCON_1  */
#define SM0_1      (*(char *) (0)) //= SCON_1^7;
#define FE_1       (*(char *) (0)) //= SCON_1^7; 
#define SM1_1      (*(char *) (0)) //= SCON_1^6; 
#define SM2_1      (*(char *) (0)) //= SCON_1^5; 
#define REN_1      (*(char *) (0)) //= SCON_1^4; 
#define TB8_1      (*(char *) (0)) //= SCON_1^3; 
#define RB8_1      (*(char *) (0)) //= SCON_1^2; 
#define TI_1       (*(char *) (0)) //= SCON_1^1; 
#define RI_1       (*(char *) (0)) //= SCON_1^0; 

/*  ADCCON0  */
#define ADCF       (*(char *) (0)) //= ADCCON0^7;
#define ADCS       (*(char *) (0)) //= ADCCON0^6;
#define ETGSEL1    (*(char *) (0)) //= ADCCON0^5;
#define ETGSEL0    (*(char *) (0)) //= ADCCON0^4;
#define ADCHS3     (*(char *) (0)) //= ADCCON0^3;
#define ADCHS2     (*(char *) (0)) //= ADCCON0^2;
#define ADCHS1     (*(char *) (0)) //= ADCCON0^1;
#define ADCHS0     (*(char *) (0)) //= ADCCON0^0;

/*  PWMCON0  */
#define PWMRUN     (*(char *) (0)) //= PWMCON0^7;
#define LOAD       (*(char *) (0)) //= PWMCON0^6;
#define PWMF       (*(char *) (0)) //= PWMCON0^5;
#define CLRPWM     (*(char *) (0)) //= PWMCON0^4;


/*  PSW */
#define CY         (*(char *) (0)) //= PSW^7;
#define AC         (*(char *) (0)) //= PSW^6;
#define F0         (*(char *) (0)) //= PSW^5;
#define RS1        (*(char *) (0)) //= PSW^4;
#define RS0        (*(char *) (0)) //= PSW^3;
#define OV         (*(char *) (0)) //= PSW^2;
#define P          (*(char *) (0)) //= PSW^0;

/*  T2CON  */
#define TF2        (*(char *) (0)) //= T2CON^7;
#define TR2        (*(char *) (0)) //= T2CON^2;
#define CM_RL2     (*(char *) (0)) //= T2CON^0;
 
/*  I2CON  */
#define I2CEN      (*(char *) (0)) //= I2CON^6;
#define STA        (*(char *) (0)) //= I2CON^5;
#define STO        (*(char *) (0)) //= I2CON^4;
#define SI         (*(char *) (0)) //= I2CON^3;
#define AA         (*(char *) (0)) //= I2CON^2;
#define I2CPX  	(*(char *) (0)) //= I2CON^0;

/*  IP  */  
#define PADC       (*(char *) (0)) //= IP^6;
#define PBOD       (*(char *) (0)) //= IP^5;
#define PS         (*(char *) (0)) //= IP^4;
#define PT1        (*(char *) (0)) //= IP^3;
#define PX1        (*(char *) (0)) //= IP^2;
#define PT0        (*(char *) (0)) //= IP^1;
#define PX0        (*(char *) (0)) //= IP^0;

/*  P3  */  
#define P30    	(*(char *) (0)) //= P3^0;


/*  IE  */
#define EA         (*(char *) (0)) //= IE^7;
#define EADC       (*(char *) (0)) //= IE^6;
#define EBOD       (*(char *) (0)) //= IE^5;
#define ES         (*(char *) (0)) //= IE^4;
#define ET1        (*(char *) (0)) //= IE^3;
#define EX1        (*(char *) (0)) //= IE^2;
#define ET0        (*(char *) (0)) //= IE^1;
#define EX0        (*(char *) (0)) //= IE^0;

/*  P2  */ 
#define P20        (*(char *) (0)) //= P2^0;

/*  SCON  */
#define SM0        (*(char *) (0)) //= SCON^7;
#define FE         (*(char *) (0)) //= SCON^7; 
#define SM1        (*(char *) (0)) //= SCON^6; 
#define SM2        (*(char *) (0)) //= SCON^5; 
#define REN        (*(char *) (0)) //= SCON^4; 
#define TB8        (*(char *) (0)) //= SCON^3; 
#define RB8        (*(char *) (0)) //= SCON^2; 
#define TI         (*(char *) (0)) //= SCON^1; 
#define RI         (*(char *) (0)) //= SCON^0; 

/*  P1  */     
#define P17  		(*(char *) (0)) //= P1^7;
#define P16  		(*(char *) (0)) //= P1^6;
#define TXD_1  	(*(char *) (0)) //= P1^6; 
#define P15  		(*(char *) (0)) //= P1^5;
#define P14  		(*(char *) (0)) //= P1^4;
#define SDA  		(*(char *) (0)) //= P1^4;    
#define P13  		(*(char *) (0)) //= P1^3;
#define SCL  		(*(char *) (0)) //= P1^3;  
#define P12        (*(char *) (0)) //= P1^2; 
#define P11        (*(char *) (0)) //= P1^1;
#define P10        (*(char *) (0)) //= P1^0;

/*  TCON  */
#define TF1        (*(char *) (0)) //= TCON^7;
#define TR1        (*(char *) (0)) //= TCON^6;
#define TF0        (*(char *) (0)) //= TCON^5;
#define TR0        (*(char *) (0)) //= TCON^4;
#define IE1        (*(char *) (0)) //= TCON^3;
#define IT1        (*(char *) (0)) //= TCON^2;
#define IE0        (*(char *) (0)) //= TCON^1;
#define IT0        (*(char *) (0)) //= TCON^0;

/*  P0  */  
#define P07        (*(char *) (0)) //= P0^7;
#define RXD        (*(char *) (0)) //= P0^7;
#define P06        (*(char *) (0)) //= P0^6;
#define TXD        (*(char *) (0)) //= P0^6;
#define P05        (*(char *) (0)) //= P0^5;
#define P04        (*(char *) (0)) //= P0^4;
#define STADC      (*(char *) (0)) //= P0^4;
#define P03        (*(char *) (0)) //= P0^3;
#define P02        (*(char *) (0)) //= P0^2;
#define RXD_1      (*(char *) (0)) //= P0^2;
#define P01        (*(char *) (0)) //= P0^1;
#define MISO       (*(char *) (0)) //= P0^1;
#define P00        (*(char *) (0)) //= P0^0;
#define MOSI       (*(char *) (0)) //= P0^0;

#endif /* __SDCC */

#endif /* __MAIN_H__ */

对于寄存器定义处理,可以直接 copy原来的 keil格式的 include文件内容,然后直接把关键字 sfrsbit 替换成 #define ,再把 = 替换成 (*(char *) (0)) //= 这样就,是不是很 nice。



七、Makefile程序化管理

SDCC并不支持同时编译多个源代码文件,所以多文件项目的编译需要分步进行。假如你的项目包含 foo1.c foo2.c main.c 三个文件,那么编译过程如下:

sdcc -c foo1.c
sdcc -c foo2.c
sdcc main.c foo1.rel foo2.rel

还可以使用以下方式编译:

sdcc -c main.c
sdcc main.rel foo1.rel foo2.rel

值得一提的是,sdcc与 gcc的命令支持还是有点出入的,但大部分都兼容,因此具体支持哪些命令,需要去翻看 sdcc的手册。

对于多文件项目最好是写一个 Makefile文件来维护或者写一个 bat批处理文件。这里就直接给出我所用的 Makefile文件吧,分析什么的,可以看以前的链接:;如果你是 Linux用户,应该很清楚这些命令,若果诸位大佬有懂得多的,还请在评论区不吝赐教:

######################################
# target path
######################################
TARGET = MS51FB

#######################################
# Build path
#######################################
BUILD_DIR = build

######################################
# source
######################################
SRCDIR = App
LIB_SRC = #Libraries/StdDriver/src

USER_SRC = source#/bsp.c \
source/bsp_time.c \
source/bsp_uart.c

# C sources
C_SOURCES := $(wildcard $(SRCDIR)/*.c $(LIB_SRC)/*.c)
C_SOURCES += $(wildcard $(USER_SRC)/*.c)
ASM_SOURCES = $(wildcard $(SRCDIR)/*.asm)

C_SRC_FILE = $(notdir $(C_SOURCES))
C_OBJ_FILE = $(C_SRC_FILE:%.c=%.c.rel)

ASM_SRC_FILE = $(notdir $(ASM_SOURCES))
ASM_OBJ_FILE = $(ASM_SRC_FILE:%.asm=%.asm.rel)

######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = 

#######################################
# cross compile
#######################################
PREFIX = 

CC = $(PREFIX)sdcc 
AS = $(PREFIX)sdas8051

MCU_MODEL = -mmcs51

RM = -rm -rf 
MAKE = make 

# ------------------------------------------------------
# Usually SDCC's small memory model is the best choice.  If
# you run out of internal RAM, you will need to declare
# variables as "xdata", or switch to larger model

# Memory Model (small, medium, large, huge)
MODEL  = --model-small
# ------------------------------------------------------
# Memory Layout
# PRG Size = 4K Bytes
#CODE_SIZE = --code-loc 0x0000 --code-size 18432
CODE_SIZE = --code-size 18432
# INT-MEM Size = 256 Bytes
#IRAM_SIZE = --idata-loc 0x0000  --iram-size 256
IRAM_SIZE = --iram-size 256
# EXT-MEM Size = 4K Bytes
#XRAM_SIZE = --xram-loc 0x0000 --xram-size 768
XRAM_SIZE = --xram-size 768

# ------------------------------------------------------

#######################################
# FLAGS
#######################################
# macros for gcc
# AS defines
AS_DEFS = 

# C defines
C_DEFS = 


# AS includes
AS_INCLUDES = 

# C includes
C_INCLUDES =  \
-IApp \
-ILibraries/Device/Include \
-ILibraries/StdDriver/inc \
-Iinclude

# libraries
LIBS = 
LIBDIR = 

# compile gcc flags
ASFLAGS = -l -s

CFLAGS = $(MCU_MODEL) $(C_DEFS) $(C_INCLUDES) $(MODEL) --out-fmt-ihx --no-xinit-opt --peep-file tools/peep.def

ifeq ($(DEBUG), 1)
CFLAGS += 
else
CFLAGS += $(OPT)
endif


#######################################
# LDFLAGS
#######################################
LDFLAGS = $(LIBDIR) $(LIBS) $(MCU_MODEL) $(MODEL) $(CODE_SIZE) $(IRAM_SIZE) $(XRAM_SIZE) --out-fmt-ihx

# default action: build all
.PHONY: all
all: $(BUILD_DIR)/$(TARGET).hex

#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(C_OBJ_FILE))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(ASM_OBJ_FILE))

$(BUILD_DIR)/%.c.rel: $(USER_SRC)/%.c
	$(CC) -o $@ $(CFLAGS) -c $^

$(BUILD_DIR)/%.c.rel: $(LIB_SRC)/%.c
	$(CC) -o $@ $(CFLAGS) -c $^

$(BUILD_DIR)/%.c.rel: $(SRCDIR)/%.c
	$(CC) -o $@ $(CFLAGS) -c $^

$(BUILD_DIR)/%.asm.rel: $(SRCDIR)/%.asm
	$(AS) $(ASFLAGS) -o $@ $^ 

$(BUILD_DIR)/%.ihx: $(OBJECTS)
	$(CC) -o $@ $(LDFLAGS) $^

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.ihx | $(BUILD_DIR)
	packihx $^ > $@

$(BUILD_DIR):
	mkdir $@


#######################################
# clean up
#######################################
.PHONY: clean
clean:
	$(RM)$(BUILD_DIR)/*

#######################################
# build asm
#######################################
HEADER_FILE = MS51_16K.h
HEADER_PATH = App

disasm: $(BUILD_DIR)/$(TARGET).hex
	./tools/mcs51-disasm.pl -M $(HEADER_FILE) -I $(HEADER_PATH) -fl -rj -as $(BUILD_DIR)/$(TARGET).hex > $(BUILD_DIR)/$(TARGET).a51

# *** EOF ***

这里说一下,sdcc特有的 packihx 命令是用来产生 Intel HEX文件的;mkdir 命令在 sdcc中是不支持,可以把他删掉,由于这一点,所以得保留着 build文件夹存放编译文件,如果删除的话,执行会出错;执行 disasm 命令需要工具链 mcs51-disasm.pl的支持,它的说明如下:

Emscripten编译sdl并且使用cmake sdcc编译器_sdcc_08


;另外,+= 好像也不支持单文件添加,看 USER_SRC处,只能通过 wildcard 扫描添加。。。不知为啥,望大佬解答一二。


八、程序编译

#include "main.h"
#include "bsp.h"
#include "bsp_uart.h"
#include "bsp_time.h"
#include "bsp_eeprom.h"
#include "bsp_adc.h"
#include "bsp_pwm.h"
#include "bsp_wdt.h"


__bit BIT_TMP;			//EA暂存(对应官方库)

#define ENABLE_WDT				1

/* ISR中断函数原型声明(原因看手册 P3.8章节) */
void UART0_ISR(void) __interrupt (4);
void Timer3_ISR(void) __interrupt (16);


/************************************************
函数名称 : System_Start
功    能 : 系统初始化
参    数 : 无
返 回 值 : 无
*************************************************/
void System_Start(void)
{
    clr_EA;

    Bsp_Init();
    UART0_Timer1_Init();
    Timer3_Init(TIME_DIV16, 15000);  // 10ms
	// Timer0_Init();
	// ADC_Config();
	// PWM0_Init();

#if ENABLE_WDT
    WDT_Init();
#endif /* ENABLE_WDT */

    set_EA;
}

/************************************************
函数名称 : main
功    能 : 主函数入口
参    数 : 无
返 回 值 : int
*************************************************/
int main(void)
{
    uint16_t i = 0;

    System_Start();

	P12_QUASI_MODE;
    P12 = 1;
    for(i=0; i<3; i++)
    {
        P12 ^= 1;
        SoftwareDelay_ms(0xFF);
    }

#if ENABLE_WDT
	WDT_EnableOpen();
	
#else
	WDT_DisableClose();
	
#endif /* ENABLE_WDT */
	
    while(1)
    {
#if ENABLE_WDT
        WDT_ReloadCounter();
#endif /* ENABLE_WDT */

        printf_small("\n Hello world!");
        SoftwareDelay_ms(0xFF);
    }
}


/*---------------------------- END OF FILE ----------------------------*/

然后 make 编译,最终输出(方框处显示成功):

Emscripten编译sdl并且使用cmake sdcc编译器_8051_09

下载进去,就可以看到 Hello world! 在不停的打印输出了:

Emscripten编译sdl并且使用cmake sdcc编译器_#define_10

在这里,需要注意以下几点:

1、中断函数必须在 main函数文件中给出 ISR原型,不然就无法进中断。详细请看手册的 P3.8章节。以下摘自部分解释:

Emscripten编译sdl并且使用cmake sdcc编译器_#define_11

2、一般,我们在 C程序中打印输出是调用 printf 语句进行输出的,但在 sdcc上,比较建议使用 printf_small 输出,因为对于 8位微控制起来说,资源是很紧缺的,使用 printf_small 已经可以满足一般输出需求了,当然以上仅限于输出整型以及字符型变量;对于浮点型变量,需要使用特殊的指令对程序进行编译才能得到输出效果,具体的介绍可以看手册的 P3.14.1章节。

3、如果是使用 bin文件烧写到芯片上,可以用 sdcc自带的 makebin.exe 命令行工具进行转换(不过这个转换出来文件比较大),路径可以在 …\SDCC\bin下找到,通过以下命令:makebin xxx.ihx > xxx.bin

或者利用 hex2bin,下载地址:https://sourceforge.net/projects/hex2bin/files/latest/download,这个的命令则是:hex2bin xxx.hex > xxx.bin

Makefile下的 bin文件生成命令:

Emscripten编译sdl并且使用cmake sdcc编译器_makefile_12

两者相比之下,由于前者是做了剩余空间填充处理的,所以转换出来的文件比较大,个人更倾向于后者。



九、总结

1、不能使用 double数据类型,否则报错。

2、make编译只能根据法则编译对应文件夹的全部源文件,不能选择编译相应源文件。

3、中断函数这里是要在 main函数所在文件处进行原型声明,否者是无法进入中断程序,原因不声明是并没用把中断函数的向量地址加载到执行文件中。

4、sdcc使用的关键字是跟 keilC51里面的关键字不同的;对于一些非 ANSI C的关键字,SDCC均采用双下滑线开头的方式定义,具体可看 sdcc手册。

5、sdcc支持的命令行命令,跟我们平常用的 gcc命令行命令有所不同,具体翻看 sdcc手册。

6、一般串口重定向后,是使用 printf函数输出,但在 sdcc编译器中要改用 printf_small这个函数进行替代。

7、sdcc在编译文件时,会把用不到的代码也编译进来,所以如果空间紧张,建议注释掉一些无关的代码,避免代码空间膨胀。

十、相关链接

SDCC Compiler User Guide

8051 C Development Using SDCC(Small Device C Compiler)

Nuvoton N76E003 with SDCC

新唐 N76E003 8051 1T 单片机入坑记录

SDCC编译器简明使用教程

使用免费的SDCC C编译器开发DS89C430/450系列微控制器固件

51单片机之开发环境使用VSCode结合SDCC取代Keil

使用Visual Studio Code + CMake + SDCC 进行C51 开发的一次尝试

台湾同胞对 SDCC的使用介绍

sdcc man阅读笔记

SDCC printf 函數介紹

开源SDCC编译器(一)-- 基本介绍