上位机(C#)与ARM Cortex-M0串口通信的实现、原理、注意事项及代码

Arm-CortexM0串口(下位机部分)

属性与原理

  1. 异步通信时不需要SCLK,同步通信时需要SCLK
  2. 串口接收和发送的过程:串口的接收和发送都是需要通过移位寄存器的
    1. 接收
      1. 一位一位的写入移位寄存器(也就是串转并
      2. 写满之后发送——输出数据缓存区
        1. 无DMA时直接给内核(触发接收缓存laa满中断)
        2. 有DMA时直接写入到给定的内存地址
    2. 发送
      1. 先从内核到输出数据寄存器
      2. 读入到移位寄存器
      3. 按照波特率的节奏一位一位发出去
  3. 通信前需要定义的5个内容
    1. 起始位
    2. 数据位
    3. 奇偶校验位——该位用来做校验,如果校验通过,接收的这一帧数据就被保留下来;如果校验失败,这一帧数据就被丢弃
    4. 停止位(一般是1、1.5、2个通信周期)
    5. 波特率
  4. 232最大有效传输20m,485可达3000米

不使用DMA时的串口通信过程:

  1. 对于发送:
    1. 发送缓存器空标志位会一直置起(开不开中断使能该标志位都是1)
    2. 因此使能发送寄存器空中断之后,程序会立马进入串口发送中断中
    3. 装载第一帧数据、移位指针、
    4. 下一次中断装载并发送下一帧,
    5. 依次循环
  2. 对于接收:
    1. 接收缓存器满后,进入中断
    2. 在中断中快速把这个数据接收起来,
    3. 在低优先级中处理判断接收buffer是否满了
    4. 如果满了在低优先级中回调用户

:由于串口的通信速度远低于CPU的运行速度,因此不用害怕在调用pendsv来不及

使用DMA的串口通信过程:

  1. 在配置完DMA通道、并使能后,
    1. 对于接收:接收缓存满之后会自动申请DMA事件,DMA会自动将这一帧数据搬运到指定位置,一般会触发
      1. 串口接收超时的串口中断
      2. DMA接收半程的DMA中断
      3. DMA接收全程的DMA中断
    2. 对于发送:发送缓存器空之后自动申请DMA事件,将指定位置的数据一帧一桢搬运到发送缓存器中,一般会触发
      1. DMA发送半程的DMA中断(可选择,一般不用)
      2. DMA发送全程的DMA中断
      3. 也可关闭DMA中断,配置一个寄存器,等所有数据发送完成之后进入串口发送中断。

注:每次接收完成后不用重置DMA,又重新从buffer的第一位或中间一位开始,用一个指针记录即可

注意事项

  1. 发送缓存器空中断标志的标志位一直都是1,因此在中断服务函数中不要判断该标志位是1就执行一些操作,尽量先判断是否为接收中断,否则为发送中断
  2. 配置DMA时,首先关闭DMA通道,等所有的DMA属性配置完成后再打开相应的通道使能(否则在上电时容易自动搬运乱码)
  3. 使用DMA接收时,自定义接收buffer大小,此时回调有三种,
    1. buffer半满DMA中断,中断后需要回调或处理接收数据
    2. buffer全满DMA中断,中断后需要回调或处理接收数据
    3. 串口接收超时(一般是255个波特时常,可配置),将最终超时数据回调

因此要注意:

  1. 如果发生超时中断时DMA的指针在前半buffer的首位或者后半buffer的首位,么说明接收数据已经进过DMA的半/满程回调了,此时什么都不做,不要再重复搬运数据了
  2. DMA全满且这一条数据刚好接收结束,在计算长度时不能直接用DMA的指针减去接收起始地址,因为DMA的指针已经指向了首地址,此时计算数据长度容易出错

C#上位机串口通信原理详解

上位机的数据接收过程:

  1. 读可用长度bytetoread
  2. 创建相应长度内存并sp.read
  3. 通过invoke回调

存在问题:

  1. 为接收到更短的数据recveivebytesthreshold 一般设置为1(接收到一个字节就触发received事件)
  2. 但这样每次进入received事件之后读到的数据都很少(几个字节),如果每次都这样回调去处理的话,会导致程序很卡

因此可以考虑使用ARM的DMA思想,设置一个接收buffer,等待一帧数据全部接收完后再统一回调(接收超时后),此时需要申请一个定时事件来判断超时

  1. 第一种方式是每触发received事件之后,就先关闭上一次申请的定时器,然后在数据读取完之后,再重新申请一个定时事件,但是这样每次申请和撤销定时事件开销太大
  2. 第二种方法:只申请一次定时事件,具体实现如下

注意事项

  1. received事件的处理函数内部对数据的处理应尽可能快,因为received函数是可以重入的
  2. serialport类默认的接收和发送缓存器的长度为4096字节(可软件配置),工作原理类似环形寄存器

上位机代码

  ///定义全局变量
        public static int receivedbuffer = 1;   ///在此处定义datareceived事件发生前内部输入缓冲区buffer(这里设置为1,一旦收到数据就进received事件)
        public static USART_struct USART_cfg = new USART_struct();
        //定义串口发送缓冲区  长度是10240字节
        public static byte[] UsartSendByteBuff = new byte[1024];
        public static int UsartSendByteOffset;                  ///发送缓存区的指针偏移量
        public static int UsartSendByteCount;                   ///发送帧数计数器
        //定义串口处理缓冲区  长度是10240字节
        public static byte[] UsartReceByteBuff = new byte[1024];
        public static int UsartReceByteBuff_len;
        //public static int UartReceByteBuff_offset;  //定义偏移量
        public static int UartReceByteBuff_offset_begin = 0;  //定义开始存储位置
        //串口接收数据的临时缓存区
        public static int UsartReceByteBuff_temp_len;      
        public static byte[] UsartReceByteBuff_temp;
        //定时开始标志
        public static bool TimerEnable = false;
        public static bool UartReceByteBuff_offset_begin_avilable = true;
        public static bool IsTimeOutData = false;
        public static bool IsReceiving  = true;        //用来判断这一帧数据接收完了没
        public static long CheckTime = 0;
        //定义接收超时
        public static int RecTimeOut = 10;     //单位/ms
        System.Timers.Timer DataRecTimer = new System.Timers.Timer(1);  //设计一个每1ms触发的定时事件,用来查看当前时间
                                                                        //这里最好是设为最小值1,因为timer类依赖于系统时钟,系统时钟分辨率可能大于
                                                                        //1ms,所以实际每次的定时触发可能是5ms左右
                                                                        //否则容易出现没有更新到IsReceiving,导致多帧数据在一次回调
        SerialPort sp ;


/// <summary>
        ///发生接收事件之后的回调函数 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// 注意,这个函数是被多个线程(多个串口)调用的,不同的sender用来区分不同的port
        /// 该函数不在主线程内,因此不能再该函数中直接访问界面控件,而是通过invoke或begininvoke来调用
        /// 串口接收原理:
        ///     1.串口的默认接收缓存为4096(可软件修改)
        ///     2.threshold设置为1,收到数据后触发received事件,并进入该函数
        ///     3.在该函数中使用DMA思想,设置自己的buffer与超时时间
        ///     4.满buffer或超时之后通过invoke回调,处理接收数据
        ///     如果使用while循环判断接收buffer满了没有,可能会出现该函数的多次重入问题
        ///     因此选择不断地读,写到自定义的buffer中,满了之后一起回调
        private void SpDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                IsReceiving = true;         //开始接收了
    
                //接收数据长度
                UsartReceByteBuff_temp_len = sp.BytesToRead;
                //申请对应的长度
                UsartReceByteBuff_temp = new byte[UsartReceByteBuff_temp_len];
                //读取所接收到的数据   使用字节读出  读出的数据存储到0开始的位置 
                sp.Read(UsartReceByteBuff_temp, 0, UsartReceByteBuff_temp_len);
                Buffer.BlockCopy(UsartReceByteBuff_temp, 0, UsartReceByteBuff, UartReceByteBuff_offset_begin, UsartReceByteBuff_temp_len);
                UartReceByteBuff_offset_begin += UsartReceByteBuff_temp_len;
                IsReceiving = false;    //接收完了,让定时器走起来
            }
            catch
            {
                AppendLineTestResult("串口硬件错误", false, true);
            }
        }

        /// <summary>
        /// 超时处理函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LastRecDataCall()
        {
            //定时器停止计数
            IsReceiving = true; 
            //这里就不用拷贝数据了直接进行回调处理
            IsTimeOutData = true;
            this.Invoke(new EventHandler(UART_receive_dealwith));
        }

        /// <summary>
        /// 函数被串口接收中断调用 接收长度 UsartReceByteBuff_temp_len 存储在 UsartReceByteBuff_temp
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UART_receive_dealwith(object sender, EventArgs e)
        {
            byte[] temp;
            try
            {
                //首先判断是否为超时数据,其次将数据快速复制到临时缓冲区处理
                temp = new byte[UartReceByteBuff_offset_begin];
                Buffer.BlockCopy(UsartReceByteBuff, 0, temp, 0, UartReceByteBuff_offset_begin);
                UartReceByteBuff_offset_begin = 0;

                //这里不解析数据了,只是把接收到的数据打印到界面显示区
                TxtRecData.Text += "接收到的数据:\r\n";
                string strReceived = "";
                {
                    int curByteIndex = 0;
                    for (curByteIndex = 0; curByteIndex < temp.Length; curByteIndex++)
                    {
                        strReceived += UsartReceByteBuff[curByteIndex].ToString("X2") + " ";
                    }
                }
                strReceived += "\r\n";
  
                if (TxtRecData.TextLength > 10000)
                {
                    TxtRecData.Text = strReceived;
                }
                else
                {
                    TxtRecData.Text += strReceived;
                }
                //获取焦点
                TxtRecData.Focus();
                //光标定位到文本最后
                TxtRecData.Select(TxtRecData.TextLength, 0);
                //滚动到光标处
                TxtRecData.ScrollToCaret();
            }
            catch (Exception Err)
            {
                int j = UsartReceByteBuff_temp_len;
                AppendLineTestResult(Err.Message + "数据接收错误@UART_receive_dealwith" + j.ToString("D3"), false, true);
            }
        }

