51單片機SNMP網(wǎng)管板卡驅動(dòng)程序
SNMP網(wǎng)管板使用了RTL8019AS 10M ISA網(wǎng)卡芯片接入以太網(wǎng)。選它的好處是:NE2000兼容,軟件移植性好;接口簡(jiǎn)單不用轉換芯片如PCI-ISA橋;價(jià)格便宜2.1$/片(我的購入價(jià)為22元RMB/片);帶寬充裕(針對51);較長(cháng)一段時(shí)間內不會(huì )停產(chǎn)。8019有3種配置模式:跳線(xiàn)方式、即插即用PP方式、串行Flash配置方式。為了節省成本,我去掉了9346而使用X5045作為閃盤(pán)存儲MAC地址和其他可配置信息。PP模式用在PC機中,這里用不上。只剩下跳線(xiàn)配置模式可用,它的電路設計參考REALTEK提供的DEMO板圖紙。一天時(shí)間就可以完成,相對來(lái)說(shuō)硬件設計比較簡(jiǎn)單。
與這部分硬件相對應的軟件是網(wǎng)卡驅動(dòng)。所謂驅動(dòng)程序是指一組子程序,它們屏蔽了底層硬件處理細節,同時(shí)向上層軟件提供硬件無(wú)關(guān)接口。驅動(dòng)程序可以寫(xiě)成子程序嵌入到應用程序里(如DOS下的I/O端口操作和ISR),也可以放在動(dòng)態(tài)鏈接庫里,用到的時(shí)候再動(dòng)態(tài)調入以便節省內存。在WIN98中,為了使V86、WIN16、WIN32三種模式的應用程序共存,提出了虛擬機的概念,在CPU的配合下,系統工作在保護模式,OS接管了I/O、中斷、內存訪(fǎng)問(wèn),應用程序不能直接訪(fǎng)問(wèn)硬件。這樣提高了系統可靠性和兼容性,也帶來(lái)了軟件編程復雜的問(wèn)題。任何網(wǎng)卡驅動(dòng)都要按VXD或WDM模式編寫(xiě),對于硬件一側要處理虛擬機操作、總線(xiàn)協(xié)議(如ISA、PCI)、即插即用、電源管理;上層軟件一側要實(shí)現NDIS規范。因此在WIN98下實(shí)現網(wǎng)卡驅動(dòng)是一件相當復雜的事情。
我這里說(shuō)的驅動(dòng)程序特指實(shí)模式下的一組硬件芯片驅動(dòng)子程序。從程序員的角度看,8019工作流程非常簡(jiǎn)單,驅動(dòng)程序將要發(fā)送的數據包按指定格式寫(xiě)入芯片并啟動(dòng)發(fā)送命令,8019會(huì )自動(dòng)把數據包轉換成物理幀格式在物理信道上傳輸。反之,8019收到物理信號后將其還原成數據,按指定格式存放在芯片RAM中以便主機程序取用。簡(jiǎn)言之就是8019完成數據包和電信號之間的相互轉換:數據包===>電信號。以太網(wǎng)協(xié)議由芯片硬件自動(dòng)完成,對程序員透明。驅動(dòng)程序有3種功能:芯片初始化、收包、發(fā)包。
以太網(wǎng)協(xié)議不止一種,我用的是802.3。它的幀結構如圖1所示。物理信道上的收發(fā)操作均使用這個(gè)幀格式。其中,前導序列、幀起始位、CRC校驗由硬件自動(dòng)添加/刪除,與上層軟件無(wú)關(guān)。值得注意的是,收到的數據包格式并不是802.3幀的真子集,而是如圖2所示。明顯地,8019自動(dòng)添加了“接收狀態(tài)、下一頁(yè)指針、以太網(wǎng)幀長(cháng)度(以字節為單位)”三個(gè)數據成員(共4字節)。這些數據成員的引入方便了驅動(dòng)程序的設計,體現了軟硬件互相配合協(xié)同工作的設計思路。當然,發(fā)送數據包的格式是802.3幀的真子集,如圖3所示。

