C#与倍福PLC(通过ADS协议)通信上位机源程序实现

一、项目结构

├── PLCControlDemo/
│   ├── Models/
│   │   └── PlcVariable.cs       # PLC变量模型
│   ├── Services/
│   │   └── AdsCommunicationService.cs  # ADS通信服务
│   ├── Views/
│   │   ├── MainWindow.xaml      # 主界面
│   │   └── DashboardView.xaml   # 仪表盘视图
│   └── App.xaml.cs            # 应用入口

二、核心代码实现

1. PLC变量模型(Models/PlcVariable.cs)

public class PlcVariable : INotifyPropertyChanged
{
    private object _value;
    public string Name { get; }
    public string Description { get; }
    public object Value
    {
        get => _value;
        set
        {
            _value = value;
            OnPropertyChanged(nameof(Value));
        }
    }

    public PlcVariable(string name, string description)
    {
        Name = name;
        Description = description;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2. ADS通信服务(Services/AdsCommunicationService.cs)

using TwinCAT.Ads;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class AdsCommunicationService : IDisposable
{
    private readonly TcAdsClient _adsClient;
    private readonly string _amsNetId = "192.168.0.1.1.1"; // PLC的AMS Net ID
    private readonly int _port = 851; // 默认ADS端口

    public event EventHandler<AdsEventArgs> ConnectionStateChanged;

    public AdsCommunicationService()
    {
        _adsClient = new TcAdsClient();
    }

    public async Task ConnectAsync()
    {
        try
        {
            await _adsClient.ConnectAsync(_amsNetId, _port);
            ConnectionStateChanged?.Invoke(this, new AdsEventArgs(ConnectionState.Connected));
        }
        catch (AdsException ex)
        {
            ConnectionStateChanged?.Invoke(this, new AdsEventArgs(ConnectionState.Failed, ex.Message));
            throw;
        }
    }

    public async Task<T> ReadVariableAsync<T>(string variableName) where T : struct
    {
        var handle = await _adsClient.CreateVariableHandleAsync(variableName);
        var value = await _adsClient.ReadAnyAsync(handle, typeof(T));
        _adsClient.DeleteVariableHandle(handle);
        return (T)value;
    }

    public async Task WriteVariableAsync<T>(string variableName, T value) where T : struct
    {
        var handle = await _adsClient.CreateVariableHandleAsync(variableName);
        await _adsClient.WriteAnyAsync(handle, value);
        _adsClient.DeleteVariableHandle(handle);
    }

    public void Disconnect()
    {
        _adsClient?.Disconnect();
    }

    public void Dispose()
    {
        _adsClient?.Dispose();
    }
}

public enum ConnectionState { Disconnected, Connected, Failed }
public class AdsEventArgs : EventArgs
{
    public ConnectionState State { get; }
    public string ErrorMessage { get; }

    public AdsEventArgs(ConnectionState state)
    {
        State = state;
    }

    public AdsEventArgs(ConnectionState state, string message)
    {
        State = state;
        ErrorMessage = message;
    }
}

3. 主界面逻辑(Views/MainWindow.xaml.cs)

using System.Windows;
using System.Windows.Controls;

namespace PLCControlDemo.Views
{
    public partial class MainWindow : Window
    {
        private readonly AdsCommunicationService _plcService = new AdsCommunicationService();
        private readonly Dictionary<string, PlcVariable> _variables = new Dictionary<string, PlcVariable>();

        public MainWindow()
        {
            InitializeComponent();
            InitializeVariables();
            DataContext = this;
            _plcService.ConnectionStateChanged += OnConnectionStateChanged;
        }

        private void InitializeVariables()
        {
            // 定义PLC变量映射
            _variables.Add("MAIN.Temperature", new PlcVariable("Temperature", "温度值"));
            _variables.Add("MAIN.StartCommand", new PlcVariable("StartCommand", "启动指令"));
        }

        private async void ConnectButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                await _plcService.ConnectAsync();
                UpdateStatus("已连接");
            }
            catch (Exception ex)
            {
                UpdateStatus($"连接失败: {ex.Message}");
            }
        }

        private async void ReadButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                var temp = await _plcService.ReadVariableAsync<double>("MAIN.Temperature");
                TemperatureText.Text = temp.ToString("0.00");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"读取失败: {ex.Message}");
            }
        }

        private async void WriteButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                await _plcService.WriteVariableAsync("MAIN.StartCommand", true);
                StartCommandText.Text = "已启动";
            }
            catch (Exception ex)
            {
                MessageBox.Show($"写入失败: {ex.Message}");
            }
        }

        private void OnConnectionStateChanged(object sender, AdsEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                ConnectionStatus.Background = e.State switch
                {
                    ConnectionState.Connected => System.Windows.Media.Brushes.Green,
                    ConnectionState.Failed => System.Windows.Media.Brushes.Red,
                    _ => System.Windows.Media.Brushes.Gray
                };
                
                ConnectionStatusText.Text = e.State.ToString() + 
                    (string.IsNullOrEmpty(e.ErrorMessage) ? "" : $"\n错误: {e.ErrorMessage}");
            });
        }

        private void UpdateStatus(string message)
        {
            StatusText.Text = message;
        }

        protected override void OnClosed(EventArgs e)
        {
            _plcService.Disconnect();
            base.OnClosed(e);
        }
    }
}

