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.h" data 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.h" xdata 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.h" idata 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.h" code 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 0x1B bit 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 0 sbit at 0x80 P0_0; // Port 0 bit 0 sbit at 0x81 P0_1; // Port 0 bit 1 sbit at 0x82 P0_2; // Port 0 bit 2 sbit at 0x83 P0_3; // Port 0 bit 3 sbit at 0x84 P0_4; // Port 0 bit 4 sbit at 0x85 P0_5; // Port 0 bit 5 sbit at 0x86 P0_6; // Port 0 bit 6 sbit at 0x87 P0_7; // Port 0 bit 7 void main(void) { P0 = 0x00; // P0 = 0x00 P0_4 = 1; // P0 = 0x10 while (1); // program loop }
绝对寻址
SDCC支持采用at标识符的绝对寻址。但是,SDCC不跟踪声明的绝对寻址变量,而且可能在其地址声明其它变量,造成相互覆盖。
以下程序显示了有趣的潜在错误。
#include "sdcc_reg420.h" unsigned 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.h" data 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-smallsdcctest.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.h" volatile 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.h" unsigned 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
审核编辑:汤梓红
-
mcu
+关注
关注
146文章
17134浏览量
351015 -
内核
+关注
关注
3文章
1372浏览量
40282 -
Linux
+关注
关注
87文章
11296浏览量
209348 -
编译器
+关注
关注
1文章
1624浏览量
49109 -
SDCC
+关注
关注
0文章
7浏览量
8386
原文标题:SDCC-Linux下的51 MCU编译器
文章出处:【微信号:TT1827652464,微信公众号:云深之无迹】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论