一步步製作下棋機器人之 coppeliasim進行Scara機械臂仿真與python控制

稚暉君又發佈了新的機器人,很是強大。
在編寫時看到了稚暉君的招聘信息,好想去試試啊!

小時候都有一個科幻夢,如今的職業也算與夢想有些沾邊了。但看到稚暉君這種閃着光芒的作品,還是很是羨慕。

以前就想做一個機械臂,實現遠程象棋對戰等功能,看到稚暉君的作品,更加心動了。心動不如行動,下面就一步一步仿真一個簡單的機器人,最終移植控制現實的機械臂,實現真正的下象棋,甚至能遠程象棋對戰。

說明

使用【FreeCAD軟件】繪製一個簡單的Scara機械臂,使用 【coppeliasim仿真軟件】仿真前面繪製的機械臂,使用python進行仿真控制,力求實現一個能夠移動棋子的仿真程序。後續目標是,將機械臂實物話,將仿真程序與實物實現通訊控制,最終實現一個移動象棋的機械臂。然後在它的基礎上添加視覺和象棋對戰算法,實現能夠自主象棋對戰(淘寶上已經有象棋對戰機械臂了)。當然,後面幾項需要花時間實現,現在先實現仿真控制。

學習機械臂的操作需要學習正逆運動學模型及解算、座標系等知識,本文由於涉及的較淺,暫不做說明,後續深入使用過程中涉及到時,再做說明。

簡介

FreeCAD軟件

軟件簡介

  • FreeCAD是一個基於OpenCASCADE的開源CAD/CAE工具。 OpenCASCADE是一套開源的CAD/CAM/CAE幾何模型核心,來自法國Matra Datavision公司,是著名的CAD軟件EUCLID的開發平臺。

  • 我個人的感覺是,FreeCAD界面操作、功能特性、實時畫面和渲染畫面等還是不如犀牛、3DMAX等軟件的,但是上手構建簡單的三維模型很快,軟件體積小,免費,學習成本也很低,對於非專業人士進行簡單的建模很方便,建模完成後如果需要可以去犀牛等軟件進行進一步精細處理。而且進階學習可以使用python進行操作,由於是開源的,你甚至可以集成在自己的python程序中。

  • FreeCAD 官網

  • FreeCAD 官網介紹中文版

  • FreeCAD Git網站

  • 推薦B站UP 灰大柱 的額視頻教程 freecad基礎教程全套-灰大柱 ,他的軟件版本較老,但是基本功能一致,跟着學習完成後簡單使用沒有問題。

FreeCAD進行機械臂仿真

  • 其實FreeCAD也是可以進行機器仿真的,它有很多插件,而且自帶 Robot 插件,就可以進行仿真。比如可以直接打開起始頁的機械臂的例子:

  • 然後在【工作臺】中選擇 【Robot】工作臺:
    image

  • 用【shift】同時選中左側的軌跡
    image

  • 然後點擊【任務】進入任務界面,就會出現機器人控制檯:
    image

  • 此時點擊【模擬軌跡】,進入運動模擬界面,點擊左側的運行右箭頭就可以進行軌跡運動了(如果有軌跡的話),機械臂就會自己動起來:
    image

  • 也可以自己進行示教運動軌跡錄入,具體方法可以參考官網說明,也可以查看B站的視頻教程:【FreeCAD讓機器人動起來】,經過簡單的示教錄入,就能實現機械臂運動仿真。

  • 對於FreeCAD的機械臂仿真,我認爲簡單玩一玩還是可以的,但功能肯定是沒有【coppeliasim仿真軟件】這種專業軟件完善的。所以此處不做深入學習。

coppeliasim仿真軟件

