应用笔记 3346

在DS80C400应用中使用SDCC编译器


摘要 : DS80C400包含一个提供网络栈、内存管理和进程调度的ROM,可以灵活地用于Java、C和8051汇编编程的应用中。SDCC为8051器件提供了一个免费、开放源码的编译器,并兼容DS80C400的24位寻址模式。用C编写的复杂应用程序在Dallas Semiconductor提供的库的帮助下,可以很容易地使用DS80C400 ROM功能创建。这些库以及文档和示例代码,可以在以下地址下载

本应用笔记阐述了如何使用SDCC工具来建立DS80C400应用程序。从一个HelloWorld应用程序出发,然后演示如何使用ROM库来实现一个简单的HTTP服务器。这里的应用程序是针对TINI®m400参考模块编写和创建的,用于具有其它存储器配置的设计时必须进行相应修改。


另请参考:

介绍

DS80C400包含一个提供网络栈、内存管理和进程调度的ROM,可以灵活地用于由Java、C和8051汇编编程的应用中。SDCC为8051器件提供了一个免费、开放源码的编译器,并兼容DS80C400的24位寻址模式。用C编写的复杂应用程序在Dallas Semiconductor提供的库的帮助下,可以很容易地使用DS80C400 ROM功能创建。

本应用笔记阐述了如何使用SDCC工具来创建DS80C400应用程序。从一个HelloWorld应用程序开始,然后说明如何使用ROM库来实现一个简单的HTTP服务器。这里的应用程序是针对TINIm400参考模块编写和创建的,用于具有其它存储器配置的设计时必须进行相应修改。

从SDCC编译器开始

遵循以下步骤,使用SDCC编译器来完成您的第一个DS80C400的C应用程序:
  1. 安装SDCC编译器¹
    • 从SDCC网站上下载最新版本SDCC编译器的安装文件。
    • 遵循安装文件的指示(可能是sdcc/doc/INSTALL.txt)。
  2. 使用你喜欢的文本编辑器创建一个新文件"main.c"。在文件中写入以下代码:
    #include <stdio.h>
    void main ()
    {
    	printf("Hello Universe!!!!....Welcome to SDCC Tini Test Program");
    
    			while (1)
    			{
    }
    		}
    
    一定要保存文件内容。
  3. 从SDCC C库站点²中拷贝文件startup400.a51和reg400.inc (包含在启动代码下载中),并保存到您保存main.c文件的目录中。此文件包含startup_code函数,将在应用程序启动时调用该函数,从而对DS80C400芯片进行初始化。启动代码完成以下工作:
    • 将DS80C400配置成24位连续地址模式
    • 配置定时器2用来为串口产生115200的波特率
    • 初始化数据存储器
  4. 从SDCC C库站点拷贝ROM initialization库文件(从init库文件下载的rominit.lib和rom400.h),并将其解压缩至相同目录下。库文件是压缩的,使用WinZip或gunzip/tar解压缩包。
  5. 在编译我们的"Hello Universe"应用程序之前,我们需要在一个SDCC安装的支持文件中作一个小改动,覆盖缺省的DS80C400支持函数并使用Dallas Semiconductor的C库代替。进行以下改动:
    • 将\SDCC\lib\ds400\libds400.lib文件重命名为\SDCC\lib\ds400\libds400.lib.old
    • 建立一个名为\SDCC\lib\ds400\libds400.lib的空文件(使用touch命令或在您喜欢的文本编辑器中建立一个新文件)
  6. 构建"Hello Universe"应用程序...
    • 要由我们的startup400.a51文件创建一个目标文件(.rel),在命令行执行以下命令:
      asx8051 -losffgp startup400.a51
      
      asx8051是SDCC工具提供的汇编器。汇编器提供的参数选项有:
      Option Purpose
      l generates a list file
      o generates an object file
      s generates a symbol file
      ff flag reolcatable references by mode in listing file
      g make undefined symbols be global
      p disables listing pagination

      "los"参数是必须的,因为连接器需要列表、目标和符号文件来生成可执行文件。"ff"和"p"参数生成一个可读的列表文件。"g"参数通知汇编器在发现一个没有定义的符号且该符号未声明为外部变量时不报错。
    • 为了由main.c生成一个目标文件,执行以下命令:
      sdcc -c -mds400 --model-flat24 --stack-10bit --no-xinit-opt main.c 
      
      sdcc为编译器。

      传递给编译器的参数选项为:
      Option Purpose
      -c compiles main.c and creates an object file
      -mds400 generates code for the DS80C400 processor
      --model-flat24 use the 24-bit contiguous memory model
      --stack-10bit use the 1024-byte extended stack (10 bit stack addresses)
      --no-xinit-opt don't initialize the external RAM memory area
      p disables listing pagination

      注意列表中最后三个参数是双破折号。
    • 为了连接目标文件并构建可执行文件,执行以下命令:
          sdcc -mds400 --model-flat24 --stack-10bit -Wl-r --xram-loc
      0x10000 --xram-size 0x3fff --code-loc 0x400000 main.rel startup400.rel -l
      rominit.lib
      
      这里使用的新参数为:
      Option Purpose
      -WI pass options through to the linker
      --xram-loc external RAM start address (only RAM for SDCC variable use!)
      --xram-size external RAM size (only RAM for SDCC variable use!)
      --code-loc code starting address
      -l include the specified libraries
      p disables listing pagination

      请注意xram-loc、 xram-size和code-loc参数为双破折号。也要注意给命令指定的RAM将会用来存储SDCC变量,不应该和init_rom函数中用来初始化DS80C400所使用的存储器范围冲突―此存储器用作网络栈和存储器管理。

    • 为了压缩可执行文件并生成一个十六进制文件,执行以下命令:
      packihx main.ihx>hellouniverse.hex
      packihx命令通过将连续数据记录累积至16个字节来压缩可执行文件。
