C#上位机开发(二) 串口使用

系列文章目录

代码下载


前言

关于C#上位机软件的制作,是我通过学习网络上的博主代码并自己进行了一些实战后总结验证以后的,一套自己的代码风格,引入了面相对象编程等思路

  1. C#上位机开发(一)—— C#上位机基础
  2. C#上位机开发(二)—— C# 串口使用
  3. C#上位机开发(三)—— C# 绘图方法
  4. C#上位机开发(四) —— C# 软件截取软件界面并保存图片
  5. C#上位机开发(五) —— C#上新建窗口并且处理 Winform高分辨率下的窗体显示模糊问题

提示:以下是本篇文章正文内容,下面案例可供参考

一、串口是什么?

在单片机项目开发中,上位机也是一个很重要的部分,主要用于数据显示(波形、温度等)、用户控制(LED,继电器等),下位机(单片机)与 上位机之间要进行数据通信的两种方式都是基于串口的: USB转串口 _ 上位机和下位机通过USB转串口连接线直接相连进行数据交互;

在这里插入图片描述

二、使用步骤

1.在界面中 增加串口的控件

在这里插入图片描述

//串口设置函数
this.serialPort1 = new System.IO.Ports.SerialPort(this.components);
//串口中断方法函数
this.serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(this.serialPort1_DataReceived);
//串口IO函数
private System.IO.Ports.SerialPort serialPort1;

2.增加自己定义的串口类来封装自己的函数:

在这里插入图片描述

2-1 创建 : Class_My_SerialPort 类

注意:
using System.Windows.Forms;
using System.IO.Ports;
引用这两个类为了引用窗体程序异常。

在这里插入图片描述
代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 新建引用
using System.Windows.Forms;
using System.IO.Ports;

namespace Move_Robot
{
    class Class_My_SerialPort
    { 
    }
}

2-2 添加扫描串口逻辑

代码分析:
USART_Search(): //返回查找到的串口名字,并添加到字符串数组里面
serial_Name = System.IO.Ports.SerialPort.GetPortNames();//查找系统内的串口资源
下面的代码就是获取系统串口信息的代码。

 private string[] USART_Search()//返回查找到的串口名字,并添加到串口的combox里面
{
    string[] serial_Name= null;
    try{
        serial_Name = System.IO.Ports.SerialPort.GetPortNames();//查找系统内的串口资源
    }
    catch{
        System.Media.SystemSounds.Hand.Play();
        MessageBox.Show("串口搜索失败!", "系统错误");
        return serial_Name;
    }
    if (serial_Name == null) { return serial_Name; }
    else {
        Array.Sort(serial_Name);  //排序
        return serial_Name;
    }
}

代码分析:
search_port_is_exist : 遍历字符串是否在字符串中
update_Serial_List : 将串口信息名字添加到 下拉列表中

private Boolean search_port_is_exist(string item, string[] port_list)
{
    for (int i = 0; i < port_list.Length; i++){
        if (port_list[i].Equals(item)) { return true; }
    }
    return false;
}

public void update_Serial_List(ComboBox Port_comboBox)
{ 
    int count = Port_comboBox.Items.Count;
    this_Serial_Name = USART_Search();
    
    if (count == 0){
        if (this_Serial_Name.Length == 0){
            Port_comboBox.Text = "";
        }
        else{ 
            Port_comboBox.Items.AddRange(this_Serial_Name); //如果原来没有就增加
            Port_comboBox.Text = Port_comboBox.Items[0].ToString();
        }
    }
    else{//如果原来有串口,那就查看串口是增加了还是减少了
        if (this_Serial_Name.Length == 0){
            Port_comboBox.Items.Clear();
            Port_comboBox.Text = "";
        }
        else {
            foreach (String str in this_Serial_Name){
                if (!Port_comboBox.Items.Contains(str)){ // 如果串口中没有当前的值那就新增串口
                    Port_comboBox.Items.Add(str);
                }
            }
            for (int i = 0; i < count; i++){// 如果串口中有,那就删除原来的记录重新录入
                if (!search_port_is_exist(Port_comboBox.Items[i].ToString(), this_Serial_Name)){
                    Port_comboBox.Items.RemoveAt(i);
                    Port_comboBox.Text = Port_comboBox.Items[0].ToString();
                }
            }
        }
        
    }           
}

2-3 初始化串口配置 波特率等参数

代码分析:
下面的程序都是一些初始化程序,就是将用到LOAD函数中一般, 将串口的各种设置参数数据加载到对应的下拉列表中

public void BaudRate_Init(ComboBox ParityRate_comboBox)
{   //波特率加载
    for (int i = 0; i < 5; i++){
        ParityRate_comboBox.Items.Add((2400 * (Math.Pow(2, i))).ToString());
    }
    for (int i = 0; i < 2; i++){
        ParityRate_comboBox.Items.Add((57600 * (Math.Pow(2, i))).ToString());
    }
    ParityRate_comboBox.Items.Add((128000).ToString());
    ParityRate_comboBox.Items.Add((230400).ToString());
    ParityRate_comboBox.Items.Add((256000).ToString());
    ParityRate_comboBox.Text = "9600";
}
public void Parity_Init(ComboBox Parity_comboBox)
{//奇偶校验加载设置
    Parity_comboBox.Items.Add("无");
    Parity_comboBox.Items.Add("奇");
    Parity_comboBox.Items.Add("偶");
    Parity_comboBox.Text = "无";
}
public void Data_Init(ComboBox Data_comboBox)
{//奇偶校验加载设置
    for (int i = 5; i <= 8; i++){
        Data_comboBox.Items.Add((i).ToString());
    }
    Data_comboBox.Text = "8";
}
public void Stop_Data_Init(ComboBox Stop_comboBox)
{//停止位加载设置            
    Stop_comboBox.Items.Add((1).ToString());
    Stop_comboBox.Items.Add((1.5).ToString());
    Stop_comboBox.Items.Add((2).ToString());
    Stop_comboBox.Text = "1";
}

2-4 串口打开程序

最重要的是下面的代码:
start_USART(串口控件,波特率,校验位,数据位,停止位,编码格式)
serialPort.IsOpen 是用来判断串口是否以及打开。
前半部分是对串口各种参数的配置进行赋值。
重点记住关于编码格式的说明,这里会直接影响到后续 ,串口数据中文英文数据格式的通讯,非常重要。
也是我经过整理后调试完成以后的
serialPort.Encoding

