最终效果:
gif
摘要说明:
使用PC做上位机,控制两路舵机实现能够上下、左右运动的云台,并安装USB摄像头采集实时视频传到PC监控端;
上位机采用Python 3.7开发,终端控制采用了AT89S52单片机;中间通过RS-232串行通信;
一、上位机程序:
# 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:数据包的开始标记(可以为A到Z,意味着数据包可以有26种) 1:动作舵机 _180:转动参数 #:数据包的结束标记 左:A12100# 右:A10420# 中:A11200# */ #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) ** ** 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; } uchar ff,temp = 0; /********************************************************************************* ** 功能 : 串口中断接收数据 *********************************************************************************/ void Interrupt_Uart() interrupt 4 //标志位TI和RI需要手动复位,TI和RI置位共用一个中断入口 { if(ReceiveString()) { Deal_UART_RecData(); //数据包长度正确则执行以下代码 } else { //数据包长度错误则执行以下代码 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; //接收数据等待延迟,等待时间太久会导致CPU运算闲置, // 太短会出现数据包被分割,默认count=130 } goto loop; } return 1; } /********************************************************************************* ** 功能 : 处理串口接收数据包函数(成功处理数据包则返回1,否则返回0) *********************************************************************************/ bit Deal_UART_RecData() { SendString(buf_string); return 0; } 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; } } } }
===========
Copyright © 2015 - 2016 DISPACE.NET | 使用帮助 | 关于我们 | 投诉建议