当前位置: 首页 > news >正文

不用域名访问网站制作动画的网站

不用域名访问网站,制作动画的网站,网站建设有哪些公司,南宁网站优化推广目录 NETCONN 编程接口简介netbuf 数据缓冲区netconn 连接结构netconn 编程API 函数 NETCONN 编程接口UDP 实验NETCONN 实现UDPNETCONN 接口的UDP 实验硬件设计软件设计下载验证 NETCONN 接口编程TCP 客户端实验NETCONN 实现TCP 客户端连接步骤NETCONN 接口的TCPClient 实验硬件… 目录 NETCONN 编程接口简介netbuf 数据缓冲区netconn 连接结构netconn 编程API 函数 NETCONN 编程接口UDP 实验NETCONN 实现UDPNETCONN 接口的UDP 实验硬件设计软件设计下载验证 NETCONN 接口编程TCP 客户端实验NETCONN 实现TCP 客户端连接步骤NETCONN 接口的TCPClient 实验硬件设计软件设计下载验证 NETCONN 编程接口TCP 服务器实验NETCONN 实现TCP 服务器步骤NETCONN 接口的TCPServer 实验硬件设计软件设计下载验证 Socket 编程接口简介Socket 编程接口简介Socket API 函数 Socket 编程接口UDP 实验Socket 编程UDP 连接流程Socket 接口的UDP 实验下载验证 Socket 接口编程TCP 客户端实验Socket 编程TCP 客户端流程Socket 接口的TCPClient 实验 Socket 编程接口TCP 服务器实验Socket 编程TCP 服务器流程Socket 接口的TCPServer 实验硬件设计软件设计下载验证 NTP 协议实验NTP 简介NTP 实验硬件设计软件设计下载验证 lwIP 测试网速JPerf 网络测速工具JPerf 网络实验硬件设计软件设计下载验证 HTTP 客户端实验OneNTE 的 HTTP 配置HTTP 客户端实验硬件设计软件设计下载验证 HTTP 服务器实验HTTP 协议简介HTTP 服务器实验硬件设计下载验证 基于 MQTT 协议连接阿里云服务器MQTT 协议简介MQTT 协议实现原理移植 MQTT 协议配置远程服务器 阿里云MQTT 协议实验硬件设计下载验证 基于 MQTT 协议连接 OneNET 服务器配置OneNET 平台工程配置基于OneNET 平台MQTT 实验硬件设计下载验证 网络摄像头ATK-MC5640实验ATK-MC5640 简介SCCB 简介OV5640 DVP 接口说明OV5640 窗口设置说明OV5640 行像素输出时序介绍OV5640 自动对焦介绍 网络摄像头实验硬件设计软件设计下载验证 网络摄像头ATK-MC2640实验ATK-MC2640 简介SCCB 介绍OV2640 行像素输出时序介绍OV2640 帧时序介绍 网络摄像头实验硬件设计软件设计下载验证 NETCONN 编程接口简介 前几章的实验我们都没有使用操作系统因此它们采用的是RAW 编程接口RAW 编程 接口使得程序效率高但是需要对lwIP 有深入的了解而且不适合大数据量等场合。本章我 们就讲解一下NETCONN 编程接口使用NETCONN API 时需要有操作系统的支持我们使 用的是FreeRTOS 操作系统。 声明本章内容参考自《嵌入式网络那些事LWIP 协议深度剖析与实战演练》作者朱升 林由于该书是使用lwIP1.3.2 版本所以它和本教程的lwIP2.1.2 源码存在许多小差异。 netbuf 数据缓冲区 在前面笔者讲过了描述数据包的pbuf 结构这里我们就不做详细讲解了本章我们来讲 解一下另一个数据包的netbuf 结构netbuf 是NETCONN 编程API 接口使用的描述数据包的 结构我们可以使用netbuf 来管理发送数据、接收数据的缓冲区。有关netbuf 的详细描述在 netbuf.c 和netbuf.h 这两个文件中netbuf 是一个结构体它在netbuf.h 中定义了这个结构体 代码如下 /* 网络缓冲区包含数据和寻址信息*/ struct netbuf {/* p 字段的指针指向pbuf 链表*/struct pbuf *p, *ptr;ip_addr_t addr; /* 记录了数据发送方的IP 地址*/u16_t port; /* 记录了数据发送方的端口号*/ }; 从上述源码可知其中p 和ptr 都指向pbuf 链表不同的是p 一直指向pbuf 链表的第一 个pbuf 结构而ptr 可能指向链表中其他的位置lwIP 提供的netbuf_next 和netbuf_first 函数 都是操作ptr 字段的。addr 和port 字段用来记录数据发送方的IP 地址和端口号lwIP 提供的 netbuf_fromaddr 和netbuf_fromport 函数用于返回addr 和port 这两个字段。从上述的描述我 们可以知道netbuf 是用来管理发送数据和接收数据的缓冲区这些收发数据都存储在pbuf 数 据缓冲区当中所以netbuf 和pbuf 肯定有着某种联系下面我们根据上述的netbuf 结构体来 介绍一下netbuf 和pbuf 之间的关系如下图所示。 从上图可知netbuf 结构体中的p 指针永远指向pbuf 链表的第一个而ptr 可能指向链表 中其他的位置。 不管是TCP 连接还是UDP 连接接收到数据包后会将数据封装在一个netbuf 中然后将 这个netbuf 交给应用程序去处理。在数据发送时根据不同的连接有不同的处理对于TCP 连接用户只需要提供待发送数据的起始地址和长度内核会根据实际情况将数据封装在合适 大小的数据包中并放入发送队列中对于UDP 来说用户需要自行将数据封装在netbuf 结 构中当发送函数被调用的时候内核直接将数据包中的数据发送出去。 在netbuf.c 中提供了几个操作netbuf 的函数如下表所示。 注意用户使用NETCONN 编程API 接口时必须在lwipopts.h 把LWIP_NETCONN 配置 项设置为1 启动NETCONN 编程API 接口。 netconn 连接结构 我们前面在使用RAW 编程接口的时候对于UDP 和TCP 连接使用的是两种不同的编程 函数udp_xxx 和tcp_xxx。NETCONN 对于这两种连接提供了统一的编程接口用于使用同 一的连接结构和编程函数在api.h 中定了netcon 结构体代码如下。 /* netconn描述符*/ struct netconn {/* 连接类型TCP UDP或者RAW */enum netconn_type type;/* 当前连接状态*/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;/* 这个netconn 最后一个异步未报告的错误*/err_t pending_err; #if !LWIP_NETCONN_SEM_PER_THREAD/* 用于两部分API同步的信号量*/sys_sem_t op_completed; #endif/* 接收数据的邮箱*/sys_mbox_t recvmbox; #if LWIP_TCP/* 用于TCP服务器端连接请求的缓冲队列*/sys_mbox_t acceptmbox; #endif /* LWIP_TCP */ /* Socket描述符用于Socket API */ #if LWIP_SOCKETint Socket; #endif /* LWIP_SOCKET */ #if LWIP_SO_RCVTIMEO/* 接收数据时的超时时间*/u32_t recv_timeout; #endif /* LWIP_SO_RCVTIMEO *//* 标识符*/u8_t flags; #if LWIP_TCP/* TCP:当传递到netconn_write的数据不适合发送缓冲区时这将临时存储消息。也用于连接和关闭。*/struct api_msg *current_msg; #endif /* LWIP_TCP *//* 连接相关回调函数实现Socket API时使用*/netconn_callback callback; }; 在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 /* 关闭状态*/ }; 下面我们来结合一下netconn 的api 函数来讲解这个结构体的作用。 netconn 编程API 函数 本节我们就讲解一下NETCONN 编程的API 函数这些函数在api_lib.c 文件中实现的,其 中这个文件包含了很多netconn 接口的函数它们大部分是lwIP 内部调用的少部分是给用户 使用的用户能使用的函数如下表所示。 netconn_new 函数是函数netconn_new_with_proto_and_callback 的宏定义此函数用来为新 连接申请一个netconn 空间参数为新连接的类型连接类型在上一节已经讲过了常用的值 是NETCONN_UDP 和NETCONN_TCP分别代表UDP 连接和TCP 连接。该函数如下所示 #define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL) struct netconn * netconn_new_with_proto_and_callback(enum netconn_type t, u8_t proto,netconn_callback callback) {struct netconn *conn;API_MSG_VAR_DECLARE(msg);/* 第一步构建api_msg结构体*/API_MSG_VAR_ALLOC_RETURN_NULL(msg);/* 申请内存*/conn netconn_alloc(t, callback);if (conn ! NULL){err_t err;/* 连接协议*/API_MSG_VAR_REF(msg).msg.n.proto proto;/* 连接的信息*/API_MSG_VAR_REF(msg).conn conn;/* 构建API消息*/err netconn_apimsg(lwip_netconn_do_newconn, API_MSG_VAR_REF(msg));if (err ! ERR_OK) /* 构建失败*/{/* 释放信号量*/sys_sem_free(conn-op_completed);/* 释放邮箱*/sys_mbox_free(conn-recvmbox);/* 释放内存*/memp_free(MEMP_NETCONN, conn);API_MSG_VAR_FREE(msg);return NULL;}}API_MSG_VAR_FREE(msg);return conn; } 上述源码可知系统对conn 申请内存如果内存申请成功则系统构建API 消息。所谓 API 消息其实就是两个API 部分的交互的消息它是由用户的调用API 函数为起点使用IPC 通信机制告诉内核需要执行那个部分的API 函数API 消息的知识点笔者已经在第七章 7.4 小节讲解了下面笔者使用一个示意图来描述netconn_new 函数交互流程如下图所示 图15.3.1 用户的应用线程与内核交互示意图 相信大家对上图很熟悉吧没错这个图我们在第七章7.4.2 小节讲解过这些用户函数 就是以这个形式调用的。 netconn_delete 函数是用来删除一个netconn 连接结构如果函数调用时双方仍然处于连接 状态则相应连接将被关闭。其中对于UDP 连接它的连接会立即被关闭UDP 控制块被删 除对于TCP 连接该函数执行主动关闭内核完成剩余的断开握手过程该函数如下所示 err_t netconn_delete(struct netconn *conn) {err_t err;if (conn NULL){return ERR_OK;}/* 构建API消息*/err netconn_prepare_delete(conn);if (err ERR_OK){netconn_free(conn);}return err; } 此函数就是调用netconn_prepare_delete 函数构建API 消息API 构建流程请参考图15.3.1 的构建流程。 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, const ip_addr_t *addr, u16_t port); netconn_connect 函数的功能是连接服务器它将指定的连接结构与目的IP 地址addr 和目 的端口号port 进行绑定当作为TCP 客户端程序时调用此函数会产生握手过程该函数原 型如下。 err_t netconn_connect(struct netconn *conn, const 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 状态该函数原型如下 #define netconn_listen(conn) netconn_listen_with_backlog(conn, TCP_DEFAULT_LISTEN_BACKLOG) netconn_accept 函数也只用于TCP 服务器程序服务器调用此函数可以从acceptmbox 邮箱 中获取一个新建立的连接若邮箱为空则函数会一直阻塞直到新连接的到来。服务器端调 用此函数前必须先调用netconn_listen 函数将连接设置为侦听状态函数原型如下。 err_t netconn_accept(struct netconn *conn, struct netconn **new_conn); netconn_recv 函数是从连接的recvmbox 邮箱中接收数据包可用于TCP 连接也可用于 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); NETCONN 编程接口UDP 实验 本章我们开始学习NETCONN API 函数的使用本章实验中我们通过电脑端的网络调 试助手给开发板发送数据开发板接收数据并通过串口将接收到的数据发送到串口调试助手上 也可以通过按键从开发板向网络调试助手发送数据。 NETCONN 实现UDP 上一章节我们已经知道lwIP 的NETCONN 编程接口API 的使用方法本章实验就是调 用这些API 函数来实现UDP 连接实验。用户使用NETCONN 编程接口实现UDP 连接分以下 几个步骤 ①调用函数netconn_new 创建UDP 控制块。 ②定义时间超时函数。 ③调用函数netconn_bind 绑定本地IP 和端口。 ④调用函数netconn_connect 建立连接。 关于UDP 的基础知识我们在第十一章时候已经讲过了这里就不重复讲解。 NETCONN 接口的UDP 实验 硬件设计 例程功能 本实验使用NETCONN 编程接口实现UDP 连接我们可通过按下KEY0 按键发送数据至 网络调试助手还可以接收网络调试助手发送的数据并在LCD 显示屏上显示。 该实验的实验工程请参考《lwIP 例程7 lwIP_NETCONN_UDP 实验》。 软件设计 16.2.2.1 netconn 的UDP 连接步骤 创建UDP 控制块 调用函数netconn_new 创建NETCONN 的UDP 控制块。连接本地IP 地址和端口号 调用函数netconn_bind 绑定本地IP 地址和本地端口号注意IP 地址必须是有效的。绑定远程IP 地址与端口号 调用函数netconn_connect 绑定远程IP 地址和远程端口号。接收数据 netconn_recv 接收数据。发送数据 调用函数netconn_send 发送数据。 程序流程图 本实验的程序流程图如下图所示 图16.2.2.2.1 NETCONN 编程UDP 实验流程图 16.2.2.3 程序解析 在本章实验中我们最主要关注两个文件它们分别为lwip_demo.c 和lwip_demo.h 文件 lwip_demo.h 文件主要定义了端口号、数据标识位以及声明lwip_demo 函数所以这些不需要 我们去讲解这里我们主要看lwip_demo.c 文件的函数。在16.1 小节中笔者已经列出实现 UDP 实验的步骤在此基础上调用NETCONN 接口配置UDP 连接。 void lwip_demo(void) {err_t err;static struct netconn *udpconn;static struct netbuf *recvbuf;static struct netbuf *sentbuf;ip_addr_t destipaddr;uint32_t data_len 0;struct pbuf *q;/* 第一步创建udp控制块*/udpconn netconn_new(NETCONN_UDP);/* 定义接收超时时间*/udpconn-recv_timeout 10;if (udpconn ! NULL) /* 判断创建控制块释放成功*/{/* 第二步绑定控制块、本地IP和端口*/err netconn_bind(udpconn, IP_ADDR_ANY, UDP_DEMO_PORT);IP4_ADDR(destipaddr, DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3); /*构造目的IP地址*//* 第三步连接或者建立对话框*/netconn_connect(udpconn, destipaddr, LWIP_DEMO_PORT);if (err ERR_OK) /* 绑定完成*/{while (1){/* 第四步如果指定的按键按下时会发送信息*/if ((lwip_send_flag LWIP_SEND_DATA) LWIP_SEND_DATA){sentbuf netbuf_new();netbuf_alloc(sentbuf, strlen((char *)udp_demo_sendbuf));memcpy(sentbuf-p-payload, (void *)udp_demo_sendbuf, |strlen((char *)udp_demo_sendbuf));err netconn_send(udpconn, sentbuf);if (err ! ERR_OK){printf(发送失败\r\n);netbuf_delete(sentbuf); /* 删除buf */}lwip_send_flag ~LWIP_SEND_DATA; /* 清除数据发送标志*/netbuf_delete(sentbuf); /* 删除buf */}/* 第五步接收数据*/netconn_recv(udpconn, recvbuf);if (recvbuf ! NULL) /* 接收到数据*/{/*数据接收缓冲区清零*/memset(lwip_demo_recvbuf, 0, LWIP_DEMO_RX_BUFSIZE);/*遍历完整个pbuf链表*/for (q recvbuf-p; q ! NULL; q q-next){if (q-len (LWIP_DEMO_RX_BUFSIZE - data_len))memcpy(lwip_demo_recvbuf data_len, q-payload,(LWIP_DEMO_RX_BUFSIZE - data_len)); /* 拷贝数据*/elsememcpy(lwip_demo_recvbuf data_len,q-payload, q-len);data_len q-len;if (data_len LWIP_DEMO_RX_BUFSIZE)break;}data_len 0; /* 复制完成后data_len要清零*/printf(%s\r\n, lwip_demo_recvbuf); /* 打印接收到的数据*/netbuf_delete(recvbuf); /* 删除buf */}elsevTaskDelay(5); /* 延时5ms */}}elseprintf(UDP绑定失败\r\n);}elseprintf(UDP连接创建失败\r\n); } 可以看出笔者调用NETCONN 接口配置UDP 连接配置完成之后调用netconn_send 发 送数据到服务器当中当然我们也可以调用netconn_recv 函数接收服务器的数据。 下载验证 代码编译完成后下载到开发板中初始化完成之后我们来看一下LCD 显示的内容如下 图所示 图16.2.3.1 LCD 显示 我们在来看一下串口调试助手如图16.2.3.2 所示在串口调试助手上也输出了我们开发板 的IP 地址子网掩码、默认网关等信息。 图16.2.3.2 串口调试助手 我们通过网络调试助手发送数据到开发板当中结果如图16.2.3.3 所示当然我们可以通 过开发板上的KEY0 发送数据到网络调式助手当中如图16.2.3.4 所示 图16.2.3.3 LCD 显示 图16.2.3.4 网络调试助手接收数据 NETCONN 接口编程TCP 客户端实验 本章实验中开发板做TCP 客户端网络调试助手为TCP 服务器。开发板连接到TCP 服务器(网络调试助手)网络调试助手给开发板发送数据开发板接收数据并通过串口将接收到的数据发送到串口调试助手上也可以通过按键从开发板向网络调试助手发送数据。 NETCONN 实现TCP 客户端连接步骤 NETCONN 实现TCP 客户端连接有以下几步 ①调用函数netconn_new 创建TCP 控制块。②调用函数netconn_connect 连接服务器。③设置接收超时时间tcp_clientconn-recv_timeout。④调用函数netconn_getaddr 获取远端IP 地址和端口号。⑤调用函数netconn_write 和netconn_recv 收发数据。 至于TCP 协议的知识请读者擦看第十二章的内容。 NETCONN 接口的TCPClient 实验 硬件设计 例程功能 本实验使用NETCONN 编程接口实现TCPClient 连接我们可通过按下KEY0 按键发送数据至网络调试助手还可以接收网络调试助手发送的数据并在LCD 显示屏上显示。 该实验的实验工程请参考《lwIP 例程8 lwIP_NETCONN_TCPClient 实验》。 软件设计 17.2.2.1 netconn 的TCPClient 连接步骤 创建TCP 控制块 调用函数netconn_new 创建TCP 控制块。绑定远程IP 地址与端口号 调用函数netconn_connect 绑定远程IP 地址和远程端口号。接收数据 netconn_recv 接收数据。发送数据 调用函数netconn_write 发送数据。 17.2.2.2 程序流程图 本实验的程序流程图如下图所示 17.2.2.3 程序解析 打开我们的例程找到lwip_demo.c 和lwip_demo.h 两个文件这两个文件就是我本章实验的源码在lwip_demo.c 中我们实现了一个函数lwip_demo同上一章一样都有操作系统的支持下如下源码所示 void lwip_demo(void) {uint32_t data_len 0;struct pbuf * q;err_t err, recv_err;ip4_addr_t server_ipaddr, loca_ipaddr;static uint16_t server_port, loca_port;char * tbuf;server_port LWIP_DEMO_PORT;IP4_ADDR( server_ipaddr, DEST_IP_ADDR0, DEST_IP_ADDR1,DEST_IP_ADDR2, DEST_IP_ADDR3); /* 构造目的IP地址*/tbuf mymalloc(SRAMIN, 200); /* 申请内存*/sprintf((char * ) tbuf, Port:%d, LWIP_DEMO_PORT); /* 客户端端口号*/lcd_show_string(5, 150, 200, 16, 16, tbuf, BLUE);myfree(SRAMIN, tbuf);while (1) {tcp_clientconn netconn_new(NETCONN_TCP); /*创建一个TCP链接*//*连接服务器*/err netconn_connect(tcp_clientconn, server_ipaddr, server_port);if (err ! ERR_OK) {printf(接连失败\r\n);/*返回值不等于ERR_OK,删除tcp_clientconn连接*/netconn_delete(tcp_clientconn);} else if (err ERR_OK) /*处理新连接的数据*/ {struct netbuf * recvbuf;tcp_clientconn - recv_timeout 10;/*获取本地IP主机IP地址和端口号*/netconn_getaddr(tcp_clientconn, loca_ipaddr, loca_port, 1);printf(连接上服务器%d.%d.%d.%d,本机端口号为:%d\r\n,DEST_IP_ADDR0,DEST_IP_ADDR1,DEST_IP_ADDR2,DEST_IP_ADDR3, loca_port);while (1) {/*有数据要发送*/if ((tcp_client_flag LWIP_SEND_DATA) LWIP_SEND_DATA) {/* 发送tcp_server_sentbuf中的数据*/err netconn_write(tcp_clientconn, tcp_client_sendbuf,strlen((char * ) tcp_client_sendbuf), NETCONN_COPY);if (err ! ERR_OK) {printf(发送失败\r\n);}tcp_client_flag ~LWIP_SEND_DATA;}/*接收到数据*/if ((recv_err netconn_recv(tcp_clientconn, recvbuf)) ERR_OK) {taskENTER_CRITICAL(); /*进入临界区*//*数据接收缓冲区清零*/memset(lwip_demo_recvbuf, 0, LWIP_DEMO_RX_BUFSIZE);for (q recvbuf - p; q ! NULL; q q - next) /*遍历完整个pbuf链表*/ {if (q - len (LWIP_DEMO_RX_BUFSIZE - data_len)) {memcpy(lwip_demo_recvbuf data_len, q - payload, (LWIP_DEMO_RX_BUFSIZE - data_len)); /* 拷贝数据*/} else {memcpy(lwip_demo_recvbuf data_len, q - payload,q - len);}data_len q - len;if (data_len TCP_CLIENT_RX_BUFSIZE) {break; /*超出TCP客户端接收数组,跳出*/}}taskEXIT_CRITICAL(); /*退出临界区*/data_len 0; /*复制完成后data_len要清零*/printf(%s\r\n, lwip_demo_recvbuf);netbuf_delete(recvbuf);} else if (recv_err ERR_CLSD) /*关闭连接*/ {netconn_close(tcp_clientconn);netconn_delete(tcp_clientconn);printf(服务器%d.%d.%d.%d断开连接\r\n, DEST_IP_ADDR0,DEST_IP_ADDR1, DEST_IP_ADDR2, DEST_IP_ADDR3);lcd_fill(5, 89, lcddev.width, 110, WHITE);break;}}}} }上述的源码结构和上一章节的UDP 实验非常相似它们唯一不同的是连接步骤以及发送函数不同注意上述函数做了一个判断服务器与客户端的连接状态如果这个连接状态是断开状态则系统不断的调用函数netconn_connect 连接服务器直到连接成功才进入第二个while 循环执行发送接收工作。 下载验证 代码编译完成后下载到开发板中初始化完成之后我们来看一下LCD 显示的内容如下图所示。 我们在来看一下串口调试助手如图17.2.3.2 所示在串口调试助手上也输出了我们开发板的IP 地址子网掩码、默认网关等信息。 我们通过网络调试助手发送数据到开发板当中结果如图17.2.3.3 所示当然我们可以通过开发板上的KEY0 发送数据到网络调式助手当中如图17.2.3.4 所示 图17.2.3.3 LCD 显示 图17.2.3.4 网络调试助手接收数据 NETCONN 编程接口TCP 服务器实验 本章实验中开发板做TCP 服务器网络调试助手做客户端网络调试助手连接TCP 服务 器(开发板)。连接成功后网络调试助手可以给开发板发送数据开发板接收数据并通过串口将 接收到的数据发送到串口调试助手上也可以通过按键从开发板向网络调试助手发送数据。本 章分为如下几个部分 19.1 NETCONN 实现TCP 服务器 19.2 NETCONN 接口的TCPServer 实验 NETCONN 实现TCP 服务器步骤 NETCONN 实现TCP 服务器有以下几步 ①调用函数netconn_new 创建TCP 控制块。 ②调用函数netconn_bind 绑定TCP 控制块、本地IP 地址和端口号。 ③调用函数netconn_listen 进入监听模式。 ④设置接收超时时间conn-recv_timeout。 ⑤调用函数netconn_accept 接收连接请求。 ⑥调用函数netconn_getaddr 获取远端IP 地址和端口号。 ⑦调用函数netconn_write 和netconn_recv 收发数据。 至于TCP 协议的知识请大家参看第十二章的内容。 NETCONN 接口的TCPServer 实验 硬件设计 例程功能 本实验使用NETCONN 编程接口实现TCPServer 连接我们可通过按下KEY0 按键发送 数据至网络调试助手还可以接收网络调试助手发送的数据并在LCD 显示屏上显示。 该实验的实验工程请参考《lwIP 例程9 lwIP_NETCONN_TCPServer 实验》。 软件设计 18.2.2.1 netconn 的TCPServer 连接步骤 创建TCP 控制块 调用函数netconn_new ()创建TCP 控制块。绑定TCP 控制块、本地IP 地址和端口号 调用函数netconn_bind()绑定本地IP 地址和端口号。配置监听模式 调用函数netconn_listen ()进入监听模式。接收连接请求 调用函数netconn_accept()接收连接请求接收数据 调用函数netconn_recv ()接收数据。发送数据 调用函数netconn_write ()发送数据。 18.2.2.2 程序流程图 本实验的程序流程图如下图所示 程序解析 打开我们的例程找到lwip_demo.c 和lwip_demo.h 两个文件这两个文件就是我本章实 验的源码在lwip_demo.c 中我们实现了一个函数lwip_demo同上一章一样都有操作系统 的支持下如下源码所示 void lwip_demo(void) {uint32_t data_len 0;struct pbuf *q;err_t err, recv_err;uint8_t remot_addr[4];struct netconn *conn, *newconn;static ip_addr_t ipaddr;static u16_t port;/* 第一步创建一个TCP控制块*/conn netconn_new(NETCONN_TCP); /* 创建一个TCP链接*//* 第二步绑定TCP控制块、本地IP地址和端口号*/netconn_bind(conn, IP_ADDR_ANY, LWIP_DEMO_PORT); /* 绑定端口8088号端口*//* 第三步监听*/netconn_listen(conn); /* 进入监听模式*/conn-recv_timeout 10; /* 禁止阻塞线程等待10ms */while (1){/* 第四步接收连接请求*/err netconn_accept(conn, newconn); /* 接收连接请求*/if (err ERR_OK)newconn-recv_timeout 10;if (err ERR_OK) /* 处理新连接的数据*/{struct netbuf *recvbuf;netconn_getaddr(newconn, ipaddr, port, 0); /* 获取远端IP地址和端口号*/remot_addr[3] (uint8_t)(ipaddr.addr 24);remot_addr[2] (uint8_t)(ipaddr.addr 16);remot_addr[1] (uint8_t)(ipaddr.addr 8);remot_addr[0] (uint8_t)(ipaddr.addr);while (1){/*有数据要发送*/if ((lwip_send_flag LWIP_SEND_DATA) LWIP_SEND_DATA){err netconn_write(newconn, tcp_server_sendbuf,strlen((char *)tcp_server_sendbuf),NETCONN_COPY); /*发送tcp_server_sendbuf中的数据*/if (err ! ERR_OK){}lwip_send_flag ~LWIP_SEND_DATA;}/*接收到数据*/if ((recv_err netconn_recv(newconn, recvbuf)) ERR_OK){taskENTER_CRITICAL(); /*进入临界区*//*数据接收缓冲区清零*/memset(lwip_demo_recvbuf, 0, LWIP_DEMO_RX_BUFSIZE);for (q recvbuf-p; q ! NULL; q q-next) /*遍历完整个pbuf链表*/{if (q-len (LWIP_DEMO_RX_BUFSIZE - data_len))memcpy(lwip_demo_recvbuf data_len, q-payload,(LWIP_DEMO_RX_BUFSIZE - data_len)); /* 拷贝数据**/elsememcpy(lwip_demo_recvbuf data_len, q-payload,q-len);data_len q-len;/* 超出TCP客户端接收数组,跳出*/if (data_len LWIP_DEMO_RX_BUFSIZE)break;}taskEXIT_CRITICAL(); /* 退出临界区*/data_len 0; /* 复制完成后data_len要清零*/netbuf_delete(recvbuf);}else if (recv_err ERR_CLSD) /* 关闭连接*/{netconn_close(newconn);netconn_delete(newconn);break;}}}} } 上述的源码结构和上一章节的TCPClient 实验非常相似它们唯一不同的是连接步骤不同。 下载验证 代码编译完成后下载到开发板中初始化完成之后我们来看一下LCD 显示的内容如下 图所示。 图18.2.3.1 LCD 显示。 我们在来看一下串口调试助手如图18.2.3.2 所示在串口调试助手上也输出了我们开发板 的IP 地址子网掩码、默认网关等信息。 图18.2.3.2 串口调试助手 我们通过网络调试助手发送数据到开发板当中结果如图18.2.3.3 所示当然我们可以通 过开发板上的KEY0 发送数据到网络调式助手当中如图18.2.3.4 所示 图18.2.3.3 LCD 显示 图18.2.3.4 网络调试助手接收数据 Socket 编程接口简介 lwIP 作者为了能更大程度上方便开发者将其他平台上的网络应用程序移植到lwIP 上也 为了能让更多开发者快速上手lwIP他设计了第三种应用程序编程接口即Socket API但 是该接口受嵌入式处理器资源和性能的限制部分Socket 接口并未在lwIP 中完全实现。 Socket 编程接口简介 说到Socket我们不得不提起BSD SocketBSD Socket 是由加州伯克利大学为Unix 系统 开发出来的所以被称为伯克利套接字Internet Berkeley SocketsBSD Socket 是采用C 语 言进程间通信库的应用程序接口API允许不同主机或者同一个计算机上的不同进程之间 的通信支持多种I/O 设备和驱动具体的实现是依赖操作系统的。这种接口对于TCP/IP 是 必不可少的所以是互联网的基础技术之一所以LWIP 也是引入该程序编程接口虽然不能 完全实现BSD Socket但是对于开发者来说已经足够了。 在lwIP 抽象出来的Socket API 中lwIP 内核为用户提供了最多NUM_SOCKETS 个可使 用的Socket 描述符并定义了结构体lwip_socket对netconn 结构的封装和增强来描述一个 具体连接。内核定义了数组Sockets通过一个Socket 描述符就可以索引得到相应的连接结构 lwip_socket从而实现对连接的操作。连接结构lwip_socket 的数据结构实现源码如下 #define NUM_SOCKETS MEMP_NUM_NETCONN #ifndef SELWAIT_T #define SELWAIT_T u8_t #endif union lwip_sock_lastdata {struct netbuf *netbuf;struct pbuf *pbuf; }; /* 包含套接字使用的所有内部指针和状态*/ struct lwip_sock {/* 套接字目前构建在网络上每个套接字有一个netconn */struct netconn *conn;/* 读上一次读取中留下的数据*/union lwip_sock_lastdata lastdata; #if LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL/* 接收数据的次数由event_callback()设置通过接收和选择功能进行测试*/s16_t rcvevent;/* 数据被隔离的次数(发送缓冲区)由event_callback()设置测试选择*/u16_t sendevent;/* 这个套接字发生错误由event_callback()设置由select测试*/u16_t errevent;/* 使用select计算有多少线程正在等待这个套接字*/SELWAIT_T select_waiting; #endif /* LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL */ #if LWIP_NETCONN_FULLDUPLEX/* 多少线程使用struct lwip_sock(不是int)的计数器*/u8_t fd_used;/* 挂起的关闭/删除操作的状态*/u8_t fd_free_pending; #define LWIP_SOCK_FD_FREE_TCP 1 #define LWIP_SOCK_FD_FREE_FREE 2 #endif }; lwip_socket 结构是对连接结构netconn 的再次封装在内核中它对lwip_socket 的操作最 终都会映射到对netconn 结构的操作上。简单来讲Socket API 完全依赖netconn 接口实现的。 Socket API 函数 (1) socket 函数 该函数的原型如下源码所示 #define socket(domain,type,protocol) lwip_socket(domain,type,protocol)向内核申请一个套接字本质上该函数调用了函数lwip_socket该函数的参数如下表所 示 (2) bind 函数 该函数的原型如下源码所示 #define bind(s,name,namelen) lwip_bind(s,name,namelen) int bind(int s, const struct sockaddr *name, socklen_t namelen)该函数与netconn_bind 函数一样用于服务器端绑定套接字与网卡信息本质上就是对函数netconn_bind 再一次封装从上述源码可以知道参数name 指向一个sockaddr 结构体它包 含了本地IP 地址和端口号等信息参数namelen 指出结构体的长度。结构体sockaddr 定义如 下源码所示 struct sockaddr {u8_t sa_len; /* 长度*/sa_family_t sa_family; /* 协议簇*/char sa_data[14]; /* 连续的14 字节信息*/ }; struct sockaddr_in {u8_t sin_len; /* 长度*/u8_t sin_family; /* 协议簇*/u16_t sin_port; /* 端口号*/struct in_addr sin_addr; /* IP地址*/char sin_zero[8]; }可以看出lwIP 作者定义了两个结构体结构体sockaddr 中的sa_family 指向该套接字所 使用的协议簇本地IP 地址和端口号等信息在sa_data 数组里面定义这里暂未用到。由于 sa_data 以连续空间的方式存在所以用户要填写其中的IP 字段和端口port 字段这样会比较 麻烦因此lwIP 定义了另一个结构体sockaddr_in它与sockaddr 结构对等只是从中抽出IP 地址和端口号port方便于用于的编程操作。 (3) connect 函数 该函数与netconn 接口的netconn_connect 函数作用是一样的因此它是被netconn_connect 函数封装了该函数的作用是将Socket 与远程IP 地址和端口号绑定如果开发板作为客户端 通常使用这个函数来绑定服务器的IP 地址和端口号对于TCP 连接调用这个函数会使客户 端与服务器之间发生连接握手过程并建立稳定的连接如果是UDP 连接该函数调用不会 有任何数据包被发送只是在连接结构中记录下服务器的地址信息。当调用成功时函数返回 0否则返回-1。该函数的原型如下源码所示 #define connect(s,name,namelen) lwip_connect(s,name,namelen) int lwip_connect(int s, const struct sockaddr *name, socklen_t namelen);(4) listen 函数 该函数和netconn 的函数netconn_listen 作用一样它是由函数netconn_listen 封装得来的 内核同时接收到多个连接请求时需要对这些请求进行排队处理参数backlog 指明了该套接 字上连接请求队列的最大长度。当调用成功时函数返回0否则返回-1。该函数的原型如下 源码所示 #define listen(s,backlog) lwip_listen(s,backlog) int lwip_listen(int s, int backlog);注意该函数作用于TCP 服务器程序。 (5) accept 函数 该函数与netconn_accept 作用是一样的当接收到新连接后连接另一端客户端的地 址信息会被填入到地址结构addr 中而对应地址信息的长度被记录到addrlen 中。函数返回新 连接的套接字描述符若调用失败函数返回-1。该函数的原型如下源码所示 #define accept(s,addr,addrlen) lwip_accept(s,addr,addrlen) int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen);注意该函数作用于TCP 服务器程序。 (6) send()/sendto()函数 该函数是被netconn_send 封装的其作用是向另一端发送UDP 报文这两个函数的原型 如下源码所示 #define send(s,dataptr,size,flags) lwip_send(s,dataptr,size,flags) #define sendto(s,dataptr,size,flags,to,tolen) lwip_sendto(s,dataptr,size,flags,to,tolen) ssize_t lwip_send(int s, const void *dataptr, size_t size, int flags); ssize_t lwip_sendto(int s, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen);可以看出函数sendto 比函数send 多了两个参数该函数如下表所示 (7) write 函数 该函数用于在一条已经建立的连接上发送数据通常使用在TCP 程序中但在UDP 程序 中也能使用。该函数本质上是基于前面介绍的send 函数来实现的其参数的意义与send 也相 同。当函数调用成功时返回成功发送的字节数否则返回-1。 (8) read()/recv()/recvfrom()函数 函数recvfrom 和recv 用来从一个套接字中接收数据该函数可以在UDP 程序使用也可 在TCP 程序中使用。该函数本质上是被函数netconn_recv 的封装其参数与函数sendto 的参 数完全相似如表20.2.3 所示数据发送方的地址信息会被填写到from 中fromlen 指明了缓 存from 的长度mem 和len 分别记录了接收数据的缓存起始地址和缓存长度flags 指明用户 控制接收的方式通常设置为0。两个函数的原型如下源码所示 #define recv(s, mem, len, flags) lwip_recv(s, mem, len, flags) #define recvfrom(s, mem, len, flags, from, fromlen) lwip_recvfrom(s, mem, len, flags, from, fromlen) ssize_t lwip_readv(int s, const struct iovec *iov, int iovcnt); ssize_t lwip_recvfrom(int s, void *mem, size_t len, int flags,struct sockaddr *from, socklen_t *fromlen); #define read(s, mem, len) lwip_read(s, mem, len) ssize_t lwip_read(int s,void *mem,size_t len); (9) close 函数 函数close 作用是关闭套接字对应的套接字描述符不再有效与描述符对应的内核结构 lwip_Socket 也将被全部复位。该函数本质上是被netconn_delete 的封装对于TCP 连接来说 该函数将导致断开握手过程的发生。若调用成功该函数返回0否则返回-1。该函数的原型 如下源码所示 #define close(s) lwip_close(s) int lwip_close(int s);这些函数到底如何调用请大家看第十五章节的内容这里已经很详细讲解了 API消息如何发送到内核并调用相关的函数。 Socket 编程接口UDP 实验 对于lwIP 的Socket 的使用方式其实和文件操作相类似的。如果我们打开一个文件首先 打开-读/写-关闭在TCP/IP 网络通信中同样存在这些操作不过它使用的接口不是文件描述 符或者FILE*而是一个称做Socket 的描述符。对于Socket它也可以通过读、写、打开、 关闭操作来进行网络数据传送。同时还有一些辅助的函数如域名/IP 地址查询、Socket 功 能设置等。本章我们使用Scokrt 编程接口实现UDP 实验。本章分为如下几个部分 20.1 Socket 编程UDP 连接流程 20.2 Socket 接口的UDP 实验 Socket 编程UDP 连接流程 实现UDP 协议之前用户必须先配置结构体sockaddr_in 的成员变量才能实现UDP 连接 该配置步骤如下所示 ①sin_family 设置为AF_INET 表示IPv4 网络协议。 ②sin_port 为设置端口号笔者设置为8080。 ③sin_addr.s_addr 设置本地IP 地址。 ④调用函数Socket 创建Socket 连接注意该函数的第二个参数SOCK_STREAM 表 示TCP 连接SOCK_DGRAM 表示UDP 连接。 ⑤调用函数bind 将本地服务器地址与Socket 进行绑定。 ⑥调用收发函数接收或者发送。 Socket 接口的UDP 实验 20.2.1 硬件设计 例程功能 本实验使用Socket 编程接口实现UDP 服务器并可通过按键发送UDP 广播数据至其他 的UDP 客户端也能够接收其他UDP 客户端广播的数据并实时显示至LCD 屏幕上。 该实验的实验工程请参考《lwIP 例程10 lwIP_SOCKET_UDP 实验》。 20.2.2 软件设计 20.2.2.1 程序流程图 本实验的程序流程图如下图所示 程序解析 在本章节中我们最主要关注两个文件分别为lwip_demo.c 和lwip_demo.h 文件 lwip_demo.h 文件主要定义了发送标志位以及声明lwip_demo 函数这里比较简单我们不需要 去讲解主要看lwip_demo.c 文件的函数我们在lwip_demo 函数中配置相关UDP 参数以及 创建了一个发送数据线程lwip_send_thread这个发送线程就是调用scokec 函数发送数据到服 务器中下面我们分别地讲解以下lwip_demo 函数以及lwip_send_thread 任务如下源码所示 /*** brief 发送数据线程* param 无* retval 无*/ void lwip_data_send(void) {sys_thread_new(lwip_send_thread, lwip_send_thread, NULL,512, LWIP_SEND_THREAD_PRIO); } /*** brief lwip_demo实验入口* param 无* retval 无*/ void lwip_demo(void) {BaseType_t lwip_err;lwip_data_send(); /* 创建发送数据线程*/memset(local_info, 0, sizeof(struct sockaddr_in)); /* 将服务器地址清空*/local_info.sin_len sizeof(local_info);local_info.sin_family AF_INET; /* IPv4地址*/local_info.sin_port htons(LWIP_DEMO_PORT); /* 设置端口号*/local_info.sin_addr.s_addr htons(INADDR_ANY); /* 设置本地IP地址*/sock_fd Socket(AF_INET, SOCK_DGRAM, 0); /* 建立一个新的Socket连接*//* 建立绑定*/bind(sock_fd, (struct sockaddr *)local_info, sizeof(struct sockaddr_in));while (1){memset(lwip_demo_recvbuf, 0, sizeof(lwip_demo_recvbuf));recv(sock_fd, (void *)lwip_demo_recvbuf, sizeof(lwip_demo_recvbuf), 0);lwip_err xQueueSend(Display_Queue, lwip_demo_recvbuf, 0);if (lwip_err errQUEUE_FULL){printf(队列Key_Queue已满数据发送失败!\r\n);}} } /*** brief 发送数据线程函数* param pvParameters : 传入参数(未用到)* retval 无*/ void lwip_send_thread(void *pvParameters) {pvParameters pvParameters;local_info.sin_addr.s_addr inet_addr(IP_ADDR); /* 需要发送的远程IP地址*/while (1){if ((lwip_send_flag LWIP_SEND_DATA) LWIP_SEND_DATA){sendto(sock_fd, /* Socket */(char *)lwip_demo_sendbuf, /* 发送的数据*/sizeof(lwip_demo_sendbuf), 0, /* 发送的数据大小*/(struct sockaddr *)local_info, /* 接收端地址信息*/sizeof(local_info)); /* 接收端地址信息大小*/lwip_send_flag ~LWIP_SEND_DATA;}vTaskDelay(100);} } 从上述的源码可知笔者在lwip_demo 函数中调用lwip_data_send 函数创建 “lwip_send_thread”发送数据线程创建发送任务之后配置Socket 的UDP 协议这个配置流 程笔者已经在21.1 小节讲解过了这里无需重复讲解该函数的while()循环主要调用recv 函 数获取数据并使用消息队列发送。至于lwip_send_thread 发送数据数据线程函数非常简单它 主要判断发送标志位是否有效如果标志位有效则程序调用sendto 发送数据并设置标志位 无效。 注意函数recv 一般处于阻塞状态当然它可以设置为非阻塞。在lwip_send_thread 线程 中我们还需要执行“local.sin_addr.s_addr inet_addr(IP_addr)”这段代码因为我们必须知 道数据发送到哪里所以宏定义IP_addr 需要根据自己的远程IP 地址来设置。 下载验证 打开串口调试助手和网络调试助手注意必须查看PC 机上的IP 地址为多少才能确定程 序的宏定义IP_addr 数值如下源码所示 #define IP_addr 192.168.1.37 /* 远程IP */代码编译完成后下载到开发板中等待lwIP 一系列初始化和等待DHCP 分配IP 地址下 面我们来看一下LCD 显示的内容如图20.2.3.1 所示 我们在来看一下串口调试助手如图20.2.3.2 所示在串口调试助手上也输出了我们开发板 的IP 地址子网掩码、默认网关等信息。 图20.2.3.2 串口调试助手 我们通过网络调试助手发送数据到开发板当中结果如图20.2.3.3 所示当然我们可以通 过开发板上的KEY0 发送数据到网络调式助手当中如图20.2.3.4 所示 图20.2.3.3 LCD 显示 图20.2.3.4 网络调试助手接收数据 Socket 接口编程TCP 客户端实验 关于TCP 协议的相关知识请参考第12 章的内容。本章笔者重点讲解lwIP 的 Socket 接口如何配置TCP 客户端并在此基础上实现收发功能。 Socket 编程TCP 客户端流程 实现TCP 客户端之前用户必须先配置结构体sockaddr_in 的成员变量才能实现TCPClient 连接该配置步骤如下所示 ①sin_family 设置为AF_INET 表示IPv4 网络协议。 ②sin_port 为设置端口号。 ③sin_addr.s_addr 设置远程IP 地址。 ④调用函数Socket 创建Socket 连接注意该函数的第二个参数SOCK_STREAM 表示TCP 连接SOCK_DGRAM 表示UDP 连接。 ⑤调用函数connect 连接远程IP 地址。 ⑥调用收发函数实现远程通讯。 Socket 接口的TCPClient 实验 21.2.1 硬件设计 例程功能 本实验使用Socket 编程接口实现TCPClient 客户端并可通过按键向所连接的TCP 服务器发送数据也能够接收来自TCP 服务器的数据并实时显示至LCD 屏幕上。 该实验的实验工程请参考《lwIP 例程11 lwIP_SOCKET_TCPClient 实验》。 21.2.2 软件设计 21.2.2.1 程序流程图 本实验的程序流程图如下图所示 1.2.2.2 程序解析 本实验我们着重讲解lwip_demo.c 文件该文件实现了三个函数它们分别为lwip_data_send、lwip_demo 和lwip_send_thread 函数下面笔者分别地讲解它们的实现功能。 /*** brief 发送数据线程* param 无* retval 无*/ void lwip_data_send(void) {sys_thread_new(lwip_send_thread, lwip_send_thread, NULL,512, LWIP_SEND_THREAD_PRIO); }此函数调用sys_thread_new 函数创建发送数据线程它的线程函数为lwip_send_thread稍后我们重点会讲解。 /*** brief lwip_demo实验入口* param 无* retval 无*/ void lwip_demo(void) {struct sockaddr_in atk_client_addr;err_t err;int recv_data_len;BaseType_t lwip_err;char * tbuf;lwip_data_send(); /* 创建发送数据线程*/while (1) {sock_start: lwip_connect_state 0;atk_client_addr.sin_family AF_INET; /* 表示IPv4网络协议*/atk_client_addr.sin_port htons(LWIP_DEMO_PORT); /* 端口号*/atk_client_addr.sin_addr.s_addr inet_addr(IP_ADDR); /* 远程IP地址*/sock Socket(AF_INET, SOCK_STREAM, 0); /* 可靠数据流交付服务既是TCP协议*/memset( (atk_client_addr.sin_zero), 0,sizeof(atk_client_addr.sin_zero));tbuf mymalloc(SRAMIN, 200); /* 申请内存*/sprintf((char * ) tbuf, Port:%d, LWIP_DEMO_PORT); /* 客户端端口号*/lcd_show_string(5, 150, 200, 16, 16, tbuf, BLUE);/* 连接远程IP地址*/err connect(sock, (struct sockaddr * ) atk_client_addr,sizeof(struct sockaddr));if (err -1) {printf(连接失败\r\n);sock -1;closeSocket(sock);myfree(SRAMIN, tbuf);vTaskDelay(10);goto sock_start;}printf(连接成功\r\n);lwip_connect_state 1;while (1) {recv_data_len recv(sock, lwip_demo_recvbuf,LWIP_DEMO_RX_BUFSIZE, 0);if (recv_data_len 0) {closeSocket(sock);sock -1;lcd_fill(5, 89, lcddev.width, 110, WHITE);lcd_show_string(5, 90, 200, 16, 16, State:Disconnect, BLUE);myfree(SRAMIN, tbuf);goto sock_start;}/* 接收的数据*/lwip_err xQueueSend(Display_Queue, lwip_demo_recvbuf, 0);if (lwip_err errQUEUE_FULL) {printf(队列Key_Queue已满数据发送失败!\r\n);}vTaskDelay(10);}} }根据21.1 小节的流程配置server_addr 结构体的字段配置完成之后调用connect 连接远程服务器接着调用recv 函数接收客户端的数据并且把数据以消息的方式发送至其他线程当中。 /*** brief 发送数据线程函数* param pvParameters : 传入参数(未用到)* retval 无*/ void lwip_send_thread(void * pvParameters) {pvParameters pvParameters;err_t err;while (1) {while (1) {if (((lwip_send_flag LWIP_SEND_DATA) LWIP_SEND_DATA) (lwip_connect_state 1)) /* 有数据要发送*/ {err write(sock, lwip_demo_sendbuf, sizeof(lwip_demo_sendbuf));if (err 0) {break;}lwip_send_flag ~LWIP_SEND_DATA;}vTaskDelay(10);}closeSocket(sock);} }此线程函数非常简单它主要判断lwip_send_flag 变量的状态若该变量的状态为发送状态则程序调用write 函数发送数据并且清除lwip_send_flag 变量的状态。 21.2.3 下载验证 初始化完成之后LCD 显示以下信息如下图所示 我们通过网络调试助手发送数据至开发板开发板接收完成之后LCD 在指定位置显示接收的数据如下图所示 当然读者可通过KEY0 按键发送数据至网络调试助手。 Socket 编程接口TCP 服务器实验 关于TCP 协议的相关知识请参考第12 章的内容。本章笔者重点讲解lwIP 的Socket 接口如何配置TCP 服务器并在此基础上实现收发功能。本章分为如下几个部分 22.1 Socket 编程TCP 服务器流程 22.2 Socket 接口的TCPServer 实验 Socket 编程TCP 服务器流程 实现TCP 服务器之前用户必须先配置结构体sockaddr_in 的成员变量才能实现 TCPServer 连接该配置步骤如下所示 ①sin_family 设置为AF_INET 表示IPv4 网络协议。 ②sin_port 为设置端口号。 ③sin_addr.s_addr 设置本地IP 地址。 ④调用函数Socket 创建Socket 连接注意该函数的第二个参数SOCK_STREAM 表 示TCP 连接SOCK_DGRAM 表示UDP 连接。 ⑤调用函数bind 绑定本地IP 地址和端口号。 ⑥调用函数listen 监听连接请求。 ⑦调用函数accept 监听连接。 ⑧调用收发函数进行通讯。 上述的步骤就是Socket 编程接口配置TCPServer 的流程。 Socket 接口的TCPServer 实验 硬件设计 例程功能 本实验使用Socket 编程接口实现TCP 服务器并可通过按键向连接的TCP 客户端发送数 据也能够接收来自TCP 客户端的数据并实时显示至LCD 屏幕上。 该实验的实验工程请参考《lwIP 例程12 lwIP_SOCKET_TCPServer 实验》。 软件设计 22.2.2.1 程序流程图 本实验的程序流程图如下图所示 程序解析 本实验我们着重讲解lwip_demo.c 文件该文件实现了三个函数它们分别为 lwip_data_send、lwip_demo 和lwip_send_thread 函数下面笔者分别地讲解它们的实现功能。 /*** brief 发送数据线程* param 无* retval 无*/ void lwip_data_send(void) {sys_thread_new(lwip_send_thread, lwip_send_thread, NULL,512, LWIP_SEND_THREAD_PRIO); } 此函数调用sys_thread_new 函数创建发送数据线程它的线程函数为lwip_send_thread 稍后我们重点会讲解。 /*** brief lwip_demo实验入口* param 无* retval 无*/ void lwip_demo() {struct sockaddr_in server_addr; /* 服务器地址*/struct sockaddr_in conn_addr; /* 连接地址*/socklen_t addr_len; /* 地址长度*/int err;int length;int sock_fd;char *tbuf;BaseType_t lwip_err;lwip_data_send(); /* 创建一个发送线程*/sock_fd Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);memset(server_addr, 0, sizeof(server_addr)); /* 将服务器地址清空*/server_addr.sin_family AF_INET; /* 地址家族*/server_addr.sin_addr.s_addr htonl(INADDR_ANY); /* 注意转化为网络字节序*//* 使用SERVER_PORT指定为程序头设定的端口号*/server_addr.sin_port htons(LWIP_DEMO_PORT);tbuf mymalloc(SRAMIN, 200); /* 申请内存*/sprintf((char *)tbuf, Port:%d, LWIP_DEMO_PORT); /* 客户端端口号*/lcd_show_string(5, 150, 200, 16, 16, tbuf, BLUE);/* 建立绑定*/err bind(sock_fd, (struct sockaddr *)server_addr, sizeof(server_addr));if (err 0) /* 如果绑定失败则关闭套接字*/{closeSocket(sock_fd); /* 关闭套接字*/myfree(SRAMIN, tbuf);}err listen(sock_fd, 4); /* 监听连接请求*/if (err 0) /* 如果监听失败则关闭套接字*/{closeSocket(sock_fd); /* 关闭套接字*/}while (1){lwip_connect_state 0;addr_len sizeof(struct sockaddr_in); /* 将链接地址赋值给addr_len *//* 对监听到的请求进行连接状态赋值给sock_conn */sock_conn accept(sock_fd, (struct sockaddr *)conn_addr, addr_len);if (sock_conn 0) /* 状态小于0代表连接故障此时关闭套接字*/{closeSocket(sock_fd);}else{lwip_connect_state 1;}while (1){length recv(sock_conn, (unsigned int *)lwip_demo_recvbuf,sizeof(lwip_demo_recvbuf), 0);if (length 0){goto atk_exit;}lwip_err xQueueSend(Display_Queue, lwip_demo_recvbuf, 0);if (lwip_err errQUEUE_FULL){printf(队列Key_Queue已满数据发送失败!\r\n);}}atk_exit:if (sock_conn 0){closeSocket(sock_conn);sock_conn -1;lcd_fill(5, 89, lcddev.width, 110, WHITE);lcd_show_string(5, 90, 200, 16, 16, State:Disconnect, BLUE);myfree(SRAMIN, tbuf);}} } 根据22.1 小节的流程配置server_addr 结构体的字段配置完成之后调用listen 和accept 监 听客户端连接请求接着调用recv 函数接收客户端的数据并且把数据以消息的方式发送至 其他线程当中。 /*** brief 发送数据线程函数* param pvParameters : 传入参数(未用到)* retval 无*/ static void lwip_send_thread(void *pvParameters) {pvParameters pvParameters;while (1){if (((lwip_send_flag LWIP_SEND_DATA) LWIP_SEND_DATA) (lwip_connect_state 1)) /* 有数据要发送*/{/* 发送数据*/send(sock_conn, lwip_demo_sendbuf, sizeof(lwip_demo_sendbuf), 0);lwip_send_flag ~LWIP_SEND_DATA;}vTaskDelay(10);} } 此线程函数非常简单它主要判断lwip_send_flag 变量的状态若该变量的状态为发送状 态则程序调用send 函数发送数据并且清除lwip_send_flag 变量的状态。 下载验证 初始化完成之后LCD 显示以下信息如下图所示 我们通过网络调试助手发送数据至开发板开发板接收完成之后LCD 在指定位置显示接 收的数据如下图所示 图22.2.3.2 LCD 显示 当然读者可通过KEY0 按键发送数据至网络调试助手。 NTP 协议实验 NTPNetwork Time Protocol网络时间协议基于UDP用于网络时间同步的协议使网 络中的计算机时钟同步到UTC再配合各个时区的偏移调整就能实现精准同步对时功能。本 章在开发板上使用UDP 协议连接阿里云的NTP 服务器向这个服务器发送NTP 报文来获取 实时时间。 NTP 简介 NTP 服务器Network Time ProtocolNTP是用来使计算机时间同步化的一种协议它 可以使计算机对其服务器或时钟源如石英钟GPS 等等做同步化它可以提供高精准度 的时间校正LAN 上与标准间差小于1 毫秒WAN 上几十毫秒且可介由加密确认的方式 来防止恶毒的协议攻击。时间按NTP 服务器的等级传播。按照离外部UTC 源的远近把所有服 务器归入不同的Stratum层中。 NTP 数据报文格式如下图所示。 NTP 数据报文格式的各个字段的作用如下表所示 从上表可知NTP 报文的字段非常多这些字段并不是每一个都必须设置的请大家根 据项目的需要来构建NTP 请求报文。下面笔者使用网络调式助手制作一个简单的NTP 实验 如下图所示 图23.1.2 获取阿里云NTP 数据 上图中笔者使用网络调试助手以UDP 协议连接阿里云NTP 服务器接着在发送框上填 入NTP 请求报文发送完成之后网络调试助手接收到一段数据这里我们只取第40 位到43 位的十六进制数值该数值就是当前时间的总秒数。我们把总秒数转换成十进制并且在在线 转换器https://tool.lu/timestamp/上计算当前时间如下图所示 从上面的内容可知我们知道获取NTP 实时时间需要哪些步骤了这些步骤如下所示 ①以UDP 协议连接阿里云NTP 服务器。 ②发送NTP 报文到阿里云NTP 服务器。 ③获取阿里云NTP 服务器返回的数据取第40 位到43 位的十六进制数值。 ④把40 位到43 位的十六进制数值转成十进制。 ⑤把十进制数值减去1900-1970 的时间差2208988800 秒。 ⑥数值转成年月日时分秒。 NTP 实验 硬件设计 例程功能 使用UDP 协议连接阿里云的NTP 服务器并周期发送NTP 请求报文发送完成之后对 阿里云NTP 服务器返回的数据进行解析把它转换成实时时间信息。 该实验的实验工程请参考《lwIP 例程13 lwIP_ntp 实验》和《lwIP 例程14 lwIP_sntp 实 验》。 软件设计 23.2.2.1 程序流程图 本实验的程序流程图如下图所示。 程序解析 为了描述NTP 报文结构的字段笔者在lwip_demo.h 文件下定义了NPTformat 结构体 它用来描述NTP 报文结构体的各个字段该结构体如下所示 typedef struct _NPTformat {char version; /* 版本号*/char leap; /* 时钟同步*/char mode; /* 模式*/char stratum; /* 系统时钟的层数*/char poll; /* 更新间隔*/signed char precision; /* 精密度*/unsigned int rootdelay; /* 本地到主参考时钟源的往返时间*/unsigned int rootdisp; /* 统时钟相对于主参考时钟的最大误差*/char refid; /* 参考识别码*/unsigned long long reftime; /* 参考时间*/unsigned long long org; /* 开始的时间戳*/unsigned long long rec; /* 收到的时间戳*/unsigned long long xmt; /* 传输时间戳*/ } NPTformat; 该结构体的成员变量与表23.1.1 的NTP 报文结构体的字段是一一对应的。 打开lwip_demo.c 文件在此文件下定义了四个函数这些函数的作用如下表所示 (1) lwip_ntp_client_init 函数 此函数用来构建NTP 请求报文通过设置NPTformat 结构体的成员变量来描述NTP 报文 的字段信息构建完成之后把该报文存储在缓冲区当中。构建NTP 报文的源码如下所示 /***brief 初始化NTP Client信息*param 无*retval 无*/ void lwip_ntp_client_init(void) {uint8_t flag;g_ntpformat.leap 0; /* 时钟同步*/g_ntpformat.version 3; /* 版本号*/g_ntpformat.mode 3; /* 模式*/g_ntpformat.stratum 0; /* 系统时钟的层数*/g_ntpformat.poll 0; /* 更新间隔*/g_ntpformat.precision 0; /* 精密度*/g_ntpformat.rootdelay 0; /* 本地到主参考时钟源的往返时间*/g_ntpformat.rootdisp 0; /* 统时钟相对于主参考时钟的最大误差*/g_ntpformat.refid 0; /* 参考识别码*/g_ntpformat.reftime 0; /* 参考时间*/g_ntpformat.org 0; /* 开始的时间戳*/g_ntpformat.rec 0; /* 收到的时间戳*/g_ntpformat.xmt 0; /* 传输时间戳*/flag (g_ntpformat.version 3) g_ntpformat.mode;memcpy(g_ntp_message, (void const *)(flag), 1); } 可以看到笔者只设置NTP 报文的版本和模式字段其他字段我们设置为0。 (2) lwip_get_seconds_from_ntp_server 函数 此函数用来获取NTP 服务器返回的数据从这个数据截取40~43 位的数值并且强制转 换成十进制数值最后递交给其他函数处理。 /***brief 从NTP服务器获取时间*param buf存放缓存*param idx定义存放数据起始位置*retval 无*/ void lwip_get_seconds_from_ntp_server(uint8_t *buf, uint16_t idx) {unsigned long long atk_seconds 0;uint8_t i 0;for (i 0; i 4; i) /* 获取40~43位的数据*/{/* 把40~43位转成16进制再转成十进制*/atk_seconds (atk_seconds 8) | buf[idx i];}/* 减去减去1900-1970的时间差2208988800秒*/atk_seconds - NTP_TIMESTAMP_DELTA;lwip_calc_date_time(atk_seconds); /* 由UTC时间计算日期*/ } 调用此函数时该函数的idx 形参为40经过for 语句的作用可在数据中截取40~43 位 的数值截取完成之后强制转换成十进制并减去1900-1970 的时间差最后由lwip_calc_date_t ime 函数计算时间。 (3) lwip_calc_date_time 函数 此函数是把总秒数转换成时间信息该函数的源码如下所示 /***brief 计算日期时间*param seconds UTC 世界标准时间*retval 无*/ void lwip_calc_date_time(unsigned long long time) {unsigned int Pass4year;int hours_per_year;if (time 0){time 0;}nowdate.second (int)(time % 60); /* 取秒时间*/time / 60;nowdate.minute (int)(time % 60); /* 取分钟时间*/time / 60;nowdate.hour (int)(time % 24); /* 小时数*//* 取过去多少个四年每四年有1461*24 小时*/Pass4year ((unsigned int)time / (1461L * 24L));nowdate.year (Pass4year 2) 1970; /* 计算年份*/time % 1461 * 24; /* 四年中剩下的小时数*/for (;;) /* 校正闰年影响的年份计算一年中剩下的小时数*/{hours_per_year 365 * 24; /* 一年的小时数*/if ((nowdate.year 3) 0) /* 判断闰年*/{hours_per_year 24; /* 是闰年一年则多24小时即一天*/}if (time hours_per_year){break;}nowdate.year;time - hours_per_year;}time / 24; /* 一年中剩下的天数*/time; /* 假定为闰年*/if ((nowdate.year 3) 0) /* 校正闰年的误差计算月份日期*/{if (time 60){time--;}else{if (time 60){nowdate.month 1;nowdate.day 29;return;}}}/* 计算月日*/for (nowdate.month 0; Days[nowdate.month] time; nowdate.month){time - Days[nowdate.month];}nowdate.day (int)(time);return; } 总秒数经过算法的处理计算得出的年、月、‘时、分和秒都保存在DateTime 结构体当中。 (40) lwip_demo 函数 此函数调用lwIP 相关的API 接口以UDP 协议连接阿里云NTP 服务器连接完成之后 开启定时器定时发送NTP 请求报文最后处理NTP 服务器返回的数据。 /*** brief lwip_demo程序入口* param 无* retval 无*/ static void lwip_demo(void) {err_t err;static struct netconn *udpconn;static struct netbuf *recvbuf;static struct netbuf *sentbuf;ip_addr_t destipaddr;uint32_t data_len 0;struct pbuf *q;atk_ntp_client_init();/* 第一步创建udp控制块*/udpconn netconn_new(NETCONN_UDP);/* 定义接收超时时间*/udpconn-recv_timeout 10;if (udpconn ! NULL) /* 判断创建控制块释放成功*/{/* 第二步绑定控制块、本地IP和端口*/err netconn_bind(udpconn, IP_ADDR_ANY, NTP_DEMO_PORT);/* 域名解析*/netconn_gethostbyname((char *)(HOST_NAME), (destipaddr));/* 第三步连接或者建立对话框*/netconn_connect(udpconn, destipaddr, NTP_DEMO_PORT); /* 连接到远端主机*/if (err ERR_OK) /* 绑定完成*/{while (1){sentbuf netbuf_new();netbuf_alloc(sentbuf, 48);memcpy(sentbuf-p-payload, (void *)ntp_message,sizeof(ntp_message));err netconn_send(udpconn, sentbuf);if (err ! ERR_OK){printf(发送失败\r\n);netbuf_delete(sentbuf); /* 删除buf */}netbuf_delete(sentbuf); /* 删除buf *//* 第五步接收数据*/netconn_recv(udpconn, recvbuf);vTaskDelay(1000); /* 延时1s */if (recvbuf ! NULL) /* 接收到数据*/{/* 数据接收缓冲区清零*/memset(ntp_demo_recvbuf, 0, NTP_DEMO_RX_BUFSIZE);/* 遍历完整个pbuf链表*/for (q recvbuf-p; q ! NULL; q q-next){/* 判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间如果大于的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据否则的话就拷贝所有的数据*/if (q-len (NTP_DEMO_RX_BUFSIZE - data_len))/* 拷贝数据*/memcpy(ntp_demo_recvbuf data_len, q-payload,(NTP_DEMO_RX_BUFSIZE - data_len));elsememcpy(ntp_demo_recvbuf data_len,q-payload, q-len);data_len q-len;/* 超出TCP客户端接收数组,跳出*/if (data_len NTP_DEMO_RX_BUFSIZE)break;}data_len 0; /* 复制完成后data_len要清零*//*从NTP服务器获取时间*/atk_get_seconds_from_ntp_server(ntp_demo_recvbuf, 40);printf(北京时间%02d-%02d-%02d %02d:%02d:%02d\r\n,nowdate.year,nowdate.month 1,nowdate.day,nowdate.hour 8,nowdate.minute,nowdate.second);sprintf((char *)lwip_time_buf,Beijing time:%02d-%02d-%02d %02d:%02d:%02d,nowdate.year,nowdate.month 1,nowdate.day,nowdate.hour 8,nowdate.minute,nowdate.second);lcd_show_string(5, 170, lcddev.width, 16, 16,(char *)lwip_time_buf, RED);netbuf_delete(recvbuf); /* 删除buf */}elsevTaskDelay(5); /* 延时5ms */}}elseprintf(NTP绑定失败\r\n);}elseprintf(NTP连接创建失败\r\n); } 下载验证 编译代码并下载到开发板中打开串口调式助手查看当前时间如下图所示。 lwIP 测试网速 我们为什么测试网速呢原因很简单在我们开发时候有一些特殊的原因导致掉包、堵 塞、延迟抖动等情况一般都是发送和接收速率的问题如果网速偏低或者达不到PHY 芯片 的最大网速则开发过程中会遇到很多的问题。 JPerf 网络测速工具 JPerf 网络测速工具是一个跨平台的网络性能测试工具它支持Win/Linux/Mac/Android/iO S 等多个平台它也可以测试最大TCP 和UDP 带宽性能具有多种参数和UDP 特性可以根 据需要调整可以报告带宽、延迟抖动和数据包丢失该软件下载地址是https://iperf.fr/iperf -download.php。 下载完成之后打开该软件可以看到该软件划分为几个区域这些区域的作用如下所示 Iperf 命令行无法直接输入 服务端设置 (1) Listen Port 监听端口。 (2) client limit:客户端限制仅允许指定客户端连接。 (3) Num Connections最大允许连接的数量为0 不限制。 客户端设置 (1) Server address 表示服务器地址。 (2) Port 表示端口。 (3) Parallel Streams 表示并发流。 应用层设置 (1) Enable Compatibilitu Mode 兼容旧版本当server 端和client 端版本不一样时使用。 (2) Transmit 设置测试模式传输字节总量大小10Bytes 或者按传输时间总长度10 秒。 (3) Dual 复选框勾上表示同时进行双向传输测试。 (4) Trade 复选框勾上表示单独进行双向传输测试先测c 到s 的带宽。 (5) Representative File 表示指定需要传输的文件。 (6) Print MSS 表示显示tcp 最大mtu 值。 传输层设置 (1) TCP 协议-设置缓冲区大小。 (2) TCP 协议-指定TCP 窗口大小。 (3) TCP 协议-设定TCP 数据包的最大mtu 值。 (4) TCP 协议-设定TCP 不延时。 (5) UDP 协议-设置UDP 最大带宽。 (6) UDP 协议-设置UDP 缓冲区。 (7) UDP 协议-UDP 包封装大小默认1470 byte。 IP 层设置: (1) TTL 表示指定ttl 值。 (2) Type of Service 表示服务类型Type of ServiceToS大小范围从0x10 (最小延迟) 到0x2 (最少费用)在使用802.11e 来控制服务质量的WLAN 中ToS 是映射在Wi-Fi 多媒体(WMM)存取范畴的。 网速显示窗口折线图的形式显示 网速相关数据输出窗口以文本的形式输出 开始和停止JPerf JPerf 网络实验 硬件设计 例程功能 本实验的目标是利用软件JPerf 测试PHY 的网速。 该实验的实验工程请参考《lwIP 例程15 lwIP 测试接收速度实验》。 软件设计 24.2.2.1 程序解析 测试开发板收发速度的代码很简单只需要移植lwip-2.1.2\src\apps\lwiperf 的文件到工程 中接着在lwip_demo.c 文件下添加以下源码如下所示 /* 报告状态*/ const char *report_type_str[] {TCP_DONE_SERVER, /* LWIPERF_TCP_DONE_SERVER*/TCP_DONE_CLIENT, /* LWIPERF_TCP_DONE_CLIENT*/TCP_ABORTED_LOCAL, /* LWIPERF_TCP_ABORTED_LOCAL */TCP_ABORTED_LOCAL_DATAERROR, /*LWIPERF_TCP_ABORTED_LOCAL_DATAERROR*/TCP_ABORTED_LOCAL_TXERROR, /* LWIPERF_TCP_ABORTED_LOCAL_TXERROR */TCP_ABORTED_REMOTE, /* LWIPERF_TCP_ABORTED_REMOTE */UDP_STARTED, /* LWIPERF_UDP_STARTED,*/UDP_DONE, /* LWIPERF_UDP_DONE */UDP_ABORTED_LOCAL, /* LWIPERF_UDP_ABORTED_LOCAL*/UDP_ABORTED_REMOTE /* LWIPERF_UDP_ABORTED_REMOTE */ }; /* 当测试结束以后会调用此函数此函数用来报告测试结果*/ static void lwiperf_report(void *arg,enum lwiperf_report_type report_type,const ip_addr_t *local_addr,u16_t local_port,const ip_addr_t *remote_addr,u16_t remote_port,u32_t bytes_transferred,u32_t ms_duration,u32_t bandwidth_kbitpsec) {printf(-------------------------------------------------\r\n);if ((report_type (sizeof(report_type_str) / sizeof(report_type_str[0]))) local_addr remote_addr){printf( %s \r\n, report_type_str[report_type]);printf( Local address : %u.%u.%u.%u , ((u8_t *)local_addr)[0],((u8_t *)local_addr)[1],((u8_t *)local_addr)[2],((u8_t *)local_addr)[3]);printf( Port %d \r\n, local_port);printf( Remote address : %u.%u.%u.%u , ((u8_t *)remote_addr)[0],((u8_t *)remote_addr)[1],((u8_t *)remote_addr)[2],((u8_t *)remote_addr)[3]);printf( Port %d \r\n, remote_port);printf( Bytes Transferred %d \r\n, bytes_transferred);printf( Duration (ms) %d \r\n, ms_duration);printf( Bandwidth (kbitpsec) %d \r\n, bandwidth_kbitpsec);}else{printf( IPERF Report error\r\n);} } /*** brief lwip_demo实验入口* param 无* retval 无*/ void lwip_demo(void) {uint8_t t 0;if (lwiperf_start_tcp_server_default(lwiperf_report, NULL)){printf(\r\n************************************************\r\n);printf( IPERF Server example\r\n);printf(************************************************\r\n);printf( IPv4 Address : %u.%u.%u.%u\r\n, lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);printf( IPv4 Subnet mask : %u.%u.%u.%u\r\n, lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);printf( IPv4 Gateway : %u.%u.%u.%u\r\n, lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);printf(************************************************\r\n);}else{printf(IPERF initialization failed!\r\n);}while (1){vTaskDelay(5);} } 测试网速的相关原理这里笔者不会讲解有兴趣的小伙伴可以看一下lwiperf.c/.h 文件。 下载验证 编译程序并下载到开发板上双击jperf.bat填写IP 地址与端口号如下所示 图24.2.3.1 测试网速的IP 地址和端口号 图24.2.3.2 网速波形图 可以看到我们的网速接近95M虽然离100M 有一点点差距但是速率受很多因素影响。 提高网速的速率可设置以下几个配置项如下所示 /* 堆内存的大小如果需要更大的堆内存那么设置高一点*/ #define MEM_SIZE (30 * 1024) /* MEMP_NUM_PBUF: 设置内存池的数量*/ #define MEMP_NUM_PBUF 25 /* MEMP_NUM_UDP_PCB: UDP协议控制块的数量. */ #define MEMP_NUM_UDP_PCB 4 /* MEMP_NUM_TCP_PCB: TCP的数量. */ #define MEMP_NUM_TCP_PCB 4 /* MEMP_NUM_TCP_PCB_LISTEN: 监听TCP的数量. */ #define MEMP_NUM_TCP_PCB_LISTEN 2 /* MEMP_NUM_TCP_SEG: 同时排队的TCP的数量段. */ #define MEMP_NUM_TCP_SEG 150 /* MEMP_NUM_SYS_TIMEOUT: 超时模拟活动的数量. */ #define MEMP_NUM_SYS_TIMEOUT 6 /* ---------- Pbuf选项---------- */ /* PBUF_POOL 内存池中每个内存块大小*/ #define PBUF_POOL_SIZE 20 /* PBUF_POOL_BUFSIZE: pbuf池中每个pbuf的大小. */ #define PBUF_POOL_BUFSIZE LWIP_MEM_ALIGN_SIZE(TCP_MSS 40 PBUF_LINK_ENCAPSULATION_HLEN PBUF_LINK_HLEN) /* TCP接收窗口*/ #define TCP_WND (20 * TCP_MSS)HTTP 客户端实验 HTTP 客户端用于实现平台与应用服务器之间的单向数据通信。平台作为客户端通过HTTP/HTTPS 请求方式将项目下应用数据、设备数据推送给用户指定服务器。本章主要介绍 lwIP 如何通过HTTP 协议将设备连接到OneNET 平台并实现远程互通。 OneNTE 的 HTTP 配置 关于OneNET 平台HTTP 接入方式可参考该官方的文档手册该文档手册地址https://open.iot.10086.cn/本实验主要参考官方文档的多协议接入/HTTP/上传数据点的内容。 OneNTE 的HTTP 服务器流程 第一步注册OneNTE 服务器账号注册完成之后打开右上角的控制台/ 全部产品服务/多协议接入如下图所示。 第二步选择HTTP 协议/添加产品。 第三步填写产品信息如下图所示。 上图中的几个技术参数非常重要剩下的技术参数根据用户的爱好填写。 第四步双击创建的产品并点击设备列表且在设备列表中添加设备如下图所示。 这些参数用户可以随便填写。 第五步打开数据流如下图所示。 第六步打开数据流管理/添加数据流模板如下图所示。 注意上图的数据名称必须与程序发送数据的标志一样。 第七步打开设备列表/设备详情查看设备信息如下图所示。 上图中的设备ID 和APIKey 是我们需要的信息。 HTTP 客户端实验 硬件设计 例程功能 本章目标是开发板使用 HTTP 协议连接OneNET 服务器并实现温湿度上报。 该实验的实验工程请参考《lwIP 例程19 lwIP_OneNET_HTTP 实验》。 软件设计 28.3.2.1 HTTP 配置步骤 配置MCU 为TCP 客户端模式 配置为TCP 客户端等步骤请参考第21 章。数据合并操作 调用函数lwip_onehttp_postpkt 把OneNET 产品的设备ID 和OneNET 设备的设备api 参数合拼成一个字符串。发送数据 调用函数netconn_write 把上述的postpkt 发送到OneNET 服务器平台。 28.3.2.2 程序流程图 本实验的程序流程图如下图所示。 程序解析 本章实验中我们重点讲解lwip_demo.c 和lwip_demo.h。 lwip_demo.h 文件很简单主要声明OneNET 平台的设备ID 和设备密钥而lwip_demo.c 文件定义了2 个函数这些函数的作用如下表所示。 lwip_onehttp_postpkt 函数 把数值封装至HTTP 数据包中如下源码所示 uint32_t lwip_onehttp_postpkt(char * pkt, /* 保存的数据*/char * key, /* 连接onenet的apikey */char * devid, /* 连接onenet的onenet_id */char * dsid, /* onenet的显示字段*/char * val ) /* 该字段的值*/ {char dataBuf[100] { 0 };char lenBuf[10] {0 }; * pkt 0;sprintf(dataBuf, ,;%s,%s, dsid, val); /* 采用分割字符串格式:type 5 */sprintf(lenBuf, %d, strlen(dataBuf));strcat(pkt, POST /devices/);strcat(pkt, devid);strcat(pkt, /datapoints?type5 HTTP/1.1\r\n);strcat(pkt, api-key:);strcat(pkt, key);strcat(pkt, \r\n);strcat(pkt, Host:api.heclouds.com\r\n);strcat(pkt, Content-Length:);strcat(pkt, lenBuf);strcat(pkt, \r\n\r\n);strcat(pkt, dataBuf);return strlen(pkt); }上述源码主要采用典型的C 语言基础调用函数strcat 把两个字符串拼接成一个字符串如果我们使用网络调试助手接收该数据包那么我们发现该数据与OneNET 平台HTTP 协议接入文档描述是一致该数据如下所示 POST /devices/655766336/datapoints?type5 HTTP/1.1 api-key:rw2p2FqVW4fhhhkj4CwpVcqJq8 Host:api.heclouds.com Content-Length:13 ,;humidity,00 ----------------湿度数据POST /devices/655766336/datapoints?type5 HTTP/1.1 api-key:rw2p2FqVW4fhhhkj4CwpVcqJq8 Host:api.heclouds.com Content-Length:16 ,;temperature,00 ----------------温度数据lwip_demo 函数 此函数非常简单它用来配置网络环境即以 TCP 协议连接OneNET 服务器。连接完成之后发送HTTP 数据包至服务器当中。 /*** brief lwip_demo程序入口* param 无* retval 无*/ void lwip_demo(void) {uint32_t data_len 0;struct pbuf * q;err_t err;ip4_addr_t server_ipaddr, loca_ipaddr;static uint16_t server_port, loca_port;server_port TCP_DEMO_PORT;netconn_gethostbyname(DEST_MANE, server_ipaddr);while (1) {atk_start: tcp_clientconn netconn_new(NETCONN_TCP); /* 创建一个TCP链接*/ err netconn_connect(tcp_clientconn, server_ipaddr, server_port); /* 连接服务器*/if (err ! ERR_OK) {printf(接连失败\r\n);/* 返回值不等于ERR_OK,删除tcp_clientconn连接*/netconn_delete(tcp_clientconn);} else if (err ERR_OK) /* 处理新连接的数据*/ {struct netbuf * recvbuf;tcp_clientconn - recv_timeout 10;/* 获取本地IP主机IP地址和端口号*/netconn_getaddr(tcp_clientconn, loca_ipaddr, loca_port, 1);lcd_show_string(5, 170, 200, 16, 16, link succeed, BLUE);while (1) {temp_rh[0] 30 rand() % 10 1; /* 温度的数据*/temp_rh[1] 54.8 rand() % 10 1; /* 湿度的数据*/tempStr[0] temp_rh[0] / 10 0x30; /* 上传温度*/tempStr[1] temp_rh[0] % 10 0x30;;humiStr[0] temp_rh[1] / 10 0x30; /* 上传湿度*/humiStr[1] temp_rh[1] % 10 0x30;/* 发送tcp_server_sentbuf中的数据*/len lwip_onehttp_postpkt(buffer, apikey,onenet_id, temperature, tempStr); // 组包HTTP数据netconn_write(tcp_clientconn, buffer, len, NETCONN_COPY);/* 发送tcp_server_sentbuf中的数据*/len lwip_onehttp_postpkt(buffer, apikey,onenet_id, humidity, humiStr); // 组包HTTP数据netconn_write(tcp_clientconn, buffer, len, NETCONN_COPY);vTaskDelay(1000);/* 接收到数据*/if (netconn_recv(tcp_clientconn, recvbuf) ERR_OK) {taskENTER_CRITICAL(); /* 进入临界区*//* 数据接收缓冲区清零*/memset(tcp_client_recvbuf, 0, TCP_CLIENT_RX_BUFSIZE);/*遍历完整个pbuf链表*/for (q recvbuf - p; q ! NULL; q q - next) {if (q - len (TCP_CLIENT_RX_BUFSIZE - data_len)) {memcpy(tcp_client_recvbuf data_len, q - payload, (TCP_CLIENT_RX_BUFSIZE - data_len));} else {memcpy(tcp_client_recvbuf data_len, q - payload, q - len);}data_len q - len;if (data_len TCP_CLIENT_RX_BUFSIZE) {break; /* 超出TCP客户端接收数组,跳出*/}}taskEXIT_CRITICAL(); /* 退出临界区*/data_len 0; /* 复制完成后data_len要清零*/printf(%s\r\n, tcp_client_recvbuf);netbuf_delete(recvbuf);} else /*关闭连接*/ {netconn_close(tcp_clientconn);netconn_delete(tcp_clientconn);goto atk_start;}}}} }下载验证 我们编译代码下载到开发板并运行打开数据流展示如下图所示。 HTTP 服务器实验 本章实验我们在开发板上搭建一个HTTP 服务器通过浏览器去访问我们的开发板这个 实验和第十四章的实验不同的是该实验使用字符串的形式描述网页数据而第十四章的实验使 用的是网页数组形式搭建HTTP 服务器。本实验参考contrib-2.1.0\apps\httpserver 路径下的 httpserver-netconn.c/.h 下的例程。 HTTP 协议简介 HTTP 协议是Hyper Text Transfer Protocol超文本传输协议的缩写,是用于从万维网 WWW:World Wide Web 服务器传输超文本到本地浏览器的传送协议。HTTP 是一种无状态 协议即服务器不保留与客户交易时的任何状态。这就大大减轻了服务器记忆负担从而保持 较快的响应速度。HTTP 是一种面向对象的协议。允许传送任意类型的数据对象。它通过数据 类型和长度来标识所传送的数据内容和大小并允许对数据进行压缩传送。当用户在一个 HTML 文档中定义了一个超文本链后浏览器将通过TCP/IP 协议与指定的服务器建立连接 如下所示 图25.1.1 HTTP 协议交互 HTTP定义了与服务器交互的不同方法其最基本的方法是GET、PORT 和HEAD。如 下图所示。 ①GET从服务端获取数据。 ②PORT向服务器传送数据。 ③HEAD检测一个对象是否存在。 浏览器Client (Server) PORT提交更新和控制 LED/BEEP 图25.1.2 HTTP 基本方法使用 可以知道“GET”请求用来获取服务器的数据而“PORT”请求是向服务器转送数据。 HTTP 服务器实验 硬件设计 例程功能 在开发板上搭建一个HTTP 服务器并实时控制开发板上的LED1 和蜂鸣器。 该实验的实验工程请参考《lwIP 例程16 lwIP_HTTPS 实验》。 25.2.2 软件设计 25.2.2.1 程序流程图 本实验流程图如下图所示 程序解析 本实验重点看lwip_demo.c 文件该文件定义了三个函数如下表所示 lwip_demo 函数用来配置网络环境这里笔者把开发板设置为TCP 服务器其端口号为 80。 /*** brief lwip_demo程序入口* param 无* retval 无*/ void lwip_demo(void) {struct netconn *conn, *newconn;err_t err;/* 创建一个新的TCP连接句柄*//* 使用默认IP地址绑定到端口80 (HTTP) */conn netconn_new(NETCONN_TCP);netconn_bind(conn, IP_ADDR_ANY, 80);/* 将连接置于侦听状态*/netconn_listen(conn);do{err netconn_accept(conn, newconn);if (err ERR_OK){http_server_netconn_serve(newconn);netconn_delete(newconn);}} while (err ERR_OK);netconn_close(conn);netconn_delete(conn); } 连接完成之后调用http_server_netconn_serve 函数实现本章节的功能。 lwip_server_netconn_serve 函数源码如下所示 static void lwip_server_netconn_serve(struct netconn *conn) {struct netbuf *inbuf;char *buf;u16_t buflen;err_t err;char *ptemp;/* 从端口读取数据如果那里还没有数据则阻塞。我们假设请求(我们关心的部分)在一个netbuf中*/err netconn_recv(conn, inbuf);if (err ERR_OK){netbuf_data(inbuf, (void **)buf, buflen);/* 这是一个HTTP GET命令吗?只检查前5个字符因为GET还有其他格式我们保持简单)*/if (buflen 5 buf[0] G buf[1] E buf[2] T buf[3] buf[4] /){start_html:/* 发送HTML标题从大小中减去1因为我们没有在字符串中发送\0NETCONN_NOCOPY:我们的数据是常量静态的所以不需要复制它*/netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1,NETCONN_NOCOPY);/* 发送我们的HTML页面*/netconn_write(conn, http_index_html, sizeof(http_index_html) - 1,NETCONN_NOCOPY);}else if (buflen 8 buf[0] P buf[1] O buf[2] S buf[3] T){ptemp lwip_data_locate((char *)buf, led1);if (ptemp ! NULL){/* 查看led1的值。为1则灯亮为2则灭此值与HTML网页中设置有关*/if (*ptemp 1){/* 点亮LED1 */LED0(0);}else{/* 熄灭LED1 */LED1(1);}}/* 查看beep的值。为3则灯亮为4则灭此值与HTML网页中设置有关*/ptemp atk_data_locate((char *)buf, beep);if (ptemp ! NULL){if (*ptemp 3){/* 打开蜂鸣器*/BEEP(1);}else{/* 关闭蜂鸣器*/BEEP(0);}}goto start_html;}}/* 关闭连接(服务器在HTTP中关闭) */netconn_close(conn);/* 删除缓冲区(netconn_recv给我们所有权所以我们必须确保释放缓冲区) */netbuf_delete(inbuf); } 上述的源码很简单理解主要分为三步 ①当浏览器输入IP 地址并且回车确认时程序调用函数netconn_write 把网页数据发送 到浏览器当中。 ②当网页发送一个PORT 命令时程序调用函数lwip_data_locate 判断触发源判断完 成之后根据触发源来执行相应的动作。 ③程序执行goto 语句重新发送网页字符串到网页当中这个步骤相当于更新网页。 下载验证 编译程序并把程序下载到开发板中打开网页同时需要查看分配的IP 地址为多少接 着在浏览器上输入IP 地址如下图所示 基于 MQTT 协议连接阿里云服务器 本章主要学习lwIP 提供的 MQTT 协议文件使用通过MQTT 协议将设备连接到阿里云服务器实现远程互通。由于 MQTT 协议是基于TCP 的协议实现的所以我们只需要在单片机端实现TCP 客户端程序并使用 lwIP 提供的MQTT 文件来连接阿里云服务器。 MQTT 协议简介 (1) MQTT 是什么 MQTTMessage Queuing Telemetry Transport消息队列遥测传输协议是一种基于发布/订阅Publish/Subscribe模式的轻量级通讯协议该协议构建于TCP/IP 协议上由IBM 在1999 年发布目前最新版本为v3.1.1。MQTT 最大的优点在于可以以极少的代码和有限的带宽为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议MQTT在物联网、小型设备、移动应用等方面有广泛的应用MQTT 协议属于应用层。 (2) MQTT 协议特点 MQTT 是一个基于客户端与服务器的消息发布/订阅传输协议。MQTT 协议是轻量、简单开放和易于实现的这些特点使它适用范围非常广泛。在很多情况下包括受限境中如机器与机器M2M通信和物联网IoT。其在通过卫星链路通信传感器、医疗设备、智能家居、及一些小型化设备中已广泛使用。 (3) MQTT 协议原理及实现方式 实现MQTT 协议需要客户端和服务器端MQTT 协议中有三种身份发布者Publish、代理Broker服务器、订阅者Subscribe。其中消息的发布者和订阅者都是客户端消息代理是服务器消息发布者可以同时是订阅者如下图所示。 MQTT 传输的消息分为主题Topic和消息的内容payload两部分。 Topic可以理解为消息的类型订阅者订阅Subscribe后就会收到该主题的消息内容payload。 Payload可以理解为消息的内容是指订阅者具体要使用的内容。 MQTT 协议实现原理 要在客户端与代理服务端建立一个TCP 连接建立连接的过程是由客户端主动发起的代理服务一直是处于指定端口的监听状态当监听到有客户端要接入的时候就会立刻去处理。 客户端在发起连接请求时携带客户端ID、账号、密码无账号密码使用除外正式项目不会允许这样、心跳间隔时间等数据。代理服务收到后检查自己的连接权限配置中是否允许该账号密码连接如果允许则建立会话标识并保存绑定客户端ID 与会话并记录心跳间隔时间判断是否掉线和启动遗嘱时用和遗嘱消息等然后回发连接成功确认消息给客户端客户端收到连接成功的确认消息后进入下一步通常是开始订阅主题如果不需要订阅则跳过。如下图所示 客户端将需要订阅的主题经过SUBSCRIBE 报文发送给代理服务代理服务则将这个主题记录到该客户端ID 下以后有这个主题发布就会发送给该客户端然后回复确认消息SUBACK 报文客户端接到SUBACK 报文后知道已经订阅成功则处于等待监听代理服务推送的消息也可以继续订阅其他主题或发布主题如下图所示 当某一客户端发布一个主题到代理服务后代理服务先回复该客户端收到主题的确认消息该客户端收到确认后就可以继续自己的逻辑了。但这时主题消息还没有发给订阅了这个主题的客户端代理要根据质量级别QoS来决定怎样处理这个主题。所以这里充分体现了是MQTT 协议是异步通信模式不是立即端到端反应的如下图所示 如果发布和订阅时的质量级别QoS 都是至多一次那代理服务则检查当前订阅这个主题的客户端是否在线在线则转发一次收到与否不再做任何处理。这种质量对系统压力最小。 如果发布和订阅时的质量级别QoS 都是至少一次那要保证代理服务和订阅的客户端都有成功收到才可以否则会尝试补充发送具体机制后面讨论。这也可能会出现同一主题多次重复发送的情况。这种质量对系统压力较大。 如果发布和订阅时的质量级别QoS 都是只有一次那要保证代理服务和订阅的客户端都有成功收到并只收到一次不会重复发送具体机制后面讨论。这种质量对系统压力最大。 移植 MQTT 协议 其实移植 lwIP 的 MQTT 文件是非常简单的只将 lwip\src\apps\mqt 路径下的 mqtt.c 文件添加到工程当中这里我们在工程中添加一个名为 Middlewares/lwip/src/apps 分组该分组用来添加 lwIP 应用层的文件如下图所示所示 mqtt.c 文件是 lwIP 根据 MQTT 协议规则编写而来的如果用户不使用这个文件请自行移植MQTT 协议包。 在Middlewares/lwip/lwip_app 分组添加hmac_sha1 和sha1 文件这些文件用来计算核心密钥这两个文件可在阿里云官方下载。 配置远程服务器 配置阿里云服务器步骤 第一步注册阿里云平台打开产品分类/物联网Iot/物联网应用开发如下图所示。 点击上图中的“立刻使用”按键进去物联网应用开发页面。 第二步在物联网应用开发页面下点击项目管理/新建项目/新建空白项目在此界面下填写项目名称等相关信息如下图所示 创建项目完成之后在项目管理页面下点击项目进去子项目管理界面如下图所示 第三步在上图中点击产品如下图所示 注上图中的节点类型、连网方式、数据格式以及认证模式的选择其他产品参数根据用户爱好设置。 第三步创建产品之后点击图26.1.3.3 中的设备选项添加设备如下图所示。 第五步在设备页面下找到我们刚刚创建的设备如下图所示。 这三个参数非常重要在本章实验中会用到。 第六步打开“产品/查看/功能定义”路径在该路径下添加功能定义如下图所示。 第七步打开自定义功能并发布上线这里我们添加了两个CurrentTemperature 和RelativeHumidity 标签。 阿里云MQTT 协议实验 硬件设计 例程功能 本章的目标是lwIP 连接阿里云实现数据上存。 该实验的实验工程请参考《lwIP 例程17 lwIP_Aliyun_MQTT 实验》。 26.2.2 软件设计 26.2.2.1 MQTT 配置步骤 配置MCU 为TCP 客户端模式 配置为TCP 客户端等步骤请参考第21 章。DNS 解析阿里云网页转成IP 地址 调用函数gethostbyname 获取DNS 解析的IP 地址。MQTT 连接 调用函数mqtt_client_connect 连接服务器。连接状态 对服务器发布和订阅操作。循环发布数据到服务器当中 在lwip_demo 函数的while()语句中定时1s 调用函数mqtt_publish 发布数据至服务器。 26.2.2.2 程序流程图 本实验的程序流程图如下图所示。 程序解析 我们打开 lwip_deom.h 文件在这个文件中我们定义了阿里云服务器创建设备的配置项另外还声明了 lwip_demo 函数关于阿里云服务器的MQTT 主题请大家查看阿里云相关手册。 重点关注的是lwip_deom.c 这个文件在这个文件定义了8 个函数如下表所示。 我们首先看一下lwip_demo 函数该函数的代码如下。 /*** brief lwip_demo进程* param 无* retval 无*/ void lwip_demo(void) {struct hostent * server;static struct mqtt_connect_client_info_t mqtt_client_info;server gethostbyname((char * ) HOST_NAME); /* 对oneNET服务器地址解析*//* 把解析好的地址存放在mqtt_ip变量当中*/memcpy( mqtt_ip, server - h_addr, server - h_length);char * PASSWORD;PASSWORD mymalloc(SRAMIN, 300); /* 为密码申请内存*//* 通过hmac_sha1算法得到password */lwip_ali_get_password(DEVICE_SECRET, CONTENT, PASSWORD);/* 设置一个空的客户端信息结构*/memset( mqtt_client_info, 0, sizeof(mqtt_client_info));/* 设置客户端的信息量*/mqtt_client_info.client_id (char * ) CLIENT_ID; /* 设备名称*/mqtt_client_info.client_user (char * ) USER_NAME; /* 产品ID */mqtt_client_info.client_pass (char * ) PASSWORD; /* 计算出来的密码*/mqtt_client_info.keep_alive 100; /* 保活时间*/mqtt_client_info.will_msg NULL;mqtt_client_info.will_qos NULL;mqtt_client_info.will_retain 0;mqtt_client_info.will_topic 0;myfree(SRAMIN, PASSWORD); /* 释放内存*//* 创建MQTT客户端控制块*/mqtt_client mqtt_client_new();/* 连接服务器*/mqtt_client_connect(mqtt_client, /* 服务器控制块*/ mqtt_ip, MQTT_PORT, /* 服务器IP与端口号*/mqtt_connection_cb, /* 设置服务器连接回调函数*/LWIP_CONST_CAST(void * , mqtt_client_info), mqtt_client_info); /* MQTT连接信息*/while (1) {if (publish_flag 1) {temp 30 rand() % 10 1; /* 温度的数据*/humid 54.8 rand() % 10 1; /* 湿度的数据*/sprintf((char * ) payload_out,{\params\:{\CurrentTemperature\: % 0.1 f, \RelativeHumidity\:%0.1f},\method\:\thing.event.property.post\}, temp, humid);payload_out_len strlen((char * ) payload_out);mqtt_publish(mqtt_client, DEVICE_PUBLISH, payload_out,payload_out_len, 1, 0, mqtt_publish_request_cb, NULL);}vTaskDelay(1000);} }此函数非常简单首先我们调用gethostbyname 函数解析阿里云的域名根据这个域名来连接阿里云服务器其次使用一个结构体配置MQTT 客户端的信息并调用mqtt_client_new 函数创建MQTT 服务器控制块接着我们调用mqtt_client_connect 函数连接阿里云服务器并添加mqtt_connection_cb 连接回调函数最后在while()语句中判断是否订阅操作成功如果系统订阅成功则构建MQTT 消息并调用mqtt_publish 函数发布。 接下来我们来讲解一下 mqtt_connection_cb 函数的作用如下源码所示 /*** brief mqtt连接回调函数* param client客户端控制块* param arg传入的参数* param status连接状态* retval 无*/ static void mqtt_connection_cb(mqtt_client_t * client, void * arg,mqtt_connection_status_t status) {err_t err;const struct mqtt_connect_client_info_t * client_info (const struct mqtt_connect_client_info_t * ) arg;LWIP_UNUSED_ARG(client);printf(\r\nMQTT client \%s\ connection cb: status %d\r\n, client_info - client_id, (int) status);/* 判断是否连接*/if (status MQTT_CONNECT_ACCEPTED) {/* 判断是否连接*/if (mqtt_client_is_connected(client)) {/* 设置传入发布请求的回调*/mqtt_set_inpub_callback(mqtt_client,mqtt_incoming_publish_cb,mqtt_incoming_data_cb,NULL);/* 订阅操作并设置订阅响应会回调函数mqtt_sub_request_cb */err mqtt_subscribe(client, DEVICE_SUBSCRIBE, 1,mqtt_request_cb, arg);if (err ERR_OK) {printf(mqtt_subscribe return: %d\n, err);lcd_show_string(5, 170, 210, 16, 16,mqtt_subscribe succeed, BLUE);}}} else /* 连接失败*/ {printf(mqtt_connection_cb: Disconnected, reason: %d\n, status);} }此函数也是非常简单它主要调用函数mqtt_client_is_connected 判断是否已经连接服务器如果连接成功则程序调用函数mqtt_set_inpub_callback 添加mqtt_incoming_publish_cb 和mqtt_incoming_data_cb 回调函数这些回调函数需要根据客户端以及服务器的发布操作才能进去该回调函数最后我们调用函数mqtt_subscribe 对服务器进行订阅操作并且添加mqtt_request_cb 订阅响应回调函数。 下载验证 下载完代码后在浏览器上打开阿里云平台并在指定的网页查看上存数据如下图所示。 基于 MQTT 协议连接 OneNET 服务器 本章主要介绍 lwIP 如何通过 MQTT 协议将设备连接到 OneNET 平台并通过MQTT 协议远程互通。关于 MQTT 协议的知识请参考第二十六章节的内容。 配置OneNET 平台 配置OneNET 服务器步骤 第一步首先打开OneNET 服务器并注册账号注册之后在主界面下打开产品服务页面下的MQTT 物联网套件如下图所示 第二步在上图中点击“立刻使用”选项页面跳转完成之后点击“添加产品”选项此时该页面会弹出产品信息小界面这里我们根据自己的项目填写相关的信息如下图所示 上图中我们重点添加的选项有联网方式和设备接入协议这里笔者选择移动蜂窝网络以及MQTT 协议接入至于其他选项根据爱好选择。创建MQTT 产品之后用户可以得到该产品的信息如下图示 本实验会用到上述的产品信息例如产品ID366007、“access_key”产品密钥以及产品名称MQTT_TSET等。 第三步在产品页面下点击设备列表添加设备如下图所示 第四步在上图创建的设备中点击右边的详情标签进入标签的链接页面在这个页面下我们得到以下设备信息如下图所示 本实验会用到上图中的设备ID617747917、设备名称MQTT 以及“key”设备的密钥。 下面我们打开OneNET 在线开发指南在这个指南中找到服务器地址这些服务器地址就是MQTT 服务器地址如下图所示 上图中OneNTE 的MQTT 服务器具有两个连接方式一种是加密接口连接而另一种是非加密接口连接本章实验使用的是非加密接口连接MQTT 服务器。 注MQTT 物联网套件采用安全鉴权策略进行访问认证即通过核心密钥计算的 token 进行访问认证简单来讲用户想连接OneNET的MQTT 服务器必须计算核心密钥这个密钥是根据我们前面创建的产品和设备相关的信息计算得来的密钥的计算方法可以使用OneNET提供的token生成工具计算该软件可在这个网址下载https://open.iot.10086.cn/doc/v5/develop/detail/242。 下面笔者简单讲解一下token 生成工具的使用如图27.1.1.7 所示 res输入格式为“products/{pid}/devices/{device_name}”这个输入格式中的“pid”就是我们MQTT 产品ID而“device_name”就是设备的名称。根据前面创建的产品和设备来填写res 选项的参数如下图所示 et访问过期时间expirationTimeunix时间这里笔者选择参考文档中的数值1672735919如下图所示 key指选择设备的key 密钥如下图所示 最后按下上图中的“Generate”按键生成核心密钥如下图所示。 这个核心密钥会在MQTT 客户端的结构体client_pass 成员变量保存。 工程配置 小上节我们使用token 生成工具根据产品信息以及设备信息来计算核心密钥这样的方式导致每次创建一个设备都必须根据这个设备信息再一次计算核心密钥才能连接这种方式会大大地降低我们的开发效率为了解决这个问题笔者使用另一个方法那就是使用代码的方式计算核心密钥它和上一章节中的方式不一样因为阿里云和OneNET 计算的方式不同所以不能使用阿里云的那两个文件来计算OneNET 的密钥。OneOS 源码中有几个文件是用来计算MQTT 协议连接OneNET 平台的核心密钥这些文件在oneos2.0\components\cloud\onenet\mqtt-kit\authorization 路径下大家先下载OneOS 源码并在该路径下复制这些文件到工程当中。 打开工程并在Middlewares/lwip/lwip_app 分组下添加以下文件如下图所示 这些文件都在oneos2.0\components\cloud\onenet\mqtt-kit\authorization 路径下获取。 基于OneNET 平台MQTT 实验 硬件设计 例程功能 本章目标是开发板使用MQTT 协议连接OneNET 服务器并实现数据上存更新。 该实验的实验工程请参考《lwIP 例程18 lwIP_OneNET_MQTT 实验》。 27.3.2 软件设计 27.3.2.1 程序流程图 本实验的程序流程图如下图所示。 程序解析 我们打开lwip_deom.h 文件在这个文件中我们定义了OneNET 服务器创建设备的配置项另外还声明了lwip_demo 函数关于OneNET 服务器的MQTT 主题请大家查看OneNET 相关手册该手册地址为https://open.iot.10086.cn/doc/v5/develop/detail/251这个地址里面已经说明了OneNET 的MQTT 服务器相关主题信息。至于lwip_deom.c 文件前面我们已经讲解过了它们唯一不同的是计算核心密钥方式。 下载验证 我们编译代码并把下载到开发板上运行打开OneNET 的MQTT 服务器查看数据流展示如下图所示。 网络摄像头ATK-MC5640实验 网络摄像头是传统摄像机与网络视频技术相结合的新一代产品除了具备一般传统摄像机 所有的图像捕捉功能外机内还内置了数字化压缩控制器和基于WEB 的操作系统使得视频 数据经压缩加密后通过局域网internet 或无线网络送至终端用户。而远端用户可在PC 上使 用标准的网络浏览器根据网络摄像机的IP 地址对网络摄像机进行访问实时监控目标现 场的情况并可对图像资料实时编辑和存储同时还可以控制摄像机的云台和镜头进行全方 位地监控。本章的实验是以网络调试助手作为客户端开发板作为服务器。服务器把摄像头处 理的数据使用网卡发送至服务器当中并且在服务器实时更新图像。 ATK-MC5640 简介 ATK-MC5640 模块通过2*9 的排针2.54mm 间距同外部相连接该模块可直接与正点 原子探索者STM32F407 开发板和正点原子MiniSTM32H750 开发板等开发板的CAMERA 摄像 头接口连接。正点原子的大部分开发板我们都提供了本模块相应的例程用户可以直接在这 些开发板上对模块进行测试。 ATK-MC5640 模块的外观如下图所示 图30.1.1 ATK-MC5640 模块实物图 ATK-MC5640 模块的原理图如下图所示 从上图可以看出ATK-MC5640 模块自带了有源晶振用于产生24MHz 的时钟作为 OV5640 传感器的XCLK 输入模块的闪光灯LED1 和LED2可由OV5640 的STROBE 脚 控制可编程控制或外部引脚控制只需焊接R2 或R3 的电阻进行切换控制同时模块 同时自带了稳压芯片用于提供OV5640 稳定的2.8V 和1.5V 工作电压。 ATK-MC5640 模块通过一个2*9 的排针P1同外部电路连接各引脚的详细描述如 下表所示 SCCB 简介 SCCBSerial Camera Control Bus串行摄像头控制总线是OmniVision 开发的一种总线 协议且广泛被应用于OV 系列图像传感器上。SCCB 协议与IIC 协议十分相似SCCB 协议 由两条信号线组成SIO_C类似IIC 协议的SCL和SIO_D类似IIC 协议的SDA。与IIC 协议一样SCCB 协议也有起始信号和停止信号只不过与IIC 协议不同的是IIC 协议在传输完1 字节数据后需要传输的接收方发送1 比特的确认位而SCCB 协议一次性要传输9 位 数据前8 位为读写的数据位第9 位在写周期为Don’t-Care 位在读周期为NA 位。这样一 次性传输的9 个位在SCCB 协议中被定义为一个相Phase。 在SCCB 协议中共包含了三种传输周期分别为3 相写传输三个相均由主机发出一般 用于主机写从机寄存器三个相分别从设备地址、寄存器地址、写入的数据、2 相写传输 两个相均由主机发出一般配合2 相读传输用与主机读从机寄存器值两个相分别为从设备 地址、寄存器地址和2 相读传输第一个相由主机发出第二个相由从机回应一般配合2 相写传输用于主机读从机寄存器值两个相分别为从设备地址、寄存器数据。 关于SCCB 协议的详细介绍请见《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification.pdf》。 在OV5640 图像传感器的初始化阶段主机MCU 需要使用SCCB 协议配置OV5640 中大 量的寄存器有关OV5640 寄存器的介绍请见《OV5640_CSP3_DS_2.01_Ruisipusheng.pdf》 和《OV5640_camera_module_software_application_notes_1.3_Sonix.pdf》。 OV5640 DVP 接口说明 OV5640 支持数字视频接口DVP和MIPI 接口因为正点原子探索者STM32F407 和正 点原子MiniSTM32H750 等开发板的CANERA 接口使用的是DCMI 接口仅支持DVP 接口 因此OV5640 必须使用DVP 输出接口才能够连正点原子探索者STM32F407 和正点原子 MiniSTM32H750 等开发板使用。 OV5640 提供了一个10 位的DVP 接口支持8 位接发可通过程序设置DVP 以MSB 或LSB 输出ATK-MC5640 模块采用8 位DVP 连接的方式如下图所示 图30.1.1.1 ATK-MC56408 位DVP 连接方式 OV5640 窗口设置说明 OV5640 输出的图像与ISPImage Signal Processor输入窗口、预缩放窗口和数据输出窗 口的大小有关如下图所示 ISP 输入窗口ISP imput size 该窗口的大小允许用于设置整个传感器区域physical pixel size26231951的执行部分 也就是在传感器里面开窗X_ADDR_ST、Y_ADDR_ST、X_ADDR_END、Y_ADDR_END 开窗范围从00~2623*1951 都可以设置该窗口所设置的范围将输入ISP 进行处理。 ISP 输入窗口通过寄存器地址为0x3800~0x3807 的八个寄存器进行配置。 预缩放窗口pre-scaling size 该窗口允许用于在ISP 输入窗口的基础上再次设置想要用于缩放的窗口大小。该窗口仅在 ISP 输入窗口内进行X、Y 方向的偏移X_OFFSET、Y_OFFSET。 预缩放窗口通过寄存器地址为0x3808~0x380B 的四个寄存器进行配置。 数据输出窗口data output size 该窗口是OV5640 输出给外部的图像尺寸当数据输出窗口的宽高比例与预缩放窗口的宽 高比例不一致时输出的图像数据会变形只有当两者比例一致时输出图像的尺寸才不会变 形。 OV5640 行像素输出时序介绍 OV5640 图像传感器的数据输出通过D[9:0]是在PCLK、VSYNC、HREFHSYNV 的控制下进行的。行输出时序如下图所示 从上图可以看出图像数据在HREF 为高的时候输出当HREF 变高后每一个PCLK 时钟输出一个8 位或10 位的数据ATK-MC5640 模块采用8 位所以每个PCLK 输出1 个 字节图像数据且在RGB/YUV 输出格式下每个像素数据需要两个PCLK 时钟在Raw 输 出格式下每个像素数据需要一个PCLK 时钟。例如采用QSXGA 分辨率RGB565 格式输出 那么一个像素的信息由两个字节组成低字节在前高字节在后这样每行图像数据就需要 25922 个PCLK 时钟输出25922 个字节。 接下来以QSXGA 分辨率为例介绍帧输出的时序如下图所示 图30.1.4.2 OV5640 帧输出时序图 上图清楚的展示了OV5640 在QSXGA 分辨率下的数据输出。只需按照这个时序去读取 OV5640 的数据就可以得到图像数据。 OV5640 自动对焦介绍 OV5640 的自动对焦Auto Focus由其内置的微控制器完成并且VCMVoice Coil Motor音圈马达驱动器也集成在传感器内部。OV5640 内置微控制器的自动对焦控制固件 Firmware需要从外接的主控芯片下载。当固件运行后内置微处理器从OV5640 传感器自 动获取自动对焦所需的信息然后计算并驱动VCM 带动镜头达到正确的对焦位置。外接主控 芯片可以通过SCCB 协议控制OV5640 内置微处理器的各种功能。 OV5640 自动对焦相关的寄存器如下表所示 OV5640 内置处理器接收到自动对焦命令后会自动将CMD_MAIN 寄存器清零当命令执 行完成后则会将CMD_ACK 寄存器清零。 自动对焦过程 下载固件 OV5640 初始化完成后就可以下载自动对焦固件固件数据由厂家提供了其操作过 程就是通过SCCB 写OV5640 的寄存器自动对焦固件下载的起始地址为0x8000固件下载 完成后通过检查FW_STATUS 寄存器来判断固件下载状态当读取到FW_STATUS 寄存器 的值为0x70 时说明自动对焦固件下载完成。自动对焦后获取图像 OV5640 支持单次自动对焦和持续自动对焦通过CMD_MAIN 寄存器配置配置 OV5640 单次自动对焦的过程如下 a. 往CMD_MAIN 寄存器写0x03触发单次自动对焦。 b. 读取FW_STATUS 寄存器直至读到0x10说明已完成对焦。 c. 往CMD_MAIN 寄存器写0x06暂停自动对焦过程镜头将保持在对焦位置。 配置OV5640 持续自动对焦的过程如下 a. 往CMD_MAIN 寄存器写0x08释放VCM 至初始状态对焦到无穷远处。 b. 往CMD_MAIN 寄存器写0x04启动持续自动对焦。 c. 读取CMD_ACK 寄存器直至读到0x00说明命令执行完成。释放VCM 到初始状状态 通过往CMD_MAIN 寄存器写0x08即可释放VCM结束自动对焦。 网络摄像头实验 硬件设计 例程功能 在本实验中开发板主控芯片通过模拟SCCB 协议对ATK-MC5640 模块中的摄像头传感 器进行配置等通讯并通过DCMI 接口获取ATK-MC5640 模块输出的JPEG 图像数据然后 将获取到的图像数据实时的发往至正点原子自研的ATK-XCAM 软件。 该实验的实验工程请参考《lwIP 例程21 lwIP_网络摄像头实验MC5640》或者《lwIP 例程21 lwIP_网络摄像头实验MC5640 全帧输出》。 注探索者、DMF407 开发板没有本实验例程。 软件设计 30.2.2.1 程序流程图 本实验的程序流程图如下图所示 程序解析 相关ATK-MC5640 驱动文件介绍请参考《ATK-MC5640 模块使用说明》和《ATK- MC5640 模块用户手册》文档。 实验的测试代码为文件lwip_demo.c在工程下的Middlewares\lwip\lwip_app 路径中。测 试代码的入口函数为lwip_demo()具体的代码如下所示 /*** brief lwip_demo实验入口* param 无* retval 无*/ void lwip_demo(void) {err_t err;struct netconn *conn;static ip_addr_t ipaddr;uint8_t remot_addr[4];static u16_t port;uint8_t *p_jpeg_buf;uint32_t jpeg_len;uint32_t jpeg_index;uint32_t jpeg_start_index;uint32_t jpeg_end_index;conn netconn_new(NETCONN_TCP); /* 创建一个TCP链接*/netconn_bind(conn, IP_ADDR_ANY, 8088); /* 绑定端口8088号端口*/netconn_listen(conn); /* 进入监听模式*/while (1) /* 等待连接*/{err netconn_accept(conn, g_newconn); /* 接收连接请求*/if (err ERR_OK) /* 成功检测到连接*/{/* 获取远端IP地址和端口号*/netconn_getaddr(g_newconn, ipaddr, port, 0);remot_addr[3] (uint8_t)(ipaddr.addr 24);remot_addr[2] (uint8_t)(ipaddr.addr 16);remot_addr[1] (uint8_t)(ipaddr.addr 8);remot_addr[0] (uint8_t)(ipaddr.addr);lwip_camera_init();delay_ms(1000); /* 此延时一定要加*/while (1) /* 开始视频传输*/{p_jpeg_buf (uint8_t *)g_jpeg_buf;jpeg_len DEMO_JPEG_BUF_SIZE / (sizeof(uint32_t));memset((void *)g_jpeg_buf, 0, DEMO_JPEG_BUF_SIZE);/* 获取ATK-MC5640模块输出的一帧JPEG图像数据*/atk_mc5640_get_frame((uint32_t)g_jpeg_buf,ATK_MC5640_GET_TYPE_DTS_32B_INC, NULL);/* 获取JPEG图像数据起始位置*/for (jpeg_start_index UINT32_MAX, jpeg_index 0;jpeg_index DEMO_JPEG_BUF_SIZE - 1; jpeg_index){if ((p_jpeg_buf[jpeg_index] 0xFF) (p_jpeg_buf[jpeg_index 1] 0xD8)){jpeg_start_index jpeg_index;break;}}if (jpeg_start_index UINT32_MAX){continue;}/* 获取JPEG图像数据结束位置*/for (jpeg_end_index UINT32_MAX, jpeg_index jpeg_start_index;jpeg_index DEMO_JPEG_BUF_SIZE - 1; jpeg_index){if ((p_jpeg_buf[jpeg_index] 0xFF) (p_jpeg_buf[jpeg_index 1] 0xD9)){jpeg_end_index jpeg_index;break;}}if (jpeg_end_index UINT32_MAX){continue;}/* 获取JPEG图像数据的长度*/jpeg_len jpeg_end_index - jpeg_start_index (sizeof(uint32_t) 1);err netconn_write(g_newconn, g_jpeg_buf,DEMO_JPEG_BUF_SIZE, NETCONN_COPY); /* 发送数据*/if ((err ERR_CLSD) || (err ERR_RST)){myfree(SRAMCCM, g_jpeg_buf);netconn_close(g_newconn);netconn_delete(g_newconn);break;}vTaskDelay(2); /* 延时2ms */}}} } 上面的代码还是比较简单的首先把开发板配置为TCP 服务器配置完成且连接成功之 后将ATK-MC5640 模块输出的JPEG 图像数据读取至缓冲空间由于JPEG 图像数据的大小是 不确定的因此首先就要计算出JPEG 图像数据的大小然后将JPEG 图像数据通过网络输出 至ATK-XCAM 上位机进行显示。 下载验证 将ATK-MC5640 模块按照前面介绍的连接方式与开发板连接同时将开发板与上位机通 讯的串口连接至PC并将实验代码编译烧录至开发板中如果DHCP 服务器分配完成那么 串口调试助手显示如下信息 图30.2.3.1 串口调试助手显示内容 接下来如果ATK-MC5640 模块初始化成功则会在上位机上显示ATK-MC5640 模块输 出的JPEG 图像如下图所示 图30.2.3.2 网络调试助手显示内容 网络摄像头ATK-MC2640实验 本章我们来实现一下ATK-MC2640 模块的网络摄像头实验。 ATK-MC2640 简介 ATK-MC2640 模块通过2*9 的排针2.54mm 间距同外部相连接该模块可直接与正点 原子战舰STM32F103 开发板、正点原子探索者STM32F407 开发板和正点原子 MiniSTM32H750 开发板等开发板的CAMERA 摄像头接口连接。正点原子的大部分开发板 我们都提供了本模块相应的例程用户可以直接在这些开发板上对模块进行测试。 ATK-MC2640 模块的外观如下图所示 图31.1.1.1 ATK-MC2640 模块实物图 ATK-MC2640 模块的原理图如下图所示 图31.1.1.2 ATK-MC2640 模块原理图 从上图可以看出ATK-MC2640 模块自带了有源晶振用于产生24MHz 的时钟作为OV2640 传感器的XCLK 输入模块的闪光灯LED1 和LED2可由OV2640 的STROBE 脚 控制可编程控制或外部引脚控制只需焊接R2 或R3 的电阻进行切换控制同时模块 同时自带了稳压芯片用于提供OV2640 稳定的2.8V 和1.3V 工作电压 ATK-MC2640 模块通过一个2*9 的排针P1同外部电路连接各引脚的详细描述如 下表所示 SCCB 介绍 SCCB 协议相关知识请读者查看30.1.1 小节内容。 在OV2640 图像传感器的初始化阶段主机MCU 需要使用SCCB 协议配置OV2640 中大 量的寄存器有关OV2640 寄存器的介绍请见《OV2640_DS(1.6).pdf》和《OV2640 Software Application Notes 1.03.pdf》。 OV2640 行像素输出时序介绍 OV2640 图像传感器的数据输出是在行参考信号的像素时钟的控制下有序输出的默 认的行像素输出时序如下图所示 图31.1.2.1 OV2640 图像传感器行像素输出时序图 如上图所示当行参考信号HREF为高电平时表示数据端口的数据有效此时每 输出一个像素时钟PCLK就输出一个数据8bit 或10bit。数据在PCLK 的下降沿更新 所以外接主控须在PCLK 的上升沿读取数据。 注意图中的tP表示像素周期像素周期可能等于一个像素时钟周期或两个像素时钟周 期。在RGB/YUV 输出格式下每个像素周期等于两个像素时钟周期在RawRGB 输出格式 下每个像素周期等于一个像素时钟周期。 以RGB565 的输出格式为例一个像素周期等于两个像素时钟周期每一个像素需要用 两个字节表示低字节在前高字节在后那么如果采用UXGA 分辨率输出图像数据那么 每输出一行图像数据就需要1600*2 个像素时钟。 当使用JPEG 格式输出图像数据时输出的图像数据是经过压缩的数据这里与普通的行 橡树输出时序略有不同普通的行像素输出时行参考信号是连续的也就是在一行数据输出 的过程中行参考信号是一直保持高电平的而JPEG 格式输出图像数据时行参考信号并不 是连续的有可能在一行图像数据输出的过程中多次出现低电平但这并不影响数据的读取 只需判断行参考信号为高电平的时候再读取数据就可以了。JPEG 格式输出的图像数据不 存在高低字节的概念只需要从头到尾将所有的数据读取保存下来就可以完成一次JPEG 数据采集。 注意PCLK 的频率可达36MHz所以外接主控在读取数据的时候必须速度够快才可 以否则就可能出现数据丢失。对于速度不够快的MCU我们可以通过设置OV2640 的寄存 器0xD3 和0x11设置PCLK 和时钟的分频来降低PCLK 速度从而使得低速外接主控也 可以读取OV2640 的数据。不过这样会降低帧率。 OV2640 帧时序介绍 一帧图像数据实际上就是由多行像素输出的数据组成的。这里以UXGA 的帧时序为例 进行介绍UXGA 的帧时序如下图所示 图31.1.3.1 UXGA 帧时序图 如上图所示tLINE为行输出时间tP为像素周期VSYNC 为帧同步信号每一个VSYNC 脉冲表示一个新帧的开始而整个帧周期内由1200 次行像素Row输出每一行为 1600 个像素这样得到的数据正好为1600*1200 的分辨率图像数据。 HSYNC 为行同步信号用于同步行输出数据不过ATK-MC2640 模块并没有引出该信号 因此使用HREF 做同步即可。 网络摄像头实验 硬件设计 例程功能 本实验与上一章节的实验类似只不过本实验配置ATK-MC2640 模块输出JPEG 图像数 据然后将通过DCMI 接口读取到的JPEG 图像数据通过网络输出至正点原子自研的ATK- XCAM 软件显示。 该实验的实验工程请参考《lwIP 例程21 lwIP_网络摄像头实验MC5640》。 注DMF407 开发板没有本实验例程。 软件设计 31.2.2.1 程序流程图 本实验的程序流程图如下图所示 程序解析 相关ATK-MC2640 驱动文件介绍请参考《ATK-MC2640 模块使用说明》和《ATK-MC2640 模块用户手册》文档。 实验的测试代码为文件lwip_demo.c在工程下的Middlewares\lwip\lwip_app 路径中。测 试代码的入口函数为lwip_demo()具体的代码如下所示 void lwip_demo(void) {struct netconn *conn;static ip_addr_t ipaddr;uint8_t remot_addr[4];static u16_t port;uint32_t *jpeg_buf;uint32_t jpeg_len;conn netconn_new(NETCONN_TCP); /* 创建一个TCP链接*/netconn_bind(conn, IP_ADDR_ANY, 8088); /* 绑定端口8088号端口*/netconn_listen(conn); /* 进入监听模式*/while (1){err netconn_accept(conn, g_newconn); /* 接收连接请求*/if (err ERR_OK){/* 初始化ATK-MC2640模块*/lwip_camera_init();/* 为JPEG缓存空间申请内存*/jpeg_buf mymalloc(SRAMIN, DEMO_JPEG_BUF_SIZE);delay_ms(1000);while (1){jpeg_len DEMO_JPEG_BUF_SIZE / (sizeof(uint32_t));memset((void *)jpeg_buf, 0, DEMO_JPEG_BUF_SIZE);/* 获取ATK-MC2640模块输出的一帧JPEG图像数据*/atk_mc2640_get_frame((uint32_t)jpeg_buf,ATK_MC2640_GET_TYPE_DTS_32B_INC, NULL);/* 获取JPEG图像数据的长度*/while (jpeg_len 0){if (jpeg_buf[jpeg_len - 1] ! 0){break;}jpeg_len--;}jpeg_len * sizeof(uint32_t);/* 发送JPEG图像数据*/err netconn_write(g_newconn, jpeg_buf, jpeg_len, NETCONN_COPY);if ((err ERR_CLSD) || (err ERR_RST)){myfree(SRAMIN, (void *)jpeg_buf);netconn_close(g_newconn);netconn_delete(g_newconn);break;}vTaskDelay(2); /* 延时2ms */}}} } 上面的代码还是比较简单的首先开发板配置为TCP 服务器模式配置完成且连接成功 之后调用函数atk_mc2640_get_frame 获取ATK-MC2640 模块输出的一帧JPEG 图像数据同时 调用netconn_write 函数把这一帧的图像数据传输至ATK-XCAM 上位机显示。 下载验证 将ATK-MC2640 模块按照前面介绍的连接方式与开发板连接同时将开发板与上位机通 讯的串口连接至PC并将实验代码编译烧录至开发板中如果DHCP 服务器分配完成那么 串口调试助手显示如下信息 图31.2.3.1 串口调试助手显示内容 接下来如果ATK-MC2640 模块初始化成功则会在上位机上显示ATK-MC2640 模块输 出的JPEG 图像如下图所示 图31.2.3.2 网络调试助手显示内容
http://www.pierceye.com/news/463777/

相关文章:

  • 建立网站免费dedecms网站地图制作
  • 网页设计公司网站制作做网站最主要是那个一类商标
  • 卫生局网站建设方案网站架构设计英文翻译
  • 学做衣服网站有哪些智能开发平台软件
  • wordpress 下载站插件wordpress清楚所有评论
  • 公司网站建设工作计划网站设置受信任
  • 网站如何做实名验证码深圳企业网站推广
  • 傻瓜式大型网站开发工具餐饮业手机php网站
  • 网站建设小细节图片东阳网站建设yw126
  • 为什么找不到做网站的软件怎么做音乐mp3下载网站
  • 做一个网站需要什么网络营销方式分析论文
  • 可以做3d电影网站企业网站优化应该怎么做
  • 中山做网站联系电话app客户端开发公司
  • 秦皇岛网站推广价钱南京建设网站制作
  • 2018钓鱼网站建设邢台seo公司
  • 深圳建设交易中心网站域名网站建设
  • 做网站色弱可以吗一个网址多少钱
  • 如何查询网站接入信息产品营销网站
  • 常用博客建站程序遂溪网站开发公司
  • 网站开发软件系统安徽通皖建设工程有限公司网站
  • 意派网站开发新手篇做平面常用的网站
  • 广州网站设计费用深圳室内设计师网
  • 有什么可以做兼职的网站吗建设网站的需求分析
  • 专门做进口产品的网站6wordpress赚钱方法
  • 长兴网站建设公司郫县城乡规划建设管理局网站
  • 天津建设工程信息网站搜索引擎推广是什么工作
  • 网站的系统建设方式网站建设报价表格
  • 商城展示网站建设我劝大家不要学android
  • 官网的建站过程云南网站建设营销
  • 那个网站上有打码的任务做台州做网站的公司