Python可以這樣玩(25):MeArm 自動控制


用搖桿來控制機器手臂,是 MeArm 機器手臂的套件,可以讓我們知道自行組裝的機器手臂是否正常運作,也順便練習一下操作技巧。

由於我是從事工廠自動化產線設計的工作,所以機器手臂對我而言,應該是讓它能夠加入生產的工作,如果要達到這個目的,我們就可以使用 Python 程式來控制機器手臂。

首先,請上傳 Standard Firmata 到開發板,所有的線路都可以保留,雖然我們用不到搖桿,也不用拆掉,因為哪天想玩搖桿夾娃娃的時候,只要上傳上一個單元的 Arduino 程式即可。




角度初始化

Python 程式的最前面,請複製以下的程式碼,前面的單元都已經介紹過,相信大家都很熟悉:

from pyfirmata import Arduino
from pyfirmata import SERVO
from time import sleep

# Setting up the Arduino board
port = 'COM3'
board = Arduino(port)
# Need to give some time to pyFirmata and Arduino to synchronize
sleep(5)

sleep 五秒鐘是為了讓電腦與開發板能夠同步,接下來就是定義 PIN 腳位以及初始的角度:

# 設定底座,左邊,右邊,鉗爪的 pin
pinBottom, pinLeft, pinRight, pinHand = 9, 10, 11, 6
bAngle, lAngle, rAngle, hAngle = 90, 90, 90, 0

請注意,四個PIN腳一定要支援PWM,當然,如果上一個單元您是按部就班做完的,這裡就沒有爭議,然後定義四個伺服馬達的角度。底座、左側(上下)、右側(前後)都定義成90度,鉗爪定義成 0度,也就是張開。

在我的實際的程式中,我將 bAngle 定義成 75度,這是因為我在安裝的時候,把伺服馬達卡上底做的時候稍微震動,所以角度跑掉了一些,就會發現90度的時候有點歪,其實不需要拆掉重新校正再重新安裝,在程式中修改一點角度就可以。

接下來我寫一個簡單的函數setServoAngle(pin, angle),去控制特定伺服馬達的角度,然後分別把四個伺服馬達物件定義出來,再轉到初始位置,程式如下:

# Custom angle to set Servo motor angle
def setServoAngle(pin, angle):
    board.digital[pin].write(angle)
    sleep(0.015)

# 初始化並設定初始角度
board.digital[pinBottom].mode = SERVO
setServoAngle(pinBottom, bAngle)
board.digital[pinLeft].mode = SERVO
setServoAngle(pinLeft, lAngle)
board.digital[pinRight].mode = SERVO
setServoAngle(pinRight, rAngle)
board.digital[pinHand].mode = SERVO
setServoAngle(pinHand, hAngle)

這裡有個地方要注意,在定義了一個伺服馬達之後,隨後要馬上設定初始角度,逐一做完。不可以先把四個伺服馬達物件定義好之後,再逐一設定初始角度,這樣的話手臂會出現怪異的動作。(這是實驗結果)

機器手臂的Hello World!

按照慣例,我們要寫 Hello World!,當然,機器手臂不會說話,也不會寫字,我是用幾個動作讓它跟世界Say Hello

我就讓它搖頭一次、抬頭三次、前後兩次、最後再磕頭兩次。





在做這些動作之前,我利用四個變數curBtmAngel, curLftAngle, curRhtAngle, curHndAngle來記憶四個伺服馬達的目前角度,這是為了讓機器手臂的動作可以連續,後面的角度會接著目前的角度做,當然,一開始的角度是由四個初始角度bAngle, lAngle, rAngle, hAngle來的。

整個搖頭的動作,就是讓底做的伺服馬達轉向右邊60度、再轉到左邊60(若是由右到左,就等於向左轉120)、然後回正(再向右轉60)。程式碼如下:

curBtmAngel,curLftAngle,curRhtAngle,curHndAngle =   
bAngle,lAngle,rAngle,hAngle
print('搖頭一次:')
for s in range(1):
    angle = 60
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel-i)
    curBtmAngel = curBtmAngel - angle
    angle = 120
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel+i)
    curBtmAngel = curBtmAngel + angle
    angle = 60
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel-i)
    curBtmAngel = curBtmAngel - angle
print('底盤角度',curBtmAngel)

我為了要讓伺服馬達很平順地轉動,所以我使用了 for 迴圈,讓它一度一度的轉,這樣看起來比較有機器手臂的感覺,如果真的夾娃娃的時候才不會掉下來。轉動完畢之後,再修正新的現行角度變數。另外,我前後使用了兩個 print() 函數把目前的執行狀態顯示出來,方便查看。

如果您夠細心,一定會發現明明只有搖頭一次,為何我在最外層還用了一個for 迴圈,而且該迴圈根本只執行一次。這是為了方便大家修改,如果想改成搖頭三次,只要把 range(1) 改成 range(3) 即可。

接下來就是抬頭三次,跟上面的程式碼很像,在這裡我並沒有用很複雜的函數去包裝這些動作,而是平舖直述,主要目的是為了可讀性,程式碼如下:

print('抬頭三次:')
for s in range(3):
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
    curLftAngle = curLftAngle + angle
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
    curLftAngle = curLftAngle - angle
print('高低角度',curLftAngle)

前後兩次也差不多,其實我的目的就是讓機器手臂的三個關節分別活動一下:

print('前後兩次:')
for s in range(2):
    angle = 60
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    angle = 120
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    angle = 60
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
print('前後角度',curRhtAngle)

最後一個動作,磕頭兩次就不太一樣了,這次必須一次動到兩個關節,這個動作的目的是讓讀者知道如何同時運動多個關節,程式碼如下:

print('磕頭兩次:')
angle = 60
for s in range(2):
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
        setServoAngle(pinRight, curRhtAngle+i)
    curLftAngle = curLftAngle - angle
    curRhtAngle = curRhtAngle + angle
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
        setServoAngle(pinRight, curRhtAngle-i)
    curLftAngle = curLftAngle + angle
    curRhtAngle = curRhtAngle - angle
print('磕頭角度',curLftAngle,curRhtAngle)

上面同時運動兩個關節的寫法有一個條件,就是移動的角度必須相同,這裡的例子是60度,才能放再同一個迴圈裡面,如果角度不同,那麼就要自行調整寫法了。可以加入if 指令另行設計。

這幾個動作做完之後,手臂又會回到初始位置,我是故意這樣設計的,方便後續的動作。

夾取物品

打完招呼之後,就是進入重頭戲,我們想像一個場景,在自動化產線上面,需要用一個機器手臂把半成品從一條產線移動到另一條產線上,所以我們就要模擬 MeArm 從右邊把一個歐治鼻空瓶子(沒有打廣告的意思,我不小心找到的空瓶,其他東西太重)夾起來,然後轉到左邊,在輕輕放下。

為了讓機器手臂的動作可以成為一個循環,所以接下來我讓機器手臂在從左邊把歐治鼻夾起來,放回右邊,這樣就可以一直循環下去。

當我們要將右邊的瓶子夾起,對機器手臂而言,其實就包含了轉、下、去、夾、上、回,這幾個動作,要在左邊放下,又必須有轉、去、下、開、回、上,這些動作。其中 上下 去回 開夾這些都是相對應的動作,必須先弄清楚。





完整的程式碼如下,可以接在 Hello World! 的程式碼後面,我讓它夾兩次並且形成一個迴圈,我直接在程式中用#紅色說明

print('夾兩次:')
print('右轉,curBtmAngel=',curBtmAngel)
angle = 45   #右轉 45
for i in range(angle):
    setServoAngle(pinBottom, curBtmAngel-i)
curBtmAngel = curBtmAngel - angle
sleep(0.5)

print(',curBtmAngel=',curBtmAngel)
angle = 40   #這個角度剛好會到瓶子頸部,測試的結果
for i in range(angle):
    setServoAngle(pinLeft, curLftAngle-i)
curLftAngle = curLftAngle - angle
sleep(0.5)

for s in range(3):
    #從這裡開始進入迴圈,可以自行修改上面的3
    print(',curLftAngle=',curLftAngle)
    angle = 40    #這個角度剛好會到瓶子頸部,我讓下、去的角度相同
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 45    #這個角度可以夾緊
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle+i)
    curHndAngle = curHndAngle + angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
    curLftAngle = curLftAngle + angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print('大左轉,curRhtAngle=',curRhtAngle)
    angle = 90
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel+i)
    curBtmAngel = curBtmAngel + angle
    sleep(0.5)
    print(',curBtmAngel=',curBtmAngel)
    angle = 40 - 4 #原本40度,我故意減少4度造成回拉效果
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
    curLftAngle = curLftAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle-i)
    curHndAngle = curHndAngle - angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40 - 4 #去的動左少4度,這裡需要回正也要少4
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle+i)
    curHndAngle = curHndAngle + angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
    curLftAngle = curLftAngle + angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print('大右轉,curRhtAngle=',curRhtAngle)
    angle = 90
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel-i)
    curBtmAngel = curBtmAngel - angle
    sleep(0.5)
    print(',curBtmAngel=',curBtmAngel)
    angle = 40 - 4 # 回拉,同上
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
    curLftAngle = curLftAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle-i)
    curHndAngle = curHndAngle - angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40 - 4 # 回正
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
#迴圈結束,準備回到原始位置
print(',curHndAngle=',curHndAngle)
angle = 40
for i in range(angle):
    setServoAngle(pinLeft, curLftAngle+i)
curLftAngle = curLftAngle + angle
sleep(0.5)

print('左轉,curLftAngle=',curLftAngle)
angle = 45
for i in range(angle):
    setServoAngle(pinBottom, curBtmAngel+i)
curBtmAngel = curBtmAngel + angle
sleep(0.5)
board.exit()

在程式碼裡面只有一個地方比較奇怪,就是我做了一個回拉的動作,那是因為我們的機器手臂很陽春,並沒有辦法做到像專業的機器手臂那樣的精確,所以如果我用 40度把東西夾起來,也用40度把東西放下的時候,位置會偏掉,下次要夾的時候就會夾不到。因此我夾起的時候用 40度,而要放下的時候,我故意上它把東西放前面一點(4),這樣下次夾到的機率會比較高。

