Easy Build:如何通过 Raspberry Pi 项目实现直线平台
投稿人:DigiKey 北美编辑
2016-09-20
像 Raspberry Pi 这样的单板计算机 (SBC) 只需数小时就能轻松构建复杂的机器,而不必使用一颗裸微控制器从头开始。 为了展示使用现代 SBC 有多简单,此“Easy Build”课程将引导您完成构建直线平台并进行编码所需的步骤。这里的直线平台是一个能够以直线方向执行来回运动的,由系统工程师或研究人员用来完成重复性任务的平台。
除了引导您完成这些步骤外,我们还将附上用于演示随附视频中直线平台的代码,以及帮助您启动的项目物料清单。
为什么选择直线平台?
我们启动此项目的初衷是,展示设定 Raspberry Pi 微型计算机来执行稍复杂的任务是如此容易。 尽管我们不会满足于几个闪光灯,但我们也不想尝试并计算轨道力学。 最终我们决定控制步进电机致动的直线平台。 毕竟,驱动步进电机正好介于闪烁的 LED 与计算三个天体的多体问题之间。
我们将项目分成三个部分:电子硬件、软件和机械构造。
电子硬件
图 1:显示从 Raspberry Pi Model B 到限位开关的所有硬件元素的完整系统布局(左上角)。 (使用 DigiKey Scheme-it 绘制的原理图)
目前为止,这些内容还非常简单、直接。 很显然,除了 Raspberry Pi 2 (Rpi) 外,我们还需要步进电机、步进电机驱动器以及上述两个器件的电源。 之后,我们确定了需要的输入。 最后,我们确定了以下项目:
- 左移按钮
- 右移按钮
- 高/低速开关
- 左限位开关
- 右限位开关
我们将电路布置到 B&K Precision GS-830 试验板上。 顾名思义,它拥有 830 个连接点以及一个总线条。
图 2:如图所示,滑动架挤靠在限位开关上,其中蓝色盒子就是限位开关,且在丝杠下方还有一个柱塞。 (来源:DigiKey)
输入从左,还是从右,完全随意。 我们只需对两个相反方向命名。 请注意,该限位开关使用上拉电阻器接线为“正常高”。 我们这样做是为了给这些输入提供一些抗噪能力。 Raspberry Pi 具有惊人的灵敏度,我们发现,紧凑型荧光灯等外部噪声源和继电器线圈等电感器有时可以在 Pi 上触发输入。
对于步进电机,我们选择了 NMB Technologies Corporation 的 23KM-K2(图 3)。 我们使用的步进驱动器是 Geckodrive 的 G210X。 Geckodrive 比市场上许多廉价的非品牌驱动器要昂贵一些,但它们可以提供流畅、无故障的步进控制。 同样,我们手头已经有了一个这样的步进驱动器。 大多数类似 G210x 这样的廉价步进电机驱动器是通过 DIRECTION 引脚和 STEP 引脚来控制的。 这很适合简单的项目:我们只需将这些引脚直接连接到 Rpi 上未使用的 GPIO 引脚即可。
图 3:对于步进电机,我们选择了 NMB Technologies Corp. 的 23KM-K2 标准混合电机。 (来源:NMB Technologies Corp.)
我们还将 Geckodrive 的 ENABLE 引脚接至 Rpi。 这样做使得驱动器仅在程序运行时才“开启”并向电机线圈供电。 否则,即使驱动器未收到执行任何操作的任何指令,驱动器仍会在我们向整个电路供电后立即将功率转储至电机。
将功率转储至电机线圈造成两个结果。 首先,它会促使电机产生制动效果。 电机会主动抵制任何旋转其轴的尝试。 这在某些情况下是希望达到的效果;比如当您将滑动架移到某个点并且希望将其锁定到位,使其不会被撞离位置。
第二个结果是我们希望避免的,即电机会在原地不断升温。 此电路在我们编辑和测试代码的过程中一直保持通电状态。 如果我们不停用驱动器,它会不断向我们的电机转储功率。 步进电机通常都会变热,但我们最好不要让其在等待指令的过程中自行加热。
图 4:36 VDC XP Power SHP350 系列 36 V 开关式电源用于替换 24 V 电源为系统供电,以增加电机转速。 (来源:XP Power)
最初,我们为电机连接 24 VDC 电源, 之后我们决定提高转速,于是找到了 36 VDC 电源,即 XP Power 的 SHP350 单输出、350 W AC-DC 电源。 这将在 24 V 电源的基础上显著提升电机转速。
Gecko 驱动器会限制通过电机线圈的最大电流。 您可以在端子 11 和 12 之间使用电阻器来设置电流限制,也可以使用驱动器上的 DIP 开关。 顺便提一下,同一组 DIP 开关还用于控制驱动器的微步进模式。 我们将在软件部分对此做进一步讨论。
软件
软件程序显示在本页面的底部。 此时我们决定选择使用两个程序:一个程序用于手动控制平台;另一个程序负责让直线平台滑动架在限位开关之间来回穿梭。 所有代码均使用 IDLE 3.2.1 以 Python 语言编写。 我们之所以使用 Python,是因为它是 Rpi 的默认语言。 IDLE 是随 Raspbian 一起提供的 Python 精简版系统开发环境。
为什么使用 Python? 不要让新语言吓住了您。 Python 与 C 非常相似,即使您不会编写 Python 代码,但只要熟悉 C,就几乎肯定能够阅读和理解程序内容。
我们的手动控制程序要执行的操作几乎一目了然,代码也未作任何说明。 具体而言,电机加速的问题。 步进电机必须逐渐提高转速。 我知道,许多读者会说,他们以前见过或控制过步进电机,电机转速完全取决于向其输入的脉冲频率。 也许是这样,但我敢打赌,这些情况下的电机负载肯定很小,甚至根本没有任何负载。
而我们的直线平台中的电机肯定带有负载。 承载的步进电机必须达到一定的转速,否则电机将会失速。 将脉冲输入到其驱动器时,步进电机只会在原地断续运转。 这也是我们在软件中置入加速程度的原因。 加速度需要足够快,使观察者不容易观察到从静止到最高转速的过程,但不能太快以至于失速。
加速程序一开始很慢地旋转电机(步进电机在低速下具有最高的扭矩),以向系统施加最大扭矩,从而克服惯性和“静摩擦力”。 之后,此程序会快速缩短步进脉冲之间的时间,直至达到最高转速。 扭矩会随着转速的提高而减小,但到此时,如果调节得当,系统中的机械阻力已被克服。
简单地介绍一下“静摩擦力”一词的含义。 此术语的真正解释远远超出了此项目分析的目的。 但我们可以采用类比的方法加深了解。 想象一下握住机器的旋钮或手轮,并尝试旋转。 通常,在旋转时必须克服一个初始阻力。 一旦克服这个阻力,旋转旋钮或手轮就会变得容易多了,现在需要的扭矩也要小得多。 在本文中,该初始阻力便是静摩擦力。 这种现象在利用滑台执行操作的机床(例如车床或铣床)中特别明显。
我们的确注意到,Python 也有一个显著的局限。 我们对为电机驱动器提供输出的步进引脚进行切换的速度存在限制。 到某一点后,我们在 5 µs 步进脉冲之间加入的延迟不论多小都无关紧要,因为 Python 代码中的命令执行之间存在固有的延迟。 我们没有完全弄清这一问题的缘由,也不知道如何解决此问题。 最好的办法是采取变通方案,这也是我们将电机驱动器设为 Full_Step 模式的原因。
如果使用任何类型的微步进,我们需要将最大脉冲率(取决于 Python 的具体情况)除以我们将驱动器切换到的微步数。
因此,对于半步进电机,需要将最高转速除以二。 使用 1/10 微步进时,我们需要将最高转速除以 10! 如果您追求更流畅、波动较小的动作,微步进方式是不错的选择,但对此项目而言则不必如此。
关于微步进的说明
我们在此项目中所用的双极步进电机类型几乎总是设计为每转 200 步。 也就是说,200 个“全”步将会旋转电机轴一周。 现在,比方说,我们将电机驱动器设置成 ¼ 微步进。 我们这样做会将电机的每个满步分成 4 个更小的步。 电机驱动器收到的每个 STEP 脉冲会将电机轴旋转 ¼ 满步。 这意味着,我们现在必须发送 800 个脉冲到驱动器才能将轴旋转一整转。
1/10 微步呢? 现在,您需要发送 2000 个脉冲才能完成一整转。 咋一看,这看起来不错。 这样做增加了电机的位置分辨率。 是的,但不完全如此:您可以采取多小的步幅存在物理限制。 而且,微步越多,您所获得的可用扭矩越小。
手动控制程序分解
首先,我们导入了时间和 GPIO 库。 然后,我们为每个变量设置引脚以及这些引脚的输入/输出。
方向按钮和速度开关设置为使用内部下拉电阻。 限位开关设置为使用内部上拉电阻。
转速和加速变量在开始时设置,并可全局更改,以调节运行程序中的电机转速和加速度。
程序仅运行一个“while”循环作为主循环,扫描“左”和“右”按钮。
按其中一个按钮后,rampUp() 子程序就会立即执行电机,沿所按按钮的方向加速至最高转速。
只要按下按钮,程序就会留在 rampUp() 程序中。 松开按钮后,rampUp() 程序将会跳入 rampDown() 程序,将电机减速至停止。 rampDown() 会持续减速,直至用完减速步数。 然后程序会返回到主循环,以检查方向按钮。 转速开关有两个设置,高和低。 此开关会将速度变量更改为对应的速度变量。
在按下左或右方向按钮以及发出步进脉冲时,步进程序还会检查确认是否已启用两个限位开关之一。 当移动平台触及限位开关时,电机就会停止,改变方向,并执行与原始行进方向相反的 50 步 (decelPulseCount) 移动。 这会将滑动架移至足够远离限位开关的位置,以免再次触及限位开关。
来回穿梭程序
从 Raspberry Pi 运行该程序后,直线平台会立即在一个方向上以固定的速度移动,直至触及其中一个限位开关。 平台随即改变方向,逐步提速,并继续在另一个方向上移动,直至其触及平台行程另一侧的限位开关,周而复始。 移动速度可通过手动控制程序中所用的相同开关,在预设的高速度或低速度之间切换。
机械构造
图 5:直线平台的最终构造使用现货零件组装而成,包括 1.5 英寸铝型材以及一根 8 螺纹/英寸的丝杠(8 头)。 (来源:DigiKey)
直线平台完全由现货零件制成:用于框架的 1.5 英寸铝型材、夹具、轴、电机底座,以及 0.5 英寸直径的 8 螺纹/英寸的丝杠(8 头)。 八头表示螺纹具有 8 条平行螺纹,类似于橙汁容器盖。 最终,丝杠每转动一整转,机械系统就会移动 1 英寸。 我们这样做是为了让直线平台操作视频更生动一些。 我们有 10 螺纹/英寸、单螺纹丝杠,但这会导致移动速度非常慢。 步进电机的局限在于其最大转速实际上很慢。
滑动架架在一对 20 mm 直线轴及配对的轴承上。 针对所用的 NEMA 23 双极步进电机,我们选择了一个通用的 NEMA 23 安装支架。 问问图片和视频您可以看到,我们使用齿轮和正时皮带将机械动力从电机传递到轴末端,以便在出现振动或机械静摩擦时能够调节齿轮比。 一些实验表明,电机和轴之间的比率为 1:1 效果很好。
对于位于丝杠从动端的轴台,我们必须做一些设计工作和加工(图 6)。 轴台是支撑轴的一个机械元件,在本例中仅传输旋转运动并阻止直线运动(至少在理论上如此)。 我们能否在没有轴台的情况下完成项目? 存在这种可能。 我们本可以将丝杠直接连接到步进电机的输出轴,但如果我们选择这种做法,我们的系统将很不稳定。 因此,我们在直线平台上设计并制造了一个直接连接到 20 mm 直线轴的轴台。
图 6:为延长设计寿命,使用轴台支撑轴并且仅传输旋转运动,同时阻止直线运动(至少在理论上如此)。 (来源:DigiKey)
步进电机内的轴承通常仅设置为处理侧向力,而非轴向力。 随着时间的推移,在滑动架来回穿梭的过程中,这些电机轴承将受到不应有的应力。 最终会损坏电机。 我们不清楚多久会发生这种情况。 但我们不希望电机在项目中途出现磨损,因此我们制造轴台时使用一些推力轴承、一个滚针轴承和一根 1/4 英寸的轴。 滚针轴承(红色)在轴台右侧中间位置固定 1/4 英寸轴,止推轴承(蓝色)负责处理从丝杠传输的任何轴向力。
最后,我们对限位开关进行定位,让滑动架在撞上其机械行程极限之前便能触发该开关。 此时,滑动架的撞击不会导致物理损毁,但肯定会开始磨损正时皮带和齿轮,因此我们会尽力避免这种情况。
以下就是运行中的完整项目:
结论
Raspberry Pi Model B 等 SBC 可以让工程师和研究人员更轻松地设计并实现可行且实用的系统。 此 Easy Build 分步指南通过一个实用的实例,详细指导读者完成直线平台的设计全过程,同时针对过程中的组件选择和设计决策,提供了深入浅出的分析。 除物料清单和相关代码外,您在实现下一个创意时也可借鉴该实例。
物料清单:
- Seeed Raspberry Pi 2 Model B
- XP Power:36 V 电源
- NMB Technologies Corporation:步进电机(零件即将缺货。)
- B&K Precision:试验板
- 项目电线
- Honeywell:限位开关
- Judco Manufacturing:表面贴装开关
- C&K Components:JS 系列滑动开关
其它零件:
Gecko 210X 步进电机控制器
½ 英寸 8 头丝杠
Bass 丝杠螺母
轴台轴承
联轴器
滑轮
电机驱动正时皮带
铝型材
直线轴承
精密连杆
两个定制底座、滑动架和轴台
代码:
代码、按钮控制
发送顺时针 (CW) 和逆时针 (CCW) 脉冲至步进驱动器;对限位开关做出反应;按初始行程方向相反的方向转动电机以“退避”限位开关。
"""
"""
导入模块和/或模块分段
"""
import RPi.GPIO as GPIO
import time
"""
设置 I/O 引脚掩码
"""
step = 18 # step signal to driver
directionPin = 23 # direction signal to driver
enable = 24 # enable signal to driver
button1 = 13 # direction pin
button2 = 5
output1 = 19
output2 = 12
output3 = 21
speedHiLo = 6
limitLeft = 12
limitRight = 16
"""
设置通用 IO
"""
GPIO.setmode(GPIO.BCM) #configure pin layout
GPIO.setwarnings(False)
GPIO.setup(step, GPIO.OUT)
GPIO.output(step, GPIO.LOW)
GPIO.setup(directionPin, GPIO.OUT)
GPIO.output(directionPin, GPIO.LOW)
GPIO.setup(enable, GPIO.OUT)
GPIO.output(enable, GPIO.HIGH)
GPIO.setup(button1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(button2, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(speedHiLo, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(limitLeft, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(limitRight, GPIO.IN, pull_up_down=GPIO.PUD_UP)
"""
一般配置及声明
"""
global lospeed, hispeed, startTime, endTime, limitFlag, timeVar, accelTime, decelTime,decelPulseCoun, flag1, highPulse, direction, limitPulseCount
flag1 = 0 # variable for loop control
timeVar = 0 # variable for loop control
direction = 0 # variable for storing direction pin value
endTime = 0 # variable for storing wait time between pulses whilst slowing to a stop
limitFlag = 0 # signals that limit has been reached and flow must return to main loop
highPulse = 0.0001 # time for pulses to go high to trigger driver
startTime = 0.001 # time between pulses at start of movement
hispeed = 0.0001 # time between pulses at full speed
lospeed = 0.001 # time between pulses
accelTime = 0.000003 # amount of time to decrement between acceleration pulses
decelTime = 0.00005 # amount of time to increment between deceleration pulses
decelPulseCount = 50 # number of pulses sent during deceleration, 1/4 rev for current setup
limitPulseCount = 200 # number of pulses sent to the driver when a limit is tripped, 1/2 rev for current setup
"""
功能定义
"""
def rampUp():
GPIO.output(enable, 1) # enable driver for movement
timeVar = startTime #initialize time variable with starting time between pulses
global flag1
global limitFlag
global direction
flag1 = 1 # set flag HI
if(GPIO.input(button1)):
direction = 1
GPIO.output(directionPin, direction)
# light an LED
if(GPIO.input(button2)):
direction = 0
GPIO.output(directionPin, direction)
# light an LED
while((GPIO.input(button1) or GPIO.input(button2) == 1)):
if(GPIO.input(limitLeft)== 0): # if a limit input goes LOW, call the limit function
limit()
if(GPIO.input(limitRight)== 0): #if a limit input goes LOW, call the limit function
limit()
if(limitFlag == 1):
limitFlag = 0
break
GPIO.output(step, 1)
time.sleep(highPulse)
GPIO.output(step, 0)
time.sleep(abs(timeVar))
if(timeVar > endTime):
timeVar = timeVar - accelTime #decrease time between pulses until they reach endTime
def rampDown():
global flag1
flag1 = 0
global timeVar
timeVar = endTime #initialize time variable with ending time bewteen pulses
x = decelPulseCount
while(x > 0):
x = x - 1
GPIO.output(step, 1)
time.sleep(highPulse)
GPIO.output(step, 0)
time.sleep(abs(timeVar))
if(timeVar < startTime):
timeVar = timeVar + decelTime
def limit(): # this routine is like the ramp down routine
print("Limit Hit")
time.sleep(0.015) #wait for a bit
if(GPIO.input(limitLeft) and GPIO.input(limitRight) == 1): # debounce
return
GPIO.output(enable, 1) # enable driver for movement
global direction
global timeVar
global limitFlag
global flag1
flag1 = 0 # disable the flag so u dont call rampDown upon exiting limit()
timeVar = endTime #initialize time variable with ending time bewteen pulses
direction = not direction
GPIO.output(directionPin, direction)
limitFlag = 1 # set this flag so that the rampUp routine 'breaks' and jumps back to Main()
x = limitPulseCount
while(x > 0):
x = x - 1
GPIO.output(step, 1)
time.sleep(highPulse)
GPIO.output(step, 0)
time.sleep(abs(timeVar))
if(timeVar < startTime):
timeVar = timeVar + decelTime
"""
主循环
"""
while(1): #loop forever, check for button presses, speed changes and limit trips
#disable driver to keep from overheatings
if(GPIO.input(button1) or GPIO.input(button2) == 1): # button pressed, call rampUp function
rampUp()
if(flag1 == 1):
rampDown()
# movement over, deactivate direction LEDs
if(GPIO.input(speedHiLo) == 0):
endTime = lospeed #if lo-speed selected, initialize endTIme with lo-speed wait time
else:
endTime = hispeed #if hi-speed selected, initialize endTIme with hi-speed wait time
# light up red LED to indicate hi-speed mode
if(GPIO.input(limitLeft)== 0): # if a limit input goes LOW, call the limit function
limit()
if(GPIO.input(limitRight)== 0): #if a limit input goes LOW, call the limit function
limit()
代码,来回穿梭
import RPi.GPIO as GPIO
import time
"""
设置 I/O 引脚掩码
"""
step = 18 # step signal to driver
directionPin = 23 # direction signal to driver
enable = 24 # enable signal to driver
button1 = 13 # direction pin
button2 = 5
output1 = 19
output2 = 12
output3 = 21
speedHiLo = 6
limitLeft = 12
limitRight = 16
"""
设置通用 IO
"""
GPIO.setmode(GPIO.BCM) #configure pin layout
GPIO.setwarnings(False)
GPIO.setup(step, GPIO.OUT)
GPIO.output(step, GPIO.LOW)
GPIO.setup(directionPin, GPIO.OUT)
GPIO.output(directionPin, GPIO.LOW)
GPIO.setup(enable, GPIO.OUT)
GPIO.output(enable, GPIO.HIGH)
GPIO.setup(button1, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(button2, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(speedHiLo, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(limitLeft, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(limitRight, GPIO.IN, pull_up_down=GPIO.PUD_UP)
"""
一般配置及声明
"""
global lospeed, hispeed, startTime, endTime, limitFlag, timeVar, accelTime, decelTime,decelPulseCoun, flag1, highPulse, direction, limitPulseCount
flag1 = 0 # variable for loop control
timeVar = 0 # variable for loop control
direction = 0 # variable for storing direction pin value
endTime = 0 # variable for storing wait time between pulses whilst slowing to a stop
limitFlag = 0 # signals that limit has been reached and flow must retuen to main loop
highPulse = 0.000005 # time for pulses to go high to trigger driver
startTime = 0.001 # time between pulses at start of movement
hispeed = 0.0005 # time bewtween pulses at full speed
lospeed = 0.0009 # time between pulses
accelTime = 0.000003 # amount of time to decrement between accleration pulses
decelTime = 0.00005 # amount of time to increment between deceleration pulses
decelPulseCount = 50 # number of pulses sent during deceleration, 1/4 rev for current setup
limitPulseCount = 5 # number of pulses sent to the driver when a limit is tripped, 1/2 rev for current setup
timeVar = startTime
def limit(): # this routine is like the ramp down routine
GPIO.output(enable, 0)
print("Limit Hit")
time.sleep(0.015) #wait for a bit
if(GPIO.input(limitLeft) and GPIO.input(limitRight) == 1): # debounce
return
GPIO.output(enable, 1) # enable driver for movement
global direction
global timeVar
global limitFlag
global flag1
flag1 = 0 # disable the flag so u dont call rampDown upon exiting limit()
timeVar = endTime #initialize time variable with ending time bewteen pulses
direction = not direction
GPIO.output(directionPin, direction)
limitFlag = 1 # set this flag so that the rampUp routine 'breaks' and jumps back to Main()
x = limitPulseCount
while(x > 0):
x = x - 1
GPIO.output(step, 1)
time.sleep(highPulse)
GPIO.output(step, 0)
time.sleep(abs(timeVar))
if(timeVar < startTime):
timeVar = timeVar + decelTime
"""
global direction
print("Limit Hit")
time.sleep(0.015)
if(GPIO.input(limitLeft) and GPIO.input(limitRight) == 1): # debounce
return
timeVar = startTime
flag1 = 0
direction = not direction
"""
"""
主循环
"""
while(1):
GPIO.output(enable, 1)
if(GPIO.input(limitLeft)== 0): # if a limit input goes LOW, call the limit function
GPIO.output(enable, 0)
limit()
if(GPIO.input(limitRight)== 0): #if a limit input goes LOW, call the limit function
GPIO.output(enable, 0)
limit()
if(limitFlag == 1):
limitFlag = 0
GPIO.output(directionPin, direction)
GPIO.output(step, 1)
time.sleep(highPulse)
GPIO.output(step, 0)
time.sleep(abs(timeVar))
if(timeVar > endTime):
timeVar = timeVar - accelTime
if(GPIO.input(speedHiLo) == 0):
endTime = lospeed #if lo-speed selected, initialize endTIme with lo-speed wait time
else:
endTime = hispeed
免责声明:各个作者和/或论坛参与者在本网站发表的观点、看法和意见不代表 DigiKey 的观点、看法和意见,也不代表 DigiKey 官方政策。