coppeliasim仿真軟件簡介

  • 瑞士CoppeliaRobotics公司的CoppeliaSim軟件是一款基於分佈式控制架構,具有集成開發環境的基於物理引擎的動力學機器人模擬器。CoppeliaSim軟件曾叫Vrep(VirtualRobotExperimentationPlatform虛擬機器人實驗平臺),於2019年底正式更名爲CoppeliaSim。CoppeliaSim與Vrep完全兼容,並豐富的功能,特別是增加了對ROS2的支持。

  • CoppeliaSim 原生支持LUA腳本語言控制和python語言控制,默認爲LUA語言。且文檔主要以LUA語言爲主。也支持C++等語言控制。由於使用python語言,可以很方便的調用一些庫,包括圖像處理等方面的庫,所以此處選用python語言進行控制。感興趣的可以學習lua語言進行控制。

  • lua語言:Lua 是一個誕生於巴西的小巧的腳本語言,純C語言編寫,體積小巧,速度高效,其執行速度較python更快,方便移植,可裸機運行於小資源的單片機上。而python只有第三方的microPython和pikapython才能運行在小資源的單片機上,兼容性不如lua。但lua的庫沒有python豐富,因爲它更多的被用作膠水語言,所以在程序界沒有python普及。

  • 菜鳥教程 lua

  • lua Git

  • lua語法簡單,學過C和python的很快就能入手lua。但是後面不用它,所以不多做介紹,感興趣的可以自行了解。

  • coppeliasim官網

  • coppeliasim 有幾個版本,推薦下載 EDU 教育版。player版本的功能限制太多,模型也不全。

  • coppeliasim英文文檔

  • coppeliasim文檔翻譯

  • 關於教程,肯定是外網的教程較多,可自行查找,此處不做推薦。內網的很多較亂,但跟着走也是可以的。

  • 想快速入門的可以看 B站UP的視頻【聽塵listener

  • 詳細視頻教程可以看 B站搬運的【Vrep / CoppeliaSim 教程(4.3版本)】(沒看過,不知道質量如何)

  • 文本教程可以看 【CoppeliaSim(原V-REP)新手上路】 ,跟着走一遍就能搭建基本的python控制程序。

安裝完後,可以拖拽即個示例模型進行測試。很多機械臂都是現實中存在的,比如和公司的那臺大機械臂,這裏面就有一款應該是同一家的很相似的產品【FrankaEmikaPanda】,拖拽後點擊【start】就可以仿真自帶的程序:
image
當然,還提供了很多帶程序的模型,比如小車等,還有不帶程序的模型,比如牆體、椅子等物品,可用於搭建碰撞環境。

  • 示例中是有一款Scara的【MTB】機械臂的,可以自行運行查看。

其他

學習機械臂控制還需要了解和學習 正逆運動學求解 、座標系、編程甚至電子電路、建模等知識,此處不做講解,請根據實際需要進行學習。

繪製簡單的機械臂模型

Scara機械臂

  • SCARA是Selective Compliance Assembly Robot Arm的縮寫,意思是一種應用於裝配作業的機器人手臂。它有3個旋轉關節,最適用於平面定位。比如自帶的【MTB】機械臂:
    image

  • 選擇Scara機械臂,一方面是因爲簡單好實現,另一方面是公司之前有設計過Scara機械臂,我層深度參與過底層程序的編寫。但是由於種種原因暫時擱置,後續會繼續開發。另外目前淘寶已經有了下象棋的機器人(可自行搜索下象棋機器人),也是類似Scara的設計。由於結構簡單,對於電機的要求也不是很高,所以實現一個Scara機械臂是入門機械臂的優先選擇。

  • 對於像稚暉君那種機械臂,我相信,在完成Scara機械臂的製作後,實現起來也會更加容易。雖然不推薦重複造輪子,但是很多事情只有從底層一步一步趟過去,纔能有到達頂峯的實力。

開始建模

  • 由於是用於測試的,所以我認爲,只要有個簡單的樣子,就能進行最簡單的仿真。後續完成基本仿真後,開始3D打印實裝時進行細化建模。
  • 所以此處只建立了四個簡單的模型,臂長等參數也沒有細緻優化。後續寫代碼過程中根據需要進行優化。模型如下:
    • 一個用於支撐的支撐座:
      image

    • 一個上下移動的短臂:
      image

    • 第一長臂:
      image

    • 第二短臂:
      image

    • 合起來就是上面的簡化模型:
      image

  • 後續會在短臂末端增加一個夾爪用於夾取物體。
  • 建模完成後,在左側工程樹中選中要導出的零件實體,選擇【文件->導出】,導出爲.stl格式(STL MESH)。四個部分恩都要導出。