在这里插入图片描述

public Boolean start_USART(SerialPort serialPort, String PortName , String BaudRate, String Parity, String DataBits, String StopBits, String code_UTFSet)
{
    String this_PortName = PortName;
    String this_BaudRate = BaudRate;
    String this_Parity = Parity;
    String this_DataBits = DataBits;
    String this_StopBits = StopBits;
    String this_code_UTFSet = code_UTFSet;
    
    try
    {
        if (this_PortName == ""){
            System.Media.SystemSounds.Hand.Play();
            MessageBox.Show("无可用串口!", "错误");
            return false;
        }
        if(serialPort.IsOpen){
            System.Media.SystemSounds.Hand.Play();
            MessageBox.Show("串口已经打开!", "错误");
            return false;
        }
        //----------------------------设置串口的连接参数------------------------
        //设置串口名字
        serialPort.PortName = this_PortName;
        //设置波特率
        serialPort.BaudRate = Convert.ToInt32(this_BaudRate);
        //设置就校验位
        if (this_Parity.Equals("无")) { serialPort.Parity = System.IO.Ports.Parity.None; }
        else if (this_Parity.Equals("奇")) { serialPort.Parity = System.IO.Ports.Parity.Odd; }
        else { serialPort.Parity = System.IO.Ports.Parity.Even; }
        //设置数据位
        serialPort.DataBits = Convert.ToInt32(this_DataBits);
        设置停止位
        if (this_StopBits.Equals("1")) { serialPort.StopBits = System.IO.Ports.StopBits.One; }
        else if (this_StopBits.Equals("1.5")) { serialPort.StopBits = System.IO.Ports.StopBits.OnePointFive; }
        else { serialPort.StopBits = System.IO.Ports.StopBits.Two; }


        //------------------------- 这里很关键--编码格式设置---------------------
        serialPort.ReceivedBytesThreshold = 1; //接收到一个字符就触发事件
        if (code_UTFSet.Equals("DEFAULT")) { serialPort.Encoding = Encoding.Default; }// 设置编码格式
        else if (code_UTFSet.Equals("UTF-8")) { serialPort.Encoding = Encoding.UTF8; }// 设置编码格式
        else if (code_UTFSet.Equals("GB232")) { serialPort.Encoding = Encoding.Default; }// 设置编码格式
        else { serialPort.Encoding = Encoding.Default; }// 设置编码格式

        //正式打开串口
        serialPort.Open();
    }
    catch
    {
        System.Media.SystemSounds.Hand.Play();
        MessageBox.Show("串口打开失败!", "错误");
        return false;
    }  
    //System.Media.SystemSounds.Beep.Play();
    return true;
}

public Boolean stop_USART(SerialPort serialPort)
{
    //如果已将是关闭状态
    try{              
        serialPort.Close();
        return true;
    }
    catch{
        System.Media.SystemSounds.Hand.Play();
        MessageBox.Show("串口关闭失败!", "错误");
        return false;
    }
    
}

3.增加调用的函数来使用配置搜索串口:

在这里插入图片描述

3-1 按钮 按下方法中添加串口搜索功能

添加扫描串口逻辑

private void button8_Click(object sender, EventArgs e)  //设备打开与设备断开的按钮添加这个函数
{
    if (button1.Tag.Equals("0")) {
        if (Port_comboBox.Text == "") {
            System.Media.SystemSounds.Hand.Play();
            MessageBox.Show("当前无串口请搜索串口!", "错误");
            talk_String("当前无串口请搜索串口!");
            return;
        }
        my_SerialPort.update_Serial_List(Port_comboBox);
        if (my_SerialPort.start_USART(serialPort1, Port_comboBox.Text, "9600", "无", "8", "1", "DEFAULT")) {
            talk_String("串口打开完成!");
            button1.Text = "设备断开";
            button1.Tag = "1";
            groupBox101.Text = "设备已连接";
            button2.Enabled = false;
            Port_comboBox.Enabled = false;
            function_MainForm.Robot_state_set(2);

            return;
        }
    }
    else {
        my_SerialPort.stop_USART(serialPort1);
        talk_String("串口已经断开!");
        button1.Text = "设备连接";
        button1.Tag = "0";
        groupBox101.Text = "设备已中断";
        button2.Enabled = Enabled;
        Port_comboBox.Enabled = Enabled;
        function_MainForm.Robot_state_set(-1);

    }
}

private void button2_Click(object sender, EventArgs e) // 设备搜索
{
    my_SerialPort.update_Serial_List(Port_comboBox);
}

3-2定时器方法中添加串口自动搜索功能

定时器启动/停止控制

使用下面的代码即可设置定时器的时间为1s,并启动定时器:

timer1.Interval = 1000;
timer1.Start();

添加扫描串口逻辑

添加扫描自动串口逻辑

private void timer1_Tick(object sender, EventArgs e)
{
    labelASCtime1.Text = "Time:" + DateTime.Now.ToString("HH:mm:ss");
    function_MainForm.Robot_state_show(pictureBox_state, label_state);//设备状态显示
    my_SerialPort.update_Serial_List(Port_comboBox);//串口自动加载与识别
    if ((button1.Tag == "1") && (serialPort1.PortName != Port_comboBox.Text)) {
        my_SerialPort.stop_USART(serialPort1);
        talk_String("串口已经断开!");
        button1.Text = "设备连接";
        button1.Tag = "0";
        groupBox101.Text = "设备已中断";
        button2.Enabled = Enabled;
        Port_comboBox.Enabled = Enabled;
        function_MainForm.Robot_state_set(-1);
    }
}

4.增加串口动作函数:

这个是反映串口数据接收以及处理的函数,很重要

4-1 发送数据,注意 数据对Byte的转换

