人工智障入门-没有灵魂的功夫茶

人工智障入门

2019-06-12 沈喆

 

 

沈喆

网易游戏资深开发工程师,主要工作方向为网易游戏 CDN 自动化平台的设计和开发,脑洞比较奇特,喜欢在各种非主流的领域研究制作各种不走寻常路的东西。

你一定没见过的工夫茶机器人

这是广东省汕头市澄海区的三名四年级的澄海小学生在老师赵世僖指导下制作的「工夫茶机器人」,并于 2018 年 7 月代表广东省参加在贵州举行的第十八届中国青少年机器人大赛。

「请泡茶。」在语音指示下,由几个机械臂组成的「工夫茶机器人」回应了一句「好的」,便开始启动冲茶程序——烫杯、润茶、冲泡、敬茶,就连「关公巡城」「韩信点兵」这两个潮汕地区流行的冲茶技艺也没有遗漏。在顺畅完成整个冲茶的「一条龙服务」后,「工夫茶机器人」很有礼貌地发出邀请:「请喝茶。」

挖藕~这样的机器人实在太好玩了,简直胖胖哒!!!小学生都能做出来,我们这样的资深 SRE 怎么能不会呢,赶快动手做一个!下面先介绍一些嵌入式和硬件编程的基础知识,再将工夫茶机器人的制作过程展示给大家。如果只对制作过程感兴趣,那就请直接跳到 没有灵魂的工夫茶

1. 嵌入式开发简介

大部分程序员写的代码都是运行在各种架构的机器上,比如服务器或个人 PC,或者手机上运行的 iOS 或 Android 程序。它们的共同点是完全使用了软件编程的形式,你有没有想到自制硬件并对其进行编程呢?学会制作硬件,也就能从虚拟世界触及到现实空间,离「人工智障」也就不远了,而达到“人工智能”其实路途还很远。

硬件开发一般是指电子产品硬件开发,比如我们所说的手机、鼠标、键盘、音响都是硬件,包括从头做起或者在现有产品上进行二次开发。

正式地来说,硬件开发一般分为:原理图设计、电路图设计、PCB 板设计、测试板生产、功能性测试、稳定性测试、单片机设计、小批量生产、正式投放市场或正式使用等步骤。这个步骤几乎不是个人能够完成的,所以实际上我们所谓的硬件开发更多的是指嵌入式开发。

相信大家多多少少都听说过「嵌入式与 Linux」这样的书或课程,也可以看出嵌入式和 Linux 的密切关系。嵌入式系统上和 x86 机器上的系统没有根本的差别,都是完整的计算机体系的实现,最重要的不同也就是 CPU 指令集了,像 x86 属于 CISC 指令集,ARM 属于 RISC 指令集。万能的 Linux 几乎可以支持所有的架构,所以只要你会 Linux,也就成功了一半。

2. 硬件模块化

可能你见过很多电子制作的教程,比如用几个三极管几个 LED 几个电容就可以制作出一个心形跑马灯去送给妹纸,这种分立元件对于嵌入式开发来说没有作用,如今的硬件多数采用模块化的方式来包装。比如你可以拆出 PC 机中的 CPU、内存、硬盘等,它们都属于模块化的硬件。模块化的好处就是,如果你发现它坏了,直接把整个模块换掉就可以了,所有的代码逻辑和接口参数都无须改变。

比较常见的模块化硬件有以下这些,如红外传感器、超声测距模块、蓝牙模块、WIFI 模块、电压转换模块等。简单的模块如红外传感器和超声测距模块输入输出方式比较简单,只需要两三条线使用指定方式读写即可,可能不同厂家使用的针脚排列会不同。而复杂的模块比如蓝牙模块和 WIFI 模块,则有一个可以称为行业规范的技术标准,甚至个人无法独立写出读写程序,需要使用封装好的库,并且需要操作系统给予底层支持。

超声测距模块

WIFI 模块

3. 控制器介绍

控制器是一个硬件的核心,它承担着和实际物品进行通讯获取数据和将数据送到指定接口的作用。比如一个红外传感器,它的控制器就是不断和两个 LED 通信(其中一个 LED 是发射红外线,另一个是接收红外线),然后把测到的时延变成电信号加载到输出针脚上。这是一个很简单的控制器,甚至我们都感觉不到它的存在。我们也可以认为整个互联网是一个控制器,当用户在浏览器输入 URL 后,互联网经过一系列复杂的数据传输,最终把数据传回用户浏览器上。这就是一个有史以来最复杂的控制器了。

