CH32V307 DHCP例程介绍

发布时间 2023-12-26 14:33:30作者: ZaiLi

1、DHCP概述

DHCP,全称为Dynamic Host Configuration Protocol,动态主机配置协议,该协议允许服务器向客户端动态分配 IP 地址和配置信息,实现了自动设置IP地址、统一管理IP地址分配,简单理解为实现即插即用。

 

2、例程介绍

main函数内容如下:

 

/*********************************************************************
 * @fn      main
 *
 * @brief   Main program
 *
 * @return  none
 */
int main(void)
{
    u8 i;

    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);                                            //USART initialize
    printf("DHCP Test\r\n");      
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
    printf("net version:%x\n", WCHNET_GetVer());
    if( WCHNET_LIB_VER != WCHNET_GetVer())
    {
        printf("version error.\n");
    }
    WCHNET_GetMacAddr(MACAddr);                                           //get the chip MAC address
    printf("mac addr:");
    for(i = 0; i < 6; i++) 
        printf("%x ", MACAddr[i]);
    printf("\n");
    TIM2_Init();
    WCHNET_DHCPSetHostname("WCHNET");                                     //Configure DHCP host name
    i = ETH_LibInit(IPAddr,GWIPAddr,IPMask,MACAddr);                      //Ethernet library initialize
    mStopIfError(i);
    if(i == WCHNET_ERR_SUCCESS)
        printf("WCHNET_LibInit Success\r\n");
    WCHNET_DHCPStart(WCHNET_DHCPCallBack);                                //Start DHCP

    while(1)
    {
        /*Ethernet library main task function,
         * which needs to be called cyclically*/
        WCHNET_MainTask();
        /*Query the Ethernet global interrupt,
         * if there is an interrupt, call the global interrupt handler*/
        if(WCHNET_QueryGlobalInt())
        {
            WCHNET_HandleGlobalInt();
        }
    }
}

 

下为具体介绍:

    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);                                            //USART initialize
    printf("DHCP Test\r\n");      
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
    printf("net version:%x\n", WCHNET_GetVer());
    if( WCHNET_LIB_VER != WCHNET_GetVer())
    {
        printf("version error.\n");
    }

首先是相关函数初始化以及例程相关信息的打印。SystemCoreClockUpdate函数、Delay_Init函数、USART_Printf_Init函数分别为系统时钟初始化、延时函数初始化、串口打印函数初始化,在此不再赘述。

打印函数中,WCHNET_GetVer函数为CH32V307以太网协议栈库函数,功能为获取库的版本号,直接调用即可。获取版本号之后会进行一个判断,若不一致,打印version error.

 

    WCHNET_GetMacAddr(MACAddr);                                           //get the chip MAC address
    printf("mac addr:");
    for(i = 0; i < 6; i++) 
        printf("%x ", MACAddr[i]);
    printf("\n");
TIM2_Init();

 

WCHNET_GetMacAddr函数主要用于获取CH32V307的MAC地址。MAC地址,即发送这个帧的设备希望接收这个帧的设备地址,由IEEE为生产商分配,全球唯一,6字节48 位。 CH32V307的MAC地址出厂时已烧录在芯片内部。地址的发送遵循低有效位在前的原则。

TIM2_Init函数主要用于检测以太网PHY连接的状态。定时器10ms进一次中断。在定时器中断函数中,主要对定时器周期进行加法计数。

 

    WCHNET_DHCPSetHostname("WCHNET");  

 

WCHNET_DHCPSetHostname为协议栈库函数,直接调用即可,用于配置DHCP主机名。关于配置的DHCP主机名,可在路由器网页中看到,在设备列表里有设备名称。

 

    i = ETH_LibInit(IPAddr,GWIPAddr,IPMask,MACAddr);                      //Ethernet library initialize
    mStopIfError(i);
    if(i == WCHNET_ERR_SUCCESS)
        printf("WCHNET_LibInit Success\r\n");

 

ETH_LibInit为以太网库初始化,ETH_LibInit函数内容如下:

 