導入到Compliance

  • 教程見【機器人系統設計-coppeliasim仿真
  • 在【coppeliasim】中 選擇【File->Import->Mesh】導入剛纔用FreeCAD導出的幾個零件。
  • 在跳出的界面要將縮放比例填爲0.001,因爲繪圖時使用的單位爲mm,而Compliance 默認的爲m,然後點擊import就能導入了:
  • 導入後是沒有先後邏輯的。我們先添加幾個關節。分析機械臂,可以得出我們需要兩個轉動關節,一個移動關節。在【add->Joint】中分別選擇 Revolute(旋轉關節)、Prismatic(移動關節)進行添加。
  • 然後對各個部件進行命名:
關節 名稱
底座 ShapeBase
固定短臂 ShapeArm1
第一長臂 ShapeArm2
第二短臂 ShapeArm3
移動關節 BaseJoint
轉動關節1 ArmJoint1
轉動關節2 ArmJoint2

邏輯關係和位置

  • 移動幾個關節的位置到指定地方,使其符合移動邏輯:
    image

  • 在左側的樹狀圖中按照邏輯順序拖拽,使其符合主從關係:
    image

  • 簡化模型:

    • 選中一個模型,然後 【Edit->Decimate selected shape】,按照默認,點擊OK即可:
      image
    • 對每個模型都簡化一下,簡化完成的會變色:
      image

運行嘗試

  • 對【ShapeBase】添加程序文件,選擇【ADD->Associated child script->Non threated->lua】,添加一個無線程的lua文件:
    image
  • 打開程序文件,在第一行添加:
simRemoteApi.start(19999)

因爲python控制coppeliasim仿真軟件實際上是用的遠程端口。

剩餘的代碼可以自行理解其意思,然後對其編程操作。我們使用外部python編程,對程序不必關心,默認即可。

  • 此時我們可以點擊 【Start】 按鈕進行仿真,並沒有什麼現象,因爲沒有控制代碼。

  • 如果想有現象,可以打開動力學屬性,方法如下:對選中的機械臂,點擊左側的【scene object properties】進入配置頁面,點擊最下面的【Show dynamic properties dialog】,將【Body is respondable】和【Body is dynamic】勾選上。分別對三個臂進行同樣處理後,再點擊運行,就可以看到,機械臂一起掉到了地上,且小臂會由於重力作用亂擺。

  • 我們簡單實用程序測試的話,不需要動力學屬性模擬,所以都取消勾選就行了,後續深入學習時再根據需要打開。

  • 下面,打開python環境,進行簡單的連接測試。此處我使用的是anaconda環境的Spyder IDE。

  • 使用python控制機械臂,需要使用官方提供好的初始化腳本和dll文件。首先,打開【coppeliasim】的安裝位置,在【CoppeliaRobotics\CoppeliaSimEdu\programming】文件夾下都是各種控制環境的支持包,在本文件夾下的【legacyRemoteApi\remoteApiBindings\python\python】文件夾下,找到 sim.py和simConst.py,以及 simpleTest.py,在【legacyRemoteApi\remoteApiBindings\lib\lib\Windows】下找到remoteApi.dll(其他環境請自行按要求配置),這四個文件,將他們複製到自己的工作區文件夾,然後在Spyder中打開simpleTest.py。

  • 注意:在運行python程序前,需要先在【 coppeliasim】中點擊【start】運行 ,在coppeliasim中開啓運行後,開始運行python程序,如果python的控制檯輸出鼠標的X軸座標,說明一切正常,python控制成功。

編寫代碼

  • 新建一個.py文件,開始編寫自己的代碼。
  • 寫之前,先構思一下代碼需求,再設定一下代碼框架。
  • 因爲是測試代碼,主要用於實現機械臂的移動旋轉控制,上下運動控制,這兩個基本功能,以及理解基本的控制代碼和邏輯。如果時間足夠,後續拓展實現(X,Y)座標點自行運動,需要添加界面控制環境,預計使用pyqt環境。
  • 基本框架就是將運動控制部分整合爲一個類,方便以後拓展串口通訊、UI控制等功能。

機械臂基本的操作類如下:

# -*- coding: utf-8 -*-
"""
Created on Wed Apr  5 13:11:11 2023

@author: ZNZZ
"""
# Make sure to have the server side running in CoppeliaSim: 
# in a child script of a CoppeliaSim scene, add following command
# to be executed just once, at simulation start:
#
# simRemoteApi.start(19999)
#
# then start simulation, and run this program.
#
# IMPORTANT: for each successful call to simxStart, there
# should be a corresponding call to simxFinish at the end!