说回硬件开发中最常见的模式吧,比如一辆红外避障自动寻路小车,我们就需要为它定制一个控制器了。这个控制器首先和红外测距模块通信,获取周边障碍物距离,然后和车轮驱动模块通信,告知驱动模块可以往某个方向走多少距离。控制器可以自动寻路,也可以从控制器接出一条线缆,插上 PC 机的 USB 接口,然后在 PC 上进行人工控制。

那么问题来了,这么神奇的控制器要用什么来实现呢?

4. 各种硬件平台比较

单片机(MCU)全称为Micro Controller Unit,比如 C51,是一种流行了很多年的微控制器,上面说的这些功能比如红外避障自动寻路小车完全可以用它来做,而且大学生电子设计竞赛也经常用它。

Arduino 是一种近年来流行的开源硬件,本质上和 MCU 相同,但模块化程度非常高。Arduino 和 MCU 一样,CPU 是 MHz 级的,RAM 和 ROM 都是 KB 级的,没法运行操作系统,只能运行叫做逻辑代码的程序,而且需要一个刷写过程。

树莓派(Raspberry Pi)和 MCU 不同,这是一种完整的 ARM 架构 PC 级计算机系统,CPU、内存等都是 GB 级的,而且可以直接插入 SD 卡来运行操作系统(比如 Debian 系的 Raspbian,Win10-ARM 版等),甚至可以用作办公用途。

通过树莓派模式建立起来的游戏平台也有很多,比如 Game Shell 也是一种类似树莓派的开源游戏机,看起来和普通掌机无异,但它运行着一个称为 Clockwork 的 Linux,可以和树莓派一样折腾它。

还有一些比较新奇独特的,比如 MicroPython,性能和 MCU 差不多,它上面并不运行操作系统,而是运行一个 Python,而且可以使用 SD 卡来存储代码。

在硬件平台的选择上,可以简单地分为需要操作系统和不需要操作系统两种,前者启动耗时较多,但启动完成后功能强大,USB、WIFI、网卡等模块只能运行在操作系统上,适合作为总控平台和人机互动使用。后者逻辑较简单,适合用来完成一些特定功能,比如收集数据、物体移动等独立模块。本文所说的自制硬件指的是后者。

5. 没有灵魂的工夫茶

5.1. 关于工夫茶

茶具需求

  • 茶海茶盘一套

  • 盖碗一套

  • 茶杯三个

     

三大主要动作

  • 若琛出浴:用第一泡茶水烫杯,又谓温杯,转动杯身,如同飞轮旋转,又似飞花观舞。

  • 关公巡城:循环斟茶,茶壶似巡城的关羽,目的是使杯中的茶浓度一致,且低斟为不使香气过多散失。

  • 韩信点兵:巡城至茶汤将尽时,将壶中所余茶水斟于每一杯中,这些是全壶茶汤中的精华,应一点一滴平均分注,因此称韩信点兵。

5.2. 模块列表

  • 舵机

  • 32 路舵机控制器

  • 各种形状连接件

  • 杜邦线

  • 木板、木块、3M 胶、磁铁……

5.3. 模块连接图

5.4. 硬件原理与制作

本文说的是硬件,这里就重点展开说说这套硬件是怎么设计制作的。

舵机外观和原理

外观图

角度与脉冲对应关系图

32 路舵机控制器

外观图

  • 串口通信

  • 指令格式

    • #0 P500 #2 P1500 #4 P2500 T1000

  • 指令说明

    • #0 第 N 路舵机(0~31)

    • P500 脉冲宽度(500~2500 表示 0 度~180 度)

    • T1000 指令执行时间(单位毫秒)

供电模块

整个系统的供电有两个部分,舵机使用 6V 供电,舵机控制器使用 12V 供电,因此需要使用一个 12V 电源,并加上降压模块,使其有 2 路不同电压输出。要注意的是需要计算整个系统运行的最高功率,以此为标准来选择电源和降压模块。本系统中全部舵机同时工作的功率约为 15W 到 20W,电流约为 5A。

这是一个强力的电池。

可调降压模块如下图,支持调节输出电压和限制输出电流。

 

机械臂的制作

