樹莓派硬件編程——(二)用RPi.GPIO庫獲取信號

說到輸入,我們可以簡單的把傳感器分爲數字傳感器模擬傳感器,數字傳感器就是指只有高低電平兩種狀態的傳感器,比如說開關、紅外線傳感器、傾斜傳感器、繼電器等等,他們只有兩種狀態:閉合和斷開,像這種傳感器我們獲取狀態就非常簡單了,今天我們也着重討論數字信號的獲取和處理。

那麼什麼是模擬傳感器呢?那麼就先舉個栗子,我們說話發出的聲音,聲音是一種連續的量,從發出到結束,能量越來越大再逐漸變小,直到結束,聲音還有頻率之分;那麼我們把這種連續的量,可以測量出具體的值的量稱之爲模擬量,這種傳感器爲模擬傳感器。我們生活中常見的模擬傳感器還有溫溼度傳感器、光敏傳感器、壓力傳感器,我們發現溫溼度是在不斷變化的,並且我們可以測量得到具體的值;亮度和物體重量我們也都可以測量出來。模擬傳感器值的獲取就會相較麻煩,這個我們後面再逐一討論。

一、用槽型光電模塊當開關,控制LED燈的亮滅

對於這裏爲什麼不用按鍵來控制,實在是按鍵模塊不翼而飛,只好用同數字傳感器代替;

  1. 我們先來回顧一下LED閃爍的效果是如何實現的:
    import RPi.GPIO as GPIO
    import time
     
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(16,GPIO.OUT)    # 定義LED引腳模式爲輸出
     
    try:
        while True:
            GPIO.output(16,GPIO.HIGH)    # 用output()函數控制引腳的電平狀態
            time.sleep(1)
            GPIO.output(16,GPIO.LOW)
            time.sleep(1)
    except KeyboardInterrupt:
        GPIO.output(16,GPIO.GPIO.LOW)
        GPIO.cleanup()
  2. 那麼,我們就可以再這個基礎上添加信號輸入的部分
    import RPi.GPIO as GPIO
    import time
    
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(16,GPIO.OUT)
    GPIO.setup(12,GPIO.IN)    # 定義槽型光電引腳爲輸入模式
    
    try:
        while True:
            if GPIO.input(12):    # 用GPIO.input(引腳)函數來獲取引腳電平狀態
                                  # 如果有信號輸入,那麼就證明有物體經過,則進行處理
                GPIO.output(16,GPIO.HIGH)
            else:
                GPIO.output(16,GPIO.LOW)
    except KeyboardInterrupt:
        GPIO.output(16,GPIO.LOW)
        GPIO.cleanup()

  3. 那麼,如果我們想槽型光電每觸發一次,更改一次LED燈的狀態,該怎麼處理呢?
    import RPi.GPIO as GPIO
    import time
    
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(16,GPIO.OUT)
    GPIO.setup(12,GPIO.IN)
    
    state = False    # 狀態位
    try:
        while True:
            if GPIO.input(12):            # 檢測到有信號
                state = not state         # 置反原先的狀態位
                GPIO.output(16,state)     # 設置引腳的輸出狀態
                time.sleep(1)             # 這裏的延時只是爲了大家方便看到效果
    except KeyboardInterrupt:
        GPIO.output(16,GPIO.LOW)
        GPIO.cleanup()


    我們通過程序的運行效果,可以看到每檢測到一次信號,確實會改變一次LED燈的狀態,但是我們需要的是“檢測到 - 信號消失”應該纔是一個信號週期,才改變一次狀態,那麼我們可以怎麼實現呢?

    import RPi.GPIO as GPIO
    import time
    
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(16,GPIO.OUT)
    GPIO.setup(12,GPIO.IN)
    
    state = False
    try:
        while True:
            if GPIO.input(12):
            while GPIO.input(12):    # 我們可以再這裏加一個while循環,如果還是檢測到有信號
                pass                 # 那麼就一直執行空語句
            state = not state
            GPIO.output(16,state)
            time.sleep(0.2)          # 這裏延時200毫秒,是爲了使電平穩定,不然可能會造成誤判
    except KeyboardInterrupt:
        GPIO.output(16,GPIO.LOW)
        GPIO.cleanup()

