CUWIN (윈도우CE 터치패널 PC)에서 PID 제어를 해보았습니다.
사용한 기종은 CWV시리즈, 개발환경은 Visual Studio 2008과 .NET Framework 3.5 입니다.
링크된 zip파일에는 두개의 프로젝트가 있습니다. 하나는 Test를 위한 프로젝트이고, 다른 하나는 PID콘트롤 구현을 위한 프로젝트입니다.
아래 설명을 참조하시여 응용하시기 바랍니다.
Creating a PID Controller To create a new PIDController, you must pass a delegate for reading the process value (PV) and another delegate for writing the controller output (CO). _pidController = new PIDController(ReadInput, WriteOutput); private double ReadInput() { //Return process value } private void WriteOutput(double value) { //send controller output } The set point can be set using the PIDController.SetPoint property. _pidController.SetPoint = 0.0d; PIDController operates on a timer, so once you call PIDController.Start() it will periodically call ReadInput and WriteOutput. By defaultPIDController uses System.Threading.Timer and its frequency can be set using the PIDController.DeltaTimeInSeconds property. But you can also pass it your own timer by implementing the IPIDTimer interface. The PIDControllerSample project includes an implementation of IPIDTimerthat wraps the System.Windows.Forms.Timer. Tuning the PIDController There are two classes in the PIDControl project that can be used for tuning the PIDController. PIDController.IdealTuner is for tuning according to the Ideal form (Kp, Ki, Kd) and PIDController.StandardTuner is for Tuning according to the Standard form (Kp, Ti, Td). _idealTuner = new PIDController.IdealTuner(_pidController); _idealTuner.Kp = 0.0d; _idealTuner.Ki = 0.0d; _idealTuner.Kd = 0.0d; _standardTuner = new PIDController.StandardTuner(_pidController); _standardTuner.Kp = 0.0d; _standardTuner.Ti = 0.0d; _standardTuner.Td = 0.0d; Additional Features Because a valve cannot be more off than all the way off, and cannot be more on that all the way on, you can constrain the output usingPIDController.OutputMinimum and PIDController.OutputMaximum. _pidController.OutputMinimum = -1000.0d; _pidController.OutputMaximum = 1000.0d; To mitigate Derivative Kick, PIDController's derivative term can be based on PV (Deriviative on Measurement) or on the error (Derivative On Error). Set the PIDContoller.DerivativeOn property accordingly. _pidController.DerivativeOn == DerivativeOn.Error; _pidController.DerivativeOn == DerivativeOn.Measurement;
다음은 동작 동영상입니다.
using System; using System.Linq; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using PIDControl; using System.Runtime.InteropServices; using System.Resources; using System.Drawing.Imaging; using System.Reflection; using System.IO; namespace PIDControllerSample { public partial class Form1 : Form { [DllImport("coredll.dll", CharSet = CharSet.Auto)] public static extern uint GetTickCount(); private const string DECIMAL_FORMAT = "0.0000"; private const int TRACK_BAR_FACTOR = 10000; public Form1() { InitializeComponent(); _pidController = new PIDController(new WindowsFormsPIDTimer(), ReadInput, WriteOutput); _idealTuner = new PIDController.IdealTuner(_pidController); _standardTuner = new PIDController.StandardTuner(_pidController); _setPointLine = new StripChart.Line(Color.Blue, SampleSetPoint); _outputLine = new StripChart.Line(Color.Red, SampleOutput); _t0 = GetTickCount(); _stripChart.UpdateInterval = 50; _stripChart.AddLine(_setPointLine); _stripChart.AddLine(_outputLine); _stripChart.Range = 10.0d; } private uint _t0; private readonly PIDController _pidController; private readonly PIDController.IdealTuner _idealTuner; private readonly PIDController.StandardTuner _standardTuner; private readonly StripChart.Line _setPointLine; private readonly StripChart.Line _outputLine; /// <summary> /// Called by PID Controller to take a measurement (PV) /// </summary> private double ReadInput() { return double.Parse(_outputLabel.Text); } /// <summary> /// Called by PID Controller to set ouptput (MV) /// </summary> private void WriteOutput(double value) { _outputLabel.Text = value.ToString(DECIMAL_FORMAT); } /// <summary> /// Called by Strip Chart to sample Set Point data point /// </summary> private StripChart.Line.DataPoint SampleSetPoint() { double t = (double)GetTickCount() / 1000.0d - _t0; double amplitude = (_stripChart.YMaximum - _stripChart.YMinimum) / 2.0d; return new StripChart.Line.DataPoint(t, double.Parse(_setPointLabel.Text)); } /// <summary> /// Called by Strip Chart to sample Output data point /// </summary> private StripChart.Line.DataPoint SampleOutput() { double t = (double)GetTickCount() / 1000.0d - _t0; double amplitude = (_stripChart.YMaximum - _stripChart.YMinimum) / 2.0d; return new StripChart.Line.DataPoint(t, double.Parse(_outputLabel.Text)); } private void Form1_Load(object sender, EventArgs e) { _derivativeOnComboBox.SelectedIndex = (int)_pidController.DerivativeOn; SetPictureBoxes(); _pidController.DeltaTimeInSeconds = double.Parse(_dtTextBox.Text); _pidController.OutputMaximum = double.Parse(_maximumTextBox.Text); _pidController.OutputMinimum = double.Parse(_minimumTextBox.Text); _idealTuner.Kp = double.Parse(_idealKpTextBox.Text); _idealTuner.Ki = double.Parse(_kiTextBox.Text); _idealTuner.Kd = double.Parse(_kdTextBox.Text); _standardKpTextBox.Text = _standardTuner.Kp.ToString(DECIMAL_FORMAT); _tiTextBox.Text = _standardTuner.Ti.ToString(DECIMAL_FORMAT); _tdTextBox.Text = _standardTuner.Td.ToString(DECIMAL_FORMAT); _setPointTrackBar.Minimum = (int)(_pidController.OutputMinimum * TRACK_BAR_FACTOR); _setPointTrackBar.Maximum = (int)(_pidController.OutputMaximum * TRACK_BAR_FACTOR); _setPointTrackBar.TickFrequency = (_setPointTrackBar.Maximum - _setPointTrackBar.Minimum) / 10; _setPointTrackBar.ValueChanged += _setPointTrackBar_ValueChanged; _pidController.SetPoint = (double)_setPointTrackBar.Value / (double)TRACK_BAR_FACTOR; _setPointLabel.Text = _pidController.SetPoint.ToString(DECIMAL_FORMAT); _outputTrackBar.Minimum = (int)(_pidController.OutputMinimum * TRACK_BAR_FACTOR); _outputTrackBar.Maximum = (int)(_pidController.OutputMaximum * TRACK_BAR_FACTOR); _outputTrackBar.TickFrequency = (_outputTrackBar.Maximum - _outputTrackBar.Minimum) / 10; _stripChart.YMaximum = _pidController.OutputMaximum; _stripChart.YMinimum = _pidController.OutputMinimum; _outputLabel.TextChanged += new EventHandler(_outputLabel_TextChanged); } private void SetPictureBoxes() { Assembly thisAssembly = Assembly.GetCallingAssembly(); string namePrefix = thisAssembly.GetName().Name + ".Resources."; if (_pidController.DerivativeOn == DerivativeOn.Error) { using (Stream stream = thisAssembly.GetManifestResourceStream(namePrefix + "IdealFormError.png")) { _idealPictureBox.Image = new Bitmap(stream); } using (Stream stream = thisAssembly.GetManifestResourceStream(namePrefix + "StandardFormError.png")) { _standardPictureBox.Image = new Bitmap(stream); } } else if (_pidController.DerivativeOn == DerivativeOn.Measurement) { using (Stream stream = thisAssembly.GetManifestResourceStream(namePrefix + "IdealFormMeasurement.png")) { _idealPictureBox.Image = new Bitmap(stream); } using (Stream stream = thisAssembly.GetManifestResourceStream(namePrefix + "StandardFormMeasurement.png")) { _standardPictureBox.Image = new Bitmap(stream); } } } private void _idealKpTextBox_TextChanged(object sender, EventArgs e) { try { _idealTuner.Kp = double.Parse(_idealKpTextBox.Text); } catch (FormatException) { } _standardKpTextBox.Text = _idealKpTextBox.Text; //These two must always be the same } private void _kiTextBox_TextChanged(object sender, EventArgs e) { try { _idealTuner.Ki = double.Parse(_kiTextBox.Text); } catch (FormatException) { } _tiTextBox.Text = _standardTuner.Ti.ToString(DECIMAL_FORMAT); //A change in Ki, results in a change in Ti } private void _kdTextBox_TextChanged(object sender, EventArgs e) { try { _idealTuner.Kd = double.Parse(_kdTextBox.Text); } catch (FormatException) { } _tdTextBox.Text = _standardTuner.Td.ToString(DECIMAL_FORMAT); //A change in Ki, results in a change in Ti } private void _standardKpTextBox_TextChanged(object sender, EventArgs e) { try { _standardTuner.Kp = double.Parse(_standardKpTextBox.Text); } catch (FormatException) { } _idealKpTextBox.Text = _standardKpTextBox.Text; //These two must always be the same } private void _tiTextBox_TextChanged(object sender, EventArgs e) { try { _standardTuner.Ti = double.Parse(_tiTextBox.Text); } catch (FormatException) { } _kiTextBox.Text = _idealTuner.Ki.ToString(DECIMAL_FORMAT); //A change in Ti, results in a change in ki } private void _tdTextBox_TextChanged(object sender, EventArgs e) { _standardTuner.Td = double.Parse(_tdTextBox.Text); _kdTextBox.Text = _idealTuner.Kd.ToString(DECIMAL_FORMAT); //A change in Td, results in a change in kd } private void _startStopButton_Click(object sender, EventArgs e) { if (_startStopButton.Text == "Start") { _pidController.Start(ReadInput()); _startStopButton.Text = "Stop"; } else if (_startStopButton.Text == "Stop") { _pidController.Stop(); _startStopButton.Text = "Start"; } } private void _maximumTextBox_TextChanged(object sender, EventArgs e) { try { _pidController.OutputMaximum = double.Parse(_maximumTextBox.Text); } catch (FormatException) { } _setPointTrackBar.Maximum = (int)(_pidController.OutputMaximum * TRACK_BAR_FACTOR); _outputTrackBar.Maximum = (int)(_pidController.OutputMaximum * TRACK_BAR_FACTOR); _setPointTrackBar.TickFrequency = (_setPointTrackBar.Maximum - _setPointTrackBar.Minimum) / 10; _outputTrackBar.TickFrequency = (_setPointTrackBar.Maximum - _setPointTrackBar.Minimum) / 10; _stripChart.YMaximum = _pidController.OutputMaximum; } private void _minimumTextBox_TextChanged(object sender, EventArgs e) { try { _pidController.OutputMinimum = double.Parse(_minimumTextBox.Text); } catch (FormatException) { } _setPointTrackBar.Minimum = (int)(_pidController.OutputMinimum * TRACK_BAR_FACTOR); _outputTrackBar.Minimum = (int)(_pidController.OutputMinimum * TRACK_BAR_FACTOR); _setPointTrackBar.TickFrequency = (_setPointTrackBar.Maximum - _setPointTrackBar.Minimum) / 10; _outputTrackBar.TickFrequency = (_setPointTrackBar.Maximum - _setPointTrackBar.Minimum) / 10; _stripChart.YMinimum = _pidController.OutputMinimum; } private void _outputLabel_TextChanged(object sender, EventArgs e) { try { double value = double.Parse(_outputLabel.Text); _outputTrackBar.Value = (int)Math.Round(value * (double)TRACK_BAR_FACTOR); } catch (FormatException) { } } private void _setPointTrackBar_ValueChanged(object sender, EventArgs e) { try { _pidController.SetPoint = (double)_setPointTrackBar.Value / (double)TRACK_BAR_FACTOR; _setPointLabel.Text = _pidController.SetPoint.ToString(DECIMAL_FORMAT); } catch (FormatException) { } } private void _clearButton_Click(object sender, EventArgs e) { _stripChart.Clear(); } private void _kebyoardButton_Click(object sender, EventArgs e) { inputPanel1.Enabled = !inputPanel1.Enabled; } private void _dtTextBox_TextChanged(object sender, EventArgs e) { try { _pidController.DeltaTimeInSeconds = double.Parse(_dtTextBox.Text); } catch (FormatException) { } } private void _derivativeOnComboBox_SelectedIndexChanged(object sender, EventArgs e) { _pidController.DerivativeOn = (DerivativeOn)_derivativeOnComboBox.SelectedIndex; SetPictureBoxes(); } private void Form1_Closed(object sender, EventArgs e) { _pidController.Dispose(); } enum RasterOperation : uint { SRC_COPY = 0x00CC0020 } [DllImport("coredll.dll")] static extern int BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, RasterOperation rasterOperation); [DllImport("coredll.dll")] private static extern IntPtr GetDC(IntPtr hwnd); [DllImport("coredll.dll")] private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); private void panel1_Click(object sender, EventArgs e) { Rectangle bounds = Screen.PrimaryScreen.Bounds; IntPtr hdc = GetDC(IntPtr.Zero); Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format16bppRgb565); using (Graphics graphics = Graphics.FromImage(bitmap)) { IntPtr dstHdc = graphics.GetHdc(); BitBlt(dstHdc, 0, 0, bounds.Width, bounds.Height, hdc, 0, 0, RasterOperation.SRC_COPY); graphics.ReleaseHdc(dstHdc); } bitmap.Save("screenshot.jpg", ImageFormat.Jpeg); ReleaseDC(IntPtr.Zero, hdc); } private void _stripChart_Click(object sender, EventArgs e) { } } }