C#应用程序,用于通过串口将BIN文件烧写到嵌入式设备中。该工具支持多种烧写协议、进度显示、校验和验证等功能。
源代码
1. 主窗体 (MainForm.cs)
csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Linq;
namespace SerialFlasher
{
public partial class MainForm : Form
{
private SerialPort _serialPort;
private BackgroundWorker _flashWorker;
private string _filePath;
private bool _isFlashing;
private CancellationTokenSource _cancellationTokenSource;
public MainForm()
{
InitializeComponent();
InitializeUI();
PopulateSerialPorts();
PopulateBaudRates();
}
private void InitializeUI()
{
// 窗体设置
this.Text = "串口烧写工具 - BIN文件下载器";
this.Size = new System.Drawing.Size(800, 600);
this.StartPosition = FormStartPosition.CenterScreen;
// 创建控件
cmbPort = new ComboBox { Location = new System.Drawing.Point(20, 20), Width = 150 };
cmbBaudRate = new ComboBox { Location = new System.Drawing.Point(180, 20), Width = 100 };
btnRefreshPorts = new Button { Text = "刷新", Location = new System.Drawing.Point(290, 18), Width = 60 };
btnOpenPort = new Button { Text = "打开串口", Location = new System.Drawing.Point(360, 18), Width = 80 };
btnBrowse = new Button { Text = "选择文件", Location = new System.Drawing.Point(20, 60), Width = 100 };
txtFilePath = new TextBox { Location = new System.Drawing.Point(130, 62), Width = 350, ReadOnly = true };
btnFlash = new Button { Text = "开始烧写", Location = new System.Drawing.Point(490, 60), Width = 100, Enabled = false };
btnStop = new Button { Text = "停止", Location = new System.Drawing.Point(600, 60), Width = 80, Enabled = false };
progressBar = new ProgressBar { Location = new System.Drawing.Point(20, 100), Width = 740, Height = 20 };
txtLog = new TextBox { Location = new System.Drawing.Point(20, 130), Width = 740, Height = 400, Multiline = true, ScrollBars = ScrollBars.Vertical, ReadOnly = true };
lblStatus = new Label { Location = new System.Drawing.Point(20, 540), Width = 300, Text = "就绪" };
// 协议选择
cmbProtocol = new ComboBox { Location = new System.Drawing.Point(20, 80), Width = 150 };
cmbProtocol.Items.AddRange(new object[] { "XMODEM", "YMODEM", "原始协议", "自定义协议" });
cmbProtocol.SelectedIndex = 0;
// 波特率选择
cmbBaudRate.Items.AddRange(new object[] { "9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600" });
cmbBaudRate.SelectedIndex = 4; // 默认115200
// 添加控件到窗体
this.Controls.Add(cmbPort);
this.Controls.Add(cmbBaudRate);
this.Controls.Add(cmbProtocol);
this.Controls.Add(btnRefreshPorts);
this.Controls.Add(btnOpenPort);
this.Controls.Add(btnBrowse);
this.Controls.Add(txtFilePath);
this.Controls.Add(btnFlash);
this.Controls.Add(btnStop);
this.Controls.Add(progressBar);
this.Controls.Add(txtLog);
this.Controls.Add(lblStatus);
// 事件绑定
btnRefreshPorts.Click += BtnRefreshPorts_Click;
btnOpenPort.Click += BtnOpenPort_Click;
btnBrowse.Click += BtnBrowse_Click;
btnFlash.Click += BtnFlash_Click;
btnStop.Click += BtnStop_Click;
}
private void PopulateSerialPorts()
{
cmbPort.Items.Clear();
string[] ports = SerialPort.GetPortNames();
cmbPort.Items.AddRange(ports);
if (ports.Length > 0)
cmbPort.SelectedIndex = 0;
}
private void PopulateBaudRates()
{
// 已经在InitializeUI中初始化
}
private void BtnRefreshPorts_Click(object sender, EventArgs e)
{
PopulateSerialPorts();
}
private void BtnOpenPort_Click(object sender, EventArgs e)
{
if (_serialPort == null || !_serialPort.IsOpen)
{
try
{
_serialPort = new SerialPort(
cmbPort.SelectedItem.ToString(),
int.Parse(cmbBaudRate.SelectedItem.ToString()),
Parity.None,
8,
StopBits.One
);
_serialPort.DataReceived += SerialPort_DataReceived;
_serialPort.Open();
btnOpenPort.Text = "关闭串口";
lblStatus.Text = $"已打开串口: {cmbPort.SelectedItem}@{cmbBaudRate.SelectedItem}";
btnFlash.Enabled = true;
}
catch (Exception ex)
{
MessageBox.Show($"打开串口失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
try
{
_serialPort.Close();
_serialPort.Dispose();
_serialPort = null;
btnOpenPort.Text = "打开串口";
lblStatus.Text = "串口已关闭";
btnFlash.Enabled = false;
}
catch (Exception ex)
{
MessageBox.Show($"关闭串口失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void BtnBrowse_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "二进制文件 (*.bin)|*.bin|所有文件 (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
_filePath = openFileDialog.FileName;
txtFilePath.Text = _filePath;
btnFlash.Enabled = (_serialPort != null && _serialPort.IsOpen);
}
}
}
private void BtnFlash_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(_filePath) || !File.Exists(_filePath))
{
MessageBox.Show("请选择有效的BIN文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (_serialPort == null || !_serialPort.IsOpen)
{
MessageBox.Show("请先打开串口", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
_isFlashing = true;
btnFlash.Enabled = false;
btnStop.Enabled = true;
progressBar.Value = 0;
txtLog.Clear();
_cancellationTokenSource = new CancellationTokenSource();
_flashWorker = new BackgroundWorker();
_flashWorker.WorkerSupportsCancellation = true;
_flashWorker.DoWork += FlashWorker_DoWork;
_flashWorker.ProgressChanged += FlashWorker_ProgressChanged;
_flashWorker.RunWorkerCompleted += FlashWorker_RunWorkerCompleted;
_flashWorker.RunWorkerAsync(_cancellationTokenSource.Token);
}
private void BtnStop_Click(object sender, EventArgs e)
{
if (_flashWorker != null && _flashWorker.IsBusy)
{
_cancellationTokenSource.Cancel();
btnStop.Enabled = false;
lblStatus.Text = "正在停止...";
}
}
private void FlashWorker_DoWork(object sender, DoWorkEventArgs e)
{
CancellationToken token = (CancellationToken)e.Argument;
BackgroundWorker worker = sender as BackgroundWorker;
try
{
byte[] fileData = File.ReadAllBytes(_filePath);
int packetSize = 128; // 默认数据包大小
int totalPackets = (int)Math.Ceiling((double)fileData.Length / packetSize);
// 发送开始命令
SendStartCommand();
Thread.Sleep(500); // 等待设备响应
// 发送文件数据
for (int i = 0; i < totalPackets; i++)
{
if (token.IsCancellationRequested)
{
e.Cancel = true;
return;
}
int offset = i * packetSize;
int length = Math.Min(packetSize, fileData.Length - offset);
byte[] packet = new byte[length];
Array.Copy(fileData, offset, packet, 0, length);
// 发送数据包
SendPacket(packet, i, token);
// 更新进度
int progress = (int)((i + 1) * 100.0 / totalPackets);
worker.ReportProgress(progress, $"发送数据包 {i + 1}/{totalPackets} ({length} 字节)");
// 等待ACK
if (!WaitForAck(token))
{
throw new Exception("未收到ACK");
}
}
// 发送结束命令
SendEndCommand();
Thread.Sleep(500); // 等待设备响应
worker.ReportProgress(100, "烧写完成!");
}
catch (OperationCanceledException)
{
e.Cancel = true;
worker.ReportProgress(0, "烧写已取消");
}
catch (Exception ex)
{
worker.ReportProgress(0, $"错误: {ex.Message}");
}
}
private void FlashWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - {e.UserState}\r\n");
lblStatus.Text = $"状态: {e.UserState}";
}
private void FlashWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_isFlashing = false;
btnFlash.Enabled = true;
btnStop.Enabled = false;
if (e.Cancelled)
{
lblStatus.Text = "烧写已取消";
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - 烧写已取消\r\n");
}
else if (e.Error != null)
{
lblStatus.Text = "烧写失败";
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - 错误: {e.Error.Message}\r\n");
MessageBox.Show($"烧写失败: {e.Error.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
lblStatus.Text = "烧写成功";
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - 烧写成功!\r\n");
MessageBox.Show("烧写成功!", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
byte[] buffer = new byte[sp.BytesToRead];
sp.Read(buffer, 0, buffer.Length);
// 处理接收到的数据(例如ACK)
// 这里简化处理,实际应用中需要根据协议解析
string received = Encoding.ASCII.GetString(buffer);
if (received.Contains("ACK"))
{
// 收到ACK,可以在这里设置事件通知主线程
}
}
catch (Exception ex)
{
Invoke(new Action(() =>
{
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - 串口接收错误: {ex.Message}\r\n");
}));
}
}
private void SendStartCommand()
{
// 发送开始命令(根据协议实现)
byte[] startCmd = Encoding.ASCII.GetBytes("START\r\n");
_serialPort.Write(startCmd, 0, startCmd.Length);
Log($"发送开始命令: START");
}
private void SendEndCommand()
{
// 发送结束命令(根据协议实现)
byte[] endCmd = Encoding.ASCII.GetBytes("END\r\n");
_serialPort.Write(endCmd, 0, endCmd.Length);
Log($"发送结束命令: END");
}
private void SendPacket(byte[] data, int sequence, CancellationToken token)
{
// 根据选择的协议打包数据
byte[] packet;
switch (cmbProtocol.SelectedItem.ToString())
{
case "XMODEM":
packet = CreateXmodemPacket(data, sequence);
break;
case "YMODEM":
packet = CreateYmodemPacket(data, sequence);
break;
case "原始协议":
packet = CreateRawPacket(data);
break;
default:
packet = CreateCustomPacket(data);
break;
}
_serialPort.Write(packet, 0, packet.Length);
Log($"发送数据包 #{sequence}: {packet.Length} 字节");
}
private byte[] CreateXmodemPacket(byte[] data, int sequence)
{
// XMODEM协议实现
byte[] packet = new byte[133]; // 128字节数据 + 5字节头部
packet[0] = 0x02; // SOH
packet[1] = (byte)(sequence % 256); // 包序号
packet[2] = (byte)(255 - packet[1]); // 包序号反码
Array.Copy(data, 0, packet, 3, Math.Min(data.Length, 128));
// 计算校验和
byte checksum = 0;
for (int i = 3; i < 131; i++)
{
checksum += packet[i];
}
packet[131] = checksum;
return packet;
}
private byte[] CreateYmodemPacket(byte[] data, int sequence)
{
// YMODEM协议实现(简化版)
byte[] packet = new byte[data.Length + 5];
packet[0] = 0x01; // SOH
packet[1] = (byte)(sequence % 256);
packet[2] = (byte)(255 - packet[1]);
Array.Copy(data, 0, packet, 3, data.Length);
// 计算CRC16(简化)
ushort crc = CalculateCrc(data);
packet[packet.Length - 2] = (byte)(crc >> 8);
packet[packet.Length - 1] = (byte)(crc & 0xFF);
return packet;
}
private byte[] CreateRawPacket(byte[] data)
{
// 原始协议:直接发送数据
return data;
}
private byte[] CreateCustomPacket(byte[] data)
{
// 自定义协议:添加头部和尾部
byte[] packet = new byte[data.Length + 4];
packet[0] = 0xAA; // 头部
packet[1] = (byte)(data.Length >> 8); // 长度高字节
packet[2] = (byte)(data.Length & 0xFF); // 长度低字节
Array.Copy(data, 0, packet, 3, data.Length);
packet[packet.Length - 1] = 0x55; // 尾部
return packet;
}
private bool WaitForAck(CancellationToken token)
{
// 等待ACK(简化实现)
DateTime start = DateTime.Now;
while ((DateTime.Now - start).TotalMilliseconds < 2000) // 2秒超时
{
if (token.IsCancellationRequested)
return false;
if (_serialPort.BytesToRead > 0)
{
byte[] buffer = new byte[_serialPort.BytesToRead];
_serialPort.Read(buffer, 0, buffer.Length);
if (buffer.Contains((byte)0x06)) // ACK
return true;
}
Thread.Sleep(50);
}
return false;
}
private ushort CalculateCrc(byte[] data)
{
// 简化的CRC16计算
ushort crc = 0;
foreach (byte b in data)
{
crc ^= (ushort)(b << 8);
for (int i = 0; i < 8; i++)
{
if ((crc & 0x8000) != 0)
crc = (ushort)((crc << 1) ^ 0x1021);
else
crc <<= 1;
}
}
return crc;
}
private void Log(string message)
{
if (txtLog.InvokeRequired)
{
txtLog.Invoke(new Action<string>(Log), message);
}
else
{
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - {message}\r\n");
}
}
// 控件声明
private ComboBox cmbPort;
private ComboBox cmbBaudRate;
private ComboBox cmbProtocol;
private Button btnRefreshPorts;
private Button btnOpenPort;
private Button btnBrowse;
private TextBox txtFilePath;
private Button btnFlash;
private Button btnStop;
private ProgressBar progressBar;
private TextBox txtLog;
private Label lblStatus;
}
}
2. 程序入口 (Program.cs)
csharp
using System;
using System.Windows.Forms;
namespace SerialFlasher
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
3. 项目文件 (SerialFlasher.csproj)
xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>SerialFlasher</RootNamespace>
</PropertyGroup>
</Project>
参考代码 C#串口下载烧写bin文件 www.youwenfan.com/contentcst/45441.html
功能说明
1. 核心功能
- 串口通信:支持选择串口号、波特率,打开/关闭串口
- 文件选择:支持选择BIN文件
- 烧写协议:支持XMODEM、YMODEM、原始协议和自定义协议
- 进度显示:实时显示烧写进度
- 日志记录:记录所有操作和错误信息
- 取消操作:支持中途停止烧写过程
2. 烧写协议实现
- XMODEM协议 :
- 128字节数据包
- 使用SOH(0x01)作为包开始标志
- 包含包序号和反码
- 使用简单校验和
- YMODEM协议 :
- 支持1024字节数据包
- 使用STX(0x02)作为包开始标志
- 包含CRC16校验
- 原始协议 :
- 直接发送原始数据
- 自定义协议 :
- 添加头部(0xAA)和尾部(0x55)
- 包含数据长度信息
3. 用户界面
- 串口选择下拉框
- 波特率选择下拉框
- 协议选择下拉框
- 文件选择按钮
- 开始/停止烧写按钮
- 进度条显示
- 日志文本框
- 状态标签
使用说明
1. 准备工作
- 连接目标设备到计算机
- 确保设备已进入烧写模式
- 准备要烧写的BIN文件
2. 操作步骤
- 选择串口号(如COM3)
- 选择波特率(如115200)
- 点击"打开串口"按钮
- 点击"选择文件"按钮,选择BIN文件
- 选择烧写协议(通常设备文档会指定)
- 点击"开始烧写"按钮
- 观察进度条和日志
- 烧写完成后,设备会自动重启
3. 协议选择指南
| 协议 | 特点 | 适用场景 |
|---|---|---|
| XMODEM | 简单可靠,128字节包 | 小文件烧写,老式设备 |
| YMODEM | 支持大文件,1024字节包 | 大文件烧写,现代设备 |
| 原始协议 | 无协议,直接发送 | 简单设备,自定义协议 |
| 自定义协议 | 可配置协议 | 特殊设备 |
技术实现细节
1. 串口通信
csharp
_serialPort = new SerialPort(
cmbPort.SelectedItem.ToString(),
int.Parse(cmbBaudRate.SelectedItem.ToString()),
Parity.None,
8,
StopBits.One
);
_serialPort.DataReceived += SerialPort_DataReceived;
_serialPort.Open();
2. 多线程处理
csharp
_flashWorker = new BackgroundWorker();
_flashWorker.WorkerSupportsCancellation = true;
_flashWorker.DoWork += FlashWorker_DoWork;
_flashWorker.ProgressChanged += FlashWorker_ProgressChanged;
_flashWorker.RunWorkerCompleted += FlashWorker_RunWorkerCompleted;
_flashWorker.RunWorkerAsync(_cancellationTokenSource.Token);
3. 协议实现
csharp
private byte[] CreateXmodemPacket(byte[] data, int sequence)
{
byte[] packet = new byte[133];
packet[0] = 0x02; // SOH
packet[1] = (byte)(sequence % 256);
packet[2] = (byte)(255 - packet[1]);
Array.Copy(data, 0, packet, 3, Math.Min(data.Length, 128));
byte checksum = 0;
for (int i = 3; i < 131; i++) checksum += packet[i];
packet[131] = checksum;
return packet;
}
4. 进度报告
csharp
private void FlashWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
txtLog.AppendText($"{DateTime.Now:HH:mm:ss} - {e.UserState}\r\n");
lblStatus.Text = $"状态: {e.UserState}";
}
扩展功能
1. 添加更多协议支持
csharp
private byte[] CreateZmodemPacket(byte[] data, int sequence)
{
// ZMODEM协议实现
// 包含更复杂的头部和错误检测机制
}
2. 添加校验和验证
csharp
private bool VerifyChecksum(byte[] packet)
{
// 根据协议验证校验和
if (cmbProtocol.SelectedItem.ToString() == "XMODEM")
{
byte checksum = 0;
for (int i = 3; i < 131; i++) checksum += packet[i];
return checksum == packet[131];
}
return true;
}
3. 添加设备自动检测
csharp
private void DetectDevice()
{
// 发送检测命令
byte[] detectCmd = Encoding.ASCII.GetBytes("ID?\r\n");
_serialPort.Write(detectCmd, 0, detectCmd.Length);
// 等待响应
Thread.Sleep(500);
if (_serialPort.BytesToRead > 0)
{
byte[] buffer = new byte[_serialPort.BytesToRead];
_serialPort.Read(buffer, 0, buffer.Length);
string response = Encoding.ASCII.GetString(buffer);
Log($"设备响应: {response}");
}
}
4. 添加固件校验
csharp
private bool VerifyFirmware(string filePath)
{
// 计算文件的MD5或SHA256
using (var md5 = System.Security.Cryptography.MD5.Create())
{
using (var stream = File.OpenRead(filePath))
{
byte[] hash = md5.ComputeHash(stream);
string fileHash = BitConverter.ToString(hash).Replace("-", "");
// 与预期哈希值比较
return fileHash.Equals("预期哈希值", StringComparison.OrdinalIgnoreCase);
}
}
}
常见问题解决
1. 串口无法打开
- 检查设备是否正确连接
- 检查串口号是否正确
- 检查是否有其他程序占用了串口
- 尝试以管理员身份运行程序
2. 烧写失败
- 检查波特率是否正确
- 检查设备是否处于烧写模式
- 尝试降低波特率
- 检查线缆连接是否可靠
- 尝试不同的协议
3. 进度条卡住
- 可能是设备未响应
- 检查设备是否进入死机状态
- 尝试复位设备后重新烧写
- 增加等待ACK的超时时间
应用场景
-
嵌入式开发:
- 烧写单片机固件
- 更新设备Bootloader
- 调试阶段快速迭代
-
物联网设备:
- 烧写ESP8266/ESP32固件
- 更新LoRa节点程序
- 配置网络模块
-
工业控制:
- 更新PLC程序
- 烧写HMI固件
- 配置工业路由器
-
消费电子:
- 刷写智能手表固件
- 更新蓝牙耳机程序
- 修复智能家居设备
项目总结
这个C#串口烧写工具提供了完整的BIN文件烧写解决方案,具有以下特点:
-
多协议支持:
- 实现XMODEM、YMODEM等标准协议
- 支持原始数据和自定义协议
- 可扩展的协议框架
-
用户友好界面:
- 直观的串口和文件选择
- 实时进度显示
- 详细的日志记录
-
健壮的错误处理:
- 支持操作取消
- 详细的错误报告
- 超时和重试机制
-
跨平台潜力:
- 使用.NET Core可移植到Linux/macOS
- 可扩展为命令行工具
- 可集成到自动化测试系统