跟我一起做电脑控制云台视频摄像头V1_摄像头云台控制采用哪种数据包-程序员宅基地

技术标签: C51 AT89S52  云台控制舵机  51单片机编程  嵌入式单片机  

一、概要说明:

使用PC做上位机,控制两路舵机实现能够上下、左右运动的云台,并安装USB摄像头采集实时视频传到PC监控端;
上位机采用Python 3.7开发,终端控制采用了AT89S52单片机;中间通过RS-232串行通信;

效果演示: 在这里插入图片描述
版本时间:2019-8-16,当前实验为首个版本,目前还存在偶尔抖动现象,后续会继续优化。另外,会在此基础上继续升级,欢迎拍砖

二、上位机程序:

# coding=utf-8
# Python3.7
# Class app 电脑控制的云台摄像头程序入口
# Author:BO
# Date:2019.06.17
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from MainWindow import *
from Controller import *
camera = 2  # 摄像头编号2
com_no = "COM8"  # 绑定串口号
if __name__ == "__main__":
    print("==>系统启动开始...")
    app = QtWidgets.QApplication(sys.argv)
    controller = Controller(camera, com_no)
    gui = MainWindow()
    gui.set_controller(controller)
    gui.show()
    sys.exit(app.exec_())
# coding=utf-8
# Python3.7
# Class Controller 控制器,主要呈现逻辑实现
# Author:BO
# Date:2019.06.19
import cv2
import serial
import utils.Calculator
from time import strftime
# 控制器程序
class Controller(object):
    ## 控制器初始化方法,默认摄像头0,默认绑定串口号COM1 ##
    def __init__(self, camera_no=0, com_no='COM1'):
        print('=>控制器初始化...')
        self.camera = cv2.VideoCapture(camera_no)
        if (self.camera.isOpened()):  # 判断视频是否打开
            print('=>摄像头已开启.')
        else:
            print('=>摄像头未打开!')
            self.camera.release()
        self.serial = serial.Serial(com_no, 9600, timeout=0.5)  # /dev/ttyUSB0
        if self.serial.isOpen():
            print("=>串口已打开.")
        else:
            print("=>串口开启失败!")
        print('=>控制器初始化完成.')
    ## 判断控制器是否可用, 目前仅判断视频头是否打开 ##
    def is_available(self):
        return self.camera.isOpened() and self.serial.isOpen()
    ## 关闭控制器,释放视频头 串口 ##
    def close(self):
        self.camera.release
        self.serial.close()
        cv2.destroyAllWindows()
    ## 读摄像头,使用了cv2 ##
    def handle_frame(self):
        ret, frame = self.camera.read()
        # 查看视频size
        # size = (int(self.camera.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))
        # print('==>size:' + repr(size))  # repr()返回一个对象的 string 格式
        cv2.putText(frame, strftime("%Y-%m-%d %H:%M:%S"), (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        return frame, gray
    ## 构造串口命令,用于控制舵机 ##
    def build_command(self, dimension, delta):
        if dimension == 'x':
            delta = 180 - delta
            n = utils.Calculator.angle_pwm(delta)
            n = round(n)
            str_delta = '%s' % (n)
            str_delta = str_delta.zfill(4)
            command = 'A1%s#' % (str_delta)
            print('=> x=%s .%s' % (delta, command))
        else:
            n = utils.Calculator.angle_pwm(delta)
            n = round(n)
            str_delta = '%s' % (n)
            str_delta = str_delta.zfill(4)
            command = 'A2%s#' % (str_delta)
            print("=> y=%s .%s" % (delta, command))
        return command
    ## 向串口发送指定指令 ##
    def send_command(self, command):
        try:  # 如果输入不是十六进制数据--
            n = self.serial.write(bytes.fromhex(command))
        except:  # --则将其作为字符串输出
            n = self.serial.write(bytes(command, encoding='utf-8'))
        return n
# coding=utf-8
# Python3.7
# Class MainWindow 程序主窗口类
# Author:BO
# Date:2019.06.19
from PyQt5.uic import loadUi
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
# 主窗口类
class MainWindow(QMainWindow):
    # 主窗口初始化
    def __init__(self, parent=None):
        print('=>窗口初始化...')
        super(MainWindow, self).__init__(parent)
        loadUi("sources/control.ui", self)
        self.timer_camera = QTimer()  # 定义定时器
        self.pushButton_start.clicked.connect(self.start)  # 按钮关联槽函数
        self.pushButton_stop.clicked.connect(self.stop)
        # 初始视频串口动画
        self.movie = QMovie("sources/cover.gif")
        self.label_frame.setMovie(self.movie)
        self.label_frame.setScaledContents(True)
        self.movie.start()
        # 手动条控制条
        self.horizontalSlider.setRange(0,180)
        self.horizontalSlider.setSingleStep(1)
        # self.horizontalSlider.setTickInterval(1000000)
        self.horizontalSlider.setValue(90)
        self.horizontalSlider.setTickPosition(QSlider.TicksBelow)
        self.horizontalSlider.valueChanged.connect(self.horizontal_change)
        # self.verticalSlider.setMinimum(420)
        # self.verticalSlider.setMaximum(2100)
        self.verticalSlider.setRange(0,180)
        self.verticalSlider.setSingleStep(1)
        self.verticalSlider.setValue(90)
        self.verticalSlider.setTickPosition(QSlider.TicksBelow)
        self.verticalSlider.valueChanged.connect(self.vertical_change)
        #创建多行文本框
        self.textEdit_cmd.setPlainText('#')
        print(self.textEdit_cmd.toPlainText())
        self.horizontalSlider.setEnabled(False)
        self.verticalSlider.setEnabled(False)
        print('=>窗口初始化完成')
    def append(self,text):
        self.textEdit_cmd.setPlainText(self.textEdit_cmd.toPlainText() + '\n' + text)
    def set_controller(self, controller):
        self.controller = controller
    ## 水平方向滑动条改变事件 ##
    def horizontal_change(self):
        self.controller.send_command(self.controller.build_command('x', self.horizontalSlider.value()))
    ## 垂直方向滑动条改变事件 ##
    def vertical_change(self):
        self.controller.send_command(self.controller.build_command('y', self.verticalSlider.value()))
    ## 启动 ##
    def start(self):
        self.timer_camera.start(100)
        self.timer_camera.timeout.connect(self.open_frame)
        self.horizontalSlider.setEnabled(True)
        self.verticalSlider.setEnabled(True)
    ## 停止 ##
    def stop(self):
        self.controller.close()
        self.timer_camera.stop()  # 停止计时器
        sys.exit(0)
    ## 用于捕获帧,在主窗口显示画面 ##
    def open_frame(self):
        if self.controller.is_available():
            frame, gray = self.controller.handle_frame()
            height, width, bytesPerComponent = frame.shape
            bytesPerLine = bytesPerComponent * width
            q_image = QImage(frame.data, width, height, bytesPerLine, QImage.Format_RGB888).scaled(self.label_frame.width(), self.label_frame.height())
            self.label_frame.setPixmap(QPixmap.fromImage(q_image))
        else:
            self.controller.close()
            self.timer_camera.stop()  # 停止计时器

三、下位机程序:

/*********************************************************************************************
 * 串口舵机控制程序
 * 单片机: AT89S52
 * 功能  : 接收上位机串口指令,控制两路舵机运动
 * http://www.dispace.net    
/*********************************************************************************************/
//------------------串口通信协议定义-----------------//
/*
定义长度为7的格式化数据包,如:
A1_180#
  A:数据包的开始标记
  1:动作舵机
  2304:转动角度参数
  #:数据包的结束标记
*/
#include <reg52.h>
#define MAIN_Fosc11059200UL
// 自定义类型名  [float:单精度浮点数(32位长度);double:双精度浮点数(64位长度)]
typedef unsigned char  uchar;       // 无符号8位整型变量
typedef unsigned int   uint;       // 无符号16位整型变量
typedef unsigned long  ulong;      // 无符号32位整型变量 
sbit servo0=P0^0; // 水平舵机信号端口
sbit servo1=P0^7; // 垂直舵机信号端口
uint pwm[] = {
    1382,1382}; // 初始90度,中值
uchar pwm_flag = 0;
uint code ms0_5Con=18432; // 20ms中断  0.02s*921600
uchar buf_string[7]; // 定义串口数据包长度为7个字符
// 函数声明
bit ReceiveString();
bit Deal_UART_RecData();
void PutString(unsigned char *TXStr);
/*********************************************************************************
** 功能 : 定时器零(Timer0)中断初始化  舵机PWM
** 使用晶振频率为11.0592M,则每秒可产生机器周期为11.0592/12=0.9216M的机器周期,也就是921600个机器周期。
** 50ms等于0.05秒,所以需要921600*0.05=46080个机器周期;
** 定时器在方式1工作,为16位,最大值为65536,所以需设初值为65536-46080=19456;转为16进制为(4c00),所以高位TH0=0x4c; TL0=0x00;
** 
** 10ms等于0.01秒,921600*0.01=9216 --> 65536-9216=56320 -> dc00
** 
** 计算:2.5ms初始值 F700, (12n/11059200=2.5/1000, n=2304, X=65536-2304=63232 > F700)
** 舵机使用的是MG996R
** 0度=0.5ms, 45度=1ms, 90度=1.5ms, 135度=2ms, 180度=2.5ms
*********************************************************************************/
void Timer0_Init()
{
      
// TMOD |= 0x01;等价于TMOD = TMOD | 0x01; 将TMOD的最低位置1,也即表示将定时/计数器的其工作方式调整为方式1(16位定时器/计数器)
    TMOD |= 0x01;
    TH0=-ms0_5Con>>8; // 给定初值,20ms中断
    TL0=-ms0_5Con;
    EA=1; // 总中断打开
    ET0=1; // 定时器0中断打开
    TR0=1; // 定时器0开关打开
}
/*********************************************************************************
** 功能 : Timer0中断处理函数,舵机控制函数  
** 为两路舵机中的每路产生20ms的脉冲,其中高电平0.5-2.5ms。  
** 2*10ms=20ms
*********************************************************************************/
void Timer0() interrupt 1
{
    
	switch(pwm_flag)
	{
    
		case 1:
			servo0 = 1;
			TH0=-pwm[0]>>8;
			TL0=-pwm[0];
			break;
		case 2:
			servo0 = 0;
			TH0=-(ms0_5Con-pwm[0])>>8;
			TL0=-(ms0_5Con-pwm[0]);
			break;
		case 3:
			servo1 = 1;
			TH0=-pwm[1]>>8;
			TL0=-pwm[1];
			break;
		case 4:
			servo1 = 0;
			TH0=-(ms0_5Con-pwm[1])>>8;
			TL0=-(ms0_5Con-pwm[1]);
		default:
			TH0=-ms0_5Con>>8;
			TL0=-ms0_5Con;
			pwm_flag=0;
	}
	pwm_flag++;
}

/*********************************************************************************
** 功能 : 串口初始化,晶振11.0592,波特率9600,使用了串口中断
*********************************************************************************/
void Com_Init()
{
    
    TMOD |= 0x20; //用定时器设置串口波特率
    TH1=0xFD; //256-11059200/(32*12*9600)=253 (FD)
    TL1=0xFD; //同上
    TR1=1; //定时器1开关打开
    REN=1; //开启允许串行接收位
    SM0=0; //串口方式,8位数据
    SM1=1; //同上
    EA=1; //开启总中断
    ES=1; //串行口中断允许位
}
//功能: 串口发送字符串
void SendString(unsigned char *TXStr)  
{
                    
    ES=0;     
     while(*TXStr!=0) 
    {
                          
        SBUF=*TXStr;
        while(TI==0);
        TI=0;    
        TXStr++;
    }
    ES=1; 
}
/*********************************************************************************
** 功能 : 串口中断接收数据
*********************************************************************************/
void Interrupt_Uart() interrupt 4	  // 标志位TI和RI需要手动复位,TI和RI置位共用一个中断入口
{
    
	if(!ReceiveString()) 
    {
    
        //数据包长度错误则执行以下代码
        P1=0x00;                
    }
    RI=0;  // 接收并处理一次数据后把接收中断标志清除一下,拒绝响应在中断接收忙的时候发来的请求
}
/*********************************************************************************
** 功能 : 接收数据
*********************************************************************************/
bit ReceiveString()    
{
    
    char *RecStr = buf_string;
    char num=0;
    unsigned char count=0;
    loop:    
    *RecStr = SBUF;
    count=0;
    RI=0;    
    if(num<7)  //数据包长度为7个字符,尝试连续接收7个
    {
    
        num++;
        RecStr++;    
        while(!RI)
        {
    
            count++;
            if(count>130) return 0;
        }
        goto loop;
    }
    return 1;
}
// 延时函数,调试用的
void DELAY_MS(uint ms)
{
    
 uint i;
    do{
    
    i = MAIN_Fosc / 96000;
        while(--i);   //96T per loop
    }while(--ms);
}
/*********************************************************************************
** 函数功能 : 主函数
** TODO接口:根据串口数据动作
**
** >>调试: 
** P1=0x00;              //灯亮
** DELAY_MS(500);
** P1=0xff;              //灯灭
*********************************************************************************/
void main()
{
    
	Timer0_Init();
	Com_Init(); 
	
	while(1)
	{
    
	
		if(buf_string[0]=='A'&&buf_string[6]=='#')  // 进行数据包头尾标记验证
    	{
    
			uchar control = buf_string[1]; // 舵机控制

			uchar th = buf_string[2]-'0'; //thousand
			uchar hu = buf_string[3]-'0';//hundred
			uchar ten = buf_string[4]-'0';// ten
			uchar an = buf_string[5]-'0'; // an

			int angle = th*1000+hu*100+ten*10+an;
			if(control=='1')
			{
    
				pwm[0]= angle;
			}
			else if(control=='2')
			{
    
				pwm[1]= angle;	
			}
		} 
	}
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/softwbc/article/details/99660559

智能推荐

深度学习04—反向传播算法(用于参数更新、troch实现)_反向传播实例及代码-程序员宅基地

文章浏览阅读4.6k次,点赞11次,收藏57次。(1)反向传播:该方法主要是应用链式法则的方法,求loss关于w和b的导数;(2)关于如下步骤中,l.backward()会将前向的各部梯度存入,而红色的两个代码会形成“计算图”;(3)其中:w是Tensor(张量类型),Tensor中包含data和grad,data和grad也是Tensor。grad初始为None,调用l.backward()方法后w.grad为Tensor,故更新w.data时需使用w.grad.data。_反向传播实例及代码

MongoDB复杂分组聚合查询_mongodb分组查询-程序员宅基地

文章浏览阅读3.7k次,点赞4次,收藏16次。目录1 聚合查询1.1 MongoDB的聚合查询2 聚合管道方法2.1 聚合流程2.1.1 详细流程2.2 聚合语法2.2.1 参数说明2.2.2 注意事项2.3 常用聚合管道2.3.1 与mysql聚合类比3 使用示例3.1 统计所有数据3.2 对所有城市人数求合3.3 对城市缩写相同的城市人数求合3.4 state重复的城市个数3.5 state重复个数大于100的城市4 MapReduce4.1 什么是MapReduce4.1.1 执行阶段4.1.2 语法_mongodb分组查询

云服务器可以改系统吗,云服务器可以改系统吗?-程序员宅基地

文章浏览阅读788次。云服务器可以改系统吗?腾佑小编给出的答案是:可以的,云服务器可以改系统,不过更换系统是一个高风险操作。在更换系统盘之前,务必认真阅读服务商给出的注意事项。云服务器改系统的风险有:1.原系统盘的会被释放,建议提前创建快照备份数据。2.更换系统盘需要停止实例,因此会中断网站的业务。3.更换完成后,需要在新的系统盘中重新部署业务运行环境,有可能会对业务造成长时间的中断。更换系统盘相当于重新为云服务器实例...

Xilinx zynqmp USB开发-程序员宅基地

文章浏览阅读6.1k次,点赞4次,收藏35次。参考Linux USB DWC3 Host/Peripheral DriverZynq Ultrascale MPSOC Linux USB device driverU-Boot USB DriverZynq UltraScale+ MPSoC USB 3.0 Mass Storage Device Class DesignZynq UltraScale+ MPSoC USB 3.0...

离散余弦变换(DCT)的来龙去脉_dcd余弦变化-程序员宅基地

文章浏览阅读5.8w次,点赞40次,收藏238次。本文主要介绍离散余弦变换(DCT),从图像的二维离散变换入手,引出变换核的可分性与对称性导出DCT,并给出实现代码。_dcd余弦变化

使用python的requests 发送multipart/form-data 请求-程序员宅基地

文章浏览阅读3.9w次,点赞5次,收藏23次。发送post请求 1 r = requests.post("http://pythontab.com/postTest", data = {"key":"value"}) 以上得知,post请求参数是以data关键字参数来传递的。现在的data参数传递的是字典,我们也可以传递一个json格式的数据,如下: 1 2 3 ...

随便推点

支付宝对账单下载Java正式商户调用-程序员宅基地

文章浏览阅读204次。package code;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.MalformedURLExcep..._java商家 alipay 账单拉取

学java对笔记本配置有何要求_java程序员应该配置什么样的笔记本-程序员宅基地

文章浏览阅读2.6k次。入门JAVA程序员需要配置什么样的笔记本?一般java程序员使用的笔记本配置有多高,怎么样才能让java程序员安心的工作!要求:1.可以运行MyEclipse,Tomcat,MySQL,oracle,flex等java web编程的软件。可能还会涉及到安卓开发。2.偶尔看看电影什么的。3.内存最好大一点,不然运行代码怕占用内存太高。5.屏幕最好能稍微大一点,看电影,敲代码会更舒服一点。6.预算在5..._java程序员笔记本配置

Python模块:基本概念、2种导入方法(import与from...import)和使用-程序员宅基地

文章浏览阅读1w次,点赞37次,收藏221次。模块是python程序架构的一个核心概念。每一个以扩展名py结尾的python源代码文件都是一个模块,模块名同样也是一个标识符,需要符合标识符的命名规则,在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具,模块就好比工具包,要想使用这个工具包中的工具,就需要先的导入这个模。

100天精通Python(数据分析篇)——第70天:Pandas常用排序、排名方法(sort_index、sort_values、rank)_pandas rank sort_value-程序员宅基地

文章浏览阅读1.3w次,点赞69次,收藏69次。一、按索引排序:sort_index() 1. Series类型排序 1)升序 2)降序 2. DataFrame类型排序 1)按行索引排序 2)按列索引排序二、按值排序:sort_values() 1. Series类型排序 1)升序 2)降序 2. DataFrame类型排序 1)单列排序 2)多列排序 3)排序算法 _pandas rank sort_value

Java基础面试题(2022最新版汇总)_白大锅,java基础面试题(2022最新版汇总)-程序员宅基地

文章浏览阅读10w+次,点赞597次,收藏3.5k次。史上最全、最新、最详细的Java基础面试题汇总_白大锅,java基础面试题(2022最新版汇总)

OPC UA Java 开发笔记(三):open62541建立基础服务器_java open62541-程序员宅基地

文章浏览阅读3.9k次,点赞2次,收藏9次。前两天一直在搞open62541,因为milo库的server sdk没有办法根据xml生成结点文件,于是准备用open62541来搭建服务器,milo结合Spring Boot来构件客户端。网络上大多是建立一个open62541.h的头文件,还有就是open62541.c的执行文件,但是新版好像并不是这样的。然后很多教程都是讲的linux,但是初学者用linux的还是比较少,所我写了这一篇帮..._java open62541

推荐文章

热门文章

相关标签