macOS下Arduino串口通信仅在minicom运行时可用的问题排查
macOS(Apple Silicon M3 Max)下Arduino串口通信异常问题
问题详情
- 环境:macOS(Apple Silicon M3 Max)、Arduino、Rust(Tauri)、Python
- 串口路径:
/dev/cu.usbmodem101 - 波特率:9600
- 现象:
- 未运行minicom时:脚本发送消息后Arduino的RX灯亮起,但内置LED闪烁3次(非预期行为)
- 运行minicom时:脚本可正常发送消息,Arduino执行预期动作
- Arduino IDE的SerialMonitor发送正确JSON时,无需minicom也可正常工作
相关代码
Rust(Tauri应用)代码片段
// Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use serialport::SerialPort; use std::time::Duration; use serde::Serialize; #[cfg(feature = "hardware-support")] use std::fs::File; #[tauri::command] fn get_temperature() -> Result<f32, String> { #[cfg(feature = "hardware-support")] { // Path to the temperature sensor's device file let path = "/sys/bus/w1/devices/28-01191ee2142f/w1_slave"; // Replace '28-xxxx' with your sensor's ID let mut file = File::open(path).map_err(|e| e.to_string())?; // Convert the error to a String; let mut contents = String::new(); file.read_to_string(&mut contents).map_err(|e| e.to_string())?; // The temperature data is usually at the end of the second line, after 't=' let temp_str = contents.split_whitespace().last().unwrap().split("=").nth(1).unwrap(); let temp_raw: i32 = temp_str.parse().unwrap(); // Convert raw temperature to Celsius Ok(temp_raw as f32 / 1000.0) } #[cfg(not(feature = "hardware-support"))] { println!("Hardware support is not enabled. Temp running in stub mode."); Ok(69.69) } } #[derive(Serialize)] struct BlinkData { blinks: u8, } #[derive(Serialize)] struct BlinkMessage { message_type: String, data: BlinkData, } #[tauri::command] fn send_blink_message(port_name: &str, num_blinks: u8) -> Result<(), String> { // Open the serial port let mut port = serialport::new(port_name, 9600).open().expect("Failed to open port"); // Create the JSON message let message = BlinkMessage { message_type: String::from("blink_led"), data: BlinkData { blinks: num_blinks }, }; // Serialize the message to JSON let json_message = match serde_json::to_string(&message) { Ok(json) => json, Err(e) => return Err(format!("Failed to serialize message: {}", e)), }; // Send the JSON message over the serial port if let Err(e) = port.write_all(json_message.as_bytes()) { return Err(format!("Failed to write to port: {}", e)); } // Print the JSON message to the console for debugging println!("JSON Message Sent: {}", json_message); // Add a newline to indicate the end of the message if let Err(e) = port.write_all(b"\n") { return Err(format!("Failed to write newline to port: {}", e)); } Ok(()) } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![get_temperature, send_blink_message]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
Python代码示例
import serial import json import time def configure_serial_port(port_name): ser = serial.Serial( port=port_name, baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=10, xonxoff=False, rtscts=False, dsrdtr=False ) ser.dtr = False ser.rts = False ser.close() def send_blink_message(port_name, num_blinks): try: # Configure the serial port settings configure_serial_port(port_name) # Open the serial port ser = serial.Serial(port_name, 9600, timeout=10) # Add a short delay to ensure the Arduino is ready to receive the message time.sleep(0.1) # Create the JSON message message = { "message_type": "blink_led", "data": {"blinks": num_blinks} } # Serialize the message to JSON json_message = json.dumps(message) # Print the JSON message for debugging print("JSON Message:", json_message) # Send the JSON message over the serial port print("Sending JSON message...") ser.write(json_message.encode('utf-8')) # Add a newline to indicate the end of the message print("Sending newline...") ser.write(b"\n") # Flush the port to ensure all data is sent print("Flushing port...") ser.flush() # Add a delay to ensure everything is sent before exiting the function time.sleep(0.1) print("Message sent to Arduino") ser.close() return True except serial.SerialException as e: print(f"Failed to open port: {e}") return False if __name__ == "__main__": port_name = "/dev/cu.usbmodem101" # Change this to your port num_blinks = 3 # Set the number of blinks send_blink_message(port_name, num_blinks)
Arduino代码
#include <ArduinoJson.h> // Define the built-in LED pin const int ledPin = LED_BUILTIN; void setup() { // Initialize the built-in LED pin as an output pinMode(ledPin, OUTPUT); // Start the serial communication at 9600 baud rate Serial.begin(9600); } void loop() { // Check if there is any data available on the serial port if (Serial.available() > 0) { // Read the incoming serial data String incomingMessage = Serial.readStringUntil('\n'); // Parse the JSON message StaticJsonDocument<200> jsonDoc; DeserializationError error = deserializeJson(jsonDoc, incomingMessage); // Check if parsing was successful if (!error) { // Extract the message type and blinks value const char* message_type = jsonDoc["message_type"]; int blinks = jsonDoc["data"]["blinks"]; // Check if the message type is 'blink_led' if (strcmp(message_type, "blink_led") == 0) { // Blink the LED the specified number of times for (int i = 0; i < blinks; i++) { digitalWrite(ledPin, HIGH); // Turn the LED on delay(500); // Wait for 500 milliseconds digitalWrite(ledPin, LOW); // Turn the LED off delay(500); // Wait for 500 milliseconds } } } else { // If parsing failed, print an error message Serial.println("Failed to parse JSONS"); } } }
核心疑问
- 为何脚本仅在minicom运行时才能正常与Arduino通信?
- minicom设置了哪些特定配置或状态需要在脚本中复现?
- 如何调整Rust和Python代码,使其无需minicom即可正常通信?
问题解答
1. 问题根源
Arduino的USB串口会被DTR/RTS信号的下降沿触发自动复位。你的脚本直接打开串口时,触发了Arduino复位,此时脚本发送数据时设备还在初始化阶段,导致数据接收不完整或乱码,JSON解析失败,最终出现非预期行为。而minicom和Arduino IDE的SerialMonitor会正确处理DTR/RTS信号,要么避免触发复位,要么等待设备初始化完成后再发送数据。
2. minicom的关键配置
minicom默认会控制DTR信号避免触发不必要的复位,并且会等待串口稳定后再通信。此外,它会确保串口参数(波特率、奇偶校验等)与Arduino完全匹配,但你的脚本已经设置了正确参数,核心差异是DTR/RTS信号处理和设备初始化等待时间。
3. 代码调整方案
Python代码修改
控制DTR信号并添加足够的初始化等待时间:
def send_blink_message(port_name, num_blinks): try: # 直接配置并打开串口,避免重复操作 ser = serial.Serial( port=port_name, baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=10, xonxoff=False, rtscts=False, dsrdtr=False ) # 控制DTR信号,等待Arduino完成初始化 ser.dtr = False time.sleep(0.5) ser.dtr = True time.sleep(2) # Arduino复位后需要约2秒完成初始化 # 构造并发送消息 message = { "message_type": "blink_led", "data": {"blinks": num_blinks} } json_message = json.dumps(message) print("JSON Message:", json_message) ser.write(json_message.encode('utf-8')) ser.write(b"\n") ser.flush() print("Message sent to Arduino") ser.close() return True except serial.SerialException as e: print(f"Failed to open port: {e}") return False
Rust代码修改
使用serialport库控制DTR信号并添加等待:
#[tauri::command] fn send_blink_message(port_name: &str, num_blinks: u8) -> Result<(), String> { // 明确配置串口参数并打开 let mut port = serialport::new(port_name, 9600) .data_bits(serialport::DataBits::Eight) .parity(serialport::Parity::None) .stop_bits(serialport::StopBits::One) .timeout(Duration::from_secs(10)) .open() .map_err(|e| format!("Failed to open port: {}", e))?; // 控制DTR信号,等待Arduino初始化完成 port.set_dtr(false).map_err(|e| format!("Failed to set DTR: {}", e))?; std::thread::sleep(Duration::from_millis(500)); port.set_dtr(true).map_err(|e| format!("Failed to set DTR: {}", e))?; std::thread::sleep(Duration::from_secs(2)); // 构造并发送消息 let message = BlinkMessage { message_type: String::from("blink_led"), data: BlinkData { blinks: num_blinks }, }; let json_message = serde_json::to_string(&message) .map_err(|e| format!("Failed to serialize message: {}", e))?; port.write_all(json_message.as_bytes()) .map_err(|e| format!("Failed to write to port: {}", e))?; println!("JSON Message Sent: {}", json_message); port.write_all(b"\n") .map_err(|e| format!("Failed to write newline to port: {}", e))?; port.flush() .map_err(|e| format!("Failed to flush port: {}", e))?; Ok(()) }
额外建议
- 尝试使用
/dev/tty.usbmodem101替代/dev/cu.usbmodem101,macOS下部分设备对tty端口兼容性更好。 - 在Arduino的
setup函数末尾添加Serial.println("ready");,脚本先接收该消息再发送指令,可更准确判断设备就绪状态。
内容的提问来源于stack exchange,提问作者Jason Hill
相关产品推荐
相关产品推荐

