1. 程式人生 > >【方法】lwip-2.0.2配置DHCP自動從路由器獲取IP地址和DNS伺服器地址, 並設定NetBIOS計算機名

【方法】lwip-2.0.2配置DHCP自動從路由器獲取IP地址和DNS伺服器地址, 並設定NetBIOS計算機名

本文使用是STM32F107VC微控制器的有線乙太網外設ETH和DP83848外部PHY收發器。

如果使用的是Keil uVision5的編譯器,需要檢查一下lwip/include/arch/cc.h裡面是否有下面這一行:

#define PACK_STRUCT_BEGIN __packed // struct前的__packed

否則NetBIOS服務是無法正常工作的。

【專案中需要新增的lwip檔案】

httpd伺服器模組:複製lwip/apps/httpd資料夾下的fs.c、fsdata.c、fsdata.h、httpd.c和httpd_structs.h,但只把fs.c和httpd.c新增到Keil專案裡。

netbiosns模組:複製lwip/apps/netbiosns資料夾,並把其中的c檔案新增到Keil專案裡。

【lwipopts.h】

#define NO_SYS 1 // 無作業系統

#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define LWIP_STATS 0

#define MEM_ALIGNMENT 4 // STM32微控制器是32位的微控制器, 因此是4位元組對齊的

#define SYS_LIGHTWEIGHT_PROT 0 // 不進行臨界區保護 (在中斷中呼叫lwip函式時要小心)

// ★配置DHCP
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1

// ★配置DNS
#define LWIP_DNS 1
#define LWIP_RAND() ((u32_t)rand())
【main.c(部分程式碼)】
#include <stdio.h>
#include <stm32f10x.h>
#include "lwip/apps/httpd.h" // http伺服器
#include "lwip/apps/netbiosns.h" // NetBIOS服務
#include "lwip/dhcp.h" // ★DHCP客戶端
#include "lwip/dns.h" // ★DNS客戶端
#include "lwip/init.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "ETH.h" // 自己編寫的網絡卡驅動標頭檔案

err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);
void dns_test(void); // DNS測試函式

/* 省略了一些無關函式的程式碼 */

// sys_now的一種實現方式
uint32_t sys_now(void)
{
  uint32_t sec = RTC_GetCounter();
  uint32_t milli = (39999 - RTC_GetDivider()) * 1000 / 40000; // RTC採用的是40kHz的LSI晶振
  return sec * 1000 + milli;
}