有了一个可执行文件后,我们需要将应用程序下载到TINIm400模块中并运行它。

将示例应用程序加载到TINIm400模块

本节说明如何使用Maxim/Dallas Semiconductor提供的微控制器工具包(MTK)向TINIm400验证模块中加载由SDCC编译器生成的十六进制文件。目前可用的MTK版本只支持Windows®。

如果您的开发环境不是Windows,需要使用JavaKit应用程序来下载和执行应用程序。要使用JavaKit,您必须有Java运行环境³ (版本至少为1.2)并且安装Java Communications API4。JavaKit工具包含在TINI软件开发包中。写本文的时候,发布的最新固件是固件版本1.13。运行JavaKit的指导说明可以在TINI SDK docs目录下的Running_JavaKit.txt文件中找到。如果您在运行MTK或JavaKit时遇到技术问题,可能其他人已经遇到过类似问题并且已经发表在Dallas Semiconductor的讨论区中。

最新版本的MTK应用软件可下载。要安装MTK,请运行安装文件并遵照提示操作。成功安装后,会增加一个新的菜单项: Start->All Programs->Dallas Semiconductor MTK。 MTK启动后,会出现图1所示的对话框。

图1. 启动时MTK选项
图1. 启动时MTK选项

选择选项TINI,以操作TINIm400评估板。

选择了TINI之后,会打开MTK主窗口。从Options->Configure Serial Port菜单选项中选择您将用来和TINIm400通讯的串口。然后,选择Tini->Tini Options菜单项,就会出现下面的对话框。选择DSTINIm400按钮,配置MTK用于和TINIm400板通讯。图2显示了带有DSTINIm400按钮的对话框。

图2. 选择TINIm400配置选项
图2. 选择TINIm400配置选项

选择Tini->Open COMx at xxx baud菜单选项打开串口。接着选择Tini->Reset选项复位评估板。会出现DS80C400的加载提示:
DS80C400 Silicon Software - Copyright (C) 2002 Maxim Integrated
Detailed product information available at http://www.maximintegrated.com