添加串口发送数据逻辑

 public String SEND_DATA(SerialPort serialPort ,String data_Style, Boolean B_data_Line, String send_text) //发送数据
{
    long send_code_count = 0;
    byte[] Data = new byte[1];
    if (serialPort.IsOpen == false) {
        System.Media.SystemSounds.Hand.Play();
        MessageBox.Show("串口没有打开", "错误");
        return "";
    }
    if (send_text == "")
    {
        System.Media.SystemSounds.Hand.Play();
        MessageBox.Show("发送数据不可以为空", "错误");
        return "";
    }
    if (data_Style.Equals("ASCLL")) // 如果发送的数据是 ASCLL的数据
    {    
        try
        {
            string send_str = send_text;
            if (B_data_Line == true) { send_str += "\r\n"; } // 如果是发送行被选择以后
            byte[] TxdBuf = Encoding.Default.GetBytes(send_str);//将数据转化为Byte数据后
            //串口处于开启状态,将发送区文本发送
            serialPort.Write(TxdBuf, 0, TxdBuf.Length); // 在数据中写到串口数据中
            send_code_count += TxdBuf.Length;  // 发送的数据数量进行记录 
            return send_str;
        }
        catch
        {
            return "Send_error";  // 捕获串口发送异常
        }
           
    }
    else if (data_Style.Equals("HEX")) // 如果发送的数据是 HEX的数据
    {
        string send_str = send_text.Trim();               
        send_str = Regex.Replace(send_str, @"[\u4e00 - \u9fa5]", ""); //去除汉字
        send_str = Regex.Replace(send_str, @"[f-z]", "");//去除HEX外的
        send_str = Regex.Replace(send_str, @"[F-Z]", "");//去除HEX外
        send_str = Regex.Replace(send_str, @"[\r\n\s\W]", "");//去除乱八糟的东西
        send_code_count += ((send_str.Length - (send_str.Length % 2)) / 2);
        try
        {
            for (int i = 0; i < ((send_str.Length - (send_str.Length % 2)) / 2); i++)
            { //将剩下的奇数末尾之前的发送完成
                Data[0] = Convert.ToByte(send_str.Substring(i * 2, 2), 16);
                serialPort.Write(Data, 0, 1);
            }
            if ((send_str.Length) % 2 != 0)//单独发送末尾数据 不是2位的数据
            {
                Data[0] = Convert.ToByte(send_str.Substring(send_str.Length - 1, 1), 16);
                serialPort.Write(Data, 0, 1);
                send_code_count += 1;
            }
            return send_str;
        }
        catch {
            return "Send_error";
        }
            
    }
    else {
        return "Send_set_error";
    }
   
}

4-2 接收数据,注意 数据对Byte的转换

由于我的面相对象编程思路我没想在这里判断数据格式以及截取数据格式按照 自定义的码值,
对于截取判断串口指令我是在另外的程序中动态补充

添加串口 接收数据逻辑

public String RECEIVE_DATA(SerialPort serialPort, String data_Style, Boolean need_tab)
{
    if (data_Style.Equals("ASCALL")){//如果接收的是ASCLL数据
        try{
            string get_str = "";
            while (serialPort.BytesToRead > 0) // 如果不是空数据就进行转换
            {
                receive_code_count += serialPort.BytesToRead;
                get_str += serialPort.ReadExisting(); // 读入数据
            }
            return get_str;
        }
        catch{
            return "Receive_error";
        }
    }

    else if (data_Style.Equals("HEX")){
        try{
            string get_str = "";
            string receive_data = "";
            receive_code_count += serialPort.BytesToRead;

            byte[] data = new byte[serialPort.BytesToRead];
            serialPort.Read(data, 0, data.Length);

            foreach (byte get_data in data){
                get_str = Convert.ToString(get_data, 16).ToUpper();//将每一位都转换为16进制的 字符串
                if (need_tab == true){//读入的数据是否需要空格间隔
                    receive_data += ((get_str.Length == 1 ? "0" + get_str : get_str) + " "); // 如果是12A->> 12 0A 自动补充0
                }
                else{
                    receive_data += ((get_str.Length == 1 ? "0" + get_str : get_str));
                }
                if (get_str.Length == 1) { receive_code_count += 1; }//自动补充
            }
            return receive_data;
        }
        catch{
            return "Receive_error";
        }

    }
    else {
        return "Receive_set_error";
    }
}

5.增加串口动作函数的应用函数:

看一下怎么用封装号的函数,很重要
在这里插入图片描述

5-1 对串口数据处理,删除截断特定标记并进行数据分割

用 (00 00 0D 0A)标记进行分割 AA 01 02 03 00 00 0D 0A AA 02 02 03 00 00 0D 0A

添加串口数据处理截断函数

public Class_Code_DATA[] Check_Data(MainForm mainForm, String s_data)
{
    int count_code = 0;
    Class_Code_DATA[] code_DATA = { };
    mainForm.Invoke((EventHandler)(delegate
    {
        String[] this_data = s_data.Split(new String[] { "00000D0A" }, StringSplitOptions.RemoveEmptyEntries);
        count_code = this_data.Count();
        code_DATA = new Class_Code_DATA[count_code];
        count_code = 0;
        foreach (String s in this_data)
        {
            Class_Code_DATA check_code = Check_code(s);
            code_DATA[count_code++] = check_code;
        }
        GC.Collect();
    }));
    return code_DATA;

}

将分割好的串口数据进行二次识别 (内部方法)

