上位机与下位机的关系及串口通信的原理和应用

时间:2024-11-13 20:17:21

上位机与下位机的关系及串口通信的原理和应用

上位机和下位机在设备控制和数据处理中起着关键作用,这其中包含了许多既有趣又实用的要点。它们如何协作,程序中的那些特殊设置,都是我们值得深入研究的。

上位机的功能

上位机,作为一种软件系统,在数据处理方面扮演着关键角色。它能接收数据,包括汇聚节点传来的数据。不过,它不能独立运作。在实际操作中,上位机接收下位机发送的信号,以了解设备的状态。此外,它还能根据接收到的数据发出控制指令。例如,在工业环境中,当上位机接收到设备传来的温度、压力等数据后,便可以发出调整设备工作状态的指令。

上位机的这一功能常应用于自动化控制系统。以智能农业温室系统为例,传感器收集到的温度等环境数据会被传输至上位机。上位机则会依据事先设定的程序,对这些数据进行分析,并据此发出相应的控制指令。

下位机是一种控制器,可以独立操作。它能直接操控设备,同时获取设备的工作状态。它能够读取设备状态的模拟量数据,并将其转换为数字信号,然后反馈给上位机。在智能家居系统中,下位机能够直接控制灯光、空调等设备。它持续读取设备的状态信息,比如灯光的亮度、空调的温度等,并将这些信息转换为数字信号,发送至上位机。

下位机接收到上位机的指令后,会将其解读为对应的时序信号,并据此操控相关设备。比如,在自动化生产线上,当上位机下达加速生产的指令,下位机会将此指令转换成控制电机加速的时序信号,确保设备能按既定要求运作。

串口通信的参数

串口通信在上下位机之间的通信中扮演着至关重要的角色。波特率,作为衡量符号传输速度的指标,对于不同设备间的串口通信至关重要。若要进行串口通信,各设备间的波特率必须保持一致。以某设备为例,若其波特率为9600,与之通信的另一设备波特率也必须是9600。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Diagnostics;
namespace serial2
{
    public partial class Form1 : Form
    {
        SerialPort s = new SerialPort();    //实例化一个串口对象,在前端控件中可以直接拖过来,但最好是在后端代码中写代码,这样复制到其他地方不会出错。s是一个串口的句柄
        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;   //防止跨线程访问出错,好多地方会用到
            button1.Text = "打开串口";
            int[] item = { 9600,115200};    //定义一个Item数组,遍历item中每一个变量a,增加到comboBox2的列表中
            foreach (int a in item)
            {
                comboBox2.Items.Add(a.ToString());
            }
           
