基于STM32的远程遥控安卓小车的设计与实现

一、前言

1.1 项目介绍

【1】项目功能介绍

随着物联网技术与移动通信技术的快速发展,远程遥控设备在日常生活及工业应用中的普及度日益提高。无论是家用扫地机器人实现自主导航清扫,还是目前抖音平台上展示的实景互动小车等创新应用,都体现了远程控制和实时视频监控技术对现代智能设备的重要性。

本项目设计并实现一款基于STM32微控制器的远程遥控安卓小车系统。该系统充分利用了淘汰下来的安卓旧手机作为车载信息处理单元,不仅实现了资源的有效再利用,还结合4G网络技术以及先进的流媒体服务和物联网技术,搭建起一套集远程操控、实时视频音频传输于一体的高效解决方案。

项目的小车搭载了STM32主控板以精确控制四个电机的动作,通过L298N驱动芯片确保了底座移动的稳定性和灵活性。同时,小车的动力源采用两节18650锂电池提供充足的电力支持。

车载的旧安卓手机通过USB线连接到STM32主控板上,接收并执行来自远端手机APP的指令。这款由Qt开发的Android APP能够利用4G网络实现实时在线,并通过摄像头采集音视频数据,通过RTMP协议将这些数据推送到华为云ECS服务器上的NGINX流媒体服务器,从而实现高清流畅的远程视频监控。

为了实现双向交互和低延迟控制,整个系统还借助MQTT协议连接至华为云IOT服务器。另一台安装了同样由Qt开发的Android手机APP的终端设备,可以通过该APP拉取小车端的实时音视频流进行播放,并通过方向键菜单实现对小车的精准远程操控。这种设计不仅极大地拓展了传统遥控小车的功能性与实用性,还为其他类似应用场景提供了可借鉴的技术框架。

本次设计里采用的小车底座:

链接:item.taobao.com/item.htm?sp...

本次设计里采用的Android手机:

2015年上市的小米4C。 现在在某鱼上也就值个200块。但是这性价比比什么树莓派、嵌入式各类的Android的、Linux的开发板那是甩了几条街的。

遥控效果:

【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网络环境下高效、低延迟的数据交互,实现对小车的实时远程控制,提升整体系统的响应速度和操作体验

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 购买云服务器

如果之前没有使用过华为云的ECS服务器,可以先申请试用1个月,了解服务器的基本使用然后再购买,不得不说提供这个试用服务还是非常不错。

产品试用领取地址: activity.huaweicloud.com/free_test/i...

每天9:30开抢,每天限量100份,这个页面不仅有云服务器可以领取试用,还有云数据库、沙盒等其他产品。

我这里领取了 2核4G S6云服务器,这个服务器是配套华为自研25GE智能高速网卡,适用于网站和Web应用等中轻载企业应用。

选择配置的时候发现S6规格的已经没有了,来晚了。

S6规格没有了,就选择了S3,2核,4GB的配置结算。

页面向下翻,下面选择系统预装的系统,我这里选择ubuntu 20.04,安装NGINX,来搭建流媒体服务器。

页面继续下翻,设置云服务器名称,设置系统的root密码。

接着就可以继续去支付了。

购买后等待一段时间,系统资源就即可分配完成。

2.2 登录云服务器

云服务器的管理控制台从这里进入: www.huaweicloud.com/product/ecs...

在官网主页,点击产品,找到计算选项,就可以看到弹性云服务器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配置文件进行添加配置。

添加配置的流程:

cpp 复制代码
命令行输入:
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协议方式登录云服务器,需要具备以下几个前提条件。

cpp 复制代码
[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服务器

cpp 复制代码
root@ecs-348470:~# sudo apt-get install nfs-kernel-server

(2)创建一个work目录方便当做共享目录使用

cpp 复制代码
root@ecs-348470:~# mkdir work

(3)编写NFS配置文件

NFS 服务的配置文件为/etc/exports,如果系统没有默认值,这个文件就不一定会存在,可以使用 vim 手动建立,然后在文件里面写入配置内容。

cpp 复制代码
/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure)    

配置文件里参数的含义:

(4)NFS服务器相关指令

cpp 复制代码
/etc/init.d/nfs-kernel-server start #启动 NFS 服务
ufw disable     #关闭防火墙
/etc/init.d/nfs-kernel-server restart  #重启NFS服务
exportfs -arv   #共享路径生效

(5)本地客户机挂载服务器的目录

cpp 复制代码
wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/

(6)设置华为云服务器的安全策略