int main(void)
{
  struct dhcp *dhcp;
  struct netif dp83848;
  uint8_t ip_displayed = 0;
  uint32_t last_check = 0;
  
  /* 網絡卡初始化過程省略 */
  
  lwip_init();
  netif_add(&dp83848, IP_ADDR_ANY, IP_ADDR_ANY, IP_ADDR_ANY, NULL, ethernetif_init, ethernet_input); // ★IP地址, 子網掩碼和預設閘道器全部為IP_ADDR_ANY
  netif_set_default(&dp83848);
  netif_set_up(&dp83848);
  
  dp83848.hostname = "STM32F107VC_ETH"; // ★路由器中顯示的名稱
  dhcp_start(&dp83848);
  
  // 需要新增lwip/apps/netbiosns/netbiosns.c
  netbiosns_init();
  netbiosns_set_name("STM32F107VC"); // ★計算機名 (測試方法: (1)ping 計算機名 (2)用瀏覽器訪問http://計算機名/)
  // 注意: 路由器中顯示的名稱可以和計算機名不同
  
  httpd_init(); // 初始化lwip自帶的http伺服器 (lwip/apps/httpd/*)
  while (1)
  {
    // 有資料包就接收資料包
    if (recven && ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET)
    {
      ETH_DMAClearFlag(ETH_DMA_FLAG_R);
      while (ETH_GetDMARxDescFlagStatus(DMARxDescToGet, ETH_DMARxDesc_OWN) == RESET)
        ethernetif_input(&dp83848);
      ETH_DMAITConfig(ETH_DMA_IT_RBU, ENABLE); // 若RBU中斷已關閉, 則再次開啟
    }
    
    // ★顯示DHCP分配到的IP地址
    if (dhcp_supplied_address(&dp83848))
    {
      if (!ip_displayed)
      {
        dhcp = netif_dhcp_data(&dp83848);
        printf("IP address: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));
        printf("Subnet mask: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));
        printf("Default gateway: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
#if LWIP_DNS
        printf("DNS Server: %s\n", ip4addr_ntoa(dns_getserver(0)));
#endif
        ip_displayed = 1;
        dns_test(); // 測試DNS解析
      }
    }
    else
      ip_displayed = 0;
    
    // lwip定時處理
    if (sys_now() - last_check > 20)
    {
      last_check = sys_now();
      sys_check_timeouts(); // 該函式不能呼叫地太頻繁, 否則開機後DHCP要等很久才能分配到IP地址
    }
  }
}

void HardFault_Handler(void)
{
  printf("Hard Error!\n");
  while (1);
}
【dns_test.c】
#include "lwip/tcp.h"
#include "lwip/dns.h"

static err_t test_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
  printf("Connected! err=%d\n", err);
  err = tcp_close(tpcb);
  if (err == ERR_OK)
    printf("Connection is successfully closed!\n");
  else
    printf("Connection cannot be closed now! err=%d\n", err);
  return ERR_OK;
}

static void test_err(void *arg, err_t err)
{
  printf("Connection error! code=%d\n", err);
}

static void connect_test(const ip_addr_t *ipaddr)
{
  struct tcp_pcb *tpcb;
  printf("Connecting to %s...\n", ip4addr_ntoa(ipaddr));
  tpcb = tcp_new();
  tcp_connect(tpcb, ipaddr, 80, test_connected);
  tcp_err(tpcb, test_err);
}

static void dns_found(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
{
  if (ipaddr != NULL)
  {
    printf("DNS Found IP: %s\n", ip4addr_ntoa(ipaddr));
    connect_test(ipaddr);
  }
  else
    printf("DNS Not Found IP!\n");
}

void dns_test(void)
{
  ip_addr_t dnsip;
  err_t err = dns_gethostbyname("zh.arslanbar.net", &dnsip, dns_found, NULL);
  if (err == ERR_OK)
  {
    printf("In cache! IP: %s\n", ip4addr_ntoa(&dnsip));
    connect_test(&dnsip);
  }
  else
    printf("Not in cache! err=%d\n", err); // 快取中沒有時需要等待DNS解析完畢在dns_found回撥函式中返回結果
}

【程式執行結果】

分配到的IP地址:


路由器中顯示的裝置名:(因為筆者沒有把開發板接到路由器上而是電腦的有線網絡卡上,所以用DHCP Server for Windows這個軟體在Win7電腦上模擬了一個DHCP伺服器)


ping命令ping通計算機名(NetBIOS):


用計算機名訪問開發板上的http伺服器:

由於無線路由器比較遠,所以筆者是把開發板用網線直接連線到電腦的有線網絡卡上的,電腦的無線網絡卡連線無線路由器。

Windows Server版有DHCP伺服器功能,但普通使用者版(如win7,xp等系統)沒有這樣的功能,需要安裝第三方DHCP伺服器軟體。下面介紹一下DHCP Server for Windows這款軟體,下載地址是http://www.dhcpserver.de/cms/

下載後解壓到一個資料夾裡面。

首先需要在Windows的無線網卡里面開啟網路共享,使有線網絡卡(本地連線)能夠通過無線網絡卡訪問Internet。在筆者的電腦上,有線網絡卡的網線是插在開發板上的,而無線網絡卡是連線到無線路由器上的。下面介紹的方法可以使開發板也可以正常上網。

(筆者最近發現,只要在Windows系統上開了ICS網路共享,即使不安裝dhcpsrv這個軟體,開發板也能分配到IP地址,但是不能自定義DNS伺服器的地址,而且每次獲取的IP地址都不相同,影響開發)

勾選後點選確定,有線網絡卡的IP地址會被系統自動改為192.168.137.1這個地址。


執行dhcpwiz.exe配置DHCP伺服器,選擇插了開發板網線的網絡卡:


HTTP, TFTP, DNS都不用勾選。這裡的DNS地址估計是當預設DNS伺服器填寫的是預設閘道器的IP地址時,自動轉發到的DNS伺服器真實地址。

配置好有線網絡卡的IP地址,以及DHCP伺服器的IP地址分配範圍:

在Advanced選項裡面設定好預設閘道器(也就是電腦有線網絡卡的IP地址)和DNS伺服器的地址(8.8.8.8):


寫入ini檔案,並點選next,啟動DHCP伺服器(可以設定為windows系統服務)


最後,建議在電腦有線網絡卡與STM32開發板之間加接一個交換機。否則STM32開發板每次燒寫或復位,電腦上的有線網絡卡都要重新初始化一次,導致dhcpsrv服務不停的重啟,長時間分配不到IP地址,降低開發效率。不過,連線或斷開無線網也會導致dhcpsrv服務重啟。

如果想要讓開發板能連線VMware虛擬機器裡面的網路環境為NAT的電腦,只需要在主機裡的VMware Network Adapter VMnet8網絡卡(IP地址和虛擬機器裡面的閘道器相同的網絡卡)上新增ICS到本地連線(有線網絡卡)就行,此時需要關閉無線網絡卡上的ICS。