/*********************************************************************
 * @fn      ETH_LibInit
 *
 * @brief   Ethernet library initialization program
 *
 * @return  command status
 */
uint8_t ETH_LibInit( uint8_t *ip, uint8_t *gwip, uint8_t *mask, uint8_t *macaddr )
{
    uint8_t s;
    struct _WCH_CFG  cfg;

    memset(&cfg,0,sizeof(cfg));
    cfg.TxBufSize = ETH_TX_BUF_SZE;
    cfg.TCPMss   = WCHNET_TCP_MSS;
    cfg.HeapSize = WCHNET_MEM_HEAP_SIZE;
    cfg.ARPTableNum = WCHNET_NUM_ARP_TABLE;
    cfg.MiscConfig0 = WCHNET_MISC_CONFIG0;
    cfg.MiscConfig1 = WCHNET_MISC_CONFIG1;
    cfg.led_link = ETH_LedLinkSet;
    cfg.led_data = ETH_LedDataSet;
    cfg.net_send = ETH_TxPktChainMode;
    cfg.CheckValid = WCHNET_CFG_VALID;
    s = WCHNET_ConfigLIB(&cfg);
    if( s ){
       return (s);
    }
    s = WCHNET_Init(ip,gwip,mask,macaddr);
    ETH_Init( macaddr );
    return (s);
}
View Code

 

该函数中,首先对_WCH_CFG结构体参数进行配置,_WCH_CFG结构体内容如下:

 

struct _WCH_CFG
{
  uint32_t TxBufSize;                         //MAC send buffer size, reserved for use
  uint32_t TCPMss;                            //TCP MSS size
  uint32_t HeapSize;                          //heap memory size
  uint32_t ARPTableNum;                       //Number of ARP lists
  uint32_t MiscConfig0;                       //Miscellaneous Configuration 0
  /* Bit 0 TCP send buffer copy 1: copy, 0: not copy */
  /* Bit 1 TCP receive replication optimization, used for internal debugging */
  /* bit 2 delete oldest TCP connection 1: enable, 0: disable */
  /* Bits 3-7 Number of PBUFs of IP segments  */
  /* Bit 8 TCP Delay ACK disable */
  uint32_t MiscConfig1;                       //Miscellaneous Configuration 1
  /* Bits 0-7 Number of Sockets*/
  /* Bits 8-12 Reserved */
  /* Bit  13 PING enable, 1: On 0: Off  */
  /* Bits 14-18 TCP retransmission times  */
  /* Bits 19-23 TCP retransmission period, in 50 milliseconds  */
  /* bit  25 send failed retry, 1: enable, 0: disable */
  /* bit  26 Select whether to perform IPv4 checksum check on
   *         the TCP/UDP/ICMP header of the received frame payload by hardware,
   *         and calculate and insert the checksum of the IP header and payload of the sent frame by hardware.*/
  /* Bits 27-31 period (in 250 milliseconds) of Fine DHCP periodic process */
  led_callback led_link;                      //PHY Link Status Indicator
  led_callback led_data;                      //Ethernet communication indicator
  eth_tx_set net_send;                        //Ethernet send
  eth_rx_set net_recv;                        //Ethernet receive
  uint32_t   CheckValid;                      //Configuration value valid flag, fixed value @WCHNET_CFG_VALID
};
View Code

 

TxBufSize主要配置MAC发送缓冲区大小

TCPMss主要配置TCP最大报文段长度

HeapSize主要配置堆大小

ARPTableNum,主要配置ARP列表数量

 

MiscConfig0为杂项配置,对应位的注释如下:

/*Bit 0 TCP发送缓冲区复制1:复制,0:不复制*/

/*Bit 1 TCP接收复制优化,用于内部调试*/

/*Bit 2删除最旧的TCP连接1:启用,0:禁用*/

/*Bits 3-7 IP段的PBUF数*/

/*Bit 8 TCP延迟ACK禁用*/

 

MiscConfig1为杂项配置,对应位的注释如下:

/*Bit 0-7Socket数量*/

