C#串口程序如何实现发送命令后等待应答再定时重发
问题分析
原有实现采用固定周期5秒的Timer触发发送,不会等待串口应答,会出现命令发送间隔不受设备响应速度影响、甚至应答还没返回就发下一条命令的问题,不符合「发送命令→等待接收完整应答→间隔5秒发送下一条命令」的逻辑。
核心实现逻辑
- 取消Timer的固定周期发送逻辑,将其改为单次触发的间隔控制器:仅当完整接收到设备应答后,才启动5秒Timer,计时结束后发送下一条命令
- 增加等待应答状态标记,避免重复触发发送逻辑
- 增加串口接收缓存,解决串口半包、粘包问题
- 增加超时兜底机制,设备无应答时不会卡死整个循环,超时后自动进入下一轮发送等待
完整修改代码
首先补充需要引入的命名空间(如果未提前添加):
using System.Text;
窗体类内的具体修改代码如下:
private Timer _timer; // 标记当前是否处于等待设备应答状态 private bool _isWaitingResponse = false; // 串口接收缓存,处理半包/粘包问题 private StringBuilder _receiveBuffer = new StringBuilder(); // 应答超时时间(毫秒),可根据设备实际响应速度调整 private const int ResponseTimeoutMs = 3000; // 超时兜底定时器 private System.Threading.Timer _responseTimeoutTimer; public Form1() { InitializeComponent(); _timer = new Timer(); _timer.Interval = 5000; _timer.Tick += _timer_Tick; // 初始化超时定时器,默认不启动 _responseTimeoutTimer = new System.Threading.Timer(ResponseTimeoutCallback, null, Timeout.Infinite, Timeout.Infinite); } private void _timer_Tick(object sender , EventArgs e) { // 触发发送时立刻停止Timer,避免重复触发 _timer.Stop(); SendData(); } private void Form1_Load(object sender, EventArgs e) { // 窗体加载后直接发起第一次命令发送 SendData(); } private void btnSendData_Click(object sender, EventArgs e) { // 未在等待、定时器未运行时,手动触发一次发送 if (!_timer.Enabled && !_isWaitingResponse) { SendData(); } else { // 停止自动发送循环 _timer.Stop(); _responseTimeoutTimer.Change(Timeout.Infinite, Timeout.Infinite); _isWaitingResponse = false; } } private void SendData() { if (serialPort1.IsOpen && !_isWaitingResponse) { // 发送前清空接收缓存 _receiveBuffer.Clear(); // 注意:协议中的<CR>代表回车符(ASCII 13),如果你的设备需要接收字面字符串"<CR>",请改回 "P<CR>" string dataOut = "P\r"; serialPort1.Write(dataOut); // 标记为等待应答状态 _isWaitingResponse = true; // 启动超时计时 _responseTimeoutTimer.Change(ResponseTimeoutMs, Timeout.Infinite); } } private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 非等待应答状态下收到的数据不参与自动循环处理,可按需自行处理 if (!_isWaitingResponse) return; // 将当前收到的数据追加到缓存 _receiveBuffer.Append(serialPort1.ReadExisting()); string receivedData = _receiveBuffer.ToString(); // ============================================== // 请替换为你设备实际的应答结束判断规则! // 常见判断规则:收到固定结束符(如\r\n)、收到固定长度数据、返回内容包含指定关键字 bool isResponseComplete = receivedData.EndsWith("\r\n") || receivedData.Contains("OK"); // ============================================== if (isResponseComplete) { // 停止超时计时 _responseTimeoutTimer.Change(Timeout.Infinite, Timeout.Infinite); // 跨线程更新UI dataIn = receivedData; this.Invoke(new EventHandler(ShowData)); // 重置等待状态 _isWaitingResponse = false; // 启动5秒定时器,计时结束后发送下一条命令 _timer.Start(); } } private void ShowData(object sender, EventArgs e) { tBoxDataIn.Text += dataIn; } // 应答超时回调 private void ResponseTimeoutCallback(object state) { this.BeginInvoke((Action)(() => { _isWaitingResponse = false; tBoxDataIn.Text += "[提示] 设备应答超时,5秒后将重试发送\r\n"; // 超时后启动5秒定时器,准备下一次发送 _timer.Start(); })); }
注意事项
- 必须根据自身设备的串口协议修改应答完成判断逻辑,否则会出现应答未接收完整就提前发送下一条、或一直等待不发送下一条的问题
- 超时时间建议设置为设备正常最大响应时长的1.2-1.5倍,避免正常响应被误判为超时
- 若串口通信存在奇偶校验、波特率不匹配等硬件配置问题,该逻辑无法正常工作,请先确认串口基础配置正确
内容的提问来源于stack exchange,提问作者Saba Soltani
相关产品推荐
相关产品推荐

