目的:實作STM32F407+FreeRTOS+Ethernet(DP83848)+Lwip實作socket通信,在實作之前我們先來了解下幾點儲備知識
一. 以太網行業標準MII/RMII
1 以太網介面MII,RMII
MII即“媒體獨立介面”,也叫“獨立于介質的介面”,它是IEEE-802.3定義的以太網行業標準,它包括一個資料介面,以及一個MAC和PHY之間的管理介面,
RMII全稱為“簡化的媒體獨立介面”,是IEEE-802.3u標準中除MII介面之外的另一種實作,
1.1. 獨立于介質的介面(MII)
獨立于介質的介面(MII)用于MAC與外接的PHY互聯,支持10Mbit/s和100Mbit/s資料傳輸模式,MII的信號線如下圖所示:
- MII_TX_EN:傳輸使能信號,此信號必需與資料前導符的起始位同步出現,并在傳輸完畢前一直保持,
- MII_TX_CLK:發送資料使用的時鐘信號,對于10M位/s的資料傳輸,此時鐘為2.5MHz,對于100M位/s的資料傳輸,此時鐘為25MHz,
- MII_TXD[3:0]:發送資料線,每次傳輸4位資料,資料在MII_TX_EN信號有效時有效,MII_TXD[0]是資料的最低位,MII_TXD[3]是最高位,當MII_TX_EN信號無效時,PHY忽略傳輸的資料,
- MII_RX_ER:接收出錯信號,保持一個或多個時鐘周期(MII_RX_CLK)的有效狀態,表明MAC在接收程序中檢測到錯誤,具體錯誤原因需配合MII_RX_DV的狀態及MII_RXD[3:0]的資料值,
- MII_RX_DV:接收資料使能信號,由PHY控制,當PHY準備好資料供MAC接收時,使能該信號,此信號必需和幀資料的首位同步出現,并保持有效直到資料傳輸完成,在傳送最后4位資料后的第一個時鐘之前,此信號必需變為無效狀態,為了正確的接收一個幀,有效電平不能滯后于資料線上的SFD位出現,
- MII_RX_CLK:接收資料使用的時鐘信號,對于10M位/s的資料傳輸,此時鐘為2.5MHz,對于100M位/s的資料傳輸,此時鐘為25MHz,
- MII_RXD[3:0]:接收資料線,每次接收4位資料,資料在MII_RX_DV信號有效時有效,MII_RXD[0]是資料的最低位,MII_RXD[3]是最高位,當MII_RX_EN無效,而MII_RX_ER有效時,MII_RXD[3:0]資料值代表特定的資訊(請參考表194),
- MII_CRS:載波偵聽信號,僅作業在半雙工模式下,由PHY控制,當發送或接收的介質非空閑時,使能此信號, PHY必需保證MII_CRS信號在發生沖突的整個時間段內都保持有效,不需要此信號與發送/接收的時鐘同步,
- MII_COL:沖突檢測信號,僅作業在半雙工模式下,由PHY控制,當檢測到介質發生沖突時,使能此信號,并且在整個沖突的持續時間內,保持此信號有效,此信號不需要和發送/接收的時鐘同步,
1.2 精簡的獨立于介質的介面(RMII)
精簡的獨立于介質介面(RMII)規范減少了以太網通信所需要的引腳數,根據IEEE802.3標準,MII介面需要16個資料和控制信號引腳,而RMII標準則將引腳數減少到了7個,RMII具有以下特性:
時鐘信號需要提高到50MHz.MAC和外部的以太網PHY需要使用同樣的時鐘源 ,使用2位寬度的資料收發
RMII的信號線如下圖所示:
1.3 時鐘源
1)MII時鐘源
為了產生TX_CLK和RX_CLK時鐘信號,外接的PHY模塊必需有來自外部的25MHz時鐘驅動,該時鐘不需要與MAC時鐘相同,可以使用外部的25MHz晶體,當時鐘來源MCO引腳時需配置合適的PLL,保證MCO引腳輸出的時鐘為25MHZ,
2)RMII時鐘源
通過將相同的時鐘源接到MAC和以太網PHY的REF_CLK引腳保證兩者時鐘源的同步,可以通過外部的50MHZ信號或者GD32F107xx微控制器的MCO引腳提供這一時鐘,當時鐘來源MCO引腳時需配置合適的PLL,保證MCO引腳輸出的時鐘為50MHZ,
3)總結
采用MII介面,PYH的時鐘頻率要求25M,不需要與MAC層時鐘一致,
采用RMII介面,PYH的時鐘頻率要求50M,需與MAC層時鐘一致,通常從MAC層獲取該時鐘源,
二. 以太網芯片(DP83848介紹)
芯片datasheet如下:
dp83848c.pdf
1. DP83848芯片概述
DP83848是TI出的一款以太網物理層IC,有以下feature
一般工規芯片選擇DP83848I就行了
2. DP83848引腳介紹
2.1 Serial Management Interface
2.2 MAC Data Interface
2.3 Clock Interface
2.4 LED Interface
2.5 Reset and Power Down
3. DP83848中斷暫存器
這個在后面移植的low_level_init會用到
三. CubeMx配置以太網芯片DP83848
1. DP83848 GPIO配置
HR91105A是這個

