完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
一、前言
其实在2019年8月份就写了一篇《基于rt-thread使用nrf24l01实现多点通信》,详细记录了怎么修改nrf24l01软件包实现多点通讯,来采集多个18B20节点的温度数据的功能。使用文件系统进行数据存储、使用OneNet软件包与OneNet云端交互也是在那个时候就完成的。 那为什么当时没写呢?没别的原因 ,就是 赖 赖 赖!!!!!拖 拖 拖 !!!! 那为什么现在又要写了呢?是因为当我现在再来看之前做的这个项目的时候,都不敢相信是自己做的,对于使用的各个软件包功能、实时过程中的一些细节等问题当时是记得非常清楚理得非常顺的,但时间一久到现在完全忘了,如同过眼云烟,再去理的时候相当痛苦。再加上最近想在单片机上实现一个web功能,所以准备重拾当时的这个项目,借此文档重新梳理一遍。备忘!!! 二、硬件环境 STM32F103ZET6:512KFALSH、64KSRAM。正点原子精英开发板 SD Card、ESP8266模块、NRF24L01模块 三、功能描述 1、利用nrf24l01无线组网实现多点通讯,采集多个18B20节点温度数据 2、使用文件系统,存储采集的温度数据 3、使用esp8266-wifi模块将接收节点的数据传输至OneNet云 4、OneNet云的简单应用开发,实现远程监控 四、组件与软件包列表
简介 https://www.rt-thread.org/document/site/programming-manual/sal/sal/ 2、netdev 组件 简介 https://www.rt-thread.org/document/site/programming-manual/netdev/netdev/ Socket 套接字描述符的创建建立在 netdev 网卡基础上,所以每个创建的 Socket 对应唯一的网卡。协议簇、网卡和 socket 之间关系如下图所示: 3、AT 组件 简介 https://www.rt-thread.org/document/site/programming-manual/at/at/ 通过 AT 组件,设备可以作为 AT Client 使用串口连接其他设备发送并接收解析数据,可以作为 AT Server 让其他设备甚至电脑端连接完成发送数据的响应,也可以在本地 shell 启动 CLI 模式使设备同时支持 AT Server 和 AT Client 功能,该模式多用于设备开发调试。 4、at device软件包 简介 https://github.com/RT-Thread-packages/at_device 示例说明 https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/ 依赖
AT device 软件包目前已经发布多个版本,各个版本之间选项配置方式和其对应的系统版本有所不同,下面主要列出当前可使用的软件包版本信息: V1.2.0:适用于 RT-Thread 版本小于 V3.1.3,AT 组件版本等于 V1.0.0; V1.3.0:适用于 RT-Thread 版本小于 V3.1.3,AT 组件版本等于 V1.1.0; V1.4.0:适用于 RT-Thread 版本小于 V3.1.3或等于 V4.0.0, AT 组件版本等于 V1.2.0; V1.5.0:适用于 RT-Thread 版本小于 V3.1.3 或等于 V4.0.0, AT 组件版本等于 V1.2.0; V1.6.0:适用于 RT-Thread 版本等于 V3.1.3 或等于 V4.0.1, AT 组件版本等于 V1.2.0; V2.0.0/V2.0.1:适用于 RT-Thread 版本大于 V4.0.1 或者大于 3.1.3, AT 组件版本等于 V1.3.0; laster:只适用于 RT-Thread 版本大于V4.0.1 或者大于 3.1.3, AT 组件版本等于 V1.3.0; 注意事项 1、AT device 软件包适配的模块暂时不支持作为 TCP Server 完成服务器相关操作(如 accept 等); 2、AT device 软件包默认设备类型为未选择,使用时需要指定使用设备型号; 3、laster 版本支持多个选中多个 AT 设备接入实现 AT Socket 功能,V1.X.X 版本只支持单个 AT 设备接入。 4、AT device 软件包目前多个版本主要用于适配 AT 组件和系统的改动,推荐使用最新版本 RT-Thread 系统,并在 menuconfig 选项中选择 latest 版本 at device与AT 组件(AT Client & AT Socket)、SAL组件、netdev组件的关系 对于AT设备,应用层在调用SAL 提供的BSP网络接口的时候,其实调用的就是 AT Socket(AT设备标准的 BSD Socket API),而AT Socket是借助于netdev组件来实现的,并不是在 at device软件包中封装了at_device(AT设备的句柄)中的3个ops功能就直接提供给了AT Socket;AT Device是先基于AT Client实现了at_device中3个ops功能 , 再将at_device 注册到 at_device_list / at_device.c链表中,最后通过netdev组件抽象网卡的功能将at_device中的netdev注册到网卡列表 netdev_list 中; 通常应用程序在使用AT设备的BSP Socket(AT Socket)的时候,会先申请一个socket,这个时候会从默认网卡netdev_default 或在 网卡列表netdev_list 中根据协议族去找到对应的网卡,再根据网卡信息从at_device_list 链表中获取到该网卡对应的at_device,接下来的AT Socket操作就是利用at_device中的3个ops来实现的。拿ec20设备来具体分析下实现过程: 第一步:在ec20的class文件at_device_ec20.c和at_socket_ec20.c中,基于at client分别实现了ec20_device_ops、ec20_netdev_ops 、ec20_socket_ops 结构中的功能。 const struct at_device_ops ec20_device_ops = { ec20_init, ec20_deinit, ec20_control, }; const struct netdev_ops ec20_netdev_ops = { ec20_netdev_set_up, ec20_netdev_set_down, RT_NULL, ec20_netdev_set_dns_server, RT_NULL, #ifdef NETDEV_USING_PING ec20_netdev_ping, #endif #ifdef NETDEV_USING_NETSTAT ec20_netdev_netstat, #endif }; static const struct at_socket_ops ec20_socket_ops = { ec20_socket_connect, ec20_socket_close, ec20_socket_send, ec20_domain_resolve, ec20_socket_set_event_cb, }; 第二步:在at_device_ec20.c中,系统自动初始化 INIT_DEVICE_EXPORT(ec20_device_class_register) 将 at_device_class(ec20_device_ops +ec20_socket_ops )自动注册到了at_device_class_list / at_device.c 链表中 struct at_device_class { uint16_t class_id; /* AT device class ID */ const struct at_device_ops *device_ops; /* AT device operaiotns */ #ifdef AT_USING_SOCKET uint32_t socket_num; /* The maximum number of sockets support */ const struct at_socket_ops *socket_ops; /* AT device socket operations */ #endif rt_slist_t list; /* AT device class list */ }; 第三步:在应用程序中调用 at_device_register / at_device.c ,调用过程如下: 1、在应用程序中定义at_device_ec20 的实例 e0 struct at_device_ec20 { char *device_name; char *client_name; int reset_pin;//2019、9、10 add by denghengli int power_pin; int power_status_pin; size_t recv_line_num; struct at_device device; void *socket_data; void *user_data; }; 2、调用 at_device_register / at_device.c 完成了 e0 中 at_device 中 at_socket 内存申请、将 at_device_class (c20_socket_ops + ec20_device_ops) 从at_device_class_list中读出赋值 at_device中的class、将 at_device 注册到 at_device_list / at_device.c链表中 struct at_device { char name[RT_NAME_MAX]; /* AT device name */ rt_bool_t is_init; /* AT device initialization completed */ struct at_device_class *class; /* AT device class object */ struct at_client *client; /* AT Client object for AT device */ struct netdev *netdev; /* Network interface device for AT device */ #ifdef AT_USING_SOCKET rt_event_t socket_event; /* AT device socket event */ struct at_socket *sockets; /* AT device sockets list */ #endif rt_slist_t list; /* AT device list */ char (*init_succ_ind)(void); void *user_data; /* User-specific data */ }; 3、调用ec20_init的函数指针,完成 e0 中 at_device 中 at_client 的初始化、netdev 网卡的注册、ec20入网使能 at_client的初始化,创建了client_parser线程,作用为ec20与串口之间的数据交互,是ops功能实现的前提 struct at_client { rt_device_t device; at_status_t status; char end_sign; /* the current received one line data buffer */ char *recv_line_buf; /* The length of the currently received one line data */ rt_size_t recv_line_len; /* The maximum supported receive data length */ rt_size_t recv_bufsz; rt_sem_t rx_notice; rt_mutex_t lock; at_response_t resp; rt_sem_t resp_notice; at_resp_status_t resp_status; struct at_urc_table *urc_table; rt_size_t urc_table_size; rt_thread_t parser; }; netdev网卡注册,netdev_ops = ec20_netdev_ops 和 sal_user_data 的赋值 /* network interface device object */ struct netdev { rt_slist_t list; char name[RT_NAME_MAX]; /* network interface device name */ ip_addr_t ip_addr; /* IP address */ ip_addr_t netmask; /* subnet mask */ ip_addr_t gw; /* gateway */ #if NETDEV_IPV6 ip_addr_t ip6_addr[NETDEV_IPV6_NUM_ADDRESSES]; /* array of IPv6 addresses */ #endif /* NETDEV_IPV6 */ ip_addr_t dns_servers[NETDEV_DNS_SERVERS_NUM]; /* DNS server */ uint8_t hwaddr_len; /* hardware address length */ uint8_t hwaddr[NETDEV_HWADDR_MAX_LEN]; /* hardware address */ uint16_t flags; /* network interface device status flag */ uint16_t mtu; /* maximum transfer unit (in bytes) */ const struct netdev_ops *ops; /* network interface device operations */ netdev_callback_fn status_callback; /* network interface device flags change callback */ netdev_callback_fn addr_callback; /* network interface device address information change callback */ #ifdef RT_USING_SAL void *sal_user_data; /* user-specific data for SAL */ #endif /* RT_USING_SAL */ void *user_data; /* user-specific data */ }; ec20入网使能,创建了ec20_init_thread_entry线程设备联网,联网成功后退出。在联网成功后接着创建了ec20_check_link_status_entry线程用为监测网络状态并更新网卡状态,可以在这里做一些网络连接异常后硬件复位、重新初始化模块,通过监测网络状态来实现掉卡重连的功能,但是一般这里监测的网络状态不是实时的,因为一般蜂窝模块设备 SIM 卡去掉之后不会实时同步模块内部状态,只有等待模块内部信息改变了,netdev 网卡信息才会同步,所以最好还是通过心跳机制来监测链路的异常情况。 5、pahomqtt软件包 简介 https://github.com/RT-Thread-packages/paho-mqtt 工作原理 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/principle.md 使用指南 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/user-guide.md 示例说明 https://github.com/RT-Thread-packages/paho-mqtt/blob/master/docs/samples.md 6、onenet软件包 简介 https://github.com/RT-Thread-packages/onenet 工作原理 https://github.com/RT-Thread-packages/onenet/blob/master/docs/principle.md 使用指南 https://github.com/RT-Thread-packages/onenet/blob/master/docs/user-guide.md 示例说明 https://github.com/RT-Thread-packages/onenet/blob/master/docs/samples.md 注意事项 1、设置命令响应回调函数之前必须要先调用onenet_mqtt_init初始化函数,因为在onenet_mqtt_init初始化函数里会将命令响应回调函数指向RT_NULL。所以如果在onenet_mqtt_init初始化函数之前设置了命令响应回调函数是不会生效的 2、命令响应回调函数里存放响应内容的 buffer 必须是 malloc 出来的,因为 在发送完响应内容后,程序会将这个 buffer 释放掉。 static void onenet_cmd_rsp(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size){ char resp_buf[] = { "cmd is received!n" }; LOG_D("recv data is %.*sn", recv_size, recv_data); /* user have to malloc memory for response data */ *resp_data = (uint8_t *) rt_malloc(strlen(resp_buf)); strncpy((char *)*resp_data, resp_buf, strlen(resp_buf)); *resp_size = strlen(resp_buf);} 3、在向主题发布数据的时候,并不能在onenet_mqtt_init 初始化成功之后就可以开始肆无忌惮的发布数据了,需要判断MQTT是否真正连接成功了,也就是是否执行了mqtt_online_callback上线回调函数,只有在执行了之后才能开始发布;一旦MQTT连接接断开,会执行mqtt_offline_callback下线回调函数,这时应该则停止发布数据,否则就会发布失败出现各种问题 如下图;还有可能就是MQTT就一直连接不成功,这时如果不判断就开始发布数据也会出现问题。具体原因看看onenet_mqtt_init 初始化中到底干了啥就清除了
五、应用实现 1、nrf24l01温度数据采集 static void nrf24l01_thread_entry(void* parameter){ struct nrf_msg nrf24l01_msg; struct temp_data temp_data; struct nrf24_cfg nrf24l01_cfg; int rlen; char rbuf[32 + 1], tmp_buf[50]; char tbuf[32] = "firstrn"; char res=0,cnt = 0; uint8_t chnum=0; nrf24l01_dev = (rt_spi_wire_device_t)rt_device_find(SPI_WIRE_DEV_NAME); if (nrf24l01_dev == RT_NULL) { LOG_E("Can't find device:%sn", SPI_WIRE_DEV_NAME); return; } nrf24l01_default_param(&nrf24l01_cfg); nrf24l01_cfg.selchx = 0X3F; nrf24l01_cfg.use_irq = 1; nrf24l01_cfg.role = ROLE_PRX; nrf24l01_dev->nrf24l01.ops->nrf_init(nrf24l01_cfg, NRF24L01_CE_PIN, NRF24L01_IRQ_PIN); while (1) { rt_memset(rbuf, 0, sizeof(rbuf)); rlen = nrf24l01_dev->nrf24l01.ops->prx_cycle((uint8_t*)rbuf, (uint8_t*)tbuf, rt_strlen((char *)tbuf), &chnum); if (rlen > 0) // received data (also indicating that the previous frame of data was sent complete) { /* 打印接收到的数据 */ rt_memset(tmp_buf, 0, sizeof(tmp_buf)); rt_sprintf(tmp_buf, "nrf24l01 pipe(%d) data:%sn", (char)chnum, rbuf); LOG_D((char *)tmp_buf); /* 准备应答的数据 */ rt_sprintf((char *)tbuf, "i-am-PRX:%dthn", cnt++); /* 通过sscnaf解析收到的数据 */ res = sscanf((char *)rbuf, "%d,+%f", &temp_data.timestamp, &temp_data.temperature); if(res != 2) { /* 通过sscnaf解析收到的数据 */ if(sscanf((char *)rbuf, "%d,-%f", &temp_data.timestamp, &temp_data.temperature) != 2) { continue; } temp_data.temperature = -temp_data.temperature; } rt_memset(tmp_buf, 0, sizeof(tmp_buf)); sprintf(tmp_buf, "%d,%fn", temp_data.timestamp, temp_data.temperature); /* 将数据存放到ringbuffer里 */ if (chnum == 0) rt_ringbuffer_put(nrf_p0_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); else if (chnum == 1) rt_ringbuffer_put(nrf_p1_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); else if (chnum == 2) rt_ringbuffer_put(nrf_p2_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); else if (chnum == 3) rt_ringbuffer_put(nrf_p3_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); else if (chnum == 4) rt_ringbuffer_put(nrf_p4_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); else if (chnum == 5) rt_ringbuffer_put(nrf_p5_ringbuf, (rt_uint8_t *)tmp_buf, strlen(tmp_buf)); /* 收到数据,并将数据存放到ringbuffer里后,才发送事件, 用于通知DFS线程存储数据 */ rt_event_send(nrf_event, WRITE_EVENT); /* 判断onenet是否上报完成,不会等待 */ if (rt_sem_take(onenet_upok_sem, 0) == RT_EOK) { nrf24l01_msg.chnum = chnum; nrf24l01_msg.data.timestamp = temp_data.timestamp; nrf24l01_msg.data.temperature = temp_data.temperature; rt_mq_send(nrf_msg_mq, &nrf24l01_msg, sizeof(struct nrf_msg)); } } else // no data { // LOG_E("nrf not receivedn"); } rt_thread_mdelay(100); }} 2、onenet数据上报 static void onenet_trans_thread_entry(void* parameter){ struct nrf_msg nrf24l01_msg; char tmp_buf[100], stream_name[20] = ""; /* 永久等待方式接收信号量,若收不到,该线程会一直挂起 */ rt_sem_take(mqttinit_sem, RT_WAITING_FOREVER); /* 后面用不到这个信号量了,把它删除了,回收资源 */ rt_sem_delete(mqttinit_sem); rt_sem_release(onenet_upok_sem); while (1) { if (rt_mq_recv(nrf_msg_mq, &nrf24l01_msg, sizeof(struct nrf_msg), RT_WAITING_FOREVER) == RT_EOK) { rt_sprintf(stream_name, "temperature_p%d", nrf24l01_msg.chnum); /* 上传发送节点1的数据到OneNet服务器,数据流名字是temperature_p0 */ if (onenet_mqtt_upload_digit(stream_name, nrf24l01_msg.data.temperature) != RT_EOK) { /* 打印接收到的数据 */ rt_memset(tmp_buf, 0, sizeof(tmp_buf)); sprintf(tmp_buf, "upload %s has an error, try againn", stream_name); LOG_D((char*)tmp_buf); } else { rt_memset(tmp_buf, 0, sizeof(tmp_buf)); sprintf(tmp_buf, "upload %s OK >>> temp:%fn", stream_name, nrf24l01_msg.data.temperature); LOG_D((char*)tmp_buf); } rt_thread_delay(rt_tick_from_millisecond(1000)); /* onenet上报成功之后,释放信号量告知nrf线程可以往消息队列发送消息了 */ rt_sem_release(onenet_upok_sem); } }}static void onenet_init_thread_entry(void* parameter){ char onenet_mqtt_init_failed_times = 0; /* mqtt初始化 */ while (1) { if (!onenet_mqtt_init()) { /* 注册平台下行命令响应回调函数 */ onenet_set_cmd_rsp_cb(onenet_cmd_rsp); /* mqtt初始化成功之后,释放信号量告知onenet_upload_data_thread线程可以上传数据了 */ rt_sem_release(mqttinit_sem); return; } rt_thread_mdelay(100); LOG_E("onenet mqtt init failed %d times", onenet_mqtt_init_failed_times++); }} 六、结果展示 1、平台设备数据流展示 2、平台应用展示 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1792 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1626 浏览 1 评论
1094 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
732 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1682 浏览 2 评论
1943浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
740浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
577浏览 3评论
600浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
562浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-26 05:25 , Processed in 0.680669 second(s), Total 46, Slave 40 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号