422 lines
14 KiB
C#
422 lines
14 KiB
C#
using System;
|
||
using System.Runtime.InteropServices;
|
||
using System.Windows.Forms;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Collections.Concurrent;
|
||
|
||
namespace BootloaderGUI
|
||
{
|
||
public class CanInterface
|
||
{
|
||
private const string DLL_NAME = "slcan.dll";
|
||
public IntPtr hDevice { get; private set; } = IntPtr.Zero;
|
||
public event Action<UInt16> ErrorReceived; // событие для ошибок
|
||
private CancellationTokenSource rxCts;
|
||
private ConcurrentQueue<SLCAN_MESSAGE> rxQueue = new ConcurrentQueue<SLCAN_MESSAGE>();
|
||
private bool slcanLoaded = false;
|
||
private bool waitingForResponse = false;
|
||
|
||
|
||
public const ushort SLCAN_BR_CIA_1000K = 0x8000;
|
||
public const ushort SLCAN_BR_CIA_800K = 0x8001;
|
||
public const ushort SLCAN_BR_CIA_500K = 0x8002;
|
||
public const ushort SLCAN_BR_CIA_250K = 0x8003;
|
||
public const ushort SLCAN_BR_CIA_125K = 0x8004;
|
||
|
||
public const uint SLCAN_MODE_CONFIG = 0x00;
|
||
public const uint SLCAN_MODE_NORMAL = 0x01;
|
||
public const uint SLCAN_MODE_LISTENONLY = 0x02;
|
||
|
||
private const byte SLCAN_PURGE_TX_ABORT = 0x01;
|
||
private const byte SLCAN_PURGE_RX_ABORT = 0x02;
|
||
private const byte SLCAN_PURGE_TX_CLEAR = 0x04;
|
||
private const byte SLCAN_PURGE_RX_CLEAR = 0x08;
|
||
|
||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||
private struct SLCAN_MESSAGE
|
||
{
|
||
public byte Info;
|
||
public uint ID;
|
||
public byte DataCount;
|
||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||
public byte[] Data;
|
||
}
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_Load(IntPtr deviceCallback, IntPtr listCallback);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_Free([MarshalAs(UnmanagedType.Bool)] bool bDoCallBack);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern uint SlCan_GetDeviceCount();
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern IntPtr SlCan_GetDevice(uint dwIndex);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceOpen(IntPtr hDevice);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceClose(IntPtr hDevice);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceWriteMessages(IntPtr hDevice, [In] SLCAN_MESSAGE[] pMsg, uint dwCount, out byte pbStatus);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
public static extern bool SlCan_DeviceSetMode(IntPtr hDevice, uint mode);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceEnaRec(IntPtr hDevice, byte bValue);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceReadMessages(
|
||
IntPtr hDevice,
|
||
uint dwTimeout, // таймаут в мс
|
||
[Out] SLCAN_MESSAGE[] pMsg, // буфер для сообщений
|
||
uint dwCount, // размер буфера
|
||
out uint pdwRead // количество реально прочитанных
|
||
);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DevicePurge(IntPtr hDevice, byte bValue);
|
||
|
||
|
||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||
private struct SLCAN_STATE
|
||
{
|
||
public byte BusMode;
|
||
public byte Dummy1;
|
||
public byte ErrCountRX;
|
||
public byte ErrCountTX;
|
||
}
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
private static extern bool SlCan_DeviceGetState(IntPtr hDevice, out SLCAN_STATE state);
|
||
|
||
|
||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||
public struct SLCAN_BITRATE
|
||
{
|
||
public ushort BRP;
|
||
public byte TSEG1;
|
||
public byte TSEG2;
|
||
public byte SJW;
|
||
public byte SAM;
|
||
}
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
public static extern bool SlCan_DeviceSetBitRate(IntPtr hDevice, ref SLCAN_BITRATE bitrate);
|
||
|
||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.StdCall)]
|
||
public static extern bool SlCan_DeviceGetBitRate(IntPtr hDevice, ref SLCAN_BITRATE bitrate);
|
||
|
||
|
||
public bool Load()
|
||
{
|
||
if (slcanLoaded) return true;
|
||
slcanLoaded = SlCan_Load(IntPtr.Zero, IntPtr.Zero);
|
||
return slcanLoaded;
|
||
}
|
||
|
||
public void Free()
|
||
{
|
||
if (slcanLoaded)
|
||
{
|
||
SlCan_Free(false);
|
||
slcanLoaded = false;
|
||
}
|
||
}
|
||
|
||
public bool ConnectFirstDevice(ushort bitrateCode, out string statusText)
|
||
{
|
||
statusText = "";
|
||
|
||
if (!slcanLoaded && !Load())
|
||
{
|
||
statusText = "slcan.dll not loaded";
|
||
return false;
|
||
}
|
||
|
||
uint count = SlCan_GetDeviceCount();
|
||
if (count == 0)
|
||
{
|
||
MessageBox.Show("No SLCAN devices found", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "No devices";
|
||
return false;
|
||
}
|
||
|
||
IntPtr dev = SlCan_GetDevice(0);
|
||
if (dev == IntPtr.Zero)
|
||
{
|
||
MessageBox.Show("Failed to get device 0", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "GetDevice failed";
|
||
return false;
|
||
}
|
||
|
||
if (!SlCan_DeviceOpen(dev))
|
||
{
|
||
MessageBox.Show("Failed to open device", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "Device open failed";
|
||
return false;
|
||
}
|
||
|
||
hDevice = dev;
|
||
|
||
SlCan_DeviceEnaRec(hDevice, 1);
|
||
|
||
// CONFIG mode
|
||
if (!SlCan_DeviceSetMode(hDevice, SLCAN_MODE_CONFIG))
|
||
{
|
||
MessageBox.Show("Failed to set CAN mode", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "Device open failed";
|
||
return false;
|
||
}
|
||
|
||
// Выбранный битрейт
|
||
SLCAN_BITRATE br = new SLCAN_BITRATE
|
||
{
|
||
BRP = bitrateCode,
|
||
TSEG1 = 0,
|
||
TSEG2 = 0,
|
||
SJW = 0,
|
||
SAM = 0
|
||
};
|
||
|
||
if (!SlCan_DeviceSetBitRate(hDevice, ref br))
|
||
{
|
||
MessageBox.Show("Failed to set CAN speed", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "Device open failed";
|
||
return false;
|
||
}
|
||
|
||
if (!SlCan_DeviceSetMode(hDevice, SLCAN_MODE_NORMAL))
|
||
{
|
||
MessageBox.Show("Failed to set CAN mode", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
statusText = "Device open failed";
|
||
return false;
|
||
}
|
||
|
||
statusText = $"Connected to device 0 (devices: {count}) at {bitrateCode}";
|
||
return true;
|
||
}
|
||
|
||
public static class CanSpeedDialog
|
||
{
|
||
public static ushort? ShowDialog()
|
||
{
|
||
Form dlg = new Form()
|
||
{
|
||
Text = "Select CAN Speed",
|
||
Width = 250,
|
||
Height = 150,
|
||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||
StartPosition = FormStartPosition.CenterParent,
|
||
MaximizeBox = false,
|
||
MinimizeBox = false
|
||
};
|
||
|
||
ComboBox combo = new ComboBox()
|
||
{
|
||
Left = 10,
|
||
Top = 10,
|
||
Width = 200,
|
||
DropDownStyle = ComboBoxStyle.DropDownList
|
||
};
|
||
|
||
// Заполняем скорости
|
||
combo.Items.Add(new { Text = "1000K", Value = CanInterface.SLCAN_BR_CIA_1000K });
|
||
combo.Items.Add(new { Text = "800K", Value = CanInterface.SLCAN_BR_CIA_800K });
|
||
combo.Items.Add(new { Text = "500K", Value = CanInterface.SLCAN_BR_CIA_500K });
|
||
combo.Items.Add(new { Text = "250K", Value = CanInterface.SLCAN_BR_CIA_250K });
|
||
combo.Items.Add(new { Text = "125K", Value = CanInterface.SLCAN_BR_CIA_125K });
|
||
combo.DisplayMember = "Text";
|
||
combo.SelectedIndex = 3; // по умолчанию 250K
|
||
|
||
Button ok = new Button() { Text = "OK", Left = 10, Top = 50, Width = 80, DialogResult = DialogResult.OK };
|
||
Button cancel = new Button() { Text = "Cancel", Left = 100, Top = 50, Width = 80, DialogResult = DialogResult.Cancel };
|
||
|
||
dlg.Controls.Add(combo);
|
||
dlg.Controls.Add(ok);
|
||
dlg.Controls.Add(cancel);
|
||
dlg.AcceptButton = ok;
|
||
dlg.CancelButton = cancel;
|
||
|
||
if (dlg.ShowDialog() == DialogResult.OK)
|
||
{
|
||
dynamic selected = combo.SelectedItem;
|
||
return (ushort)selected.Value;
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
public bool SendCmd(byte cmd, out UInt16 error)
|
||
{
|
||
error = 0;
|
||
if (hDevice == IntPtr.Zero) return false;
|
||
|
||
// Формируем сообщение
|
||
SLCAN_MESSAGE msg = new SLCAN_MESSAGE
|
||
{
|
||
Info = 0,
|
||
ID = BootloaderCommands.CAN_ID_BOOTLOADER,
|
||
DataCount = 8,
|
||
Data = new byte[8]
|
||
};
|
||
msg.Data[0] = cmd;
|
||
|
||
if (!SlCan_DeviceWriteMessages(hDevice, new SLCAN_MESSAGE[] { msg }, 1, out _))
|
||
return false;
|
||
|
||
// Таймаут: 10с для ERASE, 1с для остальных
|
||
uint timeout = (cmd == BootloaderCommands.ERASE) ? 10000u : 1000u;
|
||
|
||
if(cmd != BootloaderCommands.JUMP)
|
||
{
|
||
return WaitForResponse(timeout, out error);
|
||
}
|
||
else
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
public bool SendData(byte[] data)
|
||
{
|
||
if (hDevice == IntPtr.Zero) return false;
|
||
int totalBytes = data.Length;
|
||
int step = 8;
|
||
|
||
for (int offset = 0; offset < totalBytes; offset += step)
|
||
{
|
||
int chunkLen = Math.Min(step, totalBytes - offset);
|
||
SLCAN_MESSAGE msg = new SLCAN_MESSAGE
|
||
{
|
||
Info = 0,
|
||
ID = BootloaderCommands.CAN_ID_BOOTLOADER,
|
||
DataCount = 8,
|
||
Data = new byte[8]
|
||
};
|
||
Array.Copy(data, offset, msg.Data, 0, chunkLen);
|
||
|
||
if (!SlCan_DeviceWriteMessages(hDevice, new SLCAN_MESSAGE[] { msg }, 1, out _))
|
||
return false;
|
||
|
||
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
|
||
public bool WaitForResponse(uint timeoutMs, out UInt16 error)
|
||
{
|
||
error = 0;
|
||
waitingForResponse = true; // включаем режим ожидания
|
||
|
||
DateTime start = DateTime.Now;
|
||
while ((DateTime.Now - start).TotalMilliseconds < timeoutMs)
|
||
{
|
||
if (rxQueue.TryDequeue(out var msg))
|
||
{
|
||
byte[] response = msg.Data;
|
||
|
||
waitingForResponse = false; // мы получили ответ — отключаем режим
|
||
if (response[0] == 0)
|
||
return true;
|
||
else
|
||
{
|
||
error = (UInt16)((response[1] << 8) | response[2]);
|
||
return false;
|
||
}
|
||
}
|
||
Thread.Sleep(1);
|
||
}
|
||
|
||
waitingForResponse = false; // таймаут
|
||
error = 0xFFFF;
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Запуск фонового приёма сообщений только с ID=123.
|
||
/// </summary>
|
||
public void StartBackgroundReceive()
|
||
{
|
||
rxCts = new CancellationTokenSource();
|
||
Task.Run(() =>
|
||
{
|
||
SLCAN_MESSAGE[] buffer = new SLCAN_MESSAGE[1];
|
||
buffer[0].Data = new byte[8];
|
||
|
||
while (!rxCts.Token.IsCancellationRequested)
|
||
{
|
||
if (SlCan_DeviceReadMessages(hDevice, 200, buffer, 1, out uint read) && read > 0)
|
||
{
|
||
if (buffer[0].ID == 123)
|
||
{
|
||
if (waitingForResponse)
|
||
{
|
||
// Кладём пакет в очередь для WaitForResponse
|
||
rxQueue.Enqueue(buffer[0]);
|
||
}
|
||
// Проверяем, ошибка ли это
|
||
byte[] resp = buffer[0].Data;
|
||
if (resp[0] != 0) // ошибка
|
||
{
|
||
UInt16 err = (UInt16)((resp[1] << 8) | resp[2]);
|
||
ErrorReceived?.Invoke(err); // вызываем событие
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Thread.Sleep(1);
|
||
}
|
||
}
|
||
}, rxCts.Token);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Остановка фонового приёма.
|
||
/// </summary>
|
||
public void StopBackgroundReceive()
|
||
{
|
||
try
|
||
{
|
||
rxCts?.Cancel();
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
public void Disconnect()
|
||
{
|
||
if (hDevice != IntPtr.Zero)
|
||
{
|
||
SlCan_DeviceClose(hDevice);
|
||
hDevice = IntPtr.Zero;
|
||
}
|
||
}
|
||
}
|
||
}
|