CubeMx GPIO配置如下(通過原理圖可以看到是RMII介面):
2. Cubemx Advanced Paramter配置
這里會牽扯到三個配置:
2.1 External PHY Configuration
此部分一共牽扯到以下部分:
PHY: 外部PHY芯片,我們開發板是選擇的這個DP83848,所以默認可以直接勾選上
PHY Address Value: 外部PHY芯片的地址,可以看到DP83848芯片是根據map來的,默認PHYAD0是默認上拉的,PHYAD[4:1]是默認下來的,所以默認地址是0x01
其他默認配置就行了!
2.2 Common:External PHY Configuration
這個我們選擇默認就好
2.3 Extended:External PHY Configuration
這個我們選擇默認就好
四. lwip介紹
LwIP 全名:Light weight IP,意思是輕量化的 TCP/IP 協議,是瑞典計算機科學院 (SICS) 的 Adam Dunkels 開發的一個小型開源的 TCP/IP 協議堆疊,LwIP 的設計初衷是:用少量的資源消耗實作一個較為完整的TCP/IP 協議堆疊,其中“完整”主要指的是 TCP 協議的完整性,實作的重點是在保持 TCP 協議主要功能的基礎上減少對 RAM 的占用,此外 LwIP 既可以移植到作業系統上運行,也可以在無作業系統的情況下獨立運行,
LwIP 具有主要特性:
- 支持 ARP 協議(以太網地址決議協議),
- 支持 ICMP 協議(控制報文協議),用于網路的除錯與維護,
- 支持 IGMP 協議(互聯網組管理協議),可以實作多播資料的接收,
- 支持 UDP 協議 (用戶資料報協議),
- 支持 TCP 協議 (傳輸控制協議),包括阻塞控制、RTT 估算、快速恢復和快速轉發,
- 支持 PPP 協議(點對點通信協議),支持 PPPoE,
- 支持 DNS(域名決議),
- 支持 DHCP 協議,動態分配 IP 地址,
- 支持 IP 協議,包括 IPv4、IPv6 協議,支持 IP 分片與重裝功能,多網路介面下的資料包轉發,
- 支持 SNMP 協議(簡單網路管理協議),
- 支持 AUTOIP,自動 IP 地址配置,
- 提供專門的內部回呼介面 (Raw API),用于提高應用程式性能,
- 提供可選擇的 Socket API、NETCONN API (在多執行緒情況下使用) ,
LwIP 在嵌入式中使用有以下優點:
- 資源開銷低,即輕量化,LwIP 內核有自己的記憶體管理策略和資料包管理策略,使得內核處理資料包的效率很高,另外,LwIP 高度可剪裁,一切不需要的功能都可以通過宏編譯選項去掉,LwIP 的流暢運行需要40KB 的代碼 ROM 和幾十 KB 的 RAM,這讓它非常適合用在記憶體資源受限的嵌入式設備中,
- 支持的協議較為完整,幾乎支持 TCP/IP 中所有常見的協議,這在嵌入式設備中早已夠用,
- 實作了一些常見的應用程式:DHCP 客戶端、DNS 客戶端、HTTP 服務器、MQTT 客戶端、TFTP 服務器、SNTP 客戶端等等,
- 同時提供了三種編程介面:RAW API、NETCONN API (注:NETCONN API 即為 SequentialAPI,為了統一,下文均采用 NETCONN API)和 Socket API,這三種 API 的執行效率、易用性、可移植性以及時空間的開銷各不相同,用戶可以根據實際需要,平衡利弊,選擇合適的 API 進行網路應用程式的開發,
- 高度可移植,其源代碼全部用 C 實作,用戶可以很方便地實作跨處理器、跨編譯器的移植,另外,它對內核中會使用到作業系統功能的地方進行了抽象,使用了一套自定義的 API,用戶可以通過自己實作這些 API,從而實作跨作業系統的移植作業,
- 開源、免費,用戶可以不用承擔任何商業風險地使用它,
- 相比于嵌入式領域其它的 TCP/IP 協議堆疊,比如 uC-TCP/IP、FreeRTOS-TCP 等,LwIP 的發展歷史要更悠久一些,得到了更多的驗證和測驗,LwIP 被廣泛用在嵌入式網路設備中,國內一些物聯網公司推出的物聯網作業系統,其 TCP/IP 核心就是 LwIP;物聯網知名的 WiFi模塊 ESP8266,其 TCP/IP 韌體,使用的就是 LwIP,
LwIP 盡管有如此多的優點,但它畢竟是為嵌入式而生,所以并沒有很完整地實作 TCP/IP 協議堆疊,相比于 Linux 和 Windows 系統自帶的 TCP/IP 協議堆疊,LwIP 的功能不算完整和強大,但對于大多數物聯網領域的網路應用程式,LwIP 已經足夠了,
1.檔案串列
可以通過這個鏈接去下載lwip的代碼:http://download.savannah.nongnu.org/releases/lwip/
打開后就是這個模樣:
檔案串列大體分為幾類:
1)drivers ,主要是提供一款32位的MCU(MCF5223X)部分驅動以及一款Eth的LAN芯片(CS8900A)
2)older_versions,主要是提供一些舊(1.4.0以前的版本)的lwip版本以及Contrib source code
3)contrib-xxxx,主要提供一些輔助代碼(示例/移植等)
4)lwip-xxxxx,lwip對應版本的代碼
📎lwip-2.1.2.zip
📎contrib-2.1.0.zip
2.LwIP 的三種編程介面
LwIP 提供了三種編程介面,分別為 RAW/Callback API、NETCONN API、SOCKET API,用戶可以根據實際情況,平衡利弊,選擇合適的 API 進行網路應用程式的開發,以下內容將分別介紹這三種 API,
2.1 RAW/Callback API
RAW/Callback API 是指內核回呼型的 API,這在許多通信協議的 C 語言實作中都有所應用,
RAW/Callback API 是 LwIP 的一大特色,在沒有作業系統支持的裸機環境中,只能使用這種 API進行開發,同時這種 API 也可以用在作業系統環境中,這里先簡要說明一下“回呼”的概念,新建了一個 TCP 或者UDP 的連接,你想等它接收到資料以后去處理它們,這時你需要把處理該資料的操作封裝成一個函式,然后將這個函式的指標注冊到 LwIP 內核中, LwIP 內核會在需要的時候去檢測該連接是否收到資料,如果收到了資料,內核會在第一時間呼叫注冊的函式,這個程序被稱為“回呼”,這個注冊函式被稱為“回呼函式”,這個回呼函式中裝著你想要的業務邏輯,在這個函式中,你可以自由地處理接收到的資料,也可以發送任何資料,也就是說,這個回呼函式就是你的應用程式,到這里,我們可以發現,在回呼編程中,LwIP 內核把資料交給應用程式的程序就只是一次簡單的函式呼叫,這是非常節省時間和空間資源的,每一個回呼函式實際上只是一個普通的 C 函式,這個函式在 TCP/IP 內核中被呼叫,每一個回呼函式都作為一個引數傳遞給當前 TCP 或 UDP 連接,而且,為了能夠保存程式的特定狀態,可以向回呼函式傳遞一個指定
的狀態,并且這個指定的狀態是獨立于 TCP/IP 協議堆疊的,,在有作業系統的環境中,如果使用 RAW/Callback API,用戶的應用程式就以回呼函式的形式成為了內核代碼的一部分,用戶應用程式和內核程式會處于同一個執行緒之中,這就省去了任務間通信和切換任務的開銷了,
簡單來說,RAW/Callback API 的優點有兩個:
(1)可以在沒有作業系統的環境中使用,
(2)在有作業系統的環境中使用它,對比另外兩種 API,可以提高應用程式的效率、節省記憶體開銷,
RAW/Callback API 的優點是顯著的,但缺點也是顯著的:
(1)基于回呼函式開發應用程式時的思維程序比較復雜,在后面與 RAW/Callback API 相關的章節中可以看到,利用回呼函式去實作復雜的業務邏輯時,會很麻煩,而且代碼的可讀性較差,
(2)在作業系統環境中,應用程式代碼與內核代碼處于同一個執行緒,雖然能夠節省任務間通信和切換任務的開銷,但是相應地,應用程式的執行會制約內核程式的執行,不同的應用程式之間也會互相制約,在應用程式執行的程序中,內核程式將不可能得到運行,這會影響網路資料包的處理效率,如果應用程式占用的時間過長,而且碰巧這時又有大量的資料包到達,由于內核代碼長期得不到執行,網卡接收快取里的資料包就持續積累,到最后很可能因為滿載而丟棄一些資料包,從而造成丟包的現象,
2.2 NETCONN API
在作業系統環境中,可以使用 NETCONN API 或者 Socket API 進行網路應用程式的開發,NETCONN API 是基于作業系統的 IPC 機制(即信號量和郵箱機制)實作的,它的設計將 LwIP 內核代碼和網路應用程式分離成了獨立的執行緒,如此一來,LwIP 內核執行緒就只負責資料包的 TCP/IP封裝和拆封,而不用進行資料的應用層處理,大大提高了系統對網路資料包的處理效率,前面提到,使用 RAW/Callback API 會造成內核程式和網路應用程式、不同網路應用程式之間的相互制約,如果使用 NETCONN API 或者 Socket API,這種制約將不復存在,
在作業系統環境中,LwIP 內核會被實作為一個獨立的執行緒,名為 tcpip_thread,使用 NETCONN API 或者 Socket API 的應用程式處在不同的執行緒中,我們可以根據任務的重要性,分配不同的優先級給這些執行緒,從而保證重要任務的時效性,分配優先級的原則具體見表格 ,表格 執行緒優先級分配原則
NETCONN API 使用了作業系統的 IPC 機制,對網路連接進行了抽象,用戶可以像操作檔案一樣操作網路連接(打開/關閉、讀/寫資料),但是 NETCONN API 并不如操作檔案的 API 那樣簡單易用,舉個例子,呼叫 f_read 函式讀檔案時,讀到的資料會被放在一個用戶指定的陣列中,用戶操作起來很方便,而 NETCONN API 的讀資料 API,就沒有那么人性化了,用戶獲得的不是一個陣列,而是一個特殊的資料結構netbuf,用戶如果想使用好它,就需要對內核的 pbuf 和 netbuf 結構體有所了解,我們會在后續的章節中對它們進行講解,NETCONN API 之所以采取這種不人性的設計,是為了避免資料包在內核程式和應用程式之間發生拷貝,從而降低程式運行效率,當然,用戶如果不在意資料遞交時的效率問題,也可以把 netbuf 中的資料取出來拷貝到一個陣列中,然后去處理這個陣列,
簡單來說,NETCONN API 的優缺點是:
(1)相較于 RAW/Callback API,NETCONN API 簡化了編程作業,使用戶可以按照操作檔案的方式來操作網路連接,但是,內核程式和網路應用程式之間的資料包傳遞,需要依靠作業系統的信號量和郵箱機制完成,這需要耗費更多的時間和記憶體,另外還要加上任務切換的時間開銷,效率較低,
(2)相較于 Socket API,NETCONN API 避免了內核程式和網路應用程式之間的資料拷貝,提高了資料遞交的效率,但是,NETCONN API 的易用性不如 Socket API 好,它需要用戶對 LwIP 內核所使用資料結構有一定的了解,
2.3 SOCKET API
Socket,即套接字,它對網路連接進行了高級的抽象,使得用戶可以像操作檔案一樣操作網路連接,它十分易用,許多網路開發人員最早接觸的就是 Socket 編程,Socket 已經成為了網路編程的標準,在不同的系統中,運行著不同的 TCP/IP 協議,但是只要它實作了 Socket 的介面,那么用Socket 撰寫的網路應用程式就能在其中運行,可見用 Socket 撰寫的網路應用程式具有很好的可移植性,不同的系統有自己的一套Socket 介面,Windows 系統中支持的是 WinSock,UNIX/Linux 系統中支持的是 BSD Socket,它們雖然風格不一致,但大同小異,LwIP 中的 Socket API 是 BSD Socket,但是 LwIP 并沒有也沒辦法實作全部的 BSD Socket,如果開發人員想要移植 UNIX/Linux 系統中的網路應用程式到使用 LwIP 的系統中,就要注意這一點,
相較于 NETCONN API, Socket API 具有更好的易用性,使用 Socket API 撰寫的程式可讀性好,便于維護,也便于移植到其它的系統中,Socket API 在內核程式和應用程式之間存在資料的拷貝,這會降低資料遞交的效率,另外,LwIP 的 Socket API 是基于 NETCONN API 實作的,所以效率上相較前者要打個折扣,
五. STM32F407在沒有作業系統下移植lwip
1.Cubemx配置程序
1.1 RCC配置(選擇外部晶振)
1.2 SYS配置(時基選擇Systick)
1.3 Eth配置
我們以太網配置,我們已經在前面(CubeMx配置以太網芯片DP83848)做了說明,所以不再重復!
1.4 UART1配置(主要用來debug)
在這個基礎上需要自己添加fputc函式(勾選micro lib)
int fputc(int ch, FILE *fp)
{
if (fp == stdout)
{
/*If you want to output a line feed, you can automatically add a carriage
return to prevent the horizontal position of some terminal terminals
from changing after line feed.*/
if (ch == '\n')
{
while ((USART1->SR & USART_SR_TXE) == 0);
USART1->DR = '\r';
}
/* Output characters to USART1 */
while ((USART1->SR & USART_SR_TXE) == 0);
USART1->DR = ch;
}
return ch;
}
1.5 lwip配置
此部分我們暫時除了DHCP部分,我們其他都用默認的!
這里有一個注意的點:IP地址的第三個域要根據路由器來決定,比如我的路由器IP地址是192.168.1.1,那么我可以隨意配置成192.168.1.x(其中x在這個局域網中不能有重復地址)
1.6 stm32f407 clock
有幾個注意的點:
- ①需要根據外部晶振來填寫HSE的value,我的開發板外部晶振是25M
- ②選擇HSE
- ③選擇PLLCLK倍頻
- ④選擇Max 168M,讓cubemx自己配置就行了
1.6 在main函式的while loop中加上lwip的處理MX_LWIP_Process
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_LWIP_Init();
/* USER CODE BEGIN 2 */
printf("STM32F407 ETH LWIP RUNING...\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
MX_LWIP_Process(); /* 嗨,我在這里哦,添加在這里,別找不到我! */
}
/* USER CODE END 3 */
}
豐收的時候到了哇!!!我們來ping下試試看
2.自己移植lwip程序
2.1 配置lwip的宏檔案lwipopts.h(可以直接把這個copy到你的noos的lwip中)
#ifndef __LWIPOPTS__H__
#define __LWIPOPTS__H__
#include "main.h"
/*-----------------------------------------------------------------------------*/
/* Current version of LwIP supported by CubeMx: 2.1.2 -*/
/*-----------------------------------------------------------------------------*/
/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#ifdef __cplusplus
extern "C" {
#endif
/* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- WITH_RTOS disabled (Since FREERTOS is not set) -----*/
#define WITH_RTOS 0
/*----- CHECKSUM_BY_HARDWARE enabled -----*/
#define CHECKSUM_BY_HARDWARE 1
/*-----------------------------------------------------------------------------*/
/* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- Value in opt.h for NO_SYS: 0 -----*/
#define NO_SYS 1 /* NO_SYS 表示無作業系統模擬層,這個宏非常重要,因為無作業系統與有作業系統的移植和撰寫是完全不一樣的,我們現在是無作業系統移植,所以將這個宏定義為 1,*/
/*----- Value in opt.h for SYS_LIGHTWEIGHT_PROT: 1 -----*/
#define SYS_LIGHTWEIGHT_PROT 0
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4 /* 記憶體對齊,按照 4 位元組對齊 */
/*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
#define LWIP_ETHERNET 1
/*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
#define LWIP_DNS_SECURE 7
/*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
#define TCP_SND_QUEUELEN 9
/*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
#define TCP_SNDLOWAT 1071
/*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
#define TCP_SNDQUEUELOWAT 5
/*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
#define TCP_WND_UPDATE_THRESHOLD 536
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for LWIP_NETCONN: 1 -----*/
#define LWIP_NETCONN 0
/*----- Value in opt.h for LWIP_SOCKET: 1 -----*/
#define LWIP_SOCKET 0
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
#define RECV_BUFSIZE_DEFAULT 2000000000
/*----- Value in opt.h for LWIP_STATS: 1 -----*/
#define LWIP_STATS 0
/*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
#define CHECKSUM_GEN_IP 0
/*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
#define CHECKSUM_GEN_UDP 0
/*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
#define CHECKSUM_GEN_TCP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/
#define CHECKSUM_GEN_ICMP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
#define CHECKSUM_GEN_ICMP6 0
/*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
#define CHECKSUM_CHECK_IP 0
/*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
#define CHECKSUM_CHECK_UDP 0
/*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
#define CHECKSUM_CHECK_TCP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/
#define CHECKSUM_CHECK_ICMP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
#define CHECKSUM_CHECK_ICMP6 0
/*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
#ifdef __cplusplus
}
#endif
#endif /*__LWIPOPTS__H__ */
2.2 配置位元組對齊等宏cc.h
#ifndef __CC_H__
#define __CC_H__
#include "cpu.h"
#include <stdlib.h>
#include <stdio.h>
typedef int sys_prot_t;
#define LWIP_PROVIDE_ERRNO
#if defined (__GNUC__) & !defined (__CC_ARM)
#define LWIP_TIMEVAL_PRIVATE 0
#include <sys/time.h>
#endif
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
x, __LINE__, __FILE__); } while(0)
/* Define random number generator function */
#define LWIP_RAND() ((u32_t)rand())
#endif /* __CC_H__ */
2.3 把eth對接到lwip init/output/input函式中
主要牽扯到的函式(這三個函式都在ethernetif.c中)是:
1)low_level_init
2)low_level_output
3)low_level_input
low_level_init 就是底層初始化,代碼如下:
static void low_level_init(struct netif *netif)
{
uint32_t regvalue = 0;
HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */
uint8_t MACAddr[6] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; /* 設定自動識別速度(10M/100M) */
heth.Init.Speed = ETH_SPEED_100M;
heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX; /* 設定全雙工 */
heth.Init.PhyAddress = DP83848_PHY_ADDRESS; /* 設定物理地址 */
MACAddr[0] = 0x00;
MACAddr[1] = 0x80;
MACAddr[2] = 0xE1;
MACAddr[3] = 0x00;
MACAddr[4] = 0x00;
MACAddr[5] = 0x00; /* 設定MAC地址 */
heth.Init.MACAddr = &MACAddr[0];
heth.Init.RxMode = ETH_RXPOLLING_MODE; /* 設定模式為輪詢 */
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; /* 設定以太網硬體校驗 */
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; /* 介面為RMII */
/* USER CODE BEGIN MACADDRESS */
/* USER CODE END MACADDRESS */
hal_eth_init_status = HAL_ETH_Init(&heth);
if (hal_eth_init_status == HAL_OK)
{
/* Set netif link flag */
netif->flags |= NETIF_FLAG_LINK_UP;
}
/* Initialize Tx Descriptors list: Chain Mode */
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB); /* 設定ETH TX DMA鏈表 */
/* Initialize Rx Descriptors list: Chain Mode */
HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB); /* 設定ETH RX DMA鏈表 */
#if LWIP_ARP || LWIP_ETHERNET
/* set MAC hardware address length */
netif->hwaddr_len = ETH_HWADDR_LEN;
/* set MAC hardware address */
netif->hwaddr[0] = heth.Init.MACAddr[0];
netif->hwaddr[1] = heth.Init.MACAddr[1];
netif->hwaddr[2] = heth.Init.MACAddr[2];
netif->hwaddr[3] = heth.Init.MACAddr[3];
netif->hwaddr[4] = heth.Init.MACAddr[4];
netif->hwaddr[5] = heth.Init.MACAddr[5];
/* maximum transfer unit */
netif->mtu = 1500;
/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
/* Enable MAC and DMA transmission and reception */
HAL_ETH_Start(&heth); /* 開啟ETH網 */
/* USER CODE BEGIN PHY_PRE_CONFIG */
/* USER CODE END PHY_PRE_CONFIG */
/**** Configure PHY to generate an interrupt when Eth Link state changes ****/
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, ®value);
regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);
/* Enable Interrupts */
HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, ®value);
regvalue |= PHY_MISR_LINK_INT_EN;
/* Enable Interrupt on change of link status */
HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue); /* 以上為配置eth暫存器,至于暫存器是干嘛的,可以看下上面DP83848小節 */
/* USER CODE BEGIN PHY_POST_CONFIG */
/* USER CODE END PHY_POST_CONFIG */
#endif /* LWIP_ARP || LWIP_ETHERNET */
/* USER CODE BEGIN LOW_LEVEL_INIT */
/* USER CODE END LOW_LEVEL_INIT */
}
low_level_output是 lwip 往網卡驅動發送函式,代碼如下:
此函式就是把lwip的pbuf資料copy到eth的tx dma中,然后發送
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer = (uint8_t *)(heth.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = heth.TxDesc;
bufferoffset = 0;
/* copy frame from pbufs to driver buffers */
for(q = p; q != NULL; q = q->next)
{
/* Is this buffer available? If not, goto error */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
/* Get bytes in current lwIP buffer */
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of data to copy is bigger than Tx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE )
{
/* Copy data to Tx buffer*/
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset) );
/* Point to next descriptor */
DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
/* Check if the buffer is available */
if((DmaTxDesc->Status & ETH_DMATXDESC_OWN) != (uint32_t)RESET)
{
errval = ERR_USE;
goto error;
}
buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset);
framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy the remaining bytes */
memcpy( (uint8_t*)((uint8_t*)buffer + bufferoffset), (uint8_t*)((uint8_t*)q->payload + payloadoffset), byteslefttocopy );
bufferoffset = bufferoffset + byteslefttocopy;
framelength = framelength + byteslefttocopy;
}
/* Prepare transmit descriptors to give to DMA */
HAL_ETH_TransmitFrame(&heth, framelength);
errval = ERR_OK;
error:
/* When Transmit Underflow flag is set, clear it and issue a Transmit Poll Demand to resume transmission */
if ((heth.Instance->DMASR & ETH_DMASR_TUS) != (uint32_t)RESET)
{
/* Clear TUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_TUS;
/* Resume DMA transmission*/
heth.Instance->DMATPDR = 0;
}
return errval;
}
low_level_input是lwip接受input資料,代碼如下:
此函式就是把eth的rx dma中資料copy到pbuf中,然后交給lwip處理
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p = NULL;
struct pbuf *q = NULL;
uint16_t len = 0;
uint8_t *buffer;
__IO ETH_DMADescTypeDef *dmarxdesc;
uint32_t bufferoffset = 0;
uint32_t payloadoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t i=0;
/* get received frame */
if (HAL_ETH_GetReceivedFrame(&heth) != HAL_OK)
return NULL;
/* Obtain the size of the packet and put it into the "len" variable. */
len = heth.RxFrameInfos.length;
buffer = (uint8_t *)heth.RxFrameInfos.buffer;
if (len > 0)
{
/* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
}
if (p != NULL)
{
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
bufferoffset = 0;
for(q = p; q != NULL; q = q->next)
{
byteslefttocopy = q->len;
payloadoffset = 0;
/* Check if the length of bytes to copy in current pbuf is bigger than Rx buffer size*/
while( (byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE )
{
/* Copy data to pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset));
/* Point to next descriptor */
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
buffer = (uint8_t *)(dmarxdesc->Buffer1Addr);
byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset);
payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset);
bufferoffset = 0;
}
/* Copy remaining data in pbuf */
memcpy( (uint8_t*)((uint8_t*)q->payload + payloadoffset), (uint8_t*)((uint8_t*)buffer + bufferoffset), byteslefttocopy);
bufferoffset = bufferoffset + byteslefttocopy;
}
}
/* Release descriptors to DMA */
/* Point to first descriptor */
dmarxdesc = heth.RxFrameInfos.FSRxDesc;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i< heth.RxFrameInfos.SegCount; i++)
{
dmarxdesc->Status |= ETH_DMARXDESC_OWN;
dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
heth.RxFrameInfos.SegCount =0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((heth.Instance->DMASR & ETH_DMASR_RBUS) != (uint32_t)RESET)
{
/* Clear RBUS ETHERNET DMA flag */
heth.Instance->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
heth.Instance->DMARPDR = 0;
}
return p;
}
3.代碼簡介(以CubeMx生成代碼做說明)
我們來直接說下代碼架構跟流程,具體細項不做說明
大概不的部分分為幾個:
1)MX_LWIP_Init,包含lwip的初始化,網卡添加,設定默認網卡,啟動網卡等
2)在main的while回圈中判斷是否有資料進來,如果有資料進來,那么通過ETH讀出資料,叫給lwip處理
六. 在STM32F407使用freertos移植lwip
在確定每個配置之前,建議別修改其他引數,因為我修改了head size不能正常PING通,這個我會在下面總結問題說明
1.Cubemx配置程序
其中RCC/SYS/UART/ETH/LWIP勾選跟noos完全一模一樣,所以在這里我們就不做介紹了,我們主要介紹下勾選FreeRTOS以及上述勾選的差異點
1.1 勾選FreeRTOS
主要牽扯到兩點,我用的CMSIS_V1,另外開啟了浮點運算,防止有現成用到浮點后宕機情況
1.2 勾選FreeRTOS
SYS/ETH/LWIP的差異
SYS cubemx強烈建議別用systick,所以我們選擇TIM1
ETH mode由輪詢改為中斷(只可選擇中斷)
LWIP我們改為有RTOS
2.自己移植程序
LwIP 不僅能在裸機上運行,也能在作業系統環境下運行,而且在作業系統環境下,用戶能使用NETCONN API 與 Socket API 編程,相比 RAW API 編程會更加簡便,作業系統環境下,這意味著多執行緒環境,一般來說 LwIP 作為一個獨立的處理執行緒運行,用戶程式也獨立為一個/多個執行緒,這樣子在作業系統中就相互獨立開,并且借助作業系統的 IPC 通信機制,更好地實作功能的需求,
LwIP 在設計之初,設計者無法預測 LwIP 運行的環境是怎么樣的,而且世界上作業系統那么多,根本沒法統一,而如果 LwIP 要運行在作業系統環境中,那么就必須產生依賴,即 LwIP 需要依賴作業系統自身的通信機制,如信號量、互斥量、訊息佇列(郵箱)等,所以 LwIP 設計者在設計的時候就提供一套與作業系統相關的介面,由用戶根據作業系統的不同進行移植,這樣子就能降低耦合度,讓 LwIP 內核不受其運行的環境影響,因為往往用戶并不能完全了解內核的運作,所以只需要用戶在移植的時候對 LwIP 提供的介面根據不同作業系統進行完善即可,
2.1 移植FreeRTOS到工程
這個牽扯到RTOS的移植,不是本文章范疇,可以自行百度,或者網路上現成的工程一堆,可以自己百度使用
2.2 配置lwip的宏檔案lwipopts.h(可以直接把這個copy到你的rtos的lwip中)
#ifndef __LWIPOPTS__H__
#define __LWIPOPTS__H__
#include "main.h"
/*-----------------------------------------------------------------------------*/
/* Current version of LwIP supported by CubeMx: 2.1.2 -*/
/*-----------------------------------------------------------------------------*/
/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#ifdef __cplusplus
extern "C" {
#endif
/* STM32CubeMX Specific Parameters (not defined in opt.h) ---------------------*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- WITH_RTOS enabled (Since FREERTOS is set) -----*/
#define WITH_RTOS 1 /* 帶RTOS */
/*----- CHECKSUM_BY_HARDWARE enabled -----*/
#define CHECKSUM_BY_HARDWARE 1
/*-----------------------------------------------------------------------------*/
/* LwIP Stack Parameters (modified compared to initialization value in opt.h) -*/
/* Parameters set in STM32CubeMX LwIP Configuration GUI -*/
/*----- Value in opt.h for MEM_ALIGNMENT: 1 -----*/
#define MEM_ALIGNMENT 4
/*----- Value in opt.h for LWIP_ETHERNET: LWIP_ARP || PPPOE_SUPPORT -*/
#define LWIP_ETHERNET 1
/*----- Value in opt.h for LWIP_DNS_SECURE: (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING | LWIP_DNS_SECURE_RAND_SRC_PORT) -*/
#define LWIP_DNS_SECURE 7
/*----- Value in opt.h for TCP_SND_QUEUELEN: (4*TCP_SND_BUF + (TCP_MSS - 1))/TCP_MSS -----*/
#define TCP_SND_QUEUELEN 9
/*----- Value in opt.h for TCP_SNDLOWAT: LWIP_MIN(LWIP_MAX(((TCP_SND_BUF)/2), (2 * TCP_MSS) + 1), (TCP_SND_BUF) - 1) -*/
#define TCP_SNDLOWAT 1071
/*----- Value in opt.h for TCP_SNDQUEUELOWAT: LWIP_MAX(TCP_SND_QUEUELEN)/2, 5) -*/
#define TCP_SNDQUEUELOWAT 5
/*----- Value in opt.h for TCP_WND_UPDATE_THRESHOLD: LWIP_MIN(TCP_WND/4, TCP_MSS*4) -----*/
#define TCP_WND_UPDATE_THRESHOLD 536
/*----- Value in opt.h for LWIP_NETIF_LINK_CALLBACK: 0 -----*/
#define LWIP_NETIF_LINK_CALLBACK 1
/*----- Value in opt.h for TCPIP_THREAD_STACKSIZE: 0 -----*/
#define TCPIP_THREAD_STACKSIZE 1024 /* TCP/IP執行緒的堆疊大小為1024 */
/*----- Value in opt.h for TCPIP_THREAD_PRIO: 1 -----*/
#define TCPIP_THREAD_PRIO osPriorityNormal /* TCP/IP執行緒的優先級為normal */
/*----- Value in opt.h for TCPIP_MBOX_SIZE: 0 -----*/
#define TCPIP_MBOX_SIZE 6 /* 訊息郵箱大小為6個 */
/*----- Value in opt.h for SLIPIF_THREAD_STACKSIZE: 0 -----*/
#define SLIPIF_THREAD_STACKSIZE 1024
/*----- Value in opt.h for SLIPIF_THREAD_PRIO: 1 -----*/
#define SLIPIF_THREAD_PRIO 3
/*----- Value in opt.h for DEFAULT_THREAD_STACKSIZE: 0 -----*/
#define DEFAULT_THREAD_STACKSIZE 1024
/*----- Value in opt.h for DEFAULT_THREAD_PRIO: 1 -----*/
#define DEFAULT_THREAD_PRIO 3
/*----- Value in opt.h for DEFAULT_UDP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_UDP_RECVMBOX_SIZE 6
/*----- Value in opt.h for DEFAULT_TCP_RECVMBOX_SIZE: 0 -----*/
#define DEFAULT_TCP_RECVMBOX_SIZE 6
/*----- Value in opt.h for DEFAULT_ACCEPTMBOX_SIZE: 0 -----*/
#define DEFAULT_ACCEPTMBOX_SIZE 6
/*----- Value in opt.h for RECV_BUFSIZE_DEFAULT: INT_MAX -----*/
#define RECV_BUFSIZE_DEFAULT 2000000000
/*----- Value in opt.h for LWIP_STATS: 1 -----*/
#define LWIP_STATS 0
/*----- Value in opt.h for CHECKSUM_GEN_IP: 1 -----*/
#define CHECKSUM_GEN_IP 0
/*----- Value in opt.h for CHECKSUM_GEN_UDP: 1 -----*/
#define CHECKSUM_GEN_UDP 0
/*----- Value in opt.h for CHECKSUM_GEN_TCP: 1 -----*/
#define CHECKSUM_GEN_TCP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP: 1 -----*/
#define CHECKSUM_GEN_ICMP 0
/*----- Value in opt.h for CHECKSUM_GEN_ICMP6: 1 -----*/
#define CHECKSUM_GEN_ICMP6 0
/*----- Value in opt.h for CHECKSUM_CHECK_IP: 1 -----*/
#define CHECKSUM_CHECK_IP 0
/*----- Value in opt.h for CHECKSUM_CHECK_UDP: 1 -----*/
#define CHECKSUM_CHECK_UDP 0
/*----- Value in opt.h for CHECKSUM_CHECK_TCP: 1 -----*/
#define CHECKSUM_CHECK_TCP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP: 1 -----*/
#define CHECKSUM_CHECK_ICMP 0
/*----- Value in opt.h for CHECKSUM_CHECK_ICMP6: 1 -----*/
#define CHECKSUM_CHECK_ICMP6 0
/*-----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
#ifdef __cplusplus
}
#endif
#endif /*__LWIPOPTS__H__ */
2.3 sys_arch.c/h 檔案的撰寫
作業系統環境下,LwIP 移植的核心就是撰寫與作業系統相關的介面檔案 sys_arch.c 和 sys_arch.h,這兩個檔案可以自己創建也可以從 contrib 包中獲取,路徑分別為“contrib-2.1.0\ports\freertos”與“contrib-2.1.0\ports\freertos\includearch”,用戶在移植的時候必須根據作業系統的功能為協議堆疊提供相應的介面,如郵箱(因為本次移植以 FreeRTOS 為例子, FreeRTOS 中沒有郵箱這種概念,但是可以使用訊息佇列替代,為了迎合 LwIP 中的命名,下文統一采用郵箱表示)、信號量、互斥量等,這些 IPC 通信機制是保證內核與上層 API 介面通信的基本保障,也是內核實作管理的繼承,同時在 sys.h 檔案中宣告了用戶需要實作的所有函式框架,這些函式具體見表格
看到那么多函式,是不是頭都大了,其實這些函式的實作都是很簡單的,首先講解一下郵箱函式的實作,在 LwIP 中,用戶代碼與協議堆疊內部之間是通過郵箱進行資料的互動的,郵箱本質上就是一個指向資料的指標,API 將指標傳遞給內核,內核通過這個指標訪問資料,然后去處理,反之內核將資料傳遞給用戶代碼也是通過郵箱將一個指標進行傳遞,在作業系統環境下,LwIP 會作為一個執行緒運行,執行緒的名字叫tcpip_thread,在初始化 LwIP 的時候,內核就會自動創建這個執行緒,并且在執行緒運行的時候阻塞在郵箱上,等待資料進行處理,這個郵箱資料的來源可能在底層網卡接收到的資料或者上層應用程式的資料,總之,tcpip_thread執行緒在獲取到郵箱中的資料時候,就會退出阻塞態,去處理資料,在處理完畢資料后又進入阻塞態中等待資料的到來,如此反復,信號量與互斥量的實作為內核提供同步與互斥的機制,比如當用戶想要發送一個資料的時候,就會呼叫上層 API 介面,API 介面就會去先發送一個資料給內核去處理,然后嘗試獲取一個信號量,因為此時是沒有信號量的,所以就會阻塞用戶執行緒;內核在知道用戶想要發送資料后,就會呼叫對應的網卡去發送資料,當資料發送完成后就釋放一個信號量告知用戶執行緒發送完成,這樣子用戶執行緒就得以繼續執行,
所以這些函式的介面都必須由用戶實作,下面我們先來看下sys_arch.h
#ifndef __SYS_ARCH_H__
#define __SYS_ARCH_H__
#include "lwip/opt.h"
#if (NO_SYS != 0)
#error "NO_SYS need to be set to 0 to use threaded API"
#endif
#include "cmsis_os.h"
#ifdef __cplusplus
extern "C" {
#endif
#if (osCMSIS < 0x20000U)
#define SYS_MBOX_NULL (osMessageQId)0
#define SYS_SEM_NULL (osSemaphoreId)0
typedef osSemaphoreId sys_sem_t;
typedef osSemaphoreId sys_mutex_t;
typedef osMessageQId sys_mbox_t;
typedef osThreadId sys_thread_t;
#else
#define SYS_MBOX_NULL (osMessageQueueId_t)0
#define SYS_SEM_NULL (osSemaphoreId_t)0
typedef osSemaphoreId_t sys_sem_t;
typedef osSemaphoreId_t sys_mutex_t;
typedef osMessageQueueId_t sys_mbox_t;
typedef osThreadId_t sys_thread_t;
#endif
#ifdef __cplusplus
}
#endif
#endif /* __SYS_ARCH_H__ */
接下來我們來下sys_arch.c的實作
/* lwIP includes. */
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#if !NO_SYS
#include "cmsis_os.h"
#if defined(LWIP_PROVIDE_ERRNO)
int errno;
#endif
/*-----------------------------------------------------------------------------------*/
// Creates an empty mailbox.
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
#if (osCMSIS < 0x20000U)
osMessageQDef(QUEUE, size, void *);
*mbox = osMessageCreate(osMessageQ(QUEUE), NULL);
#else
*mbox = osMessageQueueNew(size, sizeof(void *), NULL);
#endif
#if SYS_STATS
++lwip_stats.sys.mbox.used;
if(lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used)
{
lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
}
#endif /* SYS_STATS */
if(*mbox == NULL)
return ERR_MEM;
return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/*
Deallocates a mailbox. If there are messages still present in the
mailbox when the mailbox is deallocated, it is an indication of a
programming error in lwIP and the developer should be notified.
*/
void sys_mbox_free(sys_mbox_t *mbox)
{
#if (osCMSIS < 0x20000U)
if(osMessageWaiting(*mbox))
#else
if(osMessageQueueGetCount(*mbox))
#endif
{
/* Line for breakpoint. Should never break here! */
portNOP();
#if SYS_STATS
lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
}
#if (osCMSIS < 0x20000U)
osMessageDelete(*mbox);
#else
osMessageQueueDelete(*mbox);
#endif
#if SYS_STATS
--lwip_stats.sys.mbox.used;
#endif /* SYS_STATS */
}
/*-----------------------------------------------------------------------------------*/
// Posts the "msg" to the mailbox.
void sys_mbox_post(sys_mbox_t *mbox, void *data)
{
#if (osCMSIS < 0x20000U)
while(osMessagePut(*mbox, (uint32_t)data, osWaitForever) != osOK);
#else
while(osMessageQueuePut(*mbox, &data, 0, osWaitForever) != osOK);
#endif
}
/*-----------------------------------------------------------------------------------*/
// Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
err_t result;
#if (osCMSIS < 0x20000U)
if(osMessagePut(*mbox, (uint32_t)msg, 0) == osOK)
#else
if(osMessageQueuePut(*mbox, &msg, 0, 0) == osOK)
#endif
{
result = ERR_OK;
}
else
{
// could not post, queue must be full
result = ERR_MEM;
#if SYS_STATS
lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
}
return result;
}
/*-----------------------------------------------------------------------------------*/
// Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost_fromisr(sys_mbox_t *mbox, void *msg)
{
return sys_mbox_trypost(mbox, msg);
}
/*-----------------------------------------------------------------------------------*/
/*
Blocks the thread until a message arrives in the mailbox, but does
not block the thread longer than "timeout" milliseconds (similar to
the sys_arch_sem_wait() function). The "msg" argument is a result
parameter that is set by the function (i.e., by doing "*msg =
ptr"). The "msg" parameter maybe NULL to indicate that the message
should be dropped.
The return values are the same as for the sys_arch_sem_wait() function:
Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
timeout.
Note that a function with a similar name, sys_mbox_fetch(), is
implemented by lwIP.
*/
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
#if (osCMSIS < 0x20000U)
osEvent event;
uint32_t starttime = osKernelSysTick();
#else
osStatus_t status;
uint32_t starttime = osKernelGetTickCount();
#endif
if(timeout != 0)
{
#if (osCMSIS < 0x20000U)
event = osMessageGet (*mbox, timeout);
if(event.status == osEventMessage)
{
*msg = (void *)event.value.v;
return (osKernelSysTick() - starttime);
}
#else
status = osMessageQueueGet(*mbox, msg, 0, timeout);
if (status == osOK)
{
return (osKernelGetTickCount() - starttime);
}
#endif
else
{
return SYS_ARCH_TIMEOUT;
}
}
else
{
#if (osCMSIS < 0x20000U)
event = osMessageGet (*mbox, osWaitForever);
*msg = (void *)event.value.v;
return (osKernelSysTick() - starttime);
#else
osMessageQueueGet(*mbox, msg, 0, osWaitForever );
return (osKernelGetTickCount() - starttime);
#endif
}
}
/*-----------------------------------------------------------------------------------*/
/*
Similar to sys_arch_mbox_fetch, but if message is not ready immediately, we'll
return with SYS_MBOX_EMPTY. On success, 0 is returned.
*/
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
#if (osCMSIS < 0x20000U)
osEvent event;
event = osMessageGet (*mbox, 0);
if(event.status == osEventMessage)
{
*msg = (void *)event.value.v;
#else
if (osMessageQueueGet(*mbox, msg, 0, 0) == osOK)
{
#endif
return ERR_OK;
}
else
{
return SYS_MBOX_EMPTY;
}
}
/*----------------------------------------------------------------------------------*/
int sys_mbox_valid(sys_mbox_t *mbox)
{
if (*mbox == SYS_MBOX_NULL)
return 0;
else
return 1;
}
/*-----------------------------------------------------------------------------------*/
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
*mbox = SYS_MBOX_NULL;
}
/*-----------------------------------------------------------------------------------*/
// Creates a new semaphore. The "count" argument specifies
// the initial state of the semaphore.
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
#if (osCMSIS < 0x20000U)
osSemaphoreDef(SEM);
*sem = osSemaphoreCreate (osSemaphore(SEM), 1);
#else
*sem = osSemaphoreNew(UINT16_MAX, count, NULL);
#endif
if(*sem == NULL)
{
#if SYS_STATS
++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
return ERR_MEM;
}
if(count == 0) // Means it can't be taken
{
#if (osCMSIS < 0x20000U)
osSemaphoreWait(*sem, 0);
#else
osSemaphoreAcquire(*sem, 0);
#endif
}
#if SYS_STATS
++lwip_stats.sys.sem.used;
if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
}
#endif /* SYS_STATS */
return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/*
Blocks the thread while waiting for the semaphore to be
signaled. If the "timeout" argument is non-zero, the thread should
only be blocked for the specified time (measured in
milliseconds).
If the timeout argument is non-zero, the return value is the number of
milliseconds spent waiting for the semaphore to be signaled. If the
semaphore wasn't signaled within the specified time, the return value is
SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
(i.e., it was already signaled), the function may return zero.
Notice that lwIP implements a function with a similar name,
sys_sem_wait(), that uses the sys_arch_sem_wait() function.
*/
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
#if (osCMSIS < 0x20000U)
uint32_t starttime = osKernelSysTick();
#else
uint32_t starttime = osKernelGetTickCount();
#endif
if(timeout != 0)
{
#if (osCMSIS < 0x20000U)
if(osSemaphoreWait (*sem, timeout) == osOK)
{
return (osKernelSysTick() - starttime);
#else
if(osSemaphoreAcquire(*sem, timeout) == osOK)
{
return (osKernelGetTickCount() - starttime);
#endif
}
else
{
return SYS_ARCH_TIMEOUT;
}
}
else
{
#if (osCMSIS < 0x20000U)
while(osSemaphoreWait (*sem, osWaitForever) != osOK);
return (osKernelSysTick() - starttime);
#else
while(osSemaphoreAcquire(*sem, osWaitForever) != osOK);
return (osKernelGetTickCount() - starttime);
#endif
}
}
/*-----------------------------------------------------------------------------------*/
// Signals a semaphore
void sys_sem_signal(sys_sem_t *sem)
{
osSemaphoreRelease(*sem);
}
/*-----------------------------------------------------------------------------------*/
// Deallocates a semaphore
void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
--lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
osSemaphoreDelete(*sem);
}
/*-----------------------------------------------------------------------------------*/
int sys_sem_valid(sys_sem_t *sem)
{
if (*sem == SYS_SEM_NULL)
return 0;
else
return 1;
}
/*-----------------------------------------------------------------------------------*/
void sys_sem_set_invalid(sys_sem_t *sem)
{
*sem = SYS_SEM_NULL;
}
/*-----------------------------------------------------------------------------------*/
#if (osCMSIS < 0x20000U)
osMutexId lwip_sys_mutex;
osMutexDef(lwip_sys_mutex);
#else
osMutexId_t lwip_sys_mutex;
#endif
// Initialize sys arch
void sys_init(void)
{
#if (osCMSIS < 0x20000U)
lwip_sys_mutex = osMutexCreate(osMutex(lwip_sys_mutex));
#else
lwip_sys_mutex = osMutexNew(NULL);
#endif
}
/*-----------------------------------------------------------------------------------*/
/* Mutexes*/
/*-----------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------*/
#if LWIP_COMPAT_MUTEX == 0
/* Create a new mutex*/
err_t sys_mutex_new(sys_mutex_t *mutex) {
#if (osCMSIS < 0x20000U)
osMutexDef(MUTEX);
*mutex = osMutexCreate(osMutex(MUTEX));
#else
*mutex = osMutexNew(NULL);
#endif
if(*mutex == NULL)
{
#if SYS_STATS
++lwip_stats.sys.mutex.err;
#endif /* SYS_STATS */
return ERR_MEM;
}
#if SYS_STATS
++lwip_stats.sys.mutex.used;
if (lwip_stats.sys.mutex.max < lwip_stats.sys.mutex.used) {
lwip_stats.sys.mutex.max = lwip_stats.sys.mutex.used;
}
#endif /* SYS_STATS */
return ERR_OK;
}
/*-----------------------------------------------------------------------------------*/
/* Deallocate a mutex*/
void sys_mutex_free(sys_mutex_t *mutex)
{
#if SYS_STATS
--lwip_stats.sys.mutex.used;
#endif /* SYS_STATS */
osMutexDelete(*mutex);
}
/*-----------------------------------------------------------------------------------*/
/* Lock a mutex*/
void sys_mutex_lock(sys_mutex_t *mutex)
{
#if (osCMSIS < 0x20000U)
osMutexWait(*mutex, osWaitForever);
#else
osMutexAcquire(*mutex, osWaitForever);
#endif
}
/*-----------------------------------------------------------------------------------*/
/* Unlock a mutex*/
void sys_mutex_unlock(sys_mutex_t *mutex)
{
osMutexRelease(*mutex);
}
#endif /*LWIP_COMPAT_MUTEX*/
/*-----------------------------------------------------------------------------------*/
// TODO
/*-----------------------------------------------------------------------------------*/
/*
Starts a new thread with priority "prio" that will begin its execution in the
function "thread()". The "arg" argument will be passed as an argument to the
thread() function. The id of the new thread is returned. Both the id and
the priority are system dependent.
*/
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread , void *arg, int stacksize, int prio)
{
#if (osCMSIS < 0x20000U)
const osThreadDef_t os_thread_def = { (char *)name, (os_pthread)thread, (osPriority)prio, 0, stacksize};
return osThreadCreate(&os_thread_def, arg);
#else
const osThreadAttr_t attributes = {
.name = name,
.stack_size = stacksize,
.priority = (osPriority_t)prio,
};
return osThreadNew(thread, arg, &attributes);
#endif
}
/*
This optional function does a "fast" critical region protection and returns
the previous protection level. This function is only called during very short
critical regions. An embedded system which supports ISR-based drivers might
want to implement this function by disabling interrupts. Task-based systems
might want to implement this by using a mutex or disabling tasking. This
function should support recursive calls from the same task or interrupt. In
other words, sys_arch_protect() could be called while already protected. In
that case the return value indicates that it is already protected.
sys_arch_protect() is only required if your port is supporting an operating
system.
Note: This function is based on FreeRTOS API, because no equivalent CMSIS-RTOS
API is available
*/
sys_prot_t sys_arch_protect(void)
{
#if (osCMSIS < 0x20000U)
osMutexWait(lwip_sys_mutex, osWaitForever);
#else
osMutexAcquire(lwip_sys_mutex, osWaitForever);
#endif
return (sys_prot_t)1;
}
/*
This optional function does a "fast" set of critical region protection to the
value specified by pval. See the documentation for sys_arch_protect() for
more information. This function is only required if your port is supporting
an operating system.
Note: This function is based on FreeRTOS API, because no equivalent CMSIS-RTOS
API is available
*/
void sys_arch_unprotect(sys_prot_t pval)
{
( void ) pval;
osMutexRelease(lwip_sys_mutex);
}
#endif /* !NO_SYS */
2.4 網卡底層的撰寫
在無操作性移植的時候,我們的網卡收發資料就是單純的收發資料, ethernetif_input() 函式就是處理接收網卡資料的,但是使用了作業系統的話,我們一般將接收資料函式獨立成為一個網卡接收執行緒,這樣子在收到資料的時候才去處理資料,然后遞交給內核執行緒,所以我們只需要稍作修改即可,將函式轉換成執行緒就行了,并且在初始化網卡的時候創建網卡接收執行緒,當然,我們也能將發送函式獨立成一個執行緒,我們暫時沒有必要去處理它,此處只創建一個網卡接收執行緒,具體見
void ethernetif_input(void const * argument)
{
struct pbuf *p;
struct netif *netif = (struct netif *) argument;
for( ;; )
{
if (osSemaphoreWait(s_xSemaphore, TIME_WAITING_FOR_INPUT) == osOK)
{
do
{
LOCK_TCPIP_CORE();
p = low_level_input( netif );
if (p != NULL)
{
if (netif->input( p, netif) != ERR_OK )
{
pbuf_free(p);
}
}
UNLOCK_TCPIP_CORE();
} while(p!=NULL);
}
}
}
在網卡接收執行緒中需要留意一下以下內容:網卡接收執行緒是需要通過信號量機制去接收資料的,一般來說我們都是使用中斷的方式去獲取網路資料包,當產生中斷的時候,我們一般不會在中斷中處理資料,而是告訴對應的執行緒去處理,也就是我們的網卡接收執行緒去處理資料,那么就會通過信號量進行同步,當網卡接收到了資料就會產生中斷釋放一個信號量,然后執行緒從阻塞中恢復,去獲取網卡的資料并且向上層遞交,當然我們還需要在中斷中對網卡底層進行撰寫!
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
osSemaphoreRelease(s_xSemaphore);
}
這個函式是有ETH_IRQHandler中的HAL_ETH_IRQHandler呼叫!
網卡初始化函式 low_level_init()
static void low_level_init(struct netif *netif)
{
uint32_t regvalue = 0;
HAL_StatusTypeDef hal_eth_init_status;
/* Init ETH */
uint8_t MACAddr[6] ;
heth.Instance = ETH;
heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
heth.Init.Speed = ETH_SPEED_100M;
heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
heth.Init.PhyAddress = DP83848_PHY_ADDRESS;
MACAddr[0] = 0x00;
MACAddr[1] = 0x80;
MACAddr[2] = 0xE1;
MACAddr[3] = 0x00;
MACAddr[4] = 0x00;
MACAddr[5] = 0x00;
heth.Init.MACAddr = &MACAddr[0];
heth.Init.RxMode = ETH_RXINTERRUPT_MODE;
heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
/* USER CODE BEGIN MACADDRESS */
/* USER CODE END MACADDRESS */
hal_eth_init_status = HAL_ETH_Init(&heth);
if (hal_eth_init_status == HAL_OK)
{
/* Set netif link flag */
netif->flags |= NETIF_FLAG_LINK_UP;
}
/* Initialize Tx Descriptors list: Chain Mode */
HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
/* Initialize Rx Descriptors list: Chain Mode */
HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
#if LWIP_ARP || LWIP_ETHERNET
/* set MAC hardware address length */
netif->hwaddr_len = ETH_HWADDR_LEN;
/* set MAC hardware address */
netif->hwaddr[0] = heth.Init.MACAddr[0];
netif->hwaddr[1] = heth.Init.MACAddr[1];
netif->hwaddr[2] = heth.Init.MACAddr[2];
netif->hwaddr[3] = heth.Init.MACAddr[3];
netif->hwaddr[4] = heth.Init.MACAddr[4];
netif->hwaddr[5] = heth.Init.MACAddr[5];
/* maximum transfer unit */
netif->mtu = 1500;
/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */
/* create a binary semaphore used for informing ethernetif of frame reception */
osSemaphoreDef(SEM);
s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM), 1);
/* create the task that handles the ETH_MAC */
/* USER CODE BEGIN OS_THREAD_DEF_CREATE_CMSIS_RTOS_V1 */
osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);
osThreadCreate (osThread(EthIf), netif);
/* USER CODE END OS_THREAD_DEF_CREATE_CMSIS_RTOS_V1 */
/* Enable MAC and DMA transmission and reception */
HAL_ETH_Start(&heth);
/* USER CODE BEGIN PHY_PRE_CONFIG */
/* USER CODE END PHY_PRE_CONFIG */
/**** Configure PHY to generate an interrupt when Eth Link state changes ****/
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, ®value);
regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);
/* Enable Interrupts */
HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );
/* Read Register Configuration */
HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, ®value);
regvalue |= PHY_MISR_LINK_INT_EN;
/* Enable Interrupt on change of link status */
HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);
/* USER CODE BEGIN PHY_POST_CONFIG */
/* USER CODE END PHY_POST_CONFIG */
#endif /* LWIP_ARP || LWIP_ETHERNET */
/* USER CODE BEGIN LOW_LEVEL_INIT */
/* USER CODE END LOW_LEVEL_INIT */
}
其中low_level_output/low_level_input跟noos函式撰寫一致即可
3.代碼簡介(以CubeMx生成代碼做說明)
我大概分析了一下init以及收資料的代碼流程(紅線)
4.使用lwip的socket通信
4.1 socket概念
Socket 英文原意是“孔”或者“插座”的意思,在網路編程中,通常將其稱之為“套接字”,當前網路中的主流程式設計都是使用 Socket 進行編程的,因為它簡單易用,更是一個標準,能在不同平臺很方便移植,本章講解的是 LwIP 中的 Socket 編程介面,因為 LwIP 作者為了能讓更多開發者直接上手 LwIP 的編程,專門設計了 LwIP 的第三種編程介面——Socket API,它兼容 BSDSocket,
Socket 雖然是能在多平臺移植,但是 LwIP 中的 Socket 并不完善,因為 LwIP 設計之初就是為了在嵌入式平臺中使用,它只實作了完整 Socket 的部分功能,不過,在嵌入式平臺中,這些功能早已足夠,
在 Socket 中,它使用一個套接字來記錄網路的一個連接,套接字是一個整數,就像我們操作檔案一樣,利用一個檔案描述符,可以對它打開、讀、寫、關閉等操作,類似的,在網路中,我們也可以對 Socket 套接字進行這樣子的操作,比如開啟一個網路的連接、讀取連接主機發送來的資料、向連接的主機發送資料、終止連接等操作,
4.2 socket API介紹
4.2.1 socket() API介紹
這個函式的功能是向內核申請一個套接字,lwip中的socket最終實作是lwip_socket,但是為了兼容BSD socket,所以重新define了一下,我們來看下
/** @ingroup socket */
#define socket(domain,type,protocol) lwip_socket(domain,type,protocol)
引數:
domain: 表示該套接字使用的協議簇,對于 TCP/IP 協議來說,該值始終為 AF_INET,
type: 指定了套接字使用的服務型別,可能的型別有 3 種:
1)SOCK_STREAM:提供可靠的(即能保證資料正確傳送到對方)面向連接的 Socket 服務,多用于資料(如檔案)傳輸,如 TCP 協議,
2)SOCK_DGRAM:是提供無保障的面向訊息的 Socket 服務,主要用于在網路上發廣播資訊,如 UDP 協議,提供無連接不可靠的資料報交付服務,
3)SOCK_RAW:表示原始套接字,它允許應用程式訪問網路層的原始資料包,這個套接字用得比較少,暫時不用理會它,
protocol :指定了套接字使用的協議,在 IPv4 中,只有 TCP 協議提供 SOCK_STREAM 這種可靠的服務,只有 UDP 協議提供 SOCK_DGRAM 服務,對于這兩種協議,protocol 的值均為 0,當申請套接字成功的時候,該函式回傳一個 int 型別的值,也是 Socket 描述符,用戶通過這個值可以索引到一個 Socket 連接結構——lwip_sock,當申請套接字失敗時,該函式回傳-1,
4.2.2 bind() API介紹
用于服務器端系結套接字與網卡資訊,lwip中的bind最終實作是lwip_bind,但是為了兼容BSD bind,所以重新define了一下,我們來看下
/** @ingroup socket */
#define bind(s,name,namelen) lwip_bind(s,name,namelen)
引數:
s :是表示要系結的 Socket 套接字,注意了,這個套機字必須是從 socket() 函式中回傳的索引,否則將無法完成系結操作,
name: 是一個指向 sockaddr 結構體的指標,其中包含了網卡的 IP 地址、埠號等重要的資訊,LwIP 為了更好描述這些資訊,使用了 sockaddr 結構體來定義了必要的資訊的欄位,它常被用于Socket API 的很多函式中,我們在使用 bind() 的時候,只需要直接填寫相關欄位即可
namelen: 指定了 name 結構體的長度,
下面我們來說下name的struct,如下:
struct sockaddr {
u8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
為了便于理解,lwip重新定義了一下這個結構體
/* members are in network byte order */
struct sockaddr_in {
u8_t sin_len;
sa_family_t sin_family; /* 協議簇 */
in_port_t sin_port; /* 埠號 */
struct in_addr sin_addr; /* IP地址 */
#define SIN_ZERO_LEN 8
char sin_zero[SIN_ZERO_LEN];
};
4.2.3 connect() API介紹
它用于客戶端中,將 Socket 與遠端 IP 地址、埠號進行系結,在 TCP 客戶端連接中,呼叫這個函式將發生握手程序(會發送一個 TCP 連接請求),并最終建立新的 TCP 連接,而對于 UDP協議來說,呼叫這個函式只是在 UDP 控制塊中記錄遠端 IP 地址與埠號,而不發送任何資料,引數資訊與 bind() 函式是一樣的
/** @ingroup socket */
#define connect(s,name,namelen) lwip_connect(s,name,namelen)
4.2.4 listen() API介紹
只能在 TCP 服務器中使用,讓服務器進入監聽狀態,等待遠端的連接請求, LwIP 中可以接收多個客戶端的連接,因此引數 backlog 指定了請求佇列的大小
/** @ingroup socket */
#define listen(s,backlog) lwip_listen(s,backlog)
4.2.5 accept() API介紹
用于 TCP 服務器中,等待著遠端主機的連接請求,并且建立一個新的 TCP 連接,在呼叫這個函式之前需要通過呼叫 listen() 函式讓服務器進入監聽狀態,accept() 函式的呼叫會阻塞應用執行緒直至與遠程主機建立 TCP 連接,引數 addr 是一個回傳結果引數,它的值由 accept() 函式設定,其實就是遠程主機的地址與埠號等資訊,當新的連接已經建立后,遠端主機的資訊將保存在連接句柄中,它能夠唯一的標識某個連接物件,同時函式回傳一個 int 型別的套接字描述符,根據它能索引到連接結構,如果連接失敗則回傳-1
/** @ingroup socket */
#define accept(s,addr,addrlen) lwip_accept(s,addr,addrlen)
4.2.6 read()、recv()、recvfrom() API介紹
read() 與 recv() 函式的核心是呼叫 recvfrom() 函式, recv() 與 read() 函式用于從 Socket 中接收資料,它們可以是 TCP 協議和 UDP 協議!
/** @ingroup socket */
#define recv(s,mem,len,flags) lwip_recv(s,mem,len,flags)
/** @ingroup socket */
#define recvmsg(s,message,flags) lwip_recvmsg(s,message,flags)
/** @ingroup socket */
#define recvfrom(s,mem,len,flags,from,fromlen) lwip_recvfrom(s,mem,len,flags,from,fromlen)
men 引數記錄了接收資料的快取起始地址,len 用于指定接收資料的最大長度,如果函式能正確接收到資料,將會回傳一個接收到資料的長度,否則將回傳-1,若回傳值為 0,表示連接已經終止,應用程式可以根據回傳的值進行不一樣的操作,recv() 函式包含一個 flags 引數,我們暫時可以直接忽略它,設定為 0 即可,注意,如果接收的資料大于用戶提供的快取區,那么多余的資料會被直接丟棄,
4.2.7 sendto() API介紹
這個函式主要是用于 UDP 協議傳輸資料中,它向另一端的 UDP 主機發送一個 UDP 報文,引數 data 指定了要發送資料的起始地址,而 size 則指定資料的長度,引數 flag 指定了發送時候的一些處理,比如外帶資料等,此時我們不需要理會它,一般設定為 0 即可,引數 to 是一個指向 sockaddr 結構體的指標,在這里需要我們自己提供遠端主機的 IP 地址與埠號,并且用 tolen 引數指定這些資訊的長度!
/** @ingroup socket */
#define sendto(s,dataptr,size,flags,to,tolen) lwip_sendto(s,dataptr,size,flags,to,tolen)
4.2.8 sendto() API介紹
send() 函式可以用于 UDP 協議和 TCP 連接發送資料,在呼叫 send() 函式之前,必須使用 connect()函式將遠端主機的 IP 地址、埠號與 Socket 連接結構進行系結,對于 UDP 協議,send() 函式將呼叫 lwip_sendto() 函式發送資料,而對于 TCP 協議,將呼叫 netconn_write_partly() 函式發送資料,相對于 sendto() 函式,引數基本是沒啥區別的,但無需我們設定遠端主機的資訊,更加方便操作,因此這個函式在實際中使用也是很多的
/** @ingroup socket */
#define send(s,dataptr,size,flags) lwip_send(s,dataptr,size,flags)
4.2.9 write() API介紹
這個函式一般用于處于穩定的 TCP 連接中傳輸資料,當然也能用于 UDP 協議中,它也是基于lwip_send 上實作的,但是無需我們設定 flag 引數
/** @ingroup socket */
#define write(s,dataptr,len) lwip_write(s,dataptr,len)
4.2.10 close() API介紹
close() 函式是用于關閉一個指定的套接字,在關閉套接字后,將無法使用對應的套接字描述符索引到連接結構,該函式的本質是對 netconn_delete() 函式的封裝(真正處理的函式是 netconn_prepare_delete()),如果連接是 TCP 協議,將產生一個請求終止連接的報文發送到對端主機中,如果是 UDP 協議,將直接釋放 UDP 控制塊的內容!
/** @ingroup socket */
#define close(s) lwip_close(s)
4.3 socket創建一個TCP server做一個回顯實驗
代碼如下:
#include "tcpecho.h"
#include "lwip/opt.h"
#if LWIP_SOCKET
#include <lwip/sockets.h>
#include "lwip/sys.h"
#include "lwip/api.h"
/*-----------------------------------------------------------------------------------*/
#define RECV_DATA (1024)
#define LOCAL_PORT 5001
static void
tcpecho_thread(void *arg)
{
int sock = -1,connected;
char *recv_data;
struct sockaddr_in server_addr,client_addr;
socklen_t sin_size;
int recv_data_len;
printf("本地埠號是%d\n\n",LOCAL_PORT);
recv_data = (char *)pvPortMalloc(RECV_DATA);
if (recv_data == NULL)
{
printf("No memory\n");
goto __exit;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
printf("Socket error\n");
goto __exit;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(LOCAL_PORT);
memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero));
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
printf("Unable to bind\n");
goto __exit;
}
if (listen(sock, 5) == -1)
{
printf("Listen error\n");
goto __exit;
}
while(1)
{
sin_size = sizeof(struct sockaddr_in);
connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size);
printf("new client connected from (%s, %d)\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
{
int flag = 1;
setsockopt(connected,
IPPROTO_TCP, /* set option at TCP level */
TCP_NODELAY, /* name of option */
(void *) &flag, /* the cast is historical cruft */
sizeof(int)); /* length of option value */
}
while(1)
{
recv_data_len = recv(connected, recv_data, RECV_DATA, 0);
if (recv_data_len <= 0)
break;
printf("recv %d len data\n",recv_data_len);
write(connected,recv_data,recv_data_len);
}
if (connected >= 0)
closesocket(connected);
connected = -1;
}
__exit:
if (sock >= 0) closesocket(sock);
if (recv_data) free(recv_data);
}
/*-----------------------------------------------------------------------------------*/
void
tcpecho_init(void)
{
sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, 512, 4);
}
/*-----------------------------------------------------------------------------------*/
效果如下:
這個實驗室我們STM32做TCP server,然后IP是靜態IP(192.168.1.250),開啟的TCP port是5001,
4.4 socket創建一個TCP client間隔發送資料
#include "tcp_client.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/api.h"
#include <lwip/sockets.h>
#define DEST_IP_ADDR0 192
#define DEST_IP_ADDR1 168
#define DEST_IP_ADDR2 1
#define DEST_IP_ADDR3 102
#define DEST_PORT 5001
static void client(void *thread_param)
{
int sock = -1;
struct sockaddr_in client_addr;
ip4_addr_t ipaddr;
uint8_t send_buf[]= "This is a TCP Client test...\n";
printf("目地IP地址:%d.%d.%d.%d \t 埠號:%d\n\n", \
DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3,DEST_PORT);
printf("請將電腦上位機設定為TCP Server.在User/arch/sys_arch.h檔案中將目標IP地址修改為您電腦上的IP地址\n\n");
printf("修改對應的宏定義:DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3,DEST_PORT\n\n");
IP4_ADDR(&ipaddr,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3);
while(1)
{
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
printf("Socket error\n");
vTaskDelay(10);
continue;
}
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(DEST_PORT);
client_addr.sin_addr.s_addr = ipaddr.addr;
memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero));
if (connect(sock,
(struct sockaddr *)&client_addr,
sizeof(struct sockaddr)) == -1)
{
printf("Connect failed!\n");
closesocket(sock);
vTaskDelay(10);
continue;
}
printf("Connect to server successful!\n");
while (1)
{
if(write(sock,send_buf,sizeof(send_buf)) < 0)
break;
vTaskDelay(1000);
}
closesocket(sock);
}
}
void
tcp_client_init(void)
{
sys_thread_new("client", client, NULL, 512, 4);
}
效果如下:
這個實驗室我們STM32做TCP client,然后連接pc的tcp server(PC IP地址為:192.168.1.102),PC的TCP port為5001
七. 問題總結:
問題:我隨意設定FreeRTOS為啥這里會影響ping功能?我把heap size設為71680之后就100% ping不同
希望有知道的可以評論下!!
參考書籍:
[野火EmbedFire]《[野火]LwIP應用開發實戰指南—基于野火STM32》.pdf
dp83848c.pdf
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/282173.html
標籤:其他
上一篇:ESP32基礎應用之http實驗
