fix: 修改部分外设BASE偏移量;增加WS2812后端监控器;DSO寄

This commit is contained in:
alivender 2025-08-20 01:37:27 +08:00
parent ca0322137b
commit c8444d1d4e
4 changed files with 370 additions and 36 deletions

View File

@ -0,0 +1,138 @@
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Cors;
using TypedSignalR.Client;
using Tapper;
using DotNext;
using Peripherals.WS2812Client;
using System.Collections.Concurrent;
#pragma warning disable 1998
namespace server.Hubs;
[Hub]
public interface IWS2812Hub
{
Task<RGBColor[]?> GetAllLedColors();
Task<RGBColor?> GetLedColor(int ledIndex);
}
[Receiver]
public interface IWS2812Receiver
{
Task OnReceive(RGBColor[] data);
}
[TranspilationSource]
public class WS2812TaskStatus
{
public bool IsRunning { get; set; } = false;
}
class WS2812ScanTaskInfo
{
public string BoardID { get; set; }
public string ClientID { get; set; }
public Task? ScanTask { get; set; }
public WS2812Client LedClient { get; set; }
public CancellationTokenSource CTS { get; set; } = new();
public bool IsRunning { get; set; } = false;
public WS2812ScanTaskInfo(string boardID, string clientID, WS2812Client client)
{
BoardID = boardID;
ClientID = clientID;
LedClient = client;
}
public WS2812TaskStatus ToWS2812TaskStatus()
{
return new WS2812TaskStatus
{
IsRunning = IsRunning
};
}
}
[Authorize]
[EnableCors("SignalR")]
public class WS2812Hub : Hub<IWS2812Receiver>, IWS2812Hub
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly IHubContext<WS2812Hub, IWS2812Receiver> _hubContext;
private readonly Database.UserManager _userManager = new();
private ConcurrentDictionary<(string, string), WS2812ScanTaskInfo> _scanTasks = new();
public WS2812Hub(IHubContext<WS2812Hub, IWS2812Receiver> hubContext)
{
_hubContext = hubContext;
}
private Optional<Database.Board> TryGetBoard()
{
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
if (string.IsNullOrEmpty(userName))
{
logger.Error("User name is null or empty");
return null;
}
var userRet = _userManager.GetUserByName(userName);
if (!userRet.IsSuccessful || !userRet.Value.HasValue)
{
logger.Error($"User '{userName}' not found");
return null;
}
var user = userRet.Value.Value;
var boardRet = _userManager.GetBoardByID(user.BoardID);
if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
{
logger.Error($"Board not found");
return null;
}
return boardRet.Value.Value;
}
public async Task<RGBColor[]?> GetAllLedColors()
{
try
{
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
var client = new WS2812Client(board.IpAddr, board.Port, 0);
var result = await client.GetAllLedColors();
if (!result.IsSuccessful)
{
logger.Error($"GetAllLedColors failed: {result.Error}");
return null;
}
return result.Value;
}
catch (Exception ex)
{
logger.Error(ex, "Failed to get all LED colors");
return null;
}
}
public async Task<RGBColor?> GetLedColor(int ledIndex)
{
try
{
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
var client = new WS2812Client(board.IpAddr, board.Port, 0);
var result = await client.GetLedColor(ledIndex);
if (!result.IsSuccessful)
{
logger.Error($"GetLedColor failed: {result.Error}");
return null;
}
return result.Value;
}
catch (Exception ex)
{
logger.Error(ex, "Failed to get LED color");
return null;
}
}
}

View File

