本帖最后由 dirty 于 2024-6-10 11:53 编辑
本篇讲述板载以太网TCP/UDP通讯功能.
一.硬件原理与准备
开发板以太网模块功能原理如下
图1:以太网原理图
开发板外挂一颗以太网芯片DP83848CVV,芯片使用RMII(Reduced Media Independant Interface)精简介质无关接口。RMI收发使用2位数据进行传输,收发时钟均采用50MHz时钟源。信号定义如下:
图2:RMII信号定义
准备工作,将跳线帽JP48, JP51, JP57, JP59, JP60, JP70接到以太网侧。可以看到开发板以太网功能与LCD/摄像头引脚不兼容,开发板跳线帽灵活切换还是很方便的。
图3:以太网跳线帽选择
二.了解TCP/UDP
TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。传输层是计算机网络中的一层,位于网络层和应用层之间。它主要负责在网络中的两个端系统之间提供可靠的、端到端的数据传输服务。简单理解,传输层就是负责在源主机和目标主机之间提供端到端的数据传输。
TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;
UDP是一种无连接的协议,不提供可靠性保证,但传输效率较高。它将数据分割成数据报,每个数据报都是独立的,可以独立传输,因此没有复杂的连接建立和维护过程。
三.代码准备
lwip协议栈资源可在:http://download.savannah.nongnu.org/releases/lwip/网址下载,本工程上FreeRTOS系统,并会使用到该网络协议栈。
1.关于宏的定义使用。这里使能动态分配IP,使用NET0,并配置服务端IP(电脑作为服务端,通过控制台使用命令ipconfigj即可查到)。
#define USE_DHCP /* enable DHCP, if disabled static address is used */
/*The USE_ENET0 and USE_ENET1 macros cannot be opened at the same time*/
#define USE_ENET0
//#define USE_ENET1
#define IP_S_ADDR0 192
#define IP_S_ADDR1 168
#define IP_S_ADDR2 3
#define IP_S_ADDR3 5
1.网络硬件接口初始化。包括了引脚配置和网络事件中断使能
void enet_system_setup(void)
{
nvic_configuration();
/* configure the GPIO ports for ethernet pins */
enet_gpio_config();
/* configure the ethernet MAC/DMA */
enet_mac_dma_config();
if(0 == enet_init_status) {
while(1) {
}
}
#ifdef USE_ENET0
enet_interrupt_enable(ENET0, ENET_DMA_INT_NIE);
enet_interrupt_enable(ENET0, ENET_DMA_INT_RIE);
#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
enet_desc_select_enhanced_mode(ENET0);
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */
#endif /* USE_ENET0 */
#ifdef USE_ENET1
enet_interrupt_enable(ENET1, ENET_DMA_INT_NIE);
enet_interrupt_enable(ENET1, ENET_DMA_INT_RIE);
#ifdef SELECT_DESCRIPTORS_ENHANCED_MODE
enet_desc_select_enhanced_mode(ENET1);
#endif /* SELECT_DESCRIPTORS_ENHANCED_MODE */
#endif /* USE_ENET1 */
}
/*!
\brief this function handles ethernet interrupt request
\param[in] none
\param[out] none
\retval none
*/
void ENET0_IRQHandler(void)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
/* frame received */
if(SET == enet_interrupt_flag_get(ENET0, ENET_DMA_INT_FLAG_RS)){
/* give the semaphore to wakeup LwIP task */
xSemaphoreGiveFromISR(g_rx_semaphore, &xHigherPriorityTaskWoken);
}
//printf("ENET0_IRQHandler\r\n");
/* clear the enet DMA Rx interrupt pending bits */
enet_interrupt_flag_clear(ENET0, ENET_DMA_INT_FLAG_RS_CLR);
enet_interrupt_flag_clear(ENET0, ENET_DMA_INT_FLAG_NI_CLR);
/* switch tasks if necessary */
if(pdFALSE != xHigherPriorityTaskWoken){
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}
}
2.LwIP 协议栈初始化。包含了tcp ip任务栈初始化, IP分配,网络芯片寄存器配置,网络状态事件回调注册及设置生效网络。
/*!
\brief initializes the LwIP stack
\param[in] none
\param[out] none
\retval none
*/
void lwip_stack_init(void)
{
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
/* create tcp_ip stack thread */
tcpip_init( NULL, NULL );
/* IP address setting */
#ifdef USE_DHCP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else
IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3);
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
#endif /* USE_DHCP */
#ifdef USE_ENET0
/* - netif_add(struct netif *netif, ip_addr_t *ipaddr,
ip_addr_t *netmask, ip_addr_t *gw,
void *state, err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
Adds your network interface to the netif_list. Allocate a struct
netif and pass a pointer to this structure as the first argument.
Give pointers to cleared ip_addr structures when using DHCP,
or fill them with sane numbers otherwise. The state pointer may be NULL.
The init function pointer must point to a initialization function for
your ethernet netif interface. The following code illustrates it's use.*/
netif_add(&g_mynetif0, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* registers the default network interface */
netif_set_default(&g_mynetif0);
netif_set_status_callback(&g_mynetif0, lwip_netif_status_callback);
/* when the netif is fully configured this function must be called */
netif_set_up(&g_mynetif0);
#endif /* USE_ENET0 */
#ifdef USE_ENET1
/* - netif_add(struct netif *netif, ip_addr_t *ipaddr,
ip_addr_t *netmask, ip_addr_t *gw,
void *state, err_t (* init)(struct netif *netif),
err_t (* input)(struct pbuf *p, struct netif *netif))
Adds your network interface to the netif_list. Allocate a struct
netif and pass a pointer to this structure as the first argument.
Give pointers to cleared ip_addr structures when using DHCP,
or fill them with sane numbers otherwise. The state pointer may be NULL.
The init function pointer must point to a initialization function for
your ethernet netif interface. The following code illustrates it's use.*/
netif_add(&g_mynetif1, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
/* registers the default network interface */
netif_set_default(&g_mynetif1);
netif_set_status_callback(&g_mynetif1, lwip_netif_status_callback);
/* when the netif is fully configured this function must be called */
netif_set_up(&g_mynetif1);
#endif /* USE_ENET1 */
}
3.网络状态回调。这里实现TCP/UDP 客户端服务端功能,每一个创建任务并予以实现.
/*!
\brief after the netif is fully configured, it will be called to initialize the function of telnet, client and udp
\param[in] netif: the struct used for lwIP network interface
\param[out] none
\retval none
*/
void lwip_netif_status_callback(struct netif *netif)
{
if(((netif->flags & NETIF_FLAG_UP) != 0) && (0 != netif->ip_addr.addr)) {
printf("%s\r\n",__func__);
/* initilaize the tcp server: telnet 8000 */
hello_gigadevice_init();
printf("initilaize the tcp server: telnet 8000\r\n");
/* initilaize the tcp client: echo 10260 */
tcp_client_init();
printf("initilaize the tcp client: echo 10260\r\n");
/* initilaize the udp: echo 1025 */
udp_echo_init();
printf("initilaize the udp: echo 1025\r\n");
}
}
这里以TCP Client为例,讲解并添加写日志,打印接收数据。开发板作为客户端,电脑作为服务端。ipaddr就是前面讲过电脑端IP,tcp 端口定义为10260,创建socket后,绑定IP及端口,然后连接。如连接上服务端后接收数据等待,如接收到数据,将接受的数据原样发回给服务端,这里添加了接收数据打印。其他,TCP Server 端口号为8000,UDP 端口1025.
static void tcp_client_task(void *arg)
{
int ret, recvnum, sockfd = -1;
int tcp_port = 10260;
struct sockaddr_in svr_addr, clt_addr;
char buf[100];
ip_addr_t ipaddr;
IP4_ADDR(&ipaddr, IP_S_ADDR0, IP_S_ADDR1, IP_S_ADDR2, IP_S_ADDR3);
/* set up address to connect to */
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(tcp_port);
svr_addr.sin_addr.s_addr = ipaddr.addr;
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(tcp_port);
clt_addr.sin_addr.s_addr = htons(INADDR_ANY);
while(1) {
/* create the socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
continue;
}
// printf("create tcp_client socket ok\r\n");
ret = bind(sockfd, (struct sockaddr *)&clt_addr, sizeof(clt_addr));
if(ret < 0) {
lwip_close(sockfd);
sockfd = -1;
continue;
}
// printf("bind server ok\r\n");
/* connect */
ret = connect(sockfd, (struct sockaddr *)&svr_addr, sizeof(svr_addr));
if(ret < 0) {
lwip_close(sockfd);
sockfd = -1;
continue;
}
printf("connect server ok\r\n");
while(-1 != sockfd) {
/* reveive packets, and limit a reception to MAX_BUF_SIZE bytes */
recvnum = recv(sockfd, buf, MAX_BUF_SIZE, 0);
if(recvnum <= 0) {
lwip_close(sockfd);
sockfd = -1;
break;
}
printf("receive tcp server data ,len:%d\r\n",recvnum);
for(int i=0;i<recvnum;i++)
{
printf("0x%02x ",buf[i]);
}
printf("\r\nreceive data and send to\r\n");
send(sockfd, buf, recvnum, 0);
}
lwip_close(sockfd);
sockfd = -1;
}
}
4.dhcp实现。这里通过创建任务dhcp_task,启动dhcp配置,等待路由IP分配,获取到动态IP.
void dhcp_task(void * pvParameters)
{
ip_addr_t ipaddr;
ip_addr_t netmask;
ip_addr_t gw;
#ifdef USE_ENET0
struct dhcp *dhcp_client0;
#endif /* USE_ENET0 */
#ifdef USE_ENET1
struct dhcp *dhcp_client1;
#endif /* USE_ENET1 */
printf("dhcp task run\r\n");
for(;;){
#ifdef USE_ENET0
switch(dhcp_state){
case DHCP_START:
dhcp_start(&g_mynetif0);
/* IP address should be set to 0 every time we want to assign a new DHCP address*/
dhcp_state = DHCP_WAIT_ADDRESS;
break;
case DHCP_WAIT_ADDRESS:
/* read the new IP address */
ip_address.addr = g_mynetif0.ip_addr.addr;
if(0 != ip_address.addr){
dhcp_state = DHCP_ADDRESS_ASSIGNED;
printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \
ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address));
}else{
/* DHCP timeout */
dhcp_client0 = netif_dhcp_data(&g_mynetif0);
if (dhcp_client0->tries > MAX_DHCP_TRIES){
dhcp_state = DHCP_TIMEOUT;
/* stop DHCP */
dhcp_stop(&g_mynetif0);
/* static address used */
IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
netif_set_addr(&g_mynetif0, &ipaddr , &netmask, &gw);
}
}
break;
default:
break;
}
#endif /* USE_ENET0 */
#ifdef USE_ENET1
switch(dhcp_state){
case DHCP_START:
dhcp_start(&g_mynetif1);
/* IP address should be set to 0 every time we want to assign a new DHCP address*/
dhcp_state = DHCP_WAIT_ADDRESS;
break;
case DHCP_WAIT_ADDRESS:
/* read the new IP address */
ip_address.addr = g_mynetif1.ip_addr.addr;
if(0 != ip_address.addr){
dhcp_state = DHCP_ADDRESS_ASSIGNED;
printf("\r\nDHCP -- eval board ip address: %d.%d.%d.%d \r\n", ip4_addr1_16(&ip_address), \
ip4_addr2_16(&ip_address), ip4_addr3_16(&ip_address), ip4_addr4_16(&ip_address));
}else{
/* DHCP timeout */
dhcp_client1 = netif_dhcp_data(&g_mynetif1);
if (dhcp_client1->tries > MAX_DHCP_TRIES){
dhcp_state = DHCP_TIMEOUT;
/* stop DHCP */
dhcp_stop(&g_mynetif1);
/* static address used */
IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );
IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
netif_set_addr(&g_mynetif1, &ipaddr , &netmask, &gw);
}
}
break;
default:
break;
}
#endif /* USE_ENET1 */
/* wait 250 ms */
vTaskDelay(250);
}
}
四.调试测验
编译烧录。准备一根网线,一端接到路由器网口,一端接到开发板网口。准备网络调试助手工具,个人用的NetAssist。
图4:开发板与路由器连接好网线
上电,等待获取到开发板IP,开发板作为TCP Server,配置如下图所示.
图5:开发板作为TCP Server工具配置
点击连接,在工具发送数据,可以看到通讯如下
图6:开发板作为TCPServer通讯
开发板作为TCP Client,配置通讯如下图所示
图7:开发板作为TCPClient工具配置与通讯
开发板作为UDP,配置如下图所示
图8:UDP通讯
至此,实现开发板以太网TCP/UDP通讯功能。