/*Bit 8-12保留*/

/*Bit 13 PING使能,1:开0:关*/

/*Bit 14-18 TCP重传次数*/

/*Bit 19-23 TCP重传周期,以50毫秒为单位*/

/*Bit 25发送失败重试,1:启用,0:禁用*/

/*Bit 26选择是否对执行IPv4校验和检查

*由硬件接收的帧有效载荷的TCP/UDP/ICMP报头,

*并通过硬件计算和插入发送帧的IP报头和有效载荷的校验和*/

/*Bit 27-31精细DHCP周期过程的周期(以250毫秒为单位)*/

 

led_link主要配置PHY建立连接指示灯

led_data主要配置PHY数据传输指示灯

关于接口指示灯配置,例程默认配置的是PC0和PC1,可根据自己需求进行修改

 

net_send主要配置以太网发送以链模式发送数据帧。

 

CheckValid主要配置配置值有效标志,固定值为WCHNET_CFG_VALID

 

 

WCHNET_ConfigLIB为协议栈库函数,主要用于库参数配置,即配置上述介绍参数,返回值为0表示成功。

 

WCHNET_Init函数为协议栈库函数,主要用于配置库初始化,主要设置IP地址,网关地址、子网掩码以及MAC地址,返回值为0表示成功

 

ETH_Init函数为以太网初始化函数。

 

 

 

回到main函数,以太网库初始化完成后,调用WCHNET_DHCPStart函数启动DHCP。

 

当DHCP成功或者失败时,库会调用dhcp_callback函数,通知应用层DHCP的状态,WCHNET
向本函数传递两个参数,第一个参数为DHCP状态,0为成功,其他值失败,当DHCP成功时,用户可以通过第二个参数获取到一个指针,该指针指向的地址依次保存了 IP 地址,网关地址,子网掩码,主DNS和次DNS,一共20个字节。注意该指针为临时变量dhcp_callback
返回后,该指针失效。
如果当前网络内没有DHCP Server,会产生超时时间,超时时间约为10秒。超时后调用
dhcp_callback函数通知应用层,此时DHCP并不会停止,会一直查找DHCP Server。用户可
以调用 WCHNET_DHCPStop 来停止DHCP。
使用时注意以下两点:
(1)必须在WCHNET_Init成功之后启动DHCP(必须)。
(2)在DHCP成功之后,再创建socket(推荐)。

 

(3)DHCP功能会占用一个UDP socket
如果DHCP失败,则可以用WCHNET_Init 时使用的IP地址进行通讯。

 

 

 

while循环中,首先执行WCHNET_MainTask函数,WCHNET_MainTask函数内容如下:

 

/*********************************************************************
 * @fn      WCHNET_MainTask
 *
 * @brief   library main task function
 *
 * @param   none.
 *
 * @return  none.
 */
void WCHNET_MainTask(void)
{
    WCHNET_NetInput( );                     /* Ethernet data input */
    WCHNET_PeriodicHandle( );               /* Protocol stack time-related task processing */
    WCHNET_HandlePhyNegotiation();
    WCHNET_RecProcess();
}
View Code

 

WCHNET_NetInput函数为以太网数据输入函数,总是在主程序中调用,或者在检测到接收中断后调用。

WCHNET_PeriodicHandle函数处理协议栈中的时间相关任务

WCHNET_HandlePhyNegotiation函数处理PHY的协商

 

        if(WCHNET_QueryGlobalInt())
        {
            WCHNET_HandleGlobalInt();
        }

 

查询全局中断状态,当查询到有相应的中断标志置1之后,执行中断处理函数,中断处理函数内容如下:

 

/*********************************************************************
 * @fn      WCHNET_HandleGlobalInt
 *
 * @brief   Global Interrupt Handle
 *
 * @return  none
 */
