Compare commits

..

2 Commits

Author SHA1 Message Date
2609102829 работает но не стабильно 2025-09-12 12:26:19 +03:00
614b328a58 WTF 2025-09-12 10:26:03 +03:00
10 changed files with 134 additions and 48 deletions

Binary file not shown.

View File

@ -12,10 +12,9 @@ namespace BootloaderGUI
public const byte ERASE = 0x01;
public const byte RECEIVE = 0x02;
public const byte WRITE = 0x03;
public const byte VERIFY = 0x04;
public const byte JUMP = 0x05;
public const byte RESET = 0x06;
public const byte GO_TO_BOOT = 0x07;
public const byte JUMP = 0x04;
public const byte RESET = 0x05;
public const byte GO_TO_BOOT = 0x06;
public const int PAGE_SIZE = 2048;
public const int CAN_ID_BOOTLOADER = 0x123;

View File

@ -1,6 +1,9 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace BootloaderGUI
{
@ -8,7 +11,11 @@ namespace BootloaderGUI
{
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;
@ -313,44 +320,95 @@ namespace BootloaderGUI
public bool WaitForResponse(uint timeoutMs, out UInt16 error)
{
error = 0;
waitingForResponse = true; // включаем режим ожидания
// 1) Очистим RX буфер у устройства
try
DateTime start = DateTime.Now;
while ((DateTime.Now - start).TotalMilliseconds < timeoutMs)
{
// SLCAN_PURGE_RX_CLEAR определён в заголовке
SlCan_DevicePurge(hDevice, SLCAN_PURGE_RX_CLEAR);
}
catch
{
// если purge не доступен/ошибка — можно продолжить, но логировать
}
// 2) Ждём один свежий пакет (блокирующе, с таймаутом)
SLCAN_MESSAGE[] buffer = new SLCAN_MESSAGE[1];
buffer[0].Data = new byte[8];
if (SlCan_DeviceReadMessages(hDevice, timeoutMs, buffer, 1, out uint read))
{
if (read > 0)
if (rxQueue.TryDequeue(out var msg))
{
byte[] response = buffer[0].Data;
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);
}
error = 0xFFFF; // таймаут / ничего не пришло
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)

View File

@ -74,13 +74,12 @@ namespace BootloaderGUI
top += numPage.Height + gap;
btnErase = new Button() { Top = top, Left = left, Width = wBtn, Text = "ERASE Firmware" };
btnReceive = new Button() { Top = top, Left = left + wBtn + gap, Width = wBtn, Text = "SEND Page" };
Tab.Controls.AddRange(new Control[] { btnErase, btnReceive });
Tab.Controls.AddRange(new Control[] { btnErase, });
top += hBtn + gap;
btnReceive = new Button() { Top = top, Left = left + wBtn + gap, Width = wBtn, Text = "SEND Page" };
btnWrite = new Button() { Top = top, Left = left, Width = wBtn, Text = "WRITE Page" };
btnVerify = new Button() { Top = top, Left = left + wBtn + gap, Width = wBtn, Text = "VERIFY Firmware" };
Tab.Controls.AddRange(new Control[] { btnWrite, btnVerify });
Tab.Controls.AddRange(new Control[] { btnReceive, btnWrite });
top += hBtn + gap;
btnJump = new Button() { Top = top, Left = left, Width = wBtn, Text = "JUMP to App" };
@ -92,7 +91,6 @@ namespace BootloaderGUI
btnErase.Click += (s, e) => sendCmdSync(BootloaderCommands.ERASE);
btnReceive.Click += (s, e) => sendPage((int)numPage.Value);
btnWrite.Click += (s, e) => sendCmdSync(BootloaderCommands.WRITE);
btnVerify.Click += (s, e) => sendCmdSync(BootloaderCommands.VERIFY);
btnJump.Click += (s, e) => sendCmdSync(BootloaderCommands.JUMP);
btnReset.Click += (s, e) => sendCmdSync(BootloaderCommands.RESET);
btnGoBoot.Click += (s, e) => sendCmdSync(BootloaderCommands.GO_TO_BOOT);

View File

@ -112,9 +112,18 @@ namespace BootloaderGUI
string status;
if (can.ConnectFirstDevice(bitrate.Value, out status))
{
lblStatus.Text = status;
// запуск фонового приёма
can.ErrorReceived -= Can_ErrorReceived; // безопасно удалить старую подписку
can.ErrorReceived += Can_ErrorReceived;
can.StartBackgroundReceive();
}
else
{
lblStatus.Text = "Connect failed";
}
};
@ -144,11 +153,20 @@ namespace BootloaderGUI
}
private void Can_ErrorReceived(UInt16 err)
{
this.BeginInvoke(new Action(() =>
{
lblStatus.Text = $"Error received: 0x{err:X4}";
MessageBox.Show($"Error received from device: 0x{err:X4}", "CAN Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}));
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
can?.StopBackgroundReceive();
can?.Disconnect();
can?.Free();
}
@ -222,7 +240,7 @@ namespace BootloaderGUI
{
uint timeout = 1000u;
UInt16 error = 0;
Thread.Sleep(10);
try
{
if (fwData == null) return;
@ -303,6 +321,7 @@ namespace BootloaderGUI
{
UpdateStatusOnUI("Error during RECEIVE: " + ex.Message);
}
}
private bool SendCmdSync(byte cmd)
@ -315,12 +334,16 @@ namespace BootloaderGUI
if (!can.SendCmd(cmd, out UInt16 error))
{
if (error == 0xFFFF)
if ((error == 0xFFFF) && cmd != (BootloaderCommands.RESET) && (cmd != BootloaderCommands.JUMP))
{
MessageBox.Show($"Command 0x{cmd:X2} timed out", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
else
return false;
}
else if ((error != 0xFFFF) && (error != 0))
{
MessageBox.Show($"Command 0x{cmd:X2} failed, error code: 0x{error:X4}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
return false;
}
}
UpdateStatusOnUI($"Command 0x{cmd:X2} sent successfully");

View File

@ -58,7 +58,14 @@ namespace BootloaderGUI
// Кнопка "Go To Boot"
btnGoBoot = new Button() { Top = top, Left = left + wBtn + gap, Width = wBtn, Height = hBtn, Text = "Go To Boot" };
btnGoBoot.Click += (s, e) => sendCmdSync(BootloaderCommands.GO_TO_BOOT);
btnGoBoot.Click += (s, e) =>
{
if(sendCmdSync(BootloaderCommands.GO_TO_BOOT))
UpdateStatus("You're in Bootloader");
else
UpdateStatus("Failed: Jump to Bootloader");
};
top += hBtn + gap;
@ -66,13 +73,21 @@ namespace BootloaderGUI
Button btnGoApp = new Button() { Top = top, Left = left, Width = wBtn, Height = hBtn, Text = "Go To App" };
btnGoApp.Click += (s, e) =>
{
UpdateStatus("Jumping to app...");
sendCmdSync(BootloaderCommands.JUMP);
if(sendCmdSync(BootloaderCommands.JUMP))
UpdateStatus("Jumping to app...");
else
UpdateStatus("Failed: Jump to App");
};
// Кнопка "Reset"
btnReset = new Button() { Top = top, Left = left + wBtn + gap, Width = wBtn, Height = hBtn, Text = "Reset" };
btnReset.Click += (s, e) => sendCmdSync(BootloaderCommands.RESET);
btnReset.Click += (s, e) =>
{
if (sendCmdSync(BootloaderCommands.RESET))
UpdateStatus("Resetting...");
else
UpdateStatus("Failed: Reset MCU");
};
Tab.Controls.AddRange(new Control[] { btnFirmware, btnGoBoot, btnGoApp, btnReset });
}
@ -98,24 +113,17 @@ namespace BootloaderGUI
for (int page = 0; page < totalPages; page++)
{
UpdateStatus($"Sending page {page + 1}/{totalPages}");
await Task.Delay(5); // небольшая пауза для UI
sendPage(page);
UpdateStatus($"Writing page {page + 1}/{totalPages}");
await Task.Delay(5); // небольшая пауза для UI
if (!sendCmdSync(BootloaderCommands.WRITE))
{
UpdateStatus($"Write failed on page {page}");
return;
}
await Task.Delay(5); // небольшая пауза для UI
}
UpdateStatus("Verifying...");
await Task.Delay(5); // небольшая пауза для UI
if (!sendCmdSync(BootloaderCommands.VERIFY))
{
UpdateStatus("Verify failed");
return;
}
UpdateStatus("Jumping to app...");