C#转盘电机的上位机数据采集仿真模拟,使用多个异步任务

本文是一种仿真模拟软件,适应于转盘型电机。主要功能:启动,运行【旋转60°】,清料。下料时保存相关生产数据

假设一个转盘型电机,共有6个工位,分别执行动作:【A】上料、【B】扫码、【C】采集轮廓仪数据、【D】拍照、【E】焊接、【F】下料。每旋转60°后,6个工位同时开始工作,所有工位都完成以后,方可执行下一步的转动60°操作。因此需要定义一个类TurntableData数组,固定含有6个数组。

一、新建windows窗体应用程序TurntableDemo。【.net framwork 4.5】,将默认的Form1窗体重命名为FormTurntable。

窗体FormTurntable设计如图:

二、新建转盘所有数据对象类TurntableData.cs,源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TurntableDemo
{
    /// <summary>
    /// 转盘【六个工位】所有数据组成的统一对象
    /// </summary>
    public class TurntableData
    {
        /// <summary>
        /// 【A】上料:自增的唯一编号
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 【B】扫码:通过扫码枪获取二维码条码
        /// </summary>
        public string Barcode { get; set; }
        /// <summary>
        /// 【C】采集轮廓仪数据
        /// </summary>
        public double ContourData { get; set; }
        /// <summary>
        /// 【D】拍照相机偏移数据X
        /// </summary>
        public double PhotoX { get; set; }
        /// <summary>
        /// 【D】拍照相机偏移数据Y
        /// </summary>
        public double PhotoY { get; set; }
        /// <summary>
        /// 【E】焊接工位:焊接功率
        /// </summary>
        public double WeldPower { get; set; }
        /// <summary>
        /// 【E】焊接工位:焊接速度
        /// </summary>
        public double WeldSpeed { get; set; }
        /// <summary>
        /// 【F】下料工位:生产完成时间
        /// </summary>
        public DateTime ProductTime { get; set; }
    }
}
三、新建保存生产数据文件类CsvUtil.cs,源程序如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TurntableDemo
{
    /// <summary>
    /// Csv操作类
    /// </summary>
    public class CsvUtil
    {
        private static Object thisLock = new Object();
        /// <summary>
        /// 写入csv文件
        /// </summary>
        /// <param name="_path">路径,如 @"D:\MESLog\ABC\"</param>
        /// <param name="_name">名称,不带.csv</param>
        /// <param name="_writeData">需要写入的数据</param>
        /// <param name="_append">是否拼接</param>
        /// <returns></returns>
        public static bool WriteCsv(string _path, string _name, List<string[]> _writeData, bool _append)
        {
            lock (thisLock)
            {
                try
                {
                    //判断文件夹是否存在,不存在就创建
                    DirectoryInfo directoryInfo = new DirectoryInfo(_path);
                    if (!directoryInfo.Exists)
                    {
                        directoryInfo.Create();
                    }
                    
                    string _tmPath = Path.Combine(_path, _name) + ".csv";
                    using (StreamWriter write = new StreamWriter(_tmPath, _append, Encoding.Default))
                    {
                        foreach (String[] strArr in _writeData)
                        {
                            write.WriteLine(String.Join(",", strArr));
                        }
                    }
                    return true;
                }
                catch (Exception e)
                {
                    System.Windows.Forms.MessageBox.Show("CSV文件写入失败:" + e.Message);
                    return false;
                }
            }
        }

        /// <summary>
        /// 保存转盘生产数据到csv文件
        /// </summary>
        /// <param name="alarmData"></param>
        /// <returns></returns>
        public static bool SaveProductData(TurntableData turntableData)
        {
            try
            {
                string path = AppDomain.CurrentDomain.BaseDirectory + "ProductData\\";
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                string fileName = DateTime.Now.ToString("yyyy-MM-dd");
                //写字段名
                List<string> listName = new List<string>();
                listName.Add("编号");
                listName.Add("条码");       
                listName.Add("轮廓数据");
                listName.Add("拍照X偏移");
                listName.Add("拍照Y偏移");
                listName.Add("焊接输出功率");
                listName.Add("焊接速度");
                listName.Add("生产时间");
                List<string[]> iniList = new List<string[]>();
                iniList.Add(listName.ToArray());
                bool ret = File.Exists(path + fileName + ".csv");
                if (!ret)
                {
                    bool ret2 = WriteCsv(path, fileName, iniList, false);
                    if (!ret2)
                    {
                        throw new Exception("生产日志字段名转换为csv格式失败,请检查文件是否被打开、被占用:" + path);
                    }
                }
                iniList.Clear();
                List<string> listData = new List<string>();
                listData.Add(turntableData.Id.ToString());
                listData.Add(turntableData.Barcode);
                listData.Add(turntableData.ContourData.ToString());
                listData.Add(turntableData.PhotoX.ToString());
                listData.Add(turntableData.PhotoY.ToString());
                listData.Add(turntableData.WeldPower.ToString());
                listData.Add(turntableData.WeldSpeed.ToString());
                listData.Add(turntableData.ProductTime.ToString("yyyy-MM-dd HH:mm:ss"));

                iniList.Add(listData.ToArray());
                bool rtn = WriteCsv(path, fileName, iniList, true);
                if (!rtn)
                {
                    throw new Exception("生产厂日志具体信息转换为本地csv失败,请检查文件是否被打开、被占用:" + path);
                }
                return true;
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(string.Format("保存生产日志出现异常:{0}", ex.Message), "出错");
                return false;
            }
        }
    }
}
四、新建转盘电机每旋转一次,执行的主要流程逻辑类RotateUtil.cs,源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TurntableDemo
{
    /// <summary>
    /// 旋转60°执行的操作
    /// </summary>
    public class RotateUtil
    {
        /// <summary>
        /// 六个转盘数据数组,每旋转60°,所有元素的对象都要更新
        /// 依次代表A、B、C、D、E、F工位
        /// </summary>
        public static TurntableData[] TurntableDataArray = new TurntableData[6];
        /// <summary>
        /// A上料工位对应的编号数据
        /// </summary>
        private static int CurrentId = 0;

        /// <summary>
        /// 当前第几次旋转60°操作
        /// </summary>
        public static int Sequence = 0;
        /// <summary>
        /// 是否触发清料
        /// </summary>
        public static bool TrigCleaning = false;

        /// <summary>
        /// 初始化转盘
        /// </summary>
        public static void InitTurntable()
        {
            Sequence = 0;
            TrigCleaning = false;
            for (int i = 0; i < TurntableDataArray.Length; i++)
            {
                TurntableDataArray[i] = new TurntableData()
                {
                    Id = -1,
                    Barcode = string.Empty
                };
            }            
        }

        /// <summary>
        /// 电机转动一次的数据处理
        /// 关键逻辑:①同时启动6个任务,并等待6个任务全部完成 ②依次进行偏移一个索引赋值,并修改当前值 ③保存数据,转动次数加一
        /// </summary>
        public static bool Rotate()
        {
            TurntableData turntable = new TurntableData();
            //一、同时开始六个工位的任务
            Task taskA = Task.Factory.StartNew(() => 
            {
                if (TrigCleaning)
                {
                    //当触发清料时,强行置默认值-1
                    turntable.Id = -1;
                }
                else
                {
                    turntable.Id = Feeding();
                }
            });
            Task taskB = Task.Factory.StartNew(() => 
            {
                if (Sequence >= 1)
                {
                    turntable.Barcode = GetBarcode();
                }
            });
            Task taskC = Task.Factory.StartNew(() => 
            {
                if (Sequence >= 2)
                {
                    turntable.ContourData = GetContourData();
                }
            });
            Task taskD = Task.Factory.StartNew(() => 
            {
                if (Sequence >= 3)
                {
                    double photoX;
                    double photoY;
                    GetPhotoData(out photoX, out photoY);
                    turntable.PhotoX = photoX;
                    turntable.PhotoY = photoY;
                }
            });
            Task taskE = Task.Factory.StartNew(() => 
            {
                if (Sequence >= 4)
                {
                    double weldPower;
                    double weldSpeed;
                    GetWeldData(out weldPower, out weldSpeed);
                    turntable.WeldPower = weldPower;
                    turntable.WeldSpeed = weldSpeed;
                }
            });
            Task taskF = Task.Factory.StartNew(() => 
            {
                if (Sequence >= 5)
                {
                    turntable.ProductTime = Blanking();
                }
            });

            //二、等待六个工位完成任务 在6秒内,是否已完成执行所有任务
            int millisecondsTimeout = 6000;
            bool completedExecute = Task.WaitAll(new Task[] { taskA, taskB, taskC, taskD, taskE, taskF }, millisecondsTimeout);
            if (!completedExecute)
            {
                MessageBox.Show($"等待【{millisecondsTimeout}】ms六个工位存在尚未完成,请检查", "超时");
                return false;
            }

            //三、依次进行偏移一个索引赋值 A->B->C->D->E->F
            for (int i = TurntableDataArray.Length - 1; i > 0; i--)
            {
                TurntableDataArray[i].Id = TurntableDataArray[i - 1].Id;
                TurntableDataArray[i].Barcode = TurntableDataArray[i - 1].Barcode;
                TurntableDataArray[i].ContourData = TurntableDataArray[i - 1].ContourData;
                TurntableDataArray[i].PhotoX = TurntableDataArray[i - 1].PhotoX;
                TurntableDataArray[i].PhotoY = TurntableDataArray[i - 1].PhotoY;
                TurntableDataArray[i].WeldPower = TurntableDataArray[i - 1].WeldPower;
                TurntableDataArray[i].WeldSpeed = TurntableDataArray[i - 1].WeldSpeed;
                TurntableDataArray[i].ProductTime = TurntableDataArray[i - 1].ProductTime;
            }
            //修正当前值
            TurntableDataArray[0].Id = turntable.Id;
            TurntableDataArray[1].Barcode = turntable.Barcode;
            TurntableDataArray[2].ContourData = turntable.ContourData;
            TurntableDataArray[3].PhotoX = turntable.PhotoX;
            TurntableDataArray[3].PhotoY = turntable.PhotoY;
            TurntableDataArray[4].WeldPower = turntable.WeldPower;
            TurntableDataArray[4].WeldSpeed = turntable.WeldSpeed;
            TurntableDataArray[5].ProductTime = turntable.ProductTime;

            //四、保存数据,只用保存最后一个数据
            if (TurntableDataArray[5].Id != -1)
            {
                CsvUtil.SaveProductData(TurntableDataArray[5]);
            }

            //转台 转动次数加1
            Sequence++;
            return true;
        }

        #region 六个工位分别执行的动作
        /// <summary>
        /// A工位上料,用时100~500ms
        /// </summary>
        /// <returns></returns>
        public static int Feeding()
        {
            Thread.Sleep(GetRandomTimespan(100, 500));
            Interlocked.Add(ref CurrentId, 1);
            return CurrentId;
        }

        /// <summary>
        /// B工位扫码,用时200~3000ms
        /// </summary>
        /// <returns></returns>
        public static string GetBarcode()
        {
            Thread.Sleep(GetRandomTimespan(200, 3000));
            return System.IO.Path.GetRandomFileName();
        }

        /// <summary>
        /// C工位采集轮廓仪数据,用时300~1800ms
        /// </summary>
        /// <returns></returns>
        public static double GetContourData()
        {
            Thread.Sleep(GetRandomTimespan(300, 1800));
            return new Random().Next(-100, 101) * 0.001;
        }

        /// <summary>
        /// D工位 获取拍照偏移数据,用时700~2400ms
        /// </summary>
        /// <param name="photoX"></param>
        /// <param name="photoY"></param>
        public static void GetPhotoData(out double photoX, out double photoY)
        {
            Thread.Sleep(GetRandomTimespan(700, 2400));
            photoX = new Random().Next(-300, 301) * 0.01;
            photoY = new Random().Next(-200, 201) * 0.01;
        }

        /// <summary>
        /// E工位 获取焊接相关数据,用时2200~5000ms
        /// </summary>
        /// <param name="weldPower"></param>
        /// <param name="weldSpeed"></param>
        public static void GetWeldData(out double weldPower, out double weldSpeed)
        {
            Thread.Sleep(GetRandomTimespan(2200, 5000));
            weldPower = new Random().Next(400, 601);
            weldSpeed = new Random().Next(100, 301);
        }

        /// <summary>
        /// F工位 下料
        /// </summary>
        public static DateTime Blanking()
        {
            Thread.Sleep(100);
            return DateTime.Now;
        }
        #endregion

        /// <summary>
        /// 获得一个指定范围内的随机时间间隔,单位:毫秒
        /// </summary>
        /// <param name="minValue"></param>
        /// <param name="maxValue"></param>
        /// <returns></returns>
        private static int GetRandomTimespan(int minValue, int maxValue)
        {
            return new Random(Guid.NewGuid().GetHashCode()).Next(minValue, maxValue);
        }
    }
}
五、窗体FormTurntable.cs的关键逻辑代码【事件】如下(忽略设计器自动生成的代码):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TurntableDemo
{
    public partial class FormTurntable : Form
    {
        /*
         * 一个转盘型电机,共有6个工位,分别执行动作:【A】上料、【B】扫码、【C】采集轮廓仪数据、【D】拍照、【E】焊接、【F】下料
         * 每旋转60°后,6个工位同时开始工作,所有工位都完成以后,方可执行下一步的转动60°操作
         * 【A】上料:自增的唯一编号int Id
         * 【B】扫码:通过扫码枪获取二维码条码 string Barcode
         * 【C】采集轮廓仪数据 double ContourData
         * 【D】拍照相机偏移数据 double PhotoX,double PhotoY
         * 【E】焊接功率,焊接速度 double WeldPower,double WeldSpeed
         * 【F】下料,生产完成时间,记录数据到本地,出站 DateTime ProductTime
         * 因此,需要定义一个结构数组,【所有工位数据组成的结构】,共有6个元素,每次旋转60°,所有结构数据都要更新
        */

        public FormTurntable()
        {
            InitializeComponent();
        }

        private void FormTurntable_Load(object sender, EventArgs e)
        {
            btnStart.Enabled = true;
            btnNext.Enabled = false;
            btnCleaning.Enabled = false;
        }        

        private void btnStart_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = false;
            btnNext.Enabled = true;
            btnCleaning.Enabled = true;
            RotateUtil.InitTurntable();
            DisplayMessage("初始化转盘成功...");
            DisplayMessage($"启动转盘电机,已执行【A】上料。当前序列【{RotateUtil.Sequence}】");
        }

        private void btnNext_Click(object sender, EventArgs e)
        {
            DisplayMessage($"开始同时执行6个工位操作,执行中...");
            Application.DoEvents();
            RotateAndPrintMessage();
        }

        private void btnCleaning_Click(object sender, EventArgs e)
        {
            RotateUtil.TrigCleaning = true;
            DisplayMessage($"当前开始进行清料操作,当前序列【{RotateUtil.Sequence}】");
            btnNext.Enabled = false;
            btnCleaning.Enabled = false;
            Application.DoEvents();
            //这里执行清料逻辑
            while (ExistProduct(RotateUtil.TurntableDataArray))
            {
                RotateAndPrintMessage();
            }

            btnStart.Enabled = true;
            RotateUtil.TrigCleaning = false;
            DisplayMessage("清料完成");
        }

        /// <summary>
        /// 是否存在产品未出站,所有的产品都出站完成,则清料完成
        /// </summary>
        /// <param name="turntableDataArray"></param>
        /// <returns></returns>
        private bool ExistProduct(TurntableData[] turntableDataArray)
        {
            for (int i = 0; i < turntableDataArray.Length; i++)
            {
                if (turntableDataArray[i].Id != -1)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 转盘转动60°并打印相关信息
        /// </summary>
        private void RotateAndPrintMessage()
        {
            bool result = RotateUtil.Rotate();
            DisplayMessage($"当前转盘执行完成:【{result}】,当前第【{RotateUtil.Sequence}】次转动");
            Application.DoEvents();
            for (int i = 0; i < RotateUtil.TurntableDataArray.Length; i++)
            {
                DisplayMessage(RotateUtil.TurntableDataArray[i], i);
            }
            Application.DoEvents();
        }

        /// <summary>
        /// 显示富文本框的消息
        /// </summary>
        /// <param name="message"></param>
        private void DisplayMessage(string message)
        {
            this.BeginInvoke(new Action(() =>
            {
                if (rtxtDisplay.TextLength >= 102400)
                {
                    rtxtDisplay.Clear();
                }
                rtxtDisplay.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} --> {message}\n");
                rtxtDisplay.ScrollToCaret();
            }));
        }

        /// <summary>
        /// 显示当前工位数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="index"></param>
        private void DisplayMessage(TurntableData data, int index)
        {
            List<string> list = new List<string>();
            list.Add($"编号:【{data.Id}】");
            list.Add($"条码:【{data.Barcode}】");
            list.Add($"轮廓数据:【{data.ContourData}】");
            list.Add($"拍照X偏移:【{data.PhotoX}】");
            list.Add($"拍照Y偏移:【{data.PhotoY}】");
            list.Add($"焊接输出功率:【{data.WeldPower}】");
            list.Add($"焊接速度:【{data.WeldSpeed}】");
            list.Add($"生产时间:【{data.ProductTime}】");
            if (data.Id == -1)
            {
                DisplayMessage($"当前第【{(index + 1)}】个工位数据:暂无数据");
            }
            else
            {
                DisplayMessage($"当前第【{(index + 1)}】个工位数据:{string.Join(",", list)}");
            }
        }
    }
}
六、程序运行如图:

