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

使用serialport-rs无法接收多字节UART响应的技术问题

问题描述

使用serialport-rs进行UART通信时,单字节响应能正常接收,但多字节响应会卡在读取循环超时,Python的pyserial却能正常工作。已确认Rust和Python发送的UART帧完全一致。

现象细节

  • 初始读取阶段能获取正确帧"10000000111"
  • toggle_rte调用后能接收预期单字节响应"10010101111"
  • get_current_telemetry调用时,发送的消息格式正常,但读取8字节响应超时(Python可正常读取)
  • memblk_probe单字节请求/响应正常,设备配置变更成功

Rust代码

use std::io::{Read, Write};
use std::{io, time::Duration};
use serialport::{SerialPort, StopBits, Parity, TTYPort};

pub struct PhClient { serial_port: TTYPort, port_number: u8, poll_address: u8 }


fn main() {
    let mut rx_payload:Vec<u8>  = vec![0x00];
    let port_name = &serialport::available_ports().expect("No serial port")[0].port_name;
    let mut ph_client = PhClient {
        serial_port: serialport::new(port_name, 115200)
            .stop_bits(StopBits::Two)
            .timeout(Duration::from_secs(2))
            .open_native()
            .expect("Failed to open serial port"),
        port_number: 0x00,
        poll_address: 0x62
    };


    loop {
        match ph_client.serial_port.read(&mut rx_payload) {
            Ok(bytes) => {
                if bytes == 1 {
                    println!("receive payload: {:?}", rx_payload);
                    ph_client.port_number = rx_payload[0];
                    break;
                }
            }
            Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
            Err(e) => eprintln!("{:?}", e)
        }
    }

    let mode = toggle_rte(&mut ph_client, false);
    println!("toggle rte: {:?}", mode);

    let current_telem = get_current_telemetry(&mut ph_client);
    println!("current telemetry: {:?}", current_telem);

    let memblk_probe = symbyte(&mut ph_client);
    println!("block preset: {:?}", memblk_probe);
}

fn toggle_rte(ph: &mut PhClient, enable: bool) -> bool {
    ack(ph);
    let mut rx: [u8; 1] = [0x00];
    let mut tx: Vec<u8> = vec![ph.port_number, 0x0E, enable as u8];
    tx.extend_from_slice(&crc16(&tx).to_le_bytes());
    tx.remove(0);

    println!("toggle rte: {:?}", tx);
    ph.serial_port.write(&tx).expect("Error toggling RTE");
    ph.serial_port.read(&mut rx).expect("No response toggling RTE");

    rx[0] == ph.port_number
}

fn get_current_telemetry(ph: &mut PhClient) -> Vec<u8> {
    ack(ph);
    let mut rx: Vec<u8> = Default::default();
    let tx: [u8; 1] = [0x1A];

    println!("get current telemetry: {:?}", tx);
    ph.serial_port.write(&tx).expect("Error writing current telemetry payload");
    ph.serial_port.read(&mut rx).expect("Error reading current telemetry from host");

    rx
}

fn memblk_probe(ph: &mut PhClient) -> bool {
    ack(ph);
    let mut rx: [u8; 1] = [0x00];
    let mut tx: Vec<u8> = vec![ph.port_number, 0x01];
    tx.extend_from_slice(&crc16(&tx).to_le_bytes());
    tx.remove(0);

    println!("probe payload: {:?}", tx);
    ph.serial_port.write(&tx).expect("Error accessing memblk command");
    ph.serial_port.read(&mut rx).expect("Error reading memblk start address");

    rx[0] == ph.port_number
}

#[inline]
fn ack(ph: &mut PhClient) {
    let tx: [u8; 2] = [ph.poll_address, ph.port_number];
    ph.serial_port.set_stop_bits(StopBits::One).expect("Error setting stop bits to one");
    ph.serial_port.set_parity(Parity::Mark).expect("Error setting parity to Mark");

    ph.serial_port.write(&tx).expect("Error ACKing to target device");

    ph.serial_port.set_parity(Parity::Space).expect("Error setting parity to Space");
}

#[inline]
fn crc16(msg: &[u8]) -> u16 {
    let mut crc: u16 = 0;
    let (mut c, mut q): (u16, u16);

    for byte in msg.iter() {
        c = *byte as u16;
        q = (crc ^ c) & 0o17;
        crc = (crc >> 4) ^ (q * 0o10201);
        q = (crc ^ (c >> 4)) & 0o17;
        crc = (crc >> 4) ^ (q * 0o10201);
    }

    crc
}
问题分析与解决

核心问题

  1. 空缓冲区导致读取无效:get_current_telemetry中声明的rx是空Vec<u8>,Rust的Read trait要求缓冲区必须有可写入空间,此时read方法无法接收任何数据,直接进入阻塞等待直至超时。而Python的pyserial会默认处理空缓冲区的读取逻辑。
  2. 停止位配置未恢复:ack函数中将停止位改为StopBits::One后未恢复为初始的StopBits::Two,导致后续多字节通信的帧格式不匹配,外设无法正确发送完整响应。

修复步骤

  1. 预分配多字节响应缓冲区
    修改get_current_telemetry,预先分配对应长度的缓冲区,并处理实际读取的字节数:

    fn get_current_telemetry(ph: &mut PhClient) -> Vec<u8> {
        ack(ph);
        // 预分配8字节缓冲区,匹配预期的响应长度
        let mut rx: Vec<u8> = vec![0; 8];
        let tx: [u8; 1] = [0x1A];
    
        println!("get current telemetry: {:?}", tx);
        ph.serial_port.write(&tx).expect("Error writing current telemetry payload");
        // 读取实际收到的字节数
        let bytes_read = ph.serial_port.read(&mut rx).expect("Error reading current telemetry from host");
        // 截断缓冲区到实际读取长度
        rx.truncate(bytes_read);
    
        rx
    }
    
  2. 恢复停止位配置
    在ack函数末尾恢复停止位为初始的StopBits::Two:

    #[inline]
    fn ack(ph: &mut PhClient) {
        let tx: [u8; 2] = [ph.poll_address, ph.port_number];
        ph.serial_port.set_stop_bits(StopBits::One).expect("Error setting stop bits to one");
        ph.serial_port.set_parity(Parity::Mark).expect("Error setting parity to Mark");
    
        ph.serial_port.write(&tx).expect("Error ACKing to target device");
    
        ph.serial_port.set_parity(Parity::Space).expect("Error setting parity to Space");
        // 恢复停止位为Two
        ph.serial_port.set_stop_bits(StopBits::Two).expect("Error setting stop bits back to two");
    }
    
  3. 处理部分读取场景
    所有读取逻辑中建议判断实际读取的字节数,不要假设一定会读到完整长度,避免因外设分段发送数据导致的超时问题。


内容的提问来源于stack exchange,提问作者VirusModulePointer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 07:19:51