Прошивка моста BL616 для Tang Primer 25K и программатор TangProgrammer
- src/: прошивка встроенного отладчика BL616 — составное USB-устройство 1209:C0CA: DirtyJTAG v3 (WinUSB через MS OS 2.0) + CDC-ACM UART к FPGA B3/C3; UART до 20 Мбод на DMA, JTAG до 30 МГц через аппаратный SPI; - bin/: загрузчик Sipeed (0x0) и штатная прошивка 2025030317 для отката; - gui/: TangProgrammer.exe (C# WinForms, .NET Framework 4) — загрузка FPGA в SRAM и во флеш-память, копия и стирание флеш-памяти FPGA, прошивка моста; светлая и тёмная тема; - scripts/: setup, build, flash, build_gui, make_portable, а также make_release и publish_release для релизов Gitea; - fpga_test/, tests/: тестовые проекты Gowin (loopback, stream) и тесты UART на Python; - tools/openFPGALoader/: openFPGALoader 1.1.1 для Windows x64.
This commit is contained in:
+793
@@ -0,0 +1,793 @@
|
||||
// Light/dark palette and the owner-drawn controls of the programmer GUI.
|
||||
// Stock WinForms controls (tabs, buttons, check boxes, progress bar) are
|
||||
// drawn by the light Windows theme only, so these paint themselves.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace TangProgrammer
|
||||
{
|
||||
enum Tone { Text, Dim, Ok, Err, Warn, Title }
|
||||
|
||||
sealed class Theme
|
||||
{
|
||||
public bool IsDark;
|
||||
public Color Window, Card, CardBorder, LogBg;
|
||||
public Color Text, TextDim, TextDisabled;
|
||||
public Color Button, ButtonHover, ButtonPressed, ButtonBorder, ButtonDisabled;
|
||||
public Color Accent, AccentHover, AccentPressed, AccentText, AccentDisabled, AccentTextDisabled;
|
||||
public Color ToggleFill, ToggleBorder;
|
||||
public Color Input, Track;
|
||||
public Color Ok, Err, Warn, Title;
|
||||
public Color DotOn, DotWarn, DotOff;
|
||||
public Color MenuBg, MenuHover, MenuBorder;
|
||||
|
||||
public static readonly Theme Light = new Theme
|
||||
{
|
||||
Window = Rgb(0xF3F3F3), Card = Rgb(0xFFFFFF), CardBorder = Rgb(0xE3E3E3), LogBg = Rgb(0xFFFFFF),
|
||||
Text = Rgb(0x1B1B1B), TextDim = Rgb(0x626262), TextDisabled = Rgb(0xA3A3A3),
|
||||
Button = Rgb(0xFBFBFB), ButtonHover = Rgb(0xF3F3F3), ButtonPressed = Rgb(0xEBEBEB),
|
||||
ButtonBorder = Rgb(0xD3D3D3), ButtonDisabled = Rgb(0xF5F5F5),
|
||||
Accent = Rgb(0x0067C0), AccentHover = Rgb(0x1975C5), AccentPressed = Rgb(0x3183CA),
|
||||
AccentText = Rgb(0xFFFFFF), AccentDisabled = Rgb(0xC5C5C5), AccentTextDisabled = Rgb(0xFFFFFF),
|
||||
ToggleFill = Rgb(0xFAFAFA), ToggleBorder = Rgb(0x8A8A8A),
|
||||
Input = Rgb(0xFFFFFF), Track = Rgb(0xE0E0E0),
|
||||
Ok = Rgb(0x0F7B0F), Err = Rgb(0xC42B1C), Warn = Rgb(0x9D5D00), Title = Rgb(0x005FB8),
|
||||
DotOn = Rgb(0x13A10E), DotWarn = Rgb(0xE8911A), DotOff = Rgb(0xA0A0A0),
|
||||
MenuBg = Rgb(0xFFFFFF), MenuHover = Rgb(0xEDEDED), MenuBorder = Rgb(0xD5D5D5),
|
||||
};
|
||||
|
||||
public static readonly Theme Dark = new Theme
|
||||
{
|
||||
IsDark = true,
|
||||
Window = Rgb(0x202020), Card = Rgb(0x2B2B2B), CardBorder = Rgb(0x3A3A3A), LogBg = Rgb(0x252525),
|
||||
Text = Rgb(0xEDEDED), TextDim = Rgb(0xA6A6A6), TextDisabled = Rgb(0x6E6E6E),
|
||||
Button = Rgb(0x373737), ButtonHover = Rgb(0x3E3E3E), ButtonPressed = Rgb(0x333333),
|
||||
ButtonBorder = Rgb(0x484848), ButtonDisabled = Rgb(0x313131),
|
||||
Accent = Rgb(0x4CC2FF), AccentHover = Rgb(0x48B1E8), AccentPressed = Rgb(0x42A1D2),
|
||||
AccentText = Rgb(0x000000), AccentDisabled = Rgb(0x454545), AccentTextDisabled = Rgb(0x9A9A9A),
|
||||
ToggleFill = Rgb(0x262626), ToggleBorder = Rgb(0x9E9E9E),
|
||||
Input = Rgb(0x383838), Track = Rgb(0x474747),
|
||||
Ok = Rgb(0x6CCB5F), Err = Rgb(0xFF99A4), Warn = Rgb(0xFCE100), Title = Rgb(0x60CDFF),
|
||||
DotOn = Rgb(0x6CCB5F), DotWarn = Rgb(0xF7B955), DotOff = Rgb(0x6E6E6E),
|
||||
MenuBg = Rgb(0x2C2C2C), MenuHover = Rgb(0x3D3D3D), MenuBorder = Rgb(0x484848),
|
||||
};
|
||||
|
||||
public static Theme Current = Light;
|
||||
|
||||
static Color Rgb(int rgb)
|
||||
{
|
||||
return Color.FromArgb((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
|
||||
}
|
||||
|
||||
public Color Of(Tone tone)
|
||||
{
|
||||
switch (tone)
|
||||
{
|
||||
case Tone.Dim: return TextDim;
|
||||
case Tone.Ok: return Ok;
|
||||
case Tone.Err: return Err;
|
||||
case Tone.Warn: return Warn;
|
||||
case Tone.Title: return Title;
|
||||
default: return Text;
|
||||
}
|
||||
}
|
||||
|
||||
// "Choose your app mode" in the Windows settings
|
||||
public static bool SystemPrefersDark()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (RegistryKey k = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
|
||||
{
|
||||
object v = k == null ? null : k.GetValue("AppsUseLightTheme");
|
||||
return v is int && (int)v == 0;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Ui
|
||||
{
|
||||
public static float Scale = 1f;
|
||||
|
||||
public static int S(float px)
|
||||
{
|
||||
return (int)Math.Round(px * Scale);
|
||||
}
|
||||
|
||||
public static GraphicsPath Round(RectangleF r, float radius)
|
||||
{
|
||||
var p = new GraphicsPath();
|
||||
float d = Math.Min(radius * 2, Math.Min(r.Width, r.Height));
|
||||
if (d < 1f)
|
||||
{
|
||||
p.AddRectangle(r);
|
||||
return p;
|
||||
}
|
||||
p.AddArc(r.X, r.Y, d, d, 180, 90);
|
||||
p.AddArc(r.Right - d, r.Y, d, d, 270, 90);
|
||||
p.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90);
|
||||
p.AddArc(r.X, r.Bottom - d, d, d, 90, 90);
|
||||
p.CloseFigure();
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Color ParentBack(Control c)
|
||||
{
|
||||
return c.Parent != null ? c.Parent.BackColor : c.BackColor;
|
||||
}
|
||||
|
||||
[DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
|
||||
static extern int SetWindowTheme(IntPtr hwnd, string subAppName, string subIdList);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int value, int size);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr SendMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
// dark scroll bars and combo boxes of native controls (Windows 10 1809+);
|
||||
// null restores the default look
|
||||
public static void SetNativeTheme(Control c, string themeClass)
|
||||
{
|
||||
SetWindowTheme(c.Handle, themeClass, null);
|
||||
c.Invalidate();
|
||||
}
|
||||
|
||||
public static void SetDarkTitleBar(Form f, bool dark)
|
||||
{
|
||||
int v = dark ? 1 : 0;
|
||||
// DWMWA_USE_IMMERSIVE_DARK_MODE: 20, was 19 before Windows 10 20H1
|
||||
if (DwmSetWindowAttribute(f.Handle, 20, ref v, 4) != 0)
|
||||
DwmSetWindowAttribute(f.Handle, 19, ref v, 4);
|
||||
}
|
||||
|
||||
public static void SetRedraw(Control c, bool on)
|
||||
{
|
||||
SendMessage(c.Handle, 0x000B /* WM_SETREDRAW */, (IntPtr)(on ? 1 : 0), IntPtr.Zero);
|
||||
if (on)
|
||||
c.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// White (or dark) rounded panel with a thin border
|
||||
sealed class Card : Panel
|
||||
{
|
||||
public Color Border = Color.Gray;
|
||||
|
||||
public Card()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
Graphics g = e.Graphics;
|
||||
g.Clear(Ui.ParentBack(this));
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (GraphicsPath p = Ui.Round(new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f), Ui.S(7)))
|
||||
using (var fill = new SolidBrush(BackColor))
|
||||
using (var pen = new Pen(Border))
|
||||
{
|
||||
g.FillPath(fill, p);
|
||||
g.DrawPath(pen, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text tabs with an accent underline (replaces the TabControl, which can't be dark)
|
||||
sealed class TabStrip : Control
|
||||
{
|
||||
readonly List<string> items = new List<string>();
|
||||
int selected, hot = -1;
|
||||
Font bold;
|
||||
|
||||
public event EventHandler SelectedIndexChanged;
|
||||
|
||||
public TabStrip()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.Selectable, false);
|
||||
}
|
||||
|
||||
public void Add(string text)
|
||||
{
|
||||
items.Add(text);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get { return selected; }
|
||||
set
|
||||
{
|
||||
if (value == selected || value < 0 || value >= items.Count)
|
||||
return;
|
||||
selected = value;
|
||||
Invalidate();
|
||||
if (SelectedIndexChanged != null)
|
||||
SelectedIndexChanged(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return items.Count; }
|
||||
}
|
||||
|
||||
Font Bold
|
||||
{
|
||||
get
|
||||
{
|
||||
if (bold == null || bold.Size != Font.Size)
|
||||
bold = new Font("Segoe UI Semibold", Font.Size);
|
||||
return bold;
|
||||
}
|
||||
}
|
||||
|
||||
int ItemWidth(int i)
|
||||
{
|
||||
return TextRenderer.MeasureText(items[i], Bold).Width + Ui.S(24);
|
||||
}
|
||||
|
||||
Rectangle ItemRect(int i)
|
||||
{
|
||||
int x = 0;
|
||||
for (int k = 0; k < i; k++)
|
||||
x += ItemWidth(k);
|
||||
return new Rectangle(x, 0, ItemWidth(i), Height);
|
||||
}
|
||||
|
||||
int HitTest(Point p)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
if (ItemRect(i).Contains(p))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public override Size GetPreferredSize(Size proposed)
|
||||
{
|
||||
int w = 0;
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
w += ItemWidth(i);
|
||||
return new Size(w, Font.Height + Ui.S(16));
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Theme th = Theme.Current;
|
||||
Graphics g = e.Graphics;
|
||||
g.Clear(Ui.ParentBack(this));
|
||||
int line = Ui.S(3);
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
Rectangle r = ItemRect(i);
|
||||
bool sel = i == selected;
|
||||
Color c = sel || i == hot ? th.Text : th.TextDim;
|
||||
TextRenderer.DrawText(g, items[i], sel ? Bold : Font, new Rectangle(r.X, 0, r.Width, r.Height - line),
|
||||
c, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
|
||||
TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
|
||||
if (sel)
|
||||
{
|
||||
int w = TextRenderer.MeasureText(items[i], Bold).Width - Ui.S(6);
|
||||
var u = new RectangleF(r.X + (r.Width - w) / 2f, r.Bottom - line - Ui.S(1), w, line);
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (GraphicsPath p = Ui.Round(u, line / 2f))
|
||||
using (var b = new SolidBrush(th.Accent))
|
||||
g.FillPath(b, p);
|
||||
g.SmoothingMode = SmoothingMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
int h = HitTest(e.Location);
|
||||
if (h != hot)
|
||||
{
|
||||
hot = h;
|
||||
Cursor = h >= 0 && h != selected ? Cursors.Hand : Cursors.Default;
|
||||
Invalidate();
|
||||
}
|
||||
base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
hot = -1;
|
||||
Invalidate();
|
||||
base.OnMouseLeave(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
int h = HitTest(e.Location);
|
||||
if (h >= 0 && e.Button == MouseButtons.Left)
|
||||
{
|
||||
SelectedIndex = h;
|
||||
Cursor = Cursors.Default;
|
||||
}
|
||||
base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && bold != null)
|
||||
bold.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
enum Glyph { None, Contrast }
|
||||
|
||||
// Rounded button in the colors of the current theme; Accent = the main action
|
||||
sealed class FlatButton : Button
|
||||
{
|
||||
public bool Accent;
|
||||
public Glyph Glyph;
|
||||
bool hover, pressed;
|
||||
|
||||
public FlatButton()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
FlatStyle = FlatStyle.Flat;
|
||||
FlatAppearance.BorderSize = 0;
|
||||
UseVisualStyleBackColor = false;
|
||||
}
|
||||
|
||||
public override Size GetPreferredSize(Size proposed)
|
||||
{
|
||||
Size t = TextRenderer.MeasureText(Text, Font, Size.Empty, TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
|
||||
return new Size(Math.Max(MinimumSize.Width, t.Width + Padding.Horizontal + Ui.S(8)),
|
||||
Math.Max(MinimumSize.Height, t.Height + Padding.Vertical + Ui.S(6)));
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(EventArgs e)
|
||||
{
|
||||
hover = true;
|
||||
Invalidate();
|
||||
base.OnMouseEnter(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
hover = pressed = false;
|
||||
Invalidate();
|
||||
base.OnMouseLeave(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
pressed = true;
|
||||
Invalidate();
|
||||
}
|
||||
base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseEventArgs e)
|
||||
{
|
||||
pressed = false;
|
||||
Invalidate();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
protected override void OnEnabledChanged(EventArgs e)
|
||||
{
|
||||
hover = pressed = false;
|
||||
Invalidate();
|
||||
base.OnEnabledChanged(e);
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Theme th = Theme.Current;
|
||||
Graphics g = e.Graphics;
|
||||
g.Clear(Ui.ParentBack(this));
|
||||
Color bg, fg, border;
|
||||
if (Accent)
|
||||
{
|
||||
bg = !Enabled ? th.AccentDisabled : pressed ? th.AccentPressed : hover ? th.AccentHover : th.Accent;
|
||||
fg = Enabled ? th.AccentText : th.AccentTextDisabled;
|
||||
border = bg;
|
||||
}
|
||||
else
|
||||
{
|
||||
bg = !Enabled ? th.ButtonDisabled : pressed ? th.ButtonPressed : hover ? th.ButtonHover : th.Button;
|
||||
fg = Enabled ? th.Text : th.TextDisabled;
|
||||
border = th.ButtonBorder;
|
||||
}
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (GraphicsPath p = Ui.Round(new RectangleF(0.5f, 0.5f, Width - 1f, Height - 1f), Ui.S(4)))
|
||||
using (var b = new SolidBrush(bg))
|
||||
using (var pen = new Pen(border))
|
||||
{
|
||||
g.FillPath(b, p);
|
||||
g.DrawPath(pen, p);
|
||||
}
|
||||
if (Focused && ShowFocusCues)
|
||||
{
|
||||
using (GraphicsPath p = Ui.Round(new RectangleF(2.5f, 2.5f, Width - 5f, Height - 5f), Ui.S(3)))
|
||||
using (var pen = new Pen(fg))
|
||||
g.DrawPath(pen, p);
|
||||
}
|
||||
if (Glyph == Glyph.Contrast)
|
||||
{
|
||||
float d = Math.Min(Width, Height) * 0.46f;
|
||||
var r = new RectangleF((Width - d) / 2f, (Height - d) / 2f, d, d);
|
||||
using (var b = new SolidBrush(fg))
|
||||
g.FillPie(b, r.X, r.Y, r.Width, r.Height, 90, 180);
|
||||
using (var pen = new Pen(fg, Math.Max(1.2f, Ui.Scale * 1.3f)))
|
||||
g.DrawEllipse(pen, r);
|
||||
return;
|
||||
}
|
||||
g.SmoothingMode = SmoothingMode.None;
|
||||
TextRenderer.DrawText(g, Text, Font, ClientRectangle, fg,
|
||||
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter |
|
||||
TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix | TextFormatFlags.EndEllipsis);
|
||||
}
|
||||
}
|
||||
|
||||
static class TogglePainter
|
||||
{
|
||||
const TextFormatFlags Flags = TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix;
|
||||
|
||||
static int Box(Control c)
|
||||
{
|
||||
return Math.Max(13, c.Font.Height);
|
||||
}
|
||||
|
||||
public static Size Measure(ButtonBase c)
|
||||
{
|
||||
Size t = TextRenderer.MeasureText(c.Text, c.Font, Size.Empty, Flags);
|
||||
int box = Box(c);
|
||||
return new Size(box + Ui.S(8) + t.Width + Ui.S(2), Math.Max(box, t.Height) + Ui.S(4));
|
||||
}
|
||||
|
||||
public static void Paint(ButtonBase c, Graphics g, bool on, bool radio, bool hover, bool focus)
|
||||
{
|
||||
Theme th = Theme.Current;
|
||||
g.Clear(Ui.ParentBack(c));
|
||||
int box = Box(c);
|
||||
var r = new RectangleF(0.5f, (c.Height - box) / 2f + 0.5f, box - 1f, box - 1f);
|
||||
Color accent = !c.Enabled ? th.AccentDisabled : hover ? th.AccentHover : th.Accent;
|
||||
Color border = !c.Enabled ? th.TextDisabled : hover ? th.Text : th.ToggleBorder;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
if (radio)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
using (var b = new SolidBrush(accent))
|
||||
g.FillEllipse(b, r);
|
||||
float d = box * 0.4f;
|
||||
using (var b = new SolidBrush(th.AccentText))
|
||||
g.FillEllipse(b, r.X + (r.Width - d) / 2f, r.Y + (r.Height - d) / 2f, d, d);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var b = new SolidBrush(th.ToggleFill))
|
||||
g.FillEllipse(b, r);
|
||||
using (var pen = new Pen(border))
|
||||
g.DrawEllipse(pen, r);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (GraphicsPath p = Ui.Round(r, box * 0.2f))
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
using (var b = new SolidBrush(accent))
|
||||
g.FillPath(b, p);
|
||||
using (var pen = new Pen(th.AccentText, Math.Max(1.5f, box * 0.12f)))
|
||||
{
|
||||
pen.StartCap = pen.EndCap = LineCap.Round;
|
||||
pen.LineJoin = LineJoin.Round;
|
||||
g.DrawLines(pen, new[]
|
||||
{
|
||||
new PointF(r.X + box * 0.25f, r.Y + box * 0.52f),
|
||||
new PointF(r.X + box * 0.43f, r.Y + box * 0.70f),
|
||||
new PointF(r.X + box * 0.76f, r.Y + box * 0.32f),
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var b = new SolidBrush(th.ToggleFill))
|
||||
g.FillPath(b, p);
|
||||
using (var pen = new Pen(border))
|
||||
g.DrawPath(pen, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
g.SmoothingMode = SmoothingMode.None;
|
||||
var tr = new Rectangle(box + Ui.S(8), 0, Math.Max(0, c.Width - box - Ui.S(8)), c.Height);
|
||||
TextRenderer.DrawText(g, c.Text, c.Font, tr, c.Enabled ? th.Text : th.TextDisabled,
|
||||
Flags | TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis);
|
||||
if (focus)
|
||||
{
|
||||
Size t = TextRenderer.MeasureText(c.Text, c.Font, Size.Empty, Flags);
|
||||
ControlPaint.DrawFocusRectangle(g, new Rectangle(tr.X - 1, (c.Height - t.Height) / 2, t.Width + 2, t.Height),
|
||||
th.Text, Ui.ParentBack(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ThemedCheck : CheckBox
|
||||
{
|
||||
bool hover;
|
||||
|
||||
public ThemedCheck()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
}
|
||||
|
||||
public override Size GetPreferredSize(Size proposed)
|
||||
{
|
||||
return TogglePainter.Measure(this);
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
TogglePainter.Paint(this, e.Graphics, Checked, false, hover, Focused && ShowFocusCues);
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(EventArgs e)
|
||||
{
|
||||
hover = true;
|
||||
Invalidate();
|
||||
base.OnMouseEnter(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
hover = false;
|
||||
Invalidate();
|
||||
base.OnMouseLeave(e);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ThemedRadio : RadioButton
|
||||
{
|
||||
bool hover;
|
||||
|
||||
public ThemedRadio()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
}
|
||||
|
||||
public override Size GetPreferredSize(Size proposed)
|
||||
{
|
||||
return TogglePainter.Measure(this);
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
TogglePainter.Paint(this, e.Graphics, Checked, true, hover, Focused && ShowFocusCues);
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(EventArgs e)
|
||||
{
|
||||
hover = true;
|
||||
Invalidate();
|
||||
base.OnMouseEnter(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
hover = false;
|
||||
Invalidate();
|
||||
base.OnMouseLeave(e);
|
||||
}
|
||||
}
|
||||
|
||||
enum BarState { Running, Ok, Error, Paused }
|
||||
|
||||
// Thin rounded progress bar; Marquee = "busy, amount unknown"
|
||||
sealed class FlatProgress : Control
|
||||
{
|
||||
int value;
|
||||
bool marquee;
|
||||
BarState state;
|
||||
float phase;
|
||||
readonly Timer timer = new Timer { Interval = 15 };
|
||||
|
||||
public FlatProgress()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer |
|
||||
ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.Selectable, false);
|
||||
timer.Tick += (s, e) =>
|
||||
{
|
||||
phase += 0.009f;
|
||||
if (phase > 1f)
|
||||
phase -= 1f;
|
||||
Invalidate();
|
||||
};
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get { return value; }
|
||||
set { this.value = Math.Max(0, Math.Min(100, value)); Invalidate(); }
|
||||
}
|
||||
|
||||
public bool Marquee
|
||||
{
|
||||
get { return marquee; }
|
||||
set { marquee = value; phase = 0; timer.Enabled = value; Invalidate(); }
|
||||
}
|
||||
|
||||
public BarState State
|
||||
{
|
||||
get { return state; }
|
||||
set { state = value; Invalidate(); }
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
Theme th = Theme.Current;
|
||||
Graphics g = e.Graphics;
|
||||
g.Clear(Ui.ParentBack(this));
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
float h = Math.Min(Height, Ui.S(6));
|
||||
var track = new RectangleF(0, (Height - h) / 2f, Width, h);
|
||||
using (GraphicsPath p = Ui.Round(track, h / 2f))
|
||||
using (var b = new SolidBrush(th.Track))
|
||||
g.FillPath(b, p);
|
||||
RectangleF bar;
|
||||
if (marquee)
|
||||
{
|
||||
float seg = Width * 0.28f;
|
||||
// ease in-out sweep across the track
|
||||
float t = (float)(0.5 - Math.Cos(phase * Math.PI) / 2);
|
||||
bar = RectangleF.Intersect(new RectangleF(-seg + t * (Width + seg), track.Y, seg, h), track);
|
||||
}
|
||||
else
|
||||
{
|
||||
bar = new RectangleF(0, track.Y, Width * value / 100f, h);
|
||||
}
|
||||
if (bar.Width < 0.5f)
|
||||
return;
|
||||
Color fill = state == BarState.Ok ? th.Ok : state == BarState.Error ? th.Err :
|
||||
state == BarState.Paused ? th.Warn : th.Accent;
|
||||
using (GraphicsPath p = Ui.Round(bar, h / 2f))
|
||||
using (var b = new SolidBrush(fill))
|
||||
g.FillPath(b, p);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
timer.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class StatusDot : Control
|
||||
{
|
||||
Color dotColor = Color.Silver;
|
||||
|
||||
public StatusDot()
|
||||
{
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
|
||||
}
|
||||
|
||||
public Color DotColor
|
||||
{
|
||||
get { return dotColor; }
|
||||
set { dotColor = value; Invalidate(); }
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
e.Graphics.Clear(Ui.ParentBack(this));
|
||||
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (var b = new SolidBrush(dotColor))
|
||||
e.Graphics.FillEllipse(b, 0.5f, 0.5f, Width - 1.5f, Height - 1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
// Context menus in the colors of the current theme
|
||||
sealed class MenuRenderer : ToolStripProfessionalRenderer
|
||||
{
|
||||
public MenuRenderer() : base(new MenuColors())
|
||||
{
|
||||
RoundedEdges = false;
|
||||
}
|
||||
|
||||
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
|
||||
{
|
||||
e.TextColor = e.Item.Enabled ? Theme.Current.Text : Theme.Current.TextDisabled;
|
||||
base.OnRenderItemText(e);
|
||||
}
|
||||
|
||||
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
|
||||
{
|
||||
Rectangle r = e.ImageRectangle;
|
||||
float s = Math.Min(r.Width, r.Height);
|
||||
float x = r.X + (r.Width - s) / 2f, y = r.Y + (r.Height - s) / 2f;
|
||||
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
using (var pen = new Pen(Theme.Current.Text, Math.Max(1.5f, s * 0.11f)))
|
||||
{
|
||||
pen.StartCap = pen.EndCap = LineCap.Round;
|
||||
pen.LineJoin = LineJoin.Round;
|
||||
e.Graphics.DrawLines(pen, new[]
|
||||
{
|
||||
new PointF(x + s * 0.22f, y + s * 0.52f),
|
||||
new PointF(x + s * 0.42f, y + s * 0.72f),
|
||||
new PointF(x + s * 0.78f, y + s * 0.30f),
|
||||
});
|
||||
}
|
||||
e.Graphics.SmoothingMode = SmoothingMode.None;
|
||||
}
|
||||
|
||||
sealed class MenuColors : ProfessionalColorTable
|
||||
{
|
||||
public MenuColors()
|
||||
{
|
||||
UseSystemColors = false;
|
||||
}
|
||||
|
||||
static Theme T
|
||||
{
|
||||
get { return Theme.Current; }
|
||||
}
|
||||
|
||||
public override Color ToolStripDropDownBackground { get { return T.MenuBg; } }
|
||||
public override Color ImageMarginGradientBegin { get { return T.MenuBg; } }
|
||||
public override Color ImageMarginGradientMiddle { get { return T.MenuBg; } }
|
||||
public override Color ImageMarginGradientEnd { get { return T.MenuBg; } }
|
||||
public override Color MenuBorder { get { return T.MenuBorder; } }
|
||||
public override Color MenuItemBorder { get { return T.MenuHover; } }
|
||||
public override Color MenuItemSelected { get { return T.MenuHover; } }
|
||||
public override Color MenuItemSelectedGradientBegin { get { return T.MenuHover; } }
|
||||
public override Color MenuItemSelectedGradientEnd { get { return T.MenuHover; } }
|
||||
public override Color SeparatorDark { get { return T.MenuBorder; } }
|
||||
public override Color SeparatorLight { get { return T.MenuBg; } }
|
||||
public override Color CheckBackground { get { return T.MenuBg; } }
|
||||
public override Color CheckSelectedBackground { get { return T.MenuHover; } }
|
||||
public override Color CheckPressedBackground { get { return T.MenuHover; } }
|
||||
}
|
||||
}
|
||||
}
|
||||
+2034
File diff suppressed because it is too large
Load Diff
+433
@@ -0,0 +1,433 @@
|
||||
// Helpers of the programmer GUI: USB device discovery, running the
|
||||
// command-line tools, settings, a few Win32 calls.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace TangProgrammer
|
||||
{
|
||||
// What Windows sees on USB (see README: installation and restore)
|
||||
sealed class BridgeState
|
||||
{
|
||||
public bool Present; // this firmware: DirtyJTAG + CDC UART, 1209:C0CA
|
||||
public string Version; // from bcdDevice, e.g. "1.00"
|
||||
public string UartPort; // COM port of its CDC interface
|
||||
public bool JtagDriverOk = true;
|
||||
public string BootloaderPort; // BL616 ROM USB bootloader "Bouffalo CDC DEMO", 349B:6160
|
||||
public bool SipeedStock; // stock Sipeed firmware emulates an FT2232, 0403:6010
|
||||
}
|
||||
|
||||
static class Devices
|
||||
{
|
||||
static readonly Regex ComRx = new Regex(@"\((COM\d+)\)");
|
||||
static readonly Regex RevRx = new Regex(@"REV_(\d\d)(\d\d)");
|
||||
|
||||
public static BridgeState Scan()
|
||||
{
|
||||
var st = new BridgeState();
|
||||
const string query = "SELECT Name, PNPDeviceID, HardwareID, ConfigManagerErrorCode " +
|
||||
"FROM Win32_PnPEntity WHERE PNPDeviceID LIKE 'USB%'";
|
||||
using (var searcher = new ManagementObjectSearcher(query))
|
||||
using (ManagementObjectCollection found = searcher.Get())
|
||||
{
|
||||
foreach (ManagementBaseObject o in found)
|
||||
{
|
||||
using (o)
|
||||
{
|
||||
string id = Convert.ToString(o["PNPDeviceID"]).ToUpperInvariant();
|
||||
Match com = ComRx.Match(Convert.ToString(o["Name"]));
|
||||
if (id.Contains("VID_1209&PID_C0CA"))
|
||||
{
|
||||
st.Present = true;
|
||||
if (id.Contains("&MI_00"))
|
||||
st.JtagDriverOk = Convert.ToUInt32(o["ConfigManagerErrorCode"]) == 0;
|
||||
else if (com.Success)
|
||||
st.UartPort = com.Groups[1].Value;
|
||||
var hwIds = o["HardwareID"] as string[];
|
||||
if (st.Version == null && hwIds != null)
|
||||
{
|
||||
foreach (string hw in hwIds)
|
||||
{
|
||||
Match m = RevRx.Match(hw.ToUpperInvariant());
|
||||
if (m.Success)
|
||||
{
|
||||
st.Version = int.Parse(m.Groups[1].Value) + "." + m.Groups[2].Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (id.Contains("VID_349B&PID_6160"))
|
||||
{
|
||||
if (com.Success)
|
||||
st.BootloaderPort = com.Groups[1].Value;
|
||||
}
|
||||
else if (id.Contains("VID_0403&PID_6010"))
|
||||
{
|
||||
st.SipeedStock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return st;
|
||||
}
|
||||
}
|
||||
|
||||
// A drive mapped to a share of this very PC (D: -> \\THISPC\Projects) sends
|
||||
// every tool launch through SMB; use the folder behind the share instead,
|
||||
// like Resolve-LocalPath in scripts\common.ps1.
|
||||
static class LocalPath
|
||||
{
|
||||
[DllImport("mpr.dll", CharSet = CharSet.Unicode)]
|
||||
static extern int WNetGetConnection(string localName, StringBuilder remoteName, ref int length);
|
||||
|
||||
public static string Resolve(string path)
|
||||
{
|
||||
string full = Path.GetFullPath(path);
|
||||
try
|
||||
{
|
||||
if (full.Length < 3 || full[1] != ':')
|
||||
return full;
|
||||
var unc = new StringBuilder(512);
|
||||
int len = unc.Capacity;
|
||||
if (WNetGetConnection(full.Substring(0, 2), unc, ref len) != 0)
|
||||
return full;
|
||||
Match m = Regex.Match(unc.ToString(), @"^\\\\([^\\]+)\\([^\\]+)\\?(.*)$");
|
||||
if (!m.Success)
|
||||
return full;
|
||||
string host = m.Groups[1].Value;
|
||||
if (!host.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase) &&
|
||||
!host.Equals("localhost", StringComparison.OrdinalIgnoreCase) && host != "127.0.0.1")
|
||||
return full;
|
||||
string query = "SELECT Path FROM Win32_Share WHERE Name='" + m.Groups[2].Value.Replace("'", "\\'") + "'";
|
||||
using (var searcher = new ManagementObjectSearcher(query))
|
||||
using (ManagementObjectCollection found = searcher.Get())
|
||||
{
|
||||
foreach (ManagementBaseObject o in found)
|
||||
{
|
||||
string share = Convert.ToString(o["Path"]);
|
||||
if (share.Length == 0)
|
||||
continue;
|
||||
string local = Path.Combine(Path.Combine(share, m.Groups[3].Value), full.Substring(3));
|
||||
if (Directory.Exists(local) || File.Exists(local))
|
||||
return local;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return full;
|
||||
}
|
||||
}
|
||||
|
||||
// Runs a console tool without a console window and streams its output
|
||||
// (stdout and stderr chunks, as they arrive) to a callback on a worker thread.
|
||||
static class ToolProcess
|
||||
{
|
||||
public static Task<int> Run(string exe, string args, string workDir,
|
||||
Action<string, bool> output, CancellationToken ct)
|
||||
{
|
||||
var psi = new ProcessStartInfo(exe, args)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.Default,
|
||||
StandardErrorEncoding = Encoding.Default,
|
||||
WorkingDirectory = workDir ?? Path.GetDirectoryName(exe),
|
||||
};
|
||||
Process p = Process.Start(psi);
|
||||
Thread outPump = Pump(p.StandardOutput, false, output);
|
||||
Thread errPump = Pump(p.StandardError, true, output);
|
||||
return Task.Run(() =>
|
||||
{
|
||||
using (p)
|
||||
using (ct.Register(() => Task.Run(() => KillTree(p))))
|
||||
{
|
||||
p.WaitForExit();
|
||||
outPump.Join();
|
||||
errPump.Join();
|
||||
return p.ExitCode;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Thread Pump(StreamReader reader, bool isErr, Action<string, bool> output)
|
||||
{
|
||||
var t = new Thread(() =>
|
||||
{
|
||||
var buf = new char[4096];
|
||||
try
|
||||
{
|
||||
int n;
|
||||
while ((n = reader.Read(buf, 0, buf.Length)) > 0)
|
||||
output(new string(buf, 0, n), isErr);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
t.IsBackground = true;
|
||||
t.Start();
|
||||
return t;
|
||||
}
|
||||
|
||||
// BLFlashCommand is a one-file PyInstaller exe: the real work happens in a child
|
||||
static void KillTree(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (p.HasExited)
|
||||
return;
|
||||
var psi = new ProcessStartInfo("taskkill.exe", "/T /F /PID " + p.Id)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
using (Process k = Process.Start(psi))
|
||||
k.WaitForExit(5000);
|
||||
if (!p.HasExited)
|
||||
p.Kill();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splits console output into lines. A lone '\r' also ends a line: that is
|
||||
// how progress bars redraw themselves.
|
||||
sealed class LineSplitter
|
||||
{
|
||||
readonly StringBuilder cur = new StringBuilder();
|
||||
|
||||
public List<string> Feed(string chunk)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
foreach (char ch in chunk)
|
||||
{
|
||||
if (ch == '\r' || ch == '\n')
|
||||
{
|
||||
if (cur.Length > 0)
|
||||
lines.Add(cur.ToString());
|
||||
cur.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
cur.Append(ch);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
public string Partial
|
||||
{
|
||||
get { return cur.ToString(); }
|
||||
}
|
||||
|
||||
public string Flush()
|
||||
{
|
||||
string rest = cur.ToString();
|
||||
cur.Clear();
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Settings
|
||||
{
|
||||
public readonly List<string> Recent = new List<string>();
|
||||
public int FreqMHz = 6;
|
||||
public bool Flash;
|
||||
public bool Verify = true;
|
||||
public bool AutoReload;
|
||||
public int Tab;
|
||||
public string Theme = "system"; // system | light | dark
|
||||
public Rectangle Bounds;
|
||||
public bool Maximized;
|
||||
|
||||
static string FilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Tang25kProgrammer", "settings.ini");
|
||||
}
|
||||
}
|
||||
|
||||
public static Settings Load()
|
||||
{
|
||||
var s = new Settings();
|
||||
try
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
return s;
|
||||
foreach (string line in File.ReadAllLines(FilePath, Encoding.UTF8))
|
||||
{
|
||||
int eq = line.IndexOf('=');
|
||||
if (eq <= 0)
|
||||
continue;
|
||||
string key = line.Substring(0, eq).Trim();
|
||||
string val = line.Substring(eq + 1).Trim();
|
||||
switch (key)
|
||||
{
|
||||
case "recent":
|
||||
if (val.Length > 0)
|
||||
s.Recent.Add(val);
|
||||
break;
|
||||
case "freq":
|
||||
int.TryParse(val, out s.FreqMHz);
|
||||
break;
|
||||
case "flash":
|
||||
s.Flash = val == "1";
|
||||
break;
|
||||
case "verify":
|
||||
s.Verify = val == "1";
|
||||
break;
|
||||
case "auto":
|
||||
s.AutoReload = val == "1";
|
||||
break;
|
||||
case "tab":
|
||||
int.TryParse(val, out s.Tab);
|
||||
break;
|
||||
case "theme":
|
||||
if (val == "light" || val == "dark" || val == "system")
|
||||
s.Theme = val;
|
||||
break;
|
||||
case "maximized":
|
||||
s.Maximized = val == "1";
|
||||
break;
|
||||
case "bounds":
|
||||
string[] p = val.Split(',');
|
||||
if (p.Length == 4)
|
||||
s.Bounds = new Rectangle(int.Parse(p[0]), int.Parse(p[1]), int.Parse(p[2]), int.Parse(p[3]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (string r in Recent)
|
||||
sb.AppendLine("recent=" + r);
|
||||
sb.AppendLine("freq=" + FreqMHz);
|
||||
sb.AppendLine("flash=" + (Flash ? 1 : 0));
|
||||
sb.AppendLine("verify=" + (Verify ? 1 : 0));
|
||||
sb.AppendLine("auto=" + (AutoReload ? 1 : 0));
|
||||
sb.AppendLine("tab=" + Tab);
|
||||
sb.AppendLine("theme=" + Theme);
|
||||
sb.AppendLine("maximized=" + (Maximized ? 1 : 0));
|
||||
if (!Bounds.IsEmpty)
|
||||
sb.AppendLine("bounds=" + Bounds.X + "," + Bounds.Y + "," + Bounds.Width + "," + Bounds.Height);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
|
||||
File.WriteAllText(FilePath, sb.ToString(), Encoding.UTF8);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Native
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct FLASHWINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public IntPtr hwnd;
|
||||
public uint dwFlags;
|
||||
public uint uCount;
|
||||
public uint dwTimeout;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool FlashWindowEx(ref FLASHWINFO fi);
|
||||
|
||||
// blink the taskbar button until the window is activated
|
||||
public static void FlashTaskbar(Form f)
|
||||
{
|
||||
var fi = new FLASHWINFO
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf(typeof(FLASHWINFO)),
|
||||
hwnd = f.Handle,
|
||||
dwFlags = 3 /* FLASHW_ALL */ | 12 /* FLASHW_TIMERNOFG */,
|
||||
};
|
||||
FlashWindowEx(ref fi);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct DCB
|
||||
{
|
||||
public int DCBlength;
|
||||
public uint BaudRate;
|
||||
public uint Flags;
|
||||
public ushort wReserved;
|
||||
public ushort XonLim;
|
||||
public ushort XoffLim;
|
||||
public byte ByteSize;
|
||||
public byte Parity;
|
||||
public byte StopBits;
|
||||
public byte XonChar;
|
||||
public byte XoffChar;
|
||||
public byte ErrorChar;
|
||||
public byte EofChar;
|
||||
public byte EvtChar;
|
||||
public ushort wReserved1;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern SafeFileHandle CreateFile(string name, uint access, uint share, IntPtr security,
|
||||
uint disposition, uint flags, IntPtr template);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool GetCommState(SafeFileHandle h, ref DCB dcb);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool SetCommState(SafeFileHandle h, ref DCB dcb);
|
||||
|
||||
// Opens the serial port, sets the baud rate and closes it again. The
|
||||
// bridge firmware reboots into the ROM bootloader on 1234 baud, so the
|
||||
// device vanishes while the port is open: System.IO.Ports does not
|
||||
// survive that, plain handles do. Returns null or the error text.
|
||||
public static string PokeBaudRate(string port, uint baud)
|
||||
{
|
||||
using (SafeFileHandle h = CreateFile(@"\\.\" + port, 0xC0000000 /* GENERIC_READ | GENERIC_WRITE */,
|
||||
0, IntPtr.Zero, 3 /* OPEN_EXISTING */, 0, IntPtr.Zero))
|
||||
{
|
||||
if (h.IsInvalid)
|
||||
return new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
||||
var dcb = new DCB();
|
||||
dcb.DCBlength = Marshal.SizeOf(typeof(DCB));
|
||||
if (!GetCommState(h, ref dcb))
|
||||
return new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
||||
dcb.BaudRate = baud;
|
||||
if (dcb.ByteSize == 0)
|
||||
dcb.ByteSize = 8;
|
||||
// may report an error: the device can be gone before the call returns
|
||||
SetCommState(h, ref dcb);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="TangProgrammer.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 / 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
|
||||
processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -0,0 +1,35 @@
|
||||
Tang Primer 25K — программатор (переносная версия)
|
||||
====================================================
|
||||
|
||||
Запуск: TangProgrammer.exe, двойной щелчок. Устанавливать ничего не нужно.
|
||||
|
||||
Требования
|
||||
----------
|
||||
* Windows 10 или 11, 64-бит (.NET Framework 4.8 в них уже есть).
|
||||
* Папку целиком держите в любом месте, куда можно писать (рабочий стол,
|
||||
D:\...), но не в Program Files: программа прошивки моста создаёт файлы
|
||||
рядом с собой.
|
||||
* Gowin IDE на этом компьютере нужна только для сборки своих .fs.
|
||||
|
||||
Драйверы
|
||||
--------
|
||||
Ставить ничего не нужно. При первом подключении платы Windows сама установит
|
||||
WinUSB для JTAG («DirtyJTAG») и драйвер COM-порта UART — подождите 10–30 секунд.
|
||||
Прошивка моста хранится в самой плате (в BL616), переносить её не нужно.
|
||||
|
||||
Если архив скачан из интернета или пришёл почтой
|
||||
-----------------------------------------------
|
||||
До распаковки: правой кнопкой по zip → «Свойства» → отметить «Разблокировать»
|
||||
→ OK. Иначе Windows может показать «Система Windows защитила ваш компьютер»
|
||||
(программа не подписана) — тогда «Подробнее» → «Выполнить в любом случае».
|
||||
|
||||
Что внутри
|
||||
----------
|
||||
TangProgrammer.exe сама программа
|
||||
tools\openFPGALoader\ openFPGALoader 1.1.1: загрузка FPGA, флеш-память FPGA
|
||||
tools\bouffalo_flash_cube\ BLFlashCommand — только для вкладки «Прошивка моста»
|
||||
firmware\, *.ini прошивка моста, загрузчик и штатная прошивка Sipeed
|
||||
(тоже только для вкладки «Прошивка моста»)
|
||||
|
||||
Настройки программы (тема, последние файлы) хранятся отдельно для каждого
|
||||
пользователя в %LOCALAPPDATA%\Tang25kProgrammer.
|
||||
Reference in New Issue
Block a user