使用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 }
问题分析与解决
核心问题
- 空缓冲区导致读取无效:
get_current_telemetry中声明的rx是空Vec<u8>,Rust的Readtrait要求缓冲区必须有可写入空间,此时read方法无法接收任何数据,直接进入阻塞等待直至超时。而Python的pyserial会默认处理空缓冲区的读取逻辑。 - 停止位配置未恢复:
ack函数中将停止位改为StopBits::One后未恢复为初始的StopBits::Two,导致后续多字节通信的帧格式不匹配,外设无法正确发送完整响应。
修复步骤
预分配多字节响应缓冲区
修改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 }恢复停止位配置
在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"); }处理部分读取场景
所有读取逻辑中建议判断实际读取的字节数,不要假设一定会读到完整长度,避免因外设分段发送数据导致的超时问题。
内容的提问来源于stack exchange,提问作者VirusModulePointer
相关产品推荐
相关产品推荐

