心跳包就是在客戶端和服務器間定時通知對方自己狀態的一個自己定義的命令字,按照一定的時間間隔發送,類似於心跳,所以叫做心跳包。心跳包在GPRS通信和CDMA通信的應用方面使用非常廣泛。數據網關會定時清理沒有數據的路由,心跳包通常設定在30-40秒之間。所謂的心跳包就是客戶端定時發送簡單的信息給服務器端告訴它我還在而已。代碼就是每隔幾分鍾發送一個固定信息給服務端,服務端收到后回復一個固定信息如果服務端幾分鍾內沒有收到客戶端信息則視客戶端斷開。本次設計中,心跳包時間間隔為1秒。
一、心跳包觸發器
設計一個 1 秒定時器,每隔一秒就產生一個心跳包觸發脈沖,用於下一步心跳包的組建。
二、心跳包粗略框架
本次以太網的心跳包結構如下所示:
本次發送 64 個全為0的數據,當然這個數據是自定義的,因此心跳包總長度為118。此外目的/源 MAC 地址、目的/源 IP 地址、目的/源 port 等值可以用參數的方式先寫好。利用剛剛設計的心跳包觸發脈沖,我們就能組建這個心跳包了。表中黃色部分為后面需要校驗的值,可以先填0進去,后面再覆蓋掉這三處即可。代碼也沒什么說的,用參數和case語句即可。
點擊電腦 Win + R 鍵,輸入 cmd,再出入 ipconfig -all,按回車鍵,即可看到自己電腦的 MAC 地址等參數。
當然,這次發送的目的 MAC 填寫成全 FF,弄成廣播包也是可以的。
由於 FPGA 板卡是沒有 MAC 地址的,因此源 MAC 地址可以任意設置。
三、心跳包填充:IP校驗和UDP校驗
1、IP校驗方法
IP 校驗就是把 IP 首部 20byte 按 2byte(即 16bit)分開后相加,結果如果大於 16’hffff,就將超出 16’hffff 的部分與相加結果的低 16 位相加,直到最終結果小於 16’hffff 為止。最后把小於 16’hffff 的結果取反作為 ip_checksum。高字節在前,低字節在后,替換掉前面在心跳包中填充的 0。
(1) 校驗和字段清0
假設有一段以太網包前面沒有對 IP 校驗和字段清0,而是賦了別的值,例如 IP 首部為:45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d,b5 2e 字段即為 IP 校驗和字段,清0后數據就變成了:45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d。
(2) 求和
4500+0030+804c+4000+8006+0000+d343+117b+cb51+153d = 34ace,將超過 16’hffff 的部分(即3)與低 16 位(即4ace)上,結果為:3+4ace=4ad1,因為 4ad1 小於 ffff,故作為 Ip_checksum 的反碼。
(3) 取反
將 4ad1 取反得 b52e,這就是本包數據最終的 IP 校驗和,再將 b52e 填充到對應位置即可。
2、UDP校驗方法
(1) 校驗方法
UDP_checksum 計算稍微復雜一點,需要加入 IP 偽頭部,將 IP 偽頭部、UDP 首部的 8 個 bytes 和數據包部分按 2byte(即 16bit) 分開后相加,結果如果大於16’hffff,就將超出 16’hffff 的部分與相加結果的低 16 位相加,直到最終結果小於 16’hffff 為止。最后把小於 16’hffff 的結果取反作為 UDP_checksum。高字節在前,低字節在后,替換掉前面在心跳包中填充的0。
(2) UDP結構
UDP_checksum 的組成如圖所示。
可以看到,IP 偽頭部包含了 IP 源地址,IP 目的地址,一個字節的 0,協議號和 UDP_len ,在前面做的千兆以太網圖像傳輸項目中 IP 源地址,IP 目的地址,協議號都是固定的,而通過上一篇博客設計的 UDP_len 為:UDP首部 8byte + 數據長度 64byte = 8+64='h0048。在設計的時候可以先單獨將 IP 偽頭部計算出來。
3、計算的時序安排
ip_checksum 和 udp_checksum 計算完成,該數據填充的位置已經經過,那么就沒辦法將數據填充到原來填充 0 的位置了,但我們想要將其組成完整的以太網包,這一步是不可避免的,那么我們該怎么解決呢?我們可以考慮一下,建立一個足夠大的 RAM ,在計算 ip_checksum 和 udp_checksum 的同時將前面心跳包數據存儲到 RAM 中,當這一包數據全部計算完再將 RAM 中的數據讀出。當讀出到要填充 ip_checksum 和 udp_checksum 的位置時,將計算出的兩個值取反后填充到對應位置即可。

