• [活动公告] 【有奖互动】小熊派云上奇遇记,参与IoT/嵌入式开发话题互动,赢小熊派星闪开发板!(活动已结束,获奖名单公示中)
    感谢评论区各位开发者精彩的内容分享让我们充分感受到了IoT技术开发的魅力与力量如果不是奖品数量有限,真想给每一位开发者都颁发奖品~~经过IoT技术专家评审 恭喜:林欣、小修 两位开发者成为本次优质内容创作者本次活动奖品小熊派星闪开发板将按照问卷提供的地址在公示期结束后(2024.5.10)进行邮寄也欢迎大家持续关注华为云开发者社区论坛活动哦!———————————————————————————————————————活动已结束,火热评奖中感谢各位开发者的积极参与获奖名单将于5月6日前公布哦!———————————————————————————————————————一块掌心大小的开发板可以做什么?给物联网开发爱好者,他们能将普通门锁改造成为智能指纹门锁,让家里的花花草草自动浇水给专业工程师,他们能开发出脑卒中患者步态评估等辅助医疗诊断设备,让问诊更加严谨高效若是给资深极客发明家,他们能爆改出威力无穷的机械臂,变身为野生钢铁侠体验云和物联网交融的奇妙开发之旅,你曾有过哪些嵌入式开发经历?在本帖分享可赢取小熊派最新产品——星闪开发板,快来讲讲吧!【互动方式】即日起——4月28日23:59,在本帖分享300字以上自己的嵌入式开发故事(如果附上相关视频、文章,中奖概率翻倍哦!),评选2名优质内容创作者,送小熊派星闪开发板一个~嵌入式开发故事主题满足下方任一内容即可:使用华为云产品进行物联网开发参加华为云赛事活动进行物联网开发通过小熊派开发板做过的有趣的项目开发<<更多精彩>>参与华为云开发者联盟账号今日头条、微博转发小熊派云上奇遇记内容,还有机会获得华为云云宝盲盒哦!想了解更多华为云IoT物联网开发者故事,请查看《小熊派的云上奇遇记》​【互动礼品】小熊派星闪开发板小熊派星闪(NearLink)开发板,使用了中国原生的新一代无线短距通信技术。与传统短距传输技术方案相比,星闪在功耗、速度、覆盖范围和连接性能全面领先,可以在智能终端、智能家居、智能汽车、智能制造等各类细分场景下实现更极致的用户体验。【注意事项】1、所有参与活动的内容,如发现为抄袭内容或水文,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2024年5月10日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、如您在体验中发现任何体验不友好、产品Bug、文档页面错漏等情况,欢迎通过云声平台反馈给我们,还有机会领取云声专属礼品!7、其他事宜请参考【华为云社区常规活动规则】。
  • [问题求助] 提示我设备未激活,然后调试命令失败,该怎么处理
    提示我设备未激活,然后调试命令失败,该怎么处理
  • [问题求助] IoT物联网,做智能水表项目,用户要能跟电表一样,看到每小时的用水情况
    IoT物联网,做智能水表项目,用户要能跟电表一样,看到每小时的用水情况想咨询下,1. 智能水表的数据一般是每隔多久上发一次比较好?2. 服务器需要买多少带宽?3. 统计是精确到分钟的统计,还是小时?
  • [技术干货] 绿色再生·安卓4G智能远程操作巡视机器人小车
    一、前言1.1 项目介绍【1】项目功能介绍随着物联网技术与移动通信技术的快速发展,远程遥控设备在日常生活及工业应用中的普及度日益提高。无论是家用扫地机器人实现自主导航清扫,还是目前抖音平台上展示的实景互动小车等创新应用,都体现了远程控制和实时视频监控技术对现代智能设备的重要性。本项目设计并实现一款基于STM32微控制器的远程遥控安卓小车系统。该系统充分利用了淘汰下来的安卓旧手机作为车载信息处理单元,不仅实现了资源的有效再利用,还结合4G网络技术以及先进的流媒体服务和物联网技术,搭建起一套集远程操控、实时视频音频传输于一体的高效解决方案。项目的小车搭载了STM32主控板以精确控制四个电机的动作,通过L298N驱动芯片确保了底座移动的稳定性和灵活性。同时,小车的动力源采用两节18650锂电池提供充足的电力支持。车载的旧安卓手机通过USB线连接到STM32主控板上,接收并执行来自远端手机APP的指令。这款由Qt开发的Android APP能够利用4G网络实现实时在线,并通过摄像头采集音视频数据,通过RTMP协议将这些数据推送到华为云ECS服务器上的NGINX流媒体服务器,从而实现高清流畅的远程视频监控。为了实现双向交互和低延迟控制,整个系统还借助MQTT协议连接至华为云IOT服务器。另一台安装了同样由Qt开发的Android手机APP的终端设备,可以通过该APP拉取小车端的实时音视频流进行播放,并通过方向键菜单实现对小车的精准远程操控。这种设计不仅极大地拓展了传统遥控小车的功能性与实用性,还为其他类似应用场景提供了可借鉴的技术框架。当前设计的这种基于4G网络设计的远程遥控巡检小车的技术应用场景主要是: 安全防护、环境监测、设备巡检、物料搬运、应急救援这些地方。(1)远程监控:可应用于安全防护、环境监测、农业监控等领域,例如森林防火、农田灌溉管理、危险区域侦查等,通过实时视频和音频传输,工作人员可以在远程位置对现场情况进行实时了解和操控。(2)工业巡检:在工厂、仓库或大型设施内部署此类小车,用于设备巡检、物料搬运或生产流程监控,尤其适合那些人员不易到达或者存在安全隐患的地方。(3)应急救援:在地震、火灾等灾害现场,远程遥控小车可用于进入倒塌建筑内部搜寻生命迹象,或是携带传感器测量有害气体浓度等,为救援决策提供及时信息下面是当前小车整体技术框架:小车的模型:本次设计里放在小车终端上的Android手机采用的是小米4C,一款普通的Android手机:2015年上市的小米4C。 从本身价值来讲,现在在某鱼上差不多是200块,本身就是一个完整的系统。性价比非常高。比去单独买Linux开发板进行模型开发实验来说,成本低低很多。 主流的Linux、Android开发板价格都比较贵的。开发过程中,测试遥控效果:【2】设计实现的功能(1)STM32主控板功能:控制4个电机:STM32通过L298N驱动芯片驱动4个电机,实现小车的前进、后退、左转、右转等动作。数据通信:STM32通过USB接口与安卓手机通信,接收手机APP发送的控制指令,并将小车的状态信息(如电量、速度、位置等)发送回手机。电源管理:管理2节18650锂电池的供电,确保电压稳定并监控电池电量。(2)安卓手机APP功能控制指令下发:手机APP通过USB接口向STM32发送控制指令,控制小车的动作。视频和音频流获取:APP从手机摄像头和麦克风获取视频和音频流,并进行编码处理。流媒体推流:通过RTMP协议将编码后的视频和音频流推送到华为云ECS服务器+NGINX搭建的RTMP流媒体服务器。MQTT连接:APP通过MQTT协议与华为云IOT服务器建立连接,实现双向通信。(3)华为云服务器功能:RTMP流媒体服务:接收并转发安卓手机APP推送的视频和音频流。MQTT服务:作为MQTT消息代理,实现远程手机与STM32主控板之间的通信。(4)远程Android手机APP功能:实时视频和音频播放:从华为云ECS服务器拉取视频和音频流,并实时显示在APP界面上。MQTT连接:与华为云IOT服务器建立连接,接收STM32主控板发送的小车状态信息。远程控制:提供方向键控制菜单,允许用户远程控制小车前进、后退、转弯等动作。【3】项目硬件模块组成(1)电源模块:电池组:采用两节18650锂电池作为供电源,它们具有高能量密度、体积小的特点,能够为整个系统提供稳定的直流电能。(2)主控模块:STM32微控制器:这是整个小车的核心控制单元,负责处理所有的逻辑运算和数据通信任务。通过编程实现对电机驱动、USB通信、网络连接等功能的控制。(3)电机驱动模块:L298N驱动芯片:用于驱动底座上的四个电机,L298N是一个高性能的H桥电机驱动器,可以接收来自STM32的信号,转换为足够驱动电机工作的电流和电压,并且支持电机正反转及速度调节。(4)移动平台模块:四个直流电机:直接安装在小车底座上,通过L298N驱动进行精确的速度和方向控制,以实现小车前进、后退、左右转弯等运动功能。(5)通信模块:USB接口:STM32主控板通过USB线与安卓手机物理连接,实现数据传输,接收来自手机APP的控制指令。4G模组:集成在安卓手机内部,插入SIM卡后可实现高速无线网络连接,使小车能够在远程环境下通过互联网与其他设备通信。(6)多媒体采集模块:安卓手机摄像头:用于捕捉实时视频和音频信息,是小车端环境感知的关键组件。(7)云服务交互模块:华为云ECS服务器+NGINX RTMP流媒体服务器:小车端将采集到的音视频流推送到华为云服务器上,通过RTMP协议实现实时音视频的低延迟传输和分发。华为云IOT服务器:小车和远端控制手机均通过MQTT协议与之建立连接,实现远程数据交换和控制命令的下发。【3】功能总结(1)电机驱动与控制:通过STM32微控制器和L298N驱动芯片,实现对小车上四个电机的精确控制,包括前进、后退、左转、右转等动作,从而控制小车的移动方向和速度。(2)无线通信与数据传输:STM32与安卓手机之间通过USB接口建立通信,实现控制指令的下发和小车状态信息的上传。同时,安卓手机通过4G网络连接到华为云服务器,实现了远程控制命令的远程传输和视频音频流的推送。(3)流媒体推流与播放:安卓手机APP能够捕获手机摄像头和麦克风的视频和音频流,通过RTMP协议推送到华为云服务器。另一台安卓手机APP则从服务器拉取这些流,实现实时播放,从而允许用户远程观看小车的实时画面和音频。(4)华为云服务器支持:华为云ECS服务器和NGINX搭建的RTMP流媒体服务器负责接收、转发视频和音频流,确保流媒体的稳定性和实时性。同时,华为云IOT服务器通过MQTT协议提供消息代理服务,实现远程手机与STM32之间的双向通信。(5)用户界面与交互设计:安卓手机APP提供了直观的用户界面,包括控制按钮、状态显示、视频播放器等,使用户能够方便地对小车进行控制、观看视频、监听音频,以及监控小车的状态信息。(6)远程控制:通过结合STM32的电机控制、华为云服务器的数据处理和传输,以及安卓手机的用户界面和交互设计,实现了从远程手机到小车的远程控制功能。用户可以在远离小车的地点,通过手机APP发出控制指令,实时观察小车的动作和周围环境。1.2 设计思路1.3 系统功能总结自主供电与移动控制采用2节18650锂电池为小车提供电力供应;STM32微控制器结合L298N驱动芯片,精准控制4个电机动作,实现前进、后退、转弯等移动功能手机APP通信与指令传输STM32通过USB线与安卓手机连接,接收并解析来自手机APP的控制指令,实现人机交互和远程指令执行实时视频音频流传输安卓手机利用4G网络上网,搭载Qt开发的Android APP采集摄像头视频和音频数据,并通过RTMP协议将音视频流推送到华为云ECS服务器+NGINX搭建的流媒体服务器物联网(IoT)连接与远程监控小车端及远端控制手机均通过MQTT协议连接华为云IOT服务器,实现车辆状态信息实时上传及远程音视频流拉取显示;远端手机APP提供方向键菜单以远程操控小车数据交互与低延迟控制利用MQTT协议确保在4G网络环境下高效、低延迟的数据交互,实现对小车的实时远程控制,提升整体系统的响应速度和操作体验二、搭建视频监控流媒体服务器2.1 购买云服务器如果之前没有使用过华为云的ECS服务器,可以先申请试用1个月,了解服务器的基本使用然后再购买,不得不说提供这个试用服务还是非常不错。产品试用领取地址: cid:link_4每天9:30开抢,每天限量100份,这个页面不仅有云服务器可以领取试用,还有云数据库、沙盒等其他产品。我这里领取了 2核4G S6云服务器,这个服务器是配套华为自研25GE智能高速网卡,适用于网站和Web应用等中轻载企业应用。选择配置的时候发现S6规格的已经没有了,来晚了。S6规格没有了,就选择了S3,2核,4GB的配置结算。页面向下翻,下面选择系统预装的系统,我这里选择ubuntu 20.04,安装NGINX,来搭建流媒体服务器。页面继续下翻,设置云服务器名称,设置系统的root密码。接着就可以继续去支付了。购买后等待一段时间,系统资源就即可分配完成。2.2 登录云服务器云服务器的管理控制台从这里进入: cid:link_6在官网主页,点击产品,找到计算选项,就可以看到弹性云服务器ECS,点击进去就可以看到管理控制台的选项。在弹性云服务器的选项页面可以看到刚才购买的云服务器,如果点击进去提示该区域没有可用的服务器,说明区域选择的不对,在下面截图红色框框的位置可以看到可用的区域切换按钮,切换之后就行了。点击服务器右边的更多,可以对服务器重装系统、切换操作系统、关机、开机、重启、重置密码等操作。接下来先点击登录,了解一下登录的流程,看看系统进去的效果。云服务器支持SSH协议远程登录,可以在浏览器上直接使用CloudShell方式或者VNC方式登录,如果本身你自己也是使用Linux系统,可以在Linux系统里通过ssh命令直接登录,如果是在windows下可以使用SecureCRT登录。其他登录方式。最方便的登录方式,直接在控制台使用VNC在浏览器里登录,点击立即登录。根据提示输入用户名,密码后,按下回车键即可登录。用户名直接输入root,密码就是刚才配置云服务器时,填入的root密码。注意: Linux下输入密码默认都是隐藏的,也就是在键盘上输入密码界面上是不会有反应的,自己按照正确的密码输入即可。用户名、密码输入正确后,登录成功。可以点击全屏模式,更好的操作。2.3 使用CloudShell登录云服务器在页面上直接点击CloudShell登录按钮。CloudShell方式比VNC方式方便、流畅多了,也支持命令的复制粘贴。输入密码点击连接。登录成功进入终端。2.4 使用SecureCRT登录云服务器上面演示了两种登录方式,都是直接在浏览器里登录,这种两种方式比较来看,VNC方式效率最低,CloudShell相对来说要方便很多。一般我自己正常开发时,都是使用第三方工具来登录的,如果本身自己开发环境的电脑就是Linux,MAC等,可以直接使用ssh命令登录,这种方式操作流畅方便。如果在windows下,可以使用第三方软件登录。我现在使用的系统是win10,在windows系统下有很多远程终端软件支持SSH登录到Linux云服务器,我当前采用的使用SecureCRT 6.5来作为登录终端,登录云服务器。注意: SecureCRT 6.5 登录高版本Linux系统会出现Key exchange failed问题,导致登录失败,比如: 登录ubuntu 20.04 系统。 出现这种问题需要对系统ssh配置文件进行添加配置。添加配置的流程:命令行输入:vim /etc/ssh/sshd_config​在文件最后添加:KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1​保存退出。 重启ssh服务service ssh restart如果不想这么麻烦的去修改配置文件,那么最简单的办法就是: 切换操作系统,换一个低版本的,比如:ubuntu18.04 即可。在云服务器的控制台,找到自己的服务器,然后选择切换操作系统。根据界面上的引导流程,切换即可。更换新的系统需要1~4分钟时间,稍微等待一下即可。如果要使用远程SSH协议方式登录云服务器,需要具备以下几个前提条件。[1]弹性云服务器状态为“运行中”。[2]弹性云服务器已经绑定弹性公网IP,绑定方式请参见绑定弹性公网IP。[3]所在安全组入方向已开放22端口,配置方式请参见配置安全组规则。[4]使用的登录工具(如PuTTY,SecureCRT`)与待登录的弹性云服务器之间网络连通。例如,默认的22端口没有被防火墙屏蔽。但是这些配置不用担心,在购买服务器后,根据引导一套走完,上面的这些配置都已经默认配置好了,不用自己再去单独配置。系统切换成功后,打开SecureCRT 6.5软件,进行登录。SecureCRT 6.5整体而言还是挺好用的。如果自己没有`SecureCRT,自己下载一个即可。当然,不一定非要使用SecureCRT,其他还有很多SSH远程登录工具,喜欢哪个使用哪个。下面介绍`SecureCRT登录的流程。选择新建会话。选择SSH2协议。主机名就填服务器的公网IP地址,端口号默认是22,用户名填root。自己云服务器的公网IP地址,在控制台可以看到。软件点击下一步之后,可以填充描述信息,备注这个链接是干什么的。选择刚才新建的协议端口点击连接。云服务器连接上之后,软件界面会弹出对话框填充用户名、密码。登录成功的效果如下。软件可以配置一些选项,让界面符合Linux终端配色,可以修改字体大小、字体编码等等。配置后的效果。2.5 安装NFS服务器为了方便向服务器上拷贝文件,可以采用NFS服务器、或者FTP服务器 这些方式。 我本地有一台ubuntu 18.04 系统笔记本,我这里采用NFS方式拷贝文件上去。(1)安装NFS服务器root@ecs-348470:~# sudo apt-get install nfs-kernel-server(2)创建一个work目录方便当做共享目录使用root@ecs-348470:~# mkdir work(3)编写NFS配置文件NFS 服务的配置文件为/etc/exports,如果系统没有默认值,这个文件就不一定会存在,可以使用 vim 手动建立,然后在文件里面写入配置内容。/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure) (4)NFS服务器相关指令/etc/init.d/nfs-kernel-server start #启动 NFS 服务ufw disable #关闭防火墙/etc/init.d/nfs-kernel-server restart #重启NFS服务exportfs -arv #共享路径生效(5)本地客户机挂载服务器的目录wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/(6)设置华为云服务器的安全策略需要把华为云服务器的端口号开放出来,不然其他客户端是无法访问服务器的。我这里比较粗暴直接,直接将所有端口号,所有IP地址都开放出来了。(7)本地客户机挂载服务器测试 挂载指令:sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/2.6 安装NGINX(配置RTMP服务)(1)下载编译时需要依赖的一些工具root@ecs-348470:~# sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev(2)下载NGINX编译需要的软件包root@ecs-348470:~# mkdir nginx root@ecs-348470:~# cd nginx/root@ecs-348470:~# wget http://nginx.org/download/nginx-1.10.3.tar.gzroot@ecs-348470:~# wget http://zlib.net/zlib-1.2.11.tar.gzroot@ecs-348470:~# wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gzroot@ecs-348470:~# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gzroot@ecs-348470:~# wget https://github.com/arut/nginx-rtmp-module/archive/master.zip(3)下载后的文件全部解压root@ecs-348470:~# tar xvf openssl-1.0.2k.tar.gzroot@ecs-348470:~# tar xvf nginx-rtmp-module-master.tar.gzroot@ecs-348470:~# tar xvf nginx-1.8.1.tar.gzroot@ecs-348470:~# tar xvf pcre-8.40.tar.gzroot@ecs-348470:~# tar xvf zlib-1.2.11.tar.gz(4)配置NGINX源码,生成Makefile文件root@ecs-348470:~# cd nginx-1.8.1/root@ecs-348470:~# ./configure --prefix=/usr/local/nginx --with-debug --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 --with-openssl=../openssl-1.0.2k --add-module=../nginx-rtmp-module-master执行完上一步之后,打开objs/Makefile文件,找到-Werror选项并删除。(5)编译并安装NGINX root@ecs-348470:~/nginx/nginx-1.8.1# make && make install安装之后NGINX的配置文件存放路径:/usr/local/nginx/nginx:主程序(6)查看NGINX的版本号root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -vnginx version: nginx/1.8.1(5)在配置文件里加入RTMP服务器的配置root@ecs-348470:~# vim /usr/local/nginx/conf/nginx.conf 打开文件后,在文件最后加入以下配置:rtmp { server { listen 8888; application live { live on; record all; record_unique on; record_path "./video"; #视频缓存的路径 record_suffix -%Y-%m-%d-%H_%M_%S.flv; } } }这样配置之后,服务器收到RTMP流会在NGINX的当前目录下,创建一个video目录用来缓存视频。客户端向服务器推流之后,服务器就会缓存视频到设置的目录。(5)检查配置文件是否正确root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -tnginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is oknginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful(6)NGINX常用的3个命令sudo service nginx startsudo service nginx stopsudo service nginx restart(7)启动NGINX服务器sudo service nginx start2.7 摄像头推流音视频到服务器为了模拟摄像头实时监控推流,我这使用QT+FFMPEG编写了一个小软件,在windows下运行,推流本地笔记本电脑的数据到服务器。软件运行之后,将本地音频、视频编码之后通过RTMP协议推流到NGINX服务器。软件运行效果:推流工具运行过程中效果。2.8 编写本地RTMP流播放器在上面通过推流客户端模拟摄像头,已经将本地的摄像头数据实时推流到服务器了,那么还差一个播放器,为了方便能够在任何有网的地方实时查看摄像头的音频和图像,还需要编写一个RTMP流媒体播放器。我这里的播放器内核是采用libvlc开发的,使用QT作为GUI框架,开发了一个流媒体播放器,可以实时拉取服务器上的流数据,并且还支持回放。因为服务器上的NGINX配置了自动保存的参数,可以将推上去的流按时间段保存下来。输入服务器地址之后就可以拉取流进行播放。点击获取回放列表,可以查看服务器上保存的历史视频回放播放。三、华为云IOT服务器部署过程在华为云IOT平台上,需要进行设备接入、数据模型定义、规则引擎配置和应用开发等四个核心模块的开发。其中,设备接入模块包括设备注册、获取设备证书、建立连接等步骤,以保障设备与云平台之间的安全通信;数据模型定义模块需要根据实际需求定义相应的数据模型,包括上传数据格式、设备属性和服务等。规则引擎配置模块需要完成实时消息推送、远程控制和告警等功能。应用开发模块则是将完整的智能井盖系统进行打包,为用户提供统一的操作接口。华为云官网: cid:link_7打开官网,搜索物联网,就能快速找到 设备接入IoTDA。3.1 物联网平台介绍华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。3.2 开通物联网服务地址: cid:link_5开通标准版免费单元。开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。总结:端口号: MQTT (1883)| MQTTS (8883) 接入地址: a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com根据域名地址得到IP地址信息:Microsoft Windows [版本 10.0.19044.2846](c) Microsoft Corporation。保留所有权利。C:\Users\11266>ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com正在 Ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com [121.36.42.100] 具有 32 字节的数据:来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=36ms TTL=31来自 121.36.42.100 的回复: 字节=32 时间=37ms TTL=31121.36.42.100 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位): 最短 = 36ms,最长 = 37ms,平均 = 36msC:\Users\11266>MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。3.3 创建产品(1)创建产品点击右上角创建产品。(2)填写产品信息根据自己产品名字填写,设备类型选择自定义类型。(3)添加自定义模型产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。模型简单来说: 就是存放设备上传到云平台的数据。比如:环境温度、环境湿度、环境烟雾浓度、火焰检测状态图等等,这些我们都可以单独创建一个模型保存。3.4 添加设备产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。(1)注册设备点击右上角注册设备。(2)根据自己的设备填写在弹出的对话框里填写自己设备的信息。根据自己设备详细情况填写。(3)保存设备信息创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。比如我当前设备的信息如下:{ "device_id": "64000697352830580e48df07_dev1", "secret": "12345678"}3.5 MQTT协议主题订阅与发布(1)MQTT协议介绍当前的设备是采用MQTT协议与华为云平台进行通信。MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。华为云的MQTT协议接入帮助文档在这里: cid:link_2业务流程:(2)华为云平台MQTT协议使用限制描述限制支持的MQTT协议版本3.1.1与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msgMQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)单帐号每秒最大MQTT连接请求数无限制单个设备每分钟支持的最大MQTT连接数1单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/sMQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MBMQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒产品是否支持自定义Topic支持消息发布与订阅设备只能对自己的Topic进行消息发布与订阅每个订阅请求的最大订阅数无限制(3)主题订阅格式帮助文档地址:cid:link_2对于设备而言,一般会订阅平台下发消息给设备 这个主题。设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。比如:我创建的设备信息如下以当前设备为例,最终订阅主题的格式如下:$oc/devices/{device_id}/sys/messages/down 最终的格式:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down(4)主题发布格式对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。这个操作称为:属性上报。帮助文档地址:cid:link_1根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:发布的主题格式:$oc/devices/{device_id}/sys/properties/report 最终的格式:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report发布主题时,需要上传数据,这个数据格式是JSON格式。上传的JSON数据格式如下:{ "services": [ { "service_id": <填服务ID>, "properties": { "<填属性名称1>": <填属性值>, "<填属性名称2>": <填属性值>, .......... } } ]}根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。//Up, Down, Left, Right, Stop 根据这个格式,组合一次上传的属性数据:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}3.6 MQTT三元组MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。接下来介绍,华为云平台的MQTT三元组参数如何得到。(1)MQTT服务器地址要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。帮助文档地址:cid:link_0MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)华为云的MQTT服务器地址:117.78.5.125华为云的MQTT端口号:1883(2)生成MQTT三元组华为云提供了一个在线工具,用来生成MQTT鉴权三元组: cid:link_3打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。下面是打开的页面:填入设备的信息: (上面两行就是设备创建完成之后保存得到的)直接得到三元组信息。得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。DeviceId 64000697352830580e48df07_dev1DeviceSecret 12345678ClientId 64000697352830580e48df07_dev1_0_0_2023030206Username 64000697352830580e48df07_dev1Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf4493.7 模拟设备登录测试经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。(1)填入登录信息打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。(2)打开网页查看完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。到此,云平台的部署已经完成,设备已经可以正常上传数据了。(3)MQTT录测试参数总结IP地址:117.78.5.125端口号:1883DeviceId 64000697352830580e48df07_dev1DeviceSecret 12345678ClientId 64000697352830580e48df07_dev1_0_0_2023030206Username 64000697352830580e48df07_dev1Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449订阅主题:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down发布主题:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report发布的消息:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}四、Android手机APP开发4.1 开发环境介绍在当前项目中,用于远程遥控安卓小车的Android手机APP是基于Qt框架开发的。Qt是一个功能强大且高度灵活的跨平台应用程序开发框架,特别适合构建具有丰富图形用户界面(GUI)的应用程序,同时也支持开发非GUI程序。在开发这款远程遥控APP时,Qt的优势在于其跨平台性,使得同一份代码可以轻松部署在不同操作系统平台上,包括Android。Android开发必备的工具链包括:Java JDK 、Android SDK 、Android NDK。NDK下载地址:https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zipSDK下载: https://www.androiddevtools.cn/JDK下载地址:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html4.2 ffmpeg介绍说起ffmpeg,只要是搞音视频相关的开发应该都是听过的。FFmpeg提供了非常先进的音频/视频编解码库,并且支持跨平台。现在互联网上ffmpeg相关的文章、教程也非常的多,ffmpeg本身主要是用来对视频、音频进行解码、编码,对音视频进行处理。其中主要是解码和编码。 解码的应用主要是视频播放器制作、音乐播放器制作,解码视频文件得到视频画面再渲染显示出来就是播放器的基本模型了。 编码主要是用于视频录制保存,就是将摄像头的画面或者屏幕的画面编码后写入文件保存为视频,比如:行车记录仪录制视频,监控摄像头录制视频等等。 当然也可以编码推流到服务器,现在的直播平台、智能家居里的视频监控、智能安防摄像头都是这样的应用。在本项目里,通过ffmpeg技术将手机采集的视频图像编码后,推流到搭建好的流媒体服务器,实现远程监控。4.3 Linux下编译安装ffmpeg(1)安装依赖项:sudo apt-get updatesudo apt-get install build-essential nasm yasm cmake libx264-dev libx265-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libssl-dev(2)下载FFmpeg源码:wget https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.gztar -zxvf ffmpeg-x.y.z.tar.gzcd ffmpeg-x.y.z注意替换 4.2.2 为实际的版本号。(3)配置编译选项:./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-nonfree如果需要其他编码器或功能,可以根据需要添加或修改配置选项。(4)编译和安装:make -j$(nproc)sudo make install-j$(nproc) 表示使用多个CPU核心进行并行编译,可以根据实际情况调整。(5)完成后,可以通过运行以下命令来验证FFmpeg是否正确安装:ffmpeg -version这样就完成了在Linux下编译FFmpeg源码的过程。4.4 Qt摄像头采集下面代码实现,通过子线程采集摄像头画面,并通过信号槽机制将图像传递给主线程显示。// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QCamera>#include <QCameraViewfinder>#include <QCameraImageCapture>#include <QThread>class CameraThread;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();private slots: void on_pushButton_start_clicked(); void on_pushButton_stop_clicked(); void onNewImageAvailable(const QImage &image);private: Ui::MainWindow *ui; QCamera *camera; QCameraViewfinder *viewfinder; QCameraImageCapture *imageCapture; CameraThread *cameraThread;};#endif // MAINWINDOW_H// mainwindow.cpp#include "mainwindow.h"#include "ui_mainwindow.h"#include <QThread>class CameraThread : public QThread{ Q_OBJECTpublic: explicit CameraThread(QObject *parent = nullptr);signals: void newImageAvailable(const QImage &image);protected: void run() override;private: QCamera *camera; QCameraImageCapture *imageCapture;};CameraThread::CameraThread(QObject *parent) : QThread(parent){ camera = new QCamera(this); imageCapture = new QCameraImageCapture(camera, this);}void CameraThread::run(){ camera->setCaptureMode(QCamera::CaptureStillImage); camera->start(); connect(imageCapture, &QCameraImageCapture::imageCaptured, this, [&](int id, const QImage &preview) { emit newImageAvailable(preview); }); exec();}MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); camera = new QCamera(this); viewfinder = new QCameraViewfinder(this); imageCapture = new QCameraImageCapture(camera, this); cameraThread = new CameraThread(this); cameraThread->start(); connect(cameraThread, &CameraThread::newImageAvailable, this, &MainWindow::onNewImageAvailable);}MainWindow::~MainWindow(){ delete ui;}void MainWindow::on_pushButton_start_clicked(){ camera->setViewfinder(viewfinder); camera->start(); ui->verticalLayout->addWidget(viewfinder);}void MainWindow::on_pushButton_stop_clicked(){ camera->stop(); ui->verticalLayout->removeWidget(viewfinder); viewfinder->deleteLater();}void MainWindow::onNewImageAvailable(const QImage &image){ // 在这里处理接收到的图像,例如将其显示在 QLabel 上 ui->label_image->setPixmap(QPixmap::fromImage(image));}// main.cpp#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();}(1)CameraThread 类继承自 QThread,在子线程中负责采集摄像头画面。在 run() 方法中,首先创建了一个 QCamera 对象和一个 QCameraImageCapture 对象,然后设置摄像头的捕获模式为静态图像,启动摄像头。通过连接 imageCapture 的 imageCaptured 信号到 lambda 函数,当捕获到新图像时,将图像通过自定义信号 newImageAvailable 发送出去。(2)MainWindow 类是程序的主窗口,其中包含了摄像头的视图控件、开始按钮、停止按钮以及用于显示图像的标签。在构造函数中,创建了摄像头对象、视图控件对象、图像捕获对象,并创建了一个 CameraThread 对象作为子线程来处理摄像头画面的采集。(3)当用户点击开始按钮时,调用 on_pushButton_start_clicked() 槽函数,将摄像头视图控件添加到界面上并启动摄像头,开始显示摄像头画面。(4)当用户点击停止按钮时,调用 on_pushButton_stop_clicked() 槽函数,停止摄像头捕获并移除视图控件。(5)当子线程采集到新的图像时,通过 onNewImageAvailable() 槽函数接收到图像,并在标签上显示该图像。(6)main.cpp 文件是程序的入口,创建了 QApplication 对象和 MainWindow 对象,并执行主事件循环。4.6 ffmpeg视频编码推流代码使用Qt(C++)结合FFmpeg库来采集摄像头画面,进行编码,并通过子线程将视频推送到RTMP流媒体服务器。// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QThread>extern "C" {#include <libavformat/avformat.h>#include <libavdevice/avdevice.h>#include <libavcodec/avcodec.h>#include <libswscale/swscale.h>}class VideoCaptureThread;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow();private slots: void on_pushButton_start_clicked(); void on_pushButton_stop_clicked(); void onNewFrameAvailable(const QImage &frame);private: Ui::MainWindow *ui; VideoCaptureThread *videoCaptureThread;};#endif // MAINWINDOW_H// mainwindow.cpp#include "mainwindow.h"#include "ui_mainwindow.h"class VideoCaptureThread : public QThread{ Q_OBJECTpublic: explicit VideoCaptureThread(QObject *parent = nullptr); ~VideoCaptureThread();protected: void run() override;signals: void newFrameAvailable(const QImage &frame);private: AVFormatContext *formatContext; AVCodecContext *codecContext; AVStream *videoStream; SwsContext *swsContext; bool stop;};VideoCaptureThread::VideoCaptureThread(QObject *parent) : QThread(parent), stop(false){ avformat_network_init(); formatContext = avformat_alloc_context(); AVInputFormat *inputFormat = av_find_input_format("dshow"); avformat_open_input(&formatContext, "video=YourCameraDevice", inputFormat, NULL); // 省略了初始化视频编码器的部分,需要根据实际情况添加 swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);}VideoCaptureThread::~VideoCaptureThread(){ stop = true; wait(); sws_freeContext(swsContext); avcodec_free_context(&codecContext); avformat_close_input(&formatContext); avformat_free_context(formatContext);}void VideoCaptureThread::run(){ while (!stop) { AVPacket packet; av_init_packet(&packet); if (av_read_frame(formatContext, &packet) >= 0) { // 省略了视频编码的部分,需要根据实际情况添加 QImage frameImage(codecContext->width, codecContext->height, QImage::Format_RGB32); sws_scale(swsContext, codecContext->coded_frame->data, codecContext->coded_frame->linesize, 0, codecContext->height, reinterpret_cast<uint8_t **>(frameImage.bits()), frameImage.bytesPerLine()); emit newFrameAvailable(frameImage); } av_packet_unref(&packet); }}MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){ ui->setupUi(this); videoCaptureThread = new VideoCaptureThread(this); connect(videoCaptureThread, &VideoCaptureThread::newFrameAvailable, this, &MainWindow::onNewFrameAvailable);}MainWindow::~MainWindow(){ delete ui;}void MainWindow::on_pushButton_start_clicked(){ videoCaptureThread->start();}void MainWindow::on_pushButton_stop_clicked(){ videoCaptureThread->quit();}void MainWindow::onNewFrameAvailable(const QImage &frame){ // 将QImage转换为AVFrame AVFrame *avFrame = av_frame_alloc(); avFrame->width = frame.width(); avFrame->height = frame.height(); avFrame->format = AV_PIX_FMT_RGB32; av_frame_get_buffer(avFrame, 0); for (int y = 0; y < frame.height(); ++y) { memcpy(avFrame->data[0] + y * avFrame->linesize[0], frame.scanLine(y), frame.width() * 4); } // 编码视频帧 AVPacket packet; av_init_packet(&packet); int ret = avcodec_send_frame(codecContext, avFrame); if (ret < 0) { qDebug() << "Failed to send frame to encoder"; return; } while (ret >= 0) { ret = avcodec_receive_packet(codecContext, &packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { qDebug() << "Error during encoding"; return; } // 推送到RTMP服务器 RTMP *rtmp = RTMP_Alloc(); RTMP_Init(rtmp); if (!RTMP_SetupURL(rtmp, "rtmp://your_rtmp_server_url")) { qDebug() << "Failed to set RTMP URL"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } RTMP_EnableWrite(rtmp); if (!RTMP_Connect(rtmp, NULL)) { qDebug() << "Failed to connect to RTMP server"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } if (!RTMP_ConnectStream(rtmp, 0)) { qDebug() << "Failed to connect to RTMP stream"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } packet.stream_index = videoStream->index; ret = RTMP_SendPacket(rtmp, reinterpret_cast<char*>(packet.data), packet.size, TRUE); if (ret < 0) { qDebug() << "Failed to send packet to RTMP server"; RTMP_Close(rtmp); RTMP_Free(rtmp); return; } av_packet_unref(&packet); RTMP_Close(rtmp); RTMP_Free(rtmp); } av_frame_free(&avFrame);}代码中创建了一个 VideoCaptureThread 类作为子线程,负责采集摄像头画面并进行视频编码。在 run() 方法中,利用FFmpeg库读取摄像头画面,进行视频编码,并通过自定义信号 newFrameAvailable 发送每一帧图像。主界面中的 MainWindow 类负责开始和停止视频采集线程,并处理接收到的视频帧,可以在 onNewFrameAvailable() 槽函数中将视频帧编码为RTMP流并推送到服务器。将QImage转换为AVFrame,使用avcodec_send_frame和avcodec_receive_packet函数对视频帧进行编码。创建一个RTMP连接,并将编码后的视频包发送到RTMP服务器。五、STM32端代码设计STM32端的代码主要是控制小车的移动,代码比较少。5.1 STM32小车底座驱动代码#include "stm32f10x.h"#define MOTOR1_PIN1 GPIO_Pin_0#define MOTOR1_PIN2 GPIO_Pin_1#define MOTOR2_PIN1 GPIO_Pin_2#define MOTOR2_PIN2 GPIO_Pin_3#define MOTOR3_PIN1 GPIO_Pin_4#define MOTOR3_PIN2 GPIO_Pin_5#define MOTOR4_PIN1 GPIO_Pin_6#define MOTOR4_PIN2 GPIO_Pin_7void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++);}void motor_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);}void forward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);}void backward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);}void left() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);}void right() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);}int main(void) { motor_init(); while (1) { forward(); delay_ms(1000); backward(); delay_ms(1000); left(); delay_ms(1000); right(); delay_ms(1000); }}5.2 小车控制代码#include "stm32f10x.h"#include <stdio.h>#include <string.h>#define MOTOR1_PIN1 GPIO_Pin_0#define MOTOR1_PIN2 GPIO_Pin_1#define MOTOR2_PIN1 GPIO_Pin_2#define MOTOR2_PIN2 GPIO_Pin_3#define MOTOR3_PIN1 GPIO_Pin_4#define MOTOR3_PIN2 GPIO_Pin_5#define MOTOR4_PIN1 GPIO_Pin_6#define MOTOR4_PIN2 GPIO_Pin_7void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++);}void motor_init() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);}void forward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);}void backward() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);}void left() { GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2); GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);}void right() { GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1); GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);}void usart_init() { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);}void usart_send(USART_TypeDef* USARTx, uint8_t data) { while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET); USART_SendData(USARTx, data);}uint8_t usart_receive(USART_TypeDef* USARTx) { while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET); return USART_ReceiveData(USARTx);}void usart_puts(USART_TypeDef* USARTx, char* str) { while (*str) { usart_send(USARTx, *str++); }}void control(char* cmd) { if (strcmp(cmd, "forward") == 0) { forward(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "backward") == 0) { backward(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "left") == 0) { left(); usart_puts(USART1, "OK\n"); } else if (strcmp(cmd, "right") == 0) { right(); usart_puts(USART1, "OK\n"); } else { usart_puts(USART1, "Invalid command\n"); }}int main(void) { motor_init(); usart_init(); while (1) { char cmd[10]; memset(cmd, 0, sizeof(cmd)); int i = 0; while (1) { char c = usart_receive(USART1); if (c == '\r' || c == '\n') { break; } cmd[i++] = c; } control(cmd); }}六、关于Android手机USB通信的问题在Qt中开发Android手机APP并利用USB线进行串口通信,需要启用权限。(1)添加权限:在AndroidManifest.xml文件中添加USB权限,并在Qt项目中的Android配置文件中声明需要的权限。例如,在AndroidManifest.xml中添加以下代码:<uses-permission android:name="android.permission.USB_PERMISSION" />(2)检测USB连接:通过Qt的Android JNI接口(Java Native Interface)来检测USB设备的插拔状态,并获取USB设备的信息。(3)打开和关闭USB串口:使用Qt的QSerialPort类来打开和关闭USB串口,并进行数据的读写操作。可以通过检测到的USB设备路径来打开对应的串口。(4)处理串口数据:接收到的串口数据可以通过信号槽机制或者其他方式传递给界面进行显示或进一步处理。下面是测试的代码:#include <QSerialPort>#include <QSerialPortInfo>void detectUsbDevices() { QList<QSerialPortInfo> usbDevices = QSerialPortInfo::availablePorts(); foreach (const QSerialPortInfo &info, usbDevices) { qDebug() << "USB Device Name: " << info.portName(); qDebug() << "Description: " << info.description(); }}void openUsbSerialPort(const QString &portName) { QSerialPort serialPort; serialPort.setPortName(portName); serialPort.setBaudRate(QSerialPort::Baud9600); if (serialPort.open(QIODevice::ReadWrite)) { qDebug() << "USB Serial Port opened successfully!"; // Read or write data here serialPort.close(); } else { qDebug() << "Failed to open USB Serial Port!"; }}int main(int argc, char *argv[]) { QApplication app(argc, argv); // Detect USB devices detectUsbDevices(); // Open USB serial port openUsbSerialPort("/dev/ttyUSB0"); // Replace with the actual port name return app.exec();}七、总结本文详细介绍了一款创新且环保的基于4G网络设计的远程遥控安卓小车系统的开发与实现过程。该项目巧妙地将被淘汰的安卓旧手机升级转化为车载信息处理单元,赋予其新的生命力,同时融入先进的4G网络技术、流媒体服务以及物联网技术,成功打造出一个集远程操控与实时音视频传输功能于一身的高效率解决方案。该智能小车以其独特的设计思路和强大的功能特性,展现出广泛的应用潜力。无论是作为教育科研领域的实践平台,还是在远程监控、工业巡检、应急救援、无人驾驶技术验证,甚至智能家居与物流等方面,均能发挥重要作用,显著提升了工作效率,降低了人力成本,并有效保障了作业的安全性。该项目积极响应可持续发展号召,通过资源循环利用,成功展示了科技如何助力环保,彰显了技术创新的社会价值。展望未来,随着5G网络技术的广泛应用,这款基于4G网络的远程遥控安卓小车将进一步优化性能,拓展应用场景,为社会各领域带来更加智能化、便捷化的技术服务。
  • [技术干货] 基于嵌入式的车载导航定位系统设计
    一、前言1.1 项目介绍【1】项目背景随着汽车工业的飞速发展和智能化技术的不断突破,车载导航系统作为现代汽车不可或缺的一部分,在人们的日常生活中扮演着越来越重要的角色。它不仅能够提供精确的路线导航,还能提供丰富的地理信息和娱乐服务,为驾驶者带来了极大的便利和乐趣。传统的车载导航系统主要依赖于内置的地图数据和GPS定位技术,但随着移动互联网的普及和智能设备的快速发展,用户对车载导航系统的要求也在不断提高。希望车载导航系统能够具备更高的定位精度、更丰富的地图信息、更便捷的操作体验以及更强的可扩展性。开发一款基于嵌入式技术的车载导航定位系统,以满足现代用户对高效、智能、个性化导航服务的需求,成为了当前行业发展的一个重要方向。本项目就是通过集成高性能的主控开发板、精准的GPS定位模块以及强大的Qt开发框架,实现一个功能丰富、性能稳定、用户体验优越的车载导航系统。【2】设计实现的功能(1)实时定位与地图显示:通过外接的北斗GPS模块,系统能够实时接收并解析卫星信号,获取车辆的精确位置信息。这些信息将实时显示在基于Qt开发的主界面上,与百度地图API无缝对接,为用户呈现清晰、准确的地图画面。(2)路线规划与导航:用户可以通过主界面输入目的地信息,系统根据百度地图API提供的路径规划服务,计算出最优的行驶路线,并在地图上进行高亮显示。在导航过程中,系统能够实时追踪车辆位置,提供转向、距离等导航指令,确保用户能够准确、快速地到达目的地。(3)地图预览与缩放:系统支持地图的缩放和拖动功能,用户可以根据需要调整地图的显示范围,查看不同级别的地理细节。同时,系统还提供了多种地图视图模式(如白天模式、夜间模式等),以满足用户在不同场景下的使用需求。(4)语音提示与交互:为了提升用户体验,系统集成了语音提示功能,能够在关键导航节点(如转弯、路口等)给予用户语音指令,减少用户操作干扰。此外,系统还支持通过语音指令进行简单的交互操作,如查询附近的餐饮、加油站等设施。(5)个性化设置与偏好管理:用户可以根据自己的使用习惯,在系统设置中调整界面风格、导航偏好等参数。系统还会记录用户的行驶历史,为用户提供个性化的推荐和服务。(6)系统稳定性与扩展性:基于嵌入式Linux系统的开发框架,保证了系统的稳定性和可靠性。同时,开放式的架构设计使得系统易于扩展和升级,能够随时集成新的功能模块和服务,满足用户不断增长的需求。本项目设计的基于嵌入式的车载导航定位系统,通过集成高性能硬件和先进的软件开发技术,实现了实时定位、路线规划、地图预览、语音提示、个性化设置等多项功能,为用户提供了高效、智能、个性化的导航服务体验。【3】项目硬件模块组成(1)主控开发板:采用GEC6818开发板,该开发板搭载了三星Cortex-A53系列高性能八核处理器S5P6818,最高主频高达1.4GHz。主控开发板作为整个系统的核心,负责处理导航定位系统的所有运算和控制任务,确保系统的稳定运行。(2)GPS模块:采用北斗GPS模块,该模块负责接收并解析卫星信号,获取车辆的精确位置信息。通过与主控开发板的连接,将位置数据实时传输给系统进行处理和显示。(3)显示屏:用于呈现地图、导航指令以及其他相关信息。显示屏与主控开发板相连,通过Qt开发的界面,将系统的各项功能直观地展示给用户。(4)网卡: 用于上网,调用百度地图,这是开发板本身自带。(5)语音播报模块: 利用开发板本身的声卡播放导航提示。1.2 设计思路(1)需求分析:对车载导航定位系统的需求进行深入分析。确定系统需要具备的功能,如实时定位、路线规划、地图显示、语音提示等。同时,考虑到用户的操作习惯和驾驶过程中的安全性,对界面的设计、交互的流畅性等方面也进行了充分考虑。(2)硬件选型:根据需求分析的结果,选择适合的硬件组件。主控开发板选用GEC6818开发板,其高性能的处理器和嵌入式Linux系统为系统的稳定运行提供了有力支持。GPS模块选用北斗GPS模块,以确保定位的准确性和稳定性。同时,选择高质量的显示屏和其他辅助模块,以满足系统的各项需求。(3)软件架构设计:采用Qt作为软件开发框架,利用其强大的图形界面开发能力和跨平台特性,实现系统的主界面和各项功能。通过集成百度地图API,实现地图的加载、显示和路径规划等功能。同时,设计合理的软件架构,确保各个模块之间的协同工作和数据传输的高效性。(4)功能模块划分:将系统划分为多个功能模块,如定位模块、导航模块、地图显示模块、语音提示模块等。每个模块负责实现特定的功能,并通过接口与其他模块进行交互。这种模块化的设计方式便于后期的维护和扩展。1.3 系统功能总结功能模块功能描述技术实现与特点实时定位通过北斗GPS模块获取车辆精确位置信息。利用北斗卫星导航系统,提供高精度、稳定的定位服务。地图显示在显示屏上呈现百度地图,展示地理信息。集成百度地图API,实现地图的加载、缩放、拖动等功能。路线规划根据用户输入的目的地,计算最优行驶路线。利用百度地图API的路径规划服务,提供多种路线选择。导航指引提供转向、距离等导航指令,辅助用户驾驶。实时追踪车辆位置,根据规划路线提供准确的导航指引。语音提示通过语音输出导航指令和其他相关信息。集成语音合成技术,实现人性化的交互体验。地图预览与缩放支持地图的预览、缩放和拖动操作。提供多种地图视图模式,满足不同场景下的使用需求。个性化设置用户可根据喜好设置系统界面和导航偏好。提供丰富的设置选项,满足用户的个性化需求。系统稳定性确保系统在各种环境下的稳定运行。基于嵌入式Linux系统开发,具备高度的稳定性和可靠性。扩展性系统设计易于扩展和升级,适应未来需求变化。开放的架构设计,支持新功能模块和服务的集成。1.4 原理图二、Linux下Qt开发环境搭建养老院出行管理系统项目是在Linux下开发,接下来需要搭建Linux下的开发环境。(1)第一步,安装VM虚拟机(2)第二步,在VM虚拟机里安装Ubuntu18.04系统(3)第三步,在Ubuntu18.04系统里安装QT开发环境2.1 安装VMware虚拟机软件VMware软件下载地址: cid:link_0当前电脑使用的vmware版本为: 15.52.2 安装Ubuntu18.04系统18.04最新长期支持版本: cid:link_12.3 安装Qt5.12开发环境注意,安装Qt之前要先安装以下工具(如果之前安装了就不Qt安装包下载地址:cid:link_22.4 Qt编译常见问题解决如果在编译运行程序时, 提示缺少 cannot find -lGL 库报错, 可以按照下面方法解决:在命令行执行: locate libGL.so //这一步是查看本地系统里有没有这个库/usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 //如果提示这两行, 说明系统有这个库/usr/lib/x86_64-linux-gnu/mesa/libGL.so.1.2.0下面只需要做一个链接即可:sudo ln -s /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 /usr/lib/libGL.so如果系统里没有查找到库, 就在命令行敲下面命令进行在线安装:sudo apt-get install libgl1-mesa-dev三、代码设计3.1 地图API调用下面使用Qt的网络模块来发送HTTP请求,并使用Qt的GUI模块来显示地图图片。 需要在百度地图开放平台上注册应用程序,并获取到百度地图API密钥(AK)。// mainwindow.h#ifndef MAINWINDOW_H#define MAINWINDOW_H​#include <QMainWindow>#include <QNetworkAccessManager>#include <QNetworkRequest>#include <QNetworkReply>#include <QImage>#include <QLabel>​class MainWindow : public QMainWindow{ Q_OBJECT​public: MainWindow(QWidget *parent = nullptr); ~MainWindow();​private slots: void onMapImageReceived(QNetworkReply *reply);​private: QLabel *mapLabel; QNetworkAccessManager *networkManager;};​#endif // MAINWINDOW_HcppCopy Code// mainwindow.cpp#include "mainwindow.h"#include <QUrl>​MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){ mapLabel = new QLabel(this); mapLabel->setGeometry(10, 10, 600, 400); // 设置地图图片显示位置和大小​ networkManager = new QNetworkAccessManager(this); connect(networkManager, &QNetworkAccessManager::finished, this, &MainWindow::onMapImageReceived);​ QString mapUrl = "http://api.map.baidu.com/staticimage/v2"; QUrl url(mapUrl); url.addQueryItem("ak", "your_baidu_map_api_key"); // 替换为你的百度地图API密钥 url.addQueryItem("center", "北京"); // 地图中心位置 url.addQueryItem("width", "600"); // 图片宽度 url.addQueryItem("height", "400"); // 图片高度 url.addQueryItem("zoom", "11"); // 缩放级别​ QNetworkRequest request(url); networkManager->get(request);}​MainWindow::~MainWindow(){}​void MainWindow::onMapImageReceived(QNetworkReply *reply){ if (reply->error() == QNetworkReply::NoError) { QByteArray imageData = reply->readAll(); QImage mapImage; mapImage.loadFromData(imageData); mapLabel->setPixmap(QPixmap::fromImage(mapImage)); } else { qDebug() << "Error: " << reply->errorString(); }​ reply->deleteLater();}创建了一个MainWindow类,其中包含一个用于显示地图图片的QLabel和一个QNetworkAccessManager用于发送HTTP请求。在构造函数中,通过QUrl构建了百度地图API接口的URL,并添加了必要的参数,例如地图中心位置、图片大小和缩放级别。使用QNetworkRequest发送了一个GET请求,并在onMapImageReceived槽函数中处理收到的地图图片数据,并将其显示在QLabel上。3.2 导航提示音播放使用alsa-lib库在Linux下调用声卡驱动来播放声音。#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <alsa/asoundlib.h>​#define BUFFER_SIZE 1024​int main(int argc, char *argv[]){ int err; int fd; snd_pcm_t *pcm_handle; snd_pcm_hw_params_t *hw_params; unsigned int rate = 44100; unsigned int channels = 2; unsigned int buffer_time = 500000; // 500ms unsigned int period_time = 100000; // 100ms snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; char *buffer;​ buffer = (char *)malloc(BUFFER_SIZE); if (!buffer) { printf("Error: Failed to allocate memory.\n"); return -1; }​ // 打开PCM设备 err = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); if (err < 0) { printf("Error: Failed to open PCM device. %s\n", snd_strerror(err)); return -1; }​ // 配置PCM设备参数 snd_pcm_hw_params_alloca(&hw_params); err = snd_pcm_hw_params_any(pcm_handle, hw_params); if (err < 0) { printf("Error: Failed to get PCM device parameters. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { printf("Error: Failed to set PCM device access mode. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, SND_PCM_FORMAT_S16_LE); if (err < 0) { printf("Error: Failed to set PCM device sample format. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, channels); if (err < 0) { printf("Error: Failed to set PCM device channel count. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &rate, 0); if (err < 0) { printf("Error: Failed to set PCM device sample rate. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_buffer_time_near(pcm_handle, hw_params, &buffer_time, 0); if (err < 0) { printf("Error: Failed to set PCM device buffer time. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params_set_period_time_near(pcm_handle, hw_params, &period_time, 0); if (err < 0) { printf("Error: Failed to set PCM device period time. %s\n", snd_strerror(err)); return -1; } err = snd_pcm_hw_params(pcm_handle, hw_params); if (err < 0) { printf("Error: Failed to set PCM device parameters. %s\n", snd_strerror(err)); return -1; }​ // 获取PCM设备缓冲区大小和周期大小 err = snd_pcm_get_params(pcm_handle, &buffer_size, &period_size); if (err < 0) { printf("Error: Failed to get PCM device buffer and period sizes. %s\n", snd_strerror(err)); return -1; }​ // 打开音频文件 fd = open(argv[1], O_RDONLY); if (fd < 0) { printf("Error: Failed to open audio file.\n"); return -1; }​ // 播放音频 while (1) { long frames = read(fd, buffer, BUFFER_SIZE); if (frames == 0) { // 播放完成 break; } else if (frames < 0) { // 读取错误 printf("Error: Failed to read audio data.\n"); break; } while (frames > 0) { long n = snd_pcm_writei(pcm_handle, buffer, frames); if (n < 0) { // 发生错误,重新配置PCM设备 printf("Error: Failed to write audio data to PCM device. %s\n", snd_strerror(n)); snd_pcm_prepare(pcm_handle); } else { frames -= n; buffer += n * channels * 2; // 每个采样点为16位(2字节),乘以通道数 } } }​ // 关闭PCM设备和音频文件 snd_pcm_close(pcm_handle); close(fd); free(buffer);​ return 0;}使用alsa-lib库来调用Linux声卡驱动来播放声音。(1)打开PCM设备并配置参数,然后通过snd_pcm_get_params函数获取缓冲区大小和周期大小。(2)打开音频文件并循环读取文件中的数据,每次将一定数量的数据写入PCM设备进行播放。在播放过程中,如果发生错误们需要重新配置PCM设备并重新开始播放。3.3 GPS导航模块#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <libserialport.h>​void parse_gps_data(char *data) { char *token; token = strtok(data, ","); int count = 0; double latitude, longitude;​ while (token != NULL) { if (count == 2) { // 纬度数据在第3个字段 latitude = atof(token); } else if (count == 4) { // 经度数据在第5个字段 longitude = atof(token); }​ token = strtok(NULL, ","); count++; }​ printf("Latitude: %f, Longitude: %f\n", latitude, longitude);}​int main() { struct sp_port *port; int err;​ // 打开串口 err = sp_get_port_by_name("/dev/ttyUSB0", &port); if (err != SP_OK) { fprintf(stderr, "Error: Failed to open serial port\n"); return -1; }​ err = sp_open(port, SP_MODE_READ); if (err != SP_OK) { fprintf(stderr, "Error: Failed to open serial port for reading\n"); sp_free_port(port); return -1; }​ // 设置串口参数 struct sp_port_config *config; sp_new_config(&config); sp_set_config_baudrate(config, 9600); sp_set_config_bits(config, 8); sp_set_config_parity(config, SP_PARITY_NONE); sp_set_config_stopbits(config, 1); sp_set_config(port, config);​ // 读取GPS数据 char data[256]; int bytes_read; while (1) { bytes_read = sp_input_waiting(port); if (bytes_read > 0) { sp_blocking_read(port, data, sizeof(data), 0); parse_gps_data(data); } usleep(100000); // 延时100ms }​ // 关闭串口 sp_close(port); sp_free_port(port);​ return 0;}​四、总结随着智能化和移动互联网技术的飞速发展,车载导航定位系统已经成为现代驾驶不可或缺的一部分。本项目通过集成高性能的GEC6818开发板、北斗GPS模块以及百度地图API,成功设计并实现了一个功能全面、性能稳定的车载导航定位系统。在项目实施过程中,注重用户体验和系统稳定性,通过Qt开发框架打造了直观易用的操作界面,并实现了实时定位、地图显示、路线规划、导航指引以及语音提示等核心功能。同时,系统的模块化设计和开放式架构保证了其易于维护和扩展,能够随时适应市场和用户需求的变化。通过本项目的实施,不仅提升了车载导航系统的技术水平,也为用户提供了更加便捷、智能的出行体验。
  • [技术干货] 基于视觉识别的自动采摘机器人设计与实现
    一、前言1.1 项目介绍【1】项目功能介绍随着科技的进步和农业现代化的发展,农业生产效率与质量的提升成为重要的研究对象。其中,果蔬采摘环节在很大程度上影响着整个产业链的效益。传统的手工采摘方式不仅劳动强度大、效率低下,而且在劳动力成本逐渐上升的背景下,越来越难以满足大规模种植基地的需求。人工采摘还可能因不规范的操作导致果实损伤,影响商品果率。基于视觉识别技术的自动采摘机器人的研发,正是针对这一问题提出的创新解决方案。本项目采用树莓派4B作为主控芯片,因其具有强大的计算能力和丰富的扩展接口,可以方便地集成各种传感器和执行机构,实现对复杂环境下的实时图像采集与处理。项目利用百度飞浆(PaddlePaddle)深度学习框架中的目标检测和分类算法,通过安装在机器人上的高清摄像头获取果树图像,并进行实时分析,精准识别出果实的位置、大小以及成熟度等信息。当成功识别到目标果实后,主控系统将根据识别结果快速计算出机械手臂的最佳运动路径,控制其移动至指定位置,以最适宜的方式完成果实的高效、无损采摘。基于视觉识别的自动采摘机器人设计与实现项目旨在解决传统农业中人工采摘的瓶颈问题,通过人工智能与自动化技术的深度融合,提高果园管理的智能化水平,降低劳动成本,提高生产效率,从而推动我国乃至全球农业产业向更加智能、高效的现代农业转型。【2】设计实现的功能(1)视觉识别:借助高性能的摄像头和图像处理算法(本项目采用百度飞浆的目标识别和分类算法),机器人能够捕捉到果园中的果实图像,并准确地从中识别出目标果实。(2)定位与导航:在识别到果实后,系统会通过计算果实的空间坐标和距离,确定机械手臂需要到达的精确位置。同时,机器人会根据果园内的环境信息和路径规划算法,自动导航至目标果实附近。(3)机械手臂控制:一旦机器人到达目标位置,机械手臂会在系统的精确控制下,自动调整姿态和动作,以轻柔而准确的方式采摘果实。这一过程涉及到复杂的机械动力学和协同控制算法,确保采摘动作的高效和安全。(4)果实收集与处理:采摘下来的果实会被机器人收集到专门的容器中,以便后续的分拣、包装和处理。系统还可以对采摘的果实进行数量统计和质量评估,为农业生产提供有价值的数据支持。本项目实现的功能是一个完整的自动采摘机器人系统,从视觉识别到机械手臂控制,再到果实收集与处理,形成了一个高效、智能的自动化采摘流程。这不仅大大提高了农业生产的效率和质量,也展示了人工智能技术在现代农业领域的广阔应用前景。【3】项目硬件模块组成(1)主控板:采用树莓派4B开发板作为整个系统的主控芯片。树莓派是一款功能强大且易于使用的计算机主板,具备高性能的处理器、充足的内存和存储空间,以及丰富的接口和扩展功能,可以满足本项目对计算和控制的需求。(2)视觉系统:视觉系统包括高性能的摄像头和图像处理单元。摄像头负责捕捉果园中的图像信息,而图像处理单元则基于百度飞浆的目标识别和分类算法,对图像进行处理和分析,以识别和定位目标果实。(3)机械手臂:机械手臂是实现自动采摘的关键部件,由多个关节和执行器组成,可以在三维空间内自由移动和旋转。通过精确的控制算法,机械手臂能够准确地到达目标果实的位置,并执行采摘动作。(4)传感器和导航系统:为了实现自动导航和精确定位,项目中还集成了多种传感器和导航系统。这些传感器可以感知环境信息,如距离、方位、障碍物等,而导航系统则根据这些信息规划出机器人的最佳路径。(4)电源和供电系统:为了保证机器人的持续工作,项目中还包括了电源和供电系统。电源负责为各个硬件模块提供稳定的电力供应,而供电系统则可以根据实际需要调整电力输出,以满足机器人在不同工作状态下的能耗需求。本项目的硬件模块组成包括主控板、视觉系统、机械手臂、传感器和导航系统、电源和供电系统以及其他辅助模块。这些硬件模块相互协作,共同实现了基于视觉识别的自动采摘机器人系统的功能。【3】功能总结系统集成了先进的视觉识别技术、机械手臂控制技术以及自动导航技术,能够自动识别和定位果园中的目标果实,并通过机械手臂完成采摘动作。整个过程无需人工干预,实现了果园采摘的自动化和智能化。功能包括果实的自动识别和定位、机械手臂的自动导航和控制以及果实的自动收集和处理。通过高性能的摄像头和图像处理算法,系统能够准确捕捉和识别目标果实的图像信息;借助精确的导航和控制算法,机械手臂能够自动导航至果实位置并完成采摘;最后,采摘下来的果实会被自动收集并进行后续处理。本项目的功能实现不仅提高了果园采摘的效率和准确性,降低了人力成本,同时也为农业生产的现代化和智能化发展提供了新的解决方案和思路。该系统的成功应用将为农业生产带来革命性的变革,推动农业向更高效、更环保、更可持续的方向发展。1.2 设计思路(1)需求分析:对果园采摘的实际需求进行分析,明确项目需要解决的问题和达到的目标。了解果园的环境特点、果实类型和生长状况,以及采摘作业的流程和要求,为后续设计提供基础依据。(2)技术选型:根据需求分析的结果,选择合适的技术方案。选用树莓派4B开发板作为主控芯片,利用其高性能的处理器和丰富的接口资源,实现机器人的控制和管理。同时,采用百度飞浆的目标识别和分类算法,通过视觉系统实现对目标果实的准确识别和定位。(3)硬件设计:根据技术选型,设计机器人的硬件结构。包括摄像头的选型和布局,确保能够捕捉到清晰、稳定的图像信息;机械手臂的设计和选型,使其能够适应果园环境和采摘需求;导航和传感器系统的设计和选型,实现机器人的自动导航和精确定位。(4)软件设计:编写机器人的控制程序和算法。通过图像处理算法实现对目标果实的识别和定位,将结果传递给导航和控制系统;根据导航和传感器系统提供的信息,规划机器人的运动路径和动作,控制机械手臂完成采摘动作;实现果实的计数、分类和收集等功能,以及数据的存储和传输。(5)系统集成与测试:将各个硬件模块和软件程序进行集成,并进行系统测试和调试。确保各个模块之间的通信和协作正常,机器人能够准确识别和采摘目标果实,并实现自动导航和收集等功能。1.3 系统功能总结功能模块功能描述视觉识别- 通过高性能摄像头捕捉果园图像- 利用百度飞浆的目标识别和分类算法,识别目标果实- 确定果实的空间坐标和距离导航与定位- 根据果园环境信息和路径规划算法,自动导航至目标果实附近- 集成多种传感器,感知环境信息,如距离、方位、障碍物等机械手臂控制- 在系统精确控制下,自动调整姿态和动作,采摘果实- 确保采摘动作的高效和安全果实收集与处理- 采摘下来的果实被自动收集到专门容器中- 对采摘的果实进行数量统计和质量评估- 提供数据支持,为农业生产决策提供参考通信与监控- 实现远程监控和控制功能二、树莓派4B环境搭建【1】硬件环境介绍树莓派是什么?Raspberry Pi(中文名为“树莓派”,简写为RPi,或者RasPi/RPi)是为学生计算机编程教育而设计,只有信用卡大小的卡片式电脑,其系统基于Linux。【2】资料下载第一步,先将树莓派4B需要使用的资料下载下来。【3】准备需要的配件(1)准备一张至少32G的TFT卡,用来烧写系统。(2)准备一个读卡器,方便插入TFT卡,好方便插入到电脑上拷贝系统(3)树莓派主板一个(4)一根网线(方便插路由器上与树莓派连接)(5)一根type-C的电源线。用自己Android手机的数据线就行,拿手机充电器供电。【4】准备烧写系统(1)安装镜像烧写工具(2)格式化SD卡将TFT卡通过读卡器插入到电脑上,将TFT卡格式化。(3)烧写系统接下来准备烧写的系统是这一个系统:将系统解压出来。然后打开刚才安装好的镜像烧写工具,在软件中选择需要安装的 img(镜像)文件,“Device”下选择SD的盘符,然后选择“Write”,然后就开始安装系统了,根据你的SD速度,安装过程有快有慢。注意:从网盘下载下来的镜像如果没有解压就先解压,释放出img文件。下面是烧写的流程:点击YES,开始烧写。烧写过程中:安装结束后会弹出完成对话框,说明安装就完成了,如果不成功,需要关闭防火墙一类的软件,重新插入SD进行安装。需要注意的是,安装完,windows系统下看到SD只有74MB了,这是正常现象,因为linux下的磁盘分区win下是看不到的。 烧录成功后windows系统可能会因为无法识别分区而提示格式化分区,此时千万不要格式化!不要格式化!不要格式化!点击取消,然后弹出内存卡,插入到树莓派上。至此,树莓派烧写成功。【5】启动系统(1)树莓派供电由于我买的树莓派开发板不带电源线,就采用Android手机的充电线供电。 使用Type-C供电时,要求电源头的参数要求,电压是5V,电流是3A。我的充电器是小米的120W有线快充,刚好满足要求。(2)启动树莓派(以Type-C供电示例)烧写完后把MicroSD卡直接插入树莓派的MicroSD卡插槽,如果有显示器就连接显示器,有DHMI线机也可以连接外接的显示器,有鼠标键盘都可以插上去,就可以进入树莓派系统了。但是,我这块板子就一个主板,什么都没有。就拿网线将树莓派的网口与路由器连接。上电之后,开发板的指示灯会闪烁,说明已经启动。(3)查看开发板的IP地址现在板子没屏幕,想要连接板子,只能通过SSH远程登录的方式,当前烧写的这个系统默认开机就启动了SSH,所以只要知道开发板的IP地址就可以远程登录进去。如何知道树莓派板子的IP地址?方法很多,最简单是直接登录路由器的后台界面查看连接进入的设备。我使用的小米路由器,登录后台,看到了树莓派的IP地址。(4)SSH方式登录开发板当前烧写系统的登录账号和密码如下:账号:pi 密码:yahboom打开SSH远程登录工具:PuTTY_0.67.0.0.exe。输入IP地址和端口号,点击open。然后输入账号和密码。输入用户名 pi按下回车,然后再输入密码 yahboom。 注意:Linux下为了保护隐私,输入密码是不可见的,你只需要正常输入,按下回车键确定 即可。正常情况下,就登录成功了。接下来看看联网情况。 ping一下百度测试互联网是否畅通,因为接下来要在线安装软件包。ping www.baidu.com可以看到网络没有问题。提示: 按下 Ctrl + C 可以终止命令行。 这算是Linux基础。【6】windows远程登录桌面为了方便图形化方式开发,可以使用windows系统通过远程桌面登录树莓派,就可以看到界面了,不过需要先安装工具。(1)安装xdrp在树莓派的命令行终端输入命令:sudo apt-get install xrdp按下回车之后,会弹出确认窗口。输入 y之后,按下回车,继续安装。(2)打开windows远程桌面在windows电脑上打开运行命令的窗口,输入mstsc来打开远程桌面。打开远程桌面的窗口:(3)连接树莓派远程桌面打开远程桌面后,输入树莓派开发板的IP地址,点击连接。如果弹出窗口,就选择是。接下来就进入到树莓派开发板的远程桌面的登录窗口了。接下来输入面账号和密码。账号:pi 密码:yahboom输入后点击OK按钮登录。正常情况下,就顺利的进入树莓派的桌面了。接下来就可以进行远程桌面开发了。【7】扩展树莓派SD卡可用空间树莓派系统默认启动时,树莓派默认没有把整个存储空间拓展到整张卡中,如果需要使用整个SD卡,这时候可以通过人为的把存储空间拓展到整张卡上。(1)查看内存使用情况打开命令行终端,输入df -h 命令。(2)扩展内存<1> 打开树莓派命令行终端输入:pi@raspberrypi:~ $ sudo raspi-config<2> 在弹出的命令行里选择Advanced Options<3> 选择第一个选项。<4> 点击确定<5> 点击右边的Finish按钮保存退出。确定之后,关闭界面,系统会自动重启,重启之后,使用df命令查看是否扩展成功(我这里插的是32G的SD卡)。可以看到,我的系统已经扩展成功了,目前可以内存空间是19G。【8】树莓派连接WIFI(1)配置需要连接的WIFI点击右上角的数据连接图标,打开WIFI列表,点击想要的WIFI进行连接。(2)通过WIFI的IP地址登录远程桌面在路由器的后台可以看到,目前树莓派连入了两个IP地址。接下来把网线拔掉,使用WIFI无线也可以直接连接无线桌面,这样就不用插网线了。账号和密码:账号:pi 密码:yahboom三、代码设计3.1 舵机控制代码(机械手臂控制)C语言代码: 使用wiringPi库控制树莓派上的GPIO引脚,实现对舵机的控制。通过servo_rotate()函数可以控制舵机旋转到指定的角度。在main()函数中,使用键盘输入获取目标角度,并调用servo_rotate()函数控制舵机旋转到目标角度。舵机的控制方式为PWM脉冲宽度调制,即将角度转换为脉宽值并输出对应的高低电平信号。将舵机信号线连接到GPIO18引脚,通过digitalWrite()函数输出高低电平来控制舵机旋转。#include <wiringPi.h>#include <stdio.h>​#define SERVO_PIN 18 // SG90舵机信号线连接的GPIO引脚​void servo_rotate(int angle) { int pulse_width = (angle * 11) + 500; // 将角度转换为脉宽值 digitalWrite(SERVO_PIN, HIGH); // 输出高电平 delayMicroseconds(pulse_width); // 延时脉宽值对应的时间 digitalWrite(SERVO_PIN, LOW); // 输出低电平 delay(20 - pulse_width / 1000); // 延时剩余时间}​int main(void) { wiringPiSetupGpio(); // 初始化wiringPi库 pinMode(SERVO_PIN, OUTPUT); // 将舵机信号线接口设为输出模式​ while(1) { // 从键盘输入目标角度 printf("Enter the angle to rotate (0-180): "); fflush(stdout); int angle; scanf("%d", &angle);​ // 旋转到目标角度 if(angle >= 0 && angle <= 180) { servo_rotate(angle); } else { printf("Invalid angle! Please enter an angle between 0 and 180.\n"); } }​ return 0;}Pyhon代码: 使用RPi.GPIO库来控制树莓派上的GPIO引脚,实现对舵机的控制。通过setup()函数进行初始化设置,并通过set_angle()函数控制舵机旋转到指定的角度。在main()函数中,使用键盘输入获取目标角度,并调用set_angle()函数控制舵机旋转到目标角度。import RPi.GPIO as GPIOimport time​SERVO_PIN = 18 # SG90舵机信号线连接的GPIO引脚​def setup(): GPIO.setmode(GPIO.BCM) GPIO.setup(SERVO_PIN, GPIO.OUT) global servo_pwm servo_pwm = GPIO.PWM(SERVO_PIN, 50) # 创建PWM对象,频率设置为50Hz servo_pwm.start(0) # 启动PWM输出,初始占空比设为0​def set_angle(angle): duty_cycle = (angle / 18) + 2.5 # 将角度转换为占空比值 servo_pwm.ChangeDutyCycle(duty_cycle) time.sleep(0.3) # 等待舵机转到指定角度​def main(): setup()​ while True: # 从键盘输入目标角度 angle = int(input("Enter the angle to rotate (0-180): "))​ # 旋转到目标角度 if 0 <= angle <= 180: set_angle(angle) else: print("Invalid angle! Please enter an angle between 0 and 180.")​if __name__ == '__main__': try: main() finally: servo_pwm.stop() # 停止PWM输出 GPIO.cleanup() # 清理GPIO资源3.2 调用算法识别目标(1)安装PaddlePaddle和PaddleDetection库:先安装Python和pip。然后,打开终端并执行以下命令安装PaddlePaddle和PaddleDetection库:pip install paddlepaddle paddlepaddle-gpupip install paddlehubpip install paddlehub -i https://pypi.tuna.tsinghua.edu.cn/simple(2)下载预训练模型:百度飞桨提供了预训练的目标检测模型,可以从PaddleDetection的GitHub页面下载这些模型。选择适合的任务的模型,并将其解压到合适的目录中。(3)编写调用代码:创建一个Python脚本文件,例如detect_fruits.py,并使用以下代码编写脚本:import paddlehub as hubimport cv2def detect_fruits(image_path, model_path): # 加载模型 module = hub.Module(name='yolov3_mobilenet_v1_coco2017') input_dict = {'image': [image_path]} # 目标检测 results = module.object_detection(data=input_dict) # 处理结果 for result in results: if len(result['data']) > 0: for obj in result['data']: label = obj['label'] confidence = obj['confidence'] left, top, right, bottom = obj['left'], obj['top'], obj['right'], obj['bottom'] print(f"Label: {label}, Confidence: {confidence:.2f}") print(f"Bounding Box: ({left}, {top}), ({right}, {bottom})") # 可视化结果 img = cv2.imread(image_path) for result in results: module.visualize(data=result, output_dir='output', score_thresh=0.5, use_visualize=True, visualization=True, plot_bbox=True, save_bbox_txt=True, image=img)if __name__ == '__main__': image_path = 'path/to/your/image.jpg' # 替换为你的图片路径 model_path = 'path/to/your/model' # 替换为你的模型路径 detect_fruits(image_path, model_path)在上面的代码中,使用PaddleHub库加载了预训练的yolov3_mobilenet_v1_coco2017模型,并将其应用于指定的图像。然后,处理检测结果并进行输出。最后,使用OpenCV库可视化结果并保存到指定目录中。(4)运行脚本:将目标果实图像放置在与脚本相同的目录下(或根据需要修改图像路径)。然后,在终端中执行以下命令运行脚本:python detect_fruits.py脚本将分析图像并输出检测到的目标果实的标签、置信度和边界框。会生成一个带有目标果实标注的图像。3.3 机器人小车控制代码小车的电机驱动采用L298N模块,连接在GPIO17、GPIO18、GPIO27和GPIO22上。 使用了wiringPi库来控制树莓派上的GPIO引脚,实现对小车电机驱动的控制。通过setup()函数进行初始化设置,并通过forward()、backward()、turn_left()和turn_right()函数控制小车前进、后退和转弯。其中,stop()函数用于停止小车运动。#include <wiringPi.h>#define MOTOR_ENA_PIN 0 // L298N模块ENA引脚连接的GPIO引脚#define MOTOR_ENB_PIN 2 // L298N模块ENB引脚连接的GPIO引脚#define MOTOR_IN1_PIN 3 // L298N模块IN1引脚连接的GPIO引脚#define MOTOR_IN2_PIN 4 // L298N模块IN2引脚连接的GPIO引脚#define MOTOR_IN3_PIN 5 // L298N模块IN3引脚连接的GPIO引脚#define MOTOR_IN4_PIN 6 // L298N模块IN4引脚连接的GPIO引脚void setup() { wiringPiSetup(); // 初始化wiringPi库 pinMode(MOTOR_ENA_PIN, OUTPUT); pinMode(MOTOR_ENB_PIN, OUTPUT); pinMode(MOTOR_IN1_PIN, OUTPUT); pinMode(MOTOR_IN2_PIN, OUTPUT); pinMode(MOTOR_IN3_PIN, OUTPUT); pinMode(MOTOR_IN4_PIN, OUTPUT);}void forward() { digitalWrite(MOTOR_IN1_PIN, HIGH); digitalWrite(MOTOR_IN2_PIN, LOW); digitalWrite(MOTOR_IN3_PIN, LOW); digitalWrite(MOTOR_IN4_PIN, HIGH); digitalWrite(MOTOR_ENA_PIN, HIGH); digitalWrite(MOTOR_ENB_PIN, HIGH);}void backward() { digitalWrite(MOTOR_IN1_PIN, LOW); digitalWrite(MOTOR_IN2_PIN, HIGH); digitalWrite(MOTOR_IN3_PIN, HIGH); digitalWrite(MOTOR_IN4_PIN, LOW); digitalWrite(MOTOR_ENA_PIN, HIGH); digitalWrite(MOTOR_ENB_PIN, HIGH);}void turn_left() { digitalWrite(MOTOR_IN1_PIN, LOW); digitalWrite(MOTOR_IN2_PIN, HIGH); digitalWrite(MOTOR_IN3_PIN, LOW); digitalWrite(MOTOR_IN4_PIN, HIGH); digitalWrite(MOTOR_ENA_PIN, HIGH); digitalWrite(MOTOR_ENB_PIN, HIGH);}void turn_right() { digitalWrite(MOTOR_IN1_PIN, HIGH); digitalWrite(MOTOR_IN2_PIN, LOW); digitalWrite(MOTOR_IN3_PIN, HIGH); digitalWrite(MOTOR_IN4_PIN, LOW); digitalWrite(MOTOR_ENA_PIN, HIGH); digitalWrite(MOTOR_ENB_PIN, HIGH);}void stop() { digitalWrite(MOTOR_ENA_PIN, LOW); digitalWrite(MOTOR_ENB_PIN, LOW);}int main() { setup(); while (1) { // 从键盘输入指令 char cmd = getchar(); getchar(); // 忽略回车符 // 根据指令执行动作 switch (cmd) { case 'w': // 前进 forward(); break; case 's': // 后退 backward(); break; case 'a': // 左转 turn_left(); break; case 'd': // 右转 turn_right(); break; case 'x': // 停止 stop(); break; default: break; } } return 0;}四、总结随着农业技术的不断进步,自动化、智能化已成为现代农业生产的重要趋势。本项目通过结合视觉识别技术、机器人技术和自动化控制技术,成功设计并实现了基于视觉识别的自动采摘机器人系统。这一创新性的成果不仅提高了果园采摘的效率和准确性,降低了人力成本,还为农业生产的现代化和智能化发展提供了新的解决方案和思路。
  • [技术干货] 基于STM32的儿童智能安全防护书包设计
    一、前言1.1 项目介绍【1】项目功能介绍随着社会的进步和科技的发展,儿童安全问题日益引起广泛关注。在日常生活中,尤其是在上学放学途中、户外活动时,儿童走失事件时有发生,给家庭和社会带来了极大的困扰和担忧。随着学业负担的增加,学生时常会因为忘记携带所需书籍而影响学习。如何利用现代技术手段提高儿童安全保障水平,并辅助他们培养良好的学习习惯,成为了一个待解决的社会需求。基于此背景,当前设计并实现一款基于STM32F103RCT6微控制器为核心的儿童智能安全防护书包,显得尤为必要与实际。这款书包集成了先进的定位技术和无线通信模块,能够实时追踪并发送儿童的位置信息给家长,确保在紧急情况下快速响应 (发送短信的时候,直接通过GPS经纬度拼接百度地图的HTTP请求链接,家长收到短信可以直接打开链接,在网页查看百度地图上显示的具体位置,可以直接通过百度地图导航过去)。同时,具备智能化功能,如课程表录入存储与提醒系统,利用EEPROM(例如AT24C02)进行数据持久化存储,并通过RFID-RC522射频识别模块自动检测所携带书籍是否齐全,避免孩子因疏忽遗漏课本而耽误学习。智能书包还配备了直观易读的1.44寸LCD显示屏,用于显示当前位置信息、当日课表以及未带书籍的提醒。当检测到缺少某本书籍时,蜂鸣器模块会发出声音警报,从而强化提醒效果,帮助学生养成有序整理个人物品的习惯。这款基于STM32的儿童智能安全防护书包是一个集成物联网技术、GPS定位、无线通信和智能感知于一体的创新产品,提升儿童的安全防护等级,加强家校互动,促进学生自我管理能力的培养,充分体现了科技服务于生活、服务于教育的理念。【2】设计实现的功能(1)实时定位与紧急求助功能:通过集成GPS模块,该智能书包能够实时获取并更新儿童的位置信息,并通过无线通信(GSM短信模块如Air724UG 4G)将位置数据发送给家长。当儿童在陌生环境中迷路或者遇到紧急情况时,只需按下求救按钮,系统立即向预设的家长手机发送包含当前位置信息的短信,方便家长迅速找到孩子。(2)课程表管理与提醒功能:设计有课程表录入存储模块,利用EEPROM芯片AT24C02进行非易失性数据存储,家长或学生可以预先将每日课程表输入到系统中。每天早上,书包会根据存储的课程表自动检查当天所需的书籍是否已放入书包内。通过射频识别RFID-RC522模块读取贴在书籍上的标签信息,若发现缺少某科书籍,则蜂鸣器会发出声音警报,同时LCD显示屏也会显示相应的提示信息,确保学生不会忘记携带必要的学习资料。(3)可视化信息展示:配备了1.44寸LCD显示屏,可实时显示当前地理位置信息、时间以及当日的课程表内容,使得学生和家长可以直观地查看重要信息。这款基于STM32的儿童智能安全防护书包实现了儿童安全监护和学业辅助两大核心功能,既有助于保障孩子的安全出行,又能培养他们的自我管理和规划能力,体现了科技产品在教育领域的深度应用价值。【3】项目硬件模块组成(1)主控芯片:STM32F103RCT6微控制器作为整个系统的“大脑”,负责控制和协调各个功能模块的运作,处理GPS定位数据、GSM短信通信、RFID识别信息等,并通过程序逻辑实现课程表管理、提醒以及数据显示等功能。(2)定位模块:GPS模块,用于实时获取并更新儿童所在位置信息,确保家长可以随时查看孩子的位置状态。(3)无线通信模块:Air724UG 4G GSM短信模块,提供远程通信能力,当发生紧急情况时,儿童可通过书包上的求救按钮触发发送带有位置信息的短信至预设的家长手机。(4)存储模块:AT24C02 EEPROM芯片,用作非易失性存储器,存储孩子的课程表信息,即使在断电情况下也能保存数据不丢失。(5)射频识别模块:RFID-RC522模块,配合贴在书籍上的RFID标签,检测书包内是否携带齐全当日所需的书籍资料,如果发现缺少书籍,会触发报警提示。(6)报警提示模块:高电平触发的蜂鸣器模块,在检测到未带书籍或其它异常情况时,通过发出声音警报来提醒学生。(7)显示模块:采用1.44寸LCD显示屏,实时展示当前位置、时间、当天课表及未带书籍等重要信息,方便用户直观了解当前状况。【3】功能总结基于STM32的儿童智能安全防护书包设计(1)定位模块:定位模块实时获取儿童位置信息,能够家长在放学时找到儿童位置,或者丢失时及时查找儿童的位置。 (2)GSM短信模块:儿童如果意识到自己走丢,就可以按下求救按钮发送位置短信给家长,进而使家长尽快找到儿童位置 (3)课程表录入存储模块:将每天的课程表录入系统,以便提醒学生第二天要带什么书籍。(eeprom) (4)射频识别模块:将每一科的书籍贴上标签再与系统中的课表进行对比,如果检测到当天某一科书本没有带,蜂鸣器会响,呼吸灯会亮。(RFID) (5)显示模块:LCD屏显示实时位置课表信息以及提示没有带的书籍。硬件选型:(1)主控芯片采用STM32F103RCT6(2)定位模块采用: GPS模块(3)短信发送模块采用 Air724UG 4G(4)存储模块采用AT24C02(5)射频识别模块采用RFID-RC522(6)报警提示采用高电平触发的蜂鸣器模块(7)显示屏采用1.44寸LCD显示屏1.2 设计思路(1)需求分析阶段:对目标用户群体(儿童与家长)的需求进行深入研究和理解,确定主要功能点:儿童安全定位、紧急求助、课程表管理与提醒、书籍携带检测等。(2)系统架构设计:根据需求,选择STM32F103RCT6作为主控芯片,因其具有强大的处理能力、丰富的外设接口及低功耗特性,能够满足项目所需的复杂计算任务和多模块协调工作。(3)功能模块划分:定位模块设计采用GPS接收器,实时获取并解析位置信息。无线通信模块选用4G GSM短信模块Air724UG,实现实时位置信息的远程发送和接收紧急求助信号。数据存储模块使用EEPROM AT24C02,确保课程表数据的安全可靠存储。为实现书籍携带检测,利用RFID-RC522射频识别模块,结合预置在书籍上的RFID标签,自动识别书包内书籍是否齐全。报警提示模块通过高电平触发的蜂鸣器来发出声音警告,提醒学生遗漏书籍。显示模块配备1.44寸LCD显示屏,直观展示位置信息、课程表以及未带书籍的提醒。(4)软硬件协同设计:硬件方面,合理布局各模块,优化电源管理,确保设备稳定运行;软件方面,编写高效的嵌入式程序,实现对各个硬件模块的控制和交互,包括GPS数据解析、GSM通信协议栈开发、RFID读取与比对算法、数据显示逻辑等。(5)人机交互设计:设计简洁易用的界面和操作流程,如一键求救按钮、清晰的课程表显示、直观的报警提示等,便于儿童和家长快速理解和操作。1.3 系统功能总结功能模块功能描述定位模块(GPS)实时获取并更新儿童地理位置信息,通过无线通信模块发送至家长设备,以便家长随时掌握孩子位置动态。短信通信模块(GSM)儿童在紧急情况下按下求救按钮,系统通过4G GSM模块向预设的家长手机号码发送包含定位信息的短信进行求助。课程表管理模块(EEPROM)学生或家长可以录入每日课程表至系统中,利用AT24C02 EEPROM芯片存储数据,确保断电后仍能保留课程信息。书籍检测模块(RFID)通过RFID-RC522射频识别模块读取书本上的标签信息,自动比对与当日课表要求的书籍是否齐全,如有遗漏则触发报警提示。报警提示模块(蜂鸣器)当检测到学生未携带某科书籍或发生其他异常情况时,高电平触发的蜂鸣器会发出声音警告提醒学生。显示模块(LCD显示屏)配备1.44寸LCD显示屏实时显示当前位置、时间、当天课程表以及未带书籍的提示信息,方便学生查看和确认。1.4 开发工具的选择STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解简单、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。二、代码设计2.1 GPS解析代码基于STM32 HAL库进行GPS NMEA协议数据解析的代码#include "stm32f1xx_hal.h"#include <string.h>#include <stdio.h>​// GPS 数据结构体定义typedef struct { char GPGGA[100]; // 用于存储GPGGA语句 double latitude; // 纬度 double longitude; // 经度 float UTC_time[7]; // UTC时间 int fix_quality; // 定位质量 float hdop; // 水平精度因子} GPS_Data_TypeDef;​// 全局变量声明GPS_Data_TypeDef GPS_Data;​// 串口接收缓冲区uint8_t RxBuffer[256];​// GPS NMEA数据处理函数void ProcessGPSData(void) { static uint8_t index = 0; // 从串口接收到的数据中查找特定的NMEA语句,例如GPGGA if (strstr((char*)RxBuffer, "$GPGGA")) { // 分割NMEA语句获取所需字段 char *token; token = strtok((char*)RxBuffer, ","); while(token != NULL) { // 解析各字段 if (/* 判断当前token是否为纬度 */) { GPS_Data.latitude = atof(token); // 考虑南北纬转换及格式化 } else if (/* 判断当前token是否为经度 */) { GPS_Data.longitude = atof(token); // 考虑东西经转换及格式化 } else if (/* 判断当前token是否为UTC时间 */) { // 处理时间信息 } token = strtok(NULL, ","); }​ // 清空接收缓冲区以便下次接收新的数据 memset(RxBuffer, 0, sizeof(RxBuffer)); index = 0; }}​// 串口中断服务函数void USART1_IRQHandler(void) { static uint8_t UART_Rx_STA = 0; uint8_t temp=0;​ if ((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET)) { temp = huart1.Instance->RDR & 0xff; // 读取接收到的数据​ // 将接收到的数据添加到缓冲区,并检查是否有完整的NMEA句子结束符'\r\n' if (temp == '\n') { // 根据实际使用情况可能需要同时检测'\r'和'\n' RxBuffer[index++] = temp; // 添加换行符 RxBuffer[index] = '\0'; // 字符串结束符 ProcessGPSData(); // 调用解析函数 } else if (index < (sizeof(RxBuffer)-1)) { RxBuffer[index++] = temp; } }}​int main(void) { // 初始化系统时钟和USART1 MX_GPIO_Init(); MX_USART1_UART_Init();​ // 开启串口中断 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);​ while (1) { // 主循环 // ... }}2.2 AT24C02存储代码#include "stm32f1xx_hal.h"#include "stm32f1xx_hal_i2c.h"​#define EEPROM_ADDRESS 0xA0 // AT24C02的I2C地址​I2C_HandleTypeDef hi2c1;​void SystemClock_Config(void);​void I2C_Init() { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1);}​void EEPROM_Write(uint16_t addr, uint8_t data) { HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY);}​uint8_t EEPROM_Read(uint16_t addr) { uint8_t data; HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDRESS, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, HAL_MAX_DELAY); return data;}​int main(void) { HAL_Init(); SystemClock_Config(); I2C_Init();​ uint16_t address = 0x00; uint8_t data_to_write = 0x55; uint8_t data_read;​ // 写入数据到EEPROM EEPROM_Write(address, data_to_write);​ // 从EEPROM读取数据 data_read = EEPROM_Read(address);​ // 检查读取的数据 if (data_read == data_to_write) { // 读取成功 } else { // 读取失败 }​ while (1) { }}2.3 RFID-RC522代码STM32与RFID-RC522的交互涉及到很多步骤,包括初始化RFID模块、检测卡片、读取卡片信息、以及将这些信息存储起来。#include "stm32f10x.h" #include "SPI.h" #include "RFID.h" #include "usart.h" // 如果你使用了串口打印,可以包含这个头文件 #define SS_PIN PA4 // 根据你的连接修改这个引脚 #define RST_PIN PA3 // RFID模块的复位引脚 RFID rfid(SS_PIN, RST_PIN); // 创建RFID对象 uint8_t uid[5]; // 用于存储卡片的UID int main(void) { SystemInit(); // 初始化系统 usart_init(115200); // 初始化串口,用于调试 SPI_Init(); // 初始化SPI接口 rfid.PCD_Init(); // 初始化RFID模块 while (1) { if (rfid.PICC_IsNewCardPresent()) // 检测是否有新卡片 { rfid.PICC_ReadCardSerial(); // 读取卡片UID for (uint8_t i = 0; i < rfid.uid.size; i++) // 将UID存储到uid数组中 { uid[i] = rfid.uid.uidByte[i]; } // 可以在这里添加代码将uid存储到EEPROM、Flash或其他存储介质中 // 也可以通过串口打印UID进行调试 printf("Card UID: "); for (uint8_t i = 0; i < rfid.uid.size; i++) { printf("%02X ", uid[i]); } printf("\r\n"); } HAL_Delay(100); // 延时一段时间,避免过度占用CPU } }2.4 Air724UG 4G代码使用STM32的HAL库和USART外设(串口)与Air724UG 4G模块进行通信。通过UART_SendString()函数可以发送字符串到串口,通过GSM_SendCommand()函数可以向4G模块发送AT指令。在main()函数中,设置短信格式为文本模式,设置短信接收方号码,发送短信内容。#include "stm32f1xx_hal.h"#include <string.h>​UART_HandleTypeDef huart1;​void SystemClock_Config(void);static void MX_GPIO_Init(void);static void MX_USART1_UART_Init(void);​void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { // UART发送完成回调函数}​void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // UART接收完成回调函数}​void UART_SendString(const char *str) { HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), HAL_MAX_DELAY);}​void GSM_SendCommand(const char *cmd) { UART_SendString(cmd); UART_SendString("\r\n"); HAL_Delay(100); // 等待响应}​int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init();​ char phone_number[] = "13800138000"; char message[] = "Hello, this is a test message from STM32!";​ // 初始化Air724UG 4G模块 GSM_SendCommand("AT+CMGF=1"); // 设置短信格式为文本模式 GSM_SendCommand("AT+CMGS=\"+86"); // 设置短信接收方号码 GSM_SendCommand(phone_number); GSM_SendCommand("\"");​ // 发送短信内容 GSM_SendCommand(message); HAL_UART_Transmit(&huart1, (uint8_t)26, 1, HAL_MAX_DELAY); // 发送Ctrl+Z结束符​ while (1) { }}​void SystemClock_Config(void) { // 系统时钟配置}​static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart1);}​三、总结基于STM32的儿童智能安全防护书包设计通过先进的技术手段,为儿童的安全提供全方位的保障。本项目融合了GPS定位、GSM短信通信、EEPROM存储、RFID射频识别以及LCD显示等多项功能,力求在儿童的日常生活和学习中提供便捷与安全。通过GPS模块,书包能够实时追踪儿童的位置,让家长随时了解孩子的行踪,确保在关键时刻能够迅速找到孩子。而GSM短信模块则为儿童提供了一个紧急求助的途径,一旦孩子意识到自己走丢或有其他紧急情况,只需按下求救按钮,即可将位置信息以短信的形式发送给家长,从而迅速获得帮助。在学习方面,本项目通过EEPROM存储模块实现了课程表的录入与存储功能。儿童或家长可以将每天的课程表录入系统,书包便会在需要时提醒孩子携带相应的书籍。而RFID射频识别模块则负责检测书包内是否已携带当天所需的各科书本。如果检测到某一科书本缺失,书包上的蜂鸣器会发出警报声,呼吸灯也会亮起,以提醒孩子及时补充。此外,本项目还采用了一块1.44寸的LCD显示屏,用于实时显示儿童的位置信息、课表内容以及未携带书籍的提示信息。这使得儿童在使用书包的过程中能够随时了解自己的位置和当天的学习任务,为他们的安全和学习提供了双重保障。
  • [问题求助] 物联网传感器在环境监测中的应用面临哪些挑战?如何解决这些问题?
    从智慧城市的角度来说,城市的环境监测系统,传感器被部署在多个监测点,收集空气质量、噪音、水质等数据.   这些设计过程会有什么实际的技术 挑战,和不确定因素?
  • [问题求助] 在工业物联网中,传感器数据如何帮助优化生产流程?
    在工业物联网中,传感器如何收集生产线上的数据,并通过数据分析为生产流程优化提供支持的?   如何优化提高生产效率、降低能耗和减少故障率。
  • [问题求助] 物联网传感器在农业领域的应用中,如何帮助实现精准农业?
    物联网传感器在农业领域的应用中,如何帮助实现精准农业?  有没有什么建议。 传感器的寿命很大问题。
  • [问题求助] POSTMAN无法获得设备影子数据
    请教各位大佬。新注册了华为云,现在好像只能是标准版了。能正确通过POSTMAN得到token。如下图。建好模型,定义好插件,建立虚拟设备,可以上报数据,在设备里也能看到上报的数据。但是用postman去访问设备影子数据却始终无法得到。但是同样的方法,我之前另一个账号使用基础版,是没有问题的。下图是我另一个账号,使用基础版,用postman得到的数据。是现在的标准版里还需要做什么其他的设置吗?求各位大佬指教。。。。
  • [问题求助] 新手,学习Iot物联网,需要会开发,需要买小熊派开发套件吗?
    本人是搞运维的,对开发语言不是怎么懂。shell脚本倒是会些。新手学习Iot物联网 HCIA,HCIP的内容, 需要会开发语言 JAVA,Python 吗,需要买小熊派开发套件吗?全套的小熊派,价格可不便宜呀。老手指导下学习过程与经验。
  • [问题求助] 传感器走modbus的可以接入吗
    传感器走modbus的可以接入吗
  • [问题求助] 关于mqtt协议传输数据的问题
    问题是这样:就是我需要用stm32+4g模块向华为云端传输传感器的数据(用的是lpuart,波特率最高是9600),我想让这个数据实时显示在华为云界面的ui上或者说传输延时在1s以内。但现在的问题是通过stm32向4g模块发送AT指令时,有两条指令得间隔1s左右才能发送,这样就导致传输的效率非常慢,所以想问一下各位大佬能不能用https协议传这些数呢,或者说有没有其他可以提高传输效率的方法呢?
  • [问题求助] 小熊派支持标准库开发吗
    小熊派支持标准库开发吗
总条数:623 到第
上滑加载中