【ODYSSEY-STM32MP157C】驅動 UART 讀取感測器資料

2020-10-03 12:01:43

我們在上一節《【ODYSSEY-STM32MP157C】驅動 GPIO 實現呼吸燈》 已經驅動 GPIO 實現呼吸燈功能,本節我們將在 Linux 上操作 STM32MP157C 的 UART2 串列埠與感測器進行通訊,並將感測器資料列印出來。

在這裡插入圖片描述

準備材料

PMS5003ST 簡介

PMS5003ST 是攀藤科技的一款空氣品質感測器,可以同時監測空氣中顆粒物濃度(PM1.0、PM2.5、PM10)、甲醛濃度和環境溫溼度。由於採用鐳射散射原理,因此可以連續採集,並且有較高的精度和穩定性。

在這裡插入圖片描述

同時,感測器模組以通用數位介面形式輸出,簡化了控制端的開發。

在這裡插入圖片描述

PMS5003ST 的數位介面定義如下:

管腳功能說明STM32MP157C
PIN1VCC電源正(+5V)④ 5V
PIN2GND電源負⑥ GND
PIN3SET設定管腳(3.3V)
PIN4RXD串列埠接收管腳(3.3V)⑧ PF5/USART2_TX
PIN5TXD串列埠傳送管腳(3.3V)⑩ PD6/USART2_RX
PIN6RESET模組復位訊號(3.3V,低復位)
PIN7NC-
PIN8PWM/NCPWM輸出

在本案例,我們只需要將感測器的 PIN1、PIN2、PIN4、PIN5 與 ODYSSEY-STM32MP157C 擴充套件介面的 pin 4、6、8、10 連線即可。

在這裡插入圖片描述

安裝 usart2 驅動

  1. 在編譯之前,需要下載對應版本的核心標頭檔案。

    sudo apt update
    sudo apt install linux-headers-$(uname -r) -y
    

    注意:這一步可能需要開啟代理才能完成!

  2. 下載 seeed-linux-dtoverlays 倉庫,編譯並安裝 stm32p1 驅動。

    git clone https://github.com/Seeed-Studio/seeed-linux-dtoverlays
    

    編譯、安裝

    cd seeed-linux-dtoverlays
    make all_stm32mp1 CUSTOM_MOD_FILTER_OUT="jtsn-wm8960"
    sudo make install_stm32mp1 CUSTOM_MOD_FILTER_OUT="jtsn-wm8960"
    

    對應的 ko 檔案將安裝到 /lib/modules/4.19.9-stm32-r1/extra/seeed/ 目錄,dtbo 檔案將安裝到 /lib/firmware/ 目錄。

  3. 修改 /boot/uEnv.txt 檔案,在該檔案末尾新增一行,載入 usart2 驅動。

    uboot_overlay_addr2=/lib/firmware/stm32mp1-seeed-usart2-overlay.dtbo
    
  4. reboot 重新啟動系統,可以看到系統增加了 /dev/ttySTM2 裝置節點。

    執行 dmesg | grep ttySTM* 檢視啟動資訊:

    [    0.000000] Kernel command line: console=ttySTM0,115200 root=/dev/mmcblk0p6 ro rootfstype=ext4 rootwait coherent_poot
    [    1.060447] 4000e000.serial: ttySTM2 at MMIO 0x4000e000 (irq = 25, base_baud = 4000000) is a stm32-usart
    [    1.062277] 40010000.serial: ttySTM0 at MMIO 0x40010000 (irq = 27, base_baud = 4000000) is a stm32-usart
    [    1.062366] console [ttySTM0] enabled
    [    1.064147] 5c000000.serial: ttySTM1 at MMIO 0x5c000000 (irq = 70, base_baud = 4000000) is a stm32-usart
    [    1.064420] serial serial0: tty port ttySTM1 registered
    

    執行 cat /proc/tty/driver/stm32-usart 檢視串列埠驅動:

    serinfo:1.0 driver revision:
    0: uart:stm32-usart mmio:0x40010000 irq:27 tx:1141 rx:63 RTS|CTS|DTR|DSR|CD
    1: uart:stm32-usart mmio:0x5C000000 irq:70 tx:44691 rx:2274 RTS|CTS|DTR|DSR|CD
    2: uart:stm32-usart mmio:0x4000E000 irq:25 tx:0 rx:0 CTS|DSR|CD
    

讀取感測器資料

實際上,只要我們在串列埠2連線上感測器,就可以通過 cat /dev/ttySTM2 獲取感測器的資料了。但是,這些是二進位制格式的資料,我們看不懂,因此,我們需要對其進行解析。

測試串列埠

我選擇用 Python 來程式設計,首先安裝 serial 和 pyserial 庫。

pip3 install serial
pip3 install pyserial

之後,我們可以在 Python 互動環境中進行測試:

>>> import serial
>>> port = serial.Serial('/dev/ttySTM2', 9600)
>>> port.isOpen()
True
>>> port.close()

OK,沒問題,我們就可以開始寫個解析程式了!