try:
    import sim
except:
    print ('--------------------------------------------------------------')
    print ('"sim.py" could not be imported. This means very probably that')
    print ('either "sim.py" or the remoteApi library could not be found.')
    print ('Make sure both are in the same folder as this file,')
    print ('or appropriately adjust the file "sim.py"')
    print ('--------------------------------------------------------------')
    print ('')

import time
import math 

class MyArmBasicClass():
    
    def __init__(self):
        print('MyArmTest Program started')
        self.clientID = 0
        self.Handle={  #字典,用於保存各個模塊的句柄,方便拓展和查找;key值要和CoppeliaSim中的模塊命名一致
            "ShapeBase":0,
            "ShapArm1":0,
            "ShapArm2":0,
            "ShapArm3":0,
            "BaseJoint":0,
            "ArmJoint1":0,
            "ArmJoint2":0,
            }
        self.HandleOrder=(   #元組有固定得的順序,所以用元組作爲順序記錄,要和上面的Handle的一致
            "ShapeBase",
            "ShapArm1",
            "ShapArm2",
            "ShapArm3",
            "BaseJoint",
            "ArmJoint1",
            "ArmJoint2",          
            )
        
        
        
        

        
    def ConnectedStart(self):
        '''
        連接初始化。
        需要首先在 CoppeliaSimEdu 中點擊運行,再運行本python程序,才能正確連接。
        返回-1表示連接失敗

        Returns
        -------
        TYPE
            DESCRIPTION.

        '''
        sim.simxFinish(-1) # just in case, close all opened connections
        self.clientID=sim.simxStart('127.0.0.1',19999,True,True,5000,5) # Connect to CoppeliaSim
        if self.clientID!=-1:
            print ('Connected to remote API server')
            return 0
        else:
            print ('Connected faile,please check!')
            return -1
        
    def GetArmHandle(self):
        '''
        獲取各個模塊的句柄,用於操控
        有些模塊的句柄獲取不到
        
        '''
        for i in  self.HandleOrder:
            self.Handle[i]  = sim.simxGetObjectHandle(self.clientID, i, sim.simx_opmode_blocking) #獲取句柄,返回兩個值,一個是ret,用於判斷是否獲取成功,一個是obj,表示句柄號,兩個值以元組的方式存到Handle中
            print(self.Handle[i][0])
            if self.Handle[i][0] != sim.simx_return_ok:
                print("Get "+i+" Handle Error!!") 
            else:
                print("Get "+i+" Handle OK!!") 
                   #實測可以看到,只獲取了 底座和三個關節的句柄,所以能操控底座的位置,能操控三個關節的角度,但是不能操作三個臂
                   
    def GetShapeBasePosition(self):
        '''
        獲取底座的位置
        
        '''          
        if self.clientID!=-1:
            if self.Handle[self.HandleOrder[0]][0] != sim.simx_return_ok:
                print("Get "+self.HandleOrder[0]+" Handle Error!!") 
            else:
                ret, arr = sim.simxGetObjectPosition(self.clientID, self.Handle[self.HandleOrder[0]][1], -1, sim.simx_opmode_blocking)
                print(ret,arr)
                return ret, arr
        else:    
            print("Something Error!!")
            
                
    def SetShapeBasePosition(self,X,Y,Z):
        '''
        設置底座的位置

        Parameters
        ----------
        (X,Y,Z) : TYPE
            DESCRIPTION:目標座標(世界座標系)

        Returns
        -------
        None.

        '''
        if self.clientID!=-1:
            if self.Handle[self.HandleOrder[0]][0] != sim.simx_return_ok:
                print("Get "+self.HandleOrder[0]+" Handle Error!!") 
            else:
                sim.simxSetObjectPosition(self.clientID, self.Handle[self.HandleOrder[0]][1],-1,(X,Y,Z), sim.simx_opmode_blocking)
                print("Set ShapeBase Pos to X:"+str(X)+" Y:"+str(Y)+"  Z:"+str(Z))
        
        else:    
            print("Something Error in SetShapeBasePosition!!")
        
        
        
    
    def GetJointAngle(self,num):
        '''
        獲取旋轉關節的角度

        Parameters
        ----------
        num : TYPE:控制哪個joint關節
            DESCRIPTION:可以輸入 0 1 2  分分別表示 ShapeBase ArmJoint1 ArmJoint2 
                        也可以直接輸入字符串 ShapeBase ArmJoint1 ArmJoint2 

        Returns
        -------
        None.

        '''
        if self.clientID!=-1:
            if self.Handle[self.HandleOrder[0]][0] != sim.simx_return_ok:
                print("Get "+self.HandleOrder[0]+" Handle Error!!") 
            else:
                if str(type(num)) == "<class 'int'>":  #先判斷輸入的num類型
                    if num==1:
                        targetObj_Revolute_joint = "ArmJoint1"
                    elif num == 2:
                        targetObj_Revolute_joint = "ArmJoint2"
                    elif num == 0:
                        targetObj_Revolute_joint = "ShapeBase"
                    else:
                        print("Joint num Error !!")
                        return 
                elif str(type(num)) == "<class 'str'>":
                    targetObj_Revolute_joint = num
                    
                else:
                    print("Joint num type Erroe,Pleace give 1 or 2 or stringName")
                    
                position = sim.simxGetJointPosition(self.clientID, self.Handle[targetObj_Revolute_joint][1], sim.simx_opmode_blocking)
                
                print("Joint "+targetObj_Revolute_joint + " Angle is "+str(position))
                return position
                
        else:    
            print("Something Error in GetJointAngle!!")
        
        
        
    
        
        
    
    def SetJointAngle(self,num,angle):
        '''
        設置關節角度/位置
        對於旋轉關節,是設置角度值,內部需要轉換爲弧度;對於移動關節,我還沒搞明白其單位,推測是米,所以mm需要除以1000

        Parameters
        ----------
        num : TYPE:控制哪個joint關節
            DESCRIPTION:可以輸入 0 1 2  分分別表示 ShapeBase ArmJoint1 ArmJoint2 
                        也可以直接輸入字符串 ShapeBase ArmJoint1 ArmJoint2 
        angle : TYPE  對於  ArmJoint1 ArmJoint2 ,爲旋轉得到角度值,對於ShapeBase,就是拉伸,暫時沒注意具體拉伸多少
            DESCRIPTION.

        Returns
        -------
        None.

        '''
        if self.clientID!=-1:
            if self.Handle[self.HandleOrder[0]][0] != sim.simx_return_ok:
                print("Get "+self.HandleOrder[0]+" Handle Error!!") 
            else:
                if str(type(num)) == "<class 'int'>":  #先判斷輸入的num類型
                    if num==1:
                        targetObj_Revolute_joint = "ArmJoint1"
                    elif num == 2:
                        targetObj_Revolute_joint = "ArmJoint2"
                    elif num == 0:
                        targetObj_Revolute_joint = "ShapeBase"                        
                        
                    else:
                        print("Joint num Error !!")
                        return -1
                elif str(type(num)) == "<class 'str'>":
                    targetObj_Revolute_joint = num
                    
                else:
                    print("Joint num type Erroe,Pleace give 1 or 2 or stringName")
                    return -1
                    
                    
                if  targetObj_Revolute_joint ==    "ArmJoint1" or  targetObj_Revolute_joint ==    "ArmJoint2":
                    setangle = angle*math.pi/90#角度要轉爲弧度,但是弧度計算不是 A*π/180 嗎,此處90纔是正常的?
                else:
                    #ShapeBase 關節不是角度,是運行,目前還沒弄清數值與實際運動的關係
                    setangle = angle
                
                sim.simxSetJointPosition(self.clientID, self.Handle[targetObj_Revolute_joint][1], setangle, sim.simx_opmode_blocking) 
                print("Set " + targetObj_Revolute_joint + "Angle to "+str(angle))

               
        else:    
            print("Something Error in GetJointAngle!!")    
        
        
    def ConnectedStop(self):
        '''
        斷開操控連接

        Returns
        -------
        None.

        '''
        if self.clientID != -1:
        
            # Before closing the connection to CoppeliaSim, make sure that the last command sent out had time to arrive. You can guarantee this with (for example):
            sim.simxGetPingTime(self.clientID)
    
            sim.simxStopSimulation(self.clientID, sim.simx_opmode_oneshot)
    
            # Now close the connection to CoppeliaSim:
            sim.simxFinish(self.clientID)
            
            print("Connecte Stoped!!")
            
        else:
            print("Pleace check clientID !")
        
        
            
  

        
  • 整體流程如下:
    • 遠程連接到軟件端口,並判斷是否連接成功,成功後會返回正確的 clientID ,這個ID需要全局使用。即 ConnectedStart 函數的內容
    • 連接成功後就需要可控部件的句柄了,用於操控。即 GetArmHandle 函數的內容。此處我獲取了所有部件的句柄,運行時可以看到,有些部件是獲取不到的,也就沒辦法操控。而我們需要操控的部件,都是可以獲取到的,比如底座的座標位置,可獲取可設置;三個關節的角度或拉伸值,可獲取可控制,而對於機械臂零件,則是不能獲取也不能控制。 需要注意區分不同關節的意思,雖然他們用同一個函數就能操作。
    • 獲取到句柄後,就能對其進行獲取信息或是控制了。