总结

本文对上位机(C#实现)与M0内核下位机进行串口通信的原理和实现过程进行了描述,两部分的注意事项是实现过程中容易出现的问题,希望能帮助到大家。

### 回答1: 上位机是指在工业自动化控制系统中,作为人机交互接口的计算机,它通过连接到下位机(如PLC、DCS等)来实现对工艺过程的监控与控制。上位机在自动化生产过程中扮演着重要的角色。 上位机具有以下几个主要功能。首先,它通过图形化的界面向操作人员展示工艺过程的实时状态。操作人员可以通过上位机监控设备的运行情况,包括工艺参数、设备状态等,及时发现和处理异常情况。其次,上位机可以对工艺过程进行控制。操作人员可以通过上位机设定工艺参数,对设备进行启停、调节等操作,从而实现对过程的控制。此外,上位机还可以对过程数据进行采集、处理和记录,以便进行生产数据分析和质量统计。最后,上位机还能对工艺过程进行优化和调整,通过分析历史数据和运算,提出工艺改进的建议。 上位机的应用范围非常广泛。它可以用于各种行业的自动化控制系统,包括制造业、化工、石油、电力等。通过与下位机的通信,上位机可以实时监控和控制整个生产线的运行情况,提高生产效率和产品质量。此外,上位机还可以与企业级信息系统(如ERP)集成,实现工艺过程与企业管理的无缝对接,提高企业的整体竞争力。 总的来说,上位机是工业自动化控制系统中不可或缺的一部分,它通过实时监控和控制工艺过程,提高生产效率、质量和安全性,为企业的可持续发展做出重要贡献。 ### 回答2: 上位机C是一种与下位机连接的计算机,用于控制和监测下位机的操作。下位机可以是各种各样的设备,如机械设备、工业自动化设备等。上位机C通过与下位机的通信,可以实现对下位机的远程控制和监测。 上位机C的功能非常强大,可以实现多种操作,如远程启停下位机、设定参数、查看状态、实时数据监测等。通过使用上位机C,可以提高设备的自动化程度和生产效率,降低人工操作的错误率和工作强度。 上位机C通常具有友好的图形界面,操作简便,使用者可以快速上手。同时,上位机C通常具有强大的数据处理能力,可以对下位机采集到的数据进行分析和处理,生成相关的报表和图表,帮助用户更好地理解和管理设备的工作状态和性能。 此外,上位机C还具备数据存储功能,可以将采集到的数据存储在计算机中,方便用户进行后续的数据分析和查询。同时,上位机C还支持与其他软件和系统的集成,可以与企业的数据管理系统进行联动,实现数据的共享和共同利用。 总之,上位机C是一种重要的工业自动化设备,它能够实现对下位机的远程控制和监测,提高设备的自动化程度和生产效率。它具备友好的界面、强大的数据处理能力和数据存储功能,可以帮助用户更好地理解和管理设备的工作状态和性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值