4、時序圖
四、心跳包填充:CRC校驗
1、CRC校驗范圍
CRC校驗另起一段,是因為要先算完前面的 ip 校驗值和 UDP 校驗值才行。CRC校驗時必須先去除幀頭(即前面的7個55和1個d5),還得去掉幀尾(即后面4個數據,前面我們直接在這填了0)。當 CRC 校驗值計算完成后,即可將其填充至數據的末尾 4 位即可。
2、CRC校驗方法
(1)CRC校驗初始值和空閑值都設置為 32‘hffffffff,即全 1 狀態。
(2)計算結果需高低位對調,因為我們計算時是先從高位輸入的,所以最后高低位需要對調。
(3)最后結果取反,高低位對調后的結果再取反,即可得到 CRC 的校驗值。
3、CRC校驗部分代碼
always @(posedge sclk) begin if(rst) begin crc32_value <= 32'hFFFFFFFF; end else if(crc_en) begin crc32_value[ 0] <= c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 1] <= c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 2] <= c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 3] <= c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 4] <= c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[ 5] <= c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[1]^d[7]; crc32_value[ 6] <= c[30]^d[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[ 7] <= c[31]^d[ 0]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[ 8] <= c[ 0]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[ 9] <= c[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]; crc32_value[10] <= c[ 2]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7]; crc32_value[11] <= c[ 3]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7]; crc32_value[12] <= c[ 4]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[13] <= c[ 5]^c[30]^d[ 1]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[14] <= c[ 6]^c[31]^d[ 0]^c[30]^d[ 1]^c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[5]; crc32_value[15] <= c[ 7]^c[31]^d[ 0]^c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]; crc32_value[16] <= c[ 8]^c[29]^d[ 2]^c[28]^d[ 3]^c[24]^d[ 7]; crc32_value[17] <= c[ 9]^c[30]^d[ 1]^c[29]^d[ 2]^c[25]^d[ 6]; crc32_value[18] <= c[10]^c[31]^d[ 0]^c[30]^d[ 1]^c[26]^d[ 5]; crc32_value[19] <= c[11]^c[31]^d[ 0]^c[27]^d[ 4]; crc32_value[20] <= c[12]^c[28]^d[ 3]; crc32_value[21] <= c[13]^c[29]^d[ 2]; crc32_value[22] <= c[14]^c[24]^d[ 7]; crc32_value[23] <= c[15]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[24] <= c[16]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[25] <= c[17]^c[27]^d[ 4]^c[26]^d[ 5]; crc32_value[26] <= c[18]^c[28]^d[ 3]^c[27]^d[ 4]^c[24]^c[30]^d[ 1]^d[ 7]; crc32_value[27] <= c[19]^c[29]^d[ 2]^c[28]^d[ 3]^c[25]^c[31]^d[ 0]^d[ 6]; crc32_value[28] <= c[20]^c[30]^d[ 1]^c[29]^d[ 2]^c[26]^d[ 5]; crc32_value[29] <= c[21]^c[31]^d[ 0]^c[30]^d[ 1]^c[27]^d[ 4]; crc32_value[30] <= c[22]^c[31]^d[ 0]^c[28]^d[ 3]; crc32_value[31] <= c[23]^c[29]^d[ 2]; end
end
4、時序圖
由於這次 CRC 校驗值是在末尾,因此不需要用到 RAM 也來得及填充。
至此,我們組建了以太網發送的心跳包,下一步就可以發送了。
參考資料:威三學院FPGA教程