void WCHNET_HandleGlobalInt(void)
{
    u8 intstat;
    u16 i;
    u8 socketint;

    //WCHNET_GetGlobalInt,读全局中断并将全局中断清零,具体状态码请查阅 WCHNET.h
    intstat = WCHNET_GetGlobalInt();                              //get global interrupt flag,获取全局中断并将全局中断清0
    if (intstat & GINT_STAT_UNREACH)                              //Unreachable interrupt,无法访问的中断
    {
        printf("GINT_STAT_UNREACH\r\n");
    }
    if (intstat & GINT_STAT_IP_CONFLI)                            //IP conflict,IP冲突
    {
        printf("GINT_STAT_IP_CONFLI\r\n");
    }
    if (intstat & GINT_STAT_PHY_CHANGE)                           //PHY status change,PHY状态改变
    {
        i = WCHNET_GetPHYStatus();
        if (i & PHY_Linked_Status)
            printf("PHY Link Success\r\n");
    }
    if (intstat & GINT_STAT_SOCKET) {                             //socket related interrupt,socket相关中断
        for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++)
        {
            socketint = WCHNET_GetSocketInt(i);                   //获取套接字中断,并清除套接字中断。
            if (socketint)
                WCHNET_HandleSockInt(i, socketint);               //socket中断处理函数
        }
    }
}
View Code

 

该函数具体可参考注释,此处主要注意socket中断处理函数,socket中断处理函数内容如下:

/*********************************************************************
 * @fn      WCHNET_HandleSockInt
 *
 * @brief   Socket Interrupt Handle
 *
 * @param   socketid - socket id.
 *          intstat - interrupt status
 *
 * @return  none
 */
void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
    if (intstat & SINT_STAT_RECV)                               //receive data
    {
        WCHNET_DataLoopback(socketid);                          //Data loopback
    }
    if (intstat & SINT_STAT_CONNECT)                            //connect successfully
    {
        WCHNET_ModifyRecvBuf(socketid, (u32) SocketRecvBuf[socketid], RECE_BUF_LEN);
        printf("TCP Connect Success\r\n");
    }
    if (intstat & SINT_STAT_DISCONNECT)                         //disconnect
    {
        printf("TCP Disconnect\r\n");
    }
    if (intstat & SINT_STAT_TIM_OUT)                            //timeout disconnect
    {
        printf("TCP Timeout\r\n");
        WCHNET_CreateTcpSocket();
    }
}
View Code

该函数中主要关注WCHNET_DataLoopback函数,该函数内容如下:

/*********************************************************************
 * @fn      WCHNET_DataLoopback
 *
 * @brief   Data loopback function.
 *
 * @param   id - socket id.
 *
 * @return  none
 */
void WCHNET_DataLoopback(u8 id)
{
#if 1
    u8 i;
    u32 len;
    u32 endAddr = SocketInf[id].RecvStartPoint + SocketInf[id].RecvBufLen;       //Receive buffer end address

    if ((SocketInf[id].RecvReadPoint + SocketInf[id].RecvRemLen) > endAddr) {    //Calculate the length of the received data
        len = endAddr - SocketInf[id].RecvReadPoint;
    }
    else {
        len = SocketInf[id].RecvRemLen;
    }
    i = WCHNET_SocketSend(id, (u8 *) SocketInf[id].RecvReadPoint, &len);        //send data
    if (i == WCHNET_ERR_SUCCESS) {
        WCHNET_SocketRecv(id, NULL, &len);                                      //Clear sent data
    }
#else
    u32 len, totallen;
    u8 *p = MyBuf;

    len = WCHNET_SocketRecvLen(id, NULL);                                //query length
    totallen = len;
    WCHNET_SocketRecv(id, MyBuf, &len);                                  //Read the data of the receive buffer into MyBuf
    while(1){
        len = totallen;
        WCHNET_SocketSend(id, p, &len);                                  //Send the data
        totallen -= len;                                                 //Subtract the sent length from the total length
        p += len;                                                        //offset buffer pointer
        if(totallen)continue;                                            //If the data is not sent, continue to send
        break;                                                           //After sending, exit
    }
#endif
}
View Code

该函数主要是将接收的数据发送出去。

 

以上就是整个DHCP例程的工作流程。