2019年6月28日 星期五

Raspberry Pi筆記:RTC1302

基本原理:

RPi在斷電後,本身系統時間是不會跳動的,更新時間的方式有兩種方式,一個是開機連上網路後透過NTP更新時間,一個是額外裝RTC模組(晶片+電池)。如果應用RPi的位置沒有網路,那就只能走第二條路。
這邊我使用了ref.1的程式來簡化後續的複雜度。

連線:

基於ref.1內的設定:
VCC3.3V
GNDGND
CLK#11 / GPIO#17
DAT#13 / GPIO#27
RST#15 / GPIO#22

Python程式:

import datetime
import pyRPiRTC

rtc = pyRPiRTC.DS1302()
#rtc.write_datetime(datetime.datetime.utcnow())
print(rtc.read_datetime())

討論:

1. 我手上測試的這個DS1302晶片,折磨了我很久,看了許多相關文件後仍沒辦法正常運作。在執行程式後一直跑出月份必須為1~12,在中間加了一條print發現晶片回傳的時間是2000年0月0日0時0分0秒;最後,我把RPi的系統時間設定給DS1302晶片後,一切都運作正常了。若有相似問題的,可以更新晶片內的時間試試。
2. 許多網路經驗分享,提到DS1302可以透過I2C作連線,我測試手上這片是完全無法透過i2cdetect -y 1抓到任何設備。

參考資料:

1. https://github.com/sourceperl/rpi.rtc

Raspberry PI筆記:蜂鳴器

基本原理:

蜂鳴器共分有主動式與被動式兩種,主動式蜂鳴器在通電時只會有單一蜂鳴音。
與主動式蜂鳴器不一樣的地方,被動式蜂鳴器在通電時,只會使蜂鳴器內的薄膜產生位移,整個發聲的原理,回到聲音的本質,是透過振動產生。因此透過恰當的控制,被動式蜂鳴器是能唱出有旋律的音樂。

接線:

+GPIO#2
-GND

Python程式:

主動式蜂鳴器
import RPi.GPIO as GPIO
import time
PIN = 2

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(PIN, GPIO.OUT)

GPIO.output(PIN, GPIO.LOW)
GPIO.output(PIN, GPIO.HIGH)
time.sleep(1)
GPIO.output(PIN, GPIO.LOW)

被動式蜂鳴器
import RPi.GPIO as GPIO
import time
PIN = 2

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(PIN, GPIO.OUT)

def buzz(freq, leng):
  delayValue = 0.5/freq
  cycle = freq*leng/1000
  for i in range(cycle):
    GPIO.output(PIN, GPIO.HIGH)
    time.sleep(delayValue)
    GPIO.output(PIN, GPIO.LOW)
    time.sleep(delayValue)

tone = [262, 294, 330, 349, 392, 440, 494]
duration = 1000/12
for t in tone:
  buzz(t, duration)
  time.sleep(0.01)

討論:

1. 被動式與主動式都有其適合的使用時機。
2. 被動式蜂鳴器的頻率與音符的關係,可以嘗試去找到對應關係,但建議只調整到近似的旋律即可,類似過去紅白機品質的音樂。

Raspberry Pi筆記:Stepper Motor

基本原理:

步進馬達具有精確控制馬達軸位置的特性,通常用於需要精確定位的地方。步進馬達的原理,可以參考ref.1的介紹。這邊使用28BYJ-48 5V作測試,由ref.2可以知道,這顆步進馬達轉一圈需要4096步,具有4相,在4相依序變化的設計下,步進馬達軸轉一圈共需要512次4相變化。

接線:

IN1GPIO#2
IN2GPIO#3
IN3GPIO#4
IN4GPIO#17
VCC5V
GNDGND

Python程式:

import RPi.GPIO as GPIO
import time

PIN = [2, 3, 4, 17]
MODE = {0: [0, 0, 0, 0],
        1: [1, 0, 0, 0],
        2: [1, 1, 0, 0],
        3: [0, 1, 0, 0], 

        4: [0, 1, 1, 0],
        5: [0, 0, 1, 0],
        6: [0, 0, 1, 1],
        7: [0, 0, 0, 1],
        8: [1, 0, 0, 1]}

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
for pin in PIN:
  GPIO.setup(pin, GPIO.OUT)

def setMode(m):
  mode = MODE[m]
  for idx, pin in enumerate(PIN):
    GPIO.output(pin, mode[idx])
    #print(pin, mode)

