2019年4月3日 星期三

Bridges - 讓 PC 像 Raspberry Pi 或 ESP32 一樣 連接 I2C / SPI / GPIO / UART 週邊裝置

Wei Lin
2019-4-2

[使用情境]

先說說 使用情境,合用的話再往下看:

[緣由]

  • 需要使用PC 直接連接 I2C/SPI/GPIO/UART介面的周邊裝置:
    • 最初是因為手上的一個 Python 專案,必須連接上某個 SPI 介面的裝置抓取資料來計算。
    • PC環境下的 運算能量,與強大的 Python套件資源,可以做到一些在 MicroPython + ESP32 甚至 Raspberry Pi 上無法做到的事情。
    • 但是因為 PC 上並沒有 外接的SPI介面 可供連接,必須透過轉換器。
  • 轉換器 的選擇:
    • 市面上有很多種 USB to I2C/SPI/GPIO/UART 的轉換器,常見的有 FTDI 的 FT232H (1 channel) / FT2232H (2 channels) 和 WCH 的 CH341...等數種。 FTDI 的文件與開發資源相當齊全,開發上相對容易一些。
    • 已經有很多 libraries/套件 可以用來驅動 FTDI 的晶片,但考慮跨平台的可攜性,最後選擇使用 PyFtdi,它透過 PyUSB 下達指令給 FTDI 晶片,相依的中間曾套件比較少一點。另外我們還...
  • 需要 裝置的驅動程式:
    • 可以透過 FT232H/FT2232H,與 I2C/SPI/GPIO/UART 的週邊裝置溝通,但是另外還需要 裝置專屬的驅動程式。例如要驅動 OLED display,就需要 SSD1306 (顯示面板控制 IC)的驅動程式。
    • 這些針對性的驅動程式 普遍地存在於 ESP8266 / ESP32 / Raspberry Pi 的生態圈中,許多是使用 (Mico)Python 撰寫的,有些不須修改,或者經過少許修改,就可以直接在 PC 上執行,因此可能可以重複利用,不須重新開發,但是...
  • 需要 Adapters 來轉換介面:
  • 將 PC 視同是 ESP8266 / ESP32 / Raspberry Pi:
    • 如果裝置的驅動程式可以在PC上執行,而它所面對的 SPI介面物件,行為上和 ESP32上的SPI介面物件 一模一樣,那麼對 裝置的驅動程式 而言,PC就視同是一片 ESP32,因此在特定情況下,PC就等同是一個 ESP32 的模擬器,這樣我們就可以...
  • 運用PC上強大的開發資源與除錯環境:
    • 例如在開發裝置驅動程式的時候,就可以用例如 PyCharm 之類強大的 IDE,可以設定中斷點,並隨時監看變數值,應該會相當方便。
    • 不需要插入許多 print 指令,也不需要重複地 上傳程式碼到 控制器 上,程式整潔並節省開發時間。
FT232HFT2232HCH341A

[目標]

  • 寫一套 package,要能在PC上 模擬 MicroPython+ESP8266/ESP32 和 Raspberry Pi 上的 buses 功能界面:
    • 模擬 MicroPython + ESP8266 / ESP32 環境下的:
      • machine.I2C
      • machine.SPI
      • machine.Pin
      • machine.UART
    • 模擬 Raspberry Pi 環境下的:
      • smbus2.SMbus
      • spidev.SpiDev
      • RPi.GPIO
      • PySerial.Serial
  • 要能搭配各種轉接器
    • 可同時連接多個轉換器,可擴增的 buses 數量只受到 USB bus 裝置數量上限 與 供電能力上 的限制。
  • 要能在 Windows / Linux 下通用
    • 同一套程式通用,不須做任何修改。
  • 可以在 PC / Raspberry Pi 或其他可以使用 PyFtdi 套件的機器上通用
    • 同一套程式通用,不須做任何修改。

[使用方式]

模擬 MicroPython + ESP8266 / ESP32 上的介面

# 在 MicroPython 的環境下
from machine import I2C

i2c = I2C(freq = 400000) 

