第01天_串口扫描与开关_教程
第 1 天 · 串口扫描与开关技术栈.NET 8 WinForms System.IO.Ports目标程序启动时自动扫描并列出所有可用 COM 口点击按钮能打开 / 关闭串口预计耗时1 小时运行结果一、今日目标做出一个能发现串口、打开串口、关闭串口的小程序界面长这样┌─────────────────────────────────────────────────────┐ │ 串口号[COM3 ▼] 波特率[9600 ▼] [刷新] │ │ │ │ [打开串口] [关闭串口] │ │ │ │ 状态已打开 COM39600 bps │ └─────────────────────────────────────────────────────┘学完今天你要能回答三个问题SerialPort.GetPortNames()是干什么用的串口那 5 个参数为什么必须和下位机一致为什么要在FormClosing里CloseDispose二、新建项目步骤打开Visual Studio 2022新建项目 → 搜索“Windows Forms App”→ 选择.NET 8.0→ 命名为Day01_SerialPort安装 NuGet 包右键项目 → 管理 NuGet 程序包 → 搜索System.IO.Ports→ 安装按下面的「控件清单」拖好 6 个控件用本文档里的代码替换Form1.cs和Form1.Designer.cs注意.NET 8 中SerialPort不在框架里必须装System.IO.Ports这个 NuGet 包。.NET Framework 4.8 是自带的这是两者最大的区别。三、控件清单一共 6 个控件照着拖控件类型Name作用关键属性下拉框 ComboBoxcmbPortName选串口号DropDownStyle DropDownList下拉框 ComboBoxcmbBaudRate选波特率DropDownStyle DropDownList默认 9600按钮 ButtonbtnRefresh重新扫描 COM 口Text “刷新”按钮 ButtonbtnOpen打开串口Text “打开串口”按钮 ButtonbtnClose关闭串口Text “关闭串口”初始禁用标签 LabellblStatus显示当前状态放底部显示状态文字关于下拉框ComboBoxWinForms 工具箱 → 所有 Windows 窗体 → 找到ComboBox。它让用户从固定选项里挑一个防止手输出错。四、重点答疑下拉框设默认值这是今天最容易踩的坑单独讲清楚。问题 1在属性窗口填了Text 9600运行后没生效原因下拉框的样式设成了DropDownList。这种样式只能显示列表里已有的选项不能直接显示你手输的文字。DropDownStyle三种模式的区别模式Text 属性填值的效果DropDown能显示但不会自动加进列表DropDownList填了也不生效只允许选列表里已有的项Simple能显示但变成列表框不好看第 1 天用的是DropDownList防止手输乱码所以填Text没用。问题 2在属性窗口找不到SelectedIndex正常VS 设计器里有时不显示这个属性别找了。默认值用代码设最稳。✅ 正确做法记这一种先在代码里往列表加选项再加SelectedIndex 0// 在构造函数里InitializeComponent() 之后写cmbBaudRate.Items.AddRange(newobject[]{9600,19200,38400,57600,115200});cmbBaudRate.SelectedIndex0;// 默认显示 9600// COM 口是程序启动时扫描的RefreshComPorts();if(cmbPortName.Items.Count0)cmbPortName.SelectedIndex0;顺序不能反先Items.Add加选项再SelectedIndex 0设默认。反过来会报错或空白。设计器里能做的只有这些选中cmbBaudRate属性窗口点Items→...→ 一行一个填9600/19200/38400/57600/115200DropDownStyle设DropDownListSelectedIndex 不用管代码里写SelectedIndex 0五、完整代码5.1 Form1.csusingSystem;usingSystem.IO.Ports;// .NET 8 中 SerialPort 在这个 NuGet 包里usingSystem.Windows.Forms;namespaceDay01_SerialPort{/// summary/// 第 1 天自动扫描 COM 口 打开/关闭串口/// 目标程序启动时列出所有可用串口点击按钮能打开/关闭/// /summarypublicpartialclassForm1:Form{// 字段 /// summary/// SerialPort 对象整个窗体共用这一个实例/// 用可空类型SerialPort?表示它初始可以为 null/// /summaryprivateSerialPort?_serialPort;// 构造函数 publicForm1(){// 这一行是 VS 设计器自动生成的初始化所有拖上去的控件InitializeComponent();// ---- 手动新增的初始化代码 ----// 程序启动时自动扫描并填充 COM 口列表RefreshComPorts();// 设置窗口标题this.Text第 1 天 - 串口扫描与开关;// 初始状态下关闭串口按钮不可用还没打开呢btnClose.Enabledfalse;}// 方法 /// summary/// 扫描系统中所有可用的串口填充到 ComboBox 下拉框/// /summaryprivatevoidRefreshComPorts(){// SerialPort.GetPortNames() 返回 string[]如 { COM1, COM3, COM4 }// 每次刷新前先清空防止重复添加cmbPortName.Items.Clear();string[]portsSerialPort.GetPortNames();if(ports.Length0){// 没找到任何串口给用户一个提示cmbPortName.Items.Add(未检测到串口);cmbPortName.SelectedIndex0;lblStatus.Text状态未检测到任何串口请检查设备管理器;btnOpen.Enabledfalse;// 没串口就不能点打开}else{// 把所有 COM 口号加进下拉框cmbPortName.Items.AddRange(ports);// 默认选中第一个cmbPortName.SelectedIndex0;lblStatus.Text$状态检测到{ports.Length}个串口;btnOpen.Enabledtrue;}}// 事件点击刷新按钮 privatevoidbtnRefresh_Click(objectsender,EventArgse){// 重新扫描 COM 口RefreshComPorts();}// 事件点击打开串口按钮 privatevoidbtnOpen_Click(objectsender,EventArgse){// ---- 1. 获取用户选择的串口号 ----stringportNamecmbPortName.SelectedItem?.ToString()??;if(string.IsNullOrEmpty(portName)||portName.Contains(未检测)){MessageBox.Show(请先选择一个有效的串口号,提示,MessageBoxButtons.OK,MessageBoxIcon.Warning);return;}// ---- 2. 从下拉框读取波特率等参数 ----// cmbBaudRate 里预设了 9600/19200/38400/57600/115200intbaudRateint.Parse(cmbBaudRate.SelectedItem?.ToString()??9600);// ---- 3. 创建 SerialPort 对象 ----// 五个参数必须和下位机单片机/模块完全一致否则通信失败_serialPortnewSerialPort(portName,// 串口号如 COM3baudRate,// 波特率如 9600Parity.None,// 校验位None/Odd/Even8,// 数据位通常 8StopBits.One// 停止位通常 One);// ---- 4. 打开串口 ----try{_serialPort.Open();// 打开成功后更新 UI 状态lblStatus.Text$状态已打开{portName}{baudRate}bps;btnOpen.Enabledfalse;// 打开后不能再点打开btnClose.Enabledtrue;// 允许关闭cmbPortName.Enabledfalse;// 打开期间不允许切换串口号cmbBaudRate.Enabledfalse;btnRefresh.Enabledfalse;// 标题栏也显示一下this.Text$第 1 天 -{portName}已打开;}catch(UnauthorizedAccessException){// 串口被别的程序占用了比如串口调试助手没关MessageBox.Show($无法打开{portName}该端口可能被其他程序占用。\n请关闭其他串口工具后重试。,打开失败,MessageBoxButtons.OK,MessageBoxIcon.Error);}catch(Exceptionex){// 其他异常端口不存在、驱动问题等MessageBox.Show($打开串口时发生错误\n{ex.Message},错误,MessageBoxButtons.OK,MessageBoxIcon.Error);}}// 事件点击关闭串口按钮 privatevoidbtnClose_Click(objectsender,EventArgse){// ---- 安全检查确认对象存在且确实打开了 ----if(_serialPort!null_serialPort.IsOpen){try{// 关闭串口_serialPort.Close();// 释放资源好习惯防止下次打开时报错_serialPort.Dispose();_serialPortnull;// 更新 UI 状态lblStatus.Text状态串口已关闭;btnOpen.Enabledtrue;btnClose.Enabledfalse;cmbPortName.Enabledtrue;cmbBaudRate.Enabledtrue;btnRefresh.Enabledtrue;this.Text第 1 天 - 串口扫描与开关;}catch(Exceptionex){MessageBox.Show($关闭串口时发生错误\n{ex.Message},错误,MessageBoxButtons.OK,MessageBoxIcon.Error);}}}// 事件窗体关闭时确保释放串口 privatevoidForm1_FormClosing(objectsender,FormClosingEventArgse){// 用户点右上角 × 时如果串口还开着自动关掉// 防止下次启动程序时报端口被占用if(_serialPort!null_serialPort.IsOpen){_serialPort.Close();_serialPort.Dispose();_serialPortnull;}}}}5.2 Form1.Designer.csnamespaceDay01_SerialPort{partialclassForm1{/// summary 必需的设计器变量 /summaryprivateSystem.ComponentModel.IContainercomponentsnull;/// summary 清理资源 /summaryprotectedoverridevoidDispose(booldisposing){if(disposing(components!null)){components.Dispose();}base.Dispose(disposing);}#regionWindows 窗体设计器生成的代码privatevoidInitializeComponent(){// ---- 串口号标签 ----LabellblPortnewLabel();lblPort.AutoSizetrue;lblPort.LocationnewSystem.Drawing.Point(20,25);lblPort.Text串口号;// ---- 串口号下拉框 ----cmbPortNamenewComboBox();cmbPortName.DropDownStyleComboBoxStyle.DropDownList;cmbPortName.LocationnewSystem.Drawing.Point(80,22);cmbPortName.SizenewSystem.Drawing.Size(120,25);// ---- 波特率标签 ----LabellblBaudnewLabel();lblBaud.AutoSizetrue;lblBaud.LocationnewSystem.Drawing.Point(220,25);lblBaud.Text波特率;// ---- 波特率下拉框 ----cmbBaudRatenewComboBox();cmbBaudRate.DropDownStyleComboBoxStyle.DropDownList;cmbBaudRate.LocationnewSystem.Drawing.Point(280,22);cmbBaudRate.SizenewSystem.Drawing.Size(100,25);cmbBaudRate.Items.AddRange(newobject[]{9600,19200,38400,57600,115200});cmbBaudRate.SelectedIndex0;// 默认 9600// ---- 刷新按钮 ----btnRefreshnewButton();btnRefresh.LocationnewSystem.Drawing.Point(400,20);btnRefresh.SizenewSystem.Drawing.Size(80,28);btnRefresh.Text刷新;btnRefresh.ClicknewEventHandler(btnRefresh_Click);// ---- 打开串口按钮 ----btnOpennewButton();btnOpen.LocationnewSystem.Drawing.Point(80,70);btnOpen.SizenewSystem.Drawing.Size(120,35);btnOpen.Text打开串口;btnOpen.ClicknewEventHandler(btnOpen_Click);// ---- 关闭串口按钮 ----btnClosenewButton();btnClose.LocationnewSystem.Drawing.Point(220,70);btnClose.SizenewSystem.Drawing.Size(120,35);btnClose.Text关闭串口;btnClose.ClicknewEventHandler(btnClose_Click);// ---- 状态标签 ----lblStatusnewLabel();lblStatus.AutoSizetrue;lblStatus.LocationnewSystem.Drawing.Point(20,130);lblStatus.SizenewSystem.Drawing.Size(400,20);lblStatus.Text状态等待操作...;// ---- 窗体本身 ----this.AutoScaleDimensionsnewSystem.Drawing.SizeF(7F,17F);this.AutoScaleModeAutoScaleMode.Font;this.ClientSizenewSystem.Drawing.Size(520,180);this.Controls.Add(lblPort);this.Controls.Add(cmbPortName);this.Controls.Add(lblBaud);this.Controls.Add(cmbBaudRate);this.Controls.Add(btnRefresh);this.Controls.Add(btnOpen);this.Controls.Add(btnClose);this.Controls.Add(lblStatus);this.NameForm1;this.StartPositionFormStartPosition.CenterScreen;this.Text第 1 天 - 串口扫描与开关;this.FormClosingnewFormClosingEventHandler(Form1_FormClosing);this.ResumeLayout(false);this.PerformLayout();}#endregion// ---- 控件声明Designer 自动管理 ----privateComboBoxcmbPortNamenew();privateComboBoxcmbBaudRatenew();privateButtonbtnRefreshnew();privateButtonbtnOpennew();privateButtonbtnClosenew();privateLabellblStatusnew();}}5.3 Program.cs.NET 8 入口新建项目时自动生成不用改namespaceDay01_SerialPort{internalstaticclassProgram{/// summary/// The main entry point for the application./// /summary[STAThread]staticvoidMain(){ApplicationConfiguration.Initialize();Application.Run(newForm1());}}}5.4 Day01_SerialPort.csproj项目文件ProjectSdkMicrosoft.NET.SdkPropertyGroupOutputTypeWinExe/OutputTypeTargetFrameworknet8.0-windows/TargetFrameworkUseWindowsFormstrue/UseWindowsFormsImplicitUsingsenable/ImplicitUsingsNullableenable/Nullable/PropertyGroupItemGroupPackageReferenceIncludeSystem.IO.PortsVersion8.0.0//ItemGroup/Project六、今天重点理解 3 件事要点为什么重要SerialPort.GetPortNames()自动扫描不用用户手打 COM 口号5 个参数必须和下位机一致波特率 / 校验 / 数据位 / 停止位有一个不对就收不到数据FormClosing里CloseDispose防止程序异常退出后串口被锁下次打不开七、常见问题排查现象原因解决填了Text 9600没生效DropDownList模式只能显示列表里已有的项用代码Items.AddSelectedIndex 0找不到SelectedIndex属性VS 设计器有时不显示不用找代码里写报ArgumentOutOfRangeItems还是空的就设了SelectedIndex 0先加选项再设默认打开时报端口被占用别的程序如串口调试助手没关关掉其他串口工具下次启动打不开串口上次没Close/Dispose在FormClosing里释放找不到SerialPort类.NET 8 需要装 NuGet 包装System.IO.Ports八、明日预告第 2 天发送字符串 十六进制数据加发送文本框 发送按钮支持 ASCII 和 HEX 两种发送模式用 VSPD 虚拟串口对COM3↔COM4用另一个串口工具连 COM4 接收验证