In the previous section "[ODYSSEY-STM32MP157C] Driving GPIO to achieve breathing light", we have driven GPIO to realize the breathing light function. In this section, we will operate STM32MP157C's UART2 serial port on Linux to communicate with the sensor and print out the sensor data.
Prepare materials
- Seeed: ODYSSEY-STM32MP157C development board
- Climbing Teng: PMS5003ST sensor
Introduction to PMS5003ST
PMS5003ST is an air quality sensor of Panteng Technology, which can simultaneously monitor the concentration of particulate matter (PM1.0, PM2.5, PM10), formaldehyde concentration and ambient temperature and humidity in the air. Due to the principle of laser scattering, it can be continuously collected with high accuracy and stability.
At the same time, the sensor module outputs in the form of a universal digital interface, which simplifies the development of the control terminal.
The digital interface of PMS5003ST is defined as follows:
Pin | Features | Description | STM32MP157C |
---|---|---|---|
PIN1 | VCC | Positive power supply (+5V) | ④ 5V |
PIN2 | GND | Power negative | ⑥ GND |
PIN3 | SET | Setting pin (3.3V) | |
PIN4 | RXD | Serial port receiving pin (3.3V) | ⑧ PF5/USART2_TX |
PIN5 | TXD | Serial port sending pin (3.3V) | ⑩ PD6/USART2_RX |
PIN6 | RESET | Module reset signal (3.3V, low reset) | |
PIN7 | NC | - | |
PIN8 | PWM/NC | PWM output |
In this case, we only need to connect the PIN1, PIN2, PIN4, and PIN5 of the sensor with pins 4, 6, 8, and 10 of the ODYSSEY-STM32MP157C expansion interface.
Install usart2 driver
-
Before compiling, you need to download the kernel header file of the corresponding version.
sudo apt update sudo apt install linux-headers-$(uname -r) -y
Note: This step may need to open the proxy to complete!
-
Download the seen-linux-dtoverlays repository, compile and install the stm32p1 driver.
git clone https://github.com/Seeed-Studio/seeed-linux-dtoverlays
Compile and install
cd seeed-linux-dtoverlays make all_stm32mp1 CUSTOM_MOD_FILTER_OUT="jtsn-wm8960" sudo make install_stm32mp1 CUSTOM_MOD_FILTER_OUT="jtsn-wm8960"
The corresponding ko file will be installed in the /lib/modules/4.19.9-stm32-r1/extra/seeed/ directory, and the dtbo file will be installed in the /lib/firmware/ directory.
-
Modify the /boot/uEnv.txt file and add a line at the end of the file to load the usart2 driver.
uboot_overlay_addr2=/lib/firmware/stm32mp1-seeed-usart2-overlay.dtbo
-
reboot reboot the system, the system may see an increase of
/dev/ttySTM2
device nodes.Execution
dmesg | grep ttySTM*
View startup information:[ 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
Execution
cat /proc/tty/driver/stm32-usart
View serial port driver: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
Read sensor data
In fact, if we connect the serial port on the sensor 2, you can cat /dev/ttySTM2
obtain the sensor data. However, these are data in binary format, which we cannot understand, so we need to parse them.
Test the serial port
I choose to use Python to program, first install the serial and pyserial libraries.
pip3 install serial
pip3 install pyserial
After that, we can test in the Python interactive environment:
>>> import serial
>>> port = serial.Serial('/dev/ttySTM2', 9600)
>>> port.isOpen()
True
>>> port.close()
OK, no problem, we can start writing a parsing program!
Analytical data
Create a new show_pms5003st.py file and add the code frame:
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 enters the active mode by default, that is, the sensor will actively send data out periodically, so the first thing I do after opening the serial port is to set it to passive mode, and the program will actively query the data. The format of the returned data is as follows:
Since a valid data consists of two bytes, I added a function to handle it:
def pms_value(hByte, lByte):
return (hByte << 8 | lByte)
Then the focus of parsing is in the loop function, because every valid frame starts with 0x42
+ 0x4d
, so just recognize it and get the length of the frame for parsing. For the sake of convenience (lazy), I did not add the status record, only the data length of 36 bytes was parsed, and then verified, the valid data was extracted and printed out.
The loop function code is as follows:
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)
running result
Implementation python3 show_pms5003st.py
, the operation is as follows:
Great, we have got the sensor data, in the next section we will learn how to upload this data to the cloud.