我们在上一节《【ODYSSEY-STM32MP157C】驱动 GPIO 实现呼吸灯》 已经驱动 GPIO 实现呼吸灯功能,本节我们将在 Linux 上操作 STM32MP157C 的 UART2 串口与传感器进行通信,并将传感器数据打印出来。
准备材料
- Seeed:ODYSSEY-STM32MP157C 开发板
- 攀藤:PMS5003ST 传感器
PMS5003ST 简介
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 连接即可。
安装 usart2 驱动
-
在编译之前,需要下载对应版本的内核头文件。
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 timedev_name = '/dev/ttySTM2'
baudrate = 9600CMD_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):passdef 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:continueelse:continuelen1 = 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 & 0xffffif (checksum != dsum):print("Checksum invalid. {} != {}".format(checksum, dsum))continuePM1_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])/10humi = pms_value(resp[28], resp[29])/10version = 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
,运行情况如下:
非常棒,我们已经得到传感器数据了,下一节我们将学习如何将这些数据上传到云端。