# 在 PC 的環境下
from bridges.ftdi.controllers.i2c import I2cController
I2C = I2cController().I2C

i2c = I2C(freq = 400000)

# 在 MicroPython 的環境下
from machine import SPI

spi = SPI(id, baudrate = 10000000, polarity = 0, phase = 0)
spi.init()

# 在 PC 的環境下
from bridges.ftdi.controllers.spi import SpiController 
SPI = SpiController().SPI

spi = SPI(id, baudrate = 10000000, polarity = 0, phase = 0) 
spi.init()
# 在 MicroPython 的環境下
from machine import Pin 

p0 = Pin(0, Pin.OUT) 
p0.value(0)
p0.value(1)

p2 = Pin(2, Pin.IN, Pin.PULL_UP)
print(p2.value())

# 在 PC 的環境下
from bridges.interfaces.micropython.machine import Pin
from bridges.ftdi.controllers.gpio import GpioController 
machine = GpioController()  

p0 = machine.Pin(0, mode = Pin.OUT)
p0.value(0)
p0.value(1)

p2 = machine.Pin(2, mode = Pin.IN)
print(p2.value())
# 在 MicroPython 的環境下
from machine import UART

uart = UART(1, 9600) 
uart.init(9600, bits=8, parity=None, stop=1)

# 在 PC 的環境下
from bridges.ftdi.controllers.uart import UartController
UART = UartController().UART

uart = UART(1, 9600)
uart.init(9600, bits=8, parity=None, stop=1)

模擬 Raspberry Pi 上的介面

# 在 Raspberry Pi 的環境下
from smbus2 import SMBus 

bus = SMBus(1)
b = bus.read_byte_data(80, 0)
print(b) 

# 在 PC 的環境下
from bridges.ftdi.controllers.i2c import I2cController
SMBus = I2cController().SMBus

bus = SMBus(1)  # the bus number actually doesn't matter.
b = bus.read_byte_data(80, 0)
print(b) 
# 在 Raspberry Pi 的環境下
import spidev

spi = spidev.SpiDev()
spi.open(bus, device)
to_send = [0x01, 0x02, 0x03]
spi.xfer(to_send)

# 在 PC 的環境下
from bridges.ftdi.controllers.spi import SpiController
spidev = SpiController() 

spi = spidev.SpiDev()
spi.open(bus, device)
to_send = [0x01, 0x02, 0x03]
spi.xfer(to_send)

# 在 Raspberry Pi 的環境下
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD) 
GPIO.setup(6, GPIO.OUT)

# 在 PC 的環境下
from bridges.ftdi.adapters.rpi.RPi import GPIO 

GPIO.setmode(GPIO.BOARD)  # mode actuall doesn't  matter.
GPIO.setup(6, GPIO.OUT)

# 在 Raspberry Pi 的環境下
import serial
ser = serial.Serial('/dev/ttyUSB0') 

print(ser.name)        
ser.write(b'hello')     
ser.close()            

# 在 PC 的環境下
from bridges.ftdi.controllers.uart import UartController
ser = UartController().Serial() 

print(ser.name)        
ser.write(b'hello')     
ser.close()      

[測試實例]

  • Breakpoints and variables inspection 


  • Transceive LoRa packages directly from your laptop 


Notes

  • 目前主要支援 FTDI 晶片。CH341 下只有 I2C 和 GPIO 功能,無 SPI,但 UART 可直接透過 原廠的 driver 驅動
  • FTDI 晶片的限制
    • 無 IRQ。因為 FT232H/FT2232H 並沒有 "interrupt input" 類型的 endpoint,要模擬 IRQ 的功能只能用 polling 的方式,太耗費 CPU 資源。
    • 無 PWM。PyFtdi、FT232H/FT2232H 並沒有支援。
    • 同一個 channel中,GPIO 的功能無法和 I2C/UART 共存,但是可以和 SPI共存。
    • GPIO 無 pull-up / pull-down 功能

References

2018年8月3日 星期五

數款耳機 頻率響應 比較

2018年4月7日 星期六