机械臂的制作的基本过程是这样,先使用各种形状部件进行连接形成基本外观,再加上舵机固定配件确定舵机位置和运动方向,在关节部位舵机还起到连接件的作用。可能需要担心的是第一个固定在支架上的舵机需要承担整个机械臂的全部重量,一般来说舵机的扭力还是能够胜任的。本系统使用的 MG995 舵机扭力是 13kg。

成品说明

  • 使用 U 型支架和直线型连接件制作

  • 包含 5 个舵机,形成 5 个自由度

  • 模仿手臂运动和手腕转动

  • 碗盖部位使用舵机控制转动升降,达到开关盖子的效果

  • 碗底和碗盖使用 3M 胶粘贴磁铁,机械臂上对应位置也粘贴磁铁,达到随意固定和分离的效果

成品图 1(伸直)

成品图 2(弯肘)

成品图 3(转腕并压低)

茶杯托的制作

这是一套在这个系统中使用的茶具。

为了使其可以进行洗杯操作,制作了三个使用舵机进行 180 度翻转的茶杯托。和机械臂类似,也粘贴了磁铁用来固定茶杯。

底盘和固定支架

底盘需要保证整个系统都能安装在上面,这里使用了一块松木板,也是因为松木较软便于钻孔。
在底盘之上还需要固定各模块的支架,并保证足够的稳定性,比如需要承受机械臂运动带来的各种作用力。因为只需要一个机械臂,所以多次调整之后使用了 4 个方木块拼成的柱子。如果对高度不满意还可以加减木块来调整。此部件效果图不单独展示了,见总体展示部分。

动作编排

机械臂的定位来自于多个舵机不同角度的组合效果,而一个动作则由多个定位随时间变化而形成。
单个舵机需要指定初始位置,即指定角度,当随时间变化时,需要按指定速度运动到指定位置。舵机控制器保证了多条指令的并发执行,使多个舵机同时运动形成所需效果。

假设有 2 个舵机,首先设置初始位置:(下面以伪代码描述)

machine1.set_pos(90);
machine2.set_pos(45);
action_parallel();

当按下按键或其他触发条件发生时,进行移动:

machine1.move(60, 2.0); // 2秒内移动到60度的位置
machine2.move(30, 1); // 1秒内移动到30度的位置
action_parallel();

这样两个舵机就会以不同速度运动到指定位置。通过这样的组合可以穷举出舵机所能达到的所有角度,从中调试出想做到的动作效果。

取水装置(未制作)

本系统是没有制作取水装置的,因为烧开水是一件危险的事情,需要保证烧水装备、水管、开关等运行过程的安全。在保证安全的前提下,使用的方法是机械臂对水管进行导向和开关控制,也就是上面所描述的动作编排。

在我们这个「人工智障」中,采用手动加水的方式。

5.5. 软件设计

通过上述的方法,我们已经可以将这个硬件制作出来并按照模块连接图连接完毕。和这个系统的连接是通过串口进行的,也即是通过 USB 转 TTL 的模块接入 PC,然后通过向串口发送指令来达到指定效果。我们并没有自己定义指令,而是直接使用了舵机控制器的指令,所以这个软件设计也简化为一句话:通过串口发送指令。(如果需要实现更多模块的集合,并且自定义指令,可以在系统其中再加入一个 MCU,通过它来进行指令的解析和对应模块的控制,这样就变成一种「模块中的模块」了。)

我们设想的动作流程是这样几个步骤:
1、先向茶碗和茶杯手动倒入开水,然后通过操作界面触发运行
2、从一开始回位状态出发,茶杯翻转清洁(乞丐版若琛出浴)
3、合盖,移动到第 1 杯,转腕倒茶,保持姿势移动到第 2/3 杯并循环 2-3 次(关公巡城)
4、回到第 1 杯,上下快速抖动,移动到第 2/3 杯再抖动并循环 2-3 次(韩信点兵)
5、回位并开盖,待品茶和倒入开水进入下一轮动作

在这一套操作中,我们总共需要实现回位、开盖、合盖、移动到第 N 杯、倒水、茶杯翻转等多个动作的开始和结束。
每个动作都是事先测好具体坐标和角度然后直接写入指令的,并没有采用智能检测,所以这个系统不叫「人工智能」而只能叫「人工智障」。
因为描述比较复杂,不如直接看代码:

class Tea(object):

    '''
    初始化USB连接对象
    heavy为配重设置,如水量较多则将其增大,使得手腕可以上扬来抵消机械臂被压低的高度
    舵机编号说明:
        机械臂从固定部分向手腕方向分别为:0,2,4,6
        碗盖:8
        3个茶杯:10,12,14
    '''
    def __init__(self):
        self.se = serial.Serial("/dev/ttyUSB0", 115200, timeout=1.0)
        self.heavy = 100

    '''
    多条指令的合并执行
    指令使用舵机控制器的规定,参数为:
        data = 目标设置(舵机编号,目标角度脉冲值)
        t = 用时
        delay = 完成后延时
    '''
    def instr(self, data, t=1000, delay=1):
        s = ''
        for d in data:
            s += '#%d P%d' % (d[0], d[1])
            self.se.write('#%d P%d' % (d[0], d[1]))
        s += 'T%d' % (t)
        self.se.write('T%d
' % (t))
        time.sleep(delay)

    '''
    回位
    '''
    def reset_arm(self):
        self.instr([
            (0, 750),
            (2, 1350),
            (4, 750),
            (6, 2500),
        ])

    '''
    3茶杯回位
    '''
    def reset_cup(self):
        self.instr([
            (10, 2500),
            (12, 2500),
            (14, 2500),
        ])

    '''
    3茶杯翻转
    '''
    def revert_cup(self):
        self.instr([
            (10, 1000),
            (12, 1000),
            (14, 1000),
        ])

    '''
    机械臂慢速到达茶杯1
    '''
    def to_cup_1_slow(self):
        self.instr([
            (0, 1350),
            (2, 1450),
        ])

    '''
    机械臂到达茶杯1
    '''
    def to_cup_1(self):
        self.instr([
            (0, 1350),
            (2, 1450),
        ], 400, 1)

    '''
    机械臂到达茶杯2
    '''
    def to_cup_2(self):
        self.instr([
            (0, 1350),
            (2, 1700),
        ], 400, 1)

    '''
    机械臂到达茶杯3
    '''
    def to_cup_3(self):
        self.instr([
            (0, 1500),
            (2, 1750),
        ], 400, 1)

    '''
    机械臂上下抖动
    '''
    def quake(self):
        self.instr([
            (4, 750 - self.heavy),
        ], 200, 0.2)
        self.instr([
            (4, 950 - self.heavy),
        ], 200, 0.2)

    '''
    转腕倒茶
    '''
    def pour(self):
        self.instr([
            (4, 950 - self.heavy),
            (6, 1500),
        ], 200)

    '''
    倒茶结束手腕回位
    '''
    def pour_end(self):
        self.instr([
            (4, 750 - self.heavy),
            (6, 2500),
        ], 400)

    '''
    开盖
    '''
    def cover_open(self):
        self.instr([
            (8, 500),
        ])

    '''
    合盖
    '''
    def cover_close(self):
        self.instr([
            (8, 1800),
        ])

    '''
    回位状态,包括开盖
    '''
    def ready(self):
        self.reset_arm()
        self.cover_open()

    '''
    整套流程
    '''
    def suite(self):
        self.revert_cup()
        self.reset_cup()
        self.cover_close()
        self.to_cup_1_slow()
        self.pour()
        self.to_cup_2()
        self.to_cup_3()
        time.sleep(0.5)
        self.to_cup_1()
        self.to_cup_2()
        self.to_cup_3()
        time.sleep(0.5)
        self.to_cup_1()
        self.quake()
        self.to_cup_2()
        self.quake()
        self.to_cup_3()
        self.quake()
        self.to_cup_1()
        self.quake()
        self.to_cup_2()
        self.quake()
        self.to_cup_3()
        self.quake()
        self.pour_end()
        time.sleep(0.5)
        self.ready()

5.6. 总体展示

整体系统图。

运行中的降压模块和舵机控制器,其中引入的两条电源线均从同一个 12V 电源接出。

为了便于操作,把程序放在树莓派上运行,将 USB 串口接到树莓派上,并加上触摸屏,制作了一个网页,可以进行触控操作。

下面我们来看看整体运行效果:

 

 

往期精彩

 

天下武功,唯快不破——快速搜索工具 ripgrep

MySQL Flashback 拯救手抖党

如何减少 MySQL 的 DDL   对业务的影响?

网易消息推送系统微服务化实践

原文点击此处跳转

 

版权声明:本文为weixin_38625669原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38625669/article/details/91521021