setMode(0)
time.sleep(1)
for i in range(512): 
  for i in [1, 2, 3, 4, 5, 6, 7, 8]:
    setMode(i)
    time.sleep(0.001)
setMode(0)

討論:

1. 沒有要轉動步進馬達的時候,可以將控制器的pin都設定到低電壓,節省電耗。
2. 步進馬達的控制上,在ULN2003驅動器簡化下,已簡化到只需要對輸入的4個針腳電壓作對應步進控制,就能輕鬆對步進馬達作。

參考:

1. https://www.youtube.com/watch?v=eyqwLiowZiU
2. https://sites.google.com/site/csjhmaker/d-dong-li-pian/28byj-48-bu-jin-ma-da

2019年6月27日 星期四

Raspberry Pi筆記:GPIO 按鈕

基本說明:

RPi.GPIO基本input操作,透過外部線路短路來改變GPIO的狀態。

接線:

#1GND
#2GPIO#2

Python程式:

import RPi.GPIO as GPIO
import time
PIN = 2

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(PIN, GPIO.IN)

def callback(PIN):
  print("clicked")

GPIO.add_event_detect(PIN, GPIO.RISING, bouncetime=200)
GPIO.add_event_callback(PIN, callback)

while True:
  time.sleep(10)

討論:

1. 這裡的程式使用了RPi.GPIO的事件偵測函數event_detect,呼叫以後會自動產生另外一組thread在偵測GPIO狀態,所以只需要控制主程式沒有停止就可以繼續執行。
2. 範例程式中的add_event_detect主要有2個參數,第一個為針腳位置,第二個為GPIO偵測到的狀態,可以為RISING / FALLING / BOTH,依需求設定。其餘可有可無的參數,包括bouncetime用來間隔事件發生的頻率,callback用來指定事件發生時會呼叫的函數。
3. 比較複雜的按鈕,如4x4或3x4矩陣鍵盤,原理是每條row與每條column的位置發生短路,就代表該鍵被按下。因此需要把row或column設定GPIO.OUT,column或row設定為GPIO.IN,來偵測短路位置。

參考:

1. https://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/

2019年6月24日 星期一

Raspberry Pi筆記:GPIO LED

基本說明:

RPi.GPIO基本output操作,透過控制GPIO PIN在低電壓(GPIO.LOW)與高電壓(GPIO.HIGH)來描述不同的狀態。

接線:

LED+GPIO#2
LED-330Ω-GND

Python程式:

import RPi.GPIO as GPIO
import time

LED_PIN = 2

GPIO.setmode(GPIO.BCM)

def lightOff():
  GPIO.output(LED_PIN, GPIO.LOW)

def lightOn():
  GPIO.output(LED_PIN, GPIO.HIGH)

GPIO.setup(LED_PIN, GPIO.OUT)
lightOff()
time.sleep(1)
lightOn()
time.sleep(5)
lightOff()

討論:

1. 為了避免LED燒壞,電阻在線路連接上是必要的。
2. LED通常是二極體,具有正負極,接反是不會過電的。
3. 相同的GPIO控制,可以用在多個LED上作不同的訊號輸出,抑或是RGB LED輸出。

Raspberry Pi筆記:HW-484聲音感測器

基本介紹:

HW-484是由一顆DIP可調式電阻加上一個MIC作偵測器,把偵測器換掉,類似的產品包括火源偵測、土壤濕度偵測、下雨偵測。
我在網路上搜尋不到HW-484的spec,參考相似產品KY-038 (ref.1)。

硬體安裝:

A(nalog)-
G(ND)GND
+3.3V / 5V
D(igital)GPIO#3

Python程式:

import RPi.GPIO as GPIO
import time

DETECT_PIN = 3

# initial GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(DETECT_PIN, GPIO.IN)

def callback(PIN):
  print("detected")

GPIO.add_event_detect(DETECT_PIN, GPIO.BOTH, bouncetime=200)
GPIO.add_event_callback(DETECT_PIN, callback)


while True:
  time.sleep(10)

註記:

1. 我手上這顆HW-484的MIC感應有點不良,彈指拍手、放音樂都沒有反應,測試了很久才發現它對吹氣很敏感。
2. A(nalog) PIN在這個例題中並沒有使用到,在網路上許多相似的範例中也沒有使用到。有幾個原因,RPi要讀取analog訊號相對讀取digital訊號要複雜,RPi的GPIO都只能讀取數位訊號,如果要讀取analog訊號,必須要透過一個A/D Converter,詳細的可以參考ref.2。