mini cluster of ESP32



我寫了一個 package,可以透過類似 Celery Canvas 的模式 將工作分派給 由 ESP32 組成的 cluster。

中文說明


I wrote a package with which you can dispatch tasks to an ESP32 cluster in a Celery-Canvas fashion.

Read Me



2018年3月29日 星期四

ROS node on Windows



剛剛開始接觸 ROS,發現一個簡單的辦法可以在 Windows Home 版的電腦上面 跑一個 ROS 的 node,可以收發 messages,其實這樣就可以和 Linux ROS 平台上的其他 nodes 做溝通了。





 請參考:   GitHub repo.

Wei Lin 

2017年9月12日 星期二

MQTT bridged LoRa networks with MicroPython on ESP32

Wei Lin
2017/09/12

緣由


  前一陣子做了 MQTT 的實驗,最近又開始接觸 LoRa,有天想到可以把這兩項技術結合在一起,採用 蜂巢網路的模式,透過 MQTT 串聯與整合 分散在各地區的 LoRa networks。

   為了以後可能的需要,就花了一個週末的時間先把這個模型做出來,如果以後我需要佈署自己的 LoRa 網路時就可以派上用場。

  目前只有做出這個模型,沒有經過壓力測試。我手上只有四個 LoRa transceivers,所以只能用這樣兩組 gateway + node 的組成來測試。

  不 follow LoRaWAN 的規範,純粹個人實驗。

GitHub repo.



LoRa 的優點

    LoRa 最近很熱門,因為它具有 範圍大,低耗能,成本低的特點:

  • 長距: 涵蓋面積大,只需要少數 gateways 即可涵蓋頗大的地理範圍。
  • 低耗能: 靈敏度可以到 -148dbm ,可以用較低的能量傳遞較遠的距離。
  • 末端節點成本低: 單一 channel 的 transceiver 並不算貴。
  • 平行作業: 使用無線電廣播,訊號可同時傳達 多個點。



出發點與目的

    其實我比較注重 如何建構 M2M (machine to machine) 的溝通機制,主要是想利用 LoRa 低成本、低耗能的優勢,用大範圍無線通訊 作為 M2M event system 中,事件推播的機制。



LoRa 的缺點

    但是如果要在城市中佈署 LoRa 網路,也是會遇到很多缺點與困難,例如:

  • 制高點得之不易: 制高點相對稀少 且可能有額外成本。
  • gateway 設備並不是很便宜:
    • 市面上的 LoRa gateway 通常具有同時多 channels 收發訊號的功能,價格由數千元至上萬元不等,實在不算便宜。
  • 整體資料throughput 不大,因為:
    • 障礙
      • 在都市叢林中會受到障礙與干擾的影響,傳輸速率受限。
    • 環境吵雜 訊號碰撞
      • 涵蓋範圍越大,表示其中的 nodes 越多,訊號碰撞的機率越高,整體有效的資料傳輸量較容易受到限制。
      • 訊號涵蓋範圍大,如果主要是為了 downlink 的用途的話 那就太棒了,因為發送的訊息可以同時送達範圍內所有的 nodes。但如果主要的用途是要從廣大地區收集資料回來,也就是以 uplink 為主的用途,那麼 涵蓋範圍越大,nodes 數量越多 越容易發生訊號碰撞的情況。
    • 傳輸速度不高
      • 為了要傳比較遠端距離,傳輸速度不能太快,這算是 傳輸距離遠 的代價。



