文中介绍了如何从Keil的uVision2开发套件着手构建一个DS80C400的C语言应用程序,通过实现一个简单的HTTP服务器演示如何使用DS80C400的ROM功能。所有开发都采用了TINIm400验证模块和包含7.05版C编译器'C51'的Keil uVision2 2.37。
从Keil的uVision2开始
你可以使用Keil uVision2开发套件,构建一个简单的HelloWorld型C语言程序。按照以下步骤完成你的第一个用于DS80C400的C语言应用程序。
选择Project-->Create New Project。 输入项目名称。
屏幕上将出现Select Device for Target对话框。在Data base中选择Dallas Semiconductor和DS80C400。选择Use Extended Linker和Use Extended Assembler。点击OK继续。图1所示为该对话框的正确配置。
图1. 为一个新的Keil uVision2项目选择
将有对话框提示:Copy Dallas 80C390 Startup Code to Project Folder and Add File to Project?选择No。我们将提供自己的启动代码。
当项目窗口在左侧打开时,打开Target 1。右击Source Group 1,并选择Add files to group 'Source Group 1'。在弹出的文件对话框中,将files of type改为Asm Source file。添加文件startup400.a51。该文件可以在http://files.dalsemi.com/tini/ds80c400/c_libraries/HelloWorld.zip上的zip文件中找到。
双击打开文件startup400.a51。找到段声明?C_CPURESET?0。确保代码段声明为400000h。
C_CPURESET?0 SEGMENT CODE AT 400000h
另外,应有一个"DB 'TINI'"行,后跟另一个DB行,带有注释"Target bank"。这样就可以确保应用建立地址为400000h,对应于TINIm400上flash的起始地址。请确认该行为:
DB 40h ; Target bank创建一个新的文件,以"main.c"为文件名来保存。在该文件中写入如下代码:
#include保存文件内容。右击Source Group 1,并添加源文件main.c。现在就将该源文件添加到项目中了。void main() { printf("Test 400 Program\r "); while (1) { } }
右击左侧的Target 1。选择Options for target 'Target 1'打开选项对话框。第一个选择标签应该为Target。将Memory Model改为Large: variables in XDATA。将Code Rom Size改为Contiguous Mode: 16MB program。选中检查框Use multiple DPTR registers和far memory type support。在Off-chip Code memory项目下加入第一个入口:Start:0x400000,Size:0x80000。在Off-chip XData memory项目下加入一个入口:Start:0x10000,Size:0x4000。图2所示为配置完成的对话框。确认检查框Use On-Chip Arithmetic Accelerator被清除--多线程应用很难共用算术累加器。
图2. setp 7目标选项对话框(注意,'Eprom:start' 应为0x400000,最后一个'0'未显示)。
选择Output标签。点击Create HEX File,并在下拉框中选择HEX-386。按F7键建立应用程序。如果每一步都正确完成,建立过程应不产生错误或警告信息。之后会生成一个hex文件。现在就可以将该应用程序装载到你的电路板上了。
将应用实例装载到TINIm400模块
这部分介绍如何使用JavaKit工具将Keil编译器生成的hex文件装载到TINIm400验证模块中。使用JavaKit之前,必须首先安装Java Runtime Environment2 (1.2版以上)和Java Communications API3。JavaKit包含在TINI软件开发包中(TINI Software Development Kit),http://files.dalsemi.com/tini/tini1_11.tgz上提供下载。运行JavaKit的说明可以在TINI Software Development Kit的docs目录下的Running_JavaKit.txt文件中找到。如果你在运行JavaKit时遇到任何技术问题,很可能其他人曾经遇到过类似的问题,这些问题被收集在TINI主题列表中。你可以在http://lists.dalsemi.com/search/search.html上搜索有关该列表的文档。
通过以下命令行建立JavaKit与TINIm400的通话。
java JavaKit -400 -flash 40图3显示了JavaKit窗口。
图3. JavaKit界面。
运行JavaKit后,选择用来与TINIm400进行通信的串口。使用'Open Port'按钮打开该串口。然后按Reset按钮。DS80C400装载器将提示如下信息:
DS80C400 Silicon Software - Copyright (C) 2002 Maxim Integrated Products Detailed product information available at http://www.maxim-ic.com Welcome to the TINI DS80C400 Auto Boot Loader 1.0.1 >在JavaKit顶部的'File'菜单中,选择Load HEX File as TBIN。找到并选择我们已经创建的helloworld.hex文件。Load HEX File as TBIN选项先将输入的hex文件转换为TBIN文件,然后装载。这样的操作比直接装载hex文件速度快,因为对于同样的数据,ASCII编码的hex文件的尺寸是二进制文件的二倍多。
用户程序装载后,有两种执行方法。由于程序装载到存储区40中,所以你可以直接键入:
> B40 > X要选择存储区40,并在运行这里的代码,也可以键入:
> E这将使ROM查找可执行代码,有一个特殊标记符用来表示当前存储区中有可执行代码。该标记符由字符'TINI'和紧随其后的当前区号组成。它位于当前区的地址0002处。我们的HelloWorld程序对于此标记符的声明在startup400.a51文件中如下所示: [page]
C_STARTUP: SJMP STARTUP1 DB 'TINI' ; Tag for TINI Environment 1.02c ; or later (ignored in 1.02b) DB 40h ; Target bank注意SJMP STARTUP1语句位于40区的地址0000处。由于sjmp语句占两个字节,所以紧随其后的执行标记{'T', 'I', 'N', 'I', 40h}位于地址0002。当键入'E'时,ROM在FEh存储区中向下查找可执行代码。如果键入'E'后执行了其他代码,就说明ROM在高于 400000h (装载你的代码的位置)的地址处找到了执行标记,你可能需要找到该标记,并删除该存储区中的内容。
接口至ROM和ROM库
从C中调用ROM函数比较复杂(调用ROM函数的步骤参见High-Speed Microcontroller User's Guide supplement for the DS80C4004)。必须将Keil C编译器规范的参数转换成ROM所使用的规范。Keil编译器以XDATA地址和寄存器组合的方式传递参数。而ROM函数采用不同的方式接收参数。例如,套接字函数接受存放在单个参数缓冲器中的参数,而许多其他应用函数接受特殊功能寄存器或直接存储器地址传来的参数。Dallas Semiconductor编写了访问ROM函数的库,可完成Keil调用规范与ROM参数规范间的翻译工作。
要在你的C程序中使用ROM函数,只需要导入相应的库,并包含一个头文件。为了在你的项目中导入一个库,在Keil项目窗口中右击Source Group 1,并选择Add Files to Group 'Source Group 1'。将文件过滤器改为‘*.lib’,选择你需要的库。然后在源代码顶部加入头文件。你可以使用任何一个库函数。这些ROM库可支持:
ROM初始化
DHCP客户端操作
进程管理
套接字函数(TCP、UDP、Multicast)
TFTP客户端操作
一些实用函数(CRC和伪随机数产生等)
使用扩展库
除了ROM库,还有许多其他库(还有更多正在编写中)提供了很多ROM中没有的实用功能。这些库包括:
- | 文件系统,改编自TINI文件系统,实现stdio.h中声明的方法。 |
- | DNS客户端实现。 |
- | 1-Wire®,采用Public Domain Kit (参见www.ibutton.com.cn/software/1wire/wirckit.html)中定义的API。 |
- | I2C,实现一个类似于TINI中所用的设计。 |
- | CAN,实现一个类似于TINI中所用的设计。 |
为DS80C400提供的C库项目(包括文档、应用实例和发行说明)可以在http://files.dalsemi.com/tini/ds80c400/c_libraries/index.html上找到。
一个简单的HTTP服务器和SNTP客户端应用
Dallas Semiconductor编写了一个小的应用程序来演示这些库的功能,特别是文件系统、套接字、进程调度器和TFTP库等。应用实例中包括一个SNTP 客户端和一个只响应'GET'请求的HTTP服务器。它使用Dallas Semiconductor提供的核心库调用套接字和调度器函数。它还使用文件系统保存了几个网页。该应用由两个进程组成:(1) HTTP服务器作为一个新进程被创建并用来处理端口80上的连接,以及(2)主进程位于一个循环中,约每60秒尝试进行一次时间同步。
文件系统的初始化
启动HTTP服务器之前,必须初始化文件系统。演示程序确保两个静态文件,主页(index.html)和程序源码(source.html),在服务器启动之前已位于文件系统中。 可以用多种方法将这些文件安装到文件系统。方法之一是在程序代码数据中包含这些文件的文本,然后,在启动时将文件数据写到文件系统。这是最简单的方法,而且我们的演示程序也有空闲的代码空间可供使用。
本演示程序通过TFTP服务器查找它所需要的文件并初始化其文件系统。这是一种更为有趣、并且更能展示DS80C400内置功能的方法。在我们这个实例中,TFTP服务器在一个已知的IP地址上运行。文件index.html和source.html由TFTP服务器获得,然后被写入文件系统。
void initialize_filesystem() { struct sockaddr address; unsigned int i; unsigned int result; void* start = (void*)FS_START; // initialize the file system int x = finit(FOPEN_MAX, FS_BLOCKS, start); printf("Result of FS init: %d \r ", x); if ((x==0) && (fexists("index.html")==0) && (fexists("source.html")==0)) { printf("File system OK, skip TFTP init.\r "); return; } // lets get the files we want off a TFTP server // initialize TFTP server setting for (i=0;i<18;i++) address.sin_addr[i] = 0; // since the DS80C400 supports Ipv6, the address is 16 bytes long // however, since we are only using Ipv4 addresses, only the last // 4 bytes are meaningful address.sin_addr[12] = TFTP_IP_MSB; address.sin_addr[13] = TFTP_IP_2; address.sin_addr[14] = TFTP_IP_3; address.sin_addr[15] = TFTP_IP_LSB; result = settftpserver(&address, sizeof(struct sockaddr)); printf("Set TFTP server to selected server, result: %u\r ", result); result = tftp_init(); printf("Result of TFTP init: %u \r ", result); get_tftp_file("source.html"); get_tftp_file("index.html"); } void get_tftp_file(char* filename) { unsigned int result; unsigned char* TFTP_MSG; FILE* file; printf("Free FS RAM: %ld\r ", getFreeFSRAM()); TFTP_MSG = getTFTPData(); file = fopen(filename, "w"); result = tftp_first(filename); if (result==0xFFFF) { printf("Error in TFTP_FIRST...\r "); return; } printf("Result of first segment: %u\r ", result); fwrite(TFTP_MSG, 1, result, file); while (result >= 512) { result = tftp_next(TFTP_MORE_DATA); if (result==0xFFFF) { printf("Error in TFTP_NEXT...\r "); return; } printf("Result of next segment: %u\r ", result); TFTP_MSG[result] = 0; fwrite(TFTP_MSG, 1, result, file); } tftp_next(TFTP_LAST_SEGMENT); fclose(file); printf("Done with TFTP server.\r "); }[page]注意在每次启动应用时都须调用finit函数,以确保文件系统已正确安装并能正常工作。如果文件系统已正确初始化(返回0),并且我们所要的文件已全部到位,则函数直接退出,不去尝试下载文件。否则,它会尝试从TFTP服务器下载文件,并将它们写入文件系统,正如函数get_tftp_file中所显示的。
SolarWinds为Windows (平台提供了一个免费的TFTP服务器,它被应用于该演示程序的开发中。在SolarWinds网站(www.solarwinds.net),跟随Downloads - Free Software菜单可找到TFTP服务器下载。安装以后,使用File菜单下的Configure选项来配置现有文件。确保程序使用你的TFTP服务器IP地址(TFTP_IP_MSP, TFTP_IP_2,TFTP_IP_3和TFTP_IP_LSB)。
简单的HTTP服务器
该应用中的HTTP服务器是RFC 2068所描述的HTTP服务器的一个简化版实现。在该版本下,只支持'GET'方法。输入头被忽略,只给出很少的输出头。
服务器套接字通过调用Berkley型套接字函数来创建,这样使得服务器套接字容易建立。以下代码说明了这个简单的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); }请注意,接受了新的套接字后,这个简单的应用并不启动新的线程或进程来处理请求。而是在同一进程中处理该请求。任何非演示版的HTTP服务器都会在新的线程中处理收到的请求,这样就允许多个连接出现并被同时处理。请求处理完后,关闭套接字,等待另一个收到的连接。
handleRequest方法从收到的请求中解析出一个文件名,并确定该方法是'GET'。其它方法(甚至是'POST','HEAD'或'OPTIONS')均不被接受。两个文件名被作为特例处理。当请求文件为time.html时,服务器动态产生一个响应,其中包含来自timeserver的最新结果,以及自上一次查询时间服务器以来的秒数。当请求文件为stats.html时,将显示服务器的正常运行时间和请求次数统计结果。
如果找不到文件或发出的是无效的请求方法,HTTP服务器报告错误码。
SNTP客户端
第二个主要部分是Simple Network Time Protocol客户端,参见RFC 1361的描述。它是Network Time Protocol (RFC 1305)的一个版本。SNTP要求UDP从一个侦听端口123的服务器请求时戳。我们的timeserver使用以下代码周期性地与服务器time.nist.gov同步。请注意,在写这篇文章时,DNS检索还不被支持,因此服务器的IP地址须手动设置。DNS现已被添加到了C库网站,以下代码更新后可通过检索获得IP地址。
socket_handle = socket(0, SOCKET_TYPE_DATAGRAM, 0); // set a timeout of about 2 seconds 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 = NTP_PORT; sendto(socket_handle, buffer, 48, 0, &address, sizeof(struct sockaddr)); recvfrom(socket_handle, buffer, 256, 0, &address, sizeof(struct sockaddr)); timeStamp = *(unsigned long*)(&buffer[40]); timeStamp = timeStamp - NTP_UNIX_TIME_OFFSET; // now we have time since Jan 1 1970 formatTimeString(timeStamp, "London", last_time_reading_1); last_reading_seconds = getTimeSeconds(); closesocket(socket_handle);首先生成一个数据报套接字,并给定一个约2秒的超时(0x800==2048ms)。这样可以确保在与我们选择的服务器通信失败时,不必无限期地等待下去。
下一行设定请求选项。关于这些位的描述参见RFC 1361第三节。值0x23要求闰秒时无需告警,要求采用NTP版本4,并声明模式为'Client'。当我们使用公共数据报函数sendto和recvfrom发出请求并收到回答后,时戳的秒部被赋给变量timeStamp,然后调整到参考时间1970年1月1日。formatTimeString函数将时戳转换为易读的字符串,如“In London it is 15:37:37 on March 31, 2003”。
getTimeSeconds函数以DS80C400内部时钟为基础确定上一次更新。由于该程序大约每60秒才更新一次,HTML页time.html使用这个数值来报告自上次更新以来的时间间隔。最后,关闭套接字,SNTP客户端在下面的60秒内进入休眠。
有关同步的说明
在LARGE存储模式下,Keil编译器将通过在进程交换中非安全的存储器传递有限数量的参数。这就意味着有些函数不能由多个程序同时调用。尽管已专为 400开发了C库,其中的所有变量都通过在进程交换中安全的直接存储器传递,有些函数仍然是危险的。例如,Berkeley式的套接字header要求一些较长的方法签名,它会涉及到一些通过非安全存储器传递的数据。因此,针对套接字有两个库:
一个库(rom_sock.lib)遵循Berkeley式的header。但是,两个进程同时用这个库调用某个函数是不安全的。不过,如果一个进程正在使用UDP函数而另一个正在使用TCP函数就不会有问题。为了对并发访问非安全存储器提供真正的保护,开发了另外一个套接字库(rom_sock.lib)。该库中的函数类似于Berkeley型函数,但具有更少或重新安排的变量,以使Keil编译器通过安全存储区传递参数。无论何种情况,请参考相关文档,以确认函数是否为多进程安全的。[page]
有关指针传递的说明
Keil文档提供了用8051汇编自己编写可从你的C程序中调用的方法的途径。如果你选择这样做,请注意,由C程序传递到8051汇编的指针在 DS80C390和DS80C400中不是立即可用的。因为传统的8051架构是16位的,Keil指针由两字节的指针和一字节的存储类型组成。当采用 Dallas的24位8051微控制器时,存储类型字节为指针高字节所用,但采用一种变化的形式。在当前版本的Keil编译器中,高指针字节有其高位设置并被递增1。以下来自rom_offsets.inc的宏在Dallas Semiconductor库中被用于纠正被更改的指针。
FIXKEILPOINTER MACRO DIRECT_DPX LOCAL must_be_null mov a, DIRECT_DPX jz must_be_null dec a anl a, #7Fh mov DIRECT_DPX, a must_be_null: ENDMKeil编译器通过寄存器r3:r2:r1 (r3是存储类型字节) 或XDATA存储区传递指针。该宏将工作于任何寄存器或其他直接存储器值,传给它存储类型字节,它会在同一位置返回高指针字节。以下代码演示了它的用法:
; ; Keil passes pointers as r3:r2:r1... ;---- Variable 'buffer1?972' assigned to Register 'R1/R2/R3' ---- ; FIXKEILPOINTER r3 ; ; r3:r2:r1 is now usable as a pointer value. ; ; ; ...or in XDATA. ;---- use dpx1:dph1:dpl1 for buffer pointer ---- ; mov dptr, #buffer2?1078 GETX mov dpx1, a inc dptr GETX mov dph1, a inc dptr GETX mov dpl1, a FIXKEILPOINTER dpx1 ; ; Data pointer 1 is now usable as a pointer. ;注意,还有一种与FIXKEILPOINTER宏相反的情况,可以使函数将其所需的指针转换为Keil编译器生成代码可以理解的形式。在此情况下,可以用UNFIXKEILPOINTER宏。这个宏的用法和FIXKEILPOINTER宏相同。一点不同是,当你从一个用汇编写的方法中返回一个指针时,指针必须保存在寄存器r3, r2和r1中,高指针字节在r3中。因此,在退出一个需要返回指针的函数前,它必须调用宏:
UNFIXKEILPOINTER r3 ret ; End of the assembly function保持你的Keil为最新版本
Keil会随时发布对其uVision2工具套件的更新版本。网站http://www.keil.com/update/上有关于最新版本的C51编译器和uVision2 IDE的信息。从该网页你可以选择你需要的下载,并看到所发生的变化。
更新应该是一个可执行的InstallShield。应用将显示一个标题为Setup uVision2的窗口。选择Update Current Installation选项执行更新。程序可以检测你当前的安装目录,点击Next继续。在下一屏上选择是否要保留原来的uVision2配置,并再次点击Next。最后,确认你选择的选项并开始安装。
结论
Keil C编译器和Dallas Semiconductor提供的库允许用C编写的应用也可方便地使用以前只能通过TINI的Java环境访问的功能和函数。C语言程序现在可以访问网栈、存储管理器、进程调度器、文件系统,以及DS80C400网络微控制器的许多其他特性。另外,与TINI运行环境相比,用C语言编写的应用程序为用户代码和数据提供了更多的空间。使用C语言的DS80C400开发者可轻易编写出极为精简的应用,有充裕的速度速度、能力、代码空间来应付各种问题。
参考文献
1 App Note 609: Internet Speaker with the DS80C400 Silicon Software
2 Download at http://java.sun.com/j2se/downloads.html
3 Download at http://java.sun.com/products/javacomm/
4 The High-Speed Micro User's Guide Supplement for the DS80C400 can be found at http://pdfserv.maxim-ic.com/arpdf/Design/DS80C400UG.pdf
上一篇:采用ST72F651实现的安全U盘
下一篇:基于单片机的智能无功补偿仪的设计
推荐阅读最新更新时间:2024-03-16 12:52