概述
如何发送指定数量的脉冲信号
文章目录
- 如何发送指定数量的脉冲信号
- 1 需求分析
- 2 救世主-pigpio
- 3 用pigpio发送指定数量的脉冲
- 3.1 wave_add_generic(pulses)函数介绍
- 3.2 wave_create()函数介绍
- 3.3 wave_chain()函数介绍
- 4 步进电机精确控制代码
1 需求分析
当我们在控制步进电机的时候,发送一定数量的脉冲信号是非常需要的,因为步进电机会根据收到的脉冲移动一定的角度。
图中所示的此类步进电机,往往需要一些驱动设备来控制,而使用这些驱动可以大大简化我们的控制。比如说,这些驱动可以设置分辨率,此处的分辨率指的是旋转360°所需要的脉冲波个数。例如分辨率为2000,那么意思就是说你给步进电机发送2000个脉冲信号它就可以旋转360°。很显然,分辨率越大,控制的精度就越高,因为你可以控制的最小移动角度就越小。但是,这同样是一把双刃剑,因为你需要产生更高频率的脉冲信号来驱动它,如果频率不够高的话,那么转速会很慢。除了分辨率之外,我们最需要的就是驱动提供了两个口,其中一个用于控制顺时针旋转,而另一个用于控制逆时针旋转,这样我们只需要给这两个口发送对应的脉冲信号,就可以达到控制方向的效果了。
那么根据原理,我们可以简单的写出下面代码。
from time import sleep
import RPi.GPIO as GPIO
CW = 26 # 控制顺时针旋转的GPIO口
CCW = 21 # 控制逆时针旋转的GPIO口
RESOLUTION = 2000 # 设置的分辨率,2000个脉冲信号旋转360°
GPIO.setmode(GPIO.BCM)
GPIO.setup(CW, GPIO.OUT)
GPIO.setup(CCW, GPIO.OUT)
delay = 0.005
step_count = 2000
print('开始顺时针旋转一圈')
for x in range(step_count):
GPIO.output(CW, GPIO.HIGH)
sleep(delay)
GPIO.output(CW, GPIO.LOW)
sleep(delay)
sleep(0.5)
print('开始逆时针旋转一圈')
for x in range(step_count):
GPIO.output(CCW, GPIO.HIGH)
sleep(delay)
GPIO.output(CCW, GPIO.LOW)
sleep(delay)
GPIO.cleanup()
当然了,我们还可以封装成函数,比如函数提供旋转的方向和角度这两个参数,以下提供一个简单的示例。
def rotate(direction, angle):
'''
direction: 旋转的方向,可以是CW或者CCW
angle: 旋转的角度,范围是0-任意
'''
if angle < 0:
print('无效的旋转角度')
return False
num_of_pulse_per_cycle = 2000 / 360 # 以2000为例,计算出旋转1°所需的脉冲个数
num_of_pulse_need = angle * num_of_pulse_per_cycle # 计算出所需的脉冲个数
delay = 0.005
def send_pulse(port, num_of_pulse): # 发送指定个数的脉冲
for x in range(step_count):
GPIO.output(port, GPIO.HIGH)
sleep(delay)
GPIO.output(port, GPIO.LOW)
sleep(delay)
port = CW if direction == 'CW' else CCW
send_pulse(num_of_pulse_need)
return True
至此,看起来生活很美好,是吗?我们已经可以控制步进电机了,并且能让他旋转任意角度。那这样有没有问题呢?显然是有的。
缺陷-转速过慢
可以看到,上面的实现代码中,使用的是软件方法来产生脉冲波形,这很显然会被中断,多线程等问题影响,如果暂且不谈论这个的影响,那么还有一个问题就是所产生的脉冲波形频率太低了,就像我们开头所述的那样,这样就会转速过慢,无法发挥步进电机全部的转速。上面的实现代码中,一个周期是
0.005
∗
2
=
0.01
0.005*2=0.01
0.005∗2=0.01秒,那么频率就是
1
0.01
=
100
H
z
frac{1}{0.01}=100Hz
0.011=100Hz,如果按照我们设定的参数,旋转一圈需要发送2000个脉冲信号,那也就需要
2000
÷
100
=
20
2000div100=20
2000÷100=20秒,这样的速度显然是我们所不能接受的。
缩短延迟时间,可以吗?NO
有同学可能会直接减少delay的时间,以此来提高频率,可以说这也是我们的第一直觉,然而生活不会这么美好,当你不断减少delay的数值时,你会发现当你减到很小的时候,它不工作了,这是因为软件产生脉冲波形的频率是有上限的,而且这个上限很小,不能满足我们的需要,有兴趣的朋友可以试试最快能到多少。
2 救世主-pigpio
上面一顿分析,我们现在的目标是要更高频率的波形,而这可以由pigpio来帮助你,我在第一篇文章中也提到过,这个库可以产生0-20kHz的波形无压力,甚至最高能到达1MHz,只需调用pi.hardware_PWM(GPIO_port, frequency, duty_cycle)
,当然这对于GPIO口也有一些要求,这个不在这里详述。
然而这样就可以了吗?没有
尽管我们现在的确可以产生更高频率的波形了,但是我们却不能控制其数量,这样的话我们很难达到精准的控制。有的同学表示,我们可以产生高频率的PWM波形,然后通过计时的方式来达到发送一定数量的脉冲信号。想法是非常美好的,但是现实很残酷,也许你只控制步进电机,什么也不干的时候,可以达到,当你写了一个稍微有点规模的程序,它同时还在做其他工作(测量,记录等),你会发现产生的PWM信号频率和你设定的有一定误差,比如设定产生20kHz的波形,实际可能是18.6kHz。在这种情况下,你很难估计发送的时间,也就很难达到精准控制。
3 用pigpio发送指定数量的脉冲
大家无需感到沮丧,既然有这篇文章,那肯定有解决的办法!先简单介绍以下pigpio库当中的几个函数,它可以帮助我们实现目标哦。
3.1 wave_add_generic(pulses)函数介绍
根据官方描述,这个函数可以帮助我们制造我们想要的波形,注意其所需的参数为pulses
,那么我们如何给这个函数传递参数呢?以我们的目标为例,假设我们想制造一个脉冲信号,那也就是输出高电平,过一会之后输出低电平,那我们就可以做如下的操作。
import pigpio
pi = pigpio.pi()
GPIO = 24
pi.set_mode(GPIO, pigpio.OUTPUT);
delay_time = 100 # 延迟100us
my_pulse = []
my_pulse.append(pigpio.pulse(1<<GPIO, 0, delay_time))
my_pulse.append(pigpio.pulse(0, 1<<GPIO, delay_time))
pi.wave_add_generic(my_pulse) # 添加波形
wid = pi.wave_create() # 获得波形的id用于后面调用
可以看到,我们延迟的时间是100微秒,那么频率也就是5000Hz,还是不错的,相较于软件产生的波形,提高了50倍。但是,延时也不是可以无限小的,由于pigpio默认的扫描频率是5微秒,所以我建议delay的时间要大于等于5微秒,那这样我们最多能够产生100kHz的波形,已经相当不错了!
3.2 wave_create()函数介绍
由上面的例程,我们可以在wave_add_generic
后面紧跟wave_create
函数来获得我们创建波形的id
,这将用于后面我们运行时找到我们对应的波形。当我们创建多个波形的时候,这将会非常有用。
3.3 wave_chain()函数介绍
这个函数可以发送一系列的波形,当然也可以只发一个波形啦。但值得注意的是,运行此函数之后,任何的硬件PWM都会停止运行。这个函数可以接受一系列的指令和数据,由以下表格来表示。
名称 | 命令和数据 | 作用 |
---|---|---|
Loop Start | 255 0 | 开始一个波形 |
Loop Repeat | 255 1 x y | 循环x+y*256次数 |
Loop Delay | 255 2 x y | 延迟 x + y*256 微秒 |
Loop Forever | 255 3 | 永久循环 |
聪明的同学可能已经发现了,我们所需要的不正是第二行吗,没错,利用该命令我们就可以循环我们想要的次数了,这样不就可以发送指定数量的脉冲了吗!
4 步进电机精确控制代码
以下代码是我写的步进电机类,可以精确控制旋转的角度和方向,有需要的朋友可以根据自己手头电机的参数进行配置和使用。值得注意的是,由于我使用的系统中涉及到了齿轮,因此还设置了gear_ratio
参数,使用时需要注意。
'''
Driver for the motor (stepper motor).
@author: Jingkai Zhang
@version: 1.2
version 1.1: add stepper motor (sofware PWM)
version 1.2: add stepper motor (hardware PWM)
@since: 2022
@update: 2022-4-6
edit in 2022-3-29: Add new stepper motor driver, based on pigpio, which is faster
edit in 2022-4-3: Add more information if initialization is failed
edit in 2022-4-6: fix some bugs
'''
import pigpio
import time, sys
class stepper_motor(object):
'''
Test script for step motor HW23-601 (https://www.mouser.com/c/?q=HW23-601)
with driver STR2 (https://www.applied-motion.com/products/stepper-drives/str2)
'''
num_of_steps_for_360 = 2000 # this could be changed, please see the totorial of STR2/4
resolution = num_of_steps_for_360 / 360 # resolution, how many steps would trigger 1 degree rotation
CW_control_port = 21 # GPIO 21 for clockwise (physically connected to dir+ port in the driver)
CCW_control_port = 18 # GPIO 18 for counter clockwise (physically connected to step+ port in the driver)
gear_ratio = 4 # radius of the outer gear is 4 times than the radius of the inner gear
cur_position = 0
pwm_frequency = 6250 # PWM frequency, the larger the faster rotation
'''
TODO: decide how much frequency is suitable
Here are some frequency that I recommend:
5000 Hz, 6250 Hz, 10000 Hz, 12500Hz, 15625 Hz, 20000 Hz
Better choose something that can be divided by 500000.
'''
inner_speed = 60 * pwm_frequency / num_of_steps_for_360
outer_speed = inner_speed / gear_ratio
delay_micro = int(500000 / pwm_frequency)
CCW_wave = [pigpio.pulse(1 << CCW_control_port, 0, delay_micro), pigpio.pulse(0, 1 << CCW_control_port, delay_micro)]
CW_wave = [pigpio.pulse(1 << CW_control_port, 0, delay_micro), pigpio.pulse(0, 1 << CW_control_port, delay_micro)]
waveform_id = {}
name = 'stepper_motor'
def __init__(self, log=None, ip=None):
self.log = log
if ip is not None:
self.pi = pigpio.pi(ip)
if not self.pi.connected:
self.log.critical("cannot connect to tower pi's gpio, please check the ethernet connection or pigpio daemon!")
return False # motor initialization failed
else:
self.pi = pigpio.pi('localhost')
if not self.pi.connected:
self.log.critical("cannot connect to localhost, check if pigpio daemon is running")
return False
self.pi.wave_clear()
self.pi.set_mode(self.CW_control_port, pigpio.OUTPUT)
self.pi.set_mode(self.CCW_control_port, pigpio.OUTPUT)
self.pi.wave_add_generic(self.CW_wave)
self.waveform_id[self.CW_control_port] = self.pi.wave_create()
self.pi.wave_add_generic(self.CCW_wave)
self.waveform_id[self.CCW_control_port] = self.pi.wave_create()
self.log.info('waveform created %s' % self.waveform_id)
time.sleep(0.5)
self.log.info("stepper motor initialization accomplished, outer speed: %d rpm, resolution: %d steps/degree" % (self.outer_speed, self.resolution))
def __del__(self):
self.pi = pigpio.pi()
self.pi.wave_tx_stop() # stop waveform
self.pi.wave_clear()
self.pi.stop()
def send_pulse(self, num_of_pulse, control_port):
'''
If you want to know why, please visit the following link:
https://abyz.me.uk/rpi/pigpio/python.html#wave_chain
'''
x = int(num_of_pulse) & 255
y = int(num_of_pulse) >> 8
chain = [255, 0, self.waveform_id[control_port], 255, 1, x, y] # send x + 256 * y times
# chain = [255, 0, self.waveform_id[control_port], 255, 3] # infinity loop, just for test
self.pi.wave_chain(chain)
while self.pi.wave_tx_busy():
time.sleep(0.1)
self.pi.wave_tx_stop()
def move(self, angle, direction):
num_of_steps = self.resolution * angle * self.gear_ratio # gear ratio is 4, so the outer speed is 1/4 of inner gear, need more pulse
angle = -angle if direction == 'CCW' else angle # decrease the angle if direction is CCW
self.cur_position += angle
if self.cur_position > 360 or self.cur_position < -360:
self.log.error("rotation angle will exceeded 360 degree, wire might get twisted, current movement cancelled")
self.log.info("movement: <moved %d degree, direction:%s> has been cancelled" % (angle, direction))
self.cur_position -= angle
return False
else:
if direction == 'CW':
self.send_pulse(num_of_steps, self.CW_control_port)
else:
self.send_pulse(num_of_steps, self.CCW_control_port)
self.log.info("moved %d degree, direction:%s, current position: %d" % (abs(angle), direction, self.cur_position))
return True
def get_motor_type(self):
return self.name
由于运行时需要传入log
,大家在使用的时候可以删除这部分的内容,以防止报错。
最后
以上就是纯真纸鹤为你收集整理的[树莓派] 使用pigpio库(3) 如何发送指定数量的脉冲信号如何发送指定数量的脉冲信号的全部内容,希望文章能够帮你解决[树莓派] 使用pigpio库(3) 如何发送指定数量的脉冲信号如何发送指定数量的脉冲信号所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复