解析資料

新建一個 show_pms5003st.py 檔案,新增程式碼框架:

import sys
import glob
import serial
import time

dev_name = '/dev/ttySTM2'
baudrate = 9600

CMD_READ = bytearray([0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71])
CMD_PASS = bytearray([0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70])
CMD_ACTI = bytearray([0x42, 0x4d, 0xe1, 0x00, 0x01, 0x01, 0x71])
CMD_STAN = bytearray([0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73])
CMD_NORM = bytearray([0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74])

def loop(serial):
    pass


def main():
    print("Run ODYSSEY-uart demo")
    s = serial.Serial(dev_name, baudrate)

    if not s.isOpen():
        s.open()

    try:
        s.write(CMD_PASS)
    except Exception as err:
        print(err)
    finally:
        time.sleep(1)

    loop(s)
    s.close()


if __name__ == '__main__':
    main()

PMS5003ST 預設進入主動模式,即感測器會週期地主動向外傳送資料,因此我在開啟串列埠之後做的第一件事就是將其設定為被動模式,由程式主動去查詢資料。返回的資料格式如下:

在這裡插入圖片描述

由於一個有效資料由兩個位元組構成,因此我增加了一個函數來處理:

def pms_value(hByte, lByte):
    return (hByte << 8 | lByte)

然後解析的重點就在 loop 函數中啦,因為每一個有效幀都是 0x42 + 0x4d 開頭,所以只要識別出來,並且獲取幀的長度進行解析即可。為了方便(偷懶),我沒有增加狀態記錄,只有資料長度為 36 位元組的才解析,然後進行校驗,抽取有效資料並列印出來。

loop 函數程式碼如下:

def loop(serial):
    while True:
        serial.write(CMD_READ)

        start1 = serial.read(1)

        if (start1[0] == 0x42):
            start2 = serial.read(1)
            if (start2[0] == 0x4d):
                print("Is a frame")
            else:
                continue
        else:
            continue

        len1 = serial.read(1)
        len2 = serial.read(1)
        size = pms_value(len1[0], len2[0])

        if (size == 36):
            print("Is a response")
            resp = serial.read(size)

            for i in resp:
                print("{:x}".format(i), end=' ')
            print("")

            checksum = pms_value(resp[size-2], resp[size-1])

            dsum = start1[0] + start2[0] + len1[0] + len2[0]

            for i in range(0, size - 2):
                dsum = dsum + resp[i]

            dsum = dsum & 0xffff

            if (checksum != dsum):
                print("Checksum invalid. {} != {}".format(checksum, dsum))
                continue

            PM1_0_CF1  = pms_value(resp[0], resp[1])
            PM2_5_CF1  = pms_value(resp[2], resp[3])
            PM10_0_CF1 = pms_value(resp[4], resp[5])
            PM1_0_atm  = pms_value(resp[6], resp[7])
            PM2_5_atm  = pms_value(resp[8], resp[9])
            PM10_0_atm = pms_value(resp[10], resp[11])
            air_0_3um  = pms_value(resp[12], resp[13])
            air_0_5um  = pms_value(resp[14], resp[15])
            air_1_0um  = pms_value(resp[16], resp[17])
            air_2_5um  = pms_value(resp[18], resp[19])
            air_5_0um  = pms_value(resp[20], resp[21])
            air_10_0um = pms_value(resp[22], resp[23])
            hcho       = pms_value(resp[24], resp[25])
            temp       = pms_value(resp[26], resp[27])/10
            humi       = pms_value(resp[28], resp[29])/10
            version    = resp[32]
            errorCode  = resp[33]

            print("\nResponse => len: {} bytes, version: {:0>2x}, Error: {:0>2x}".format(size+4, version, errorCode))
            print("+-----------------------------------------------------+")
            print("|  CF=1  | PM1.0 = {:<4d} | PM2.5 = {:<4d} | PM10  = {:<4d} |".format(PM1_0_CF1, PM2_5_CF1, PM10_0_CF1))
            print("|  atm.  | PM1.0 = {:<4d} | PM2.5 = {:<4d} | PM10  = {:<4d} |".format(PM1_0_atm, PM2_5_atm, PM10_0_atm))
            print("|        | 0.3um = {:<4d} | 0.5um = {:<4d} | 1.0um = {:<4d} |".format(air_0_3um, air_0_5um, air_1_0um))
            print("|        | 2.5um = {:<4d} | 5.0um = {:<4d} | 10um  = {:<4d} |".format(air_2_5um, air_5_0um, air_10_0um))
            print("| extra  | hcho  = {:<4d} | temp  = {:<.1f} | humi  = {:<.1f} |".format(hcho, temp, humi))
            print("+-----------------------------------------------------+\n")

        time.sleep(3)

執行效果

執行 python3 show_pms5003st.py,執行情況如下:

在這裡插入圖片描述

非常棒,我們已經得到感測器資料了,下一節我們將學習如何將這些資料上傳到雲端。


在這裡插入圖片描述