private Class_Code_DATA Check_code(String check_s)
{
    /*
            {0xAA,0x01,X,X} //温度
         {0xAA,0x02,X,X} //湿度
         {0xAA,0x03,X,X} //感应到人坐标							  
         {0xAA,0x04,X,X} //当前人坐标 
         {0xAA,0x05,X,X} //暂停完成

         {0xAA,0x010,X,X} //上电初始化完成 
         {0xAA,0x011,X,X} //测试完成
         {0xAA,0x012,X,X} //复位完成							  
         {0xAA,0x013,X,X} //移动完成 

    警告类:
         {0xAB,0xF0,0xFF,0xFF} //主机到从机数据警告 
         {0xBA,0xF1,0xFF,0xFF} //从机到主机数据警告
         {0xAA,0xF2,0xFF,0xFF} //主机到PC机数据警告
    尾部数据 00000D0A
 * 
 */

    Class_Code_DATA code_DATA = new Class_Code_DATA();
    if (check_s.Length != 8) { code_DATA.set_Code_Aim("中继-主机"); code_DATA.set_Code_Name("主机到PC机数据警告,请重新建立链接");
        code_DATA.set_Code_Data1(""); code_DATA.set_Code_Data2("");
        GC.Collect();
        GC.WaitForPendingFinalizers(); 
        return code_DATA; }

    if (check_s.Substring(0, 2).Equals("AA")) { code_DATA.set_Code_Aim("中继-主机"); }
    else if(check_s.Substring(0, 2).Equals("0xAB")){ code_DATA.set_Code_Aim("中继-从机"); }
    else if (check_s.Substring(0, 2).Equals("0xAB")) { code_DATA.set_Code_Aim("从机-中继"); }
    else { code_DATA.set_Code_Aim("error"); }

    if (check_s.Substring(2, 2).Equals("01")) { code_DATA.set_Code_Name("温度"); }
    else if (check_s.Substring(2, 2).Equals("02")) { code_DATA.set_Code_Name("湿度"); }
    else if (check_s.Substring(2, 2).Equals("03")) { code_DATA.set_Code_Name("感应到人坐标"); }
    else if (check_s.Substring(2, 2).Equals("04")) { code_DATA.set_Code_Name("当前人坐标"); }
    else if (check_s.Substring(2, 2).Equals("05")) { code_DATA.set_Code_Name("暂停"); }
    else if (check_s.Substring(2, 2).Equals("10")) { code_DATA.set_Code_Name("上电初始化完成"); }
    else if (check_s.Substring(2, 2).Equals("11")) { code_DATA.set_Code_Name("测试完成");}
    else if (check_s.Substring(2, 2).Equals("12")) { code_DATA.set_Code_Name("复位完成");}
    else if (check_s.Substring(2, 2).Equals("13")) { code_DATA.set_Code_Name("移动完成");}

    else if (check_s.Substring(2, 2).Equals("F0")) { code_DATA.set_Code_Name("主机到从机数据警告,请重新建立链接");}
    else if (check_s.Substring(2, 2).Equals("F1")) { code_DATA.set_Code_Name("从机到主机数据警告,请重新建立链接");}
    else if (check_s.Substring(2, 2).Equals("F2")) { code_DATA.set_Code_Name("主机到PC机数据警告,请重新建立链接");}
    else { code_DATA.set_Code_Name("error"); }

    code_DATA.set_Code_Data1(Convert.ToInt32(check_s.Substring(4, 2), 16).ToString());
    code_DATA.set_Code_Data2(Convert.ToInt32(check_s.Substring(6, 2), 16).ToString());

    GC.Collect();
    GC.WaitForPendingFinalizers();
    return code_DATA;
}

完成公共接口函数