            comboBox2.SelectedItem = comboBox2.Items[1];    //默认为列表第二个变量
        }
        private void Form1_Load(object sender, EventArgs e)   //窗体事件要先配置端口信息。
        {
            string[] ports = SerialPort.GetPortNames();
            comboBox1.Items.AddRange(ports);
            comboBox1.SelectedItem=comboBox1.Items[0];
            //Array.Sort(ports);
            
        }
        private void button1_Click(object sender, EventArgs e)   //下面讲解中差不多已经讲清楚了
        {
            try
            {
                if (!s.IsOpen)
                {
                    s.PortName = comboBox1.SelectedItem.ToString();
                    s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
                    s.Open();
                    s.DataReceived += s_DataReceived;
                    button1.Text = "关闭串口";
                    //MessageBox.Show("串口已打开");
                }
                else
                {
                    s.Close();
                    s.DataReceived -= s_DataReceived;
                    button1.Text = "打开串口";
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        void s_DataReceived(object sender, SerialDataReceivedEventArgs e)   //数据接收事件,读到数据的长度赋值给count,如果是8位(节点内部编程规定好的),就申请一个byte类型的buff数组,s句柄来读数据
        {
            int count =s.BytesToRead;    
            string str=null ;
            if (count == 8)
            {
                byte[] buff = new byte[count];
                s.Read(buff, 0, count);
                foreach (byte item in buff)    //读取Buff中存的数据,转换成显示的十六进制数
                {
                    str += item.ToString("X2")+" ";
                }
                richTextBox1.Text =System.DateTime.Now.ToString()+": "+ str + "\n" + richTextBox1.Text;      //这是跨线程访问richtextbox,原程序和DataReceived事件是两个不同的线程同时在执行
                if (buff[0] == 0x04)   //如果节点是04发来的数据
                {
                    ID.Text = buff[0].ToString();   //这下面是上位机右边那一段,用来显示处理好的数据的温度、湿度、光照、灰尘、ID信息的。buff【0】中存的是数据的ID信息,显示在ID的Label上面
                    switch (buff[2])   //判断数据类型  buff【0】和buff【1】代表ID的低位和高位,同理2和3代表数据类型的低位和高位,当2和3的值为1时,4和5代表温度,6和7代表湿度;
                    
         {  
                      case 0x01:       //当2和3的值为1,4和5是温度,6和7是湿度
          {
                                Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
                                Hum.Text = (buff[6]  + buff[7]).ToString();
                                break;
                            }
                        case 0x02://6和7是光照
                       {
                                Light.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        case 0x04://6和7是灰尘
                        {
                                Dust.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        default:
                            break;
                    }
                }
            
            }
            
        }
        private void button3_Click(object sender, EventArgs e)   //每次发一个字节
   {
            string[] sendbuff = richTextBox2.Text.Split();  //分割输入的字符串,判断有多少个字节需要发送
        Debug.WriteLine("发送字节数:"+sendbuff.Length);
            foreach (string  item in sendbuff)
            {
                int count = 1;
                byte[] buff = new byte[count];
                buff[0] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber);//格式化字符串为十六进制数值
              s.Write(buff, 0, count);
            }
        }
        private void button2_Click(object sender, EventArgs e)//刷新右边的数值
      {
            int count = 1;
            byte[] buff = new byte[count];
            buff[0] = byte.Parse("04", System.Globalization.NumberStyles.HexNumber);//这里只显示04节点的信息
        s.Write(buff, 0, count);
        }
    }
}

数据位是衡量通信中实际数据位的指标。停止位则是用来标识一个数据包的结束,这些关键参数确保了串口通信的精确性。奇偶校验位同样不可或缺,它是一种基本的错误检测手段。在数据传输至关重要的场合,奇偶校验能够有效识别传输过程中的错误。

程序中的异常处理

程序出错的风险较高时,使用try语句非常关键。比如,当两个上位机同时需要使用同一串口,例如本实验中我们自编的上位机和从网上下载的上位机都试图占用COM3串口时,若代码未置于try语句中,便可能引发冲突和错误。

将代码置于try块内,并创建catch代码实例,即创建一个异常处理句柄。如此一来,即便程序遭遇错误,也不会中断运行,而是会显示出错误的具体原因。在复杂的程序执行环境中,这种方法能有效防止程序突然崩溃,从而增强系统的稳定性。

上位机的界面按钮设置

这个上位机的界面按钮设置颇为考究。以串口的开启与关闭为例,按常规操作确实需要两个按钮。然而,为了节省空间并实现直观显示,我们选择将这两个功能合并为一个控件。

 private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                if (!s.IsOpen)
                {
                    s.PortName = comboBox1.SelectedItem.ToString();
                    s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
                    s.Open();
                    s.DataReceived += s_DataReceived;
                    button1.Text = "关闭串口";
                    //MessageBox.Show("串口已打开");
                }
                else
                {
                    s.Close();
                    s.DataReceived -= s_DataReceived;
                    button1.Text = "打开串口";
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }

在事件操作中,若先点击该按钮且串口处于关闭状态,则会自动打开串口,并且按钮的显示文本将更改为“关闭串口”。相反,如果串口原本就是关闭的,点击按钮后,按钮的显示文本将变为“打开串口”,同时接收到的数据也会被清空。这样的设置既方便又实用。

代码输入时的便利操作

输入变量或方法时,程序提供了便捷的显示功能。其中,“正方体”符号代表“方法”,“小钳子”符号代表“变量”,“闪电”符号代表“事件”。例如,输入s.后,事件会自动显示;紧接着输入“+=”,系统便会给出提示,此时只需按下Tab键即可。

按下tab键,事件处理函数便会自动生成。这项功能显著提升了代码输入的效率,使得开发者能够更加迅速且精确地编写代码。

详细地掌握了上位机、下位机及程序关键点后,大家是否有过类似的开发经历?不妨在评论区留言,分享你的讨论和经验。同时,也请大家点赞并转发这篇文章,让更多的人能够接触到这些知识。