使用C#以编程方式更改串口配置

6
我有两种不同协议的设备,它们通过一个串行端口连接。所谓协议是指串行端口配置不同。
我有一个协议ID p_id,可以检查当前正在读取哪个设备。以下是我的代码:
下面是我的主函数,调用了一个名为CombinedEngine的类。
 static class Program
 {
   private static CombinedEngine _eng;
   static async Task Main(string[] args)
    {
      try
      {
         _eng = new CombinedEngine();
      }
      catch (Exception ex)
      {
            Debug.WriteLine(ex.Message.ToString());
                //_log.Error(ex, ex.Message);
      }
    }
     while(true);
 }

合并引擎类

class CombinedEngine
{
   SerialPort port = new SerialPort();
   public CombinedEngine()
    {          

        try
        {
            
            var p = mdc.mdc_protocol.ToList();
            
            if(p.Count > 0)
            {
                foreach(var pr in p)
                {
                    var p_id = pr.protocol_id;

                    if(p_id=="01")//modbus
                    {
                        if (port.IsOpen)
                            port.Close();

                        port = new SerialPort("COM8", 9600, Parity.Even, 8, StopBits.One);
                        port.ReadTimeout = 500;
                        port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);                          
                        port.Open();

                        Console.WriteLine("Port opened successfully for modbus...");
                        Console.WriteLine("I am Recieving for modbus...");


                        var result = mdc.mdc_meter_config.Where(m => m.config_flag == 0)
                            .Where(m=>m.p_id == p_id).ToList();

                        if (result.Count > 0)
                        {
                            foreach (var item in result)
                            {
                                var iteration = new Iterations()
                                {
                                    hex = (string)item.m_hex,
                                    row_id = (string)item.row_id,
                                    device_id = (int)item.meter_id,
                                    protocol_id = (string)item.p_id,
                                    command_id = (string)item.command_id,
                                    config_flag = (int)item.config_flag,
                                    msn = (string)item.msn,
                                    time = (string)item.time
                                };
                                confList.Add(iteration);
                                time = Convert.ToDouble(item.time);
                            }

                            var modbus = confList.Where(x => x.protocol_id == "01").ToList();
                            
                            aTimer = new System.Timers.Timer();


                            // Create a timer...
                            aTimer = new System.Timers.Timer();
                            // Hook up the Elapsed event for the timer. 
                            aTimer.Interval = time * 1000.0;
                            aTimer.Elapsed += (sender, e) => MyModbusMethod(sender, e, modbus, aTimer);            
                            aTimer.AutoReset = true;
                            aTimer.Enabled = true;

                        }
                        else
                        {

                            Console.WriteLine("No Data available");
                        }
                    }
                    else if(p_id=="02")//ytl_bus
                    {
                        if (port.IsOpen)
                            port.Close();

                        port = new SerialPort("COM8", 38400, Parity.None, 8, StopBits.One);
                        port.ReadTimeout = 500;
                        port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
                        port.Open();

                        Console.WriteLine("Port opened successfully for ytlbus...");
                        Console.WriteLine("I am Recieving for ytlbus...");


                        var result = mdc.mdc_meter_config.Where(m => m.config_flag == 0)
                            .Where(m => m.p_id == p_id).ToList();

                        if (result.Count > 0)
                        {
                            foreach (var item in result)
                            {
                                var iteration = new Iterations()
                                {
                                    hex = (string)item.m_hex,
                                    row_id = (string)item.row_id,
                                    device_id = (int)item.meter_id,
                                    protocol_id = (string)item.p_id,
                                    command_id = (string)item.command_id,
                                    config_flag = (int)item.config_flag,
                                    msn = (string)item.msn,
                                    time = (string)item.time
                                };
                                confList.Add(iteration);
                                time = Convert.ToDouble(item.time);
                            }

                            
                            var ytlbus = confList.Where(x => x.protocol_id == "02").ToList();

                            

                            aTimer = new System.Timers.Timer();


                            // Create a timer...
                            aTimer = new System.Timers.Timer();
                            // Hook up the Elapsed event for the timer. 
                            aTimer.Interval = time * 1000.0;
                            aTimer.Elapsed += (sender, e) => MyElapsedMethod(sender, e,ytlbus , aTimer);            
                            aTimer.AutoReset = true;
                            aTimer.Enabled = true;

                        }
                        else
                        {

                            Console.WriteLine("No Data available");
                        }

                    }

                   
                    
                   
                }
               
            }
           

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error at Line " + LineNumber(), ex.Message.ToString());
            throw ex;
        }
       
       
    }


}