Welcome to the TINI DS80C400 Auto Boot Loader 1.0.1
> 
从文件菜单中选择Load HEX File。找到我们刚刚生成的hellouniverse.hex文件并选中。一旦您的程序加载后有两种方法运行它。因为我们将程序加载到40区,您可以输入:
> B40 
> X 
要选择40区并运行那里的代码。您也可以输入:
> E 
这会使ROM查找可执行的代码。它查找一个标识当前区具有可执行代码的特定标签。此标签由文本'TINI'组成,其后面紧跟着当前区的号码(或零),并位于当前区的0002h地址。SDCC编译器在生成的汇编代码中插入此标签。如果打开为hellouniverse工程生成的main.asm源代码,您会找到下面的代码段:
   .area CSEG    (CODE)
interrupt_vect:
   ; DS80C400 IVT must be generated at runtime.
	sjmp	__sdcc_400boot
	.ascii	'TINI'	; required signature for 400 boot loader.
	.db	0	; selected bank or zero...
__sdcc_400boot:
	ljmp	__sdcc_gsinit_startup
注意sjmp__sdcc_400boot语句位于40区的0000h地址。其后跟随可执行标签{ 'T', 'I', 'N', 'I', 0h},由于simp语句为两个字节,因此该标签位于地址0002处。当您键入'E'时,ROM从C0h区开始向下搜索可执行代码。如果您键入'E'时,执行了其它的代码,则意味着ROM在一个比您的代码加载位置400000h更高的地址找到了一个可执行标签。您可能需要找到此标签的位置,并删除那个区的内容。

和ROM以及SDCC ROM库接口

在高速微控制器用户指南DS80C400补充资料中说明了在汇编语言中调用ROM函数的过程。但是,在C中调用这些ROM函数会复杂一些。必须将参数从SDCC C编译器的规则转换成ROM使用的规则。SDCC编译器通过硬件堆栈、累加器和数据指针相结合的方式来传递参数。ROM函数采用许多不同的方式来接受参数。例如,socket函数接收存储在一个外部RAM缓冲区中的参数。相反地,许多功能函数接收由特殊功能寄存器或直接存储器位置传递的参数。为了从SDCC调用方式转换成ROM参数方式,Dallas Semiconductor已经编写了访问ROM函数的库。

在您的C程序中使用ROM函数只需包含一个头文件并与相应的库文件连接即可。用于SDCC编译器的ROM库包括:
  • ROM初始化程序
  • DHCP客户端
  • 进程调度
  • Sockets (TCP、UDP和Multicast)
  • TFTP客户端
  • 功能函数(CRC16, 随机数)
在写本文时,还没有为SDCC编译器提供包括如文件系统、邮件客户端和HTTP服务器之类的扩展库。请关注SDCC库主页上的DS80C400升级信息,我们会添加支持SDCC的库。

简单应用:HTTP服务器

编写了一个简单的http服务器来说明如何使用一些ROM库函数,特别是socket和进程调度库。这个示例会偶尔通过网络时间服务器更新它的时间,并且通过它的web服务器提供这个信息。

示例应用程序由两个模块组成,一个HTTP服务器和SNTP客户端。主程序生成一个新的子任务来运行http服务器,用于处理80端口上的客户连接。父任务每60秒会试图通过时间服务器同步当前时间。

SNTP客户端模块

以下代码段实现SNTP客户端模块的核心功能。
socket_handle = socket(0, SOCKET_TYPE_DATAGRAM, 0);

// set a timeout of about 2 seconds
for (i=0;i<256;i++)
    buffer[i] = 0;
buffer[0] = 0x0;
buffer[1] = 0x0;
buffer[2] = 0x8;
buffer[3] = 0x0;
setsockopt(socket_handle, 0, SO_TIMEOUT, buffer, 200);

buffer[2] = 0;       //reset since we used this in call to setsockopt
buffer[0] = 0x23;    // No warning/NTP Ver 4/Client