有了收發(fā)包的格式,如何發(fā)送和接收數據包呢?如圖4所示,先將待發(fā)送的數據包存入芯片RAM,給出發(fā)送緩沖區首地址和數據包長(cháng)度(寫(xiě)入TPSR、TBCR0,1),啟動(dòng)發(fā)送命令(CR=0x3E)即可實(shí)現8019發(fā)送功能。8019會(huì )自動(dòng)按以太網(wǎng)協(xié)議完成發(fā)送并將結果寫(xiě)入狀態(tài)寄存器。如圖5所示,接收緩沖區構成一個(gè)循環(huán)FIFO隊列,PSTART、PSTOP兩個(gè)寄存器限定了循環(huán)隊列的開(kāi)始和結束頁(yè),CURR為寫(xiě)入指針,受芯片控制,BNRY為讀出指針,由主機程序控制。根據CURR==BNRY+1?可以判斷是否收到新的數據包,新收到的數據包按圖2格式存于以CURR指出的地址為首址的RAM中。當CURR==BNRY時(shí)芯片停止接收數據包。如果做過(guò)FPGA設計,用過(guò)VHDL,可以想象到硬件芯片的工作原理。此處,設計2個(gè)8bit寄存器和一個(gè)2輸入比較器,當收到數據包時(shí),接收狀態(tài)機根據當前狀態(tài)和比較器結果決定下一個(gè)狀態(tài),如果CURR=BNRY,進(jìn)入停收狀態(tài);反之,CURR按模增1。8019數據手冊沒(méi)有給出硬件狀態(tài)機實(shí)現方法,說(shuō)明也很簡(jiǎn)略,往往要通過(guò)作實(shí)驗的方法推理出工作過(guò)程。比如,ISR寄存器不只和中斷有關(guān),當接收緩沖溢出時(shí),如果不清ISR(寫(xiě)入FFH),芯片將一直停止接收。在流量較大時(shí)溢出經(jīng)常發(fā)生,此時(shí)不清ISR,就會(huì )導致網(wǎng)卡芯片死機。

明白了發(fā)送和接收數據包的原理,那么數據包又是怎樣被主機寫(xiě)入芯片RAM和從芯片RAM讀出的呢?如圖6所示,主機設置好遠端DMA開(kāi)始地址(RSAR0,1)和遠端DMA數據字節數(RBCR0,1),并在CR中設置讀/寫(xiě),就可以從遠端DMA口寄存器里讀出芯片RAM里的數據/把數據寫(xiě)入芯片RAM。
何謂本地/遠端DMA呢?如圖7所示,“遠端”指CPU接口側;“本地”指8019的硬件收發(fā)電路側。沒(méi)有更深的意思,與遠近無(wú)關(guān),僅僅為了區分主機和芯片硬件兩個(gè)接口端。這里的DMA與平時(shí)所說(shuō)的DMA有點(diǎn)不同。RTL8019AS的local DMA操作是由控制器本身完成的,而其remote DMA并不是在無(wú)主處理器的參與下,數據能自動(dòng)移到主處理器的內存中。remote DMA指主機CPU給出起址和長(cháng)度就可以讀寫(xiě)芯片RAM,每操作一次RAM地址自動(dòng)加1。而普通RAM操作每次要先發(fā)地址再處理數據,速度較慢。
在一些高檔通信控制器上自帶有MAC控制器,工作原理與8019的差不多,比如:Motorola 68360/MPC860T內部的CPM帶有以太網(wǎng)處理器,通過(guò)設置BD表,使軟件和硬件協(xié)同工作,它的緩沖區更大且可靈活配置。這些通信控制器的設計,體現了軟硬件互相融合協(xié)同工作的趨勢:軟件硬化(VHDL),硬件軟化(DSP),希望大家關(guān)注!

