技术标签: 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;
}
}
}
}
文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。
文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退
文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet
文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程
文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据
文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端
文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库
文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密
文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error
文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库
文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame
文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量