写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
SDCC是一个小型设备的 C语言编译器,该编译器支持标准 C语言;相对于 GCC编译器来说可能知名度不是很高,但它跟 GCC一样,是跨平台,并且遵循 GPL开源协议。本次实验是使用 nuvoton的 MS51系列单片机来操作(基于 8051内核)
sdcc官方网址:http://sdcc.sourceforge.net/
Wiki主页:https://sourceforge.net/p/sdcc/wiki/Home/
目录
一、关于 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后,通过指令查看版本号可以看到它所支持的设备类型:
然后,这里有个帖子有讨论 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命令的终端控制台就行)
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、支持的数据类型
2、存储类型
相对于 Keil,其存储类型关键字加上了前缀 ’ __ ’ 双下划线,这也是 SDCC的特色风格。
__data
:这是小内存模式的默认(通用)地址空间,声明的变量将被放在 8051内核的直接寻址 RAM中。
__idata
:这个地址空间中的变量将被分配到 8051的内部 RAM的间接可寻址部分。
__pdata
:存储类型 pdata用于访问分页的外部数据存储器。
__xdata
:这个地址空间中的变量将被放在外部 RAM中。
__code
:存放程序代码的内存地址空间。
3、存储器模式
SDCC支持四种存储器模式:(small, medium, large, huge)
采用 SDCC编译时,默认为小模式。如果要强制 SDCC使用特定的存储器模式,可使用以下命令行参数(在手册的 P3.3.6章节可以查询得到):
类似于 Keil的这个选项(只不过 Keil是 GUI操作,SDCC是命令操作):
关于不同模式下变量的存储位置不一样,可以查阅手册 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,里面有提供一个转换工具,当然你也可以自己去写一个程序。
五、工程构建
因为是用 VSCode做编辑开发,只要有 .vscode文件夹的配置项就可以了,剩下的编译过程就交给 sdcc编译,所以工程的构建比较简单,文件夹创建以及移植 SDK库都方便,以下是我的工程文件分布(看起来还是比较容易理解的):
这里 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文件内容,然后直接把关键字 sfr
、sbit
替换成 #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文件吧,分析什么的,可以看以前的链接:https://blog.csdn.net/qq_42992084/article/details/95893283;如果你是 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的支持,它的说明如下:
;另外,+=
好像也不支持单文件添加,看 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
编译,最终输出(方框处显示成功):
下载进去,就可以看到 Hello world!
在不停的打印输出了:
在这里,需要注意以下几点:
1、中断函数必须在 main函数文件中给出 ISR原型,不然就无法进中断。详细请看手册的 P3.8章节。以下摘自部分解释:
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文件生成命令:
两者相比之下,由于前者是做了剩余空间填充处理的,所以转换出来的文件比较大,个人更倾向于后者。
九、总结
1、不能使用 double数据类型,否则报错。
2、make编译只能根据法则编译对应文件夹的全部源文件,不能选择编译相应源文件。
3、中断函数这里是要在 main函数所在文件处进行原型声明,否者是无法进入中断程序,原因不声明是并没用把中断函数的向量地址加载到执行文件中。
4、sdcc使用的关键字是跟 keilC51里面的关键字不同的;对于一些非 ANSI C的关键字,SDCC均采用双下滑线开头的方式定义,具体可看 sdcc手册。
5、sdcc支持的命令行命令,跟我们平常用的 gcc命令行命令有所不同,具体翻看 sdcc手册。
6、一般串口重定向后,是使用 printf函数输出,但在 sdcc编译器中要改用 printf_small这个函数进行替代。
7、sdcc在编译文件时,会把用不到的代码也编译进来,所以如果空间紧张,建议注释掉一些无关的代码,避免代码空间膨胀。
十、相关链接
8051 C Development Using SDCC(Small Device C Compiler)
使用免费的SDCC C编译器开发DS89C430/450系列微控制器固件
51单片机之开发环境使用VSCode结合SDCC取代Keil