參考資料:

1. https://html.alldatasheet.com/html-pdf/1138845/ETC2/KY-038/110/1/KY-038.html
2. https://projects.raspberrypi.org/en/projects/physical-computing/15

2019年6月21日 星期五

Raspberry Pi筆記:HC-SR04 超音波感測器

基本說明:

HC-SR04 超音波感測器透過超音波反彈來測定感測器與障礙物之間的距離。
根據說明書(ref.1),能測到的障礙物會在感測器方向15°角之內,有效測定距離為2公分~400公分之間。
驅動HC-SR04的方式,透過給Trig針腳高電壓持續10µs,即開始測定距離。
HC-SR04回傳時,會將Echo針腳維持在高電壓,透過維持高電壓的時間來回饋測定的距離。
這裡需要注意,當維持高電壓時間會介於150µs~25ms之間,若時間大於38ms表示未偵測到障礙物。
距離(cm) = 高電壓時間(µs) / 58 

接線:

VCC5V
Trig>GPIO#20
EchoGPIO#21
GNDGND
Ref.2內有建議接線圖,在Echo針腳與GND用一1K電阻連接,

Python程式:

import RPi.GPIO as GPIO
import time

TRIG = 20
ECHO = 21

# init GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(TRIG, GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)
GPIO.setup(TRIG, GPIO.LOW)

time.sleep(0.1)
GPIO.output(TRIG, GPIO.HIGH)
time.sleep(0.00001)
GPIO.output(TRIG, GPIO.LOW)

echo_count = 0
while GPIO.input(ECHO) == GPIO.LOW:
  if echo_count < 1000:
    echo_count += 1
    sonarStartTime = time.time()
  else:
    exit(1)
while GPIO.input(ECHO) == GPIO.HIGH:
  sonarEndTime = time.time()
distance = (sonarEndTime - sonarStartTime) / 58 * 1000000
print("{}".format(distance))

討論:

1. HC-SR04技術文件(Ref.1)有提到距離的計算上,可以不使用推導出來的1/58作為係數,可以透過音波公式來做更精確的計算,音波會受到溫度影響而有變化,可以參考Ref.3的相關內容。
2. 距離公式中的距離係數1/58=0.01724 cm/µs = 172.4 m/s,但波傳遞的距離是從感測器至障礙物反彈再回到感測器上,實際距離是兩倍,因此公式中實際使用的音速是344.8 m/s,或空氣溫度在22.76°C下計算而得。

參考:

1. https://www.linuxnorth.org/raspi-sump/HC-SR04Users_Manual.pdf
2. https://www.linuxnorth.org/hcsr04sensor/
3. https://en.wikipedia.org/wiki/Speed_of_sound

2019年6月12日 星期三

Raspberry Pi筆記:HS420361K-32 4位數字顯示器

基本說明:

HS420361K-32可以視為4組5101BS所組成,除了共有4跟針腳分別控制四位數字,顯示LED二極體的電壓控制不大相同外,基本原理與5101BS是相同的。
HS420361K-32針腳設計,每次更新只能對其中一位數進行更新,所以讓四位數字同時有數值是不可能的。必須藉由視覺殘留原理,透過不停更新四組顯示值來達成視覺上的四位數值顯示。

接線:

硬體設計可以參閱ref.1 4.1,這邊透過74HC595來控制顯示器訊號,4組顯示部份直接連接在RPi的GPIO上
74HC595晶片
QBpin 6VCC3.3V
QCpin 4QApin 7
QDpin 2SERGPIO#16
QEpin 1OEGND
QFpin 9RCLKGPIO#20
QGpin 10SRCLKGPIO#21
QHpin 5SRCLR3.3V
GNDGNDQH'

HS420361K-32顯示器
pin 1QEpin 12GPIO#2
pin 2QDpin 11QA
pin 3QHpin 10QF
pin 4QCpin 9GPIO#3
pin 5QGpin 8GPIO#4
pin 6GPIO#17pin 7QB

Python程式:

import RPi.GPIO as GPIO
import time

SRCLK = 21
RCLK = 20
SER = 16
D1 = 2
D2 = 3
D3 = 4
D4 = 17

