【方法】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 // 不进行临界区保护

// ★配置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测试函数

/* 省略了一些无关函数的代码 */

/* RTC时间转化为毫秒数 (lwip协议栈要求实现的函数) */
// 该函数必须保证: 除非定时器溢出, 否则后获取的时间必须大于先获取的时间
uint32_t sys_now(void)
{
  uint32_t sec[2];
  uint32_t div, milli;
  do
  {
    time(&sec[0]); // 秒
    div = rtc_divider();
    time(&sec[1]);
  } while (sec[0] != sec[1]);
  
  // CNT是在DIV从P-1跳变到P-2瞬间发生更新的 (P=RTC_PRESCALER)
  if (div == RTC_PRESCALER - 1)
    milli = div;
  else
    milli = RTC_PRESCALER - div - 2;
  milli = milli * 1000 / RTC_PRESCALER; // 毫秒
  return sec[0] * 1000 + milli;
}

int main(void)
{
  struct dhcp *dhcp;
  struct netif dp83848;
  uint8_t ip_displayed = 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定时处理
    sys_check_timeouts();
  }
}

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这款软件,下载地址是DHCP Server for Windows

下载后解压到一个文件夹里面。

首先需要在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。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值