[其他ST产品] 基于STM32的移动视频监控系统

[复制链接]
1796|26
 楼主| 慢醇 发表于 2023-1-30 23:54 | 显示全部楼层 |阅读模式
前言
关键词:无线通信;单片机;视频监控;android;WiFi
一、材料
1.1制作材料:

硬件系统主要由单片机主控模块、电源模块、电机驱动模块、WiFi通信模块和无线视频监控模块组成。
1.2主控模块
主控模块采用STM32F103为主控制器,STM32F103属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3。该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。芯片集成定时器,CAN,ADC,SPI,I2C,USB,UART,等多种功能。STM32F103可使用keilC语言编译,支持STLink-SWD在线调试,主要用于收集信息、处理数据、协调系统中的每个功能模块预计要完成的任务。(图3)

评论

———————————————— 版权声明:本文为CSDN博主「学海浪太大」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/lianghuajunone/article/details/123962401  发表于 2023-1-30 23:54
 楼主| 慢醇 发表于 2023-1-30 23:54 | 显示全部楼层
图3单片机
1211763d7e83db578a.png
1.3 WiFi模块ESP8266
WIFI通信模块作为STM32和云平台通信的中介,两端都通过WIFI模块进行数据交互,该模块选用ESP8266芯片,其特点就是如果断开连接,再次连接,模块会连接到最近一次连接过的热点。数据将通过ESP8266存放到云平台,并且stm32也能通过ESP8266获取云平台的数据。
 楼主| 慢醇 发表于 2023-1-30 23:59 | 显示全部楼层
ESP8266与STM32的接线图。WiFi模块ESP8266如图4所示。

图4WiFi模块ESP8266
5971663d7e9340c07e.png
1.4L298N电机驱动模块
电机驱动原理:小车左轮右轮上分别配有两个电机,左轮电机A与右轮电机B的正转、反转和停止决定着小车的运动模式。而STM32单片机4根电机控制信号线连接着L298N的IN1~IN4,另外两根PWM调速信号线连着ENA和ENB。输入信号线IN1和IN2控制电机A的运动,直流电机A接OUT1和OUT2。
 楼主| 慢醇 发表于 2023-1-31 00:08 | 显示全部楼层
同理,IN3和IN4合起来控制了电机B的运动,如图5电机模块L298N所示。
468363d7eb8c71f25.png

图5电机模块L298N
 楼主| 慢醇 发表于 2023-1-31 00:09 | 显示全部楼层
电机调速原理:L298N上还有ENA和ENB两个信号输入端口,这两个端口的作用是控制信号的使能,低电平有效。由于L298N有控制使能的信号线,则可以通过控制ENA和ENB的信号来进行PWM调速。原理是开关管在一个周期T的时间内导通的时间为t,那么电机两端的平均电压U=V*t()T=aV。其中,a=t/T(占空比),V是电源电压。电动机的转速与电动机两端的电压成正比,而电动机两端的电压与控制波形的占空比成正比,因此电动机的转动速度与PWM信号的占空比成比例,信号的占空比越大电动机转动得越快。在硬件电路上将STM32单片机的PA0~PA3端口分别接到L298N的IN1~IN4上,通过改变PA0~PA3口的高低电平控制小车的行驶方向,通过调节信号线ENA和ENB上的PWM占空比来控制小车运动速度。
 楼主| 慢醇 发表于 2023-1-31 00:10 | 显示全部楼层
1.5电源模块DC-DC
DC-DC是用开关电源的思想实现的。DC-DC有降压和升压两种,这里叫降压。DC-DC内部有振荡器和斩波器模块。输出端有电容器,对中间的脉冲波形进行微积分,输出5V的直流波形。该降压的过程相对于恒压模块,更大幅度地避免了降压模块上的电力消耗,内部振荡部通过控制其占空比,输出恒定。例如在本次设计中使用输入为12V电源的降压模块,其中输出11V引脚给电机供电,输出3.3V引脚给单片机供电。下面图6所示为电源模块DC-DC。
1018963d7ebfc8d468.png
图6电源模块DC-DC
 楼主| 慢醇 发表于 2023-1-31 00:12 | 显示全部楼层