def init():
  GPIO.setwarnings(False)
  GPIO.setmode(GPIO.BCM)
  GPIO.cleanup()

  GPIO.setup(SRCLK, GPIO.OUT)
  GPIO.setup(RCLK, GPIO.OUT)
  GPIO.setup(SER, GPIO.OUT)
  GPIO.output(SER, GPIO.LOW)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.LOW)

  GPIO.setup(D1, GPIO.OUT)
  GPIO.setup(D2, GPIO.OUT)
  GPIO.setup(D3, GPIO.OUT)
  GPIO.setup(D4, GPIO.OUT)
  GPIO.output(D1, GPIO.HIGH)
  GPIO.output(D2, GPIO.HIGH)
  GPIO.output(D3, GPIO.HIGH)
  GPIO.output(D4, GPIO.HIGH)

def display(frag):
  for v in frag:
    if v:
      GPIO.output(SER, GPIO.HIGH)
    else:
      GPIO.output(SER, GPIO.LOW)
    GPIO.output(SRCLK, GPIO.HIGH)
    GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)

def display4Digits(number):
  num = {' ': [0, 0, 0, 0, 0, 0, 0, 0],
       '0': [0, 0, 1, 1, 1, 1, 1, 1],
       '1': [0, 0, 0, 0, 0, 1, 1, 0],
       '2': [0, 1, 0, 1, 1, 0, 1, 1],
       '3': [0, 1, 0, 0, 1, 1, 1, 1],
       '4': [0, 1, 1, 0, 0, 1, 1, 0],
       '5': [0, 1, 1, 0, 1, 1, 0, 1],
       '6': [0, 1, 1, 1, 1, 1, 0, 1],
       '7': [0, 0, 0, 0, 0, 1, 1, 1],
       '8': [0, 1, 1, 1, 1, 1, 1, 1],
       '9': [0, 1, 1, 0, 1, 1, 1, 1],
       'E': [0, 1, 1, 1, 1, 0, 0, 1],
       'F': [0, 1, 1, 1, 0, 0, 0, 1]}
  digits = {1: D1, 2: D2, 3: D3, 4: D4}
  for t in range(100):
    for i in range(4):
      d = number[i]
      display(num[d])
      GPIO.output(digits[i+1], GPIO.LOW)
      time.sleep(0.01)
      GPIO.output(digits[i+1], GPIO.HIGH)

init()
display4Digits("89EF")
GPIO.cleanup()

參考文件:

1. https://e-radionica.com/productdata/LD3361BS.pdf
2. https://theokelo.co.ke/how-to-get-your-hs420361k-32-4-digit-7-segment-display-working-with-an-arduino/

2019年6月6日 星期四

Raspberry Pi筆記:5101BS 7段數字顯示器

基本說明:

5101BS由8個LED所組成的8字顯示器與一個小數點,透過10跟針腳來控制顯示。
由於網路上搜尋不到5101BS的技術文件,這邊我參照ref.1中1.3 Common Anode 0.56 Inch的腳位說明來作相關的接線,透過74HC595控制

接線:

74HC595晶片
QBpin 6VCC3.3V
QCpin 4QApin 7
QDpin 2SERGPIO#16
QEpin 1OEGND
QFpin 9RCLKGPIO#20
QGpin 10SRCLKGPIO#21
QHpin 5SRCLR3.3V
GNDGNDQH'

5101BS顯示器
pin 1QEpin 10QG
pin 2QDpin 9QF
pin 3
pin 83.3V
pin 4QCpin 7QA
pin 5QHpin 6QB

注意5101BS各節點LED二極體是有方向性的,所以pin 3與pin 8擇一接電源就好
當pin為低電壓時,所對應的LED二極體就會發光

Python程式:


import RPi.GPIO as GPIO
import time

SRCLK = 21
RCLK = 20
SER = 16

def init():
  GPIO.setwarnings(False)
  GPIO.setmode(GPIO.BCM)
  GPIO.cleanup()

  GPIO.setup(SRCLK, GPIO.OUT)
  GPIO.setup(RCLK, GPIO.OUT)
  GPIO.setup(SER, GPIO.OUT)
  GPIO.output(SER, GPIO.LOW)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.LOW)

def display(frag):
  for v in frag:
    if v:
      GPIO.output(SER, GPIO.HIGH)
    else:
      GPIO.output(SER, GPIO.LOW)
    GPIO.output(SRCLK, GPIO.HIGH)
    GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)

init()
#       H  G  F  E  D  C  B  A
display([1, 0, 1, 0, 0, 1, 0, 0]) # display 2

