最终我的项目要求是在freertos系统下面编写LWIP网口任务,将DDR中的大量原始数据传输到上位机当中。
前面两节都使用的是RAW API,在裸机环境下开发。
然后试了一下,freertos下面确实不能用RAW API了,会出现下面的报错。所以我打算用Socket API和UDP协议先在freertos下面编写发送数据的程序。
一、概念扫盲
1.服务端(Server)和客户端(client)
服务端通常是提供某种网络服务或资源的设备,而客户端则是请求这些服务或资源的设备。
(还没理解)
2.Socket API
是一种网络连接接口,和之前学习的RAW API非常类似,可以通过调用相应的函数去实现功能,比如bind 函数,send 发送函数等等。
但是他要在线程当中使用,感觉大概就是,单独给他创建一个任务,在这个任务里去完成这一系列任务。
所以现在我的任务是,就按照文章中的伪代码框架,还是先写发数据的程序
二、编程思路
1.整体思路
- 首先在主函数当中,创建一个main_thread主线程;
- 在主线程当中,初始化LWIP库,并且创建真正的网络数据传输线程network_thread;
- 然后配置默认的IP地址、网关和子网掩码;打印一些需要的提示信息;
- 然后启动UDP客户端函数(在这里我把开发板作为客户端,电脑是服务器端)
- 设置要连接到的IP地址和端口信息(也就是服务器端,也就是电脑)
- 创建套接字,用socket函数
- 通过connect函数将套接字与指定的目标地址(UDP服务器地址)进行连接
参考了伪代码框架和Xilinx已经集成好了的例程
void network_thread(void *p)
{
struct netif *netif, server_netif;
struct ip_addr ipaddr, netmask, gw;
//板子的MAC地址,每个PHY都不同
unsigned char mac_ethernet_address[] =
{0x00, 0x0a, 0x35, 0x00, 0x01, 0x02};
netif = &server_netif;
//初始化使用的IP地址
IP4_ADDR(&ipaddr,192,168,1,10);
IP4_ADDR(&netmask,255,255,255,0);
IP4_ADDR(&gw,192,168,1,1);
//把网络接口添加到netif_list, 并设为默认
if (!xemac_add(netif, &ipaddr, &netmask,
&gw, mac_ethernet_address, EMAC_BASEADDR)) {
printf(“Error adding N/W interface\n\r”);
return;
}
netif_set_default(netif);
netif_set_up(netif); //指定网络是否打开
//启动包接收线程
sys_thread_new(“xemacif_input_thread”, xemacif_input_thread,
netif, THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
//启动应用程序线程
sys_thread_new(“httpd” web_application_thread, 0,
THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
}
int main_thread()
{
//调用sys_thread_new前初始化lwIP
lwip_init();
//使用lwIP的所有线程都要用sys_thread_new()创建
sys_thread_new(“network_thread” network_thread,
NULL, THREAD_STACKSIZE DEFAULT_THREAD_PRIO);
return 0;
}
三、具体函数解析
1.创建线程
在使用LWIP所有线程都要用这个函数创建,其他的任务比如UART可以用task的那个函数
sys_thread_new("main_thread", (void(*)(void*))main_thread, 0,
THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
2.设置IP地址等信息
和之前那个函数很像,也相当于为了提升易读性,在前面用人们习惯的方式表达的IP地址信息,用这个函数转换成标准的网络格式
assign_default_ip(&(server_netif.ip_addr), &(server_netif.netmask),
&(server_netif.gw));
3.启动UDP客户端start_application
(1)创建套接字。
反正直接调用这个函数,属于封装好了的。第一个参数是表示使用IPv4地址还是什么,第二个参数表示用什么协议,SOCK_DGRAM是表示UDP,SOCK_STREAM表示TCP,第三个参数通常是指定特定协议的标志,但在这种情况下(UDP套接字),通常会使用0,表示使用默认的协议。
//宏定义
socket(domain,type,protocol)
//具体程序中用下面这句
sock[i] = socket(AF_INET, SOCK_DGRAM, 0)
(2)连接
通过connect函数将套接字与指定的目标地址(UDP服务器地址)进行连接。在UDP中,connect函数的调用并不会真正建立连接,而是为套接字关联一个特定的远程地址,以后所有通过该套接字发送的数据都将被发送到该远程地址。
第一个参数是选择套接字,第二个参数是目标地址的结构体指针,第三个参数是传入了 结构体的大小
//宏定义
connect(s,name,namelen)
//实际
connect(sock[i], (struct sockaddr *)&addr, sizeof(addr));
(3)传输transfer_data
实际上主要是利用udp_packet_send函数
因为例程里面有一些发送报告的程序,我暂时删掉简化了一下。只让他发送一个简单的hello world即可。实现的功能是每隔1s发送一个hello world.
static int udp_packet_send(u8_t finished) {
int i=0;
socklen_t len = sizeof(addr);
char *message = "hello world";
while (1)
{
lwip_sendto(sock[i], message, strlen(message), 0,
(struct sockaddr *)&addr, len);
sleep(1);
}
return 0;
}
/* Transmit data on a udp session */
int transfer_data(void)
{
if (udp_packet_send(!FINISH) < 0)
return FINISH;
return 0;
}
四、上板测试
打开网络助手,IP地址设置为:"192.168.1.100",端口号:5001
测试结果如下,可以正确实现功能。
(PS:这里我把左上角的端口号设置成了5001,没改端口号之前是没有接收到数据的,那意味着这个5001定义的是远程主机,也就是服务器端,也就是电脑的端口号)