?
DS80C400微控制器的ROM包含多種功能,可通過由8051匯編語言、C或Java編寫的程序訪問。DS80C400的ROM功能提供成熟的MxTNI?網棧、進程調度和存儲器管理,是開始構建C和匯編程序的最佳選擇。對于簡單程序,可以用匯編語言輕松實現。對于更復雜的程序,C語言可以充分利用Dallas Semiconductor提供的ROM組件和軟件庫。這些軟件庫幫助您利用Keil μVision2?、SDCC和IAR 8051編譯器構建應用程序。
本應用筆記闡述了如何開始使用8051 IAR Embedded Workbench?來創建DS80C400的C語言應用程序。通過一個簡單的HTTP服務器演示如何使用DS80C400的ROM功能。所有開發都采用TINIm400驗證模塊和包含6.11A版本C編譯器的8051 IAR Embedded Workbench。
從8051 IAR Embedded Workbench開始
本節闡述如何使用IAR Embedded Workbench套件創建一個C語言編寫的Hello World程序,即您的第一個DS80C400 C應用程序。
安裝IAR Embedded Workbench
選擇File→New→Workspace,在工作空間窗口,輸入workspace名稱appnote
選擇Project→Create New Project。在出現的對話框中,輸入項目名稱hellowworld,并確定選擇了8051作為工具鏈
當左側的項目窗口打開時,選擇Project→Add Files...。在彈出的對話框中,將files of type改為匯編程序文件。添加文件Cstartup.s51,該文件可在Download的zip文件中找到
雙擊并打開文件Cstartup.s51。找到程序段聲明RSEG CSTART:CODE:ROOT(0)。這是代碼段的開始。變量段的起始地址在link51ew_400.xcl中聲明。代碼的開始在文件中的0x400000h進行聲明。另外,應有一個DB 'TINI'行,后跟另一個DB, high(?INIT)',帶有注釋Target Bank。這樣就可以確保應用程序對應于TINIm400 flash的起始地址
建立新文件main.c。在該文件中寫入如下代碼:
#include
#include
void main ()
{
printf("Test program using IAR compiler");
while (1)
{
}
}
保存文件內容。通過選擇Project→Add Files,然后在文件對話框中選擇main.c,這樣就把剛建立的文件加入到項目hellowworld中。確定加入的文件就是您建立的文件main.c;因為在默認目錄中很有可能存在另一個具有相同名字的文件
同樣,將文件low_level_init.s51和putchar.c加入到項目中。low_level_init.s51文件包含初級DS80C400初始化程序;putchar.c文件包含將字符輸出至默認控制臺的初級程序
從ftp://ftp.dalsemi.com/pub/tini/ds80c400/c_libraries/iar/bin/init.zip復制ROM initialization庫文件,并將其解壓縮到相同目錄下。將rominit.r51庫文件添加到項目中
在編譯Hello_World應用程序之前,我們需要配置IAR工具鏈來滿足DS80C400的目標要求
選擇Project→Options→General
單擊Target標簽并瀏覽,在Derivative中選擇DS80C400。將Extended stack at:對應的地址值改為0xFFDC00。這是因為IAR起始代碼重新定位DS80C400硬件堆棧至0xFFDC00。對應這些設置請參考圖1。
單擊Data Pointer標簽。選擇Number of DPTRs = 1。這是因為由Dallas Semiconductor所提供的庫文件是在該選項下產生。
?
圖1. 選擇新的IAR 8051項目Target選項。
選擇Project-Options→Options→ICC8051
單擊Code標簽。選擇Optimizations下的Size和None。
單擊List標簽。選擇Output List File和Output assembler File。
單擊Preprocessor標簽。在include路徑中放入以下程序:
$TOOLKIT_DIR$\INC\
$TOOLKIT_DIR$\INC\CLIB
$TOOLKIT_DIR$\src\lib
$TOOLKIT_DIR$\src\lib\clib
..\include\
上面最后一行是庫的頭文件(*.h)所處的include路徑。確定頭文件在上面指定的路徑中。$TOOLKIT_DIR$與IAR安裝路徑有關(例如,c:/program files/iar systems/embedded workbench 3.n/8051)。
選擇Project→Options→A8051
單擊List標簽。選中Output List File。
單擊Preprocessor標簽,在include路徑中放入以下程序:
$TOOLKIT_DIR$\INC\
$TOOLKIT_DIR$\src\lib
..\include\
上面最后一行是庫的頭文件(*.h; *.inc)所處的include路徑。確定頭文件在上面指定的路徑中。
選擇Project-→Options→XLINK
單擊Output標簽
在Output file中,選擇Override default,并將文本文件名改為hello_world.hex。
在Format中,選擇Other,并從選項中選擇Intel Extended。詳情請參考圖2。
單擊List標簽。選擇Generate Linker Listing。
單擊include標簽,參考圖3。
選擇Ignore CSTARTUP in Library。
單擊Override default,將文件名改為$TOOLKIT_DIR$\config\lnk51ew_400.xcl
。 $TOOLKIT_DIR$與IAR安裝路徑有關(例如,c:/program files/iar systems/embedded workbench 3.n/8051)。確定文件lnk51ew_400.xcl和lnk_base_400.xcl在指定的路徑中。可在zip文件Download中找到這些文件。
?
圖2. 選擇新的IAR 8051項目的XLINK Output選項。
?
圖3. 選擇新的IAR 8051項目的XLINK Include選項。
建立Hello_World應用程序。如果正確完成了每一步,建立過程應不會出現錯誤或警告信息;之后會在
現在已經生成了可執行文件,我們需要將應用程序裝載到TINIm400模塊中并運行。
將示例應用程序裝載到TINIm400模塊
本節說明如何使用Maxim/Dallas Semiconductor提供的Microcontroller Tool Kit (MTK)工具將IAR編譯器生成的hex文件裝載到TINIm400驗證模塊中。目前可用的MTK版本僅支持Windows。如果您的開發環境不是Windows?,需要使用JavaKit應用程序來裝載和執行應用程序。要使用JavaKit,您必須具備Java Runtime Environment1 (版本至少為1.2),并且安裝了Java Communications API2。JavaKit工具包含在MxTNI軟件開發包中。請下載MxTNI SDK。在撰寫本文時,發布的最新固件版本是1.15。運行JavaKit的指導說明可在TINI SDK docs目錄下的Running_JavaKit.txt文件中找到。如果您在運行MTK或JavaKit時遇到問題,可能其他人已經遇到過類似問題并已在Dallas Semiconductor討論組公布。您可以在討論組搜索現有文章(和新發表的文章)。
在此可下載最新版本的應用程序。要安裝MTK,請運行安裝文件并按照提示操作。成功安裝后,將會添加一個新的菜單組:Start→All Programs→Dallas Semiconductor MTK。MTK啟動后,會出現圖4. 所示的對話框。
?
圖4. 啟動時 MTK選項。
選擇TINI選項,以操作TINIm400評估板。
選擇了TINI之后,會打開MTK主窗口。從Options→Configure Serial Port菜單選項中選擇您用來與TINIm400通信的串口。然后,選擇Tini→Tini Options 菜單,就會出現下面的對話框。選擇DSTINIm400按鈕,配置MTK用于和TINIm400板通訊。圖5顯示了帶有DSTINIm400按鈕的對話框。
?
圖5. 選擇TINIm400配置選項。
選擇Tini→Open COMx在xxx baud菜單選項打開串口。接著選擇Tini→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
>
從File菜單中選擇Load HEX File。找到并選擇我們剛才生成的hello_world.hex文件。加載程序后,有兩種方法運行它。因為我們將程序加載到40區,您可以輸入:
> B40
> X
要選擇40區并運行那里的代碼,您也可以輸入:
> E
這會使ROM查找可執行代碼。它查找一個標識當前區具有可執行代碼的特定標簽。此標簽由文本'TINI'和隨后的當前區號碼組成,并位于當前區的0x0002地址。應用程序的起始代碼采用下面幾行聲明該標簽:
?VECTOR_TABLE:
sjmp ?INIT
DB 'TINI' ; Tag for TINI Environment 1.02c
; or later (ignored in 1.02b)
DB high(?INIT) ; Target bank
注意sjmp ?INIT語句位于0x40區的0x0000地址。其后緊跟著可執行標簽{ 'T', 'I', 'N', 'I', 0h},由于sjmp語句為兩個字節,所以該標簽地址位于0x0002處。當您鍵入E時,ROM從C0h區開始向下搜索可執行代碼。如果您鍵入E時,執行了其它代碼,則意味著ROM在一個比您的代碼裝載位置0x400000更高的地址找到了一個可執行標簽。如果出現這種情況,您可能需要找到此標簽的位置,并刪除那個區的內容。
與ROM以及IAR ROM庫接口
在高速微控制器用戶指南DS80C4003補充資料中說明了在匯編語言中調用ROM函數的過程。但是,在C中調用這些ROM函數會復雜一些。必須將參數從IAR C編譯器的規則轉換成ROM使用的規則。IAR編譯器通過硬件堆棧和寄存器相結合的方式傳遞參數。ROM函數以多種不同方式接受參數。例如,socket函數接收存儲在一個參數緩沖器中的參數。相反,許多功能函數接收由特殊功能寄存器或堆棧存儲器傳遞的參數。為了從IAR調用方式轉換為ROM參數方式,Dallas Semiconductor已經編寫了訪問ROM函數的庫。
在您的C程序中使用ROM函數只需包含一個頭文件并與相應的庫文件連接即可。用于IAR編譯器的ROM庫包括:
ROM初始化程序
DHCP客戶端
進程調度
Sockets (TCP、UDP和Multicast)
TFTP客戶端
功能函數(CRC16, 隨機數)
在撰寫本文時,還沒有為IAR編譯器提供包括文件系統、郵件客戶端和HTTP服務器之類的擴展庫。請關注IAR庫主頁上的DS80C4004升級信息,我們會添加更多支持IAR的庫。
簡單應用: HTTP服務器
這里編寫了一個簡單的http服務器說明如何使用一些ROM庫函數,特別是socket和進程調度庫。該示例應用程序由兩個模塊組成:一個HTTP服務器和一個SNTP客戶端。主程序生成一個新的子任務來運行http服務器,用于處理80端口上的客戶連接。父任務每60秒會試圖通過時間服務器同步當前時間。
SNTP客戶端模塊
以下代碼實現SNTP客戶端模塊的核心功能。
socket_handle = socket(0, SOCKET_TYPE_DATAGRAM, 0);
for (i=0;i<256;i++)
buffer[i] = 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 = htons(NTP_PORT) // port number
sendto(socket_handle, buffer, 48, 0, &address, sizeof(struct sockaddr));
recvfrom(socket_handle, buffer, 256, 0, &address, sizeof(struct sockaddr));
//IAR 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, "London", last_time_reading_1);
formatTimeString(timeStamp - (6 * SECONDS_PER_HOUR), "Dallas", last_time_reading_2);
formatTimeString(timeStamp + (5 * SECONDS_PER_HOUR) + (30 * SECONDS_PER_MINUTE), "Bangalore", last_time_reading_3);
formatTimeString(timeStamp - (10 * SECONDS_PER_HOUR), "Honolulu",
last_time_reading_4);
last_reading_seconds = getTimeSeconds();
closesocket(socket_handle);
SNTP客戶端模塊是通過RFC 1361實現的。SNTP模塊通過使用UDP協議和time.nist.gov通信,并請求一個時間標記。需注意撰寫本應用筆記時還不能提供DNS查找支持,因此time.nist.gov的IP地址是人工設定的。
首先,創建一個數據包socket并分配一個大約2秒(0x800==2048毫秒)的超時。這樣會保證如果和我們選中的服務器通信失敗,我們不會無休止地等待響應。
接下來的一行用來設置請求選項。在RFC 1361的第3節對這些位進行了說明。0x23在一個閏秒不產生告警,要求使用版本4 NTP,并聲明模式為Client。我們使用普通數據包函數sendto和recvfrom請求發送并接收響應后,將時間標記的秒賦予變量timeStamp,然后調整至參考日期1970年1月1號。用函數formatTimeString將時間標記轉換成一個可讀字符串,比如說"In London it is 05:33:19 on May 11, 2005"。
用函數getTimeSeconds 確定基于DS80C400內部時鐘的最后一次更新時間。由于程序大約每60秒更新一次,HTML網頁time.html將會使用該數值來報告上一次時間更新后已經過了多長時間。最后,關閉socket,SNTP客戶端進入另一個60秒的休眠期。
簡單HTTP服務器
這個時間服務器應用程序的另一個子模塊為web服務器。此應用程序中的HTTP服務器實現了一個RFC 2068中描述的HTTP服務器簡易版本。在我們的版本中,僅支持GET方法,忽略輸入頭文件,并且幾乎不給出輸出包頭。在撰寫這篇應用筆記時尚未提供文件系統庫,因此示例應用程序動態地生成HTML網頁。
通過調用Berkley-style socket函數來創建服務器socket。這使得建立一個服務器socket十分容易。以下代碼給出我們的簡易HTTP服務器的創建、綁定和接受新連接。
struct sockaddr local;
unsigned int socket_handle, new_socket_handle, temp;
socket_handle = socket(0, SOCKET_TYPE_STREAM, 0);
local.sin_port = htons(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)。
為IAR編譯器編寫DS80C400匯編函數的注意事項
IAR文檔提供了在8051匯編中編寫程序,可從C程序中調用的方法。若8051匯編函數由IAR編譯器編寫的C程序來調用,在編寫這些匯編語言時需切記以下幾點。如果沒有可用的寄存器傳遞變量時,會將這些變量以Little Endian順序壓入堆棧。
函數參變量傳遞約定
下表說明了變量的傳遞方式。ArgumentsCharacter
8-bit valuesR1,R2,R3,R4,R5
16-bit valuesR3:R2 or R5:R4
24-bit (pointer) valuesR3:R2:R1
32-bit valuesR5:R4:R3:R2
下表顯示了函數返回值的規則。ArgumentsCharacter
8-bit valuesR1
16-bit valuesR3:R2
24-bit (pointer) valuesR3:R2:R1
32-bit valuesR5:R4:R3:R2
函數int foo(int x, int y,void* ptr);的變量和返回值的傳遞如下:
?
數據類型存儲規則
IAR遵循Little Endian存儲規則。注意,IAR使用最低有效字節在前的二進制數據存儲格式。
例如,一個4字節長的數值0xDEADBEEF,將會按如下方式存儲:
?
一個簡單的匯編程序與'C'接口
本節演示如何編寫一個匯編程序并用IAR Embedded Workbench與'C'程序接口。應用程序交換16位和32位字節,并將交換后的字節輸出到默認的控制臺。C的可調用函數原型是int ltob( int *shortptr , long *longptr)。
本示例程序由兩個文件組成:main.c和eswap.s51。main.c調用我們用匯編語言編寫的示例函數ltob()。創建一個新項目,命名為endian;添加cstartup.s51、low_level_init.s51、putchar.c文件以及Dallas Semiconductor ROM初始化庫rominit.r51。詳細資料請參考上述從8051 IAR Embedded Workbench開始。
用以下內容來創建一個新的main.c文件,并將該文件添加到項目endian中。在C中,必須聲明一個函數,以便讓編譯器知道如何調用它。ltob()函數在main()之前聲明。注意在成功運行后函數ltob()會返回'0',而且,如果任一指針為NULL則返回非零值。程序應向控制臺輸出以下結果:
--------------------------------------------------------------------------------------------------------------------
Program output:
Set values: int= 0xdead long = 0x12345678
Converted values: int= 0xedde long = 0x78563412
-------------------------------------------------------------------------------------------------------------------
// program main.c
#include
#include
#include
int ltob(unsigned int *intptr,unsigned long *longptr);
void main()
{
unsigned long i = 0x12345678;
unsigned int k = 0xdead;
int err;
printf("set values: int=0x%x long=0x%lx
",k,i);
err = ltob(&k,&i);
if(err)
printf("Error: One of the pointers is NULL
");
else
printf("converted values: int=0x%x long=0x%lx
",k,i);
while(1)
;
}
創建一個新文件eswap.s51,輸入以下匯編代碼,并將它加入到項目endian中。這個匯編程序將我們的函數ltob()聲明為PUBLIC,因此它能夠由'C'程序調用。ltob()的第一個參數是指針,并通過DS80C400控制器的寄存器r3:r2:r1來傳遞。第二個參數也是一個指針,由IAR編譯器壓入偏移3至5堆棧(偏移3含有最低有效字節,偏移5含有最高有效字節)。首先,函數重新找到堆棧中存儲的指針(指向一個32位值),交換它所指向的值,將交換后的字節存儲在相同位置。同樣,16位值也被字節交換并存儲在交換前的同一位置。注意,通過匯編函數來保留寄存器r6和r7。這是因為IAR編譯器將這些寄存器視為永久寄存器,意味著任何函數調用都不應修改這些寄存器。
#include "reg400.inc"
r0_b0 equ 0 ; Register bank 0 equates.
r1_b0 equ 1
r2_b0 equ 2
r3_b0 equ 3
r4_b0 equ 4
r5_b0 equ 5
r6_b0 equ 6
r7_b0 equ 7
PROGRAM ENDIAN_SWAP
PUBLIC ltob
RSEG FAR_CODE:CODE:NOROOT(0)
; ********************************************************************
;
; int ltob(unsigned int* shortptr, unsigned long* longptr)
;
; ********************************************************************
ltob:
// shortptr is in r3:r2:r1
// longptr is in stack at offset 5
; get the longptr stored in the stack
mov a,SP
clr c
subb a,#5
mov b,a
mov a,esp
anl a,#0x3
orl a,#0xDC ; extended stack is at 0xff dc00
subb a,#00 ; subtract 0x0005 to point to MSB of 2 nd argument
mov DPX,#0xFF
mov DPH,a
mov DPL,b
push r6_b0 ; save r6:r7 for the compiler
push r7_b0
movx a,@DPTR
mov r4,a ;store least significant byte of 'longptr' in r4
inc DPTR
movx a,@DPTR
mov r5,a ;store middle byte of 'longptr' in r5
inc DPTR
movx a,@DPTR
mov r6,a ;store most significant byte of 'longptr' in r6
mov a,r4_b0
orl a,r5_b0
orl a,r6_b0
jz ltob_err ; is (longptr == NULL)?
mov dpx,r6_b0 ; point to the memory where 'longptr' is pointing to
mov dph,r5_b0
mov dpl,r4_b0
pop r6_b0 ; restore r6:r7 for the compiler
pop r7_b0
push dpx
push dph
push dpl
movx a,@dptr ; get the long value (in r4:r3:r2:r1) from the memory
mov r4,a
inc dptr
movx a,@dptr
mov r5,a
inc dptr
movx a,@dptr
mov r6,a
inc dptr
movx a,@dptr
mov r7,a
inc dptr
pop dpl
pop dph
pop dpx
mov a,r7_b0 ; swap the long value bytes and store it in memory
movx @dptr,a
inc dptr
mov a,r6_b0
movx @dptr,a
inc dptr
mov a,r5_b0
movx @dptr,a
inc dptr
mov a,r4_b0
movx @dptr,a
mov a,r1_b0 ; is (shortptr == NULL)?
orl a,r2_b0
orl a,r3_b0
jz ltob_err
mov dpx,r3_b0 ; point to a memory where the 'shortptr' is pointing to
mov dph,r2_b0
mov dpl,r1_b0
push dpx
push dph
push dpl
movx a,@DPTR ; get the integer value from memory
mov r2,a
inc dptr
movx a,@dptr
mov r1,a
inc dptr
pop dpl
pop dph
pop dpx
mov a,r1_b0 ; swap the integer bytes
movx @dptr,a
inc dptr
mov a,r2_b0
movx @dptr,a ; bytes of an integer are swapped and stored in memory
mov r3,#00 ; return 'success'
mov r2,#00
sjmp ltob_exit
ltob_err:
mov r3,#00 ; return 'error'
mov r2,#01
ltob_exit:
ret
END ; end of assembly program
?
局限性以及開發問題
以下是使用6.11A版的IAR編譯器時發現的局限性:
IAR編譯器用堆棧存儲本地變量。在DS80C400中,堆棧限制為1024字節。DS80C400庫的默認堆棧交換為384字節(ROM_SAVESIZE) 。如果您的程序聲明了多個堆棧變量,確保該限制也適當地變化。要改變默認任務的交換大小,使用Dallas Semiconductor的task_genesis(unsigned int savesize)庫或rom400_task.h中定義的task_fork(unsigned char priority, unsigned int savesize),并給savesize參數提供正確的值。
printf、sprintf等函數存在一些問題:只有選擇了'lowest optimization level'函數才能正常工作。要選擇優化等級,找到project→options→ICC8051,并選擇Code標簽中的'None'。
IAR printf, sprintf的默認庫不能正常工作。要使它們正常工作,您的C程序應包含IAR提供的C文件(如#include
結論
Dallas Semiconductor為IAR編譯器提供支持C程序訪問DS80C400 ROM軟件的函數。用C程序能夠訪問網棧、存儲管理器、進程調度器以及DS80C400的其它許多函數。使用C語言的DS80C400微控制器開發者能夠編寫出更精簡的應用程序,賦予系統足夠的速度、能力和代碼空間。Dallas Semiconductor正致力于將所有目前工作與Keil編譯器的DS80C400庫移植到IAR。請經常訪問DS80C400 IAR庫主頁獲得升級。
評論
查看更多