在上述代码中,我已经检查了如果p_id等于01,那么应该进行modbus串口配置。但是,如果p_id02,则应遇到ytlbus串口配置。这两个设备具有不同的波特率和奇偶校验位。因此,我尝试对它们进行设置。
另外,我有一个计时器,它是60秒。因此,在每个60秒后,下一个计时器将被初始化。
例如,如果p_id01,则代码将设置波特率为9600,并将奇偶校验位设置为Even。然后调用SerialDataRecievedEventHandler,它将检查设备是否有任何传入数据,并负责将数据转储到DB中。
然后,代码将从表格mdc_meter_config中检查设备详细信息,并从中提取相关信息。对于所有设备,所有细节都逐一添加到列表中。同时,时间也会被记录。在这种情况下,所有设备的时间都是相同的,即60秒。
然后,将该列表传递给一个变量,然后将其传递给ElapsedEventHandler函数。它处理frame发送。
对于p_id等于02,将执行相同的操作,唯一的区别是将波特率设置为38400,奇偶校验设置为None
我面临的问题是以上代码运行时,我遇到的问题是两个条件同时起作用。即对于01,它将起作用,然后同时跳转到02条件。下面是图片。

enter image description here

它应该完成任何p_id值的工作,然后完成其他p_id值的工作。 更新1 我已经更新了我的代码。添加了一个新的async函数,仅添加了一个计时器,并为串口扩展添加了一个类。
    public static class SerialPortExtensions
{
    public async static Task ReadAsync(this SerialPort serialPort, byte[] buffer, int offset, int count)
    {
        var bytesToRead = count;
        var temp = new byte[count];

        while (bytesToRead > 0)
        {
            var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);
            Array.Copy(temp, 0, buffer, offset + count - bytesToRead, readBytes);
            bytesToRead -= readBytes;
        }
    }

    public async static Task<byte[]> ReadAsync(this SerialPort serialPort, int count)
    {
        var buffer = new byte[count];
        await serialPort.ReadAsync(buffer, 0, count);
        return buffer;
    }
}

public CombinedEngine()
    {
        try
        {
            var p = mdc.mdc_protocol.ToList();

            if (p.Count > 0)
            {
                foreach (var pr in p)
                {
                    var p_id = pr.protocol_id;

                    if (p_id == "01")//modbus
                    {
                        if (port.IsOpen)
                            port.Close();
                        comm = true;
                        port = new SerialPort("COM8", 9600, Parity.Even, 8, StopBits.One);
                        port.ReadTimeout = 500;
                        //port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
                        port.Open();
                        Work();

                        Console.WriteLine("Port opened successfully for modbus...");
                        Console.WriteLine("I am Recieving for modbus...");
                        
                    }
                    else if (p_id == "02")//ytl_bus
                    {
                        if (port.IsOpen)
                            port.Close();
                        comm = true;
                        port = new SerialPort("COM8", 38400, Parity.None, 8, StopBits.One);
                        port.ReadTimeout = 500;
                        //port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
                        port.Open();
                        Work();

                        Console.WriteLine("Port opened successfully for ytlbus...");
                        Console.WriteLine("I am Recieving for ytlbus...");                          

                    }
                    var result = mdc.mdc_meter_config.Where(m => m.config_flag == 0).ToList();
                    if (result.Count > 0)
                    {
                        foreach (var item in result)
                        {
                            var iteration = new Iterations()
                            {
                                hex = (string)item.m_hex,
                                row_id = (string)item.row_id,
                                device_id = (int)item.meter_id,
                                protocol_id = (string)item.p_id,
                                command_id = (string)item.command_id,
                                config_flag = (int)item.config_flag,
                                msn = (string)item.msn,
                                time = (string)item.time
                            };
                            confList.Add(iteration);
                            time = Convert.ToDouble(item.time);
                        }

                        var modbus = confList.Where(x => x.protocol_id == "01").ToList();
                        var ytlbus = confList.Where(x => x.protocol_id == "02").ToList();

                        //ModbusMethod(modbus);

                        aTimer = new System.Timers.Timer();
                        // Create a timer...
                        aTimer = new System.Timers.Timer();
                        // Hook up the Elapsed event for the timer. 
                        aTimer.Interval = time * 1000.0;
                        aTimer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, ytlbus, modbus, aTimer);
                        //aTimer.Elapsed += OnTimedEvent(iterations, dataItems);            
                        aTimer.AutoReset = true;
                        aTimer.Enabled = true;

                    }
                    else
                    {

                        //Console.WriteLine("No Data available");
                    }

                }

            }


        }
        catch (Exception ex)
        {
            Console.WriteLine("Error at Line " + LineNumber(), ex.Message.ToString());
            throw ex;
        }
        finally
        {
        }


    }

