针对串口错误 "device reports readiness to read but returned no data (Device disconnected or multiple access on port?)" 的排查和解决方法

硬件方面
- 检查连接
- 确认串口设备(如串口线、连接的模块等)与龙芯设备之间的物理连接是否牢固,没有松动、脱落情况。尝试重新插拔串口线,确保两端都插紧。
- 查看串口线是否有破损、断裂等物理损坏迹象,如有,更换新的串口线。
- 检查设备供电
- 确保串口设备供电正常。如果是外接设备,检查其电源适配器是否正常工作,输出电压和电流是否符合设备要求。可以使用万用表测量电源适配器输出,若不正常,更换适配 器。
- 对于由龙芯设备直接供电的串口设备,检查龙芯设备的电源输出是否稳定,可查看龙芯设备电源指示灯状态,或使用相关工具监测电源输出。
- 排查设备冲突
- 确认是否有多个设备同时尝试访问该串口端口。如果有其他程序或设备在占用 /dev/ttyS3 端口,可能会导致冲突。关闭其他可能使用该串口的程序或设备,然后重新测试。
软件方面
- 权限检查
- 确保当前用户对 /dev/ttyS3 端口有足够的读写权限。在龙芯系统的终端中,使用命令
ls -l /dev/ttyS3
查看端口权限。如果权限不足,可通过sudo chmod 666 /dev/ttyS3
临时赋予读写权限(不建议长期使用这种宽泛的权限设置,生产环境可通过添加用户到 dialout 等相关组来解决权限问题,如sudo usermod -a -G dialout your_username
,然后重新登录)。
- 确保当前用户对 /dev/ttyS3 端口有足够的读写权限。在龙芯系统的终端中,使用命令
- 驱动检查与更新
- 确认串口设备的驱动程序已正确安装。在龙芯系统中,可通过查看系统日志(一般在 /var/log 目录下,如
dmesg
日志),搜索与串口设备相关的信息,看是否有驱动加载错误提示。 - 如果驱动程序版本过旧,尝试更新驱动。可到龙芯官方或设备制造商官网查找适用于龙芯平台的最新驱动程序,按照说明进行安装。
- 确认串口设备的驱动程序已正确安装。在龙芯系统中,可通过查看系统日志(一般在 /var/log 目录下,如
- 程序逻辑检查
- 检查使用串口的应用程序代码,确认打开串口的参数设置(如波特率、数据位、停止位、校验位等)是否与串口设备实际设置一致。如果设置不匹配,会导致数据读取异常。
- 排查程序中是否存在多线程或异步操作对串口访问的影响,确保对串口的读写操作是线程安全的,避免出现竞争条件导致数据读取失败。
python
import json
import time
import paho.mqtt.client as mqtt
import serial
# 配置
MQTT_BROKER = "115.28.209.116"
MQTT_PORT = 1883
MQTT_USERNAME = "bkrc"
MQTT_PASSWORD = "88888888"
CLIENT_ID = "*************"
DEVICE_ID = "*************"
TOPIC_UP = f"device/{DEVICE_ID}/up"
TOPIC_DOWN = f"device/{DEVICE_ID}/down"
SERIAL_PORT = "COM4"
BAUDRATE = 115200
TIMEOUT = 1
# 连接成功回调
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
print("已连接到MQTT服务器")
client.subscribe(TOPIC_DOWN)
else:
print(f"连接失败,错误码:{reason_code}")
def on_message(client, userdata, msg):
print(f"收到下行消息:{msg.topic} -> {msg.payload.decode()}")
# 从串口读取温度和湿度数据
def read_data_from_serial(ser):
"""从串口读取温湿度数据,格式:26.7C24.0 %RH"""
if ser and ser.is_open:
raw_data = ser.readline().decode('utf-8').strip() # 读取串口数据
if raw_data:
try:
temperature = None
humidity = None
weight = None
# # 提取温度数据(温度后面跟着 'C')
# temp_str = raw_data.split('C')[0] # 以 'C' 为分隔符提取温度
# temperature = float(temp_str) if temp_str else None
#
# # 提取湿度数据(湿度后面跟着 '%RH')
# humidity_str = raw_data.split('C')[1].split('%')[0] # 先分割 'C',然后分割 '%'
# humidity = float(humidity_str) if humidity_str else None
#
# # 提取温度数据(温度后面跟着 'C')
# weight_str = raw_data.split('g')[0] # 以 'C' 为分隔符提取温度
# weight = float(weight_str) if weight_str else None
# 判断是温度
if raw_data.endswith("C"):
temperature = float(raw_data[:-1]) # 去掉 C
# 湿度:如 24.5 %RH 或 24.5%%RH
elif "%RH" in raw_data:
humi_str = raw_data.replace("%RH", "").replace("%%RH", "").strip()
humidity = float(humi_str)
# 重量:如 25.7g(独立上报)
elif raw_data.endswith("g"):
weight = float(raw_data[:-1]) # 去掉 g
return temperature, humidity,weight
except ValueError:
print("无法解析串口数据:", raw_data)
return None, None,None
return None, None,None
# 创建MQTT客户端
client = mqtt.Client(client_id=CLIENT_ID, protocol=mqtt.MQTTv5)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
client.loop_start()
# 串口连接
try:
ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=TIMEOUT)
print(f"成功连接串口 {SERIAL_PORT}")
temperature_from_sensor = None
humidity_from_sensor = None
weight_from_sensor = None
while True:
# 获取温度和湿度数据
t, h, w = read_data_from_serial(ser)
if t is not None:
temperature_from_sensor = t
if h is not None:
humidity_from_sensor = h
if w is not None:
weight_from_sensor = w # 缓存最新重量
# 只要温湿度都有数据,就上传
if temperature_from_sensor is not None and humidity_from_sensor is not None:
data = {
"sign": DEVICE_ID,
"type": "1",
"data": {
"Temp": {"temp": str(temperature_from_sensor)},
"Humi": {"humi": str(humidity_from_sensor)},
}
}
if w is not None:
data["data"]["Weight"] = {"weight": str(weight_from_sensor)}
weight_from_sensor = None # 上传后清空缓存
client.publish(TOPIC_UP, json.dumps(data, ensure_ascii=False))
print(f"上报数据:{json.dumps(data, ensure_ascii=False)}")
# 立刻清空温湿度数据,避免重复上传同样数据
temperature_from_sensor = None
humidity_from_sensor = None
time.sleep(0.1)
except KeyboardInterrupt:
print("程序终止")
client.loop_stop()
client.disconnect()
except serial.SerialException as e:
print(f"串口错误:{str(e)}")
except Exception as e:
print(f"程序运行错误:{str(e)}")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭")
这段代码实现了一个通过 MQTT 协议将串口设备数据上传到服务器 的功能。它从串口读取温湿度和重量数据 ,然后将这些数据封装成 JSON 格式发布到 MQTT 服务器。以下是对代码的详细解释:
模块导入和配置
import json
import time
import paho.mqtt.client as mqtt
import serial
# 配置
MQTT_BROKER = "115.28.209.116"
MQTT_PORT = 1883
MQTT_USERNAME = "bkrc"
MQTT_PASSWORD = "88888888"
CLIENT_ID = "1c097bc15129e3e2ef44f88c286b4475"
DEVICE_ID = "29c9c7bf1dafd9ca"
TOPIC_UP = f"device/{DEVICE_ID}/up"
TOPIC_DOWN = f"device/{DEVICE_ID}/down"
SERIAL_PORT = "COM4"
BAUDRATE = 115200
TIMEOUT = 1
- 模块导入 :导入了处理JSON、时间、MQTT 通信和串口通信的模块。
- MQTT 配置 :设置了MQTT 服务器的地址、端口、用户名、密码以及客户端 ID 和设备 ID ,并定义了上下行消息的主题。
- 串口配置 :设置了串口的端口号、波特率和超时时间。
MQTT 回调函数
python
# 连接成功回调
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code == 0:
print("已连接到MQTT服务器")
client.subscribe(TOPIC_DOWN)
else:
print(f"连接失败,错误码:{reason_code}")
def on_message(client, userdata, msg):
print(f"收到下行消息:{msg.topic} -> {msg.payload.decode()}")
- on_connect:当成功连接到 MQTT 服务器时被调用,订阅下行消息主题。
- on_message:当收到 MQTT 服务器的消息时被调用,打印收到的消息内容。
串口数据读取函数
python
# 从串口读取温度和湿度数据
def read_data_from_serial(ser):
"""从串口读取温湿度数据,格式:26.7C24.0 %RH"""
if ser and ser.is_open:
raw_data = ser.readline().decode('utf-8').strip() # 读取串口数据
if raw_data:
try:
temperature = None
humidity = None
weight = None
# 判断是温度
if raw_data.endswith("C"):
temperature = float(raw_data[:-1]) # 去掉 C
# 湿度:如 24.5 %RH 或 24.5%%RH
elif "%RH" in raw_data:
humi_str = raw_data.replace("%RH", "").replace("%%RH", "").strip()
humidity = float(humi_str)
# 重量:如 25.7g(独立上报)
elif raw_data.endswith("g"):
weight = float(raw_data[:-1]) # 去掉 g
return temperature, humidity,weight
except ValueError:
print("无法解析串口数据:", raw_data)
return None, None,None
return None, None,None
- 该函数从串口读取一行数据,并根据数据的后缀(C、% RH、g)来判断数据类型(温度、湿度、重量)。
- 处理了可能出现的格式错误,确保数据能够被正确解析。
MQTT 客户端初始化和连接
python
# 创建MQTT客户端
client = mqtt.Client(client_id=CLIENT_ID, protocol=mqtt.MQTTv5)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
client.loop_start()
- 创建 MQTT 客户端实例,设置用户名和密码,绑定回调函数。
- 连接到 MQTT 服务器并启动后台线程处理网络通信。
串口连接和数据处理主循环
python
# 串口连接
try:
ser = serial.Serial(SERIAL_PORT, BAUDRATE, timeout=TIMEOUT)
print(f"成功连接串口 {SERIAL_PORT}")
temperature_from_sensor = None
humidity_from_sensor = None
weight_from_sensor = None
while True:
# 获取温度和湿度数据
t, h, w = read_data_from_serial(ser)
if t is not None:
temperature_from_sensor = t
if h is not None:
humidity_from_sensor = h
if w is not None:
weight_from_sensor = w # 缓存最新重量
# 只要温湿度都有数据,就上传
if temperature_from_sensor is not None and humidity_from_sensor is not None:
data = {
"sign": DEVICE_ID,
"type": "1",
"data": {
"Temp": {"temp": str(temperature_from_sensor)},
"Humi": {"humi": str(humidity_from_sensor)},
}
}
if w is not None:
data["data"]["Weight"] = {"weight": str(weight_from_sensor)}
weight_from_sensor = None # 上传后清空缓存
client.publish(TOPIC_UP, json.dumps(data, ensure_ascii=False))
print(f"上报数据:{json.dumps(data, ensure_ascii=False)}")
# 立刻清空温湿度数据,避免重复上传同样数据
temperature_from_sensor = None
humidity_from_sensor = None
time.sleep(0.1)
except KeyboardInterrupt:
print("程序终止")
client.loop_stop()
client.disconnect()
except serial.SerialException as e:
print(f"串口错误:{str(e)}")
except Exception as e:
print(f"程序运行错误:{str(e)}")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭")
- 串口连接:尝试打开指定的串口。
- 数据处理循环 :
- 不断从串口读取数据,并更新缓存的温湿度和重量数据。
- 当同时有温度和湿度数据时,将它们封装成 JSON 格式并发布到 MQTT 服务器。
- 如果有重量数据,也一并封装到 JSON 中,并在上传后清空重量缓存。
- 上传数据后清空温湿度缓存,避免重复上传相同数据。
- 异常处理:捕获键盘中断、串口异常和其他异常,确保程序能够优雅地退出,并在退出前关闭串口和 MQTT 连接。