Compare commits
3 Commits
2de059e0cc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2d33924a | ||
|
|
2609102829 | ||
|
|
614b328a58 |
Binary file not shown.
@@ -12,10 +12,11 @@ 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 byte PING = 0xAA;
|
||||
|
||||
public const int PAGE_SIZE = 2048;
|
||||
public const int CAN_ID_BOOTLOADER = 0x123;
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
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
|
||||
public class CanInterface : IBootTransport
|
||||
{
|
||||
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;
|
||||
@@ -191,6 +198,7 @@ namespace BootloaderGUI
|
||||
return false;
|
||||
}
|
||||
|
||||
StartBackgroundReceive();
|
||||
statusText = $"Connected to device 0 (devices: {count}) at {bitrateCode}";
|
||||
return true;
|
||||
}
|
||||
@@ -313,44 +321,98 @@ 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
|
||||
if (rxQueue.TryDequeue(out var msg))
|
||||
{
|
||||
// если purge не доступен/ошибка — можно продолжить, но логировать
|
||||
}
|
||||
byte[] response = msg.Data;
|
||||
|
||||
// 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)
|
||||
{
|
||||
byte[] response = buffer[0].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 bool Connect(ushort bitrateCode, out string statusText)
|
||||
{
|
||||
return ConnectFirstDevice(bitrateCode, out statusText);
|
||||
}
|
||||
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
if (hDevice != IntPtr.Zero)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -5,8 +5,67 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BootloaderGUI
|
||||
{
|
||||
public class SelectUartDialog : Form
|
||||
{
|
||||
private ComboBox comboPorts;
|
||||
private ComboBox comboBaud;
|
||||
private Button ok, cancel;
|
||||
|
||||
public string SelectedPort => comboPorts.SelectedItem?.ToString();
|
||||
public int SelectedBaudRate => int.Parse(comboBaud.SelectedItem?.ToString());
|
||||
|
||||
public SelectUartDialog()
|
||||
{
|
||||
this.Text = "Select UART Port";
|
||||
this.Width = 300;
|
||||
this.Height = 180;
|
||||
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
this.StartPosition = FormStartPosition.CenterParent;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
|
||||
Label lbl1 = new Label() { Text = "COM Port:", Left = 10, Top = 20, Width = 80 };
|
||||
comboPorts = new ComboBox() { Left = 100, Top = 20, Width = 150 };
|
||||
comboPorts.Items.AddRange(SerialPort.GetPortNames());
|
||||
if (comboPorts.Items.Count > 0) comboPorts.SelectedIndex = 0;
|
||||
|
||||
Label lbl2 = new Label() { Text = "Baud Rate:", Left = 10, Top = 60, Width = 80 };
|
||||
comboBaud = new ComboBox() { Left = 100, Top = 60, Width = 150 };
|
||||
comboBaud.Items.AddRange(new object[] { "115200", "57600", "38400", "19200", "9600" });
|
||||
comboBaud.SelectedIndex = 0;
|
||||
|
||||
ok = new Button() { Text = "OK", Left = 50, Top = 100, Width = 80, DialogResult = DialogResult.OK };
|
||||
cancel = new Button() { Text = "Cancel", Left = 150, Top = 100, Width = 80, DialogResult = DialogResult.Cancel };
|
||||
|
||||
this.Controls.Add(lbl1);
|
||||
this.Controls.Add(comboPorts);
|
||||
this.Controls.Add(lbl2);
|
||||
this.Controls.Add(comboBaud);
|
||||
this.Controls.Add(ok);
|
||||
this.Controls.Add(cancel);
|
||||
this.AcceptButton = ok;
|
||||
this.CancelButton = cancel;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IBootTransport
|
||||
{
|
||||
bool Connect(ushort bitrateCode, out string statusText);
|
||||
void Disconnect();
|
||||
bool SendData(byte[] data);
|
||||
bool SendCmd(byte cmd, out UInt16 error);
|
||||
bool WaitForResponse(uint timeout, out UInt16 error);
|
||||
event Action<UInt16> ErrorReceived;
|
||||
void StopBackgroundReceive();
|
||||
void Free();
|
||||
}
|
||||
|
||||
static class Program
|
||||
{
|
||||
[STAThread]
|
||||
@@ -24,11 +83,13 @@ namespace BootloaderGUI
|
||||
private BootloaderTab regTab;
|
||||
private LowLevelBootloaderTab llTab;
|
||||
private Label lblStatus;
|
||||
private Button btnPing;
|
||||
private ProgressBar progressBar;
|
||||
private Button btnSelect, btnConnect;
|
||||
private Button btnSelect, btnConnect, btnDisconnect;
|
||||
private ComboBox comboTransport;
|
||||
private IBootTransport transport;
|
||||
|
||||
// CAN SETTINGS
|
||||
private CanInterface can;
|
||||
|
||||
|
||||
private byte[] fwData = null;
|
||||
@@ -40,7 +101,6 @@ namespace BootloaderGUI
|
||||
public MainForm()
|
||||
{
|
||||
InitializeUI();
|
||||
can = new CanInterface();
|
||||
|
||||
|
||||
regTab = new BootloaderTab(
|
||||
@@ -67,7 +127,7 @@ namespace BootloaderGUI
|
||||
{
|
||||
this.Text = "Bootloader Tool";
|
||||
this.Width = 520;
|
||||
this.Height = 420;
|
||||
this.Height = 450;
|
||||
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
|
||||
@@ -76,16 +136,59 @@ namespace BootloaderGUI
|
||||
{
|
||||
Top = 10,
|
||||
Left = 10,
|
||||
Width = 480,
|
||||
Height = 20,
|
||||
Text = "Ready"
|
||||
Width = 340, // подгоняем под нужную ширину
|
||||
Height = 25,
|
||||
Text = "Ready",
|
||||
TextAlign = System.Drawing.ContentAlignment.MiddleLeft
|
||||
};
|
||||
this.Controls.Add(lblStatus);
|
||||
|
||||
// Кнопка Ping справа
|
||||
btnPing = new Button()
|
||||
{
|
||||
Text = "Ping",
|
||||
Top = 10,
|
||||
Width = 60,
|
||||
Height = 25,
|
||||
Left = lblStatus.Right + 10 // небольшой отступ
|
||||
};
|
||||
btnPing.Click += (s, e) =>
|
||||
{
|
||||
if (transport == null)
|
||||
{
|
||||
MessageBox.Show("Device not connected", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SendCmdSync(BootloaderCommands.PING)) // PING должен быть определён
|
||||
UpdateStatusOnUI("PING Ok");
|
||||
else
|
||||
UpdateStatusOnUI("PING failed");
|
||||
};
|
||||
this.Controls.Add(btnPing);
|
||||
|
||||
// Чтобы статус расширялся при ресайзе формы (опционально)
|
||||
lblStatus.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
btnPing.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
|
||||
|
||||
|
||||
// ComboBox выбора транспорта
|
||||
comboTransport = new ComboBox()
|
||||
{
|
||||
Top = lblStatus.Bottom + 10,
|
||||
Left = 10,
|
||||
Width = 200,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
};
|
||||
comboTransport.Items.AddRange(new object[] { "CAN", "UART" });
|
||||
comboTransport.SelectedIndex = 0;
|
||||
this.Controls.Add(comboTransport);
|
||||
|
||||
// Кнопки Select Firmware и Connect CAN
|
||||
btnSelect = new Button()
|
||||
{
|
||||
Top = lblStatus.Bottom + 10,
|
||||
Top = comboTransport.Bottom + 10,
|
||||
Left = 10,
|
||||
Width = 200,
|
||||
Text = "Select Firmware"
|
||||
@@ -103,23 +206,72 @@ namespace BootloaderGUI
|
||||
Top = lblStatus.Bottom + 10,
|
||||
Left = btnSelect.Right + 10,
|
||||
Width = 200,
|
||||
Text = "Connect CAN"
|
||||
Text = "Connect MCU"
|
||||
};
|
||||
btnConnect.Click += (s, e) =>
|
||||
{
|
||||
string status;
|
||||
|
||||
if (comboTransport.SelectedItem.ToString() == "CAN")
|
||||
{
|
||||
transport = new CanInterface();
|
||||
ushort? bitrate = CanInterface.CanSpeedDialog.ShowDialog();
|
||||
if (bitrate == null) return;
|
||||
|
||||
string status;
|
||||
if (can.ConnectFirstDevice(bitrate.Value, out status))
|
||||
if (transport.Connect(bitrate.Value, out status))
|
||||
{
|
||||
lblStatus.Text = status;
|
||||
transport.ErrorReceived += Can_ErrorReceived;
|
||||
}
|
||||
else
|
||||
lblStatus.Text = "Connect failed";
|
||||
{
|
||||
lblStatus.Text = "CAN connect failed";
|
||||
MessageBox.Show("CAN connect failed", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
else if (comboTransport.SelectedItem.ToString() == "UART")
|
||||
{
|
||||
transport = new UartInterface();
|
||||
if (transport.Connect(0, out status)) // для UART bitrateCode не используется
|
||||
{
|
||||
lblStatus.Text = status;
|
||||
transport.ErrorReceived += Can_ErrorReceived;
|
||||
}
|
||||
else
|
||||
{
|
||||
lblStatus.Text = "UART connect failed";
|
||||
MessageBox.Show("UART connect failed", "Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
btnDisconnect = new Button()
|
||||
{
|
||||
Top = comboTransport.Bottom + 10,
|
||||
Left = btnSelect.Right + 10,
|
||||
Width = 200,
|
||||
Text = "Disconnect"
|
||||
};
|
||||
btnDisconnect.Click += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
transport?.StopBackgroundReceive();
|
||||
transport?.Disconnect();
|
||||
transport?.Free();
|
||||
lblStatus.Text = "Disconnected";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error during disconnect: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.Controls.Add(btnSelect);
|
||||
this.Controls.Add(btnConnect);
|
||||
this.Controls.Add(btnDisconnect);
|
||||
|
||||
// создаём TabControl ниже кнопок
|
||||
tabControl = new TabControl()
|
||||
@@ -144,58 +296,27 @@ 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?.Disconnect();
|
||||
can?.Free();
|
||||
transport?.StopBackgroundReceive();
|
||||
transport?.Disconnect();
|
||||
transport?.Free();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
|
||||
private void ConnectCan()
|
||||
{
|
||||
using (var dlg = new Form())
|
||||
{
|
||||
dlg.Text = "Select CAN Speed";
|
||||
dlg.Width = 300;
|
||||
dlg.Height = 200;
|
||||
var combo = new ComboBox() { Left = 20, Top = 20, Width = 200 };
|
||||
combo.Items.AddRange(new object[]
|
||||
{
|
||||
new { Text="1000K", Value=CanInterface.SLCAN_BR_CIA_1000K },
|
||||
new { Text="800K", Value=CanInterface.SLCAN_BR_CIA_800K },
|
||||
new { Text="500K", Value=CanInterface.SLCAN_BR_CIA_500K },
|
||||
new { Text="250K", Value=CanInterface.SLCAN_BR_CIA_250K },
|
||||
new { Text="125K", Value=CanInterface.SLCAN_BR_CIA_125K },
|
||||
});
|
||||
combo.DisplayMember = "Text";
|
||||
combo.ValueMember = "Value";
|
||||
combo.SelectedIndex = 3; // по умолчанию 250K
|
||||
|
||||
var ok = new Button() { Text = "OK", Left = 20, Top = 60, DialogResult = DialogResult.OK };
|
||||
dlg.Controls.Add(combo);
|
||||
dlg.Controls.Add(ok);
|
||||
dlg.AcceptButton = ok;
|
||||
|
||||
if (dlg.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
ushort bitrate = (ushort)((dynamic)combo.SelectedItem).Value;
|
||||
|
||||
if (can.ConnectFirstDevice(bitrate, out string status))
|
||||
lblStatus.Text = status;
|
||||
else
|
||||
{
|
||||
MessageBox.Show(status, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
lblStatus.Text = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int ReadFirmware()
|
||||
{
|
||||
@@ -222,7 +343,7 @@ namespace BootloaderGUI
|
||||
{
|
||||
uint timeout = 1000u;
|
||||
UInt16 error = 0;
|
||||
|
||||
Thread.Sleep(10);
|
||||
try
|
||||
{
|
||||
if (fwData == null) return;
|
||||
@@ -269,7 +390,7 @@ namespace BootloaderGUI
|
||||
byte[] chunk = new byte[8];
|
||||
Array.Copy(full, offset, chunk, 0, chunkLen);
|
||||
|
||||
if (!can.SendData(chunk))
|
||||
if (!transport.SendData(chunk))
|
||||
{
|
||||
UpdateStatusOnUI($"Failed to send data chunk at offset {offset}");
|
||||
return;
|
||||
@@ -279,13 +400,13 @@ namespace BootloaderGUI
|
||||
int percent = (int)((sentBytes * 100L) / totalBytes);
|
||||
UpdateProgressOnUI(percent);
|
||||
|
||||
Thread.Sleep(1); // throttle
|
||||
Thread.Sleep(5); // throttle
|
||||
}
|
||||
|
||||
UpdateProgressOnUI(100);
|
||||
|
||||
|
||||
if(can.WaitForResponse(timeout, out error))
|
||||
if(transport.WaitForResponse(timeout, out error))
|
||||
{
|
||||
UpdateStatusOnUI($"Page {pageNum} sent (RECEIVE)");
|
||||
}
|
||||
@@ -303,25 +424,30 @@ namespace BootloaderGUI
|
||||
{
|
||||
UpdateStatusOnUI("Error during RECEIVE: " + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool SendCmdSync(byte cmd)
|
||||
{
|
||||
if (can == null)
|
||||
if (transport == null)
|
||||
{
|
||||
MessageBox.Show("CAN device not connected", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!can.SendCmd(cmd, out UInt16 error))
|
||||
if (!transport.SendCmd(cmd, out UInt16 error))
|
||||
{
|
||||
if ((error == 0xFFFF) && cmd != (BootloaderCommands.RESET) && (cmd != BootloaderCommands.JUMP))
|
||||
{
|
||||
if (error == 0xFFFF)
|
||||
MessageBox.Show($"Command 0x{cmd:X2} timed out", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
else
|
||||
MessageBox.Show($"Command 0x{cmd:X2} failed, error code: 0x{error:X4}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatusOnUI($"Command 0x{cmd:X2} sent successfully");
|
||||
return true;
|
||||
|
||||
199
botterm/botterm/UartInterface.cs
Normal file
199
botterm/botterm/UartInterface.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace BootloaderGUI
|
||||
{
|
||||
public class UartInterface : IBootTransport
|
||||
{
|
||||
private SerialPort port;
|
||||
private CancellationTokenSource rxCts;
|
||||
private ConcurrentQueue<byte[]> rxQueue = new ConcurrentQueue<byte[]>();
|
||||
private bool waitingForResponse = false;
|
||||
|
||||
public event Action<UInt16> ErrorReceived;
|
||||
public bool Connect(ushort bitrateCode, out string status)
|
||||
{
|
||||
status = "";
|
||||
|
||||
using (var dlg = new SelectUartDialog())
|
||||
{
|
||||
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
status = "UART connect cancelled";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
port = new SerialPort(dlg.SelectedPort, dlg.SelectedBaudRate)
|
||||
{
|
||||
Parity = Parity.None,
|
||||
StopBits = StopBits.One,
|
||||
DataBits = 8,
|
||||
Handshake = Handshake.None,
|
||||
ReadTimeout = 500,
|
||||
WriteTimeout = 500
|
||||
};
|
||||
port.Open();
|
||||
|
||||
StartBackgroundReceive();
|
||||
status = $"UART connected ({dlg.SelectedPort}, {dlg.SelectedBaudRate} baud)";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status = $"UART connect failed: {ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
try
|
||||
{
|
||||
StopBackgroundReceive();
|
||||
port?.Close();
|
||||
port = null;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public bool SendData(byte[] data)
|
||||
{
|
||||
if (port == null || !port.IsOpen) return false;
|
||||
try
|
||||
{
|
||||
port.Write(data, 0, data.Length);
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
public bool SendCmd(byte cmd, out UInt16 error)
|
||||
{
|
||||
error = 0;
|
||||
if (port == null || !port.IsOpen) return false;
|
||||
|
||||
byte[] frame = new byte[1] { cmd };
|
||||
if (!SendData(frame)) return false;
|
||||
|
||||
// Таймаут: ERASE — 10с, остальные — 1с
|
||||
uint timeout = (cmd == BootloaderCommands.ERASE) ? 10000u : 1000u;
|
||||
|
||||
if (cmd != BootloaderCommands.JUMP)
|
||||
{
|
||||
return WaitForResponse(timeout, out error);
|
||||
}
|
||||
else
|
||||
{
|
||||
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 response))
|
||||
{
|
||||
waitingForResponse = false;
|
||||
if (response.Length > 0 && response[0] == 0)
|
||||
return true;
|
||||
else if (response.Length >= 3)
|
||||
{
|
||||
error = (UInt16)((response[1] << 8) | response[2]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
|
||||
waitingForResponse = false;
|
||||
error = 0xFFFF;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void StartBackgroundReceive()
|
||||
{
|
||||
rxCts = new CancellationTokenSource();
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] packetBuffer = new byte[3];
|
||||
int packetIndex = 0;
|
||||
bool accumulating = false;
|
||||
|
||||
while (!rxCts.Token.IsCancellationRequested && port != null && port.IsOpen)
|
||||
{
|
||||
try
|
||||
{
|
||||
int b = port.ReadByte(); // читаем один байт
|
||||
if (b == -1) continue;
|
||||
|
||||
byte received = (byte)b;
|
||||
|
||||
if (!accumulating)
|
||||
{
|
||||
if (received == 0) // ответ на команду
|
||||
{
|
||||
if (waitingForResponse)
|
||||
rxQueue.Enqueue(new byte[] { 0, 0, 0 });
|
||||
}
|
||||
else
|
||||
{
|
||||
// начинаем собирать пакет
|
||||
packetBuffer[0] = received;
|
||||
packetIndex = 1;
|
||||
accumulating = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
packetBuffer[packetIndex++] = received;
|
||||
if (packetIndex == 3)
|
||||
{
|
||||
UInt16 err = (UInt16)((packetBuffer[1] << 8) | packetBuffer[2]);
|
||||
ErrorReceived?.Invoke(err);
|
||||
|
||||
packetIndex = 0;
|
||||
accumulating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException) { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}, rxCts.Token);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void StopBackgroundReceive()
|
||||
{
|
||||
try
|
||||
{
|
||||
rxCts?.Cancel();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
// Для UART освобождать особо нечего,
|
||||
// можно просто вызвать Disconnect()
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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) =>
|
||||
{
|
||||
if(sendCmdSync(BootloaderCommands.JUMP))
|
||||
UpdateStatus("Jumping to app...");
|
||||
sendCmdSync(BootloaderCommands.JUMP);
|
||||
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...");
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -52,8 +52,11 @@
|
||||
<Compile Include="BootloaderCommands.cs" />
|
||||
<Compile Include="CanInterface.cs" />
|
||||
<Compile Include="LowLevelBoot.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Program.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UartInterface.cs" />
|
||||
<Compile Include="UserFriendlyBoot.cs" />
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
6e4f7064cd453f33ddf5825a7ce97a1c149aef21
|
||||
53453d69e3ea95b15cdc39cca4e57903e0f3fae9
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user