@ -10,11 +10,12 @@ static class HdmiInAddr
public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0; public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
public const UInt32 START_WR_ADDR0 = BASE + 0x2; public const UInt32 START_WR_ADDR0 = BASE + 0x20;
public const UInt32 END_WR_ADDR0 = BASE + 0x3; public const UInt32 END_WR_ADDR0 = BASE + 0x21;
public const UInt32 HDMI_NOT_READY = BASE + 0x8; public const UInt32 HDMI_NOT_READY = BASE + 0x26;
public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9; public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x27;
public const UInt32 CAPTURE_HEIGHT_WIDTH = BASE + 0x28;
public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000; public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000;
} }
@ -31,7 +32,7 @@ public class HdmiIn
public int Width { get; private set; } public int Width { get; private set; }
public int Height { get; private set; } public int Height { get; private set; }
public int FrameLength => Width * Height * 3 / 4; public int FrameLength => Width * Height / 2;
/// <summary> /// <summary>
/// 初始化HDMI输入客户端 /// 初始化HDMI输入客户端
@ -129,7 +130,7 @@ public class HdmiIn
this.taskID, // taskID this.taskID, // taskID
HdmiInAddr.ADDR_HDMI_WD_START, HdmiInAddr.ADDR_HDMI_WD_START,
FrameLength, // 使用当前分辨率的动态大小 FrameLength, // 使用当前分辨率的动态大小
BurstType.FixedBurst, BurstType.ExtendBurst,
this.timeout); this.timeout);
if (!result.IsSuccessful) if (!result.IsSuccessful)
@ -165,25 +166,22 @@ public class HdmiIn
return Optional<(byte[] header, byte[] data, byte[] footer)>.None; return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
} }
var rgb24Data = frameResult.Value; var rgb565Data = frameResult.Value;
// 验证数据长度是否正确 (RGB24为每像素2字节) // 验证数据长度是否正确 (RGB24为每像素2字节)
var expectedLength = Width * Height * 2; var expectedLength = Width * Height * 2;
if (rgb24Data.Length != expectedLength) if (rgb565Data.Length != expectedLength)
{ {
logger.Warn("HDMI数据长度不匹配期望: {Expected}, 实际: {Actual}", logger.Warn("HDMI数据长度不匹配期望: {Expected}, 实际: {Actual}",
expectedLength, rgb24Data.Length); expectedLength, rgb565Data.Length);
} }
// 将RGB24转换为JPEG参考Camera版本的处理 // 将RGB24转换为JPEG参考Camera版本的处理
var jpegStartTime = DateTime.UtcNow; var jpegResult = Common.Image.ConvertRGB565ToJpeg(rgb565Data, Width, Height, 80, false);
var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, Width, Height, 80);
var jpegEndTime = DateTime.UtcNow;
var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds;
if (!jpegResult.IsSuccessful) if (!jpegResult.IsSuccessful)
{ {
logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error); logger.Error("HDMI RGB565转JPEG失败: {Error}", jpegResult.Error);
return Optional<(byte[] header, byte[] data, byte[] footer)>.None; return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
} }
@ -224,8 +222,8 @@ public class HdmiIn
return new(new Exception("Invalid HDMI resolution data length")); return new(new Exception("Invalid HDMI resolution data length"));
} }
var width = data[3] | (data[2] << 8); var width = (data[3] | (data[2] << 8)) - 1 - (((data[3] | (data[2] << 8)) - 1)%2);
var height = data[1] | (data[0] << 8); var height = (data[1] | (data[0] << 8)) - 1 - (((data[1] | (data[0] << 8)) - 1)%2);
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
@ -234,6 +232,7 @@ public class HdmiIn
return new((width, height)); return new((width, height));
} }
public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height) public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height)
{ {
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0)
@ -242,7 +241,22 @@ public class HdmiIn
return new(new ArgumentException("Invalid HDMI resolution")); return new(new ArgumentException("Invalid HDMI resolution"));
} }
var frameSize = (UInt32)(width * height); var frameSize = (UInt32)(width * height) / 2;
{
var ret = await UDPClientPool.WriteAddr(
this.ep, this.taskID, HdmiInAddr.CAPTURE_HEIGHT_WIDTH, (uint)((height << 16) + width), this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set CAPTURE_HEIGHT_WIDTH: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"Failed to set HDMI output start address");
return false;
}
}
{ {
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(

View File

@ -9,7 +9,7 @@ namespace Peripherals.LogicAnalyzerClient;
static class AnalyzerAddr static class AnalyzerAddr
{ {
const UInt32 BASE = 0x9000_0000; const UInt32 BASE = 0x9000_0000;
const UInt32 DMA1_BASE = 0x7000_0000; const UInt32 DMA_BASE = 0xA000_0000;
const UInt32 DDR_BASE = 0x0000_0000; const UInt32 DDR_BASE = 0x0000_0000;
/// <summary> /// <summary>
@ -68,9 +68,9 @@ static class AnalyzerAddr
public const UInt32 PRE_LOAD_NUM_ADDR = BASE + 0x0000_0003; public const UInt32 PRE_LOAD_NUM_ADDR = BASE + 0x0000_0003;
public const UInt32 CAHNNEL_DIV_ADDR = BASE + 0x0000_0004; public const UInt32 CAHNNEL_DIV_ADDR = BASE + 0x0000_0004;
public const UInt32 CLOCK_DIV_ADDR = BASE + 0x0000_0005; public const UInt32 CLOCK_DIV_ADDR = BASE + 0x0000_0005;
public const UInt32 DMA1_START_WRITE_ADDR = DMA1_BASE + 0x0000_0012; public const UInt32 DMA_CAPTURE_RD_CTRL1 = DMA_BASE + 0x1;
public const UInt32 DMA1_END_WRITE_ADDR = DMA1_BASE + 0x0000_0013; public const UInt32 DMA_START_WRITE_ADDR1 = DMA_BASE + 0x22;
public const UInt32 DMA1_CAPTURE_CTRL_ADDR = DMA1_BASE + 0x0000_0014; public const UInt32 DMA_END_WRITE_ADDR1 = DMA_BASE + 0x23;
public const UInt32 STORE_OFFSET_ADDR = DDR_BASE + 0x0100_0000; public const UInt32 STORE_OFFSET_ADDR = DDR_BASE + 0x0100_0000;
/// <summary> /// <summary>
@ -327,20 +327,34 @@ public class Analyzer
/// <returns>操作结果成功返回true否则返回异常信息</returns> /// <returns>操作结果成功返回true否则返回异常信息</returns>
public async ValueTask<Result<bool>> SetCaptureMode(bool captureOn, bool force) public async ValueTask<Result<bool>> SetCaptureMode(bool captureOn, bool force)
{ {
// 构造寄存器值
UInt32 value = 0;
if (captureOn) value |= 1 << 0;
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_CAPTURE_CTRL_ADDR, value, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_CAPTURE_RD_CTRL1, 0x00000000u, this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
{ {
logger.Error($"Failed to set DMA1_CAPTURE_CTRL_ADDR: {ret.Error}"); logger.Error($"Failed to set DMA_CAPTURE_RD_CTRL to 0: {ret.Error}");
return new(ret.Error); return new(ret.Error);
} }
if (!ret.Value) if (!ret.Value)
{ {
logger.Error("WriteAddr to DMA1_CAPTURE_CTRL_ADDR returned false"); logger.Error("WriteAddr to DMA_CAPTURE_RD_CTRL returned false");
return new(new Exception("Failed to set DMA1_CAPTURE_CTRL_ADDR")); return new(new Exception("Failed to set DMA_CAPTURE_RD_CTRL"));
}
}
await Task.Delay(5);
// 构造寄存器值
UInt32 value = 0;
if (captureOn) value |= 1 << 0;
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_CAPTURE_RD_CTRL1, value, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to set DMA_CAPTURE_RD_CTRL: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("WriteAddr to DMA_CAPTURE_RD_CTRL returned false");
return new(new Exception("Failed to set DMA_CAPTURE_RD_CTRL"));
} }
} }
if (force) value |= 1 << 8; if (force) value |= 1 << 8;
@ -472,29 +486,29 @@ public class Analyzer
} }
} }
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_START_WRITE_ADDR1, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
{ {
logger.Error($"Failed to set DMA1_START_WRITE_ADDR: {ret.Error}"); logger.Error($"Failed to set DMA_START_WRITE_ADDR: {ret.Error}");
return new(ret.Error); return new(ret.Error);
} }
if (!ret.Value) if (!ret.Value)
{ {
logger.Error("WriteAddr to DMA1_START_WRITE_ADDR returned false"); logger.Error("WriteAddr to DMA_START_WRITE_ADDR returned false");
return new(new Exception("Failed to set DMA1_START_WRITE_ADDR")); return new(new Exception("Failed to set DMA_START_WRITE_ADDR"));
} }
} }
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_END_WRITE_ADDR1, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
{ {
logger.Error($"Failed to set DMA1_END_WRITE_ADDR: {ret.Error}"); logger.Error($"Failed to set DMA_END_WRITE_ADDR: {ret.Error}");
return new(ret.Error); return new(ret.Error);
} }
if (!ret.Value) if (!ret.Value)
{ {
logger.Error("WriteAddr to DMA1_END_WRITE_ADDR returned false"); logger.Error("WriteAddr to DMA_END_WRITE_ADDR returned false");
return new(new Exception("Failed to set DMA1_END_WRITE_ADDR")); return new(new Exception("Failed to set DMA_END_WRITE_ADDR"));
} }
} }
{ {

View File

@ -0,0 +1,168 @@
using System.Net;
using DotNext;
namespace Peripherals.WS2812Client;
class WS2812Addr
{
public const UInt32 BASE = 0xB0_00_01_00;
public const int LED_COUNT = 128;
}
/// <summary>
/// RGB颜色结构体包含红、绿、蓝三个颜色分量
/// </summary>
public struct RGBColor
{
public byte Red { get; set; }
public byte Green { get; set; }
public byte Blue { get; set; }
public RGBColor(byte red, byte green, byte blue)
{
Red = red;
Green = green;
Blue = blue;
}
/// <summary>
/// 从32位数据的低24位提取RGB颜色
/// </summary>
/// <param name="data">32位数据</param>
/// <returns>RGB颜色</returns>
public static RGBColor FromUInt32(UInt32 data)
{
return new RGBColor(
(byte)((data >> 16) & 0xFF), // Red
(byte)((data >> 8) & 0xFF), // Green
(byte)(data & 0xFF) // Blue
);
}
/// <summary>
/// 转换为32位数据格式
/// </summary>
/// <returns>32位数据</returns>
public UInt32 ToUInt32()
{
return ((UInt32)Red << 16) | ((UInt32)Green << 8) | (UInt32)Blue;
}
public override string ToString()
{
return $"RGB({Red}, {Green}, {Blue})";
}
}
public class WS2812Client
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout = 500;
readonly int taskID;
readonly int port;
readonly string address;
private IPEndPoint ep;
public WS2812Client(string address, int port, int taskID, int timeout = 500)
{
if (timeout < 0)
throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
this.address = address;
this.port = port;
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
this.taskID = taskID;
this.timeout = timeout;
}
/// <summary>
/// 获取指定灯珠的RGB颜色
/// </summary>
/// <param name="ledIndex">灯珠索引范围0-127</param>
/// <returns>RGB颜色结果</returns>
public async ValueTask<Result<RGBColor>> GetLedColor(int ledIndex)
{
if (ledIndex < 0 || ledIndex >= WS2812Addr.LED_COUNT)
{
return new(new ArgumentOutOfRangeException(nameof(ledIndex),
$"LED index must be between 0 and {WS2812Addr.LED_COUNT - 1}"));
}
if (MsgBus.IsRunning)
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
else
return new(new Exception("Message Bus not work!"));
var addr = WS2812Addr.BASE + (UInt32)(ledIndex * 4); // 每个地址32位步长为4字节
var ret = await UDPClientPool.ReadAddrByte(this.ep, this.taskID, addr, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Get LED {ledIndex} color failed: {ret.Error}");
return new(ret.Error);
}
var retData = ret.Value.Options.Data;
if (retData is null)
return new(new Exception($"Device {address} receive none"));
if (retData.Length < 4)
{
var error = new Exception($"Invalid data length: expected 4 bytes, got {retData.Length}");
logger.Error($"Get LED {ledIndex} color failed: {error}");
return new(error);
}
var colorData = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value);
var color = RGBColor.FromUInt32(colorData);
return new(color);
}
/// <summary>
/// 获取所有灯珠的RGB颜色
/// </summary>
/// <returns>包含所有灯珠颜色的数组</returns>
public async ValueTask<Result<RGBColor[]>> GetAllLedColors()
{
if (MsgBus.IsRunning)
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
else
return new(new Exception("Message Bus not work!"));
try
{
// 一次性读取所有LED数据每个LED占用4字节总共128*4=512字节
var ret = await UDPClientPool.ReadAddr4Bytes(this.ep, this.taskID, WS2812Addr.BASE, WS2812Addr.LED_COUNT, this.timeout);
if (!ret.IsSuccessful)
{
logger.Error($"Get all LED colors failed: {ret.Error}");
return new(ret.Error);
}
var data = ret.Value;
var expectedLength = WS2812Addr.LED_COUNT * 4; // 128 * 4 = 512 bytes
if (data.Length < expectedLength)
{
var error = new Exception($"Invalid data length: expected {expectedLength} bytes, got {data.Length}");
logger.Error(error.Message);
return new(error);
}
var colors = new RGBColor[WS2812Addr.LED_COUNT];
for (int i = 0; i < WS2812Addr.LED_COUNT; i++)
{
var offset = i * 4;
// 将4字节数据转换为UInt32
var colorData = BitConverter.ToUInt32(data, offset);
colors[i] = RGBColor.FromUInt32(colorData);
}
return new(colors);
}
catch (Exception ex)
{
logger.Error($"Get all LED colors failed: {ex}");
return new(ex);
}
}
}