public async void Work()
    {
        try
        {
            var data = await port.ReadAsync(4096);
            Console.WriteLine("Data at Line " + LineNumber(), data.ToString());
            //DoStuff(data);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error at Line " + LineNumber(), ex.Message.ToString());
        }
    }

我现在遇到的错误是 由于线程退出或应用程序请求,I/O 操作已中止。

在System.IO.Ports.InternalResources.WinIOError(Int32 errorCode, String str)处发生错误 在System.IO.Ports.SerialStream.EndRead(IAsyncResult asyncResult)处终止读取操作 在System.IO.Stream.<>c.b__43_1(Stream stream, IAsyncResult asyncResult)处 在System.Threading.Tasks.TaskFactory<code>1.FromAsyncTrimPromise</code>1.Complete(TInstance thisRef, Func<code>3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)处 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)处抛出非成功的任务 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)处处理非成功和调试器通知的任务 在CommunicationProfile.SerialPortExtensions.d__0.MoveNext()处,位于F:\MDC Development\Scheduler\CommunicationProfile\CombinedEngine.cs第1198行 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)处抛出非成功的任务 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)处处理非成功和调试器通知的任务 在System.Runtime.CompilerServices.TaskAwaiter.GetResult()处获取结果 在CommunicationProfile.SerialPortExtensions.d__1.MoveNext()处,位于F:\MDC Development\Scheduler\CommunicationProfile\CombinedEngine.cs第1207行 在System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)处抛出非成功的任务 在System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)处处理非成功和调试器通知的任务 在System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()处获取结果 在CommunicationProfile.CombinedEngine.d__27.MoveNext()处,位于F:\MDC Development\Scheduler\CommunicationProfile\CombinedEngine.cs第368行

错误发生在下面的行。
var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);//1198 line
await serialPort.ReadAsync(buffer, 0, count);//1207 line 
var data = await port.ReadAsync(4096); // 368 line

注意:上述方法应在设备通电后持续运行,并在每个60秒后发送其数据。

非常感谢您的帮助。


1
据我所知,您不能同时在同一COM端口上拥有两个外设。 - Fildor
1
while(true); ... 烤一下CPU。尝试使用 Console.ReadLine(); 代替。 - Fildor
3
串口通信不是那样工作的。这就像试图将两根电缆插入同一个端口。如果你有两个外围设备,你需要两个端口,无论它们是物理的还是逻辑的。 - Fildor
5
它可以使用RS485多点连接而不是通用的RS232C。这种硬件方法经常用于工业设备。在这种情况下,至少需要共同确定要进行通信的设备编号/地址的协议。但是,可能已经存在某种方法。这些是您要使用的特定系统问题,而不是像这个网站那样的通用知识问题。与您设备的零售商、供应商和系统顾问交谈。 - kunif
4
你有双路RS-485转RS-232的引脚映射吗?我认为你不能以你现在的方式实现你想要做的事情。你试图在一个串行端口上以不同的速度读取两种不同的串行协议。你需要以最高速度读入整个流(幸运的是它是低速度的倍数),然后在代码中进行解多路复用。如果你说服硬件工程师给你2个串口,这会容易得多。有2个串口,编写代码只需要几个小时。如果只有1个串口,编写代码可能需要几个月,并且仍然不太可靠。 - justjoshin
显示剩余19条评论
1个回答

