?一、環境介紹
網卡:ENC28J60
協議棧: UIP
開發軟件:Keil5
二、功能介紹
STM32控制ENC28J60+UIP協議棧創建TCP服務器(WEB服務器),支持瀏覽器訪問完成數據傳輸。 瀏覽器可以實時顯示溫度、時間、可以控制STM32開發板上的LED燈、蜂鳴器。
?
?
?
?
?
?
三、ENC28J60芯片介紹
ENC28J60 是帶有行業標準串行外設接口(Serial Peripheral Interface,SPI)的獨立以太網 控制器。它可作為任何配備有 SPI 的控制器的以太網接口。ENC28J60 符合 IEEE 802.3 的全部規范,采用了一系列包過濾機制以對傳入數據包進行限制。 它還提供了一個內部 DMA 模塊, 以實現快速數據吞吐和硬件支持的 IP 校驗和計算。 與主控制器的通信通過兩個中斷引腳和 SPI 實現,數據傳輸速率高達 10 Mb/s。兩個專用的引腳用于連接 LED,進行網絡活動狀態指示。ENC28J60 總共只有 28 腳,提供 QFN/TF。
ENC28J60 的主要特點如下:
兼容 IEEE802.3 協議的以太網控制器
集成 MAC 和 10 BASE-T 物理層支持全雙工和半雙工模式
數據沖突時可編程自動重發
SPI 接口速度可達 10Mbps
提供快速數據移動的內部 DMA 控制器
可配置的接收和發送緩沖區大小
兩個可編程 LED 輸出
帶7個中斷源的兩個中斷引腳
TTL 電平輸入
提供多種封裝:SOIC/SSOP/SPDIP/QFN 等。
ENC28J60 由七個主要功能模塊組成:
1) SPI 接口,充當主控制器和 ENC28J60 之間通信通道。
2) 控制寄存器,用于控制和監視 ENC28J60。
3) 雙端口 RAM 緩沖器,用于接收和發送數據包。
4) 判優器,當 DMA、發送和接收模塊發出請求時對 RAM 緩沖器的訪問進行控制。
5) 總線接口,對通過 SPI 接收的數據和命令進行解析。
6) MAC(Medium Access Control)模塊,實現符合 IEEE 802.3 標準的 MAC 邏輯。
7) PHY(物理層)模塊,對雙絞線上的模擬數據進行編碼和譯碼。
ENC28J60 還包括其他支持模塊,諸如振蕩器、片內穩壓器、電平變換器(提供可以接受 5V 電壓的 I/O 引腳)和系統控制邏輯。
四、UIP 簡介
uIP 由瑞典計算機科學學院(網絡嵌入式系統小組)的Adam Dunkels 開發。其源代碼由C 語 言編寫,并完全公開,uIP 的最新版本是 1.0 版本。 uIP 協議棧去掉了完整的 TCP/IP 中不常用的功能,簡化了通訊流程,但保留了網絡通信 必須使用的協議,設計重點放在了 IP/TCP/ICMP/UDP/ARP 這些網絡層和傳輸層協議上,保證 了其代碼的通用性和結構的穩定性。
由于 uIP 協議棧專門為嵌入式系統而設計,因此還具有如下優越功能:
1) 代碼非常少,其協議棧代碼不到 6K,很方便閱讀和移植。
2) 占用的內存數非常少,RAM 占用僅幾百字節。
3) 其硬件處理層、協議棧層和應用層共用一個全局緩存區,不存在數據的拷貝,且發送 和接收都是依靠這個緩存區,極大的節省空間和時間。
4) 支持多個主動連接和被動連接并發。
5) 其源代碼中提供一套實例程序:web 服務器,web 客戶端,電子郵件發送程序(SMTP 客 戶端),Telnet 服務器, DNS 主機名解析程序等。通用性強,移植起來基本不用修改就可以通過。
6) 對數據的處理采用輪循機制,不需要操作系統的支持。 由于 uIP 對資源的需求少和移植容易,大部分的 8 位微控制器都使用過uIP 協議棧, 而且很多的著名的嵌入式產品和項目(如衛星,Cisco 路由器,無線傳感器網絡)中都在使用 uIP 協議棧。 uIP 相當于一個代碼庫,通過一系列的函數實現與底層硬件和高層應用程序的通訊,對于 整個系統來說它內部的協議組是透明的,從而增加了協議的通用性。
uIP 提供的接口函數有:
1.初始化 uIP 協議棧:uip_init()
2.處理輸入包:uip_input()
3.處理周期計時事件:uip_periodic()
4.開始監聽端口:uip_listen()
5.連接到遠程主機:uip_connect()
6.接收到連接請求:uip_connected()
7.主動關閉連接:uip_close()
8.連接被關閉:uip_closed()
9.發出去的數據被應答:uip_acked()
10.在當前連接發送數據:uip_send()
11.在當前連接上收到新的數據:uip_newdata()
12.告訴對方要停止連接:uip_stop()
13.連接被意外終止:uip_aborted()
?
五、核心代碼
5.1 main.c
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include
#include
#include "enc28j60.h"
#include "time.h"
#include "uip.h"
#include "uip_arp.h"
#include "tapdev.h"
#include "timer.h"
#include "uip-conf.h"
#include "httpd.h"
#include "ds18b20.h"
#include "rtc.h"
void uip_EventPoll(void); //事件處理函數
#define UIP_BUF ((struct uip_eth_hdr *)&uip_buf[0])
/*
當Uip接收到Uip接收到底層傳遞的數據,將接收到的數據通過調用http_appcall(),傳遞給Webserver處理,
再通過handle_connection()先后調用handle_input()函數和handle_output()函數
handle_input()主要作用是分析http數據流:得到請求的路徑、解析出請求的文件名稱。
然后調用函數handle_output進行查找對應文件,進行發送到瀏覽器,完成交互。
注意:瀏覽器最好使用谷歌瀏覽器,否則會導致訪問失敗!
*/
int main()
{
u8 key;
u32 tcnt=0;
uip_ipaddr_t ipaddr; //保存IP地址信息
BeepInit(); //蜂鳴器初始化
LedInit(); //LED燈初始化
UsartInit(USART1,72,115200);
KeyInit(); //按鍵初始化
TimerInit(TIM6,72,10000); //定時器初始化,
DS18B20_Init();
RTC_Init();
SET_RTC_TIME(2019,5,24,10,58,20);
printf("串口工作正常!rn");
while(tapdev_init()) //初始化ENC28J60錯誤
{
printf("ENC28J60 Init Error!rn");
Delay72M_Ms(500);
}
printf("ENC28J60 初始化成功!rn");
uip_init(); //uIP初始化
uip_ipaddr(ipaddr, 192,168,1,89); //填充開發板IP地址
uip_sethostaddr(ipaddr); //設置開發板IP地址
uip_ipaddr(ipaddr, 192,168,1,1); //填充開發板網關地址
uip_setdraddr(ipaddr); //設置開發板網關IP地址(其實就是你路由器的IP地址)
uip_ipaddr(ipaddr, 255,255,255,0); //填充開發板網絡掩碼
uip_setnetmask(ipaddr); //填充開發板網絡掩碼
httpd_init(); //創建WEB服務器,設置監聽端口
while(1)
{
uip_EventPoll(); //輪詢方式處理處理網絡數據
}
}
/*
函數功能:uip事件處理函數,需要將該函數插入用戶主循環,循環調用
*/
void uip_EventPoll(void)
{
u8 i;
static struct timer arp_timer; //定義定時器
static u8 timer_ok=0;
if(timer_ok==0)//僅初始化一次
{
timer_ok = 1;
timer_set(&arp_timer,CLOCK_SECOND*10); //創建1個10秒的定時器
}
uip_len=tapdev_read(); //從網絡設備讀取一個IP包,得到數據長度.uip_len在uip.c中定義
if(uip_len>0) //有數據
{
//處理IP數據包(只有校驗通過的IP包才會被接收)
if(UIP_BUF->type == htons(UIP_ETHTYPE_IP))//是否是IP包?
{
uip_arp_ipin(); //去除以太網頭結構,更新ARP表
uip_input(); //IP包處理
//當上面的函數執行后,如果需要發送數據,則全局變量 uip_len > 0
//需要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量)
if(uip_len>0)//需要回應數據
{
uip_arp_out();//加以太網頭結構,在主動連接時可能要構造ARP請求
tapdev_send();//發送數據到以太網
}
} else if (UIP_BUF->type==htons(UIP_ETHTYPE_ARP))//處理arp報文,是否是ARP請求包?
{
uip_arp_arpin();
//當上面的函數執行后,如果需要發送數據,則全局變量uip_len>0
//需要發送的數據在uip_buf, 長度是uip_len(這是2個全局變量)
if(uip_len>0)tapdev_send();//需要發送數據,則通過tapdev_send發送
}
}
//輪流處理每個TCP連接, UIP_CONNS缺省是40個
for(i=0; i0
//需要發送的數據在uip_buf, 長度是uip_len (這是2個全局變量)
if(uip_len>0)
{
uip_arp_out();//加以太網頭結構,在主動連接時可能要構造ARP請求
tapdev_send();//發送數據到以太網
}
}
//每隔10秒調用1次ARP定時器函數 用于定期ARP處理,ARP表10秒更新一次,舊的條目會被拋棄
if(timer_expired(&arp_timer))
{
timer_reset(&arp_timer);
uip_arp_timer();
}
}
;>
5.2 enc28j60.c
#include "delay.h"
#include
#include "enc28j60.h"
/*
以下是ENC28J60驅動移植接口:
MISO--->PA6----主機輸入
MOSI--->PA7----主機輸出
SCLK--->PA5----時鐘信號
CS----->PA4----片選
RESET-->PG15---復位
*/
#define ENC28J60_CS PAout(4) //ENC28J60片選信號
#define ENC28J60_RST PGout(15) //ENC28J60復位信號
#define ENC28J60_MOSI PAout(7) //輸出
#define ENC28J60_MISO PAin(6) //輸入
#define ENC28J60_SCLK PAout(5) //時鐘線
static u8 ENC28J60BANK;
static u32 NextPacketPtr;
/*
函數功能:底層SPI接口收發一個字節
說 明:模擬SPI時序,ENC28J60時鐘線空閑電平為低電平,在第一個下降沿采集數據
*/
u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data)
{
u16 cnt=0;
while((SPI1->SR&1<<1)==0) //等待發送區空--等待發送緩沖為空
{
cnt++;
if(cnt>=65530)return 0; //超時退出 u16=2個字節
}
SPI1->DR=tx_data; //發送一個byte
cnt=0;
while((SPI1->SR&1<<0)==0) //等待接收完一個byte
{
cnt++;
if(cnt>=65530)return 0; //超時退出
}
return SPI1->DR; //返回收到的數據
}
/*
函數功能:復位ENC28J60,包括SPI初始化/IO初始化等
MISO--->PA6----主機輸入
MOSI--->PA7----主機輸出
SCLK--->PA5----時鐘信號
CS----->PA4----片選
RESET-->PG15---復位
*/
void ENC28J60_Reset(void)
{
/*開啟時鐘*/
RCC->APB2ENR|=1<<12; //開啟SPI1時鐘
RCC->APB2ENR|=1<<2; //PA
GPIOA->CRL&=0X0000FFFF; //清除寄存器
GPIOA->CRL|=0XB8B30000;
GPIOA->ODR|=0XF<<4; // 上拉--輸出高電平
GPIOA->ODR&=~(1<<5);
RCC->APB2ENR|=1<<8; //2 3 4 5 6 7 8
GPIOG->CRH&=0x0FFFFFFF;
GPIOG->CRH|=0x30000000;
/*SPI2基本配置*/
SPI1->CR1=0X0; //清空寄存器
SPI1->CR1|=0<<15; //選擇“雙線雙向”模式
SPI1->CR1|=0<<11; //使用8位數據幀格式進行發送/接收;
SPI1->CR1|=0<<10; //全雙工(發送和接收);
SPI1->CR1|=1<<9; //啟用軟件從設備管理
SPI1->CR1|=1<<8; //NSS
SPI1->CR1|=0<<7; //幀格式,先發送高位
SPI1->CR1|=0x1<<3;//當總線頻率為36MHZ時,SPI速度為18MHZ,高速。
SPI1->CR1|=1<<2; //配置為主設備
SPI1->CR1|=1<<1; //空閑狀態時, SCK保持高電平。
SPI1->CR1|=1<<0; //數據采樣從第二個時鐘邊沿開始。
SPI1->CR1|=1<<6; //開啟SPI設備。
//針對ENC28J60的特點(SCK空閑為低電平)修改SPI的設置
SPI1->CR1&=~(1<<6); //SPI設備失能
SPI1->CR1&=~(1<<1); //空閑模式下SCK為0 CPOL=0
SPI1->CR1&=~(1<<0); //數據采樣從第1個時間邊沿開始,CPHA=0
SPI1->CR1|=1<<6; //SPI設備使能
ENC28J60_RST=0; //復位ENC28J60
DelayMs(10);
ENC28J60_RST=1; //復位結束
DelayMs(10);
}
/*
函數功能:讀取ENC28J60寄存器(帶操作碼)
參 數:op:操作碼
addr:寄存器地址/參數
返 回 值:讀到的數據
*/
u8 ENC28J60_Read_Op(u8 op,u8 addr)
{
u8 dat=0;
ENC28J60_CS=0;
dat=op|(addr&ADDR_MASK);
ENC28J60_SPI_ReadWriteOneByte(dat);
dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
//如果是讀取MAC/MII寄存器,則第二次讀到的數據才是正確的,見手冊29頁
if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);
ENC28J60_CS=1;
return dat;
}
/*
函數功能:讀取ENC28J60寄存器(帶操作碼)
參 數:
op:操作碼
addr:寄存器地址
data:參數
*/
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)
{
u8 dat = 0;
ENC28J60_CS=0;
dat=op|(addr&ADDR_MASK);
ENC28J60_SPI_ReadWriteOneByte(dat);
ENC28J60_SPI_ReadWriteOneByte(data);
ENC28J60_CS=1;
}
/*
函數功能:讀取ENC28J60接收緩存數據
參 數:
len:要讀取的數據長度
data:輸出數據緩存區(末尾自動添加結束符)
*/
void ENC28J60_Read_Buf(u32 len,u8* data)
{
ENC28J60_CS=0;
ENC28J60_SPI_ReadWriteOneByte(ENC28J60_READ_BUF_MEM);
while(len)
{
len--;
*data=(u8)ENC28J60_SPI_ReadWriteOneByte(0);
data++;
}
*data='?';
ENC28J60_CS=1;
}
/*
函數功能:向ENC28J60寫發送緩存數據
參 數:
len:要寫入的數據長度
data:數據緩存區
*/
void ENC28J60_Write_Buf(u32 len,u8* data)
{
ENC28J60_CS=0;
ENC28J60_SPI_ReadWriteOneByte(ENC28J60_WRITE_BUF_MEM);
while(len)
{
len--;
ENC28J60_SPI_ReadWriteOneByte(*data);
data++;
}
ENC28J60_CS=1;
}
/*
函數功能:設置ENC28J60寄存器Bank
參 數:
ban:要設置的bank
*/
void ENC28J60_Set_Bank(u8 bank)
{
if((bank&BANK_MASK)!=ENC28J60BANK)//和當前bank不一致的時候,才設置
{
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0));
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5);
ENC28J60BANK=(bank&BANK_MASK);
}
}
/*
函數功能:讀取ENC28J60指定寄存器
參 數:addr:寄存器地址
返 回 值:讀到的數據
*/
u8 ENC28J60_Read(u8 addr)
{
ENC28J60_Set_Bank(addr);//設置BANK
return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr);
}
/*
函數功能:向ENC28J60指定寄存器寫數據
參 數:
addr:寄存器地址
data:要寫入的數據
*/
void ENC28J60_Write(u8 addr,u8 data)
{
ENC28J60_Set_Bank(addr);
ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data);
}
/*
函數功能:向ENC28J60的PHY寄存器寫入數據
參 數:
addr:寄存器地址
data:要寫入的數據
*/
void ENC28J60_PHY_Write(u8 addr,u32 data)
{
u16 retry=0;
ENC28J60_Write(MIREGADR,addr); //設置PHY寄存器地址
ENC28J60_Write(MIWRL,data); //寫入數據
ENC28J60_Write(MIWRH,data>>8);
while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待寫入PHY結束
}
/*
函數功能:初始化ENC28J60
參 數:macaddr:MAC地址
返 回 值:
0,初始化成功;
1,初始化失敗;
*/
u8 ENC28J60_Init(u8* macaddr)
{
u16 retry=0;
ENC28J60_Reset(); //復位底層引腳接口
ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//軟件復位
while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待時鐘穩定
{
retry++;
DelayMs(1);
};
if(retry>=500)return 1;//ENC28J60初始化失敗
// do bank 0 stuff
// initialize receive buffer
// 16-bit transfers,must write low byte first
// set receive buffer start address 設置接收緩沖區地址 8K字節容量
NextPacketPtr=RXSTART_INIT;
// Rx start
//接收緩沖器由一個硬件管理的循環FIFO 緩沖器構成。
//寄存器對ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作
//為指針,定義緩沖器的容量和其在存儲器中的位置。
//ERXST和ERXND指向的字節均包含在FIFO緩沖器內。
//當從以太網接口接收數據字節時,這些字節被順序寫入
//接收緩沖器。 但是當寫入由ERXND 指向的存儲單元
//后,硬件會自動將接收的下一字節寫入由ERXST 指向
//的存儲單元。 因此接收硬件將不會寫入FIFO 以外的單
//元。
//設置接收起始字節
ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);
//ERXWRPTH:ERXWRPTL 寄存器定義硬件向FIFO 中
//的哪個位置寫入其接收到的字節。 指針是只讀的,在成
//功接收到一個數據包后,硬件會自動更新指針。 指針可
//用于判斷FIFO 內剩余空間的大小 8K-1500。
//設置接收讀指針字節
ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF);
ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
//設置接收結束字節
ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF);
ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
//設置發送起始字節
ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF);
ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
//設置發送結束字節
ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF);
ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
// do bank 1 stuff,packet filter:
// For broadcast packets we allow only ARP packtets
// All other packets should be unicast only for our mac (MAADR)
//
// The pattern to match on is therefore
// Type ETH.DST
// ARP BROADCAST
// 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
// in binary these poitions are:11 0000 0011 1111
// This is hex 303F->EPMM0=0x3f,EPMM1=0x30
//接收過濾器
//UCEN:單播過濾器使能位
//當ANDOR = 1 時:
//1 = 目標地址與本地MAC 地址不匹配的數據包將被丟棄
//0 = 禁止過濾器
//當ANDOR = 0 時:
//1 = 目標地址與本地MAC 地址匹配的數據包會被接受
//0 = 禁止過濾器
//CRCEN:后過濾器CRC 校驗使能位
//1 = 所有CRC 無效的數據包都將被丟棄
//0 = 不考慮CRC 是否有效
//PMEN:格式匹配過濾器使能位
//當ANDOR = 1 時:
//1 = 數據包必須符合格式匹配條件,否則將被丟棄
//0 = 禁止過濾器
//當ANDOR = 0 時:
//1 = 符合格式匹配條件的數據包將被接受
//0 = 禁止過濾器
ENC28J60_Write(ERXFCON,ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
ENC28J60_Write(EPMM0,0x3f);
ENC28J60_Write(EPMM1,0x30);
ENC28J60_Write(EPMCSL,0xf9);
ENC28J60_Write(EPMCSH,0xf7);
// do bank 2 stuff
// enable MAC receive
//bit 0 MARXEN:MAC 接收使能位
//1 = 允許MAC 接收數據包
//0 = 禁止數據包接收
//bit 3 TXPAUS:暫停控制幀發送使能位
//1 = 允許MAC 發送暫停控制幀(用于全雙工模式下的流量控制)
//0 = 禁止暫停幀發送
//bit 2 RXPAUS:暫停控制幀接收使能位
//1 = 當接收到暫停控制幀時,禁止發送(正常操作)
//0 = 忽略接收到的暫停控制幀
ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
// bring MAC out of reset
//將MACON2 中的MARST 位清零,使MAC 退出復位狀態。
ENC28J60_Write(MACON2,0x00);
// enable automatic padding to 60bytes and CRC operations
//bit 7-5 PADCFG2:PACDFG0:自動填充和CRC 配置位
//111 = 用0 填充所有短幀至64 字節長,并追加一個有效的CRC
//110 = 不自動填充短幀
//101 = MAC 自動檢測具有8100h 類型字段的VLAN 協議幀,并自動填充到64 字節長。如果不
//是VLAN 幀,則填充至60 字節長。填充后還要追加一個有效的CRC
//100 = 不自動填充短幀
//011 = 用0 填充所有短幀至64 字節長,并追加一個有效的CRC
//010 = 不自動填充短幀
//001 = 用0 填充所有短幀至60 字節長,并追加一個有效的CRC
//000 = 不自動填充短幀
//bit 4 TXCRCEN:發送CRC 使能位
//1 = 不管PADCFG如何,MAC都會在發送幀的末尾追加一個有效的CRC。 如果PADCFG規定要
//追加有效的CRC,則必須將TXCRCEN 置1。
//0 = MAC不會追加CRC。 檢查最后4 個字節,如果不是有效的CRC 則報告給發送狀態向量。
//bit 0 FULDPX:MAC 全雙工使能位
//1 = MAC工作在全雙工模式下。 PHCON1.PDPXMD 位必須置1。
//0 = MAC工作在半雙工模式下。 PHCON1.PDPXMD 位必須清零。
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
// set inter-frame gap (non-back-to-back)
//配置非背對背包間間隔寄存器的低字節
//MAIPGL。 大多數應用使用12h 編程該寄存器。
//如果使用半雙工模式,應編程非背對背包間間隔
//寄存器的高字節MAIPGH。 大多數應用使用0Ch
//編程該寄存器。
ENC28J60_Write(MAIPGL,0x12);
ENC28J60_Write(MAIPGH,0x0C);
// set inter-frame gap (back-to-back)
//配置背對背包間間隔寄存器MABBIPG。當使用
//全雙工模式時,大多數應用使用15h 編程該寄存
//器,而使用半雙工模式時則使用12h 進行編程。
ENC28J60_Write(MABBIPG,0x15);
// Set the maximum packet size which the controller will accept
// Do not send packets longer than MAX_FRAMELEN:
// 最大幀長度 1500
ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF);
ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
// do bank 3 stuff
// write MAC address
// NOTE: MAC address in ENC28J60 is byte-backward
//設置MAC地址
ENC28J60_Write(MAADR5,macaddr[0]);
ENC28J60_Write(MAADR4,macaddr[1]);
ENC28J60_Write(MAADR3,macaddr[2]);
ENC28J60_Write(MAADR2,macaddr[3]);
ENC28J60_Write(MAADR1,macaddr[4]);
ENC28J60_Write(MAADR0,macaddr[5]);
//配置PHY為全雙工 LEDB為拉電流
ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);
// no loopback of transmitted frames 禁止環回
//HDLDIS:PHY 半雙工環回禁止位
//當PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 時:
//此位可被忽略。
//當PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 時:
//1 = 要發送的數據僅通過雙絞線接口發出
//0 = 要發送的數據會環回到MAC 并通過雙絞線接口發出
ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
// switch to bank 0
//ECON1 寄存器
//寄存器3-1 所示為ECON1 寄存器,它用于控制
//ENC28J60 的主要功能。 ECON1 中包含接收使能、發
//送請求、DMA 控制和存儲區選擇位。
ENC28J60_Set_Bank(ECON1);
// enable interrutps
//EIE: 以太網中斷允許寄存器
//bit 7 INTIE: 全局INT 中斷允許位
//1 = 允許中斷事件驅動INT 引腳
//0 = 禁止所有INT 引腳的活動(引腳始終被驅動為高電平)
//bit 6 PKTIE: 接收數據包待處理中斷允許位
//1 = 允許接收數據包待處理中斷
//0 = 禁止接收數據包待處理中斷
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE);
// enable packet reception
//bit 2 RXEN:接收使能位
//1 = 通過當前過濾器的數據包將被寫入接收緩沖器
//0 = 忽略所有接收的數據包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功
else return 1;
}
/*
函數功能:讀取EREVID
參 數:
*/
u8 ENC28J60_Get_EREVID(void)
{
//在EREVID 內也存儲了版本信息。 EREVID 是一個只讀控
//制寄存器,包含一個5 位標識符,用來標識器件特定硅片
//的版本號
return ENC28J60_Read(EREVID);
}
/*
函數功能:通過ENC28J60發送數據包到網絡
參 數:
len :數據包大小
packet:數據包
*/
void ENC28J60_Packet_Send(u32 len,u8* packet)
{
//設置發送緩沖區地址寫指針入口
ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF);
ENC28J60_Write(EWRPTH,TXSTART_INIT>>8);
//設置TXND指針,以對應給定的數據包大小
ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF);
ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8);
//寫每包控制字節(0x00表示使用macon3的設置)
ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00);
//復制數據包到發送緩沖區
//printf("len:%drn",len); //監視發送數據長度
ENC28J60_Write_Buf(len,packet);
//發送數據到網絡
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS);
//復位發送邏輯的問題。參見Rev. B4 Silicon Errata point 12.
if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS);
}
/*
函數功能:從網絡獲取一個數據包內容
函數參數:
maxlen:數據包最大允許接收長度
packet:數據包緩存區
返 回 值:收到的數據包長度(字節)
*/
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet)
{
u32 rxstat;
u32 len;
if(ENC28J60_Read(EPKTCNT)==0)return 0; //是否收到數據包?
//設置接收緩沖器讀指針
ENC28J60_Write(ERDPTL,(NextPacketPtr));
ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8);
// 讀下一個包的指針
NextPacketPtr=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
NextPacketPtr|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
//讀包的長度
len=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
len|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
len-=4; //去掉CRC計數
//讀取接收狀態
rxstat=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0);
rxstat|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8;
//限制接收長度
if (len>maxlen-1)len=maxlen-1;
//檢查CRC和符號錯誤
// ERXFCON.CRCEN為默認設置,一般我們不需要檢查.
if((rxstat&0x80)==0)len=0;//無效
else ENC28J60_Read_Buf(len,packet);//從接收緩沖器中復制數據包
//RX讀指針移動到下一個接收到的數據包的開始位置
//并釋放我們剛才讀出過的內存
ENC28J60_Write(ERXRDPTL,(NextPacketPtr));
ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8);
//遞減數據包計數器標志我們已經得到了這個包
ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
return(len);
}
5.3 enc28j60.h
#include "sys.h"
#ifndef __ENC28J60_H
#define __ENC28J60_H
#include "stm32f10x.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ENC28J60 Control Registers
// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address (bits 0-4)
// - Bank number (bits 5-6)
// - MAC/PHY indicator (bit 7)
#define ADDR_MASK 0x1F
#define BANK_MASK 0x60
#define SPRD_MASK 0x80
// All-bank registers
#define EIE 0x1B
#define EIR 0x1C
#define ESTAT 0x1D
#define ECON2 0x1E
#define ECON1 0x1F
// Bank 0 registers
#define ERDPTL (0x00|0x00)
#define ERDPTH (0x01|0x00)
#define EWRPTL (0x02|0x00)
#define EWRPTH (0x03|0x00)
#define ETXSTL (0x04|0x00)
#define ETXSTH (0x05|0x00)
#define ETXNDL (0x06|0x00)
#define ETXNDH (0x07|0x00)
#define ERXSTL (0x08|0x00)
#define ERXSTH (0x09|0x00)
#define ERXNDL (0x0A|0x00)
#define ERXNDH (0x0B|0x00)
//ERXWRPTH:ERXWRPTL 寄存器定義硬件向FIFO 中
//的哪個位置寫入其接收到的字節。 指針是只讀的,在成
//功接收到一個數據包后,硬件會自動更新指針。 指針可
//用于判斷FIFO 內剩余空間的大小。
#define ERXRDPTL (0x0C|0x00)
#define ERXRDPTH (0x0D|0x00)
#define ERXWRPTL (0x0E|0x00)
#define ERXWRPTH (0x0F|0x00)
#define EDMASTL (0x10|0x00)
#define EDMASTH (0x11|0x00)
#define EDMANDL (0x12|0x00)
#define EDMANDH (0x13|0x00)
#define EDMADSTL (0x14|0x00)
#define EDMADSTH (0x15|0x00)
#define EDMACSL (0x16|0x00)
#define EDMACSH (0x17|0x00)
// Bank 1 registers
#define EHT0 (0x00|0x20)
#define EHT1 (0x01|0x20)
#define EHT2 (0x02|0x20)
#define EHT3 (0x03|0x20)
#define EHT4 (0x04|0x20)
#define EHT5 (0x05|0x20)
#define EHT6 (0x06|0x20)
#define EHT7 (0x07|0x20)
#define EPMM0 (0x08|0x20)
#define EPMM1 (0x09|0x20)
#define EPMM2 (0x0A|0x20)
#define EPMM3 (0x0B|0x20)
#define EPMM4 (0x0C|0x20)
#define EPMM5 (0x0D|0x20)
#define EPMM6 (0x0E|0x20)
#define EPMM7 (0x0F|0x20)
#define EPMCSL (0x10|0x20)
#define EPMCSH (0x11|0x20)
#define EPMOL (0x14|0x20)
#define EPMOH (0x15|0x20)
#define EWOLIE (0x16|0x20)
#define EWOLIR (0x17|0x20)
#define ERXFCON (0x18|0x20)
#define EPKTCNT (0x19|0x20)
// Bank 2 registers
#define MACON1 (0x00|0x40|0x80)
#define MACON2 (0x01|0x40|0x80)
#define MACON3 (0x02|0x40|0x80)
#define MACON4 (0x03|0x40|0x80)
#define MABBIPG (0x04|0x40|0x80)
#define MAIPGL (0x06|0x40|0x80)
#define MAIPGH (0x07|0x40|0x80)
#define MACLCON1 (0x08|0x40|0x80)
#define MACLCON2 (0x09|0x40|0x80)
#define MAMXFLL (0x0A|0x40|0x80)
#define MAMXFLH (0x0B|0x40|0x80)
#define MAPHSUP (0x0D|0x40|0x80)
#define MICON (0x11|0x40|0x80)
#define MICMD (0x12|0x40|0x80)
#define MIREGADR (0x14|0x40|0x80)
#define MIWRL (0x16|0x40|0x80)
#define MIWRH (0x17|0x40|0x80)
#define MIRDL (0x18|0x40|0x80)
#define MIRDH (0x19|0x40|0x80)
// Bank 3 registers
#define MAADR1 (0x00|0x60|0x80)
#define MAADR0 (0x01|0x60|0x80)
#define MAADR3 (0x02|0x60|0x80)
#define MAADR2 (0x03|0x60|0x80)
#define MAADR5 (0x04|0x60|0x80)
#define MAADR4 (0x05|0x60|0x80)
#define EBSTSD (0x06|0x60)
#define EBSTCON (0x07|0x60)
#define EBSTCSL (0x08|0x60)
#define EBSTCSH (0x09|0x60)
#define MISTAT (0x0A|0x60|0x80)
#define EREVID (0x12|0x60)
#define ECOCON (0x15|0x60)
#define EFLOCON (0x17|0x60)
#define EPAUSL (0x18|0x60)
#define EPAUSH (0x19|0x60)
// PHY registers
#define PHCON1 0x00
#define PHSTAT1 0x01
#define PHHID1 0x02
#define PHHID2 0x03
#define PHCON2 0x10
#define PHSTAT2 0x11
#define PHIE 0x12
#define PHIR 0x13
#define PHLCON 0x14
// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN 0x80
#define ERXFCON_ANDOR 0x40
#define ERXFCON_CRCEN 0x20
#define ERXFCON_PMEN 0x10
#define ERXFCON_MPEN 0x08
#define ERXFCON_HTEN 0x04
#define ERXFCON_MCEN 0x02
#define ERXFCON_BCEN 0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE 0x80
#define EIE_PKTIE 0x40
#define EIE_DMAIE 0x20
#define EIE_LINKIE 0x10
#define EIE_TXIE 0x08
#define EIE_WOLIE 0x04
#define EIE_TXERIE 0x02
#define EIE_RXERIE 0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF 0x40
#define EIR_DMAIF 0x20
#define EIR_LINKIF 0x10
#define EIR_TXIF 0x08
#define EIR_WOLIF 0x04
#define EIR_TXERIF 0x02
#define EIR_RXERIF 0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT 0x80
#define ESTAT_LATECOL 0x10
#define ESTAT_RXBUSY 0x04
#define ESTAT_TXABRT 0x02
#define ESTAT_CLKRDY 0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC 0x80
#define ECON2_PKTDEC 0x40
#define ECON2_PWRSV 0x20
#define ECON2_VRPS 0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST 0x80
#define ECON1_RXRST 0x40
#define ECON1_DMAST 0x20
#define ECON1_CSUMEN 0x10
#define ECON1_TXRTS 0x08
#define ECON1_RXEN 0x04
#define ECON1_BSEL1 0x02
#define ECON1_BSEL0 0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK 0x10
#define MACON1_TXPAUS 0x08
#define MACON1_RXPAUS 0x04
#define MACON1_PASSALL 0x02
#define MACON1_MARXEN 0x01
// ENC28J60 MACON2 Register Bit Definitions
#define MACON2_MARST 0x80
#define MACON2_RNDRST 0x40
#define MACON2_MARXRST 0x08
#define MACON2_RFUNRST 0x04
#define MACON2_MATXRST 0x02
#define MACON2_TFUNRST 0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2 0x80
#define MACON3_PADCFG1 0x40
#define MACON3_PADCFG0 0x20
#define MACON3_TXCRCEN 0x10
#define MACON3_PHDRLEN 0x08
#define MACON3_HFRMLEN 0x04
#define MACON3_FRMLNEN 0x02
#define MACON3_FULDPX 0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN 0x02
#define MICMD_MIIRD 0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID 0x04
#define MISTAT_SCAN 0x02
#define MISTAT_BUSY 0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST 0x8000
#define PHCON1_PLOOPBK 0x4000
#define PHCON1_PPWRSV 0x0800
#define PHCON1_PDPXMD 0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX 0x1000
#define PHSTAT1_PHDPX 0x0800
#define PHSTAT1_LLSTAT 0x0004
#define PHSTAT1_JBSTAT 0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK 0x4000
#define PHCON2_TXDIS 0x2000
#define PHCON2_JABBER 0x0400
#define PHCON2_HDLDIS 0x0100
// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN 0x08
#define PKTCTRL_PPADEN 0x04
#define PKTCTRL_PCRCEN 0x02
#define PKTCTRL_POVERRIDE 0x01
// SPI operation codes
#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_CTRL_REG 0x40
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_SOFT_RESET 0xFF
// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
//
// start with recbuf at 0/
#define RXSTART_INIT 0x0
// receive buffer end
#define RXSTOP_INIT (0x1FFF-1518-1)
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (0~1518 bytes)
#define TXSTART_INIT (0x1FFF-1518)
// stp TX buffer at end of mem
#define TXSTOP_INIT 0x1FFF
// max frame length which the conroller will accept:
#define MAX_FRAMELEN 1518 // (note: maximum ethernet frame length would be 1518)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ENC28J60_Reset(void);
u8 ENC28J60_Read_Op(u8 op,u8 addr);
void ENC28J60_Write_Op(u8 op,u8 addr,u8 data);
void ENC28J60_Read_Buf(u32 len,u8* data);
void ENC28J60_Write_Buf(u32 len,u8* data);
void ENC28J60_Set_Bank(u8 bank);
u8 ENC28J60_Read(u8 addr);
void ENC28J60_Write(u8 addr,u8 data);
void ENC28J60_PHY_Write(u8 addr,u32 data);
u8 ENC28J60_Init(u8* macaddr);
u8 ENC28J60_Get_EREVID(void);
void ENC28J60_Packet_Send(u32 len,u8* packet);
u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet);
#endif
5.4 httpd-fs.c
/*
* Copyright (c) 2001, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
* Author: Adam Dunkels
*
* $Id: httpd-fs.c,v 1.1 2006/06/07 09:13:08 adam Exp $
*/
#include "httpd.h"
#include "httpd-fs.h"
#include "httpd-fsdata.h"
#ifndef NULL
#define NULL 0
#endif /* NULL */
#include "httpd-fsdata.c"
#if HTTPD_FS_STATISTICS
static u16_t count[HTTPD_FS_NUMFILES];
#endif /* HTTPD_FS_STATISTICS */
/*-----------------------------------------------------------------------------------*/
static u8_t
httpd_fs_strcmp(const char *str1, const char *str2)
{
u8_t i;
i = 0;
loop:
if(str2[i] == 0 ||
str1[i] == 'r' ||
str1[i] == 'n') {
return 0;
}
if(str1[i] != str2[i]) {
return 1;
}
++i;
goto loop;
}
#include
#include
#include "led.h"
#include "ds18b20.h"
#include "stm32f10x.h"
#include "rtc.h"
extern const unsigned char web_data[];
extern const char led1_on[];
extern const char led1_off[];
char ds18b20_temp[100]; //存放DS18B20溫度信息
u16 ds18b20_T;
u16 ds18b20_intT,ds18b20_decT; //溫度值的整數和小數部分
/*-----------------------------------------------------------------------------------*/
int
httpd_fs_open(const char *name, struct httpd_fs_file *file)
{
//第一次的默認頁面
if(strstr(name,"/index.html"))
{
file->data=(char*)web_data;
file->len = sizeof(web_data);
return 1;
}
else if(strstr(name,"/404.html"))
{
file->data=(char*)data_404_html;
file->len = sizeof(data_404_html);
return 0;
}
else if(strstr(name,"/test?data=off1"))
{
file->data=(char*)led1_on;
file->len = strlen(led1_on);
LED1=1;
return 1;
}
else if(strstr(name,"/test?data=on1"))
{
file->data=(char*)led1_off;
file->len = strlen(led1_off);
LED1=0;
return 1;
}
else if(strstr(name,"/test?data=off2"))
{
file->data=(char*)led1_on;
file->len = strlen(led1_on);
LED2=1;
return 1;
}
else if(strstr(name,"/test?data=on2"))
{
file->data=(char*)led1_off;
file->len = strlen(led1_off);
LED2=0;
return 1;
}
else if(strstr(name,"/test?data=off3"))
{
file->data=(char*)led1_on;
file->len = strlen(led1_on);
LED3=1;
return 1;
}
else if(strstr(name,"/test?data=on3"))
{
file->data=(char*)led1_off;
file->len = strlen(led1_off);
LED3=0;
return 1;
}
else if(strstr(name,"/test?data=off4"))
{
file->data=(char*)led1_on;
file->len = strlen(led1_on);
LED4=1;
return 1;
}
else if(strstr(name,"/test?data=on4"))
{
file->data=(char*)led1_off;
file->len = strlen(led1_off);
LED4=0;
return 1;
}
else if(strstr(name,"/test?data=off5"))
{
file->data=(char*)led1_on;
file->len = strlen(led1_on);
BEEP=0;
return 1;
}
else if(strstr(name,"/test?data=on5"))
{
file->data=(char*)led1_off;
file->len = strlen(led1_off);
BEEP=1;
return 1;
}
else if(strstr(name,"/test?data=temp"))
{
/*讀取溫度信息*/
ds18b20_T=DS18B20_Get_Temp();
ds18b20_intT = ds18b20_T >> 4; //分離出溫度值整數部分
ds18b20_decT = ds18b20_T & 0xF; //分離出溫度值小數部分
//printf("%d-%d-%d %d:%d:%drn",rtc_time.year,rtc_time.mon,rtc_time.day,rtc_time.hour,rtc_time.min,rtc_time.sec);
sprintf(ds18b20_temp,"%d.%d&%d-%d-%d %d:%d:%d",ds18b20_intT,ds18b20_decT,rtc_time.year,rtc_time.mon,rtc_time.day,rtc_time.hour,rtc_time.min,rtc_time.sec);
file->data=(char*)ds18b20_temp;
file->len = strlen(ds18b20_temp);
return 1;
}
return 0;
}
/*-----------------------------------------------------------------------------------*/
void
httpd_fs_init(void)
{
#if HTTPD_FS_STATISTICS
u16_t i;
for(i = 0; i < HTTPD_FS_NUMFILES; i++) {
count[i] = 0;
}
#endif /* HTTPD_FS_STATISTICS */
}
/*-----------------------------------------------------------------------------------*/
#if HTTPD_FS_STATISTICS
u16_t httpd_fs_count
(char *name)
{
struct httpd_fsdata_file_noconst *f;
u16_t i;
i = 0;
for(f = (struct httpd_fsdata_file_noconst *)HTTPD_FS_ROOT;
f != NULL;
f = (struct httpd_fsdata_file_noconst *)f->next) {
if(httpd_fs_strcmp(name, f->name) == 0) {
return count[i];
}
++i;
}
return 0;
}
#endif /* HTTPD_FS_STATISTICS */
/*-----------------------------------------------------------------------------------*/
@sics.se>
審核編輯:湯梓紅
?
-
服務器
+關注
關注
12文章
9225瀏覽量
85616 -
STM32
+關注
關注
2270文章
10906瀏覽量
356530 -
TCP
+關注
關注
8文章
1370瀏覽量
79135
發布評論請先 登錄
相關推薦
評論