模块的驱动(keil5写)
2.1小车制作部分
2.1.1主控制器
主控制器STM32F103C8T6以ARM32位CortexM3作为CPU,其开发环境为常用的ARM开发环境,本系统采用KeilμVision5作为开发工具。ARM公司在2015年发布的集成开发环境RealViewMDK集成了最新版本的KeilμVision5,其编译器、调试工具实现与ARM器件的最完美匹配,用C语言完成编程。
 楼主| 慢醇 发表于 2023-1-31 00:13 | 显示全部楼层
2.1.2控制小车的软件设计

电机的GPIO口的初始化,通过调用库函数RCC_APB2PeriphClockCmd[1],使能RCC_APB2Periph_GPIOA/RCC_APB2Periph_GPIOB端口时钟,而由于需要重映射,所以使能AFIO时钟RCC_APB2Periph_AFIO,之后通过复用推挽输出GPIO_Mode_Out_PP来将GPIO进行输出,输出到L298N的IN1~IN4,这里有两个电机,所以初始化了8个引脚,分别为PB引脚的GPIO_Pin_11,GPIO_Pin_12,和PA引脚的GPIO_Pin_3~GPIO_Pin_8。之后通过GPIO_SetBits拉高使能和GPIO_ResetBits拉低使能来控制电机的正反转动。
TIM3定时器的初始化,通过调用RCC_APB1PeriphClockCmd使能RCC_APB1Periph_TIM3,用于设定定时器3中断服务程序。
 楼主| 慢醇 发表于 2023-1-31 00:13 | 显示全部楼层
TIM1定时器的初始化,通过调用RCC_APB1PeriphClockCmd使能RCC_APB2Periph_TIM1,开启了TIM1的时钟之后,设置ARR和PSC两个寄存器的值来控制输出PWM的周期。之后输出比较通道1到4的初始化,之后,就可以控制TIM1的CH1到CH4输出PWM了,这里我使用的是PA6和PA7来进行左轮的使能,PB0和PB1进行右轮的使能。通过调整输出PWM波的频率还可以对轮子的转速进行调速。
关于小车的前进后退,可以这样实现:前进:左轮和右轮以同样的速率向前转动,小车向正前方运动;后退:左轮和右轮以同样的速率向后转动,小车向正后方运动。右转:当左轮速率大于右轮的速率时,小车右转;左转:当右轮速率大于左轮的速率时,小车左转。原地打转:当两轮速率相同,方向相反时,小车原地打转。转弯半径与差值的关系:小车转弯半径由左右电机数值差决定,差值越小,半径越大;差值越大,半径越小。以下图7为MCU控制小车的流程图。
 楼主| 慢醇 发表于 2023-1-31 00:15 | 显示全部楼层
 楼主| 慢醇 发表于 2023-1-31 00:15 | 显示全部楼层