2
您的代码最后一次修改的主要问题是您在不使用await的情况下调用了Work(),因此该调用只会创建一个异步后台任务,而不等待其完成。此外,这个功能不应该存在于构造函数中,而应该存在于一个单独的async方法中。
第二个建议是从循环中删除if/switch语句,并将区分这些协议所需的数据放置在一个单独的类中。您可以将每个协议所需的任何其他属性放置在此类中:
// contains specific settings for each ProtocolId
class ProtocolCfg
{
    public string ProtocolId { get; set; }
    public string PortName { get; set; }
    public int BaudRate { get; set; }
    public Parity Parity { get; set; }
    public int DataBits { get; set; }
    public StopBits StopBits { get; set; }

    public ProtocolCfg(string id, string port, int baud, Parity parity, int bits, StopBits stop)
    {
        ProtocolId = id; PortName = port; BaudRate = baud; Parity = parity;
        DataBits = bits; StopBits = stop;
    }
}

有了这个设置,你的for循环就不需要区分这些协议:

class CombinedEngine
{
    readonly ProtocolCfg[] _portConfigs;

    public CombinedEngine(ProtocolCfg[] portConfigs)
    {
        // just assign the field and do nothing else
        _portConfigs = portConfigs;
    }

    public async Task Run(CancellationToken cancelToken)
    {
        // repeat indefinitely
        while (!cancelToken.IsCancellationRequested)
        {
            // run all protocols
            foreach (var portcfg in _portConfigs)
            {
                SerialPort serialPort = null;

                try
                {
                    // init using current config
                    serialPort = new SerialPort(
                         portcfg.PortName, portcfg.BaudRate, portcfg.Parity,
                         portcfg.DataBits, portcfg.StopBits);

                    serialPort.ReadTimeout = 500;

                    // await data
                    var data = await serialPort.ReadAsync(4096);

                    // do something with this data
                    Console.WriteLine($"P{portcfg.ProtocolId}: {data.Length}B received");

                    // do other stuff here

                    // wait between protocol changes if needed?
                    await Task.Delay(500, cancelToken);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                finally
                {
                    serialPort?.Close();
                    serialPort?.Dispose();
                }
            }

            // wait between iterations?
            await Task.Delay(500, cancelToken);
        }
    }
}

在调用Run函数时,请记住它是异步的,因此您需要调用await。但是,您可能还想在控制台内等待按键,因此在这种情况下,您将把返回的Task存储在变量中,并在需要时取消它:

class Program
{
    static void Main(string[] args)
    {
        // define all possible protocols
        var protocols = new[]
        {
            new ProtocolCfg("01", "COM8",  9600, Parity.Even, 8, StopBits.One),
            new ProtocolCfg("02", "COM8", 38400, Parity.None, 8, StopBits.One)
        };

        // we will need this to tell the async task to end
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token; 

        // note that this constructor does not do anything of importance
        var engine = new CombinedEngine(protocols);

        // this is where all the work is done, pass the cancellation token 
        var task = engine.Run(token);

        // wait until Q is pressed
        Console.WriteLine("Running, press Q to quit... ");
        ConsoleKey k;
        do { k = Console.ReadKey().Key; }
        while (k != ConsoleKey.Q);

        // shutdown
        tokenSource.Cancel();
        task.Wait();            
        Console.WriteLine("Done.");
    }        
}

我想要补充的一件事是,我已经添加了一个定时器,在每隔N分钟(比如1分钟)后启用。我该如何将其管理/添加到您提供的解决方案中? - Moeez
@Moeez: 你能否稍微澄清一下,即每个“N”分钟这个计时器是用来做什么的?考虑到循环实现的方式,重要的是要澄清这个周期性任务会如何与串口在三种不同状态(关闭,连接9600波特率,连接38400波特率)交互。例如,可以在函数末尾使用Task.Delay()调用等待1分钟,而不是使用计时器。 - vgru

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接