我們在上一節《【ODYSSEY-STM32MP157C】驅動 GPIO 實現呼吸燈》 已經驅動 GPIO 實現呼吸燈功能,本節我們將在 Linux 上操作 STM32MP157C 的 UART2 串列埠與感測器進行通訊,並將感測器資料列印出來。
PMS5003ST 是攀藤科技的一款空氣品質感測器,可以同時監測空氣中顆粒物濃度(PM1.0、PM2.5、PM10)、甲醛濃度和環境溫溼度。由於採用鐳射散射原理,因此可以連續採集,並且有較高的精度和穩定性。
同時,感測器模組以通用數位介面形式輸出,簡化了控制端的開發。
PMS5003ST 的數位介面定義如下:
管腳 | 功能 | 說明 | STM32MP157C |
---|---|---|---|
PIN1 | VCC | 電源正(+5V) | ④ 5V |
PIN2 | GND | 電源負 | ⑥ GND |
PIN3 | SET | 設定管腳(3.3V) | |
PIN4 | RXD | 串列埠接收管腳(3.3V) | ⑧ PF5/USART2_TX |
PIN5 | TXD | 串列埠傳送管腳(3.3V) | ⑩ PD6/USART2_RX |
PIN6 | RESET | 模組復位訊號(3.3V,低復位) | |
PIN7 | NC | - | |
PIN8 | PWM/NC | PWM輸出 |
在本案例,我們只需要將感測器的 PIN1、PIN2、PIN4、PIN5 與 ODYSSEY-STM32MP157C 擴充套件介面的 pin 4、6、8、10 連線即可。
在編譯之前,需要下載對應版本的核心標頭檔案。
sudo apt update
sudo apt install linux-headers-$(uname -r) -y
注意:這一步可能需要開啟代理才能完成!
下載 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/ 目錄。
修改 /boot/uEnv.txt 檔案,在該檔案末尾新增一行,載入 usart2 驅動。
uboot_overlay_addr2=/lib/firmware/stm32mp1-seeed-usart2-overlay.dtbo
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
,執行情況如下:
非常棒,我們已經得到感測器資料了,下一節我們將學習如何將這些資料上傳到雲端。