软件环境:vivado 2019.1 硬件平台:XC7Z020
赶紧赶在二月的最后一天发一个简单却很实用的程序来把二月的坑补了。相信大家在用SDK测网口的时候,用的最多的就是TCP_ECHO_SERVER的例程了,这个例程是板卡做SERVER,跑TCP时候的测试例程,既然是ECHO_SERVER,自然就离不开建立连接,然后收到什么发出去什么。但是有些时候ECHO_SERVER连接建立不起来,想要进一步排查究竟是PHY的问题,或者是接收、发送通道问题的时候,ECHO_SERVER就没法直接使用了,这时候需要的就是一个类似UDP的,不需要建立连接,直接就能不停往外发数据的程序。所以今天,就简简单单给大家,搞一个UDP_ECHO_SERVER!!!拿来就能直接用!!最简程序!!!
vivado这边,依旧用最简单带网口串口的就行。
主要的还是在SDK程序这边,首先说下,SDK里随便建立个空工程,然后修改BSP,在里面把LWIP的支持勾上。
接下来进一步修改LWIP参数配置细节。DHCP关掉,TCP关掉,TCP_QUEUE_OOSEQ关掉,UDP打开,这里只更改这几个,其他涉及PBUF大小什么的,个人根据工程需求更改,需求不高的话当然按默认的也可以。
都OK了之后,向工程中以此添加如下5个文件。
main.c
#include "sys_intr.h"
#include "lwip/err.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "lwipopts.h"
#include "sleep.h"
#include "udp_process.h"
static XScuGic Intc; //GIC
extern void lwip_init(void);
static u8 RecvBuffer[] = "hello joker!\n";
void init_intr_sys(void)
{
Init_Intr_System(&Intc); // initial interrupt system
Setup_Intr_Exception(&Intc);
}
int main(void)
{
struct netif *netif, server_netif;
ip_addr_t ipaddr, netmask, gw;
/* the mac address of the board. this should be unique per board */
unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
init_intr_sys();
netif = &server_netif;
IP4_ADDR(&ipaddr, 169, 254, 236, 21);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 169, 254, 236, 1);
lwip_init();
if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {
xil_printf("Error adding NetWork interface\r\n");
return -1;
}
netif_set_default(netif);
/* specify that the network if is up */
netif_set_up(netif);
/* initialize tcp pcb */
udp_recv_init();
while (1)
{
//udp_period_send(RecvBuffer,sizeof(RecvBuffer));
xemacif_input(netif);
}
return 0;
}
熟悉TCP_ECHO_SERVER的朋友看得出来,main初始化这边跟TCP_ECHO_SERVER基本是一个套路的,都是网卡绑定,LWIP初始化,UDP回调函数绑定等等。
sys_intr.c
#include "sys_intr.h"
void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
{
/* Enable interrupts from the hardware */
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
(void *)IntcInstancePtr);
Xil_ExceptionEnable();
}
int Init_Intr_System(XScuGic * IntcInstancePtr)
{
int Status;
XScuGic_Config *IntcConfig;
/*
* Initialize the interrupt controller driver so that it is ready to
* use.
*/
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == IntcConfig) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
sys_intr.h
#ifndef SYS_INTR_H_
#define SYS_INTR_H_
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xscugic.h"
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
int Init_Intr_System(XScuGic * IntcInstancePtr);
void setup_Intr_Exception(XScuGic * IntcInstancePtr);
#endif
这两个是通用的,只要涉及中断初始化,都避不开。
udp_process.c
#include "udp_process.h"
#include <stdio.h>
#include <string.h>
#include "lwip/err.h"
#include "lwip/udp.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "xil_printf.h"
#include "sleep.h"
struct udp_pcb *pcb = NULL;
struct udp_pcb *connected_pcb = NULL;
static struct pbuf *pbuf_to_be_sent = NULL;
static unsigned local_port = 9000; /* server port */
static unsigned remote_port = 9000;
ip_addr_t ipaddr;
void udp_period_send(u8_t *BufferPtr,u32_t count)
{
err_t err;
pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, count, PBUF_POOL);
memcpy(pbuf_to_be_sent->payload, BufferPtr, count);
//err = udp_sendto(pcb, pbuf_to_be_sent,&ipaddr,remote_port);
err = udp_send(pcb, pbuf_to_be_sent);
sleep(1);
if (err != ERR_OK)
{
xil_printf("Error on udp_send: %d\r\n", err);
pbuf_free(pbuf_to_be_sent);
return;
}
pbuf_free(pbuf_to_be_sent);
}
void udp_recv_callback(void *arg, struct udp_pcb *tpcb,
struct pbuf *p, ip_addr_t *addr, u16_t port)
{
err_t err;
err = udp_sendto(tpcb, p,&ipaddr,remote_port);
if (err != ERR_OK)
{
xil_printf("Error on udp_send: %d\r\n", err);
pbuf_free(p);
return;
}
pbuf_free(p);
return;
}
int udp_recv_init()
{
err_t err;
/* create new UDP PCB structure */
pcb = udp_new();
if (!pcb) {
xil_printf("Error creating PCB. Out of Memory\r\n");
return -1;
}
/* bind to local port */
err = udp_bind(pcb, IP_ADDR_ANY, local_port);
if (err != ERR_OK) {
xil_printf("udp_recv_init: Unable to bind to port %d: err = %d\r\n", local_port, err);
return -2;
}
IP4_ADDR(&ipaddr, 169, 254, 236, 94);
err = udp_connect(pcb, &ipaddr, remote_port);
if (err != ERR_OK)
xil_printf("error on udp_connect: %x\n\r", err);
udp_recv(pcb, udp_recv_callback, NULL);
return 0;
}
这里稍微细说一下,从下往上说吧。
最后一个一看就知道是udp初始化的函数,先是udp块的建立,然后绑定本地的IP地址和端口号,接着连接远端的IP地址和端口号,当然这里的连接是个假连接,不像TCP那种需要握手的连接,最后将UDP_PCB和接收回调函数绑定,只要收到数据,就会调用回调函数。
中间的就是刚说的,回调函数,接收到什么就会发回去什么。
第一个是另外单独建立的定时发送数据的函数,至于定时的时间,简单的拿sleep()控制就行。
不细心的朋友可能发现不了,在回调函数里和定时发送数据的函数里,我用来发送udp数据的接口不一样,一个用的是udp_sendto()另外一个用的是udp_send(),这两个的区别简单的来说就是udp_sendto在使用的时候可以不需要初始化时候的udp_connect的绑定,因为要发送的对端IP和端口都在udp_sendto的参数列表里,而udp_send在使用时候需要先使用udp_connect绑一个要发送端的IP和端口,不然不知道数据要发送给谁,所以相对于udp_send来说,udp_sendto在使用时候要灵活一些。
最后最后最后最后,在说一点,这个程序里有个小坑,但是影响不大,就是主函数while(1)里有两个函数,只运行上面的,会定时向外发送"hello joker",只运行下面的,会收到什么返回什么,两个一起运行,下面的会发送不正常,估计是受到上面定时发送函数中的sleep的影响了,等到回头有空了再改一版把这个坑补了,现在用问题不大哈哈哈哈哈。
udp_process.h
#ifndef UDP_PROCESS_H_
#define UDP_PROCESS_H_
#include "lwip/err.h"
#include "lwip/udp.h"
#include "lwipopts.h"
void udp_recv_callback(void *arg, struct udp_pcb *tpcb,struct pbuf *p, ip_addr_t *addr, u16_t port);
int udp_recv_init(void);
void udp_period_send(u8_t *BufferPtr,u32_t count);
#endif