执行清料后:

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个基于C#异步采集CyUSB3014数据的示例代码: ```csharp using System; using System.Threading.Tasks; using CyUSB; namespace CyUSB3014DataAcquisition { class Program { static async Task Main(string[] args) { // Find the first available CyUSB device USBDeviceList usbDevices = new USBDeviceList(CyConst.DEVICES_CYUSB); if (usbDevices.Count == 0) { Console.WriteLine("No available CyUSB device found."); return; } CyUSBDevice usbDevice = usbDevices[0] as CyUSBDevice; // Set up the endpoint for data acquisition CyBulkEndPoint inEndpoint = usbDevice.EndPointOf(0x81) as CyBulkEndPoint; inEndpoint.TimeOut = 1000; // Start asynchronous data acquisition byte[] buffer = new byte[inEndpoint.MaxPktSize]; while (true) { int bytesRead = await Task.Run(() => inEndpoint.XferData(ref buffer, inEndpoint.MaxPktSize)); if (bytesRead > 0) { // Process the acquired data here Console.WriteLine($"Acquired {bytesRead} bytes of data."); } else { Console.WriteLine("Failed to acquire data."); } } } } } ``` 该代码首先使用 `USBDeviceList` 类从系统中找到第一个可用的 `CyUSB` 设备,然后使用 `CyUSBDevice` 和 `CyBulkEndPoint` 类设置用于数据采集的端点。最后,使用 `Task.Run()` 方法在异步线程中开始数据采集,并在每次采集到数据时处理该数据。 这只是一个简单的示例,实际应用中可能需要配置更多的设备参数、采集模式和数据处理方式。建议在实际应用中根据需要进行修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斯内科

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值