如圖7所示,8019以太網(wǎng)控制器以存儲器(16K雙口RAM)為核心,本地和遠端控制器并發(fā)操作。這種體系結構滿(mǎn)足了數據帶寬的需要。8019擁有控制、狀態(tài)、數據寄存器,通過(guò)它們,51單片機可以與8019通信。由于51資源緊張,在實(shí)現TCPIP協(xié)議棧時(shí)不要進(jìn)行內存塊拷貝,建議(1)使用全局結構體變量,在內存中只保存一個(gè)數據包拷貝,其他沒(méi)有來(lái)得及處理的包保存在8019的16K RAM里;(2)使用查詢(xún)方式而不用中斷;(3)客戶(hù)服務(wù)器模型中服務(wù)器工作于串行方式,并發(fā)模式不適合51單片機。
芯片內部地址空間的分配如圖8所示,其中0x00-0x0B(工作于8位DMA模式)用于存放本節點(diǎn)MAC地址,奇偶地址內容是重復放置的。如:MAC地址0000 1234 5678存放在0x00-0x0B中為000000001212343456567878,單地址和雙地址的內容是重復的.一般使用偶數地址的內容,這主要是為了同時(shí)適應8位和16位的dma。Prom內容是網(wǎng)卡在上電復位的時(shí)候從93C46里讀出來(lái)的。如果你沒(méi)有使用93C46,就不要使用Prom,那么使用了93C46后如何獲得網(wǎng)卡的地址呢?有兩種方法,一是直接讀93C46,二是讀Prom。網(wǎng)卡MAC地址既不由93C46也不由Prom決定,而是由PAR0-PAR5寄存器決定。Prom只保存上電時(shí)從9346中讀出的MAC地址(如果有93C46的話(huà)),僅此而矣。
網(wǎng)卡MAC地址不是隨便定義的,它的組成結構如圖9所示。以太網(wǎng)的地址為48位,由ieee統一分配給網(wǎng)卡制造商,每個(gè)網(wǎng)卡的地址都必須是全球唯一的。共6個(gè)字節的長(cháng)度。FF:FF:FF:FF:FF:FF為廣播地址,只能用在目的地址段,不能作為源地址段。目的地址為廣播地址的數據包,可以被一個(gè)局域網(wǎng)內的所有網(wǎng)卡接收到。合法的以太網(wǎng)地址第32位組播標志必須為0。例如:
X0:XX:XX:XX:XX:XX
X2:XX:XX:XX:XX:XX
X4:XX:XX:XX:XX:XX
X6:XX:XX:XX:XX:XX
X8:XX:XX:XX:XX:XX
XA:XX:XX:XX:XX:XX
XC:XX:XX:XX:XX:XX
XE:XX:XX:XX:XX:XX
為合法以太網(wǎng)地址。上面的X代表0-F中的任一個(gè)。
地址
X1:XX:XX:XX:XX:XX
X3:XX:XX:XX:XX:XX
X5:XX:XX:XX:XX:XX
X7:XX:XX:XX:XX:XX
X9:XX:XX:XX:XX:XX
XB:XX:XX:XX:XX:XX
XD:XX:XX:XX:XX:XX
XF:XX:XX:XX:XX:XX
為組播地址,只能作為目的地址,不能作為源地址。組播地址可以被支持該組播地址的一組網(wǎng)卡接收到。組播地址主要用在視頻廣播,遠程喚醒(通過(guò)發(fā)一個(gè)特殊的數據包使網(wǎng)卡產(chǎn)生一個(gè)中斷信號,啟動(dòng)電腦),游戲(多個(gè)人在局域網(wǎng)里聯(lián)機打游戲)里等。
以下是一些具體的組播地址:
地址范圍
01:00:5E:00:00:00---01:00:5E:7F:FF:FF 用于ip地址的組播,其他組播地址跟tcp/ip無(wú)關(guān),不做介紹。
網(wǎng)卡可以接收以下3種地址的數據包:
第一種 目的地址跟自己的網(wǎng)卡地址是一樣的數據包;
第二種 目的地址為FF:FF:FF:FF:FF:FF廣播地址的數據包;
第三種 目的地址為跟自己的組播地址范圍相同的數據包。
在以太網(wǎng)的應用當中,如果你希望你的數據包只發(fā)給一個(gè)網(wǎng)卡,目的地址用對方的網(wǎng)卡地址;
如果你想把數據包發(fā)給所有的網(wǎng)卡,目的地址用廣播地址;
如果你想把數據包發(fā)給一組網(wǎng)卡,目的地址用組播地址。
其他用到的寄存器:
CR---命令寄存器 TSR---發(fā)送狀態(tài)寄存器 ISR---中斷狀態(tài)寄存器
RSR---接收狀態(tài)寄存器 RCR---接收配置寄存器 TCR---發(fā)送配置寄存器
DCR---數據配置寄存器 IMR---中斷屏蔽寄存器 NCR---包發(fā)送期間碰撞次數
FIFO---環(huán)回檢測后,查看FIFO內容
CNTR0---幀同步錯總計數器
CNTR1---CRC錯總計數器
CNTR2---丟包總計數器
PAR0-5---本節點(diǎn)MAC地址
MAR0-7---多播地址匹配
建議:將圖形中寄存器名稱(chēng)標注上頁(yè)號和地址偏移(如:BNRY 0頁(yè)0x03),打印出此圖,看圖編程,直觀(guān)且不容易出錯。
備注:收緩沖區、發(fā)緩沖區、數據存儲區在16K雙口RAM里的安排由用戶(hù)自行決定,只要不引起沖突即可,以下源程序代碼實(shí)現的只是其中的一種分配方案。
部分源程序清單:
struct ethernet{
unsigned char status; //接收狀態(tài)
unsigned char nextpage; //下一個(gè)頁(yè)
unsigned int length; //以太網(wǎng)長(cháng)度,以字節為單位
unsigned int destnodeid[3]; //目的網(wǎng)卡地址
unsigned int sourcenodeid[3]; //源網(wǎng)卡地址
unsigned int protocal; //下一層協(xié)議
unsigned char packet[1500]; //包的內容
};
void ne2000init()//ne2000網(wǎng)卡初始化
{
rtl8019as_rst();
reg00=0x21; //選擇頁(yè)0的寄存器,網(wǎng)卡停止運行,因為還沒(méi)有初始化。
delay_ms(10); //延時(shí)10毫秒,確保芯片進(jìn)入停止模式
//使芯片處于mon和loopback模式,跟外部網(wǎng)絡(luò )斷開(kāi)
page(0);
reg0a=0x00;
reg0b=0x00;
reg0c=0xE0; //monitor mode (no packet receive)
reg0d=0xE2; //loop back mode
//使用0x40-0x4B為網(wǎng)卡的發(fā)送緩沖區,共12頁(yè),剛好可以存儲2個(gè)最大的以太網(wǎng)包。
//使用0x4c-0x7f為網(wǎng)卡的接收緩沖區,共52頁(yè)。
reg01=0x4C; //Pstart 接收緩沖區范圍
reg02=0x80; //Pstop
reg03=0x4C; //BNRY
reg04=0x40; //TPSR 發(fā)送緩沖區范圍
reg07=0xFF;/*清除所有中斷標志位*/
reg0f=0x00;//IMR disable all interrupt
reg0e=0xC8; //DCR byte dma 8位dma方式
page(1); //選擇頁(yè)1的寄存器
reg07=0x4D; //CURR
reg08=0x00; //MAR0
reg09=0x41; //MAR1
reg0a=0x00; //MAR2
reg0b=0x80; //MAR3
reg0c=0x00; //MAR4
reg0d=0x00; //MAR5
reg0e=0x00; //MAR6
reg0f=0x00; //MAR7
initNIC(); //初始化MAC地址和網(wǎng)絡(luò )相關(guān)參數
//將網(wǎng)卡設置成正常的模式,跟外部網(wǎng)絡(luò )連接
page(0);
reg0c=0xCC; //RCR
reg0d=0xE0; //TCR
reg00=0x22; //這時(shí)讓芯片開(kāi)始工作?
reg07=0xFF; //清除所有中斷標志位
}
void send_packet(union netcard *txdnet,unsigned int length)//ne2000發(fā)包子程序
{//發(fā)送一個(gè)數據包的命令,長(cháng)度最小為60字節,最大1514字節需要發(fā)送的數據包要先存放在txdnet緩沖區
unsigned char i;
unsigned int ii;
page(0);
if(length60) length=60;
for(i=0;i3;i++)
txdnet->etherframe.sourcenodeid[i]=my_ethernet_address.words[i];
txd_buffer_select=!txd_buffer_select;
if(txd_buffer_select)
reg09=0x40 ; //txdwrite highaddress
else
reg09=0x46 ; //txdwrite highaddress
reg08=0x00; //read page address low
reg0b=length>>8; //read count high
reg0a=length0xFF; //read count low;
reg00=0x12; //write dma, page0
for(ii=4;ii reg10=txdnet->bytes.bytebuf[ii];
for(i=0;i6;i++){ //最多重發(fā)6次
for(ii=0;ii1000;ii++) //檢查txp為是否為低
if((reg000x04)==0) break;
if((reg040x01)!=0) break; //表示發(fā)送成功
reg00=0x3E;
}
if(txd_buffer_select) reg04=0x40; //txd packet start;
else reg04=0x46; //txd packet start;
reg06=length>>8; //high byte counter
reg05=length0xFF; //low byte counter
reg00=0x3E; //to sendpacket;
}
bit recv_packet(union netcard *rxdnet)//ne2000收包子程序
{
unsigned char i;
unsigned int ii;
unsigned char bnry,curr;
page(0);
reg07=0xFF;
bnry=reg03; //bnry page have read 讀頁(yè)指針
page(1);
curr=reg07; //curr writepoint 8019寫(xiě)頁(yè)指針
page(0);
if(curr==0)
return 0; //讀的過(guò)程出錯
bnry=bnry++;
if(bnry>0x7F) bnry=0x4C;
if(bnry!=curr){ //此時(shí)表示有新的數據包在緩沖區里
//讀取一包的前18個(gè)字節:4字節的8019頭部,6字節目的地址,6字節原地址,2字節協(xié)議
//在任何操作都最好返回page0
page(0);
reg09=bnry; //read page address high
reg08=0x00; //read page address low
reg0b=0x00; //read count high
reg0a=18; //read count low;
reg00=0x0A; //read dma
for(i=0;i18;i++)
rxdnet->bytes.bytebuf[i]=reg10;
i=rxdnet->bytes.bytebuf[3]; //將長(cháng)度字段的高低字節掉轉
rxdnet->bytes.bytebuf[3]=rxdnet->bytes.bytebuf[2];
rxdnet->bytes.bytebuf[2]=i;
rxdnet->etherframe.length=rxdnet->etherframe.length-4; //去掉4個(gè)字節的CRC
//表示讀入的數據包有效
if(((rxdnet->bytes.bytebuf[0]0x01)==0)||(rxdnet->bytes.bytebuf[1]>0x7F)||(rxdnet->bytes.bytebuf[1]0x4C)||(rxdnet->bytes.bytebuf[2]>0x06)){
//接收狀態(tài)錯誤,或者next_page_start錯誤或者長(cháng)度錯誤,將丟棄所有數據包
page(1);
curr=reg07; //page1
page(0); //切換回page0
bnry=curr-1;
if(bnry0x4C) bnry=0x7F;
reg03=bnry; //write to bnry
return 0;
}
else{//表示數據包是完好的.讀取剩下的數據
if((rxdnet->etherframe.protocal==0x0800)||(rxdnet->etherframe.protocal==0x0806)){
//協(xié)議為IP或ARP才接收
reg09=bnry; //read page address high
reg08=4; //read page address low
reg0b=rxdnet->etherframe.length>>8; //read count high
reg0a=rxdnet->etherframe.length0xFF; //read count low;
reg00=0x0A; //read dma
for(ii=4;iietherframe.length+4;ii++)
rxdnet->bytes.bytebuf[ii]=reg10;
}
bnry=rxdnet->bytes.bytebuf[1]-1;//next page start-1
if(bnry0x4C) bnry=0x7F;
reg03=bnry; //write to bnry
return 1; //have new packet
}
}
return 0;
}
評論