數字2顯示節點為ABDEG,所以對應的點設定為低電壓形成通路。

參考文件:

1. https://e-radionica.com/productdata/LD3361BS.pdf

2019年6月5日 星期三

Raspberry Pi筆記:Python控制74HC595晶片

基本原理:

74HC595晶片透過三個輸入針腳來產生1與0給74HC595晶片,晶片會將最新輸入的值塞在8個位元的最前面,原先在晶片的值則往後位移。晶片紀錄的8個值分別透過QA~QH輸出。
輸入值的方式,是透過變動SERRCLK、SRCLK三個針腳的電壓達成。
參閱ref.1 page.2
- SRCLK電壓由 低 ➡  變化時會依據SER針腳的電壓狀態產生一個值
- 當SER為高電壓時,會產生1;當SER為低電壓時,會產生0
- 最後,當RCLK電壓由 低 ➡  變化時會將所有產生的值塞到8位元紀錄當中

接線:

QBLED 2VCC3.3V
QCLED 3QALED 1
QDLED 4SERGPIO#16
QELED 5OEGND
QFLED 6RCLKGPIO#20
QGLED 7SRCLKGPIO#21
QHLED 8SRCLR3.3V
GNDGNDQH'
74HC595允許電壓從3V到6V,可以放心使用5V作為工作電壓

Python程式:

import RPi.GPIO as GPIO
import time

SRCLK = 21
RCLK = 20
SER = 16

# init
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

GPIO.setup(SRCLK, GPIO.OUT)
GPIO.setup(RCLK, GPIO.OUT)
GPIO.setup(SER, GPIO.OUT)
GPIO.output(SER, GPIO.LOW)
GPIO.output(SRCLK, GPIO.LOW)
GPIO.output(RCLK, GPIO.LOW)

# mode 1: all 1
GPIO.output(SER, GPIO.HIGH)
for i in range(8):
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
GPIO.output(RCLK, GPIO.HIGH)
GPIO.output(RCLK, GPIO.LOW)
time.sleep(1)

# mode 2: all 0
GPIO.output(SER, GPIO.LOW)
for i in range(8):
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
GPIO.output(RCLK, GPIO.HIGH)
GPIO.output(RCLK, GPIO.LOW)
time.sleep(1)

# mode 3: A ~ G to 1 sequentially
GPIO.output(SER, GPIO.HIGH)
for i in range(8):
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)
  time.sleep(0.5)

# mode 4: A ~ G to 0 sequentially
GPIO.output(SER, GPIO.LOW)
for i in range(8):
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)
  time.sleep(0.5)

# mode 5: 0 & 1 crossing sequence
mode = GPIO.HIGH
for i in range(8):
  GPIO.output(SER, mode)
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)
  if mode == GPIO.HIGH:
    mode = GPIO.LOW
  else:
    mode = GPIO.HIGH
  time.sleep(0.5)
GPIO.output(SER, GPIO.LOW)
for i in range(8):
  GPIO.output(SRCLK, GPIO.HIGH)
  GPIO.output(SRCLK, GPIO.LOW)
  GPIO.output(RCLK, GPIO.HIGH)
  GPIO.output(RCLK, GPIO.LOW)
  time.sleep(0.5)

程式下載:

https://github.com/1412chen/rpi_learning/tree/master/74HC595N
- led.py: 控制LED交互閃爍

參考:

1. https://www.ti.com/lit/ds/symlink/cd74hc595.pdf
2. https://circuitdigest.com/microcontroller-projects/raspberry-pi-74hc595-shift-register-tutorial

2019年5月28日 星期二

Raspberry Pi筆記:DHT11溫濕度感測器

硬體安裝:

參考ref.1的Figure.1 (page 5)
在pin 2的部份與RPi的GPIO 4連接,並使用一電阻與VCC相連

python程式:

1. 快速上手方式:

在RPi已經安裝好pip的狀態下,只需要額外安裝Adafruit_DHT (ref.2)
sudo pip install Adafruit_DHT
接著,在python環境中輸入:
import Adafruit_DHT as dht

sensor = dht.DHT11
gpio = 4
print( dht.read_retry(sensor, gpio) )
可以快速得到DHT11偵測到的相對濕度與溫度:
(76.0, 29.0)
在Googled結果,可以看到許多類似的maker分享,許多是從git clone Adafruit_DHT 開始,安裝Adafruit_DHT,有興趣的可以參考ref.4。

