добавлен бета uart и пинг

This commit is contained in:
Razvalyaev 2025-09-19 14:11:04 +03:00
parent 2609102829
commit 0a2d33924a
14 changed files with 386 additions and 75 deletions

Binary file not shown.

View File

@ -16,6 +16,8 @@ namespace BootloaderGUI
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;
// Если нужно, можно добавить дополнительные команды

View File

@ -7,7 +7,7 @@ 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;
@ -198,6 +198,7 @@ namespace BootloaderGUI
return false;
}
StartBackgroundReceive();
statusText = $"Connected to device 0 (devices: {count}) at {bitrateCode}";
return true;
}
@ -406,7 +407,10 @@ namespace BootloaderGUI
}
public bool Connect(ushort bitrateCode, out string statusText)
{
return ConnectFirstDevice(bitrateCode, out statusText);
}
public void Disconnect()

View File

@ -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,32 +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;
// запуск фонового приёма
can.ErrorReceived -= Can_ErrorReceived; // безопасно удалить старую подписку
can.ErrorReceived += Can_ErrorReceived;
can.StartBackgroundReceive();
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()
@ -166,54 +309,14 @@ namespace BootloaderGUI
{
try
{
can?.StopBackgroundReceive();
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()
{
@ -287,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;
@ -297,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)");
}
@ -326,13 +429,13 @@ namespace BootloaderGUI
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))
{

View 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();
}
}
}

View File

@ -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>

View File

@ -1 +1 @@
6e4f7064cd453f33ddf5825a7ce97a1c149aef21
53453d69e3ea95b15cdc39cca4e57903e0f3fae9