【樹莓派】Python開發工控機急停設計

鄭立賽發表於2021-12-02

背景

我們在一些工業產品中使用樹莓派替代了PLC和上位機,並藉助樹莓派的算力將AI和機器視覺引入工業領域。
以前的產品都不存在動作機構,僅僅將結果輸出到指示燈、蜂鳴器或者顯示器上,沒有安全隱患,
現在引入了動作機構,需要根據結果驅動裝置執行一定的動作,動作機構的引入,增加了產品的安全隱患,比如可能會夾手,撞機等。為此我們需要設計額外的保護程式,其中最重要的是急停功能的實現。

要求

  • 急停訊號優先順序最高,任何情況下按下急停都應該馬上停止

問題分析

  • 動作機構由24V供電,急停開關串聯在電源上,可以做到開關按下後,動作機構斷電。(急停開關都帶有鎖定機構,按下後不會彈起,會保持按下狀態)
  • 樹莓派獨立於動作機構供電,急停開關按下後,樹莓派收到訊號,開始終止程式,之後一直監聽急停按鈕訊號。
  • Python一般情況下是單執行緒執行,為了及時響應急停,需要將急停功能做成主程式,業務動作邏輯作為子程式,當監聽到急停訊號後,馬上終止子程式

設計思路

  • 擇子程式而不是子執行緒的原因為:Python中子執行緒無法傳送kill訊號,沒有很好的辦法干預子執行緒的行為(除非每一步都判斷一下,會造成程式碼複雜度升高),而子程式可以直接傳送terminate訊號殺死。
  • 急停使用低電平觸發原因為:我們認為低電平是一個穩定的狀態,高電平不是一個穩定的狀態,比如由於某種原因導致斷電,那麼也應該觸發急停,發生任何非正常的情況,停下來總是沒錯的。

接線示意圖

Python程式流程圖

程式碼實現

import RPi.GPIO as GPIO
import time
from multiprocessing import Process
# 定義訊號引腳
button_stop = 20
button_reset = 21
button_start = 22


# 初始化GPIO
def init_gpio():
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    # 初始化按鈕,按鈕均為低電平觸發
    GPIO.setup(button_reset, GPIO.IN)
    GPIO.setup(button_start, GPIO.IN)
    GPIO.setup(button_stop, GPIO.IN)


# 業務動作
def step_1():
    time.sleep(3)
    return True


def step_2():
    time.sleep(3)
    return True


def step_3():
    time.sleep(3)
    return True


# 復位動作組合
def run_reset():
    move_reset_list = [
        step_3,
        step_2,
        step_1
    ]
    result = True
    try:
        for func in move_reset_list:
            func_name = func.__name__
            print("正在執行: %s" % func_name)
            func_result = func()
            if not func_result:
                result = False
                break
    except:
        result = False
    finally:
        if not result:
            exit(1)
        else:
            exit(0)


# 業務動作組合
def run_step():
    result = True
    try:
        auto_cover_list = [
            step_1,
            step_2,
            step_3
        ]
        for func in auto_cover_list:
            func_name = func.__name__
            print("正在執行: %s" % func_name)
            func_result = func()
            if not func_result:
                result = False
                break
    except:
        result = False
    finally:
        if not result:
            exit(1)
        else:
            exit(0)


if __name__ == '__main__':
    # 開始工作
    init_gpio()
    while True:
        if GPIO.input(button_start) == 0:
            try:
                p_run = Process(target=run_step, daemon=True)
                p_run.start()
                # 監聽急停訊號
                while p_run.is_alive():
                    if GPIO.input(button_stop) == 0:
                        p_run.terminate()
                        break
                    else:
                        time.sleep(0.1)
                if p_run.exitcode == 0 or p_run.exitcode is None:
                    print("執行成功")
                else:
                    print("執行失敗")
            except:
                print("執行失敗")
        elif GPIO.input(button_reset) == 0:
            p_reset = Process(target=run_reset, daemon=True)
            p_reset.start()
            # 監聽急停訊號
            while p_reset.is_alive():
                if GPIO.input(button_stop) == 0:
                    p_reset.terminate()
                    break
                else:
                    time.sleep(0.1)
        elif GPIO.input(button_stop) == 0:
            # 急停按鈕釋放後,再釋放程式
            while True:
                if GPIO.input(button_stop) == 0:
                    time.sleep(0.1)
                else:
                    break
        else:
            time.sleep(0.1)

相關文章