當然,在一個地方少移動4度回拉之後,它的相對動作就必須也少移動4度,才能回正。這樣,增加了夾到東西的機率,也保證整個迴圈的動作做完能夠回到正確的位置。

(下面是影片,可以看到夾取的動作)

最後我再列出完整的程式碼,理論上複製貼上就可以執行:

from pyfirmata import Arduino
from pyfirmata import SERVO
from time import sleep

# Setting up the Arduino board
port = 'COM3'
board = Arduino(port)
# Need to give some time to pyFirmata and Arduino to synchronize
sleep(5)

# 設定底座,左邊,右邊,鉗爪的 pin
pinBottom, pinLeft, pinRight, pinHand = 9, 10, 11, 6
bAngle, lAngle, rAngle, hAngle = 75, 90, 90, 0    #75原本是90,目的是校正

# Custom angle to set Servo motor angle
def setServoAngle(pin, angle):
    board.digital[pin].write(angle)
    sleep(0.025)

# 初始化並設定初始角度
board.digital[pinBottom].mode = SERVO
setServoAngle(pinBottom, bAngle)
board.digital[pinLeft].mode = SERVO
setServoAngle(pinLeft, lAngle)
board.digital[pinRight].mode = SERVO
setServoAngle(pinRight, rAngle)
board.digital[pinHand].mode = SERVO
setServoAngle(pinHand, hAngle)

curBtmAngel,curLftAngle,curRhtAngle,curHndAngle = bAngle,lAngle,rAngle,hAngle

print('夾兩次:')
print('右轉,curBtmAngel=',curBtmAngel)
angle = 45
for i in range(angle):
    setServoAngle(pinBottom, curBtmAngel-i)
curBtmAngel = curBtmAngel - angle
sleep(0.5)

print(',curBtmAngel=',curBtmAngel)
angle = 40
for i in range(angle):
    setServoAngle(pinLeft, curLftAngle-i)
curLftAngle = curLftAngle - angle
sleep(0.5)

for s in range(3):

    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle+i)
    curHndAngle = curHndAngle + angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
    curLftAngle = curLftAngle + angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print('大左轉,curRhtAngle=',curRhtAngle)
    angle = 90
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel+i)
    curBtmAngel = curBtmAngel + angle
    sleep(0.5)
    print(',curBtmAngel=',curBtmAngel)
    angle = 40 - 4 #回拉
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
    curLftAngle = curLftAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle-i)
    curHndAngle = curHndAngle - angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40 - 4 #回正
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle+i)
    curHndAngle = curHndAngle + angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle+i)
    curLftAngle = curLftAngle + angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)
    print('大右轉,curRhtAngle=',curRhtAngle)
    angle = 90
    for i in range(angle):
        setServoAngle(pinBottom, curBtmAngel-i)
    curBtmAngel = curBtmAngel - angle
    sleep(0.5)
    print(',curBtmAngel=',curBtmAngel)
    angle = 40 - 4 # 回拉
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle+i)
    curRhtAngle = curRhtAngle + angle
    sleep(0.5)
    print(',curRhtAngle=',curRhtAngle)
    angle = 40
    for i in range(angle):
        setServoAngle(pinLeft, curLftAngle-i)
    curLftAngle = curLftAngle - angle
    sleep(0.5)
    print(',curLftAngle=',curLftAngle)
    angle = 45
    for i in range(angle):
        setServoAngle(pinHand, curHndAngle-i)
    curHndAngle = curHndAngle - angle
    sleep(0.5)
    print(',curHndAngle=',curHndAngle)
    angle = 40 - 4 # 回正
    for i in range(angle):
        setServoAngle(pinRight, curRhtAngle-i)
    curRhtAngle = curRhtAngle - angle
    sleep(0.5)

print(',curHndAngle=',curHndAngle)
angle = 40
for i in range(angle):
    setServoAngle(pinLeft, curLftAngle+i)
curLftAngle = curLftAngle + angle
sleep(0.5)

print('左轉,curLftAngle=',curLftAngle)
angle = 45
for i in range(angle):
    setServoAngle(pinBottom, curBtmAngel+i)
curBtmAngel = curBtmAngel + angle
sleep(0.5)
board.exit()

玩到這裡,您就可以藉由修改上面的程式碼,試著拿不同的東西來夾,在整個夾取的過程裡面,您一定會發現整個機器手臂在左右轉的時候感覺不是很順,這也沒辦法,因為底盤的伺服馬達承受整個手臂的重量,小小的伺服馬達又不是很夠力,只能接受了。

當然,您還可以在搖桿上面做改變,例如使用藍芽模組來連接,就可以用手機來控制機器手臂。或者,加上 Raspberry Pi 模組讓 Python在上面執行來操作機器手臂,這樣就可以脫離電腦了。幾個單元之後,我們就會進入 Raspberry Pi 的領域了。


影片DEMO如下:


留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(11):數學繪圖

Python可以這樣玩(15):蜂鳴器與音樂