需要把华为云服务器的端口号开放出来,不然其他客户端是无法访问服务器的。

我这里比较粗暴直接,直接将所有端口号,所有IP地址都开放出来了。

**(7)本地客户机挂载服务器测试 **

挂载指令:

cpp 复制代码
sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/

2.6 安装NGINX(配置RTMP服务)

(1)下载编译时需要依赖的一些工具

cpp 复制代码
root@ecs-348470:~# sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev

(2)下载NGINX编译需要的软件包

cpp 复制代码
root@ecs-348470:~# mkdir nginx      
root@ecs-348470:~# cd nginx/
root@ecs-348470:~# wget http://nginx.org/download/nginx-1.10.3.tar.gz
root@ecs-348470:~# wget http://zlib.net/zlib-1.2.11.tar.gz
root@ecs-348470:~# wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz
root@ecs-348470:~# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz
root@ecs-348470:~# wget https://github.com/arut/nginx-rtmp-module/archive/master.zip

(3)下载后的文件全部解压

cpp 复制代码
root@ecs-348470:~# tar xvf openssl-1.0.2k.tar.gz
root@ecs-348470:~# tar xvf nginx-rtmp-module-master.tar.gz
root@ecs-348470:~# tar xvf nginx-1.8.1.tar.gz
root@ecs-348470:~# tar xvf pcre-8.40.tar.gz
root@ecs-348470:~# tar xvf zlib-1.2.11.tar.gz

(4)配置NGINX源码,生成Makefile文件

cpp 复制代码
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

cpp 复制代码
 root@ecs-348470:~/nginx/nginx-1.8.1# make && make install

安装之后NGINX的配置文件存放路径:

cpp 复制代码
/usr/local/nginx/nginx:主程序

(6)查看NGINX的版本号

cpp 复制代码
root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.8.1

(5)在配置文件里加入RTMP服务器的配置

cpp 复制代码
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)检查配置文件是否正确

cpp 复制代码
root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

(6)NGINX常用的3个命令

cpp 复制代码
sudo service nginx start
sudo service nginx stop
sudo service nginx restart

(7)启动NGINX服务器

cpp 复制代码
sudo service nginx start

2.7 摄像头推流音视频到服务器

为了模拟摄像头实时监控推流,我这使用QT+FFMPEG编写了一个小软件,在windows下运行,推流本地笔记本电脑的数据到服务器。软件运行之后,将本地音频、视频编码之后通过RTMP协议推流到NGINX服务器。

软件运行效果:

推流工具运行过程中效果。

2.8 编写本地RTMP流播放器

在上面通过推流客户端模拟摄像头,已经将本地的摄像头数据实时推流到服务器了,那么还差一个播放器,为了方便能够在任何有网的地方实时查看摄像头的音频和图像,还需要编写一个RTMP流媒体播放器。

我这里的播放器内核是采用libvlc开发的,使用QT作为GUI框架,开发了一个流媒体播放器,可以实时拉取服务器上的流数据,并且还支持回放。因为服务器上的NGINX配置了自动保存的参数,可以将推上去的流按时间段保存下来。

输入服务器地址之后就可以拉取流进行播放。

点击获取回放列表,可以查看服务器上保存的历史视频回放播放。

三、华为云IOT服务器部署过程

在华为云IOT平台上,需要进行设备接入、数据模型定义、规则引擎配置和应用开发等四个核心模块的开发。其中,设备接入模块包括设备注册、获取设备证书、建立连接等步骤,以保障设备与云平台之间的安全通信;数据模型定义模块需要根据实际需求定义相应的数据模型,包括上传数据格式、设备属性和服务等。规则引擎配置模块需要完成实时消息推送、远程控制和告警等功能。应用开发模块则是将完整的智能井盖系统进行打包,为用户提供统一的操作接口。

华为云官网: www.huaweicloud.com/

打开官网,搜索物联网,就能快速找到 设备接入IoTDA

3.1 物联网平台介绍

华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。

使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。

物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。

设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。

业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。

3.2 开通物联网服务

地址: www.huaweicloud.com/product/iot...

开通标准版免费单元。

开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。

总结:

cpp 复制代码
端口号:   MQTT (1883)| MQTTS (8883)	
接入地址: a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com

根据域名地址得到IP地址信息:

cpp 复制代码
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=31

121.36.42.100 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 36ms,最长 = 37ms,平均 = 36ms

C:\Users\11266>

MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。

3.3 创建产品

(1)创建产品

