嵌入式軟體 期中報告 (ENC28J60 乙太網路驅動 base on cortex-m3) 指導老師 : 陳慶瀚教授 學號 :995201100 姓名 : 余坤昇
乙太網路 : 簡介 : 乙太網路 (Ethernet) 是一種電腦區域網路組網技術 IEEE 制定的 IEEE 802.3 標準給出了乙太網路的技術標準 它規定了包括物理層的連線 電訊號和介質存取層協定的內容 乙太網路是當前應用最普遍的區域網路技術 它很大程度上取代了其他區域網路標準, 如令牌環網 (token ring) FDDI 和 ARCNET 在乙太網路系統中, 控制方式是利用 載波感測多重存取 / 碰撞偵測 ( Carrier Sense Mutltiple Access with Collision Detection CSMA /CD ) 的演算法 乙太網路上須具有的三項基本能力 : (1) 傳送 接收已格式化的資料或封包 ( Packet ) (2) 在通知上層軟體之前, 需先對資料解碼並檢查位址是否正確 (3) 能對網路或資料封包有偵錯的能力 乙太網路的資料接收 ( Ethernet reception ) 在乙太網路上的資料接收非常簡單, 因為乙太網路為廣播式的傳輸媒介 接收端的主機會取回所有在網路上的封包, 但這不表示所有主機會處理所接收的每一個封包 接收端會先檢查封包是否符合最小長度 ( 64 bytes ), 隨後檢查接收位址是否為自己, 若位址不符則丟棄該封包, 而使用者將看不到此封包 下圖為其接收及傳送的流程 Ethernet 數據封包格式 : 前導碼 : 之所以要設前導碼, 是為了讓訊號接收端同步接收傳來的訊號, 指出訊框的起始點 前導碼包含一個位元組 (byte) 的資料, 這個位元組資料便是訊框起始界定符
號 ( SFD, Start of Frame Deliniter ) 有了 SFD 位元組後, 便能找出 MAC 訊 框的開頭 SFD 的八個位元碼 (bit) 是 10101011 目地位址 : 每一個網路節點都有其獨一無二的乙太網路位址 網路位址的前三個位元稱之為區段號碼 ( Block ID ) 區段號碼是由 IEEE 協會所核發的, 顯示這個網路設備的製造廠商 例如 : Intel 所取得的區段號碼是 00AA00 (hex) 網路位址的後三個位元組稱為裝置碼 ( Device ID ), 裝置號碼由各家廠商自行指定 所以所有的網路裝置, 都擁有獨一無二 ( 不重覆 ) 的網路位址 來源位址 : 顯示訊框是由何人所發送的 欄位長度 : 顯示出訊框所承載的資料總長度, 範圍由 0 ~ 1500 bytes 資料欄位 : 範圍由 0 ~ 1500 Bytes 如果資料欄位過短, 使得整個框架小於最短長度的限制時, 那麼 MAC 控制器會在該欄位上填入不定長度的留空, 讓整個訊框長度至少有 64 Bytes 若傳送的資料超過 1500 Bytes 時, 就由更高層協定 ( 通常是網路層 ) 來將資料分割成數個訊框來傳送 訊框檢查碼 : 最後, 在訊框的尾端加上檢查碼, 確保傳輸過程中沒有發生錯誤 以 CRC 檢查法, 檢 驗傳輸是否發生錯誤 參考網址 : http://www.cs.nchu.edu.tw/~fileman/notepad/dc1_3.htm SPI 介面簡介 序列周邊介面 (Serial Peripheral Interface Bus,SPI), 類似 I²C, 是一種 4 線同步序列資料協定, 適用於可攜式裝置平台系統, 但使用率較 I²C 少 序列周邊介面一般是 4 線, 有時亦可為 3 線, 有別於 I²C 的 2 線, 以及 1-Wire SPI 匯流排定義四組 logic signals. (1).SCLK Serial Clock ( 自 master 輸出 ) (2).MOSI/SIMO Master Output, Slave Input( 自 master 輸出 ) (3).MISO/SOMI Master Input, Slave Output( 自 slave 輸出 ) (4).SS Slave Select (active low; 自 master 輸出 )
ENC28J60 此晶片為已 SPI 為控制接口的獨立 ethernet 晶片乙太網控制器特性 IEEE 802.3 相容的乙太網控制器 集成 MAC 和 10 BASE-T PHY 接收器和衝突抑制電路 支持一個帶自動極性檢測和校正的 10BASE-T 端口 支援全雙工和半雙工模式 可程式設計在發生衝突時自動重發 可程式設計填充和 CRC 生成 可程式設計自動拒絕錯誤資料包 最高速度可達 10 Mb/s 的 SPI 介面 ENC28J60 由七個主要功能模組組成 : SPI 介面 扮演主控制器和 ENC28J60 之間通信通道
控制寄存器 用於控制和監視 ENC28J60 雙埠 RAM 緩衝器 用於接收和發送數據包 判優器 當 DMA 發送和接收模組發出請求時對 RAM 緩衝器的訪問進行控制 總線介面 對通過 SPI 接收的數據和命令進行解析 MAC (Medium Access Control) 模組 實現符合 IEEE 802.3 標準的 MAC 邏輯 PHY( 物理層 ) 模組 對雙絞線上的模擬數據進行編碼和解碼 原始碼 : int main(void) int rev; printf("set_system... \r\n"); Set_System(); /* System clocks configuration ---------------------------------------------*/ getchar(); printf("rcc_configuration... \r\n"); RCC_Configuration(); printf("spi1& GPIO configuration... \r\n"); SPI1_Init(); /* SPI1 configuration */ printf("webserver start... \r\n\n"); WebServer(); return rev; void RCC_Configuration(void) /* PCLK2 = HCLK/2 */ RCC_PCLK2Config(RCC_HCLK_Div2); /* GPIOA, GPIOB and SPI1 clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA RCC_APB2Periph_GPIOB RCC_APB2Periph_SPI1, ENABLE); //===============================SPI Init & GPIO Init=========================== void SPI1_Init(void) SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; /* Configure SPI1 pins: SCK = pin5, MISO = pin6, MOSI = pin7 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 GPIO_Pin_6 GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 設為 AF_PP 模式
GPIO_Init(GPIOA, &GPIO_InitStructure); /* 註 : MISO 也設為 AF_PP 是許可的, 一樣可以拿來當 MI(INPUT)*/ /* ConfigurePORTA.4 為 CS 並設為 OUT_PP */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); /* SPI1 configuration */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //spi 傳輸模式, 全雙工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI operating mode SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI data size 8b or 16b SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //SPI_CPOL selects the serial clock steady state SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //the clock active edge for the bit capture SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //data transfers start from MSB or LSB bit SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); /* Enable SPI1 */ SPI_Cmd(SPI1, ENABLE); //===============================SPI Init & GPIO Init=========================== unsigned char SPI1_ReadWrite(unsigned char writedat) /* Loop while DR register in not emplty */ while(spi_i2s_getflagstatus(spi1, SPI_I2S_FLAG_TXE) == RESET); /* Send byte through the SPI1 peripheral */ SPI_I2S_SendData(SPI1, writedat); /* Wait to receive a byte */ while(spi_i2s_getflagstatus(spi1, SPI_I2S_FLAG_RXNE) == RESET); /* Return the byte read from the SPI bus */ return SPI_I2S_ReceiveData(SPI1); //===============================SPI Init & GPIO Init=========================== const unsigned char mymac[6] = 0x54,0x55,0x58,0x10,0x00,0x24; const unsigned char myip[4] = 192,168,2,25; static unsigned int mywwwport =80; int WebServer(void)
unsigned int plen; unsigned char i=0; printf("initialize enc28j60... \r\n"); enc28j60init((unsigned char *)mymac); /*initialize enc28j60*/ printf("execute function init_ip_arp_udp_tcp \r\n"); /*initialize ip arp udp tcp*/ init_ip_arp_udp_tcp((unsigned char *)mymac,(unsigned char *)myip,mywwwport); while(1) // 抓出讀取緩衝器的東西丟到 buf 並回復數據長度 plen = enc28j60packetreceive(buffer_size, buf); // 若成立代表所來之 PACKET 是給你的 if(eth_type_is_arp_and_my_ip(buf,plen)) make_arp_answer_from_request(buf); // 來源 MAC 與目標 MAC 互換 continue; // 檢查 IP PACKET 是不是給我們的 : if(eth_type_is_ip_and_my_ip(buf,plen)==0) continue; // 判斷 buf[23]=1(icmp) & ICMP 為 echorequest 代表來的封包是 ping 指令 if(buf[ip_proto_p]==ip_proto_icmp_v && buf[icmp_type_p]==icmp_type_echorequest_v) make_echo_reply_from_request(buf, plen); // 回應 echo request 回送 echo reply continue; //============================= 初始化 ENC28J60====================================// void enc28j60init(unsigned char* macaddr) // initialize I/O ENC28J60_CSH(); enc28j60writeop(enc28j60_soft_reset, 0, ENC28J60_SOFT_RESET); NextPacketPtr = RXSTART_INIT; /* 設定讀寫緩衝器之指標起始位置 */ // Rx start enc28j60write(erxstl, RXSTART_INIT&0xFF); enc28j60write(erxsth, RXSTART_INIT>>8); // set receive pointer address enc28j60write(erxrdptl, RXSTART_INIT&0xFF); enc28j60write(erxrdpth, RXSTART_INIT>>8); // RX end enc28j60write(erxndl, RXSTOP_INIT&0xFF);
enc28j60write(erxndh, RXSTOP_INIT>>8); // TX start enc28j60write(etxstl, TXSTART_INIT&0xFF); enc28j60write(etxsth, TXSTART_INIT>>8); // TX end enc28j60write(etxndl, TXSTOP_INIT&0xFF); enc28j60write(etxndh, TXSTOP_INIT>>8); /* 設定 bank 1 裡面之必要值如 packet filter*/ enc28j60write(erxfcon, ERXFCON_UCEN ERXFCON_CRCEN ERXFCON_PMEN); enc28j60write(epmm0, 0x3f); enc28j60write(epmm1, 0x30); enc28j60write(epmcsl, 0xf9); enc28j60write(epmcsh, 0xf7); /* 設定 bank 2 裡面之必要值 enable MAC recieve 全雙工模式 */ enc28j60write(macon1, MACON1_MARXEN MACON1_TXPAUS MACON1_RXPAUS); // bring MAC out of reset enc28j60write(macon2, 0x00); /*MACON3 之 PADCFG0 = TXCRCEN = FRMLNEN = FULDPX = 1 代表用 0 填充所有短偵至 60BYTES 並加有效 CRC 全雙工模式 */ enc28j60writeop(enc28j60_bit_field_set, MACON3, MACON3_PADCFG0 MACON3_TXCRCEN MACON3_FRMLNEN MACON3_FULDPX); // set inter-frame gap (non-back-to-back) enc28j60write(maipgl, 0x12); enc28j60write(maipgh, 0x0C); // set inter-frame gap (back-to-back) enc28j60write(mabbipg, 0x12); // Set the maximum packet size which the controller will accept // Do not send packets longer than MAX_FRAMELEN: enc28j60write(mamxfll, MAX_FRAMELEN&0xFF); enc28j60write(mamxflh, MAX_FRAMELEN>>8); /*========= 寫 MAC ADDR 進去給 ENC28J60============*/ enc28j60write(maadr5, macaddr[0]); enc28j60write(maadr4, macaddr[1]); enc28j60write(maadr3, macaddr[2]); enc28j60write(maadr2, macaddr[3]); enc28j60write(maadr1, macaddr[4]); enc28j60write(maadr0, macaddr[5]);
printf("maadr5 = 0x%x\r\n", enc28j60read(maadr5)); printf("maadr4 = 0x%x\r\n", enc28j60read(maadr4)); printf("maadr3 = 0x%x\r\n", enc28j60read(maadr3)); printf("maadr2 = 0x%x\r\n", enc28j60read(maadr2)); printf("maadr1 = 0x%x\r\n", enc28j60read(maadr1)); printf("maadr0 = 0x%x\r\n", enc28j60read(maadr0)); /*========= 寫 MAC ADDR 進去給 ENC28J60============*/ enc28j60phywrite(phcon1, PHCON1_PDPXMD); // no loopback of transmitted frames enc28j60phywrite(phcon2, PHCON2_HDLDIS); // switch to bank 0 enc28j60setbank(econ1); // enable interrutps enc28j60writeop(enc28j60_bit_field_set, EIE, EIE_INTIE EIE_PKTIE); // enable packet reception enc28j60writeop(enc28j60_bit_field_set, ECON1, ECON1_RXEN); //============================= 初始化 ENC28J60====================================// //=======================ENC28J60 write Op========================================// void enc28j60writeop(unsigned char op, unsigned char address, unsigned char data) unsigned char dat = 0; ENC28J60_CSL(); // issue write command dat = op (address & ADDR_MASK); SPI1_ReadWrite(dat); // write data dat = data; SPI1_ReadWrite(dat); ENC28J60_CSH(); //=========================ENC28J60 write Op=======================================// //=============================ENC28J60 write=====================================// void enc28j60write(unsigned char address, unsigned char data) // set the bank enc28j60setbank(address); // do the write enc28j60writeop(enc28j60_write_ctrl_reg, address, data); //==============================ENC28J60 write====================================// //===============================ENC28J60 PHY write================================//
void enc28j60phywrite(unsigned char address, unsigned int data) // set the PHY register address enc28j60write(miregadr, address); // write the PHY data enc28j60write(miwrl, data); enc28j60write(miwrh, data>>8); // wait until the PHY write completes while(enc28j60read(mistat) & MISTAT_BUSY) //===========================ENC28J60 PHY write=================================// //==============================ENC28J60 set bank==============================// void enc28j60setbank(unsigned char address) // set the bank (if needed) if((address & BANK_MASK)!= Enc28j60Bank) // set the bank enc28j60writeop(enc28j60_bit_field_clr, ECON1, (ECON1_BSEL1 ECON1_BSEL0)); enc28j60writeop(enc28j60_bit_field_set, ECON1, (address & BANK_MASK)>>5); Enc28j60Bank = (address & BANK_MASK); //==============================ENC28J60 set bank==============================// //==============================ENC28J60 Read================================// unsigned char enc28j60read(unsigned char address) // set the bank enc28j60setbank(address); // do the read return enc28j60readop(enc28j60_read_ctrl_reg, address); //===============================ENC28J60 Read===============================// //==============================ENC28J60 Read Op===============================// unsigned char enc28j60readop(unsigned char op, unsigned char address) unsigned char dat = 0; ENC28J60_CSL(); dat = op (address & ADDR_MASK); SPI1_ReadWrite(dat); dat = SPI1_ReadWrite(0xFF); // do dummy read if needed (for mac and mii, see datasheet page 29) if(address & 0x80)
dat = SPI1_ReadWrite(0xFF); ENC28J60_CSH(); return dat; //==============================ENC28J60 Read Op===============================// //============================= 接收封包 ========================================// unsigned int enc28j60packetreceive(unsigned int maxlen, unsigned char* packet) unsigned int rxstat; unsigned int len; if( enc28j60read(epktcnt) ==0 ) return(0); // Set the read pointer to the start of the received packet enc28j60write(erdptl, (NextPacketPtr)); enc28j60write(erdpth, (NextPacketPtr)>>8); // read the next packet pointer NextPacketPtr = enc28j60readop(enc28j60_read_buf_mem, 0); NextPacketPtr = enc28j60readop(enc28j60_read_buf_mem, 0)<<8; // read the packet length (see datasheet page 43) len = enc28j60readop(enc28j60_read_buf_mem, 0); len = enc28j60readop(enc28j60_read_buf_mem, 0)<<8; len-=4; //remove the CRC count // read the receive status (see datasheet page 43) rxstat = enc28j60readop(enc28j60_read_buf_mem, 0); rxstat = enc28j60readop(enc28j60_read_buf_mem, 0)<<8; // limit retrieve length if (len>maxlen-1) len=maxlen-1; // check CRC and symbol errors (see datasheet page 44, table 7-3): // The ERXFCON.CRCEN is set by default. Normally we should not // need to check this. if ((rxstat & 0x80)==0) // invalid len=0; else
enc28j60readbuffer(len, packet); // 把接收緩衝器的東西讀進 packet 裡面 // Move the RX read pointer to the start of the next received packet // This frees the memory we just read out 回復讀指標 enc28j60write(erxrdptl, (NextPacketPtr)); enc28j60write(erxrdpth, (NextPacketPtr)>>8); // decrement the packet counter indicate we are done with this packet enc28j60writeop(enc28j60_bit_field_set, ECON2, ECON2_PKTDEC); return(len); //============================= 接收封包 ========================================// //============================= 傳送封包 ========================================// void enc28j60packetsend(unsigned int len, unsigned char* packet) // Set the write pointer to start of transmit buffer area enc28j60write(ewrptl, TXSTART_INIT&0xFF); enc28j60write(ewrpth, TXSTART_INIT>>8); // Set the TXND pointer to correspond to the packet size given enc28j60write(etxndl, (TXSTART_INIT+len)&0xFF); enc28j60write(etxndh, (TXSTART_INIT+len)>>8); // write per-packet control byte (0x00 means use macon3 settings) enc28j60writeop(enc28j60_write_buf_mem, 0, 0x00); // copy the packet into the transmit buffer enc28j60writebuffer(len, packet); // send the contents of the transmit buffer onto the network enc28j60writeop(enc28j60_bit_field_set, ECON1, ECON1_TXRTS); // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12. if( (enc28j60read(eir) & EIR_TXERIF) ) enc28j60writeop(enc28j60_bit_field_clr, ECON1, ECON1_TXRTS); //============================= 傳送封包 ========================================// //============================ 讀取 buffer======================================// void enc28j60readbuffer(unsigned int len, unsigned char* data) ENC28J60_CSL(); // issue read command
SPI1_ReadWrite(ENC28J60_READ_BUF_MEM); while(len) len--; // read data *data = (unsigned char)spi1_readwrite(0); data++; *data='\0'; ENC28J60_CSH(); //============================= 讀取 buffer======================================// //============================== 寫 buffer=====================================// void enc28j60writebuffer(unsigned int len, unsigned char* data) ENC28J60_CSL(); // issue write command SPI1_ReadWrite(ENC28J60_WRITE_BUF_MEM); while(len) len--; SPI1_ReadWrite(*data); data++; ENC28J60_CSH(); //============================= 寫 buffer======================================// void ENC28J60_CSL(void) GPIO_ResetBits(GPIOA, GPIO_Pin_4); void ENC28J60_CSH(void) GPIO_SetBits(GPIOA, GPIO_Pin_4); void init_ip_arp_udp_tcp(unsigned char *mymac,unsigned char *myip,unsigned char wwwp) unsigned char i=0; wwwport=wwwp; printf("ip address:"); while(i<4) ipaddr[i]=myip[i]; // 將設好之 ip 丟到 ipaddr 以供使用 printf("%d.",ipaddr[i]); i++;
printf("\n\n"); i=0; while(i<6) macaddr[i]=mymac[i]; // 將設好之 MAC 丟到 macaddr 以供使用 i++; //=====================packet 為 ARP 且 ip 為目標 ip================================ unsigned char eth_type_is_arp_and_my_ip(unsigned char *buf,unsigned int len) unsigned char i=0; //LEN <41 代表不是給目標機的 PACKET if (len<41) return(0); //type = 0x0806 則代表 type 為 ARP if(buf[eth_type_h_p]!= ETHTYPE_ARP_H_V buf[eth_type_l_p]!= ETHTYPE_ARP_L_V) return(0); // 檢查送來之 PACKET 內 ip 訊息是否跟目標機 IP 一樣 while(i<4) if(buf[eth_arp_dst_ip_p+i]!= ipaddr[i]) return(0); i++; return(1); //=====================packet 為 ARP 且 ip 為目標 ip================================ //=====================type 為 ip 且 ip 為目標 ip================================ unsigned char eth_type_is_ip_and_my_ip(unsigned char *buf,unsigned int len) unsigned char i=0; //eth+ip+udp header is 42 if (len<42) return(0); // 若 type = 0x0800 則為 IP type if(buf[eth_type_h_p]!=ethtype_ip_h_v buf[eth_type_l_p]!=ethtype_ip_l_v)
return(0); if (buf[ip_header_len_ver_p]!=0x45) // must be IP V4 and 20 byte header return(0); // 若 ip 不是接收方 ip 則 return 0 while(i<4) if(buf[ip_dst_p+i]!=ipaddr[i]) return(0); i++; return(1); //=====================type 為 ip 且 ip 為目標 ip================================ //=================eth 把 source & destination 的 MAC 互換 =================== void make_eth(unsigned char *buf) unsigned char i=0; // //copy the destination mac from the source and fill my mac into src while(i<6) buf[eth_dst_mac +i]=buf[eth_src_mac +i]; buf[eth_src_mac +i]=macaddr[i]; i++; //=================eth 把 source & destination 的 MAC 互換 =================== //=================eth 把 dst_ip = 源 ip & 源 ip = 自己 ip=================== void make_ip(unsigned char *buf) 源 unsigned char i=0; while(i<4) buf[ip_dst_p+i]=buf[ip_src_p+i]; buf[ip_src_p+i]=ipaddr[i]; i++;
fill_ip_hdr_checksum(buf); //=================eth 把 dst_ip = 源 ip & 源 ip = 自己 ip=================== 實驗步驟 : Step1: 將 ENC28J60 乙太網路模組織控制腳 (CS SCK SO SI) 與 SIOC 之 SPI 階腳 (MISO MOSI SCK P4.5) 連接 ENC28J60 SIOC(SPI 介面 ) CS Port4.5 SCK SCK (Port 4.6) SI MOSI(Port 4.8) SO MISO(Port 4.7) GND GND 3.3V 3.3V Step2: 將 dfu 檔燒進 SIOC 內 ( 此時先別將網路線連接 ) SIOC 燒入之資料 MAC & IP 為 MAC : 0x54,0x55,0x58,0x10,0x00,0x24; IP : 192.168.2.25 Step3: 離開燒錄模式, 進入 virtual comport 會得到以下畫面 已將 0x54.0x55.0x58.0x10.0x24 設為 ENC28J60 的 MAC address 已將 192.168.2.25 設為 ENC28J60 的 IP address
Step4: 設定本機電腦之 IP Host IP 192.168.2.10 子網路遮罩 255.255.255.0 預設閘道 192.168.2.1 DNS 設為空白用網路線將 SIOC+ENC28J60 與本機電腦作連接 Step5: 於本機電腦進入 命令提示字元 嘗試 ping 自己, 以測試本機電腦 IP 是否已成功修改 上圖可知, 本機電腦之 IP 確實已改為 192.168.2.10 Step6: 運用 ping 指令, 嘗試 ping SIOC+ENC28J60 成功 ping 到 SIOC+ENC28J60, 代表 embedded server 已達到初步設定 注 : Ping 的用法與原理 : ping 是 : 一個電腦網路工具, 用來測試特定主機能否通過 IP 到達 ping 的運作原理是 : 向目標主機傳出一個 ICMPecho 要求封包, 等待接收 echo 回應封包 程式會按時間和反應成功的次數, 估計失去封包率 ( 丟包率 ) 和封包來回時間 ( 網路時延 )(Round-trip delay time) 來源 : http://zh.wikipedia.org/wiki/ping