address.sin_addr[12] = TIME_NIST_GOV_IP_MSB;
address.sin_addr[13] = TIME_NIST_GOV_IP_2;
address.sin_addr[14] = TIME_NIST_GOV_IP_3;
address.sin_addr[15] = TIME_NIST_GOV_IP_LSB;
address.sin_port_high = (NTP_PORT/0x100); //higher byte of port number
address.sin_port_low = (NTP_PORT%0x100); //lower byte of port number

sendto(socket_handle, buffer, 48, 0, &address, sizeof(struct sockaddr));
recvfrom(socket_handle, buffer, 256, 0, &address, sizeof(struct sockaddr));

//SDCC uses little Endian for storing data, so reorganize the data before converting it to long
buffer[0]=buffer[43];
buffer[1]=buffer[42];
buffer[2]=buffer[41];
buffer[3]=buffer[40];

timeStamp = *(unsigned long *)(&buffer[0]);

formatTimeString(timestamp - (5 * SECONDS_PER_HOUR), "Tampa, USA",
                 last_time_reading_1);
formatTimeString(timeStamp - (3 * SECONDS_PER_HOUR), "Sao Paulo, Brazil",
                 last_time_reading_2);
formatTimeString(timeStamp + (1 * SECONDS_PER_HOUR),"Marseille, France",
                 last_time_reading_3);
formatTimeString(timeStamp + (5 * SECONDS_PER_HOUR) + (30 *
                 SECONDS_PER_MINUTE), "Bangalore, India",
                 last_time_reading_4);
formatTimeString(timeStamp +  (8 * SECONDS_PER_HOUR), "Hsinchu, Taiwan",
                 last_time_reading_5);
last_reading_seconds = getTimeSeconds();
closesocket(socket_handle);
SNTP客户端模块同样是通过RFC 1361实现的。SNTP模块通过使用UDP协议和time.nist.gov通讯,并请求一个时间戳。需注意编写这个应用笔记时还不提供SDCC编译器的DNS支持,因此time.nist.gov的IP地址是手工设定的。

首先,创建一个数据包socket并分配一个大约2秒(0x800==2048 milliseconds)的超时。这样会保证如果和我们选择的服务器通讯失败,我们不会无休止地等待响应。

接下来的一行用来设置请求的参数。在RFC 1361的第3节对这些位进行了说明。值0x23在一个跳秒时不要求产生警告,要求使用版本4的NTP,并声明模式为"Client"。我们使用普通数据包函数sendto和recvfrom发送请求和接收响应之后,将时间戳数值的秒部分赋予变量timeStamp,然后调整至参考日期1970年1月1日。用函数formatTimeString将时间戳转换成一个可读的字符串,比如说"In Marseille, France it is 9:37:37 on September 3, 2000。"

用getTimeSeconds函数来确定基于DS80C400内部时钟的最后一次更新的时间。由于程序只是大约每60秒更新一次,HTML网页time.html将会使用这一数值来报告上一次时间更新后已经过了多长时间。最后,socket关闭并且SNTP客户端进入另一个60秒的休眠期。

简易的HTTP服务器

这个时间服务器应用程序的另一个子模块为一个web服务器。此应用程序中的服务器实现了一个RFC 2068中描述的HTTP服务器简易版本。只支持"GET"方法--忽略输入包头,并且几乎不给出输出包头。当编写这个应用笔记时尚未提供文件系统库,因此示例应用程序动态地生成HTML页面。

通过调用Berkley-style socket函数来创建服务器soket。这使得建立一个服务器socket十分容易。下面的代码给出了我们的简易HTTP服务器如何创建、绑定和接收新的连接。
struct sockaddr local;
unsigned int socket_handle, new_socket_handle, temp;

socket_handle = socket(0, SOCKET_TYPE_STREAM, 0);
local.sin_port = 80;
bind(socket_handle, &local, sizeof(local));
listen(socket_handle, 5);