下面,寫一個簡單的控制函數,實現三個關節的循環控制:

            
def main():
    '''
    簡易機械臂控制測試

    '''
    MyArmClass = MyArmBasicClass()
    ret = MyArmClass.ConnectedStart()
    if ret == 0: #連接成功
        MyArmClass.GetArmHandle()
        MyArmClass.GetShapeBasePosition()#獲取底座座標
        position1 = MyArmClass.GetJointAngle("ArmJoint1")  #獲取三個關節的角度/位置
        position2 = MyArmClass.GetJointAngle("ArmJoint2")
        position1 = MyArmClass.GetJointAngle("BaseJoint")
        
    
        movedir = 0
        movecount = 0
        movestep = 5    #步進量,可用於調速
        moveangle = 90 #目標角度
        
        #讓機械臂循環流暢動起來
        
        while(True):
            if movedir == 0:
               movecount = movecount-movestep
               if movecount < -moveangle*movestep:
                   movedir = 1
                   
            elif movedir == 1:
                movecount = movecount+movestep
                if movecount > moveangle*movestep:
                    movedir = 0
            MyArmClass.SetJointAngle("ArmJoint1",movecount/10)
            MyArmClass.SetJointAngle("ArmJoint2",movecount/10)
            MyArmClass.SetJointAngle("BaseJoint",movecount/5000) #可以看到機械臂在上下運動
        
        
        MyArmClass.ConnectedStop() #若是需要斷掉,請主動調用
    else:
        print("Connected Error!! Pleace check and retry!!")
        print("需要先在  coppeliasim 軟件中開啓仿真,再開始本python才能正確遠程連接!!")
                      
            
