diff --git a/botterm/.vs/botterm/v16/.suo b/botterm/.vs/botterm/v16/.suo index 09b380d..59c6983 100644 Binary files a/botterm/.vs/botterm/v16/.suo and b/botterm/.vs/botterm/v16/.suo differ diff --git a/botterm/botterm/BootloaderCommands.cs b/botterm/botterm/BootloaderCommands.cs index c9a533f..03dd5f3 100644 --- a/botterm/botterm/BootloaderCommands.cs +++ b/botterm/botterm/BootloaderCommands.cs @@ -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; // Если нужно, можно добавить дополнительные команды diff --git a/botterm/botterm/CanInterface.cs b/botterm/botterm/CanInterface.cs index 1637aab..41b7adc 100644 --- a/botterm/botterm/CanInterface.cs +++ b/botterm/botterm/CanInterface.cs @@ -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() diff --git a/botterm/botterm/Program.cs b/botterm/botterm/Program.cs index 82b4f99..8bdda65 100644 --- a/botterm/botterm/Program.cs +++ b/botterm/botterm/Program.cs @@ -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 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 ProgressBar progressBar; - private Button btnSelect, btnConnect; + private Button btnPing; + private ProgressBar progressBar; + 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) => { - ushort? bitrate = CanInterface.CanSpeedDialog.ShowDialog(); - if (bitrate == null) return; - string status; - if (can.ConnectFirstDevice(bitrate.Value, out status)) - { - lblStatus.Text = status; - // запуск фонового приёма - can.ErrorReceived -= Can_ErrorReceived; // безопасно удалить старую подписку - can.ErrorReceived += Can_ErrorReceived; - can.StartBackgroundReceive(); - } - else + if (comboTransport.SelectedItem.ToString() == "CAN") { - lblStatus.Text = "Connect failed"; + transport = new CanInterface(); + ushort? bitrate = CanInterface.CanSpeedDialog.ShowDialog(); + if (bitrate == null) return; + + if (transport.Connect(bitrate.Value, out status)) + { + lblStatus.Text = status; + transport.ErrorReceived += Can_ErrorReceived; + } + else + { + 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)) { diff --git a/botterm/botterm/UartInterface.cs b/botterm/botterm/UartInterface.cs new file mode 100644 index 0000000..ec0fa4c --- /dev/null +++ b/botterm/botterm/UartInterface.cs @@ -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 rxQueue = new ConcurrentQueue(); + private bool waitingForResponse = false; + + public event Action 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(); + } + + } +} diff --git a/botterm/botterm/bin/Debug/botterm.exe b/botterm/botterm/bin/Debug/botterm.exe index 0de060a..d097616 100644 Binary files a/botterm/botterm/bin/Debug/botterm.exe and b/botterm/botterm/bin/Debug/botterm.exe differ diff --git a/botterm/botterm/bin/Debug/botterm.pdb b/botterm/botterm/bin/Debug/botterm.pdb index f6f96b7..db0d399 100644 Binary files a/botterm/botterm/bin/Debug/botterm.pdb and b/botterm/botterm/bin/Debug/botterm.pdb differ diff --git a/botterm/botterm/botterm.csproj b/botterm/botterm/botterm.csproj index 9d9e726..899b417 100644 --- a/botterm/botterm/botterm.csproj +++ b/botterm/botterm/botterm.csproj @@ -52,8 +52,11 @@ - + + Form + + ResXFileCodeGenerator diff --git a/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferences.cache b/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferences.cache index 6af2086..f5e894a 100644 Binary files a/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferences.cache and b/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferences.cache differ diff --git a/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache index 504d367..4532d62 100644 Binary files a/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache and b/botterm/botterm/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/botterm/botterm/obj/Debug/botterm.csproj.AssemblyReference.cache b/botterm/botterm/obj/Debug/botterm.csproj.AssemblyReference.cache index 1228a24..a7d294a 100644 Binary files a/botterm/botterm/obj/Debug/botterm.csproj.AssemblyReference.cache and b/botterm/botterm/obj/Debug/botterm.csproj.AssemblyReference.cache differ diff --git a/botterm/botterm/obj/Debug/botterm.csproj.CoreCompileInputs.cache b/botterm/botterm/obj/Debug/botterm.csproj.CoreCompileInputs.cache index e30f43b..7109b84 100644 --- a/botterm/botterm/obj/Debug/botterm.csproj.CoreCompileInputs.cache +++ b/botterm/botterm/obj/Debug/botterm.csproj.CoreCompileInputs.cache @@ -1 +1 @@ -6e4f7064cd453f33ddf5825a7ce97a1c149aef21 +53453d69e3ea95b15cdc39cca4e57903e0f3fae9 diff --git a/botterm/botterm/obj/Debug/botterm.exe b/botterm/botterm/obj/Debug/botterm.exe index 0de060a..d097616 100644 Binary files a/botterm/botterm/obj/Debug/botterm.exe and b/botterm/botterm/obj/Debug/botterm.exe differ diff --git a/botterm/botterm/obj/Debug/botterm.pdb b/botterm/botterm/obj/Debug/botterm.pdb index f6f96b7..db0d399 100644 Binary files a/botterm/botterm/obj/Debug/botterm.pdb and b/botterm/botterm/obj/Debug/botterm.pdb differ