printf("Ready to accept HTTP connections...\r
");

// here is the main loop of the HTTP server
while (1)
{
    new_socket_handle = accept(socket_handle, &address, sizeof(address));
    handleRequest(new_socket_handle);
    closesocket(new_socket_handle);
}
要注意当接收一个新的socket时,这个简易应用程序不会启动一个新的线程或进程来对请求进行处理,而是在同一进程中处理请求。任何优于演示品质的HTTP服务器都会在一个新的线程中处理到来的请求,允许同时发生多个连接并能进行处理。请求处理完毕后我们关闭socket并等待下一个到来的连接。

HandleRequest方法从接入的请求中解析出文件名并且检查验证请求方法为'GET'。不允许使用其它的方法(即使是'POST'、 'HEAD'或'OPTIONS')。

为SDCC编译器编写DS80C400汇编函数的注意事项

虽然SDCC提供了丰富的库函数集合,但是有时我们会想用汇编语言编写优化的模块,或将现有的8051汇编模块移植到我们的应用程序中。若8051汇编语言函数由SDCC编译器编写的C程序来调用时,在编写这些汇编函数时需切记以下要点:
  1. 函数参变量传递约定:下表说明了重入函数的变量传递方式
    Argument position Character Integer Long Address
    First argument Dpl Dph:dpl B:dpx:dph:dpl B:dpx:dph:dpl
    Second argument onwards the values will be passed through hardware stack from right to left

    函数void sample_func(long x, long y,int z) reentrant;的参数传递如下:



  2. 数据类型存储规则:
    SDCC遵循低字节在前(Little Endian)的存储规则。换句话说,SDCC使用最低有效字节在前的二进制数据存储格式。

    例如,一个32位长的数值0xDEADBEEF将会按如下方式存储:



  3. 地址指针大小
    SDCC使用四个字节来保存存储器地址。下表给出了存储器地址的格式:
    Most significant byte 3rd byte 2nd Byte Least significant byte
    address type (possible values for ds80c400: 0-near, 1-far, 2-code) MSB of address 2nd byte of address LSB of address

    近距离地址指针使用间接寻址、内部RAM存储器(idata)来进行存储,并且其地址大小只有一个字节。原地址的高16位不用。

    远距离地址指针用于访问外部存储器,为24位。

    有关SDCC ASx8051汇编器的更多信息,请参考ASxxxx assembler reference manual (ASxxxx汇编器参考手册)。所有SDCC文档可以从http://sdcc.sourceforge.net/snap.php#Docs/处下载。

局限性以及开发问题

以下是我们使用4.0版本的SDCC编译器时发现的局限性:
  1. 编译器不支持递归函数
  2. 库程序未优化
  3. 编译器不支持宏指令
  4. 像printf和sprintf之类的函数存在一些问题,在一些参数组合下不能正常工作。例如,以下代码会导致应用程序挂起:
    char temp[50];
    sprinf(temp,"%d",234234);
    
  5. 带有长常量的算术表达式不能正常工作。
  6. 用于数组初始化('int[] values={1, 2, 3, 4, 5};')所生成的汇编代码不能正确初始化存储器区域。
由于SDCC一直处于飞速发展之中,如果在你的SDCC工具现有版本中发现任何的缺陷或者你的版本比当前发布的版本早很久时,请下载最新版本。

结论

Dallas Semiconductor为SDCC编译器提供的DS80C400 ROM库为嵌入式网络应用设计者们寻找低成本网络微控制器解决方案提供了更多的选择。和TINI Java运行环境相比,使用C语言的DS80C400开发者将能够编写精巧的应用程序,赋予系统足够的速度、能力和代码空间来解决任何问题。Dallas Semiconductor正致力于将所有目前工作于Keil编译器的DS80C400库移植到SDCC。请经常访问DS80C400 SDCC库主页来获得升级。

相关链接

备注

¹可以下载SDCC编译器
²SDCC ROM库主页
³Java运行环境
4Java通信API