您现在的位置是:网站首页> PY&Rust
Python硬件相关开发
- PY&Rust
- 2023-11-26
- 487人已阅读
Python硬件相关开发
使用ffmpeg+opencv读取摄像头并推流到rtmp服务器
串口通讯
经常做嵌入式开发的兄弟们应该对串口、RS232、RS485比较熟悉,主要用来传感器数据采集和设备之间通信等,是一个引用很广泛的通信方式,window系统PC的USB口通过USB转RS232线连接上嵌入式设备的RS232接口。万能语言python也有对应的库pyserial可以支持串口通信。
1.什么是RS232
1.1RS232接口介绍
RS-232标准接口是常用的串行通信接口标准之一。而工业控制的RS-232口一般只使用RXD、TXD、GND三条线。在大部分的嵌入式设备都会使用RS232接口,可以通过接口实现对设备通信。
RS232接口任何一条信号线的电压均为负逻辑关系。即:逻辑“1”为-3—-15V;逻辑“0”:+3—+15V,噪声容限为2V。即要求接收器能识别高于+3V的信号作为逻辑“0”,低于-3V的信号作为逻辑“1”,TTL电平为5V为逻辑正,0为逻辑负。与TTL电平不兼容故需使用电平转换电路方能与TTL电路连接。但是RS232的传输距离有限,最大传输距离标准值为50英尺,实际上也只能用在15米左右。
(1)RS232没有时钟线,只有两根数据线,分别是rx和tx,这两根线都是1bit位宽的。其中rx是接受数据的线,tx是发送数据的线。通过串口线rx一位接收数据,从最低位到最高位依次接收,最后形成8bit数据。
(2)串口数据的发送与接收是基于帧结构,即一帧一帧的发送与接收数据。每一帧除了中间包含 8bit 有效数据外,每一帧的开头都必须有一个起始位,且固定为 0。在每一帧的结束时也必须有一个停止位,且固定为 1,即最基本的帧结构(不包括校验等)有 10bit。在不发送或者不接收数据的情况下,rx 和 tx 处于空闲状态,此时 rx 和 tx 线都保持 高电平,如果有数据帧传输时,首先会有一个起始位,然后是 8bit 的数据位,接着有 1bit 的停止位,然后 rx 和 tx 继续进入空闲状态,然后等待下一次的数据传输。如下图所示 为一个最基本的 RS232 帧结构。
1.2通信参数
(1)波特率
在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进 行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元 的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、9600、115200 等
(2)比特率
每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为 “每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 * 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为:9600Bps * 1bit= 9600bps。
1.3 RS232串口开发流程
开发前需要掌握RS232串口开发流程,在使用python的pyserial库时,我们可先用我们的串口调试工具确认串口指令功能是否正常,而后再进行下一步的调用实验,避免因串口功能异常,影响正常的代码编写,具体如下。
(1)接入RS232转USB串口线
在电脑的设备管理器里找到对应的USB端口号(若识别不到设备,请相应的驱动包是否正确安装)。
(2)打开串口调试助手工具(sscom、XCOM等)
选中对应的USB端口号,设置波特率、根据需要勾选hex发送、设置校验位,打开串口。
(3)发送指令,接受返回值
连接串口、设置串口通信的参数(波特率),校验方式(奇偶校验、CRC-16校验和LRC校验)。
2. pySerial
pySerial封装了串口通讯模块,可支持Linux、Windows、BSD(可能支持所有支持POSIX的操作系统),支持Jython(Java)和IconPython(.NET and Mono)。所有程序全由Python完成,除了标准库外不依赖其他包,安装pyserial库。
通信流程
(1)确定要使用的串口号。
(2)配置波特率、数据位、奇偶校验位、停止位、DTR/DSR、RTS/CTS 和 XON/XOFF。
(3)打开串口。
(4)收发数据。
(5)关闭串口。
首先要安装库
pip install pyserial
2.1 pyserial库常用函数介绍
serial = serial.Serial(‘COM1’, 115200) 打开COM1并设置波特率为115200,COM1只适用于Windows
serial = serial.Serial(‘/dev/ttyS0’, 115200) 打开/dev/ttyS0并设置波特率为115200, 只适用于Linux
print(serial .portstr) 能看到第一个串口的标识
serial .write(“hello”) 往串口里面写数据
serial .close() 关闭serial 表示的串口
serial .open() 打开串口
data = serial .read(num) 读num个字符
data = serial .readline() 读一行数据,以/n结束,要是没有/n就一直读,阻塞。
serial .baudrate = 9600 设置波特率
print(serial) 可查看当前串口的状态信息
serial .isOpen() 当前串口是否已经打开
serial.inWaiting() 判断当前接收的数据
serial.flushInput() 清除输入缓冲区数据
serial.flushOutput() 中止当前输出并清除输出缓冲区数据
2.2 python发送字符串指令
import time
import serial
#RS232指令,使用一个字典,把需要被操作的RS232命令封装到一起
RS232_Command = {
'command1_utf8': 'DCBA',
'command2_utf8': 'ABCD',
}
#把字符串类型转换为bytes数据流进行发送,RS232命令发送函数
def serial_sent_utf(command):
#从字典里获取对应的RS232命令
var = RS232_Command["%s" % command]
#encode()函数是编码,把字符串数据转换成bytes数据流
ser.write(var.encode())
data = ser.read(10)
# 获取指令的返回值,并且进行类型转换,转换为字符串后便可以进行字符串对比,因而便可以根据返回值进行判断是否执行特定功能
data = str(data, encoding="utf-8")
return data
if __name__ == '__main__':
#实现串口的连接
ser = serial.Serial('COM7', 115200, timeout=3)#连接com7端口
command1_utf8 = serial_sent_utf('command1_utf8')
command2_utf8 = serial_sent_utf('command2_utf8')
基本流程如下
(1)连接串口,设置串口通信的参数
使用 ser = serial.Serial(‘COM7’, 115200, timeout=3) 函数实现串口端口的连接,这里的COM7为PC连接嵌入式设备的USB端口号,115200为串口的波特率,timeout为连接超时。
(2)发送命令
serial_sent_utf(command),使用var = RS232_Command[“%s” % command]把需要发送的RS232命令赋值给 var,然后通过pyserial库里面的 ser.write(var.encode())函数发送到嵌入式设备,这里要注意 var.encode(),因为这里我们发送的RS232命令为字符串格式数据,但一般发送到嵌入式设备的数据是比特流格式,所以这边使用encode()函数进行编码,把发送的数据变成比特流发送。
(3)接收返回值
发送了RS232命令后,一般都会有返回值,通过pyserial库里面的data = ser.read()函数获取返回值,并把返回值赋值给data,使用read()相关函数时要注意避免出现一直读取返回值的情况。这里获取到的数据格式也是比特流数据,所以为了便于后续对返回值的判断等操作,这里加入了函数str(data, encoding=“utf-8”)对data的数值格式进行了转换,转换成utf-8的格式数据。
2.3python发送发送HEX指令
import time
import serial
#RS232指令,使用一个字典,把需要被操作的RS232命令封装到一起
RS232_Command = {
'command_hex1': '6B 30 31 73 50 32 30 31 0D',
'command_hex2': '6B 30 31 73 50 32 30 30 0D',
}
#把16进制的数据转换为bytes数据流进行发送,RS232命令发送函数
def serial_sent_hex(command):
#bytes.fromhex(),使用这个函数进行数据转换,可以把16进制的数值转换字节数据(即比特流,字符串与比特流间还可以用encode()和decode()进行编解码)
var = bytes.fromhex(RS232_Command["%s" % command])
ser.write(var)
data = ser.read(10)
# 获取指令的返回值,并且进行类型转换,转换为字符串后便可以进行字符串对比,因而便可以根据返回值进行判断是否执行特定功能
data = str(data, encoding="utf-8")
return data
if __name__ == '__main__':
#实现串口的连接
ser = serial.Serial('COM7', 115200, timeout=3)
command_hex1 = serial_sent_hex('command_hex1')
command_hex2 = serial_sent_hex('command_hex2')
不同地方就是其对发送的数据类型的不一样,发送HEX格式的命令,这里用了var = bytes.fromhex(RS232_Command[“%s” % command]),这里使用了bytes.fromhex()函数把hex的数据类型转换成比特流数据。
综上所述,在对串口库的使用过程中,要特别留意对发送数据的处理:要根据实际情况进行数据转换,把字符或HEX格式数据转换成bytes数据流发送。
2.4 ubunt 读取串口
(1)首先查看外接串口的端口号
(2)运行接收代码
'''
date:...
author:
function:
'''
import serial
data_ser = serial.Serial("/dev/ttyUSB0",9600,timeout = 5)
data_ser.flushInput()#清楚缓存
if __name__ == '__main__':
while True:
data_count = data_ser.inWaiting()#判断当前接收的数据
if data_count !=0 :#如果接收数据不为空,则读取数据
recv = data_ser.read(data_ser.in_waiting).decode("gbk")
print(recv)
Python GPIO操作
常用GPIO库
RPi.GPIO和gpiozero
pip install RPi.GPIO
pip install gpiozero
使用RPi.GPIO库控制GPIO
下面是一个使用RPi.GPIO库控制GPIO的示例代码:
import RPi.GPIO as GPIO
import time
# 设置GPIO模式为BCM模式
GPIO.setmode(GPIO.BCM)
# 设置GPIO引脚为输出模式
GPIO.setup(17, GPIO.OUT)
# 控制GPIO引脚输出高电平
GPIO.output(17, GPIO.HIGH)
# 延时1秒
time.sleep(1)
# 控制GPIO引脚输出低电平
GPIO.output(17, GPIO.LOW)
# 清理GPIO设置
GPIO.cleanup()
上述代码中,首先导入RPi.GPIO库和time库。然后,通过GPIO.setmode(GPIO.BCM)将GPIO模式设置为BCM模式,这是树莓派上常用的一种模式。接下来,通过GPIO.setup(17, GPIO.OUT)将GPIO引脚17设置为输出模式。然后,通过GPIO.output(17, GPIO.HIGH)将引脚17输出高电平,等待1秒后再输出低电平,并最后通过GPIO.cleanup()清理GPIO设置。
使用gpiozero库控制GPIO
下面是一个使用gpiozero库控制GPIO的示例代码:
from gpiozero import LED
from time import sleep
# 初始化LED对象
led = LED(17)
# 控制LED亮1秒,然后熄灭1秒,重复5次
for i in range(5):
led.on()
sleep(1)
led.off()
sleep(1)
上述代码中,首先导入LED类和sleep函数。然后,通过LED(17)创建一个LED对象,将GPIO引脚17与该对象绑定。接下来,通过led.on()和led.off()方法控制LED的亮和灭,通过sleep(1)方法实现1秒的延时。最后,通过循环重复亮和灭的过程5次。
使用ffmpeg+opencv读取摄像头并推流到rtmp服务器
1、环境
python3
OSX 12.5
vscode
2、安装ffmpeg
brew install ffmpeg
3、安装cv2
pip install opencv-python
# or
pip --default-timeout=100 install opencv-python -i https://pypi.douban.com/simple
4、脚本
import cv2
# subprocess 模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。
import subprocess
# 视频读取对象
cap = cv2.VideoCapture(0)
# 推流地址
rtmp = "rtmp://192.168.10.225:1935/stream/example"# 推流的服务器地址
# 设置推流的参数
command = ['ffmpeg',
'-y',
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24',
'-s', '1280*720', # 根据输入视频尺寸填写
'-r', '25',
'-i', '-',
'-c:v', 'h264',
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast',
'-f', 'flv',
rtmp]
# 创建、管理子进程
pipe = subprocess.Popen(command, stdin=subprocess.PIPE)
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
# 循环读取
while cap.isOpened():
# 读取一帧
ret, frame = cap.read()
if frame is None:
print('read frame err!')
continue
# 显示一帧
# fps = int(cap.get(cv2.CAP_PROP_FPS))
cv2.imshow("frame", frame)
# 按键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 读取尺寸、推流
img = cv2.resize(frame, size)
pipe.stdin.write(img.tobytes())
# 关闭窗口
cv2.destroyAllWindows()
# 停止读取
cap.release()
备注:分辨率要根据本机摄像头支持的分比率设置,不然可能显示不出图像
树莓派推流问题
最近在用ffmpeg+Python opencv 的方法实现采集摄像头实时视频并完成推流。实现思路是:cv2先获取摄像头视频数据,然后建立一个subprocess.popen管道去推流,然后将视频帧处理完后写入管道中完成图像处理并推流的工作。在windows下调试可以正常实现,代码如下(这个网上很多,我把我调试成功的代码也贴出来)。但是我想要在树莓派上实现,将该部分代码移植到树莓派上后就出现报错:pipe.stdin.write(img.tostring()) BrokenPipeError: [Errno 32] Broken pipe。现象是:代码刚跑起来,刚显示一两帧画面就中断了。
import cv2
import subprocess
# 视频读取对象
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
# 推流地址
rtsp = "rtsp://192.168.1.100:554/live/test1"# 推流的服务器地址
# 设置推流的参数
command = ['ffmpeg',
'-y',
'-f', 'rawvideo',
'-vcodec', 'rawvideo',
'-pix_fmt', 'bgr24',
'-s', '640*480', # 根据输入视频尺寸填写
'-r', '30',
'-i', '-',
'-c:v', 'h264',
'-pix_fmt', 'yuv420p',
'-preset', 'ultrafast',
'-f', 'rtsp',
rtsp]
# 创建、管理子进程
pipe = subprocess.Popen(command,shell=True, stdin=subprocess.PIPE)
size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
# 循环读取
num = 0
while cap.isOpened():
num = num + 1
print(num)
# 读取一帧
ret, frame = cap.read()
if frame is None:
print('read frame err!')
continue
# 显示一帧
fps = int(cap.get(cv2.CAP_PROP_FPS))
cv2.imshow("frame", frame)
print("fps:",fps)
# 按键退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 读取尺寸、推流
img = cv2.resize(frame, size)
pipe.stdin.write(img.tobytes())
# 关闭窗口
cv2.destroyAllWindows()
# 停止读取
cap.release()
问题解决
1.找到问题
从别人的帖子里发现可以用pipe.poll()来检查当前popen的工作状态,如果然后0,表示进程正常结束,返回1表示进程sleep挂起,返回2,表示子进程不存在,返回-15表示进程被kill,返回None,表示进程正在在运行。我print之后发现,返回了1,所以预估应该是,在树莓派上和windows上的popen创建方法有差别,在树莓派这里popen刚开始工作就被挂起了,终于找到解决办法。
2.解决办法
尝试了一下,将创建popen的shell = True 改为 shell = False就可以了。
将如下代码,
pipe = subprocess.Popen(command,shell=True, stdin=subprocess.PIPE)
改为
pipe = subprocess.Popen(command,shell=False, stdin=subprocess.PIPE)
即可
上一篇:Rust学习资料
下一篇:Python与Rust同行者