二、什麼是上拉電阻和下拉電阻

我們先來看一下下面的現象(只是將槽型光電換成了普通按鍵,程序、引腳都沒有更改):

很神奇,我並沒有按按鍵,爲什麼它一直處於觸發狀態,而且不是一直保持高電平或者低電平,不然LED燈就不會一直閃爍了;

這是因爲此時按鍵引腳是處於浮空狀態的,什麼意思,就是它的狀態不確定,受周圍電平的干擾,可能是高電平也可能是低電平,那麼我們就需要添加一個上拉電阻(使引腳默認爲高電平)或者一個下拉電阻(使引腳默認爲低電平)來保證引腳狀態的穩定,不然就會出現上面的現象了。

那麼,這裏問題來了,之前的槽型光電爲什麼是正常的呢?這是因爲,槽型光電模塊在硬件電路上就已經添加了下拉電阻,使其默認是低電平狀態,所以我們在編程的時候可以不設置,能夠保證電平穩定;但是我們現在佈置的電路可沒有設計下拉電阻,所以現在引腳的狀態是不確定的。

注意:不是所有的傳感器都會設計上拉/下拉電阻的,所以我們在編程的時候最好進行一步設置,再有就是,不是所有的傳感器都是下拉電阻的,這個跟硬件的電路設計有關,所以我們可以注意一下傳感器上面到底是高電平觸發還是低電平觸發。

OK,接下來我們就需要通過編程,來確定引腳的狀態了:

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setup(16,GPIO.OUT)
GPIO.setup(12,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)
# 我們可以通過 pull_up_down 設置上拉/下拉電阻,對應的就是GPIO.PUD_UP/GPIO.PUD_DOWN

state = False
try:
    while True:
        if GPIO.input(12):
            while GPIO.input(12):
                pass
            state = not state
            GPIO.output(16,state)
            time.sleep(0.2)
except KeyboardInterrupt:
    GPIO.output(16,GPIO.LOW)
    GPIO.cleanup()

三、樹莓派引腳輸入的檢測方式:輪詢和中斷

  1. 什麼是輪詢:
    我們上面檢測按鍵的方式就是輪詢,就是說樹莓派在不斷的檢測按鍵是否有輸入,這是一種比較初等的檢測方式,它對資源的消耗會比較大,且當樹莓派在執行其他任務是,可能會錯過按鍵的檢測;
  2. 什麼是中斷:
    舉個栗子,我們週末在家,作業做完了正在看電視,這時候媽媽說家裏醬油沒有了,讓你幫忙去買醬油,這時候你就需要暫停看電視這件事,先去把醬油買了之後,再回來看電視。中斷其實就是這個道理,我不阻止你做其他事情,你在做其他事情的時候可能會被優先級更高的事情打斷,那麼就要先把優先級更高的事情做完,再回來做之前沒有完成的事情,這就是中斷。
    中斷的好處就是,不會佔用很多資源(可以忽略不計),且當觸發中斷時,可以第一時間來處理中斷,不會造成錯過事件觸發。
  3. 什麼是邊沿檢測:
    之前我們講過,對於我們的硬件來講,它們就只有兩種狀態:高電平和低電平,其實這個電平是一個相對狀態;什麼意思,就是說低電平不一定是0V,我們一般規定低於一個閾值,那麼此時就是低電平狀態,高於一個閾值就是高電平狀態;那麼在電平變化的過程中,肯定會有一個上升或者下降的趨勢,那麼這個趨勢我們稱之爲上升沿或者下降沿。
    這裏我們就可以知道了,邊沿檢測就是檢測這個變化趨勢是怎麼樣的,比如說我們按下按鍵,本來是低電平的,現在要變成高電平了,這時候就處於一個上升的趨勢,那麼這就是上升沿,當樹莓派檢測到這個電平變化之後呢,就可以對其進行處理了。

四、wait_for_edge()函數的使用

