本帖最后由 DKENNY 于 2024-9-29 11:50 编辑
#申请原创# @21小跑堂
前言
接上文,在构建UDP实例时,我们采用了裸机开发的方式,也就是说没有使用操作系统,因此使用了RAW编程接口。虽然RAW编程接口可以提供更高的程序效率,但它需要对LWIP有深入的了解,并且不适合处理大数据量的场合。本篇文章将介绍NETCONN编程接口。使用NETCONN API时,需要操作系统的支持,这里我们使用的是FreeRTOS操作系统。
本文的目录章节如下:
- 1. LWIP移植FREERTOS
- 2. NETCONN编程接口简介
- 2.1 netbuf 数据缓冲区
- 2.2 netconn 连接结构
- 2.3 netconn api函数
- 3. 编写NETCONN UDP实例
- 4. 总结
- 附录
1. LWIP移植FREERTOS
废话不多说,既然NETCONN API需要使用操作系统才能编程,我们就先在已有LWIP移植成功的前提下,把FREERTOS添加进去。
FREERTOS源码获取:https://github.com/FreeRTOS
1.1 打开LWIP模板工程,添加FREERTOS相关头文件
1.2 新建FREERTOS,添加相关源文件。
打开FreeRTOS\Source,添加所有.c文件
打开FreeRTOS\Source\portable\MemMang,添加堆操作文件
打开FreeRTOS\Source\portable\RVDS\ARM_CM4F,添加port.c文件
1.3 除此之外,打开LWIP/contrib/ports/freertos,将sys_arch.c和sys_arch.h复制到工程的include和source。
1.4 新建FREERTOSConfig.h,代码内容如下。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0//1
#define configUSE_TICK_HOOK 0//1
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 100 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 20 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 10 )
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 0//2
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_MALLOC_FAILED_HOOK 0//1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configGENERATE_RUN_TIME_STATS 0
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
//#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
#endif /* FREERTOS_CONFIG_H */
1.5 打开lwipopts.h,修改NO_SYS宏,开启SYS。
1.6 注释掉一些中断服务函数,避免重复实现。
1.7 因为是在LWIP已经移植好的前提下,所以我们已经节约了大部分时间了,直接编译,会出现以下报错。
这里提示空间不够,怎么办呢?我们发现不管是修改启动文件的堆栈大小还是修改编译优化等级,都无法解决这个问题,这样我们就需要考虑是不是FREERTOS本身的堆栈分配有误导致的,我们只需要修改FreeRTOS分配的固定堆栈大小就行了。打开FreeRTOSConfig.h,减少堆大小的配置即可。
这样就编译通过了。
1.8 关闭DHCP功能,只需注释掉main.h USE_DHCP即可,打开NETConfig.h,修改静态IP地址。
1.9 下载烧录代码到开发板,使用网线连接开发板和pc端,测试能否ping通。
正常ping通,移植成功。
2. NETCONN编程接口简介
2.1 netbuf 数据缓冲区
netbuf 是 NETCONN API 编程接口使用的描述数据包的结构,我们可以使用 netbuf 来管理发送数据、接收数据的缓冲区。有关 netbuf 的详细描述在 netbuf.c 和 netbuf.h 这两个文件中,netbuf 是一个结构体,在 netbuf.h 中定义了这个结构体,代码如下,这里我们去掉了条件编译的代码。
struct netbuf
{
struct pbuf *p, *ptr;
ip_addr_t addr;
u16_t port;
};
我们可以从上面的代码中看出,p 和 ptr 指向 pbuf 链表,其中 p 和 ptr 都指向 pbuf 链表,不同的是 p 一直指向 pbuf 链表的第一个 pbuf 结构,而 ptr 可能指向链表中其他的位置,netbuf_next()和 netbuf_first()操作 ptr 字段。addr 和 port 字段用来记录数据发送方的 IP 地址和端口号,netbuf_fromaddr 和 netbuf_fromport 这两个宏定义用于返回 addr 和 port 这两个字段。netbuf和 pbuf 之间的关系如下图所示。
不管是 TCP 连接还是 UDP 连接,接收到数据包后会将数据封装在一个 netbuf 中,然后将这个 netbuf 交给应用程序去处理。在数据发送时,根据不同的连接有不同的处理:对于 TCP 连接,用户只需要提供待发送数据的起始地址和长度,内核会根据实际情况将数据封装在合适大小的数据包中,并放入发送队列中,对于 UDP 来说,用户需要自行将数据封装在 netbuf 结构中,当发送函数被调用的时候内核直接将数据包中的数据发送出去。
在 netbuf.c 中提供了几个操作 netbuf 的函数,如下表所示。
函数 | | | 申请一个 netbuf 空间,但是不会分配任何数据空间(不指向任何的 pbuf),真正的数据存储区域需要调用 netbuf_alloc()函数来分配。 | | 释放一个 netbuf 的内存,如果 netbuf 中的 p 指针上还记录有数据,则相应的pbuf 也会被释放掉。 | | 为 netbuf 结构分配指定大小的数据空间,数据空间是通过 pbuf 的形式表现的,函数返回成功分配的数据空间起始地址(pbuf 的 payload 指向的地址) | | 释放 netbuf 中的 p 指向的 pbuf 数据空间内存。 | | 和 netbuf_alloc 类似,不过只是分配一个 pbuf 首部结构(首部结构中包含协议的首部空间),pbuf 中的 payload 指向参数 dataptr,这种描述数据包的方式在静态数据包的发送时经常用到。 | | 将参数 tail 的 pbuf 连接到参数 head 的 pbuf 的后面,调用此函数后参数 tail 结构会被删除掉。 | | 将 netbuf 结构中的 ptr 指针记录的 pbuf 数据起始地址填入参数 dataptr 中,同时将该 pbuf 中的数据长度填入到参数 len 中。 | | 将 netbuf 结构的 ptr 指针指向 pbuf 链表中的下一个 pbuf 结构。 | | 将 netbuf 结构的 ptr 指针指向 pbuf 链表中的第一个 pbuf 结构。 |
2.2 netconn 连接结构
我们前面在使用 RAW 编程接口的时候,对于 UDP 和 TCP 连接使用的是两种不同的编程函数:udp_xxx 和 tcp_xxx。NETCONN 对于这两种连接提供了统一的编程接口,用于可以使用同一的连接结构和编程函数,在 api.h 中定了了 netcon 结构体,代码如下。
struct netconn
{
enum netconn_type type; // 连接类型,TCP UDP 或者 RAW
enum netconn_state state; // 当前连接状态
union { // 内核中与连接相关的控制块指针
struct ip_pcb *ip; // IP 控制块
struct tcp_pcb *tcp; // TCP 控制块
struct udp_pcb *udp; // UDP 控制块
struct raw_pcb *raw; // RAW 控制块
} pcb;
err_t last_err; // 此连接上最新的错误
sys_sem_t op_completed; // 用于两部分 API 同步的信号量
sys_mbox_t recvmbox; // 接收数据的邮箱
#if LWIP_TCP
sys_mbox_t acceptmbox; // 用于 TCP 服务器端,连接请求的缓冲队列
#endif
#if LWIP_SOCKET
int socket; // socket 描述符,用于 socket API
#endif
#if LWIP_SO_SNDTIMEO
s32_t send_timeout; // 发送数据时的超时时间
#endif
#if LWIP_SO_RCVTIMEO
int recv_timeout; // 接收数据时的超时时间
#endif
#if LWIP_SO_RCVBUF
int recv_bufsize; // 接收消息队列长度
s16_t recv_avail; // 数据邮箱 recvmbox 中已缓存的数据长度
#endif
u8_t flags; // 标识符
#if LWIP_TCP
// 当调用 netconn_write 发送数据但缓存不足的时候,数据会暂时存放在 current_msg 中,等待下一次数据发送,write_offset 记录下一次发送时的索引
size_t write_offset;
struct api_msg_msg *current_msg;
#endif
netconn_callback callback; // 连接相关回调函数,实现 socket API 时使用
};
在 api.h 文件中还定义了连接状态和连接类型,这两个都是枚举类型。
//枚举类型,用于描述连接类型
enum netconn_type {
NETCONN_INVALID = 0, //无效类型
NETCONN_TCP = 0x10, //TCP
NETCONN_UDP = 0x20, //UDP
NETCONN_UDPLITE = 0x21, //UDPLite
NETCONN_UDPNOCHKSUM= 0x22, //无校验 UDP
NETCONN_RAW = 0x40 //原始链接
};
//枚举类型,用于描述连接状态,主要用于 TCP 连接中
enum netconn_state
{
NETCONN_NONE, //不处于任何状态
NETCONN_WRITE, //正在发送数据
NETCONN_LISTEN, //侦听状态
NETCONN_CONNECT, //连接状态
NETCONN_CLOSE //关闭状态
};
2.3 netconn api 函数
在文件 api_lib.c 中实现了 NETCONN 的各个函数,在 api_lib.c 中有很多 netconn 的函数,其中大部分是 LWIP 内部调用的,我们最后使用到的有 9 个函数,如下表所示。
函数 | | | 这个函数其实就是一个宏定义,用来为新连接申请一个连接结构netconn 空间。 | | 该函数的功能是删除一个 netconn 连接结构。 | | 该函数用来获取一个 netconn 连接结构的源 IP 地址和源端口号或者目的 IP 地址和目的端口号。 | | 将一个连接结构与本地 IP 地址和端口号进行绑定。 | | 将一个连接结构与目的 IP 地址和端口号进行绑定。 | | 只能用在 UDP 连接结构中,用来断开与服务器的连接。 | | 这个函数就是一个宏定义,只在 TCP 服务器程序中使用,用来将连接结构 netconn 置为侦听状态。 | | 这个函数也只用与 TCP 服务器程序中,服务器调用此函数可以获得一个新的连接。 | | 从连接的 recvmbox 邮箱中接收数据包,可用于 TCP 连接,也可用于UDP 连接。 | | | | | | |
- netconn_new()函数是函数 netconn_new_with_proto_and_callback()的宏定义,此函数用来为新连接申请一个 netconn 空间,参数为新连接的类型,常用的值是 NETCONN_UDP 和 NETCONN_TCP,分别代表 UDP 连接和 TCP 连接。
- netconn_delete()函数用来删除一个 netconn 连接结构,如果函数调用时双方仍然处于连接状态,则相应连接将被关闭:对于 UDP 连接,连接立即被关闭,UDP 控制块被删除;对于 TCP连接,则函数执行主动关闭,内核完成剩余的断开握手过程。
- netconn_getaddr()函数用来获取一个 netconn 连接结构的源 IP 地址和源端口号或者目的 IP地址和目的端口号,IP 地址保存在 addr 中,端口信息保存在 port 中,参数 local 指明是获取源地址还是目的地址,当 local 为 1 时表示本地地址,此函数原型如下。
err_t netconn_getaddr(struct netconn *conn, ip_addr_t *addr,u16_t *port, u8_t local);
- netconn_bind()函数将一个连接结构与本地 IP 地址 addr 和端口号 port 进行绑定,服务器端程序必须执行这一步,服务器必须与指定的端口号绑定才能接受客户端的连接请求,函数原型如下。
err_t netconn_bind(struct netconn *conn, ip_addr_t *addr, u16_t port);
- netconn_connect()函数的功能是连接服务器,将指定的连接结构与目的 IP 地址 addr 和目的端口号 port 进行绑定,当作为 TCP 客户端程序时,调用此函数会产生握手过程,函数原型如下。
err_t netconn_connect(struct netconn *conn, ip_addr_t *addr, u16_t port);
- netconn_disconnect()函数只能使用在 UDP 连接中,功能是断开与服务器的连接。对于 UDP连接来说就是将 UDP 控制块中的 remote_ip 和 remote_port 字段值清零,函数原型如下。
err_t netconn_disconnect (struct netconn *conn);
- netconn_listen()函数只有在 TCP 服务器程序中使用,将一个连接结构 netconn 设置为侦听状态,既将 TCP 控制块的状态设置为 LISTEN 状态。
- netconn_accept()函数也只用于 TCP 服务器程序,服务器调用此函数可以从 acceptmbox 邮箱中获取一个新建立的连接,若邮箱为空,则函数会一直阻塞,直到新连接的到来。服务器端调用此函数前必须先调用 netconn_listen()函数将连接设置为侦听状态,函数原型如下。
err_t netconn_accept(struct netconn *conn, struct netconn **new_conn);
- netconn_recv()函数是从连接的 recvmbox 邮箱中接收数据包,可用于 TDP 连接,也可用于UDP 连接,函数会一直阻塞,直到从邮箱中获得数据消息,数据被封装在 netbuf 中。如果从邮箱中接收到一条空消息,表示对方已经关闭当前的连接,应用程序也应该关闭这个无效的连接,函数原型如下。
err_t netconn_recv(struct netconn *conn, struct netbuf **new_buf);
- netconn_send()函数用于在 UDP 连接上发送数据,参数 conn 指出了要操作的连接,参数 buf为要发送的数据,数据被封装在 netbuf 中。如果 IP 层分片功能未使能,则 netbuf 中的数据不能太长,不能超过 MTU 的值,最好不要超过 1000 字节。如果 IP 层分片功能使能的情况下就可以忽略此细节,函数原型如下。
err_t netconn_send(struct netconn *conn, struct netbuf *buf);
- netconn_write()函数用于在稳定的 TCP 连接上发送数据,参数 dataptr 和 size 分别指出了待发送数据的起始地址和长度,函数并不要求将数据封装在 netbuf 中,对于数据长度也没有限制,内核会直接处理这些数据,将他们封装在 pbuf 中,并挂接到 TCP 的发送队列中。
- netconn_close()函数用来关闭一个 TCP 连接,该函数会产生一个 FIN 握手包的发送,成功后函数便返回,而后剩余的断开握手操作由内核自动完成,用户程序不用关心,该函数只是断开一个连接,但不会删除连接结构 netconn,但需要我们调用 netconn_delete()函数来删除连接结构,否则会造成内存泄漏,函数原型如下。
err_t netconn_close(struct netconn *conn);
3. 编写NETCONN UDP实例
接下来,我们就可以在这个基础上,开始使用netconn尝试编写一个udp server实例。
3.1 开启NETCONN编程
3.2 在lwipopts.h添加以下代码。
// TCP/IP 线程名称
#define TCPIP_THREAD_NAME "TCP/IP"
// TCP/IP 线程栈大小
#define TCPIP_THREAD_STACKSIZE 1000
// TCP/IP 邮箱大小
#define TCPIP_MBOX_SIZE 5
// 默认 UDP 接收邮箱大小
#define DEFAULT_UDP_RECVMBOX_SIZE 2000
// 默认 TCP 接收邮箱大小
#define DEFAULT_TCP_RECVMBOX_SIZE 2000
// 默认接受邮箱大小
#define DEFAULT_ACCEPTMBOX_SIZE 2000
// 默认线程栈大小
#define DEFAULT_THREAD_STACKSIZE 500
// TCP/IP 线程优先级
#define TCPIP_THREAD_PRIO (8 - 2)
// LWIP 兼容互斥量
#define LWIP_COMPAT_MUTEX 1
// 允许 LWIP 兼容互斥量
#define LWIP_COMPAT_MUTEX_ALLOWED 0
// LWIP TCP/IP 核心锁定
#define LWIP_TCPIP_CORE_LOCKING 0
3.3 新建udpecho.c,实现udp的回显功能。
#include "FreeRTOS.h"
#include "lwip/opt.h"
#include "task.h"
#if LWIP_NETCONN
#include "lwip/api.h"
#include "lwip/sys.h"
#define UDPECHO_THREAD_PRIO ( tskIDLE_PRIORITY + 5 )
static struct netconn *conn;
static struct netbuf *buf;
static struct ip4_addr *addr;
static unsigned short port;
/*-----------------------------------------------------------------------------------*/
static void udpecho_thread(void *arg)
{
err_t err, recv_err;
LWIP_UNUSED_ARG(arg);
conn = netconn_new(NETCONN_UDP);
if (conn!= NULL)
{
err = netconn_bind(conn, IP_ADDR_ANY, 6000);
if (err == ERR_OK)
{
while (1)
{
recv_err = netconn_recv(conn, &buf);
if (recv_err == ERR_OK)
{
addr = netbuf_fromaddr(buf);
port = netbuf_fromport(buf);
netconn_connect(conn, addr, port);
buf->addr.addr = 0;
netconn_send(conn,buf);
netbuf_delete(buf);
}
}
}
else
{
netconn_delete(conn);
printf("can not bind netconn");
}
}
else
{
printf("can create new UDP netconn");
}
}
/*-----------------------------------------------------------------------------------*/
void udpecho_init(void)
{
sys_thread_new("udpecho_thread", udpecho_thread, NULL, DEFAULT_THREAD_STACKSIZE,UDPECHO_THREAD_PRIO );
}
#endif /* LWIP_NETCONN */
代码简单分析:
实现了一个简单的UDP回显服务器,使用FreeRTOS和LWIP协议栈。其主要功能是:
1. 创建UDP连接:在 udpecho_thread 函数中,创建一个新的UDP netconn。
2. 绑定端口:将该连接绑定到6000端口,监听来自任意IP地址的UDP数据包。
3. 接收数据:进入一个无限循环,调用 netconn_recv 来接收数据。
4. 回显数据:当收到数据包时,获取发送者的地址和端口,并使用 netconn_send 将数据包回传给发送者。
5. 内存管理:处理完数据后,删除 netbuf 以释放内存。
整体上,该代码实现了一个UDP回显服务,可以接收并回传数据,适用于网络通信测试。
3.4 编写main函数。
int main(void)
{
/* Init task */
xTaskCreate(Main_task, (const char *)"Main", configMINIMAL_STACK_SIZE * 2, NULL,MAIN_TASK_PRIO, NULL);
/* Start scheduler */
vTaskStartScheduler();
while(1);
}
void Main_task(void * pvParameters)
{
char LCDDisplayBuf[100] = {0};
struct ip4_addr DestIPaddr;
uint8_t flag = 0;
/* User config the different system Clock */
// UserRCMClockConfig();
// /* Configure SysTick */
// ConfigSysTick();
USART_Init();
/* Configures LED2 and LED3 */
APM_BOARD_LEDInit(LED2);
APM_BOARD_LEDInit(LED3);
/* KEY init*/
APM_BOARD_PBInit(BUTTON_KEY1, BUTTON_MODE_GPIO);
APM_BOARD_PBInit(BUTTON_KEY2, BUTTON_MODE_GPIO);
printf("This is a UDP NETCONN Demo! \r\n");
/* Configure ethernet (GPIOs, clocks, MAC, DMA) */
ConfigEthernet();
/* Initilaize the LwIP stack */
LwIP_Init();
#ifndef USE_DHCP
/* Use Com printf static IP address*/
sprintf(LCDDisplayBuf,"TINY board Static IP address \r\n");
printf("%s",LCDDisplayBuf);
sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d \r\n",
IP_ADDR0,
IP_ADDR1,
IP_ADDR2,
IP_ADDR3);
printf("%s",LCDDisplayBuf);
sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d \r\n",
NETMASK_ADDR0,
NETMASK_ADDR1,
NETMASK_ADDR2,
NETMASK_ADDR3);
printf("%s",LCDDisplayBuf);
sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d \r\n",
GW_ADDR0,
GW_ADDR1,
GW_ADDR2,
GW_ADDR3);
printf("%s",LCDDisplayBuf);
printf("Connect port: %d", COMP_PORT);
#endif
udpecho_init();
while(1)
{
/* check if any packet received */
if (ETH_CheckReceivedFrame())
{
/* process received ethernet packet */
LwIP_Pkt_Handle();
}
/* handle periodic timers for LwIP */
LwIP_Periodic_Handle(ETHTimer);
}
}
代码简单分析:
实现了一个基于FreeRTOS的UDP网络演示程序。主要功能包括:
1. 任务创建:在 main 函数中创建了一个名为 Main_task 的任务,并启动了调度器。
2. 初始化:在 Main_task 中,进行了多种硬件和网络的初始化,包括USART、LED、按键和以太网配置。
3. LwIP堆栈:初始化LwIP(轻量级IP)堆栈,用于网络通信。
4. 静态IP配置:如果未使用DHCP,代码会打印出静态IP地址、子网掩码和网关信息。
5. UDP回显功能:调用 udpecho_init() 来初始化UDP回显功能。
6. 主循环:在无限循环中,检查接收到的以太网帧,并处理它们,同时定期处理LwIP定时器。
整体而言,该程序为设备提供了基本的网络通信能力,允许接收和处理UDP数据包。
注意:这里一定要修改main.c中LWIP_Init()中的内存初始化函数。
使用操作系统时,LWIP的初始化需要通过tcpip_init来处理网络协议栈的线程安全和事件管理。这是因为在多任务环境中,LWIP的操作可能需要在特定的上下文中进行,以确保线程间的同步和资源的正确管理。而在没有操作系统的情况下,初始化可以直接在主线程中完成,避免了复杂性。
3.5 实验现象
使用网线连接PC和开发板后,使用网络调试助手,可达到回显的效果,如下图。
4. 总结
在本文中,我们深入探讨了NETCONN在LWIP下的实现,并成功将现有的LWIP例程移植到了FreeRTOS操作系统,实现了NETCONN UDP例程。
NETCONN编程相比于RAW编程更加高级层面,它提供了更抽象、更易用的接口,使网络编程更简单直观。NETCONN隐藏了底层协议细节,提供了更友好的API函数,使得建立连接和数据传输更加便捷。相比之下,RAW编程需要处理更多底层细节,如数据包构建和协议处理,需要更多的技术知识和精细操作。虽然NETCONN提供了便利,但有时可能会牺牲一些灵活性和性能优势,而RAW编程则更加灵活,可以更精细地控制数据流和处理过程。NETCONN适合快速开发和简单应用,RAW适合对网络细节有更深入了解和控制需求的人员。
以上就是本文全部内容,上文只介绍了部分关键代码的实现,详细实现代码可见附件。
附件:
1. LWIP移植FREERTOS Demo
LWIP_FREERTOS.zip
(6.66 MB)
2. NETCONN UDP Demo
NETCONN_UDP.zip
(6.67 MB)
|
内容充实,段落清晰,深入探讨了NETCONN在LWIP下的实现,并移植FreeRTOS操作系统,实现NETCONN UDP例程