想法

    主要的想法是:

  • 採用 GSM 蜂巢網路的模式

    • 城市中 建物障礙多,line of sight 難得,LoRa 訊號的傳輸功率開大的話,影響範圍更大,涵蓋的 node 數量會較多,容易會有更多的訊號碰撞。
    • 不如採用 GSM 蜂巢網路的模式,功率開小一點,gateway 多一點,每個 gateway 因為功率較小,涵蓋範圍也較小,範圍內的 node 數量少,訊號碰撞的機率就會跟著比較低,傳輸速率可以提高;而且 每個 gateway 的涵蓋範圍雖然不大,但因為數量多, 所以整體涵蓋範圍也不小,也比較容易消除訊號的死角,反而比較適合在城市之中佈署。
  • 以 MQTT 作為傳遞資料的 backbone
    • MQTT 因為採用 publish / subscribe 的模式,可以做到廣播的效果,也可以針對性地傳遞資料給指定的對象,相當彈性且高效能,可以做為 在各 gateways 之間傳遞資料的backbone。
    • 一個 gateway 其實就是一個 MQTT client,可以收發 MQTT 訊息。
  • gateway as woker
    • 以 Celery 的觀點來看,一個 gateway 也是一個 worker,我們可以透過 MQTT 傳達 message 給各個 gateways,指揮它們做特定的工作。各個 gateway 之間也可以藉此機制互相溝通與合作。




作法與特色

    主要的作法與特色是:

  • 定址
    • 每個 LoRa node 或者 gateway 都有一個 EUI-64 的地址
  • 無主機
    • gateways 們會互相分享與交換資料,並無中央主機來做管理。
  • Routing table 自動產生與更新
    • LoRa node 每次發射訊號的時候,會被依據訊號強度 自動 clustering 並歸屬到最接近的 gateway,由該 gateway 負責回應 ACK 或收發其他 LoRa packet。而且 gateways 之間會隨時互相交換 routing table 資料,LoRa node 可以無隙 roaming。
    • 可以用 RSSI 來做 地理位置定位 (Geolocation)。
  • 兩種傳遞資料的模式
    • 如果 payload 中有註明 目的地 的 EUI,則會藉由 MQTT 將之 route 到離目的地最近的 gateway 才轉而以 LoRa 的訊號發送出去。
      • 一個 LoRa node 可以 "撥號" 並傳遞資料給某一個特定的 LoRa node ,只要指定對方的 EUI 地址即可,payload 會由 gateways 自動做 routing,降低對其他地區不相關的 LoRa nodes 的干擾.
    • 如果 payload 中沒有註明 目的地 的 EUI,則會藉由 MQTT 將 payload 傳送給每一個 gateway,再轉為 LoRa 訊號發射出去給各 network 中的 LoRa nodes,效果等同是做全域的廣播。因此,台中,台北,高雄若各有一個 小型的 LoRa network,可以藉由此 MQTT 平台 串聯並視為單一個 LoRa network。
  • 降低佈署 gateway 的門檻
    • 成本低,每個 gateway 本身的硬體成本 相對低很多, 志願者負擔得起。
所使用的 Gateway (ESP32 + RFM96W)


市面上也有賣這種 整合 ESP32 + SX1278 的模組,
成本可以壓低到三百元以下


    • 不需要設定,只需要連上WiFi 網路,即可成為一個gateway,服務周圍幾百公尺內的 LoRa nodes。
  • 提升資料 throughput
    • 降低碰撞的機率
      • 因為可以依據 目的地的 EUI 位址決定 MQTT route 資料的路線,所以可以避免做全域性的廣播,可以傳遞資料並且避免干擾不相關的其他地區網路。
  • MQTT 與 LoRa 雙向的資料交換
    • 可以由 外部送一個 MQTT message 給 gateway,命令其發送 LoRa 訊號並傳遞資料給特定的 node 。
    • 可以藉由 MQTT 的機制收集 末端節點透過 LoRa 訊號 所傳送出來的資料。



實驗模型說明

  • 由左到右分別是 node2, gateway2, gateway1, node1
  • 使用 SF(Spreading Factor) 隔離:  node2, gateway2 的 SF = 9,node1, gateway1 的 SF = 8。這樣可以模擬兩個距離相當遠,原本無法相通訊息的 LoRa networks。
  • node1, node2 隨機 每隔 2~4 秒傳送一個 packet 給各自的 gateway。
  • node 或 gateway,收到 packet 之後 LED燈會閃一下。
  • gateway 收到 node 所傳來的訊號之後
    • 會透過 MQTT 互相交換網路拓樸資料。
    • 由最接近的 gateway 回應 ACK。
    • 會依據所收到的 payload 中所標註的 目的地 EUI,將 payload forward 給 最接近目的地 的 gateway 做 transmit。
  • 可以由 PC端發送 MQTT message 命令某一個 gateway 傳送 message中夾帶的 payload 給任一指定 EUI位址的 LoRa node。
  • node1 是用 ESP8266 做的,LED燈在上方,有點被擋住,透過電腦螢幕的鏡面反射反而看得比較清楚。