public void Deal_The_Data(MainForm mainForm, Class_Code_DATA[] s_data, TextBox textBox_data, PictureBox pictureBox_state, RichTextBox command_richTextBox, 
            Label labelx, Label labely, ProgressBar command_progressBar1 , TextBox textBox401, TextBox textBox402, TextBox textBox403)
{
    mainForm.Invoke((EventHandler)(delegate
    {
        Show_data(s_data, textBox_data);
        foreach (Class_Code_DATA data in s_data)
        {
            if (data.get_Code_Name().Equals("当前人坐标")) { Show_Point(data, labelx, labely , command_progressBar1); }
            else if (data.get_Code_Name().Equals("感应到人坐标")) { Paint_PIR(data , pictureBox_state);  }
            else if (data.get_Code_Name().Equals("上电初始化完成")) { talk_String(command_richTextBox , "上电初始化完成"); }
            else if (data.get_Code_Name().Equals("暂停")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "暂停完成"); Robot_state_set(2); }
            else if (data.get_Code_Name().Equals("测试完成")) { Save_ImageAuto(mainForm , command_richTextBox,textBox401, textBox402, textBox403); Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "测试完成"); Robot_state_set(2); }
            else if (data.get_Code_Name().Equals("复位完成")) {  Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "复位完成"); Robot_state_set(2); }
            else if (data.get_Code_Name().Equals("移动完成")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "移动完成"); Robot_state_set(2); }

            else if (data.get_Code_Name().Equals("主机到从机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "主机到从机数据警告"); Robot_state_set(0); }
            else if (data.get_Code_Name().Equals("从机到主机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "从机到主机数据警告"); Robot_state_set(0); }
            else if (data.get_Code_Name().Equals("主机到PC机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "主机到PC机数据警告"); Robot_state_set(0); }
            else {
                if (data.get_Code_Name().Equals("温度") || data.get_Code_Name().Equals("湿度")) { ; }
                else { talk_String(command_richTextBox, data.get_Code_Name() + " " + data.get_Code_Data1() + "" + data.get_Code_Data2()); }
            }
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }));
}

5-2 对串口数据处理封装函数进行主程序控件调用

串口控件下选择属性-》方法,中的第一个DATAreceive方法,双击产生默认的接口方法:

在这里插入图片描述

怎么使用呢:
对数据进行检测-》进行代码识别

 private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    /*串口接收,在使用串口接收之前要先为串口注册一个Receive事件,
       相当于单片机中的串口接收中断,然后在中断内部对缓冲区的数据进行读取*/
    String s_data = "";
    s_data = function_MainForm.Receive_data(this, serialPort1);
    Class_Code_DATA[] code_DATA = function_MainForm.Check_Data(this, s_data);
    function_MainForm.Deal_The_Data(this, code_DATA, textBox_data, Point_Box, command_richTextBox1, label6, label7, command_progressBar1, textBox401, textBox402, textBox403);

    GC.Collect(); //垃圾回收
    GC.WaitForPendingFinalizers();
}

三、串口类的 完整代码

1.自定义 Class_My_SerialPort 类中的方法

using System;
using System.Collections.Generic;

using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 新建引用
using System.Windows.Forms;
using System.IO.Ports;
using System.Text.RegularExpressions;

namespace Move_Robot
{
    class Class_My_SerialPort 
    { 
        public static string[] this_Serial_Name ;
        public static long send_code_count;
        public static long receive_code_count;

        //------------------------------------ 数据区域 --------------------------------------

        public long send_code_count_get() {
            return send_code_count;
        }
        public void send_code_count_set(long set_data){
            send_code_count = set_data;
        }
   
        public long receive_code_count_get(){
            return receive_code_count;
        }
        public void receive_code_count_set(long receive_data){
            receive_code_count = receive_data;
        }
     

        //-------------------------------------- 串口设置区域 -------------------------------------------

        private string[] USART_Search()//返回查找到的串口名字,并添加到串口的combox里面
        {
            string[] serial_Name= null;
            try{
                serial_Name = System.IO.Ports.SerialPort.GetPortNames();//查找系统内的串口资源
            }
            catch{
                System.Media.SystemSounds.Hand.Play();
                MessageBox.Show("串口搜索失败!", "系统错误");
                return serial_Name;
            }
            if (serial_Name == null) { return serial_Name; }
            else {
                Array.Sort(serial_Name);  //排序
                return serial_Name;
            }
        }

        private Boolean search_port_is_exist(string item, string[] port_list)
        {
            for (int i = 0; i < port_list.Length; i++){
                if (port_list[i].Equals(item)) { return true; }
            }
            return false;
        }

        public void update_Serial_List(ComboBox Port_comboBox)
        { 
            int count = Port_comboBox.Items.Count;
            this_Serial_Name = USART_Search();
            
            if (count == 0){
                if (this_Serial_Name.Length == 0){
                    Port_comboBox.Text = "";
                }
                else{ 
                    Port_comboBox.Items.AddRange(this_Serial_Name); //如果原来没有就增加
                    Port_comboBox.Text = Port_comboBox.Items[0].ToString();
                }
            }
            else{//如果原来有串口,那就查看串口是增加了还是减少了
                if (this_Serial_Name.Length == 0){
                    Port_comboBox.Items.Clear();
                    Port_comboBox.Text = "";
                }
                else {
                    foreach (String str in this_Serial_Name){
                        if (!Port_comboBox.Items.Contains(str)){ // 如果串口中没有当前的值那就新增串口
                            Port_comboBox.Items.Add(str);
                        }
                    }
                    for (int i = 0; i < count; i++){// 如果串口中有,那就删除原来的记录重新录入
                        if (!search_port_is_exist(Port_comboBox.Items[i].ToString(), this_Serial_Name)){
                            Port_comboBox.Items.RemoveAt(i);
                            Port_comboBox.Text = Port_comboBox.Items[0].ToString();
                        }
                    }
                }
                
            }           
        }


        public void BaudRate_Init(ComboBox ParityRate_comboBox)
        {   //波特率加载
            for (int i = 0; i < 5; i++){
                ParityRate_comboBox.Items.Add((2400 * (Math.Pow(2, i))).ToString());
            }
            for (int i = 0; i < 2; i++){
                ParityRate_comboBox.Items.Add((57600 * (Math.Pow(2, i))).ToString());
            }
            ParityRate_comboBox.Items.Add((128000).ToString());
            ParityRate_comboBox.Items.Add((230400).ToString());
            ParityRate_comboBox.Items.Add((256000).ToString());
            ParityRate_comboBox.Text = "9600";
        }
        public void Parity_Init(ComboBox Parity_comboBox)
        {//奇偶校验加载设置
            Parity_comboBox.Items.Add("无");
            Parity_comboBox.Items.Add("奇");
            Parity_comboBox.Items.Add("偶");
            Parity_comboBox.Text = "无";
        }
        public void Data_Init(ComboBox Data_comboBox)
        {//奇偶校验加载设置
            for (int i = 5; i <= 8; i++){
                Data_comboBox.Items.Add((i).ToString());
            }
            Data_comboBox.Text = "8";
        }
        public void Stop_Data_Init(ComboBox Stop_comboBox)
        {//停止位加载设置            
            Stop_comboBox.Items.Add((1).ToString());
            Stop_comboBox.Items.Add((1.5).ToString());
            Stop_comboBox.Items.Add((2).ToString());
            Stop_comboBox.Text = "1";
        }

        public Boolean start_USART(SerialPort serialPort, String PortName , String BaudRate, String Parity, String DataBits, String StopBits, String code_UTFSet)
        {
            String this_PortName = PortName;
            String this_BaudRate = BaudRate;
            String this_Parity = Parity;
            String this_DataBits = DataBits;
            String this_StopBits = StopBits;
            String this_code_UTFSet = code_UTFSet;
            
            try
            {
                if (this_PortName == ""){
                    System.Media.SystemSounds.Hand.Play();
                    MessageBox.Show("无可用串口!", "错误");
                    return false;
                }
                if(serialPort.IsOpen){
                    System.Media.SystemSounds.Hand.Play();
                    MessageBox.Show("串口已经打开!", "错误");
                    return false;
                }
                //----------------------------设置串口的连接参数------------------------
                //设置串口名字
                serialPort.PortName = this_PortName;
                //设置波特率
                serialPort.BaudRate = Convert.ToInt32(this_BaudRate);
                //设置就校验位
                if (this_Parity.Equals("无")) { serialPort.Parity = System.IO.Ports.Parity.None; }
                else if (this_Parity.Equals("奇")) { serialPort.Parity = System.IO.Ports.Parity.Odd; }
                else { serialPort.Parity = System.IO.Ports.Parity.Even; }
                //设置数据位
                serialPort.DataBits = Convert.ToInt32(this_DataBits);
                设置停止位
                if (this_StopBits.Equals("1")) { serialPort.StopBits = System.IO.Ports.StopBits.One; }
                else if (this_StopBits.Equals("1.5")) { serialPort.StopBits = System.IO.Ports.StopBits.OnePointFive; }
                else { serialPort.StopBits = System.IO.Ports.StopBits.Two; }


                //------------------------- 这里很关键--编码格式设置---------------------
                serialPort.ReceivedBytesThreshold = 1; //接收到一个字符就触发事件
                if (code_UTFSet.Equals("DEFAULT")) { serialPort.Encoding = Encoding.Default; }// 设置编码格式
                else if (code_UTFSet.Equals("UTF-8")) { serialPort.Encoding = Encoding.UTF8; }// 设置编码格式
                else if (code_UTFSet.Equals("GB232")) { serialPort.Encoding = Encoding.Default; }// 设置编码格式
                else { serialPort.Encoding = Encoding.Default; }// 设置编码格式

                //正式打开串口
                serialPort.Open();
            }
            catch
            {
                System.Media.SystemSounds.Hand.Play();
                MessageBox.Show("串口打开失败!", "错误");
                return false;
            }  
            //System.Media.SystemSounds.Beep.Play();
            return true;
        }

        public Boolean stop_USART(SerialPort serialPort)
        {
            //如果已将是关闭状态
            try{              
                serialPort.Close();
                return true;
            }
            catch{
                System.Media.SystemSounds.Hand.Play();
                MessageBox.Show("串口关闭失败!", "错误");
                return false;
            }
            
        }

        //-----------------------------------串口动作函数------------------------------


        public String SEND_DATA(SerialPort serialPort ,String data_Style, Boolean B_data_Line, String send_text) //发送数据
        {
            long send_code_count = 0;
            byte[] Data = new byte[1];
            if (serialPort.IsOpen == false) {
                System.Media.SystemSounds.Hand.Play();
                MessageBox.Show("串口没有打开", "错误");
                return "";
            }
            if (send_text == "")
            {
                System.Media.SystemSounds.Hand.Play();
                MessageBox.Show("发送数据不可以为空", "错误");
                return "";
            }
            if (data_Style.Equals("ASCLL")) // 如果发送的数据是 ASCLL的数据
            {    
                try
                {
                    string send_str = send_text;
                    if (B_data_Line == true) { send_str += "\r\n"; } // 如果是发送行被选择以后
                    byte[] TxdBuf = Encoding.Default.GetBytes(send_str);//将数据转化为Byte数据后
                    //串口处于开启状态,将发送区文本发送
                    serialPort.Write(TxdBuf, 0, TxdBuf.Length); // 在数据中写到串口数据中
                    send_code_count += TxdBuf.Length;  // 发送的数据数量进行记录 
                    return send_str;
                }
                catch
                {
                    return "Send_error";  // 捕获串口发送异常
                }
                   
            }
            else if (data_Style.Equals("HEX")) // 如果发送的数据是 HEX的数据
            {
                string send_str = send_text.Trim();               
                send_str = Regex.Replace(send_str, @"[\u4e00 - \u9fa5]", ""); //去除汉字
                send_str = Regex.Replace(send_str, @"[f-z]", "");//去除HEX外的
                send_str = Regex.Replace(send_str, @"[F-Z]", "");//去除HEX外
                send_str = Regex.Replace(send_str, @"[\r\n\s\W]", "");//去除乱八糟的东西
                send_code_count += ((send_str.Length - (send_str.Length % 2)) / 2);
                try
                {
                    for (int i = 0; i < ((send_str.Length - (send_str.Length % 2)) / 2); i++)
                    { //将剩下的奇数末尾之前的发送完成
                        Data[0] = Convert.ToByte(send_str.Substring(i * 2, 2), 16);
                        serialPort.Write(Data, 0, 1);
                    }
                    if ((send_str.Length) % 2 != 0)//单独发送末尾数据 不是2位的数据
                    {
                        Data[0] = Convert.ToByte(send_str.Substring(send_str.Length - 1, 1), 16);
                        serialPort.Write(Data, 0, 1);
                        send_code_count += 1;
                    }
                    return send_str;
                }
                catch {
                    return "Send_error";
                }
                    
            }
            else {
                return "Send_set_error";
            }
           
        }

        
        public String RECEIVE_DATA(SerialPort serialPort, String data_Style, Boolean need_tab)
        {
            if (data_Style.Equals("ASCALL")){//如果接收的是ASCLL数据
                try{
                    string get_str = "";
                    while (serialPort.BytesToRead > 0) // 如果不是空数据就进行转换
                    {
                        receive_code_count += serialPort.BytesToRead;
                        get_str += serialPort.ReadExisting(); // 读入数据
                    }
                    return get_str;
                }
                catch{
                    return "Receive_error";
                }
            }

            else if (data_Style.Equals("HEX")){
                try{
                    string get_str = "";
                    string receive_data = "";
                    receive_code_count += serialPort.BytesToRead;

                    byte[] data = new byte[serialPort.BytesToRead];
                    serialPort.Read(data, 0, data.Length);

                    foreach (byte get_data in data){
                        get_str = Convert.ToString(get_data, 16).ToUpper();//将每一位都转换为16进制的 字符串
                        if (need_tab == true){//读入的数据是否需要空格间隔
                            receive_data += ((get_str.Length == 1 ? "0" + get_str : get_str) + " "); // 如果是12A->> 12 0A 自动补充0
                        }
                        else{
                            receive_data += ((get_str.Length == 1 ? "0" + get_str : get_str));
                        }
                        if (get_str.Length == 1) { receive_code_count += 1; }//自动补充
                    }
                    return receive_data;
                }
                catch{
                    return "Receive_error";
                }

            }
            else {
                return "Receive_set_error";
            }
        }

    }
}

2.自定义主程序Function_MainForm接口方法类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//新增类
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;

namespace Move_Robot
{
    class Function_MainForm
    {
        public static int Robot_State = -1;     // 0 故障 1运行 2暂停 
        public static Boolean Robot_paint_receivecommand = true;
        private static bool state_show_tag = false;
        private static String File_name;

         
        // ---------------------------------发送各类测试指令------------------------
        public String RESET_Command(MainForm mainForm, SerialPort serialPort)
        {
            String s_command = "";
            mainForm.Invoke((EventHandler)(delegate
            {
                s_command = Convert.ToString(new Class_My_SerialPort().SEND_DATA(serialPort, "HEX", false, "AA020000"));
                Robot_state_set(1);
            }));
            return s_command;
        }

        public String Stop_Command(MainForm mainForm, SerialPort serialPort)
        {
            String s_command = "";
            mainForm.Invoke((EventHandler)(delegate
            {
                s_command = Convert.ToString(new Class_My_SerialPort().SEND_DATA(serialPort, "HEX", false, "AA040000"));
                Robot_state_set(1);
            }));
            return s_command;
        }

        public String AUTO_Command(MainForm mainForm, SerialPort serialPort, String max_distance)
        {
            String s_command = "";
            String command = "AA0100";
            max_distance = Convert.ToString(Convert.ToInt32(max_distance), 16);
            if (max_distance.Length % 2 != 0) { command += "0"; }
            command += max_distance.ToUpper();
            mainForm.Invoke((EventHandler)(delegate
            {
                s_command = Convert.ToString(new Class_My_SerialPort().SEND_DATA(serialPort, "HEX", false, command));
                Robot_state_set(1);
            }));
            return s_command;
        }
        public String Line_Command(MainForm mainForm, SerialPort serialPort, String max_distance)
        {
            String s_command = "";
            String command = "AA0600";
            max_distance = Convert.ToString(Convert.ToInt32(max_distance), 16);
            if (max_distance.Length % 2 != 0) { command += "0"; }
            command += max_distance.ToUpper();
            mainForm.Invoke((EventHandler)(delegate
            {
                s_command = Convert.ToString(new Class_My_SerialPort().SEND_DATA(serialPort, "HEX", false, command));
                Robot_state_set(1);
            }));
            return s_command;
        }

        
        public String Point_Command(MainForm mainForm, SerialPort serialPort, String x_distance, String y_distance)
        {
            String s_command = "";
            String command = "AA03";
            x_distance = Convert.ToString(Convert.ToInt32(x_distance), 16);
            if (x_distance.Length % 2 != 0) { command += "0"; }
            command += x_distance.ToUpper();

            y_distance = Convert.ToString(Convert.ToInt32(y_distance), 16);
            if (x_distance.Length % 2 != 0) { command += "0"; }
            command += y_distance.ToUpper();

            mainForm.Invoke((EventHandler)(delegate
            {
                s_command = Convert.ToString(new Class_My_SerialPort().SEND_DATA(serialPort, "HEX", false, command));
                Robot_state_set(1);
            }));
            return s_command;
        }


        // ---------------------------------接收数据指令------------------------
        public String Receive_data(MainForm mainForm, SerialPort serialPort)
        {
            String s_data = "";
            mainForm.Invoke((EventHandler)(delegate
            {
                s_data = new Class_My_SerialPort().RECEIVE_DATA(serialPort, "HEX", false);
            }));
            return s_data;
        }

        //------------------------------------指令数据处理了----------------------------

        private Class_Code_DATA Check_code(String check_s)
        {
            /*
                    {0xAA,0x01,X,X} //温度
	                {0xAA,0x02,X,X} //湿度
	                {0xAA,0x03,X,X} //感应到人坐标							  
	                {0xAA,0x04,X,X} //当前人坐标 
	                {0xAA,0x05,X,X} //暂停完成

	                {0xAA,0x010,X,X} //上电初始化完成 
	                {0xAA,0x011,X,X} //测试完成
	                {0xAA,0x012,X,X} //复位完成							  
	                {0xAA,0x013,X,X} //移动完成 
 
            警告类:
	                {0xAB,0xF0,0xFF,0xFF} //主机到从机数据警告 
	                {0xBA,0xF1,0xFF,0xFF} //从机到主机数据警告
	                {0xAA,0xF2,0xFF,0xFF} //主机到PC机数据警告
            尾部数据 00000D0A
         * 
         */

            Class_Code_DATA code_DATA = new Class_Code_DATA();
            if (check_s.Length != 8) { code_DATA.set_Code_Aim("中继-主机"); code_DATA.set_Code_Name("主机到PC机数据警告,请重新建立链接");
                code_DATA.set_Code_Data1(""); code_DATA.set_Code_Data2("");
                GC.Collect();
                GC.WaitForPendingFinalizers(); 
                return code_DATA; }

            if (check_s.Substring(0, 2).Equals("AA")) { code_DATA.set_Code_Aim("中继-主机"); }
            else if(check_s.Substring(0, 2).Equals("0xAB")){ code_DATA.set_Code_Aim("中继-从机"); }
            else if (check_s.Substring(0, 2).Equals("0xAB")) { code_DATA.set_Code_Aim("从机-中继"); }
            else { code_DATA.set_Code_Aim("error"); }

            if (check_s.Substring(2, 2).Equals("01")) { code_DATA.set_Code_Name("温度"); }
            else if (check_s.Substring(2, 2).Equals("02")) { code_DATA.set_Code_Name("湿度"); }
            else if (check_s.Substring(2, 2).Equals("03")) { code_DATA.set_Code_Name("感应到人坐标"); }
            else if (check_s.Substring(2, 2).Equals("04")) { code_DATA.set_Code_Name("当前人坐标"); }
            else if (check_s.Substring(2, 2).Equals("05")) { code_DATA.set_Code_Name("暂停"); }
            else if (check_s.Substring(2, 2).Equals("10")) { code_DATA.set_Code_Name("上电初始化完成"); }
            else if (check_s.Substring(2, 2).Equals("11")) { code_DATA.set_Code_Name("测试完成");}
            else if (check_s.Substring(2, 2).Equals("12")) { code_DATA.set_Code_Name("复位完成");}
            else if (check_s.Substring(2, 2).Equals("13")) { code_DATA.set_Code_Name("移动完成");}

            else if (check_s.Substring(2, 2).Equals("F0")) { code_DATA.set_Code_Name("主机到从机数据警告,请重新建立链接");}
            else if (check_s.Substring(2, 2).Equals("F1")) { code_DATA.set_Code_Name("从机到主机数据警告,请重新建立链接");}
            else if (check_s.Substring(2, 2).Equals("F2")) { code_DATA.set_Code_Name("主机到PC机数据警告,请重新建立链接");}
            else { code_DATA.set_Code_Name("error"); }

            code_DATA.set_Code_Data1(Convert.ToInt32(check_s.Substring(4, 2), 16).ToString());
            code_DATA.set_Code_Data2(Convert.ToInt32(check_s.Substring(6, 2), 16).ToString());

            GC.Collect();
            GC.WaitForPendingFinalizers();
            return code_DATA;
        }
        public Class_Code_DATA[] Check_Data(MainForm mainForm, String s_data)
        {
            int count_code = 0;
            Class_Code_DATA[] code_DATA = { };
            mainForm.Invoke((EventHandler)(delegate
            {
                String[] this_data = s_data.Split(new String[] { "00000D0A" }, StringSplitOptions.RemoveEmptyEntries);
                count_code = this_data.Count();
                code_DATA = new Class_Code_DATA[count_code];
                count_code = 0;
                foreach (String s in this_data)
                {
                    Class_Code_DATA check_code = Check_code(s);
                    code_DATA[count_code++] = check_code;
                }
                GC.Collect();
            }));
            return code_DATA;

        }


        public void Deal_The_Data(MainForm mainForm, Class_Code_DATA[] s_data, TextBox textBox_data, PictureBox pictureBox_state, RichTextBox command_richTextBox, 
            Label labelx, Label labely, ProgressBar command_progressBar1 , TextBox textBox401, TextBox textBox402, TextBox textBox403)
        {
            mainForm.Invoke((EventHandler)(delegate
            {
                Show_data(s_data, textBox_data);
                foreach (Class_Code_DATA data in s_data)
                {
                    if (data.get_Code_Name().Equals("当前人坐标")) { Show_Point(data, labelx, labely , command_progressBar1); }
                    else if (data.get_Code_Name().Equals("感应到人坐标")) { Paint_PIR(data , pictureBox_state);  }
                    else if (data.get_Code_Name().Equals("上电初始化完成")) { talk_String(command_richTextBox , "上电初始化完成"); }
                    else if (data.get_Code_Name().Equals("暂停")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "暂停完成"); Robot_state_set(2); }
                    else if (data.get_Code_Name().Equals("测试完成")) { Save_ImageAuto(mainForm , command_richTextBox,textBox401, textBox402, textBox403); Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "测试完成"); Robot_state_set(2); }
                    else if (data.get_Code_Name().Equals("复位完成")) {  Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "复位完成"); Robot_state_set(2); }
                    else if (data.get_Code_Name().Equals("移动完成")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "移动完成"); Robot_state_set(2); }

                    else if (data.get_Code_Name().Equals("主机到从机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "主机到从机数据警告"); Robot_state_set(0); }
                    else if (data.get_Code_Name().Equals("从机到主机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "从机到主机数据警告"); Robot_state_set(0); }
                    else if (data.get_Code_Name().Equals("主机到PC机数据警告")) { Command_progressBar_Stop(command_progressBar1); talk_String(command_richTextBox, "主机到PC机数据警告"); Robot_state_set(0); }
                    else {
                        if (data.get_Code_Name().Equals("温度") || data.get_Code_Name().Equals("湿度")) { ; }
                        else { talk_String(command_richTextBox, data.get_Code_Name() + " " + data.get_Code_Data1() + "" + data.get_Code_Data2()); }
                    }
                }
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }));
        }

        //------------------------------------------------------------------反馈动作程序开始---------------------------
        private void talk_String(RichTextBox command_richTextBox, String Str)
        {
            command_richTextBox.AppendText("\r\n" + "[ 进程 ]" + "->" + Str);
            command_richTextBox.ScrollToCaret();
        }      

        private void Show_data(Class_Code_DATA[] s_data, TextBox textBox_data)
        {
            textBox_data.AppendText("[ 进程 ]" + "->");
            foreach (Class_Code_DATA data in s_data)
            {
                textBox_data.AppendText("\r\n"
                    + data.get_Code_Aim() + " "
                    + data.get_Code_Name() + " "
                    + data.get_Code_Data1() + " "
                    + data.get_Code_Data2() + " ");
            }
            textBox_data.AppendText("\r\n");
        }

        private void Show_Point(Class_Code_DATA data, Label labelx, Label labely,ProgressBar command_progressBar1)
        {
            if ((labelx.Text != data.get_Code_Data1()) || (labely.Text != data.get_Code_Data2()))
            {
                if(command_progressBar1.Value< command_progressBar1.Maximum)
                {
                    command_progressBar1.Value += 1;

                }
                
            }
            labelx.Text = data.get_Code_Data1();
            labely.Text = data.get_Code_Data2();
            
        }
        private void Paint_PIR(Class_Code_DATA data, PictureBox pictureBox_state)
        {
            if (Robot_paint_receivecommand == false) { return; }
            class_My_Paint.Point_set(data.get_Code_Data1(), data.get_Code_Data2(), "Draw") ;
            class_My_Paint.PointBox_Paint(pictureBox_state.CreateGraphics(), "draw_point");
        }

         
    }
}

3.主程序调用处:

private void button201_Click(object sender, EventArgs e)
{
    String s_command;

    textBox_data.Clear();
    s_command = function_MainForm.RESET_Command(this, serialPort1);

    talk_String("开始复位 请等待");//就是一个窗口输出
 
}

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) //串口添加的程序
{
    /*串口接收,在使用串口接收之前要先为串口注册一个Receive事件,
       相当于单片机中的串口接收中断,然后在中断内部对缓冲区的数据进行读取*/
    String s_data = "";
    s_data = function_MainForm.Receive_data(this, serialPort1);
    Class_Code_DATA[] code_DATA = function_MainForm.Check_Data(this, s_data);
    function_MainForm.Deal_The_Data(this, code_DATA, textBox_data, Point_Box, command_richTextBox1, label6, label7, command_progressBar1, textBox401, textBox402, textBox403);
    
    GC.Collect();  //垃圾回收
    GC.WaitForPendingFinalizers(); 
}

参考博主的博客。
C#上位机开发(六)——SerialAssistant功能优化(串口自动扫描功能、接收数据保存功能、加载发送文件、发送历史记录、打开浏览器功能、定时发送功能)


总结

注意,关于串口是独立于 窗体的线程,需要在串口数据下更新窗口的状态一定要用到 委托:

mainForm.Invoke((EventHandler)(delegate{ }));

版权声明:本文为CSDN博主「DRMIVET Stone」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012651389/article/details/122566893

生成海报
点赞 0

DRMIVET Stone

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

基于STM32的室内环境监测系统

设计简介: 本设计是基于单片机的室内环境监测器,主要实现以下功能: 可实现LCD1602显示环境数据实时监测一氧化碳、甲烷、烟雾值空气质量大于各自限值报警,并通过TTS报警 标签&#x

stm32+AMG8833+VS2015 C#上位机图像显示

设计思路 本人在电场工作,于是想做一款热成像智能控制小车,通过网上查找资料,发现AMG8833这款芯片可以说是物美价廉,基本能够实现想要的功能。因为我有一定的单片机开发基础&#xff0c

QT_串口操作

文章简介: 文章内容主要分为以下几个部分: 1.基本信息的解释 2.获取基本的串口信息(端口号,波特率,数据位,停止位,校验位,流