boot_terminal/botterm/botterm/CanInterface.cs

422 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}