後記
  • 2017/09/15: 
    • 打破界線,將 SF 都設為 8,所以現在四個 device 彼此都會聽到對方的 LoRa 訊號,而兩個 gateways 就必須要能判斷一個訊號是由別的 gateway 還是  node 傳過來的,並且藉由 RSSI 來隨時判斷每個 node 要由哪一個 gateway 負責對應來收發 LoRa 訊號。
  • 最近入手這種 ESP32 + SX1278 LoRa + SSD1306 OLED 整合模組,整體設計相當簡潔,目前的價格大約台幣 300元,以後可能更便宜。

      為了兼容這個模組,特別加了一個 controller_esp_ttgo_lora_oled 的 subclass,並且將有使用到的 SSD1306 driver 含括進來。 雖然腳位定義不同,但已經都封裝在 controller 的子類別 (controller_esp_ttgo_lora_oled ) 中,所以應用的時候使用者可以如常使用 controller 所定義的 interface 即可,不需要去操煩底層實作的細節。





有個 display 還是比較方便一點,也許以後可以做個 LoRa 推文顯示器




參考資料




    2017年8月11日 星期五

    SX127x (LoRa transceiver) driver for (Micro)Python on ESP8266/ESP32/Raspberry_Pi

    Wei Lin
    2017/08/11



    1. 緣由

        最近幾年 LoRa 很紅,使用這種技術的無線通訊模組 通常比較 便宜(使用免費頻段)、小、省電、通訊距離長 (~15Km),主要用來讓末端遠距的 IoT 設備能互相溝通 或 與主機交換資料。市面上的 LoRa 模組很多,最近入手的是 HopeRF 出的 RFM96W,其上搭載 SX1278 (相容)晶片體積很小 很迷人。

    RFM96W 模組的外觀如下圖,大約只有 16mm x 16mm 見方,中間的正方形 IC 就是 SX1278 (相容)晶片 。

    使用 Arduino 搭配 RFM96W 很快就可以做出一個可以收發資料的裝置,網路上有 SX127x library for Arduino 可以用,很方便。

    如果是使用 Raspberry Pi 來主控,可以用 Python 搭配 pySX127x 來驅動 RFM96W。

    但是我最近比較常用 ESP8266 + MicroPython ,pySX127x 太大了 無法在 ESP8266 上面使用,所以我只好用 Python 把 arduino-LoRa 改寫成 for  MicroPython + ESP8266 的版本。

    GitHub repo. 在這。



    2. 效果

    可以半雙工 互傳資料,很有趣,改天再來試試看能打多遠的距離。

    LoRa 半雙工 互傳資料
    使用 SX1278 + MicroPython + ESP8266 


    reboot 的時候 LED 會閃兩次
    後續每收到一個 packet 的時候 LED 會閃一下



    3. Config

    RFM96W vs. NodeMCU 接腳對應

    RFM96W pinout



    NodeMCU (ESP8266) pinout


    接腳對應


    接好大概就像這樣




    4. 使用方式

    簡單的收發測試方式:

    1. 按照上述的接腳對應方式準備兩組 NodeMCU+SX1278模組 (RFM96W)。
    2. NodeMCU (ESP8266) 燒錄 MicroPython 韌體。
    3. 由 GitHub 下載 檔案。
    4. 將下圖 DuplexCallback 資料夾中的檔案分別 全部上傳到兩組 NodeMCU上 (直接放在 根目錄之下即可)。
    5. 上電之後兩台 NodeMCU 應該就會開始互傳資料。






    5. 使用 ESP32

    ESP8266 內建 WiFi 連網的能力,這樣已經足以作為一個 Gateway 了,但是 ESP8266 的記憶體比較小 有時候不夠用,所以今天把其中一台改為使用 ESP32,程式也調整了一下 可以兼容。

    for ESP32 的 MicroPython 韌體 在這。

    NodeMCU (ESP32) pinout

    接腳對應

    接好線 大概像這樣

    哥倆好



    6. 使用 Raspberry Pi

    既然已經做到這邊,索性再多花一點時間兼容 Raspberry Pi,我是使用 Raspberry Pi Zero W。
    以前一直沒有用 Raspberry Pi 實作硬體專案,這次剛好藉這個機會熟悉一下 RPi 的硬體介面操作。



    Raspberry Pi 的 pinout


    接腳對應
    (這是對應 2017/09/02 之前的程式碼)

     (這是對應 2017/09/02 之後的程式碼)


    接好線 大概像這樣
    (這是對應 2017/09/02 之前的程式碼,
    之後已經調整 NSS和DIO0 的對應腳位)


    LoRa 半雙工 互傳資料
    ESP8266+ESP32+Raspberry Pi Zero W



    6.1 使用 Raspberry Pi 測試
    1. 要先 enable Raspberry 上的 SPI 介面。
    2. RPi 上需安裝:
      1. Python3
      2. sudo pip3 install RPi.GPIO
      3. sudo pip3 install spidev
    3. 按照上述的接腳對應方式接線。
    4. 由 GitHub 下載 檔案。
    5. 將下圖 DuplexCallback 資料夾中的檔案 全部上傳到 RPi 上的某一個資料夾中。
    6. cd 到該資料夾的位置。
    7. 在該資料夾中 執行如下的 Python 程式碼,RPi 就會開始半雙工收發資料 : 
      • sudo python3 main.py





    7. 支援雙通道 (雙 transceivers)

    看到網路上有人用 ESP8266 搭配兩個 LoRa transceivers 做成雙通道的模組,這倒是一個好主意,我也來改一下。


    接腳對應 for ESP8266


    接好線 大概像這樣

    LoRa 雙通道 on ESP8266




    接腳對應 for ESP32

    接好線 大概像這樣

    LoRa 雙通道 on ESP32




    接腳對應 for Raspberry Pi

    接好線 大概像這樣



    7.1 使用 Raspberry Pi 測試
    1. 要先 enable Raspberry 上的 SPI 介面。
    2. RPi 上需安裝:
      1. Python3
      2. sudo pip3 install RPi.GPIO
      3. sudo pip3 install spidev
    3. 按照上述的接腳對應方式接線。
    4. 由 GitHub 下載 檔案。
    5. 將所有的 *.py 檔案都複製到 RPi 上的某一個資料夾中。
    6. cd 到該資料夾中。
    7. 將 main.py 中 第 8 行的 "import test" 改為 "import test_dual_channels as test"
    8. 進入 Python3 環境:
      • sudo python3
    9. 執行如下的 Python 指令,RPi 就會開始使用兩個 RFM96W 模組接收資料 : 
      • import main

    8. 使用 TTGO 的 ESP32+LoRa+OLED 整合模組

      網路上有賣這種 ESP32 + SX1278 LoRa + SSD1306 OLED 的整合模組,整體設計相當簡潔,目前的價格大約台幣 300元,以後可能更便宜。

      為了兼容這個模組,特別加了一個 controller_esp_ttgo_lora_oled 的 subclass,並且將有使用到的 SSD1306 driver 含括進來。 雖然腳位定義不同,但已經都封裝在 controller 的子類別 (controller_esp_ttgo_lora_oled ) 中,所以應用的時候使用者可以如常使用 controller 所定義的 interface 即可,不需要去操煩底層實作的細節。







    有個 display 還是比較方便一點,也許以後可以做個 LoRa 推文顯示器





    A. MQTT bridged LoRa networks with MicroPython on ESP32

      有天想到可以用 蜂巢網路的模式,透過 MQTT 串聯分散在各地區的 LoRa networks,於是就做了 這個實驗。