4. 主界面XAML(Views/MainWindow.xaml)

<Window x:Class="PLCControlDemo.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="倍福PLC控制面板" Height="450" Width="800">
    <Grid>
        <!-- 状态栏 -->
        <StatusBar VerticalAlignment="Bottom">
            <StatusBarItem Content="{Binding ConnectionStatusText}" 
                           Background="{Binding ConnectionStatus.Background}"/>
            <StatusBarItem Content="{Binding StatusText}"/>
        </StatusBar>

        <!-- 控制面板 -->
        <StackPanel VerticalAlignment="Top" Margin="20">
            <Button Content="连接PLC" Click="ConnectButton_Click" Margin="5"/>
            <Button Content="读取温度" Click="ReadButton_Click" Margin="5"/>
            <Button Content="启动设备" Click="WriteButton_Click" Margin="5"/>
            
            <!-- 实时数据显示 -->
            <TextBlock Text="温度值:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="TemperatureText" Width="200" Margin="5" IsReadOnly="True"/>
            
            <TextBlock Text="启动指令:" FontWeight="Bold" Margin="5"/>
            <TextBox x:Name="StartCommandText" Width="200" Margin="5" IsReadOnly="True"/>
        </StackPanel>
    </Grid>
</Window>

三、关键功能实现

1. 异步通信优化

// 使用异步方法避免界面冻结
public async Task<double> GetTemperatureAsync()
{
    return await _plcService.ReadVariableAsync<double>("MAIN.Temperature");
}

// 批量读取示例
public async Task<PlcDataBatch> GetBatchDataAsync()
{
    var data = new PlcDataBatch();
    data.Temperature = await GetTemperatureAsync();
    data.Pressure = await _plcService.ReadVariableAsync<double>("MAIN.Pressure");
    data.Position = await _plcService.ReadVariableAsync<int>("MAIN.Position");
    return data;
}

2. 报警监控系统

// 报警规则定义
public class AlarmRule
{
    public string VariableName { get; }
    public double HighLimit { get; }
    public double LowLimit { get; }

    public AlarmRule(string varName, double high, double low)
    {
        VariableName = varName;
        HighLimit = high;
        LowLimit = low;
    }

    public bool CheckAlarm(double value)
    {
        return value > HighLimit || value < LowLimit;
    }
}

// 报警处理服务
public class AlarmService
{
    private readonly AdsCommunicationService _plcService;
    private readonly List<AlarmRule> _rules = new List<AlarmRule>();

    public AlarmService(AdsCommunicationService plcService)
    {
        _plcService = plcService;
        InitializeRules();
    }

    private void InitializeRules()
    {
        _rules.Add(new AlarmRule("MAIN.Temperature", 80.0, 10.0));
        _rules.Add(new AlarmRule("MAIN.Pressure", 200.0, 50.0));
    }

    public async Task CheckAlarmsAsync()
    {
        foreach (var rule in _rules)
        {
            var value = await _plcService.ReadVariableAsync<double>(rule.VariableName);
            if (rule.CheckAlarm(value))
            {
                ShowAlarm($"[{rule.VariableName}] 超出范围: {value}");
            }
        }
    }

    private void ShowAlarm(string message)
    {
        Application.Current.Dispatcher.Invoke(() =>
        {
            AlarmLog.AppendText($"{DateTime.Now}: {message}\n");
            System.Media.SoundPlayer alarmSound = new System.Media.SoundPlayer("alarm.wav");
            alarmSound.Play();
        });
    }
}

参考代码 基于C# 编写的倍福plc 应用上位机源程序 www.youwenfan.com/contentcnp/113111.html

四、扩展功能实现

1. 历史数据记录

public class DataLogger
{
    private readonly string _logFilePath = "plc_data_log.csv";
    private readonly List<PlcDataPoint> _dataBuffer = new List<PlcDataPoint>();