wait_for_edge()函數的作用,我們來打個比方:它就像一個霸王,它在執行的時候,一定要得到他想要的東西(檢測到邊沿變化),不然的話它就一直等在這裏,不讓後面的語句運行,直到檢測到邊沿變化。

那麼,它存在的意義是什麼呢?它佔用資源的時間很少,基本可以忽略不計。

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setup(16,GPIO.OUT)
GPIO.setup(12,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

state = False
try:
    while True:
        GPIO.wait_for_edge(12, GPIO.RISING)
        # GPIO.RISING    上升沿檢測
        # GPIO.FALLING   下降沿檢測
        # GPIO.BOTH      兩者都可以,也就是說檢測到邊沿變化
        state = not state
        GPIO.output(16,state)
        time.sleep(0.2)
except KeyboardInterrupt:
    GPIO.output(16,GPIO.LOW)
    GPIO.cleanup()

但是我們總不可能就讓樹莓派執行一個任務吧,我們可以給他添加響應時間,在響應時間內接受響應,超過響應時間就允許你做接下來的任務:

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setup(16,GPIO.OUT)
GPIO.setup(12,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

state = False
try:
    while True:
        channel = GPIO.wait_for_edge(12, GPIO.RISING,timeout=5000)
        # 用 timeout 設置響應時間
        if channel is None:
            print('Timeout',channel)
        else:
            state = not state
            GPIO.output(16,state)
            print(channel)
            time.sleep(0.2)
        print('lalala')
except KeyboardInterrupt:
    GPIO.output(16,GPIO.LOW)
    GPIO.cleanup()

我們發現,當我們沒有在響應時間內按下按鍵,那麼wait_for_edge()返回的值是None,如果觸發了,返回的則是引腳編號;
但是這裏需要注意的一點是,這裏並沒有使用中斷,也就是說其他任務在執行的時候,按下按鍵是沒有任何反應的,不然我們通過在後面加一個五秒延時,在延時的時候,按鍵是沒有任何反應的;

五、用add_event_detect()添加中斷檢測

其實我們可以這樣理解,樹莓派有一個小本本,記錄了當什麼中斷觸發的時候,該處理什麼事情,這時候我們就可以在這個小本本上面添加我們的需求,當引腳12檢測到邊沿變化的時候,就要對這個事件進行處理。

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BOARD)
GPIO.setup(16,GPIO.OUT)
GPIO.setup(12,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

state = False
def comm(chn):
    global state
    state = not state
    GPIO.output(16,state)
    time.sleep(0.2)    # 這個語句的作用是防止按鍵電平不穩定,造成多次觸發
                       # 可以在add_event_detect()中添加bouncetime=200來代替

try:
    GPIO.add_event_detect(12,GPIO.RISING,callback=comm)
    # GPIO.add_event_detect(12,GPIO.RISING,callback=comm,bouncetime=200)
    # 如果在後面我們不需要事件響應了,可以用GPIO.remove_event_detect(引腳)在事件列表中刪除
    while True:
        print('lalala')
        time.sleep(5)
except KeyboardInterrupt:
    GPIO.output(16,GPIO.LOW)
    GPIO.cleanup()

我們分析一下,上面這段程序都做了些什麼事情:

  1. 首先,我們將按鍵按下去之後應該做的事情定義了一個函數comm(chn)
    注意:此時的函數後面會被其他函數調用,所以這個函數我們稱之爲回調函數,且這個函數會有一個默認的參數:chn,即引腳號,我們這麼定義就好,不歸我們使用
  2. 然後我們用 GPIO.add_event_detect()函數添加了一箇中斷事件
    其參數就是:引腳號、邊沿變化模式和回調函數
    注意:添加事件我們不能夠放在循環中,重複添加就會報錯的
  3. 最後,循環主體我們可以做其他任務,當事件觸發的時候,會停下當前的事情,把事件的回調函數做完之後再回來做之前沒有完成的任務。

那麼,一個事件可不可以對應多個回調函數呢?
答案是可以的,但是它不是同時執行多個回調函數的,他會按照你的添加事件列表的順序去執行;

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