Repository navigation
[macOS] macOS 下第一次通过串口连接esp32单片机读到了固件默认打印信息导致握手必失败第二次正常 #56
Description
Activity
免费的古法网页对话 GPT 只能把问题排查到这里了,临时的解决方案是跳过握手,直接将串口当作已连接的设备使用。
替换文件
/src/EasyCon.Device/Connection/TTLSerialClients.csusing System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO.Ports; using static EasyDevice.Connection.SerialPortClient; namespace EasyDevice.Connection; internal class TTLv2SerialClient(string name, int port) : IConnection { private bool _forceConnected = false; private SerialPortClient _serialPortClient = new(name, port); public override event BytesTransferedHandler BytesSent; public override event BytesTransferedHandler BytesReceived; public override event StatusChangedHandler StatusChanged; public override Status CurrentStatus { get { if (_forceConnected) return Status.Connected; return _serialPortClient.CurrentState switch { ConnectionState.Connected => Status.Connected, ConnectionState.Connecting => Status.Connecting, _ => Status.Error }; } protected set => throw new NotImplementedException(); } public override void Connect() { _serialPortClient.DataReceived += (sender, args) => { BytesReceived?.Invoke(_serialPortClient.ConnectPort, args.Data); }; Task.Run(() => { try { Console.WriteLine( $"[{_serialPortClient.ConnectPort}] 准备打开串口..." ); if (!_serialPortClient.Open()) { throw new Exception("串口打开失败"); } Console.WriteLine( $"[{_serialPortClient.ConnectPort}] 串口打开成功" ); // ======================================================== // 临时测试:跳过 EasyCon 的 A5 A5 81 -> 80 握手 // 只要串口能够成功打开,就认为设备已经连接 // ======================================================== _forceConnected = true; Console.WriteLine( $"[{_serialPortClient.ConnectPort}] 跳过握手,强制认为已连接" ); StatusChanged?.Invoke(Status.Connected); } catch (Exception ex) { Console.WriteLine(ex); _forceConnected = false; StatusChanged?.Invoke(Status.Error); } }); } public override void Disconnect() { _serialPortClient.Close(); } public override void Write(params byte[] val) { BytesSent?.Invoke(_serialPortClient.ConnectPort, val); _serialPortClient.SendCommand(val); } public override void ClearQueue() { // SerialPortClient 无发送队列,空实现 } } class TTLSerialClient : IConnection { readonly string _connStr; readonly int _port; SerialPort _sport; readonly List<byte> _inBuffer = new(); readonly ConcurrentQueue<byte[]> _outQueue = new(); DateTime _time = DateTime.MinValue; Status _status = Status.Connecting; public override event BytesTransferedHandler BytesSent; public override event BytesTransferedHandler BytesReceived; public override event StatusChangedHandler StatusChanged; Task _t; CancellationTokenSource source; TimeSpan _Timeout => TimeSpan.FromMilliseconds(Timeout); public double Timeout { get; set; } = 200; public bool Connected { get; protected set; } public override Status CurrentStatus { get => _status; protected set { if (_status == value) return; _status = value; Task.Run(() => StatusChanged?.Invoke(_status)); } } public TTLSerialClient(string connStr, int port = 115200) { _connStr = connStr; _port = port; } public override void Connect() { if (_t != null) return; _sport = new SerialPort(_connStr, _port); source = new CancellationTokenSource(); var token = source.Token; _t = Task.Run(() => { Loop(); }, token); } public override void Disconnect() { source?.Cancel(); Connected = false; ClearQueue(); } void Loop() { try { byte[] inBuffer = new byte[2550]; _sport.Open(); Console.WriteLine($"[{_connStr}] 串口打开成功"); // ======================================================== // 临时 macOS 测试模式: // 只要串口能够成功打开,就直接认为设备已连接。 // 完全跳过 A5 A5 81 -> 80 握手。 // ======================================================== Connected = true; CurrentStatus = Status.Connected; Console.WriteLine( $"[{_connStr}] 跳过 A5 A5 81 -> 80 握手,强制认为设备已连接" ); StatusChanged?.Invoke(Status.Connected); var outBuffer = new List<byte>(); while (!source.IsCancellationRequested) { // ==================================================== // read // ==================================================== if (_sport.BytesToRead > 0) { int count = _sport.Read( inBuffer, 0, inBuffer.Length ); #if DEBUG Debug.WriteLine( $"[{_connStr}] " + string.Join( " ", inBuffer .Take(count) .Select(b => b.ToString("X2")) ) ); #endif lock (_inBuffer) { _inBuffer.Clear(); _inBuffer.AddRange( inBuffer.Take(count) ); } BytesReceived?.Invoke( _connStr, _inBuffer.ToArray() ); } // ==================================================== // write // ==================================================== outBuffer.Clear(); while (_outQueue.TryDequeue(out var item)) { outBuffer.AddRange(item); } if (outBuffer.Count > 0) { var bytes = outBuffer.ToArray(); _sport.Write( bytes, 0, bytes.Length ); BytesSent?.Invoke( _connStr, bytes ); } Thread.Sleep(1); } } catch (Exception ex) { Console.WriteLine( $"[{_connStr}] 串口异常: {ex}" ); Connected = false; CurrentStatus = Status.Error; StatusChanged?.Invoke(Status.Error); } finally { _sport.Close(); } } public override void Write(params byte[] val) { if (_t == null) return; #if DEBUG Debug.WriteLine("Output: " + string.Join(" ", val.Select(b => b.ToString("X2")))); #endif _outQueue.Enqueue(val); _time = DateTime.Now; } public override void ClearQueue() { while (_outQueue.TryDequeue(out _)) { } } }
这是esp固件已知问题,暂时没有很好的解决办法.
根本原因就像你说的那样,打开串口后收到了 ESP32 的启动信息,这些信息根据s3固件作者排查无法通过编译条件完全排除.
暂时的避免方法就是再连一次,让第一次把调试信息刷完第二次即可正常连接.
临时跳过握手的方案不便采纳,因为串口的通用性,只有握手协议通讯成功才会被认定单片机可用
如果还有其他好办法欢迎反馈
- changed the title
[-][macOS] macOS 下无法通过串口连接单片机,但 Windows 和独立串口测试均正常[/-][+][macOS] macOS 下第一次通过串口连接esp32单片机读到了固件默认打印信息导致握手必失败第二次正常[/+]on Sep 1, 2026 @SlenderData 正常行为串口插上之后这些固件信息就会输出掉,我怀疑macos环境下的连接串口会导致DTR/RTS跳变触发硬件复位,ESP32官方的开发板通常会不接这个引脚,所以即使不改程序也不会出现问题,你用的开发板可能包含了自动下载电路。你的python代码中加入了对DTR/RTS的禁用,所以macos下表现正常,但是你的.NET程序没有复现怀疑是某种重新链接所以没有复现。
由于我没有macos,可以尝试在.NET程序中一并禁用DTR/RTS来尝试是否可以修复这个问题,如果有必要可以连硬件流控一并关闭 @ca1e
@zhantss 感谢思路,确实是 DTR/RTS 导致的复位,关键在于撤销的先后顺序。打开串口时 DTR/RTS 会先被拉高,
SerialPort随后先撤销 DTR、再撤销 RTS,中间短暂的“RTS 高、DTR 低”状态经自动下载电路拉低 EN,ESP32 就复位了。改成先撤销 RTS、再撤销 DTR 就不会复位。@ca1e 修复已提交 PR:#67。没有跳过握手,只在非 Windows 平台调整了撤销顺序。macOS 和 Linux(树莓派 5)实测第一次连接就能握手成功,测试程序和结果都在 PR 里。
Windows 上只有插上 USB 后首次连接会复位,再连一次就能解决,影响不大,这次不处理。
- added a commit that references this issue
on Oct 2, 2026 - added a commit that references this issue
on Oct 7, 2026
环境
dev/dev/cu.usbserial-2110/dev/tty.usbserial-2110问题描述
我正在尝试在 macOS 上从源码编译运行 EasyCon 的
dev分支。Avalonia GUI 可以正常编译和运行,macOS 也可以正常识别 USB 串口。
但是 EasyCon 无法完成与 ESP32-S3 的连接握手,最终提示:
同一个 ESP32、同一个固件、同一个 USB 转串口设备:
System.IO.Ports测试程序也可以正常通信。因此目前怀疑问题出在 EasyCon 自身的串口连接/握手逻辑,而不是硬件、固件、串口驱动或 .NET 本身。
Windows 测试
同一块 ESP32 连接 Windows 后,通过 EasyCon 发送:
单片机返回:
连接正常。
macOS + Python 测试
使用 Python +
pyserial,连接:测试代码:
测试结果:
因此 macOS 下 USB 串口本身工作正常。
macOS + .NET 独立测试
为了排除 .NET 串口库的问题,我另外创建了一个最小的 .NET 10 Console 项目,只使用
System.IO.Ports。测试代码:
运行结果:
因此:
这一整条链路是正常的。
EasyCon 的实际表现
EasyCon 可以打开串口,并且可以发送:
但是无法正常完成握手。
返回信息出现了大量 ESP32-S3 ROM 启动信息,例如:
转换成 ASCII 后可以看到:
完整日志中可以看到:
也就是说,EasyCon 并不是完全收不到串口数据,而是收到了 ESP32 的启动信息以及其他数据,但没有正确完成预期的
80握手。/dev/cu.*和/dev/tty.*我分别测试了:
和:
两者结果基本一致,均无法完成 EasyCon 的握手。
其他尝试
目前已经尝试:
dev分支重新编译 EasyCon;/dev/cu.usbserial-2110;/dev/tty.usbserial-2110;A5 A5 81 -> 80,正常;System.IO.Ports测试A5 A5 81 -> 80,正常;对比结果
A5 A5 81→80A5 A5 81→80System.IO.Ports→A5 A5 81→80A5 A5 81因此目前的问题应该不是:
System.IO.Ports完全不支持 macOS而更可能与 EasyCon 的串口打开、ESP32 重启后的串口数据处理、或者握手逻辑有关。