代码1——onenet.c
代码如下(示例):
  1. //单片机头文件
  2. #include "stm32f10x.h"

  3. //网络设备
  4. #include "esp8266.h"

  5. //协议文件
  6. #include "onenet.h"
  7. #include "mqttkit.h"

  8. //硬件驱动
  9. #include "usart.h"

  10. //C库
  11. #include <string.h>
  12. #include <stdio.h>



  13. #define PROID                "111111"   //产品ID

  14. #define AUTH_INFO        "111111"                //鉴权信息       

  15. #define DEVID                "1111111"        //设备ID

  16. extern unsigned char esp8266_buf[128];

  17. //==========================================================
  18. //        函数名称:        OneNet_DevLink
  19. //
  20. //        函数功能:        与onenet创建连接
  21. //
  22. //        入口参数:        无
  23. //
  24. //        返回参数:        1-成功        0-失败
  25. //
  26. //        说明:                与onenet平台建立连接
  27. //==========================================================
  28. _Bool OneNet_DevLink(void)
  29. {
  30.        
  31.         MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};                                        //协议包

  32.         unsigned char *dataPtr;
  33.        
  34.         _Bool status = 1;
  35.        
  36.         printf("OneNet_DevLink\r\nPROID: %s,        AUIF: %s,        DEVID:%s\r\n", PROID, AUTH_INFO, DEVID);
  37.        
  38.         if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
  39.         {
  40.                 ESP8266_SendData(mqttPacket._data, mqttPacket._len);                        //上传平台
  41.                
  42.                 dataPtr = ESP8266_GetIPD(250);                                                                        //等待平台响应
  43.                 if(dataPtr != NULL)
  44.                 {
  45.                         if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
  46.                         {
  47.                                 switch(MQTT_UnPacketConnectAck(dataPtr))
  48.                                 {
  49.                                         case 0:printf("Tips:        连接成功\r\n");status = 0;break;
  50.                                         case 1:printf("WARN:        连接失败:协议错误\r\n");break;
  51.                                         case 2:printf("WARN:        连接失败:非法的clientid\r\n");break;
  52.                                         case 3:printf("WARN:        连接失败:服务器失败\r\n");break;
  53.                                         case 4:printf("WARN:        连接失败:用户名或密码错误\r\n");break;
  54.                                         case 5:printf("WARN:        连接失败:非法链接(比如token非法)\r\n");break;
  55.                                        
  56.                                         default:printf("ERR:        连接失败:未知错误\r\n");break;
  57.                                 }
  58.                         }
  59.                 }
  60.                
  61.                 MQTT_DeleteBuffer(&mqttPacket);                                                                //删包
  62.         }
  63.         else
  64.                 printf("WARN:        MQTT_PacketConnect Failed\r\n");
  65.        
  66.         return status;
  67.        
  68. }
  69. u8 velue0 ;
  70. u8 velue1 ;
  71. unsigned char OneNet_FillBuf(char *buf)
  72. {       
  73.        
  74.         char text[32];
  75.        
  76.         memset(text, 0, sizeof(text));
  77.        
  78.         //strcpy(buf, ",;");
  79.         strcpy(buf, ",;");
  80.                
  81.         memset(text, 0, sizeof(text));
  82.         sprintf(text, "temperature,%d;", velue0);
  83.         strcat(buf, text);
  84.        
  85.         memset(text, 0, sizeof(text));
  86.         sprintf(text, "humidity,%d;", velue1);
  87.         strcat(buf, text);
  88.         //printf("buf: %s \r\n", buf);
  89.         return strlen(buf);
  90. }

  91. //==========================================================
  92. //        函数名称:        OneNet_SendData
  93. //
  94. //        函数功能:        上传数据到平台
  95. //
  96. //        入口参数:        type:发送数据的格式
  97. //
  98. //        返回参数:        无
  99. //
  100. //        说明:               
  101. //==========================================================
  102. void OneNet_SendData(void)
  103. {
  104.        
  105.         MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};                        //协议包
  106.        
  107.         char buf[128];
  108.        
  109.         short body_len = 0, i = 0;
  110.        
  111. //        printf("Tips:        OneNet_SendData-MQTT\r\n");
  112.        
  113.         memset(buf, 0, sizeof(buf));
  114.        
  115.         body_len = OneNet_FillBuf(buf);        //获取当前需要发送的数据流的总长度
  116.        
  117.         if(body_len)
  118.         {
  119.                 if(MQTT_PacketSaveData(DEVID, body_len, NULL, 5, &mqttPacket) == 0)                                                        //封包
  120.                 {
  121.                         for(; i < body_len; i++)
  122.                                 mqttPacket._data[mqttPacket._len++] = buf[i];
  123.                        
  124.                         ESP8266_SendData(mqttPacket._data, mqttPacket._len);                                                                        //上传数据到平台
  125.                         //printf(" buf[%d]:%c \r\n",i, buf[i]);
  126.                         //printf("Send %d Bytes\r\n", mqttPacket._len);
  127.                        
  128.                         MQTT_DeleteBuffer(&mqttPacket);                                                                                                                        //删包
  129.                 }
  130.                 else
  131.                         printf("WARN:        EDP_NewBuffer Failed\r\n");
  132.         }
  133.        
  134. }

  135. //==========================================================
  136. //        函数名称:        OneNet_RevPro
  137. //
  138. //        函数功能:        平台返回数据检测
  139. //
  140. //        入口参数:        dataPtr:平台返回的数据
  141. //
  142. //        返回参数:        无
  143. //
  144. //        说明:               
  145. //==========================================================
  146. void OneNet_RevPro(unsigned char *cmd)
  147. {
  148.        
  149.         MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};                                                                //协议包
  150.        
  151.         char *req_payload = NULL;
  152.         char *cmdid_topic = NULL;
  153.        
  154.         unsigned short req_len = 0;
  155.        
  156.         unsigned char type = 0;
  157.        
  158.         short result = 0;

  159.         char *dataPtr = NULL;
  160.         char numBuf[10];
  161.         int num = 0;
  162.        
  163.         type = MQTT_UnPacketRecv(cmd);
  164.         switch(type)
  165.         {
  166.                 case MQTT_PKT_CMD:                                                                                                                        //命令下发
  167.                        
  168.                         result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len);        //解出topic和消息体
  169.                         if(result == 0)
  170.                         {
  171.                                 printf("cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);
  172.                                
  173.                                 if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0)        //命令回复组包
  174.                                 {
  175.                                         printf("Tips:        Send CmdResp\r\n");
  176.                                        
  177.                                         ESP8266_SendData(mqttPacket._data, mqttPacket._len);                        //回复命令
  178.                                         MQTT_DeleteBuffer(&mqttPacket);                                                                        //删包
  179.                                 }
  180.                         }
  181.                
  182.                 break;
  183.                        
  184.                 case MQTT_PKT_PUBACK:                                                //发送Publish消息,平台回复的Ack
  185.                
  186.                         if(MQTT_UnPacketPublishAck(cmd) == 0)
  187. //                                printf("Tips:        MQTT Publish Send OK\r\n");
  188.                        
  189.                 break;
  190.                
  191.                 default:
  192.                         result = -1;
  193.                 break;
  194.         }
  195.        
  196.         ESP8266_Clear();                                                                        //清空缓存
  197.        
  198. //        if(result == -1)
  199. //                return;
  200.        
  201.         dataPtr = strchr(req_payload, ':');                                        //搜索':'

  202.         if(dataPtr != NULL && result != -1)                                        //如果找到了
  203.         {
  204.                 dataPtr++;
  205.                
  206.                 while(*dataPtr >= '0' && *dataPtr <= '9')                //判断是否是下发的命令控制数据
  207.                 {
  208.                         numBuf[num++] = *dataPtr++;
  209.                 }
  210.                 numBuf[num] = 0;
  211.                
  212.                 num = atoi((const char *)numBuf);                                //转为数值形式
  213.                
  214.                 if(strstr((char *)req_payload, "onoff"))                //搜索"onoff"
  215.                 {
  216.                         printf("onoff = %d", num);
  217.                         velue0 = num;
  218.                 }
  219.                 else if(strstr((char *)req_payload, "switch"))                //搜索"switch"
  220.                 {
  221.                         printf("switch = %d", num);
  222.                         velue1 = num;
  223.                 }
  224.         }

  225.         if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
  226.         {
  227.                 MQTT_FreeBuffer(cmdid_topic);
  228.                 MQTT_FreeBuffer(req_payload);
  229.         }

  230. }
  231. //定义一个数据交换函数
  232. void Shujujiaohuan(unsigned char wendu,unsigned char shidu)
  233. {
  234.         velue0=wendu;
  235.         velue1=shidu;
  236. }

 楼主| 慢醇 发表于 2023-1-31 00:15 | 显示全部楼层