点击右上角创建产品。

(2)填写产品信息

根据自己产品名字填写,设备类型选择自定义类型。

(3)添加自定义模型

产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。

模型简单来说: 就是存放设备上传到云平台的数据。比如:环境温度、环境湿度、环境烟雾浓度、火焰检测状态图等等,这些我们都可以单独创建一个模型保存。

3.4 添加设备

产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。

(1)注册设备

点击右上角注册设备。

(2)根据自己的设备填写

在弹出的对话框里填写自己设备的信息。根据自己设备详细情况填写。

(3)保存设备信息

创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。

3.5 MQTT协议主题订阅与发布

(1)MQTT协议介绍

当前的设备是采用MQTT协议与华为云平台进行通信。

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。

MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。

华为云的MQTT协议接入帮助文档在这里: support.huaweicloud.com/devg-iothub...

业务流程:

(2)华为云平台MQTT协议使用限制

描述 限制
支持的MQTT协议版本 3.1.1
与标准MQTT协议的区别 支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg
MQTTS支持的安全等级 采用TCP通道基础 + TLS协议(最高TLSv1.3版本)
单帐号每秒最大MQTT连接请求数 无限制
单个设备每分钟支持的最大MQTT连接数 1
单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关 3KB/s
MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝 1MB
MQTT连接心跳时间建议值 心跳时间限定为30至1200秒,推荐设置为120秒
产品是否支持自定义Topic 支持
消息发布与订阅 设备只能对自己的Topic进行消息发布与订阅
每个订阅请求的最大订阅数 无限制

(3)主题订阅格式

帮助文档地址:support.huaweicloud.com/devg-iothub...

对于设备而言,一般会订阅平台下发消息给设备 这个主题。

设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。

(4)主题发布格式

对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。

这个操作称为:属性上报。

帮助文档地址:support.huaweicloud.com/usermanual-...

3.6 MQTT三元组

MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。

接下来介绍,华为云平台的MQTT三元组参数如何得到。

(1)MQTT服务器地址

要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。

帮助文档地址:console.huaweicloud.com/iotdm/?regi...

MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。

根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)

cpp 复制代码
华为云的MQTT服务器地址:121.36.42.100
华为云的MQTT端口号:1883

(2)生成MQTT三元组

华为云提供了一个在线工具,用来生成MQTT鉴权三元组: iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。

下面是打开的页面:

四、STM32端代码设计

4.1 STM32小车底座驱动代码

cpp 复制代码
#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_7

void 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);
    }
}

4.2 小车控制代码

cpp 复制代码
#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_7

void 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中添加以下代码:

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)处理串口数据:接收到的串口数据可以通过信号槽机制或者其他方式传递给界面进行显示或进一步处理。

下面是测试的代码:

cpp 复制代码
#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();
}

六、总结

本基于STM32的远程遥控安卓小车设计与实现项目,成功构建了一个集智能控制、无线通信、实时音视频传输和物联网技术于一体的创新应用平台。通过精心选择和整合各组件,实现了资源循环利用和高效能目标:淘汰的安卓手机作为车载智能终端,利用STM32微控制器精确驱动四个电机,确保了小车的稳定移动性能;采用18650锂电池提供充足能源保障。

在系统通信方面,该项目充分利用4G网络高速率、大带宽的优势,结合华为云服务,一方面通过流媒体技术将车载摄像头采集的音频视频信息实时推送到RTMP服务器,为用户提供流畅清晰的远程监控体验;另一方面,借助MQTT协议连接至华为云IOT服务器,实现了低延迟、高可靠性的双向数据交换。

在用户体验上,设计开发的双端Qt Android应用程序不仅具备直观易用的方向键控制界面,使得用户能够轻松远程操控小车行动,还能实现实时接收和播放小车端的音视频流,极大地提升了交互性和实用性。

相关推荐
IT_陈寒15 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈16 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员17 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊17 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户83562907805117 小时前
Python 操作 Word 文档节与页面设置
后端·python
酒後少女的夢18 小时前
设计模式教程
后端·架构
凌览18 小时前
别再手搓 Skill 了,用这个工具 5 分钟搞定
前端·后端
weixin_4080996718 小时前
python请求文字识别ocr api
开发语言·人工智能·后端·python·ocr·api·ocr文字识别
weixin_4080996718 小时前
【组合实战】OCR + 图片去水印 API:自动清洗图片再识别文字(完整方案 + 代码示例)
图像处理·后端·ocr·api·文字识别·去水印·ocr识别优化