跟我一起做电脑控制云台视频摄像头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

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue

推荐文章

热门文章

相关标签