You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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");
    }
  }
}

核心疑问

  1. 为何脚本仅在minicom运行时才能正常与Arduino通信?
  2. minicom设置了哪些特定配置或状态需要在脚本中复现?
  3. 如何调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:19:49