聚丰项目 > 远程蜂箱检测系统
蜂蜜具有促进消化,护肤美容,抗菌消炎,提高免疫力,入药等作用,但是传统蜂蜜的产量却是有限的,因为野中蜂对生存环境也比较苛刻,野中蜂比较喜欢生存在干燥,通风,污染低,蜜源丰富的情况,温度不宜过高,噪声较小,没有刺激性气味的环境下,并且巢虫对蜂蜜的危害比较大,一般会附在蜂箱底部,如果生长过多,就会趴在巢房上,像这种情况下,蜜蜂就会飞逃,或者出现其他环境不适合的情况下也会出现蜂群飞逃的情况,蜂蜜的质量跟酿蜜的时间是成正比的,所以当一年中出现蜜蜂飞逃会严重影响蜂蜜的质量和产量。所以蜂农一般过一段时间就会对蜜蜂的情况进行检查,但是由于时间或者距离等限制不能及时检查,接下来就是我用所学知识对蜜蜂生存环境的检测,基于AB32VG1与VWXR2并搭载RT-Thread操作系统的远程蜂箱检测系统,使用DHT11对蜂箱内的温湿度进行检测,判断蜂箱是否淋雨,使用光敏电阻对蜂箱内的亮度进行检测,是HC-SR04对蜂箱底部进行巢虫检测,并通VWXR2进行远程的数据传输。然后在客户端上查看蜂箱状况。即使出差也不用担心。
jf_15811252
分享jf_15811252
团队成员
理想三旬 嵌入式软件工程师
RT-Thread使用情况概述:
整个方案涉及的技术栈有:主控选型、传感器选型、IO口的分配 、操作系统的选择与运用、API 接口对接、wifi通信协议、整个系统的调试等等。通过这个作品,让我对RT-Thread操作系统的框架,内核启动流程等有了深刻的认识,对面对对象的编程手法有了更深的理解,实现对线程、信号量、互斥量静态和动态的创建和删除与运用,实现ADC、UARTD等设备的运用。
内核部分:使用了线程、信号量、定时器等
组件部分:Fish
软件包:DHT11、HC-SR04
设备驱动:GPIO,ADC,UART等
硬件主要使用了AB32VG1开发板,DHT11温湿度模块,HC-SR04模块,MH检测装置,以及VWXR2三明治开发板等(由于在实验室有些模块没有找到,如4G,声音等,所以对项目进行简化)。
I/O分配情况:
整个系统接线图:
各个模块简介:
AB32VG1:
CPU: AB5301A;( LQFP48 封装,主频 120M,片上集成 RAM 192K, flash 8 Mbit, ADCPWM, USB, UART, IIC 等资源).
搭载蓝牙模块,FM 模块,一路 TF Card 接口,一路 USB 接口,一路 IIC 接口,一路音频接口(美标 CTIA),六路 ADC 输入引脚端子引出,六路 PWM 输出引脚端子引出,一个全彩 LED 灯模块, 一个电源指示灯, 三个烧录指示灯,一个 IRDA(红外接收端口),一个 Reset 按键, 三个功能按键(通用版为两个功能按键)。主要功能是作为主控并且搭载RT-Thread操作系统实现实时性控制。
VWXR2:
内置低功耗32位cpu,可兼做处理器,主频最高支持380MHz
工作电压:2.7-5.5V
外设:9*GPIO,2*Uart,2*ADC
天线支持:板载+可选ipex
采样率:16K/16bit
语音输入:内置2路音频ADC,可直接模拟mic
音频输出:1路
板载音频功放:最大支持2.6W
推荐唤醒距离:<=3m
默认语音技能:天气、百科、日历、计算器、成语、翻译、已支持音乐内容点播支持线性双MIC,间距灵活可调(>40mm),ID和MD结构设计灵活,易集成.
主要功能是实现MCU与客户端的连接,进行数据交互。
DHT11:
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。单线通讯方式,测量精度不怎么样。供电5v不然会出现意想不到的BUG.主要功能是实现蜂箱内部的温湿度采集,让蜂农看到蜂箱内部的情况。
HC-SR04:
采用IO口TRIG触发测距,给至少10us的高电平信号;模块自动发送8个40khz的方波,自动检测是否有信号返回;有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测距范围是2cm-400cm,值得注意的是,供电为5V,不然会出现采集数据不准或者不能采集的情况。实现巢虫检测。
MH采集:
即光照强度采集,模拟量输出,能使数据的处理更灵活,实现蜂箱盖的检测,由于蜜蜂是喜欢黑暗的环境,通过采集光敏电阻的值,并且有用户设置亮度为多少时才触发。
摘要:RT-Thread 是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,RT-Thread 也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。RT-Thread 具备一个 IoT OS 平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过 8亿 台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于 2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。其他的不说,就非常符合国人习惯,RT-Thread Studio 也好用,完善化的论坛,里面有个非常好用的功能就是对一个函数按CTRL键就会跳到相应说明的位置,还有中文的API参考手册等诸多功能,对新手入门特友好。
VWXR2与MCU通信协议
1 串口通信约定
波特率:9600/115200
数据位:8
奇偶校验:无
停止位:1
数据流控:无
MCU:用户控制板控制芯片,与涂鸦模组通过串口进行通信
2 帧格式说明
• 所有大于 1 个字节的数据均采用大端模式传输。
• 一般情况下,采用同命令字一发一收同步机制。
• 模组控制命令下发及 MCU 状态上报则采用异步模式,假设模组控制命令下发的命令字为
x,MCU 状态上报的命令字为 y,如下所示:
•模组控制命令下发
• MCU 状态上报
•数据类型
在本项目中用到了raw(蜂箱盖状态),value(温湿度,电源电压值),bool(巢虫检测)以及defaut(开发板状态)上报数据类型。
/** * @brief raw型dp数据上传 * @param[in] {dpid} dpid号 * @param[in] {value} 当前dp值指针 * @param[in] {len} 数据长度 * @return Null * @note Null */ unsigned char mcu_dp_raw_update(unsigned char dpid,const unsigned char value[],unsigned short len) { unsigned short send_len = 0; if(stop_update_flag == ENABLE) return SUCCESS; // send_len = set_wifi_uart_byte(send_len,dpid); send_len = set_wifi_uart_byte(send_len,DP_TYPE_RAW); // send_len = set_wifi_uart_byte(send_len,len / 0x100); send_len = set_wifi_uart_byte(send_len,len % 0x100); // send_len = set_wifi_uart_buffer(send_len,(unsigned char *)value,len); wifi_uart_write_frame(STATE_UPLOAD_CMD,MCU_TX_VER,send_len); return SUCCESS; } /** * @brief bool型dp数据上传 * @param[in] {dpid} dpid号 * @param[in] {value} 当前dp值 * @return Null * @note Null */ unsigned char mcu_dp_bool_update(unsigned char dpid,unsigned char value) { unsigned short send_len = 0; if(stop_update_flag == ENABLE) return SUCCESS; send_len = set_wifi_uart_byte(send_len,dpid); send_len = set_wifi_uart_byte(send_len,DP_TYPE_BOOL); // send_len = set_wifi_uart_byte(send_len,0); send_len = set_wifi_uart_byte(send_len,1); // if(value == FALSE) { send_len = set_wifi_uart_byte(send_len,FALSE); }else { send_len = set_wifi_uart_byte(send_len,1); } wifi_uart_write_frame(STATE_UPLOAD_CMD, MCU_TX_VER, send_len); return SUCCESS; } /** * @brief value型dp数据上传 * @param[in] {dpid} dpid号 * @param[in] {value} 当前dp值 * @return Null * @note Null */ unsigned char mcu_dp_value_update(unsigned char dpid,unsigned long value) { unsigned short send_len = 0; if(stop_update_flag == ENABLE) return SUCCESS; send_len = set_wifi_uart_byte(send_len,dpid); send_len = set_wifi_uart_byte(send_len,DP_TYPE_VALUE); // send_len = set_wifi_uart_byte(send_len,0); send_len = set_wifi_uart_byte(send_len,4); // send_len = set_wifi_uart_byte(send_len,value >> 24); send_len = set_wifi_uart_byte(send_len,value >> 16); send_len = set_wifi_uart_byte(send_len,value >> 8); send_len = set_wifi_uart_byte(send_len,value & 0xff); wifi_uart_write_frame(STATE_UPLOAD_CMD,MCU_TX_VER,send_len); return SUCCESS; } /** * @brief fault型dp数据上传 * @param[in] {dpid} dpid号 * @param[in] {value} 当前dp值 * @return Null * @note Null */ unsigned char mcu_dp_fault_update(unsigned char dpid,unsigned long value) { unsigned short send_len = 0; if(stop_update_flag == ENABLE) return SUCCESS; send_len = set_wifi_uart_byte(send_len,dpid); send_len = set_wifi_uart_byte(send_len,DP_TYPE_BITMAP); // send_len = set_wifi_uart_byte(send_len,0); if((value | 0xff) == 0xff) { send_len = set_wifi_uart_byte(send_len,1); send_len = set_wifi_uart_byte(send_len,value); }else if((value | 0xffff) == 0xffff) { send_len = set_wifi_uart_byte(send_len,2); send_len = set_wifi_uart_byte(send_len,value >> 8); send_len = set_wifi_uart_byte(send_len,value & 0xff); }else { send_len = set_wifi_uart_byte(send_len,4); send_len = set_wifi_uart_byte(send_len,value >> 24); send_len = set_wifi_uart_byte(send_len,value >> 16); send_len = set_wifi_uart_byte(send_len,value >> 8); send_len = set_wifi_uart_byte(send_len,value & 0xff); } wifi_uart_write_frame(STATE_UPLOAD_CMD, MCU_TX_VER, send_len); return SUCCESS; } /** * @brief 系统所有dp点信息上传,实现APP和muc数据同步 * @param Null * @return Null * @note 此函数SDK内部需调用,MCU必须实现该函数内数据上报功能,包括只上报和可上报可下发型数据 */ void all_data_update(void) { mcu_dp_raw_update(DPID_MEAL_PLAN,0,0); //当前蜂箱状况检测指针,当前蜂箱状况检测数据长度RAW型数据上报; mcu_dp_value_update(DPID_BATTERY_PERCENTAGE,0); //当前电池电量VALUE型数据上报; mcu_dp_bool_update(DPID_CHARGE_STATE,0); //当前充电状态BOOL型数据上报; mcu_dp_enum_update(DPID_COVER_STATE,0); //当前蜂箱盖状态枚举型数据上报; mcu_dp_fault_update(DPID_FAULT,0); //当前故障告警故障型数据上报; mcu_dp_value_update(DPID_FEED_REPORT,0); //当前检测结果上报VALUE型数据上报; mcu_dp_value_update(DPID_VOICE_TIMES,0); //当前语音播放次数VALUE型数据上报; mcu_dp_bool_update(DPID_LIGHT,0); //当前小夜灯BOOL型数据上报; mcu_dp_value_update(DPID_NOW_TEMP,0); //当前温度检测VALUE型数据上报; mcu_dp_value_update(DPID_HUM,90); //当前湿度检测VALUE型数据上报; mcu_dp_bool_update(DPID_CHECK,0); // 当前巢虫检测 BOOL型数据上报; mcu_dp_value_update(DPID_LIGHT,0); //VALUE型数据上报; }
调试:调试过程中我采用的是一个传感器一个传感器的调,出错容易找,然后在进行多个传感器的融合
创建好工程后,编写按键(实现按键VWXR2复位与配网模式切换),LED(实现AB32VG1开发板正常运行观察,避免出现卡死情况然后并不知道哪里出了问题)功能。
//贴的主要代码,全部代码已经放入gitee static void key_thread_entry(void* p) { uint8_t byn_value = 0; while(1) { byn_value = btn_scan(0); switch(byn_value) { case KEY0_PRES: rt_kprintf("key0 pushed\n"); mcu_reset_wifi(); rt_kprintf("mcu_reset_wifi\n");//使VWXR2开发板复位,并进入配网模式 break; case KEY1_PRES: rt_kprintf("key1 pushed\n"); mcu_set_wifi_mode(0); rt_kprintf("smart_wifi_mode");//实现智能配网 break; default: break; } rt_thread_mdelay(100); } } int rt_thread_key(void) { rt_thread_t key_ret = RT_NULL; key_init(); key_ret = rt_thread_create("key", key_thread_entry, RT_NULL, 512, 9, 10); if(key_ret == RT_NULL) { rt_kprintf("key init ERROR"); return RT_ERROR; } else { rt_kprintf("rt_thread_key succeed....\n"); } rt_thread_startup(key_ret); } INIT_APP_EXPORT(rt_thread_key);
打开调试助手观察串口输出情况
按下key0后VWXR2开发板会播报“我已进入配网状态,请下载APP为我完成配网”,演示如下:
2.DHT11调试与数据上报
在RT-Thread Setting中添加DHT11的sdk包,选择第一个并保存
配置自己分配的引脚
删掉SDK包中的void rt_hw_us_delay(rt_uint32_t us)函数
在board.c中添加自己的硬件定时器us函数,uint8_t等会报错,在前面加上rt_就可以了
void rt_hw_us_delay(rt_uint32_t us) { rt_uint32_t ticks; rt_uint32_t told, tnow, tcnt = 0; rt_uint32_t reload = TMR0PR; ticks = us * reload / (1000); told = TMR0CNT; while (1) { tnow = TMR0CNT; if (tnow != told) { if (tnow > told) { tcnt += tnow - told; } else { tcnt += reload - told + tnow; } told = tnow; if (tcnt >= ticks) { break; } } } }
观察串口输出
测试成功,在线程中添加数据上报代码
#define DHT11_DATA_PIN GET_PIN(A, 0) static void read_temp_entry(void *parameter) { rt_device_t dev = RT_NULL; struct rt_sensor_data sensor_data; rt_size_t res; rt_uint8_t get_data_freq = 1; /* 1Hz */ dev = rt_device_find("temp_dht11"); if (dev == RT_NULL) { return; } if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("open device failed!\n"); return; } rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)(&get_data_freq)); while (1) { res = rt_device_read(dev, 0, &sensor_data, 1); if (res != 1) { rt_kprintf("read data failed! result is %d\r\n", res); rt_device_close(dev); return; } else { if (sensor_data.data.temp >= 0) { uint8_t temp = (sensor_data.data.temp & 0xffff) >> 0; // get temp uint8_t humi = (sensor_data.data.temp & 0xffff0000) >> 16; // get humi rt_kprintf("temp:%d, humi:%d\n" ,temp, humi); mcu_dp_value_update(DPID_NOW_TEMP,temp); //当前温度检测VALUE型数据上报; mcu_dp_value_update(DPID_HUM,humi); //当前湿度检测VALUE型数据上报; } } rt_thread_delay(3000); } } static int dht11_read_temp_sample(void) { rt_thread_t dht11_thread; dht11_thread = rt_thread_create("dht_tem",read_temp_entry, RT_NULL,1024,13,20); if(dht11_thread==RT_NULL) { rt_kprintf("dht11_thread fail....\n"); } else { rt_kprintf("dht11_thread succeed....\n"); } rt_thread_startup(dht11_thread); } INIT_APP_EXPORT(dht11_read_temp_sample); static int rt_hw_dht11_port(void) { struct rt_sensor_config cfg; cfg.intf.user_data = (void *)DHT11_DATA_PIN; rt_hw_dht11_init("dht11", &cfg); return RT_EOK; } INIT_COMPONENT_EXPORT(rt_hw_dht11_port);
打开手机APP,观察数据
app对温度进行了一个缩小10的处理
3.HC-SR04
添加SDK包,并且修改引脚,并进行数据上报
#define SR04_TRIG_PIN GET_PIN(E, 0) #define SR04_ECHO_PIN GET_PIN(A, 6) int sr04_read_distance_sample(void); int rt_hw_sr04_port(void); static void sr04_read_distance_entry(void *parameter) { rt_device_t dev = RT_NULL; struct rt_sensor_data sensor_data; rt_size_t res; rt_uint16_t distance=0; dev = rt_device_find(parameter); if (dev == RT_NULL) { rt_kprintf("Can't find device:%s\n", parameter); return; } if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK) { rt_kprintf("open device failed!\n"); return; } rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100); while (1) { res = rt_device_read(dev, 0, &sensor_data, 1); if (res != 1) { rt_kprintf("read data failed!size is %d\n", res); rt_device_close(dev); return; } else { distance=sensor_data.data.proximity / 10+sensor_data.data.proximity % 10; if(distance>=20&&distance<=400) //rt_kprintf("distance:%3d.%dcm, timestamp:%5d\n", sensor_data.data.proximity / 10, sensor_data.data.proximity % 10, sensor_data.timestamp); if(distance>30) mcu_dp_bool_update(DPID_CHECK,0); // 当前巢虫检测 BOOL型数据上报; else { mcu_dp_bool_update(DPID_CHECK,1); // 当前巢虫检测 BOOL型数据上报; } } rt_thread_mdelay(2000); } } int sr04_read_distance_sample(void) { rt_thread_t sr04_thread; sr04_thread = rt_thread_create("sr04", sr04_read_distance_entry, "pr_sr04", 1024, 11, 20); if (sr04_thread != RT_NULL) { rt_thread_startup(sr04_thread); } return RT_EOK; } INIT_APP_EXPORT(sr04_read_distance_sample);
4.AB32VG1与VWXR2之间的通信
#include"uart1.h" /* 用于接收消息的信号量 */ static struct rt_semaphore rx_sem; rt_device_t serial; /* 接收数据回调函数 */ static rt_err_t uart_input(rt_device_t dev, rt_size_t size) { /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ rt_sem_release(&rx_sem); return RT_EOK; } static void serial_thread_entry(void *parameter) { char ch; rt_kprintf("test UART1 \n"); while (1) { /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */ while (rt_device_read(serial, -1, &ch, 1) != 1) { /* 阻塞等待接收信号量,等到信号量后再次读取数据 */ rt_sem_take(&rx_sem, RT_WAITING_FOREVER); } uart_receive_input(ch); } } static int uart_sample(int argc, char *argv[]) { rt_err_t ret = RT_EOK; char uart_name[RT_NAME_MAX]; char str[] = "hello RT-Thread!\r\n"; if (argc == 2) { rt_strncpy(uart_name, argv[1], RT_NAME_MAX); } else { rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); } /* 查找系统中的串口设备 */ serial = rt_device_find(uart_name); if (!serial) { rt_kprintf("find %s failed!\n", uart_name); return RT_ERROR; } /* 初始化信号量 */ rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); /* 以中断接收及轮询发送模式打开串口设备 */ rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); /* 设置接收回调函数 */ rt_device_set_rx_indicate(serial, uart_input); /* 发送字符串 */ rt_device_write(serial, 0, str, (sizeof(str) - 1)); /* 创建 serial 线程 */ rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 7, 10); /* 创建成功则启动线程 */ if (thread != RT_NULL) { rt_kprintf("uart1_thread succeed....\n"); rt_thread_startup(thread); } else { ret = RT_ERROR; } return ret; } //在protel.c中添加单字节函数,进行数据的上报 /** * @brief 串口发送数据 * @param[in] {value} 串口要发送的1字节数据 * @return Null */ void uart_transmit_output(unsigned char value) { rt_device_write(serial, 0,&value, 1);//单字节上报 }
程序写好后,打开涂鸦调试助手,选择模拟模组,因为我要调试我写的程序是否和模组通信
VWXR2与串口接线如图所示
当调试助手出现下面这个界面时,证明程序没问题,然后就可以和模组连接了
项目代码地址:
https://gitee.com/AB32VG1/ab32.git