    public void LogData(PlcDataPoint data)
    {
        _dataBuffer.Add(data);
        if (_dataBuffer.Count >= 100) // 每100条写入文件
        {
            File.WriteAllLines(_logFilePath, _dataBuffer.Select(d => 
                $"{d.Timestamp:yyyy-MM-dd HH:mm:ss},{d.Temperature},{d.Pressure}"));
            _dataBuffer.Clear();
        }
    }
}

public class PlcDataPoint
{
    public DateTime Timestamp { get; } = DateTime.Now;
    public double Temperature { get; set; }
    public double Pressure { get; set; }
}

2. 数据可视化仪表盘

<!-- DashboardView.xaml -->
<UserControl x:Class="PLCControlDemo.Views.DashboardView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <!-- 温度仪表盘 -->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBlock Text="温度监控" FontSize="16" Margin="10"/>
        
        <Viewbox Grid.Row="1" Stretch="Uniform">
            <Grid>
                <Ellipse Width="200" Height="200" Fill="LightGray"/>
                <RadialGauge 
                    Value="{Binding Temperature}" 
                    Minimum="0" 
                    Maximum="100" 
                    NeedleBrush="Red"
                    TickBrush="Black"
                    ScaleWidth="20"/>
            </Grid>
        </Viewbox>
    </Grid>
</UserControl>

五、部署与调试

1. 环境配置

  • 开发环境:Visual Studio 2022 + .NET 6.0

  • 依赖库

    <!-- PLC通信 -->
    <PackageReference Include="TwinCAT.Ads" Version="6.0.1000.15" />
    <!-- 数据可视化 -->
    <PackageReference Include="MahApps.Metro" Version="2.4.3" />
    

2. 调试技巧

  1. Wireshark抓包分析

    • 过滤条件:tcp.port == 851
    • 验证ADS协议报文结构是否符合规范
  2. 异常捕获增强

    public async Task SafeReadAsync<T>(string varName, out T value)
    {
        try
        {
            value = await ReadVariableAsync<T>(varName);
        }
        catch (AdsException ex) when (ex.ErrorCode == 0x800A0002)
        {
            value = default;
            ShowError($"变量不存在: {varName}");
        }
        catch (TimeoutException)
        {
            value = default;
            ShowError("通信超时,请检查网络连接");
        }
    }
    

六、性能优化

  1. 连接池管理

    private static readonly ObjectPool<TcAdsClient> _clientPool = 
        new ObjectPool<TcAdsClient>(() => new TcAdsClient(), 3); // 维护3个连接实例
    
  2. 批量数据传输

    public async Task<byte[]> ReadBulkDataAsync(string[] variables)
    {
        using (var ms = new MemoryStream())
        {
            foreach (var var in variables)
            {
                var handle = await _adsClient.CreateVariableHandleAsync(var);
                var data = await _adsClient.ReadAnyAsync(handle, typeof(byte[]));
                ms.Write(data, 0, data.Length);
                _adsClient.DeleteVariableHandle(handle);
            }
            return ms.ToArray();
        }
    }
    
  3. 数据压缩

    public byte[] CompressData(byte[] rawData)
    {
        using (var memoryStream = new MemoryStream())
        {
            using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Compress))
            {
                gzipStream.Write(rawData, 0, rawData.Length);
            }
            return memoryStream.ToArray();
        }
    }
    

七、测试用例

1. 基本功能测试

测试项 输入值 预期结果
温度读取 显示实时温度值
启动指令写入 true PLC启动状态变为运行
报警触发 温度>80℃ 弹出警告并记录日志

2. 压力测试

// 模拟高频数据采集
public async Task StressTestAsync()
{
    var stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        await _plcService.ReadVariableAsync<double>("MAIN.Temperature");
    }
    stopwatch.Stop();
    Assert.IsTrue(stopwatch.ElapsedMilliseconds < 500); // 1000次读取应在500ms内完成
}

八、扩展应用场景

  1. 多PLC协同控制

    public class PlcClusterManager
    {
        private readonly List<AdsCommunicationService> _plcs = new List<AdsCommunicationService>();
    
        public void AddPlc(string amsNetId)
        {
            var plc = new AdsCommunicationService();
            plc.ConnectAsync(amsNetId).Wait();
            _plcs.Add(plc);
        }
    }
    
  2. OPC UA集成

    public class OpcUaGateway
    {
        private readonly Opc.Ua.Client
    
posted @ 2026-01-15 10:00  u95900090  阅读(2)  评论(0)    收藏  举报