制作云台
驱动器制作
云台由两个无刷电机分别控制摄像头的 yaw,pitch。
无刷电机由SimpleFOC库驱动,由esp32作为控制器,drv8313为无刷电机驱动器最大输出电流2.5a,对于云台电机已经足够了。
下面是驱动板的原理图,可以驱动两个云台电机,板载了一个5v-5a线性稳压芯片 可以给外部设备供电
无刷电机需要位置反馈,这里使用了as5600磁编码器,下面是原理图
simpleFoc库的使用
simplefoc基于arduino环境,vscode安装platformlo插件,然后board选择 espressif esp32 dev module,framework选择 arduino就可以自动下载arduino环境了。然后在libraries中选择simplefoc就可以自动下载了。自带了各种实例由于使用了arduino框架,所以使用非常的简单
。官网地址https://docs.simplefoc.com/
下面代码可以控制一个电机且通过串口对电机进行控制
#include <SimpleFOC.h>
MagneticSensorI2C sensor = MagneticSensorI2C(AS5600_I2C);
TwoWire I2Cone = TwoWire(0);
BLDCMotor motor = BLDCMotor(7); //根据选用电机的极对数,修改此处BLDCMotor()的值
BLDCDriver3PWM driver = BLDCDriver3PWM(32, 33, 25, 22);//修改pwm接口
Commander commander = Commander(Serial);
void onMotion(char* cmd){ commander.motion(&motor,cmd); }
void onMotor(char* cmd){ commander.motor(&motor,cmd); }
void setup(){
Serial.begin(115200);
I2Cone.begin(19, 18, 400000);
sensor.init(&I2Cone);//修改i2c口
motor.linkSensor(&sensor);
driver.voltage_power_supply = 12;//电机供电电压这里是12v
driver.init();
motor.linkDriver(&driver);
motor.foc_modulation = FOCModulationType::SpaceVectorPWM;
motor.controller = MotionControlType::angle;//闭环角度控制
/控制参数///
motor.PID_velocity.P = 0.05;//每个电机的pid参数不同,需要自己修改
motor.PID_velocity.I = 1;
motor.P_angle.P = 10;
motor.voltage_limit = 1.5;
motor.LPF_velocity.Tf = 0.01;
motor.velocity_limit = 20;
motor.init();
motor.initFOC();
///打印参数//
motor.monitor_variables = _MON_TARGET | _MON_VEL | _MON_ANGLE;
motor.monitor_downsample = 1000;//1s打印一次可以修改单位ms
motor.useMonitoring(Serial);
commander.add('T',onMotion,"motion control");
commander.add('M',onMotor,"full motor config");
}
void loop(){
motor.loopFOC();
motor.monitor();
motor.move();
commander.run();
}
不同的电机pid参数不同,使用串口命令修改参数,下图是命令的格式
比较常用的一些命令
D - D current PID controller & LPF (see pid and lpf for commands)
V - Velocity PID controller & LPF (see pid and lpf for commands)
A - Angle PID controller & LPF- (see pid and lpf for commands)
S - Sensor offsets
M - sensor offset
E - sensor electrical zero
M - Monitoring control
D - downsample monitoring
C - clear monitor
S - set monitoring variables
G - get variable value
C - Motion control type config - see motion control
D - downsample motion loop
0 - torque
1 - velocity
2 - angle
3 - velocity_openloop
4 - angle_openloop
E - Motor status (enable/disable) - see motion control
0 - enable
1 - disable
MMG0 get variable - target
MMG6 get variable - angle
4G网络搭建
ipv6介绍
普通的图传在低空或者有遮挡的情况下性能会变得非常差,依靠4g网络就可以解决这个问题。
现在手机的4g5g网络都支持ipv6,开启热点后连接它的设备也都会分配ipv6地址,并且这个地址是公网可以访问的,这个地址不是固定的,重启网络就会变
当然也可以淘宝购买4g模块,就可以将设备联网了
使用ifconfig查看本机ipv6地址,ipv6地址是冒分十六进制表示法,格式如“ABCD:CCDD:…”一共128位
电信网络ipv6是240e开头的,移动是2409开头的,联通是2408开头的。将这个地址记录下来,可以使用ping命令看看网络是否连通。
数传的实现
这里用EC600S模块为数据的发送端和接收端
首先安装驱动
安装驱动
uname -r #查看内核版本安装相近的驱动
cd v5.6.11 #进入驱动目录
sudo make
sudo make install
报错了,需要安装头文件可以参考https://blog.csdn.net/m0_58944591/article/details/129836645
安装好linux头文件就可以顺利安装了,安装号后需要重启
此时在/dev中将会多ttyUSB0 ttyUSB1 ttyUSB2设备
使用minicom打开usb1
sudo apt-get install minicom
minicom -D /dev/ttyUSB1
为了使用方便先设置串口的设置
USB 端口绑定
将模块插到usb口查看USB端口信息,使用KERNELS作为标识
udevadm info --attribute-walk --name=/dev/ttyUSB1 |grep KERNELS
KERNELS=="ttyUSB1"
KERNELS=="2-1:1.3"
KERNELS=="2-1"
KERNELS=="usb2"
KERNELS=="fc880000.usb"
KERNELS=="platform"
创建映射文件
sudo vim /etc/udev/rules.d/×××.rules
在新建的*.rules文件中添加刚刚获取的设备id
KERNEL=="ttyUSB*", KERNELS=="2-1:1.3", MODE:="0777", SYMLINK+="my_network"
重启生效,可以查看创建成功了
ll /dev/ | grep ttyUS*
lrwxrwxrwx 1 root root 7 Sep 5 15:38 myNetwork -> ttyUSB1
sudo minicom -s
在Serial port setup设置 Serial Device 为/dev/my_network,然后Save setup as dfl 就可以了
按ctrl+a q就能退出minicom了
AT指令udp通信
使用at指令创建sock,依靠UDP传输数据
at指令可以参考官方的手册Quectel_EC200x&EG912Y&EC600S系列_TCP(IP)_应用指导_V1.2
下面的python代码可以把两块EC600S模块设置为udp的服务端与客户端,并互发消息
服务端
import serial
import time
import threading
class UDPOperate:
def __init__(self):
self.uartAT = serial.Serial(port='/dev/ttyUSB1', baudrate=115200,timeout=5)
self.initSock()
self.radioIp = None
self.radioPort = '1081'
self.readDate = ''
readThread = threading.Thread(target=self.read)
readThread.start()
while True:
self.write('hello')
time.sleep(2)
def read(self):
while True:
rec = self.uartAT.readline().decode().strip()
if rec:
if rec.startswith('+'): #获取从机ip格式+QIACT: 1,1,3,"10.122.217.145","240E:440:100C:9D5:177C:665F:1D69:E883"
temp = rec.split()
ip = temp[1].split(',')[3]
self.radioIp = ip.strip('"')
if not (rec.startswith('SEND') or rec.startswith('>') or rec.startswith('+')):#乎略
self.readDate = rec
print('read: ',self.readDate)
def write(self,data):
if not self.radioIp is None:
l = len(data)
cmd = 'AT+QISEND=0,' + str(l) + ',' + self.radioIp + ',' + self.radioPort + '\r\n'
self.uartAT.write(cmd.encode())
time.sleep(0.005)
self.uartAT.write(data.encode())
else:
print('now do not rec target ip ')
def initSock(self):
print('now init sock...')
time.sleep(1)
cmd0 = b'ATE1\r\n'#显示回显
cmd1 = b'AT+QICLOSE=0\r\n' #关闭存在的sock
cmd2 = b'AT+QIOPEN=1,0,"UDP SERVICE",":::1",0,1080,1\r\n'
cmd3 = b'AT+QIACT?\r\n'#获取ip
cmd4 = b'ATE0\r\n' #关闭回显
self.sendCmd(cmd0)
self.sendCmd(cmd1)
self.sendCmd(cmd2)
self.sendCmd(cmd3)
self.sendCmd(cmd4)
def sendCmd(self,cmd):
self.uartAT.write(cmd)
rec = self.uartAT.read_until(b'OK').decode().strip()
time.sleep(0.3)
print(rec)
udp = UDPOperate()
客户端
import serial
import time
import threading
# AT+CSQ
# 是查询信号的,如果注册网络成功,会有信号产生,最大 31,如果信号小于 10,说明当前网络信号不佳
# AT+CGATT?
# 是查询注册网络情况,如果注册成功,会返回 1,如果失败,返回 0
class UDPOperate:
def __init__(self):
self.readDate = ''
self.serialIP = '240E:440:100C:9D5:177C:665F:1D69:E883'
self.uartAT = serial.Serial(port='/dev/ttyUSB1', baudrate=115200,timeout=5)
self.initSock()
readThread = threading.Thread(target=self.read)
readThread.start()
cnt = 0
while True:
cnt = cnt+1
self.write('a' + str(cnt))
time.sleep(2)
def read(self):
while True:
rec = self.uartAT.readline().decode().strip()
if rec:
if not (rec.startswith('SEND') or rec.startswith('>')):
if not rec.startswith('+'):
self.readDate = rec
print('rec',self.readDate)
def write(self,data):
l = len(data)
cmd = 'AT+QISEND=0,' + str(l) + '\r\n'
self.uartAT.write(cmd.encode())
time.sleep(0.005)
self.uartAT.write(data.encode())
def initSock(self):
print('now init sock...')
time.sleep(1)
cmd0 = b'ATE1\r\n'#显示回显
cmd1 = b'AT+QICLOSE=0\r\n' #关闭存在的sock
cmd2 = 'AT+QIOPEN=1,0,"UDP",' + self.serialIP + ',1080,1081,1\r\n'
cmd3 = b'AT+QIACT?\r\n'#获取ip
cmd4 = b'ATE0\r\n' #关闭回显
self.sendCmd(cmd0)
self.sendCmd(cmd1)
self.sendCmd(cmd2.encode())
self.sendCmd(cmd3)
self.sendCmd(cmd4)
def sendCmd(self,cmd):
self.uartAT.write(cmd)
rec = self.uartAT.read_until(b'OK').decode().strip()
time.sleep(0.3)
print(rec)
udp = UDPOperate()
下面就可以依靠udp来做更加有意义的事情了
使用udp传输摄像头视频
gst-launch-1.0 v4l2src device=/dev/video10 ! video/x-raw,width=640,height=480,framerate=30/1 ! x264enc speed-preset=1 tune=zerolatency ! udpsink host=240e:440:1002:17db:39f2:c71f:6eb2:7f36 port=4951
如果有硬件编码器可以修改一下
gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,width=640,height=480,framerate=30/1 ! mpph264enc ! queue ! udpsink host=240e:440:1002:17db:39f2:c71f:6eb2:7f36 port=4951
接收端用mpv播放器
mpv -v --geometry=115:0 --no-border --profile=low-latency --untimed udp://[::1]:4951
可以显示实时画面,几乎看不到延迟。当然这个视网络情况有差异。
这样使用4g模块和simpleFOC库就搭建好了低成本的图数传,并且在4g网络覆盖的区域无限距离传输。