2. 透過GPIO方式:

要透過GPIO操控DHT11,基本上要了解DHT11的運作機制 (ref.1):
RPi GPIO控制階段:
a. pin 2針腳在高電位下時,給予不少於18ms長的低電位,讓DHT11能截取到電壓變化
b. 恢復pin 2針腳回高電位
DHT11初始化階段:
c. DHT11將pin 2針腳調整為低電壓,持續80μs
d. DHT11將pin 2針腳調整為高電壓,持續80μs
DHT11訊號傳回階段:
e. DHT11會連續的將pin 2針腳,在低電壓與高電壓之間變換共40個循環;
 低電位維持50μs,高電位維持26~28μs時,表示為0
 低電位維持50μs,高電位維持70μs時,表示為1
f. DHT11將pin 2針腳調整為低電位50μs後,結束控制pin 2針腳電位控制

DHT11輸出的40個bits,共由5個8 bits所組成:
1~8 bits: 相對濕度整數部位
9~16 bits: 相對濕度小數部位
17~24 bits: 溫度整數部位
25~32 bits: 溫度小數部位
33~40 bits: checksum檢查碼,等於前4個數字的總和

在了解DHT11的訊號控制方式,可以寫相對應的GPIO控制來讀取訊號
import RPi.GPIO as GPIO
import time

PIN = 4

# initial GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# step a, b
GPIO.setup(PIN, GPIO.OUT)
GPIO.output(PIN, GPIO.LOW)
time.sleep(0.0181)
GPIO.output(PIN, GPIO.HIGH)

# step c, d
GPIO.setup(PIN, GPIO.IN, GPIO.PUD_UP)
while GPIO.input(PIN) == GPIO.LOW:
  continue
while GPIO.input(PIN) == GPIO.HIGH:
  continue

# step e
bits = []
MAXCount = 3000
for n in range(40):
  lowCount = 1
  highCount = 1
  while GPIO.input(PIN) == GPIO.LOW:
    lowCount += 1
  while GPIO.input(PIN) == GPIO.HIGH:
    highCount += 1
    if highCount > MAXCount:
      print("missing data")
      exit(1)
  if lowCount < highCount:
    bits.append(1)
  else:
    bits.append(0)

# bits to bytes
RH = 0
RHD = 0
T = 0
TD = 0
checksum = 0
for bit in bits[:8]:
  RH = (RH << 1) | bit
for bit in bits[8:16]:
  RHD = (RHD << 1) | bit
for bit in bits[16:24]:
  T = (T << 1) | bit
for bit in bits[24:32]:
  TD = (TD << 1) | bit
for bit in bits[32:40]:
  checksum = (checksum << 1) | bit

# checksum
if RH+RHD+T+TD != checksum:
  print("checksum error")
  exit(1)

print("RH: {}%, T: {}C".format(RH+0.1*RHD, T+0.1*TD))
執行結果:
RH: 76.0%, T: 29.6C

程式下載:

https://github.com/1412chen/rpi_learning/tree/master/dht11
- dht11.py: 直接呼叫Adafruit_DHT讀取溫濕度值
- dht11_gpio_direct.py: 透過GPIO讀取溫濕度值
- dht11_gpio.py: 透過GPIO讀取溫濕度值,一次讀取完DHT11電壓變化後才解析。

討論:

1. 在寫這個document的時候,我發現到使用Adafruit_DHT跑出來的結果,不會有小數點,讓我在確認GPIO結果一直充滿疑問。我想這個應該是Adafruit_DHT故意忽略小數位輸出,DHT11本身的誤差太大(相對濕度±5%,溫度±2°C),描述到小數點,確實沒有太大意義。
2. 控制GPIO擷取資料,蠻容易出現missing data與checksum error的結果。
3. 相對於Adafruit_DHT是python呼叫C的GPIO控制,ref.3中的dht11是python透過GPIO對DHT11進行操作,該程式為了避免python在擷取pin 2電位過程中額外的操作的時間差導致電位資料遺失或延遲,先將所有電位資料擷取下來後再解析。這樣也許能比較穩定得到結果,我有參照這個邏輯重新撰寫,參考程式下載的dht11_gpio.py。

參考:

1. https://www.mouser.com/ds/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
2. https://github.com/adafruit/Adafruit_Python_DHT
3. https://github.com/szazo/DHT11_Python
4. https://kknews.cc/zh-tw/digital/ea26b4q.html