0 写在前面
找到适应自己的工作节奏以及合作伙伴将是高效完成项目的前提。
by the way 本文不具体展示机械结构设计部分,原因是小鱼不具有该部分的版权 : (
1 功能描述
通过一键启动的(没错是鼠标,不过添加一个按键事件也是一件不难的事)自动起重机器人,对于一定范围内的重物(本文仅针对单钩砝码,是的,秤砣)进行自动识别并通过抓手进行抓取,抓取后需要将该重物放置到固定点位,至此为完成了对该重物的作业;下文将从外向里依次解决自动化的实现问题。
1.1 机架的水平移动
机架的核心抓取部分(即抓手),并不是每时每刻都处于每一次作业的理想位置的,所以要完成作业固然少不了:跑过来,跑过去。
1.2 抓手起降
在作业中难免发生目标位置的高度不一致的问题,针对本问题即为抓取单钩砝码的高度与放置高度并不一致,且需要一定程度的避免碰撞和拖地等问题,上下移动将是完成任务的又一重点。
1.3 抓手的抓取作业
不把东西拿起来,靠意念是不能作业的 :)
1.4 计算机视觉
摸黑在房间走路也难免踢到床头,不具备视觉的起重机将更加抓瞎(固化方案将消耗更多能源与时间),视觉将是灵魂一举。
1.5 把他们串起来
我猜你也不希望车子爪子各玩儿各的吧。
2 功能实现
好的我们已经知道了需要哪些部分才能赋我们的起重机以赛博生命,坐上小鱼的老爷车,一起来看看吧!
2.1 机架的水平移动
首先让我们感谢 AccelStepper 标准库
水平移动有两种主流方案,一是小车型(大多采用麦克纳姆轮小车,相信你知道这是什么),另一是龙门架结构,我们针对龙门式展开;相信你现在已经有了自己理想的移动方案,或是主动轮转动,或是齿轮履带拉动,这些都好,只不过需要注意的是:我们的轮子或齿轮需要像汽车的主动轮同样是左右对称的,这将拯救松散的车架和瘪瘪的钱包。
因为考虑到转速、扭矩,最重要的还有经费问题,我们最终采用了两块57步进电机和一块42步进电机,驱动板型号为DM542(经费充足的条件下可以选择扭力更大,转速更加优秀的直流电机)。在这里统一说明,该项目的上位机平台为一块树莓派4B,下位机均为arduino_uno。在这里将三块电机与驱动板正确连接后,将驱动板与arduino连接。硬件解决了,是时候开始软件了:
如果我们希望当输入步数时,电机输出步数而不是方向,那么对于引脚的定义和正确连接就至关重要,又或许你更希望先写代码再连线,这些都没关系,只要选对正确的引脚;由于我们要给步进电机两个信息,即脉冲信息和方向信息,所以除了电源和共地之外,就要选取两个arduino上的数字引脚连接电机,如下的 dirPin 即为方向引脚, stepPin 即为脉冲引脚,相邻的两个引脚定义一个电机:
#define dirPin1 4
#define stepPin1 3
#define dirPin2 6
#define stepPin2 5
#define dirPin3 8
#define stepPin3 7
我们给每个引脚起好了名字,终于可以使唤他们做点什么了,那就先考虑考虑如何跑起来吧。由于考虑到电机启动时的同步轮(或轮子)打滑问题,在不断地尝试下,确定了分别对应于不同型号的步进电机的加速度;至于速度上限,可以根据实际运行情况进行尝试,这里因为缓启动解决了打滑问题,而我们希望尽可能快速地完成任务,所以对于速度上限的设定远超于实际最大速度。
void setup() {
Serial.begin(115200);
stepper1.setMaxSpeed(1000000);
stepper1.setAcceleration(10000);
stepper2.setMaxSpeed(1000000);
stepper2.setAcceleration(10000);
stepper3.setMaxSpeed(5000);
stepper3.setAcceleration(800);
}
由于我们的上下位机通信方式,通信方式是显而易见的,而交流规则是朦胧的。我时常在想,如果我只需要说几个字,别人就能懂我的核心意思,那效率会有多高?好在现在可以短暂实现了。我们需要发号施令的内容包含且限于:
- 哪位电机?
- 哪个方向?
- 走多少步?
 ;
我们先在这里用1代表控制步进电机1和2,3控制步进电机03;接着的00和01则代表相反的方向;为了实现自由控制,这里的输入并不是一定的,如果你希望修改,可以根据需求且符合规则地修改成任何你希望的数字。一头雾水吗,看一眼代码就全明白了:
if (motorCode == 1) { // 控制步进电机1和2
if (direction == 0) {
stepper1.move(steps);
stepper2.move(-steps);
} else {
stepper1.move(-steps);
stepper2.move(steps);
}
} else if (motorCode == 3) { // 控制步进电机3
if (direction == 0) {
stepper3.move(steps);
} else {
stepper3.move(-steps);
}
}
1,2位 | 3,4位 | 其余五位 |
---|---|---|
01 or 03 | 00 or 01 | 04750 |
01代表步进电机1和2,03代表步进电机3 | 00和01代表不同方向 | 运行04750步 |
Fine?现在我们的下位机准备好了,准备好接收来自串口的一些神秘数字,是吗?No!在它张开嘴巴之前,并不算完全准备好;现在让我们撬开它的嘴巴:
String inputString = Serial.readStringUntil('\n'); // 读取输入字符串
if (inputString.length() == 9) { // 确保输入字符串长度为9
char motorCode = inputString.substring(0, 2).toInt();
char direction = inputString.substring(2, 4).toInt();
int steps = inputString.substring(4).toInt();
Fine. 这下真的可以发号施令了。
很显然,我们对发号施令这件事还是没有做好准备的,为了满足高效率,就需要写出一个上位机程序用来管理这个arduino设备了:
import serial
import time
class StepperMotorControl:
def __init__(self, port='COM7', baudrate=115200): # 这里的port一定要记得修改成正确的接口序号
self.ser = serial.Serial(port, baudrate, timeout=1)
time.sleep(2) # 等待串口稳定
def send_command(self, command):
"""向Arduino发送9个字符长度的命令"""
if len(command) == 9:
self.ser.write(command.encode())
self.ser.write(b'\n') # 发送换行符来结束命令
print(f"Sent command: {command}")
else:
raise ValueError("Command must be 9 characters long.")
def close(self):
if self.ser.is_open:
self.ser.close()
if __name__ == "__main__":
stepper = StepperMotorControl()
stepper.send_command("010002000")
stepper.close()
这段python代码很简单,只实现一个功能:把一个来自于用户输入的9位数字发送给下位机。
if name == "main":
stepper = StepperMotorControl()
stepper.send_command("010002000")
stepper.close()
该特性用于单独运行该代码。因为我们很容易注意到,代码中并不包含main()函数,所以将基于该命令进行单独测试代码的可行性。
2.2 抓手起降
在调查过程中发现,同类型需求的作品中,绝大多数作品采用带编码器的直流电机控制起降,而我们在这里选用扭矩为35kg*cm的串行总线数字舵机,配合一个尺寸适当的齿轮。数字舵机的绝对优势在于其精准可控,仅仅通过几个数字,就可以达到精准起降的效果;当然了,贵的东西唯一的缺点就是贵,明白吧。
如上所说,想要控制好起降装置,我们先感谢 SoftwareSerial 标准库,