三、APP
2.1.3ONENet云平台软件设计

OneNET[3]是中国移动推出的物联网开放平台,该平台屏蔽了复杂的技术细节,提供多种协议类型,支持多种智能硬件的接入和大数据服务。用户按照OneNET云平台的规范接入平台,上传数据,实现数据传输与存储管理功能,同时平台还支持MQTT、EDP、HTTP等接入协议。用户在官网注册账号后即可进入云平台创建项目。数据上传完成后,用户可以在网页和手机APP端查看数据和对应的变化曲线,也可以下发控制指令,控制智能设备的运行。本系统采用OneNET云平台提供的网页应用控件、命令下发控件等实现了数据接收和远程指令下发等应用。通过网页端和手机APP端的同步应用,可快速方便地构建远程智能监控系统。系统运行时,智能小车在远程指令的控制下,实现前进、后退、转弯。
 楼主| 慢醇 发表于 2023-1-31 00:22 | 显示全部楼层
我们通过云平台产品的多协议接入中的MQTT协议创建一个产品,还需要创建一个设备,之后取出里面的产品ID,设备ID,还有鉴权信息,用于上传数据、查看数据、下发命令。
在keil5中通过RCC_APB2PeriphClockCmd库使能RCC_APB2Periph_GPIOB端口时钟,对ESP8266的使能引脚和复位引脚初始化,之后通过AT指令如:AT+CWMODE=1、AT+CWDHCP=1等等进行模式设置,连接手机热点,开启单连接,连接TCP服务器。
 楼主| 慢醇 发表于 2023-1-31 00:22 | 显示全部楼层
