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);
}

    由于我们的上下位机通信方式,通信方式是显而易见的,而交流规则是朦胧的。我时常在想,如果我只需要说几个字,别人就能懂我的核心意思,那效率会有多高?好在现在可以短暂实现了。我们需要发号施令的内容包含且限于:

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  标准库