SDCC-Linux下的51 MCU编译器

sdcc (小型设备c编译器)是为8位微控制器开发的免费c编译器。尽管兼容多种不同体系结构,但sdcc c编译器更适合8051内核。 sdcc是命令行固件开发工具,含预处理器、编译器、汇编器、链接器和优化器。安装文件中还捆绑了sdcdb、类似于gdb (gnu调试器)的源码级调试器。无错的程序采用sdcc编译、链接后,生成一个intel十六进制格式的加载模块。
安装sdcc免费c编译器
如果需要安装sdcc,请从网址下载sdcc最新版本。虽然也可使用该软件的日常构建(nightly builds)版,但通常最安全的方式是下载经过完全测试的最新发布版。
在“download”页为不同的操作系统提供不同的sdcc。如果您使用运行microsoft windows的pc,请下载并运行win32自解压sdcc安装文件。
安装程序时会出现一个提示,询问是否将含有程序二进制文件的目录添加到您的路径中。
采用sdcc编译器编译一个简单的c程序
为确保sdcc已在硬盘上正确安装,请在命令提示符下键入sdcc --version,然后回车,窗口中应出现图1所示文本(实际文本与您下载的sdcc版本有关):
图1. 通过版本检查确认sdcc是否正确安装.
为测试包含路径,生成名为sdcctest.c的文件,并将以下源代码复制到该文件中。
#include char str[6] = maxim;bit flag;void main(void){ if (strcmp(str,maxim) == 0) flag = 0; else flag = 1; while(1); // program loop}  
以普通ascii格式(如使用microsoft记事本程序)保存该文件。在命令提示符下,键入sdcc sdcctest.c,然后回车。如像图2那样没有任何反应,则说明程序编译成功。
图2. 编译简单的sdcc程序.
当源代码编译成功时,sdcc会生成多个文件。在编译目录中可找到以下文件:
sdcctest.asm:程序的汇编文件
sdcctest.lst:程序的列表文件
sdcctest.rst:被链接器更新的列表文件
sdcctest.map:被链接器更新的最终存储器映射
sdcctest.ihx:intel十六进制格式的加载模块。该文件必须被下载到微控制器中。
同时还生成其它文件(多数用于源码级调试器)。请阅读sdcc文档了解更详细的信息。
sdcc专有数据类型
sdcc支持多数ansi-c数据类型。此外,sdcc支持多种扩展数据类型(也称为存储类型),以充分利用8051体系结构的优势,这将在后面以实例说明。
与一些商用8051微控制器开发工具不同,sdcc仅支持声明位和字节可寻址特殊功能寄存器。尽管8051汇编语言支持,但sdcc并不支持共享位和字节可寻址ram。为证实这一点,请观察以下代码实例和编译完的汇编代码。
c源程序:
union{ unsigned char a_byte; struct { unsigned char bit0 : 1; unsigned char bit1 : 1; unsigned char bit2 : 1; unsigned char bit3 : 1; unsigned char bit4 : 1; unsigned char bit5 : 1; unsigned char bit6 : 1; unsigned char bit7 : 1; } a_bit;} a;bit b;void main(void){ a.a_byte = 0x05; a.a_bit.bit6 = 1; b = 1; while(1); // program loop}assembly listing (.rst file): ... 159 ;sdcctest.c a.a_byte = 5; 160 ; genpointerset 161 ; gennearpointerset 162 ; gendatapointerset 0031 75 21 05 163 mov _a,#0x05 164 ;sdcctest.c a.a_bit.bit6 = 1; 165 ; genpointerset 166 ; gennearpointerset 0034 78 21 167 mov r0,#_a 168 ; genpackbits 0036 e6 169 mov a,@r0 0037 44 40 170 orl a,#0x40 0039 f6 171 mov @r0,a 172 ;sdcctest.c b = 1; 173 ; genassign 003a d2 00 174 setb _b 175 ;sdcctest.c while(1); // program loop ...  
尽管在声明中“a”看起来是位寻址存储器,但汇编列表文件(来自由sdcc生成的.rst文件)表明变量并没有使用位寻址。在列表中不要混淆“a”和“_a”。“a”指累加器,而“_a”指变量。
near/data
以near或data存储类型声明的变量将被放在8051内核的直接寻址ram中。ds89c430/450系列微控制器具有128字节直接寻址存储器,这是8051能够访问的速度最快的存储器,生成的汇编代码只需一个mov指令即可读写该ram中的数据。
#include sdcc_reg420.hdata unsigned char outport0 = 0x4a;void main(void){ p0 = outport0; while (1); // program loop}  
该例中使用的定义文件sdcc_reg420.h见附录a。
far/xdata
以far或xdata存储类型声明的变量将被放在外部ram中。这样开发人员能够访问更大的ram空间,但生成的汇编代码需要使用movx指令来读写该存储器,这要求将外部存储器地址装入数据指针。
ds89c430/450系列微控制器含有1k字节的内部sram,可被用于以far/xdata声明的变量。注意,电源管理寄存器(pmr)中的dme1:0位在该存储器初始化或使用之前,必须先被置为内部sram模式。
#include sdcc_reg420.hxdata unsigned char ioports[2];void main(void){ pmr |= 0x01; // enable internal 1k sram ioports[0] = 0x4a; ioports[1] = 0x56; p0 = ioports[0]; p1 = ioports[1]; while (1); // program loop}  
idata
以idata存储类型声明的变量将被放在8051内核的间接寻址存储器中。间接可寻址存储器与直接寻址存储器类似,在8051内核中共有128字节(不包括特殊功能寄存器)。但是,访问idata需要额外的mov命令将ram地址移至工作寄存器中。
#include sdcc_reg420.hidata unsigned int port0_x2;void main(void){ while (1) // program loop { port0_x2 = p0 * 2; }}  
pdata
存储类型pdata用于访问分页的外部数据存储器。该存储类型超出了本应用笔记范畴,有兴趣的读者可以阅读sdcc文档的pdata部分。
code
以code存储类型声明的变量将被放在程序存储器(ds89c430/450微控制器内部的闪存)中。对于sdcc来说,这类变量只读,因此常使用code来声明常量(如:查找表)。
#include sdcc_reg420.hcode unsigned char out[10] = {0x03,0x45,0xfa,0x43,0xdd, 0x1a,0xe0,0x00,0x87,0x91};void main(void){ data unsigned char i = 0; while (1) // program loop { p0 = out[i++]; if (i==10) i=0; }}  
bit
以bit存储类型声明的变量被放在8051内核的位寻址存储器中。8051内核的16字节直接寻址ram可用作位寻址存储器(字节0x20至0x2f),提供128个可寻址位。使用该类变量作为标志位可高效利用存储空间。
#include sdcc_reg420.h#define escape 0x1bbit esc_char_flag = 0;void main(void){ p1 = 0x00; while (!esc_char_flag) { if (p0 == escape) esc_char_flag = 1; } p1 = 0xff; while (1); // program loop}  
sfr
存储类型sfr被用来定义8051内核专有的特殊功能寄存器(sfr)。附录a定义文件中使用sfr标识符定义了ds89c430/450微控制器中的所有sfr。
注意,下面的实例已定义了sfr,因此没有必要包含定义文件s
dcc_reg420.h。sfr at 0x80 p0;sfr at 0x90 p1;void main(void){ p0 = 0x00; p1 = 0xff; while (1); // program loop}  
sbit
存储类型sbit用于定义可位寻址sfr中的特殊位。在8051内核中,地址以0或者8 (十六进制)结束的所有sfr均可位寻址。附录a定义文件中使用sbit标识符定义了ds89c430/450微控制器sfr的所有可寻址位。
sfr at 0x80 p0; // port 0sbit at 0x80 p0_0; // port 0 bit 0sbit at 0x81 p0_1; // port 0 bit 1sbit at 0x82 p0_2; // port 0 bit 2sbit at 0x83 p0_3; // port 0 bit 3sbit at 0x84 p0_4; // port 0 bit 4sbit at 0x85 p0_5; // port 0 bit 5sbit at 0x86 p0_6; // port 0 bit 6sbit at 0x87 p0_7; // port 0 bit 7void main(void){ p0 = 0x00; // p0 = 0x00 p0_4 = 1; // p0 = 0x10 while (1); // program loop}  
绝对寻址
sdcc支持采用at标识符的绝对寻址。但是,sdcc不跟踪声明的绝对寻址变量,而且可能在其地址声明其它变量,造成相互覆盖。
以下程序显示了有趣的潜在错误。
#include sdcc_reg420.hunsigned char a = 0x4a;unsigned int b = 0x0000;unsigned char c[64] = {0x00};unsigned char at 0x0010 y;unsigned char at 0x0010 z;void main(void){ for(b=0; b<64; b++) c[b] = 0xaa; y = 0xf1; z = 0xf2; a = c[5]; while (1); // program loop}  
使用sdcc时,尽管变量y和z分配同一个位置,也可进行无错误或警告的编译。如果要运行该程序,我们认为程序(a = c[5])中a最终将被设置为0xaa。但情况并非如此。a最终被分配的值为0xf2。
如果查看sdcc生成的.map文件中以下几行语句(显示每个变量的实际地址),便会明白这种情况的原因。
area addr size decimal bytes (attributes)-------------------------------- ---- ---- ------- ----- ------------. .abs. 0000 0000 = 0. bytes (abs,ovr) value global -------- -------------------------------- ... 0010 _y 0010 _z ...area addr size decimal bytes (attributes)-------------------------------- ---- ---- ------- ----- ------------dseg 0008 0043 = 67. bytes (rel,con) value global -------- -------------------------------- 0008 _a 0009 _b 000b _c  
注意,变量名称前的下划线是由编译器添加的。如果c位于地址0x000b,长度为64字节,那么它将覆盖位于地址0x0010处的变量y和z。
绝对寻址可用于仿真位寻址变量。在下面的例子中,在位寻址存储器的最后一个字节处定义变量n_byte。然后,在8051内核位寻址存储器的最后8位定义n_bit0至n_bit7。由于这种重叠,可采用变量n_bit0至n_bit7对变量n_byte进行位寻址。
#include sdcc_reg420.hdata unsigned char at 0x002f n_byte;bit at 0x78 n_bit0;bit at 0x79 n_bit1;bit at 0x7a n_bit2;bit at 0x7b n_bit3;bit at 0x7c n_bit4;bit at 0x7d n_bit5;bit at 0x7e n_bit6;bit at 0x7f n_bit7;void main(void){ n_byte = 0x00; n_bit4 = 1; p0 = n_byte; // p0 = 0x10 while (1); // program loop}  
存储器模式
sdcc支持两种存储器模式:小模式和大模式。使用存储器小模式时,sdcc在内部ram中声明所有不带存储类型的变量(如,data、idata、xdata、pdata、bit、code)。使用存储器大模式时,sdcc在外部ram中声明所有不带存储类型的变量。
采用sdcc编译时,默认为小模式。如果要强制sdcc使用特定的存储器模式,可使用以下命令行参数:
sdcc --model-small sdcctest.c  
或者
sdcc --model-large sdcctest.c  
不要链接使用不同存储器模式编译的模块或目标文件。
sdcc的中断
定义中断服务程序(isr)时,应使用以下格式:
void interrupt_identifier (void) interrupt interrupt_number using bank_number{ ...}  
其中interrupt_identifier可以是任意有效的sdcc函数名,interrupt_number代表中断在中断向量表中的位置。表1列出了ds89c430/450系列微控制器支持的每个中断的中断号。可选参数bank_number用于指示sdcc采用哪个寄存器区存储isr中的局部变量。
表1. ds89c430/450中断服务程序的中断号.
interrupt name interrupt vector interrupt number
external interrupt 0 0x03 0
timer 0 overflow 0x0b 1
external interrupt 1 0x13 2
timer 1 overflow 0x1b 3
serial port 0 0x23 4
timer 2 overflow 0x2b 5
power fail 0x33 6
serial port 1 0x3b 7
external interrupt 2 0x43 8
external interrupt 3 0x4b 9
external interrupt 4 0x53 10
external interrupt 5 0x5b 11
watchdog interrupt 0x63 12
sdcc处理与isr编程相关的许多细节,如使用堆栈保存和恢复累加器及数据指针。(实际上所有函数均进行此操作。请参考sdcc手册中的_naked关键字来禁止在堆栈中保存这些变量)。其它细节不由sdcc处理(因为合理的原因),这对嵌入式编程开发新手带来一定难度。许多这类问题属于高级编程范畴,已超出本文讨论的范围,sdcc手册和嵌入式编程教材可提供更深入的内容。
使用中断时,应遵循以下原则。
可在isr内部写、并可在isr外部访问的每个全局变量必须被声明为volatile,以确保优化器不会删除与该变量相关的指令。
以非原子(non-atomic)方式使用数据时(如,访问16位/32位变量)应禁止中断。当对变量的访问为原子方式时,处理器无法中断(带有isr)对存储器的数据存取。
避免在isr内部调用函数。如果必须这样做,需要将函数声明为reentrant (参见sdcc手册),这样函数中的所有局部变量被分配在堆栈中,而不是在ram中。
注意,如果被sdcc使用的含isr的源文件不含main()函数,那么含main()函数的源文件应包含每个isr的函数原型。
下面的例子定义了一个处理串行通信接口1 (sci_1)的中断服务程序(isr)。程序接收来自sci_1接收器的一个字节,将接收字节加1,通过sci_1发射器连续发送出去。
#include sdcc_reg420.hvolatile unsigned char n = 0x4a;void sci1isr (void) interrupt 7{ if (ri_1) { n = sbuf1+1; // save rx byte ri_1 = 0; // reset sci_1 rx interrupt flag } else if (ti_1) { sbuf1 = n; // load byte to tx ti_1 = 0; // reset sci_1 tx interrupt flag }}void main(void){ // 1. init serial port ea = 0; // enable global interrupt mask scon1 = 0x50; // set sci_1 to 8n1, rx enabled tmod |= 0x20; // set timer 1 as mode 2 th1 = 0xdd; // set sci_1 for 2400 baud tr1 = 1; // enable timer 1 es1 = 1; // enable interrupts for sci_1 ea = 1; // disable global interrupt mask // 2. initiate sci_1 tx sbuf1 = n; // 3. program loop... while (1);}  
内嵌汇编
sdcc完全支持内嵌汇编。使用该功能时,汇编代码应嵌在_asm和_endasm标识符之间。注意,通过在变量名前加下划线,内嵌汇编代码也可以访问c变量。以下实例采用内嵌汇编执行nop指令(用于在微控制器内部占用一个时钟周期),然后将变量a加1。
#include sdcc_reg420.hunsigned char a;void main(void){ // program loop... while (1) { a = p0; _asm nop nop nop inc _a _endasm; p1 = a; }}  
sdcc还可用于c和汇编函数接口,这是较深入的问题;请参考sdcc手册,了解详细信息。
附录a:ds89c430/450的sfr定义文件(sdcc_reg420.h)
/* * sdcc_reg420.h * * maxim integrated products * * special function register definitions file * ds89c430/450 ultra-high speed 8051-compatible ucs * */#ifndef __reg420_h__#define __reg420_h__/* byte registers */sfr at 0x80 p0;sfr at 0x81 sp;sfr at 0x82 dpl;sfr at 0x83 dph;sfr at 0x84 dpl1;sfr at 0x85 dph1;sfr at 0x86 dps;sfr at 0x87 pcon;sfr at 0x88 tcon;sfr at 0x89 tmod;sfr at 0x8a tl0;sfr at 0x8b tl1;sfr at 0x8c th0;sfr at 0x8d th1;sfr at 0x8e ckcon;sfr at 0x90 p1;sfr at 0x91 exif;sfr at 0x96 ckmod;sfr at 0x98 scon0;sfr at 0x99 sbuf0;sfr at 0x9d acon;sfr at 0xa0 p2;sfr at 0xa8 ie;sfr at 0xa9 saddr0;sfr at 0xaa saddr1;sfr at 0xb0 p3;sfr at 0xb1 ip1;sfr at 0xb8 ip0;sfr at 0xb9 saden0;sfr at 0xba saden1;sfr at 0xc0 scon1;sfr at 0xc1 sbuf1;sfr at 0xc2 romsize;sfr at 0xc4 pmr;sfr at 0xc5 status;sfr at 0xc7 ta;sfr at 0xc8 t2con;sfr at 0xc9 t2mod;sfr at 0xca rcap2l;sfr at 0xcb rcap2h;sfr at 0xcc tl2;sfr at 0xcd th2;sfr at 0xd0 psw;sfr at 0xd5 fcntl;sfr at 0xd6 fdata;sfr at 0xd8 wdcon;sfr at 0xe0 acc;sfr at 0xe8 eie;sfr at 0xf0 b;sfr at 0xf1 eip1;sfr at 0xf8 eip0;/* bit registers *//* p0 */sbit at 0x80 p0_0;sbit at 0x81 p0_1;sbit at 0x82 p0_2;sbit at 0x83 p0_3;sbit at 0x84 p0_4;sbit at 0x85 p0_5;sbit at 0x86 p0_6;sbit at 0x87 p0_7;/* tcon */sbit at 0x88 it0;sbit at 0x89 ie0;sbit at 0x8a it1;sbit at 0x8b ie1;sbit at 0x8c tr0;sbit at 0x8d tf0;sbit at 0x8e tr1;sbit at 0x8f tf1;/* p1 */sbit at 0x90 p1_0;sbit at 0x91 p1_1;sbit at 0x92 p1_2;sbit at 0x93 p1_3;sbit at 0x94 p1_4;sbit at 0x95 p1_5;sbit at 0x96 p1_6;sbit at 0x97 p1_7;/* scon0 */ sbit at 0x98 ri_0;sbit at 0x99 ti_0;sbit at 0x9a rb8_0;sbit at 0x9b tb8_0;sbit at 0x9c ren_0;sbit at 0x9d sm2_0;sbit at 0x9e sm1_0;sbit at 0x9f sm0_0;sbit at 0x9f fe_0;/* p2 */sbit at 0xa0 p2_0;sbit at 0xa1 p2_1;sbit at 0xa2 p2_2;sbit at 0xa3 p2_3;sbit at 0xa4 p2_4;sbit at 0xa5 p2_5;sbit at 0xa6 p2_6;sbit at 0xa7 p2_7;/* ie */sbit at 0xa8 ex0;sbit at 0xa9 et0;sbit at 0xaa ex1;sbit at 0xab et1;sbit at 0xac es0;sbit at 0xad et2;sbit at 0xae es1;sbit at 0xaf ea;/* p3 */sbit at 0xb0 p3_0;sbit at 0xb1 p3_1;sbit at 0xb2 p3_2;sbit at 0xb3 p3_3;sbit at 0xb4 p3_4;sbit at 0xb5 p3_5;sbit at 0xb6 p3_6;sbit at 0xb7 p3_7;/* ip0 */sbit at 0xb8 lpx0;sbit at 0xb9 lpt0;sbit at 0xba lpx1;sbit at 0xbb lpt1;sbit at 0xbc lps0;sbit at 0xbd lpt2;sbit at 0xbe lps1;/* scon1 */sbit at 0xc0 ri_1;sbit at 0xc1 ti_1;sbit at 0xc2 rb8_1;sbit at 0xc3 tb8_1;sbit at 0xc4 ren_1;sbit at 0xc5 sm2_1;sbit at 0xc6 sm1_1;sbit at 0xc7 sm0_1;/* t2con */sbit at 0xc8 cp_rl_2;sbit at 0xc9 c_t_2;sbit at 0xca tr_2;sbit at 0xcb exen_2;sbit at 0xcc tclk;sbit at 0xcd rclk;sbit at 0xce exf_2;sbit at 0xcf tf_2;/* psw */sbit at 0xd0 parity;sbit at 0xd0 p;sbit at 0xd1 f1;sbit at 0xd2 ov;sbit at 0xd3 rs0;sbit at 0xd4 rs1;sbit at 0xd5 f0;sbit at 0xd6 ac;sbit at 0xd7 cy;/* wdcon */sbit at 0xd8 rwt;sbit at 0xd9 ewt;sbit at 0xda wtrf;sbit at 0xdb wdif;sbit at 0xdc pfi;sbit at 0xdd epfi;sbit at 0xde por;sbit at 0xdf smod_1;/* eie */sbit at 0xe8 ex2;sbit at 0xe9 ex3;sbit at 0xea ex4;sbit at 0xeb ex5;sbit at 0xec ewdi;/* eip0 */sbit at 0xf8 lpx2;sbit at 0xf9 lpx3;sbit at 0xfa lpx4;sbit at 0xfb lpx5;sbit at 0xfc lpwdi;#endif  


模拟开关将555定时器转换为脉宽调制器
OpenEM的应用原理、使用效率和局限的研究
基站回传网络建设部署的简单探讨
纽扣电池表面AI视觉缺陷检测解决方法
推荐一款用于乳腺癌的光声成像系统Imagio
SDCC-Linux下的51 MCU编译器
MediaTek发布基于NIDD技术LwM2M协议验证的NB-IoT芯片
哪一些人群受人工智能的影响最大
对存储而言机器学习又意味着什么呢?
高频PCB电路设计中经常遇到的70个问题
用Arduino和安卓旧手机,DIY远程遥控机器人
格力要约收购长园集团告吹 长园股价崩盘
倒闸操作方式及分类
区块链应用领域中金融和溯源被公认为是最佳的应用场景
圆柱锂电池定制的流程通常分为以下几个步骤
千视电子为全球用户献上科技“盛宴” IBC 2023展会大放异彩
统信软件与OpenHarmony深度合作共筑自主智能终端场景新生态
放大器市场趋势:高集成度系统解决方案
安好ARM后再安C51不能工作的解决方法分享
河北工大:激光写入多孔石墨烯泡沫,用于多路电化学汗液传感器