之后通过MQTT协议与云平台创建连接,如果8266成功连接到onenet云平台会打印Tips:连接成功的信息提示语,如果连接失败会打印出连接失败的原因。
连接成功后通过OneNet_SendData和OneNet_RevPro函数来进行发送和接收数据,在接收数据的方法中使用字符串匹配函数来进行在云端下发命令。
 楼主| 慢醇 发表于 2023-1-31 00:23 | 显示全部楼层
2.2APP制作部分

本设计釆用Android Studio作为系统开发软件。Android Studio提供了用于开发和调试的集成Android开发工具,功能比Eclipse更强大。虽然安卓手机或者平板具有很多种尺寸的屏幕与分辨率,但开发人员应用集成Android开发工具都能够比较轻松的调整每个分辨率设备上的应用程序.开发人员能够一边写和调试程序并能够看到这个程序在不同屏幕里的外观。以下图8 为Android Studio开发界面。
 楼主| 慢醇 发表于 2023-1-31 00:24 | 显示全部楼层
2.2.1视频监控
本次开发使用声网(Agora)[2]的视频通话和信令的SDK进行二次开发。集成了Agora视频SDK并调用API。
创建项目后我们将Agora视频SDK集成到项目中,添加mavenCentral依赖,之后添加网络和设备权限(INTERNET、CAMERA、RECORD_AUDIO、MODIFY_AUDIO_SETTINGS、ACCESS_WIFI_STATE、ACCESS_NETWORK_STATE、BLUETOOTH)
之后我们创建界面布局,在用户界面中,需要设置两个帧布局(FrameLayout),分别展示本地视图和远端视图。
处理Android系统逻辑,导入必要的Android类,如Manifest、FrameLayout等等。添加必要权限的授权逻辑,启动应用程序时,检查是否已在app中授予了实现视频通话所需的权限。如果未授权,使用内置的Android功能申请权限;如果已授权,则返回true。
实现视频通话逻辑,应用开启时,需要依次创建RtcEngine实例,开启视频模块,让本地用户加入频道,将本地视图与处于较低图层的帧布局(FrameLayout)绑定。当其他用户加入频道时,应用捕捉到远端用户加入频道的事件,并将远端视图与处于较高图层的帧布局(FrameLayout)绑定。首先导入必要的Agora类
 楼主| 慢醇 发表于 2023-1-31 00:30 | 显示全部楼层
(io.agora.rtc.RtcEngine;io.agora.rtc.video.VideoCanvas;io.agora.rtc.IRtcEngineEventHandler;)
创建变量用以创建并加入视频通话频道。如AppID、频道名称。通过IRtcEngineEventHandler函数监听频道内的远端用户,获取用户的uid信息。从onUserJoined回调获取uid后,调用setupRemoteVideo,设置远端视频视图。由于视频默认禁用,需要调用enableVideo开始视频流,调用CreateRendererView创建一个SurfaceView对象,并将其作为FrameLayout的子对象,将SurfaceView对象传入Agora,以渲染本地视频。
 楼主| 慢醇 发表于 2023-1-31 00:30 | 显示全部楼层
当远端用户加入频道时,通过setupRemoteVideo函数更新远端用户界面。以下图9为视频监控驱动流程图。
1280563d7f0ad9bd4c.png
 楼主| 慢醇 发表于 2023-1-31 00:31 | 显示全部楼层
图9视频监控驱动流程图
2.2.2APP控制端

Onenet云平台提供开放的API接口,用户可以通过HTTP/HTTPS调用,进行设备管理,数据查询,设备命令交互等操作,在API的基础上,根据自己的个性化需求搭建上层应用。
Android端控制小车,主要是给OneNet服务器发送一个post请求。通过调用平台API-http://api.heclouds.com/devices/868917927来下发命令,下发的格式为onoff:%d(自己设定),当命令下发成功,平台会作出回应。而单片机那边通过订阅的方式接收到APP下发的指令,之后则会执行相应的程序来驱动小车的前进后退调速等。
 楼主| 慢醇 发表于 2023-1-31 00:32 | 显示全部楼层
APP的按钮通过单击监听事件setOnTouchListener来完成,当按钮按下发送驱动小车命令,当按钮松开执行小车停止命令,按钮颜色的变换,通过调用setImageResource方法进行更换图片来完成。布局界面通过RelativeLayout布局嵌套LinearLayout布局来完成,保证按钮在不同的安卓设备端不会乱跑,也保证了美观。以下图10为OneNet数据收流程图。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

134

主题

1382

帖子

6

粉丝
快速回复 在线客服 返回列表 返回顶部