if __name__ == '__main__':
     #multiprocessing.freeze_support()
    main()
  
  • 以上測試代碼簡單實現了機械臂的長臂短臂的循環±90度旋轉運動和上下循環運動。
    image

總結

  • 以上,我們實現了對機械臂的簡單控制,重點是理清了如何用程序控制一個自己建模設計的物體。
  • 由於時間關係,沒有實現更進一步的操作邏輯,比如使用正逆運動學解算實現座標點與機械臂運行的匹配,實現給定三維空間座標點,機械臂就能移動過去。
  • 實際上,以前在公司做機械臂時,解算這一步已經實現了。當時還寫了個簡單的模擬程序,就是能根據座標點連續移動機械臂到指定角度,實現末端的座標點位移:
    image

後續會進一步實現運動學解算、象棋夾起放置等步驟,還會簡單實現一個對應的硬件,以實現實際控制。

以上完整的資料都放在【ChessRobot_PythonControlCoppeliasimModel

本文水平有限,內容很多詞語由於知識水平問題不嚴謹或很離譜,但主要作爲記錄作用,希望以後的自己和路過的大神對必要的錯誤提出批評與指點,對可笑的錯誤請指出來,我會改正的。

另外,轉載使用請註明作者和出處,不要刪除文檔中的關於作者的註釋。

隨夢,隨心,隨願,恆執念,爲夢執戰,執戰蒼天! ------------------執念執戰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章