应用笔记 3477

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


摘要 : SDCC (小型设备C编译器)是为8位微控制器开发的免费C编译器。本应用笔记演示如何使用SDCC来开发DS89C430/450系列超高速8051兼容微控制器固件。文中还介绍了如何安装免费SDCC C编译器。

简介

SDCC (小型设备C编译器)是为8位微控制器开发的免费C编译器。尽管兼容多种不同体系结构,但SDCC C编译器更适合8051内核。本应用笔记主要介绍采用SDCC来开发Maxim/Dallas Semiconductor的DS89C430/450系列超高速8051兼容微控制器固件。

SDCC是命令行固件开发工具,含预处理器、编译器、汇编器、链接器和优化器。安装文件中还捆绑了SDCDB、类似于gdb (GNU调试器)的源码级调试器。无错的程序采用SDCC编译、链接后,生成一个Intel十六进制格式的加载模块。之后可采用串行加载器将该文件加载至DS89C430/450微控制器闪存。(参见DS89C430/450文档和应用笔记,了解固件下载至器件的详细信息。)

关于SDCC的最新信息,请访问http://sdcc.sourceforge.net,或者阅读SDCC手册sdccman.pdf (在安装过程中复制到您的硬盘上)。也可以将问题提交给SDCC在线消息论坛,或发邮件至SDCC网页“Support”列出的邮件地址。

安装SDCC免费C编译器

如果需要安装SDCC,请从网址http://sdcc.sourceforge.net下载SDCC最新版本。虽然也可使用该软件的日常构建(nightly builds)版,但通常最安全的方式是下载经过完全测试的最新发布版。

在“Download”页为不同的操作系统提供不同的SDCC。如果您使用运行Microsoft Windows的PC,请下载并运行win32自解压SDCC安装文件。

安装程序时会出现一个提示,询问是否将含有程序二进制文件的目录添加到您的路径中。建议同意添加,本应用笔记假设用户路径中已添加该目录。

采用SDCC编译器编译一个简单的C程序

为确保SDCC已在您的硬盘上正确安装,请在命令提示符下键入sdcc --version,然后回车,窗口中应出现图1所示文本(实际文本与您下载的SDCC版本有关):

图1. 通过版本检查确认SDCC是否正确安装
图1. 通过版本检查确认SDCC是否正确安装

为测试包含路径,生成名为sdcctest.c的文件,并将以下源代码复制到该文件中。
#include <string.h>

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程序
图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:21: a.a_byte = 5;
                            160 ;     genPointerSet
                            161 ;     genNearPointerSet
                            162 ;     genDataPointerSet
   0031 75 21 05            163 	mov	_a,#0x05
                            164 ;sdcctest.c:23: 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:25: b = 1;
                            173 ;     genAssign
   003A D2 00               174 	setb	_b
                            175 ;sdcctest.c:27: while(1); // program loop
                            ...
尽管在声明中“a”看起来是位寻址存储器,但汇编列表文件(来自由SDCC生成的.rst文件)表明变量并没有使用位寻址。在列表中不要混淆“a”和“_a”。“a”指累加器,而“_a”指变量。

注意,本应用笔记在“绝对寻址”一节介绍了一种可真正实现存储器位寻址的方法。

near/data

neardata存储类型声明的变量将被放在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

farxdata存储类型声明的变量将被放在外部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,因此没有必要包含定义文件sdcc_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_bit0n_bit7。由于这种重叠,可采用变量n_bit0n_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-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.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
下一步
EE-Mail 订阅EE-Mail,接收关于您感兴趣的新文档的自动通知。
© , Maxim Integrated Products, Inc.
The content on this webpage is protected by copyright laws of the United States and of foreign countries. For requests to copy this content, contact us.
APP 3477:
应用笔记 3477,AN3477, AN 3477, APP3477, Appnote3477, Appnote 3477