FPGA_WebLab/server/src/Peripherals/CameraClient.cs

1427 lines
68 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Net;
using DotNext;
using Peripherals.PowerClient;
namespace Peripherals.CameraClient;
static class CameraAddr
{
public const UInt32 BASE = 0x7000_0000;
public const UInt32 DMA0_START_WRITE_ADDR = BASE + 0x0C;
public const UInt32 DMA0_END_WRITE_ADDR = BASE + 0x0D;
public const UInt32 DMA0_CAPTURE_CTRL = BASE + 0x0E; //[0]: on, 1 is on. [8]: reset, 1 is reset.
public const UInt32 EXPECTED_VH = BASE + 0x0F;
public const UInt32 CAMERA_POWER = BASE + 0x10; //[0]: rstn, 0 is reset. [8]: power down, 1 is down.
}
class Camera
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout = 2000;
readonly int taskID;
readonly int port;
readonly string address;
private IPEndPoint ep;
const uint CAM_I2C_ADDR = 0x3C;
const Peripherals.I2cClient.I2cProtocol CAM_PROTO = Peripherals.I2cClient.I2cProtocol.SCCB;
const byte PLL_MUX = 105;
const UInt32 FrameAddr = 0x00;
// 动态分辨率参数
private UInt16 _currentWidth = 640;
private UInt16 _currentHeight = 480;
private UInt32 _currentFrameLength = 640 * 480 * 2 / 4; // RGB565格式2字节/像素按4字节对齐
/// <summary>
/// 初始化摄像头客户端
/// </summary>
/// <param name="address">摄像头设备IP地址</param>
/// <param name="port">摄像头设备端口</param>
/// <param name="timeout">超时时间(毫秒)</param>
public Camera(string address, int port, int timeout = 2000)
{
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.timeout = timeout;
}
public async ValueTask<Result<bool>> Init()
{
await PowerHardwareCamera(true);
await SleepHardwareCamera(false);
// 步骤1: 复位
var resetResult = await Reset();
if (!resetResult.IsSuccessful) return resetResult;
var wakeupResult = await Sleep();
if (!wakeupResult.IsSuccessful) return wakeupResult;
// 步骤3: 配置基础寄存器
var basicResult = await ConfigureBasicRegisters();
if (!basicResult.IsSuccessful) return basicResult;
// 步骤4: 配置传感器控制
var sensorResult = await ConfigureSensorControl();
if (!sensorResult.IsSuccessful) return sensorResult;
// 步骤5: 配置模拟控制
var analogResult = await ConfigureAnalogControl();
if (!analogResult.IsSuccessful) return analogResult;
// 步骤6: 配置时钟控制
var clockResult = await ConfigureClockControl();
if (!clockResult.IsSuccessful) return clockResult;
// 步骤7: 配置PSRAM控制
var psramResult = await ConfigurePSRAMControl();
if (!psramResult.IsSuccessful) return psramResult;
// 步骤8: 配置DVP时序
var dvpResult = await ConfigureDVPTiming();
if (!dvpResult.IsSuccessful) return dvpResult;
// 步骤9: 配置基础控制
var baseResult = await ConfigureBaseControl();
if (!baseResult.IsSuccessful) return baseResult;
// 步骤10: 配置图像格式
var formatResult = await ConfigureImageFormat();
if (!formatResult.IsSuccessful) return formatResult;
// 步骤11: 配置ISP控制
var ispResult = await ConfigureISPControl();
if (!ispResult.IsSuccessful) return ispResult;
// 步骤12: 配置AEC
var aecResult = await ConfigureAEC();
if (!aecResult.IsSuccessful) return aecResult;
// 步骤13: 配置LENC
var lencResult = await ConfigureLENC();
if (!lencResult.IsSuccessful) return lencResult;
// 步骤14: 配置AWB
var awbResult = await ConfigureAWB();
if (!awbResult.IsSuccessful) return awbResult;
// 步骤15: 配置Gamma
var gammaResult = await ConfigureGamma();
if (!gammaResult.IsSuccessful) return gammaResult;
// 步骤16: 配置CMX
var cmxResult = await ConfigureCMX();
if (!cmxResult.IsSuccessful) return cmxResult;
// 步骤17: 配置SDE
var sdeResult = await ConfigureSDE();
if (!sdeResult.IsSuccessful) return sdeResult;
// 步骤18: 配置CIP
var cipResult = await ConfigureCIP();
if (!cipResult.IsSuccessful) return cipResult;
// 步骤19: 配置时序控制
var timingResult = await ConfigureTimingControl();
if (!timingResult.IsSuccessful) return timingResult;
// 步骤20: 配置测试和闪光灯
var testResult = await ConfigureTestAndFlash();
if (!testResult.IsSuccessful) return testResult;
// 步骤21: 配置分辨率默认640x480
var resolutionResult = await ConfigureResolution640x480();
// var resolutionResult = await ConfigureResolution1280x720();
if (!resolutionResult.IsSuccessful) return resolutionResult;
// var autofocusResult = await InitAutoFocus();
// if (!autofocusResult.IsSuccessful) return autofocusResult;
var startResult = await WakeUp();
if (!startResult.IsSuccessful) return startResult;
// var sleepResult = await Sleep();
// if (!sleepResult.IsSuccessful) return sleepResult;
// var resetResult2 = await Reset();
// if (!resetResult2.IsSuccessful) return resetResult2;
return true;
}
public async ValueTask<Result<bool>> EnableHardwareTrans(bool isEnable)
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_CAPTURE_CTRL, (isEnable ? 0x00000001u : 0x00000100u));
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write CAPTURE_ON to camera at {this.address}:{this.port}, error: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"CAPTURE_ON write returned false for camera at {this.address}:{this.port}");
return false;
}
return true;
}
public async ValueTask<Result<bool>> PowerHardwareCamera(bool isEnable)
{
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAMERA_POWER, (isEnable ? 0x00000001u : 0x00000000u));
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_ADDR: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("STORE_ADDR write returned false");
return new(new Exception("STORE_ADDR write returned false"));
}
}
await Task.Delay(5); // 确保硬件状态稳定
return true;
}
public async ValueTask<Result<bool>> SleepHardwareCamera(bool isEnable)
{
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAMERA_POWER, (isEnable ? 0x00000101u : 0x00000001u));
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_ADDR: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("STORE_ADDR write returned false");
return new(new Exception("STORE_ADDR write returned false"));
}
}
await Task.Delay(5);
return true;
}
/// <summary>
/// 读取一帧图像数据
/// </summary>
/// <returns>包含图像数据的字节数组</returns>
public async ValueTask<Result<byte[]>> ReadFrame()
{
// 只在第一次或出错时清除UDP缓冲区避免每帧都清除造成延迟
// MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
logger.Trace($"Reading frame from camera {this.address}");
// 使用UDPClientPool读取图像帧数据
var result = await UDPClientPool.ReadAddr4BytesAsync(
this.ep,
this.taskID, // taskID
FrameAddr,
(int)_currentFrameLength, // 使用当前分辨率的动态大小
this.timeout);
if (!result.IsSuccessful)
{
logger.Error($"Failed to read frame from camera {this.address}:{this.port}, error: {result.Error}");
// 读取失败时清除缓冲区,为下次读取做准备
try
{
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
}
catch (Exception ex)
{
logger.Warn($"Failed to clear UDP data after read error: {ex.Message}");
}
return new(result.Error);
}
logger.Trace($"Successfully read frame from camera {this.address}:{this.port}, data length: {result.Value.Length} bytes");
return result.Value;
}
/// <summary>
/// 批量配置I2C寄存器
/// </summary>
/// <param name="registerTable">寄存器配置表,每个元素格式为:[16位寄存器地址, 数据1, 数据2, ...],数据按地址递增写入</param>
/// <param name="customDelayMs">自定义延时时间毫秒如果为null则使用默认延时逻辑</param>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureRegisters(UInt16[][] registerTable, int? customDelayMs = null)
{
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout);
foreach (var cmd in registerTable)
{
if (cmd.Length < 2)
{
logger.Error($"Invalid register command length: {cmd.Length}, expected at least 2 elements (address + data)");
return new(new ArgumentException($"Invalid register command length: {cmd.Length}, expected at least 2 elements"));
}
var baseAddress = cmd[0];
logger.Debug($"ConfigureRegisters: 配置寄存器组,基地址=0x{baseAddress:X4}, 数据数量={cmd.Length - 1}");
// 为每个数据字节单独写入到连续地址
for (int i = 1; i < cmd.Length; i++)
{
var currentAddress = (UInt16)(baseAddress + i - 1);
var data = (byte)cmd[i];
logger.Debug($"ConfigureRegisters: 写入地址=0x{currentAddress:X4}, 数据=0x{data:X2}");
// 准备I2C数据16位地址 + 8位数据
var i2cData = new byte[3];
i2cData[0] = (byte)(currentAddress >> 8); // 地址高位
i2cData[1] = (byte)(currentAddress & 0xFF); // 地址低位
i2cData[2] = data; // 数据
var ret = await i2c.WriteData(CAM_I2C_ADDR, i2cData, CAM_PROTO);
if (!ret.IsSuccessful)
{
logger.Error($"I2C write 0x{CAM_I2C_ADDR:X} failed: Address=0x{currentAddress:X4}, Data=0x{data:X2}, error: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error($"I2C write 0x{CAM_I2C_ADDR:X} returned false: Address=0x{currentAddress:X4}, Data=0x{data:X2}");
return false;
}
// 处理延时逻辑
if (customDelayMs.HasValue)
{
await Task.Delay(customDelayMs.Value);
}
else
{
await Task.Delay(1); // 1ms延时
}
}
}
return true;
}
/// <summary>
/// 读取I2C寄存器字节值
/// </summary>
/// <param name="register">要读取的寄存器地址 (16位)</param>
/// <returns>ret</returns>
public async ValueTask<Result<byte>> ReadRegister(UInt16 register)
{
var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout);
// Convert 16-bit register address to byte array
var registerBytes = new byte[] { (byte)(register >> 8), (byte)(register & 0xFF) };
var ret = await i2c.ReadData(CAM_I2C_ADDR, registerBytes, 1, CAM_PROTO);
if (!ret.IsSuccessful)
return new(ret.Error);
return new Result<byte>(ret.Value[0]);
}
/// <summary>
/// 配置摄像头分辨率和相关参数
/// </summary>
/// <param name="hStart">水平起始位置</param>
/// <param name="vStart">垂直起始位置</param>
/// <param name="dvpHo">输出水平像素数</param>
/// <param name="dvpVo">输出垂直像素数</param>
/// <param name="hts">水平总像素数</param>
/// <param name="vts">垂直总像素数</param>
/// <param name="hOffset">水平偏移</param>
/// <param name="vOffset">垂直偏移</param>
/// <param name="hWindow">水平窗口大小默认1500</param>
/// <param name="vWindow">垂直窗口大小默认1300</param>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution(
UInt16 hStart, UInt16 vStart,
UInt16 dvpHo, UInt16 dvpVo,
UInt16 hts, UInt16 vts,
UInt16 hOffset, UInt16 vOffset,
UInt16 hWindow = 1500, UInt16 vWindow = 1300)
{
// 计算结束位置
UInt16 hEnd = (UInt16)(hStart + hWindow - 1);
UInt16 vEnd = (UInt16)(vStart + vWindow - 1);
// 计算帧长度
UInt32 frameLength = (UInt32)(dvpHo * dvpVo * 16 / 32);
// 1. 配置UDP相关寄存器
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_START_WRITE_ADDR, FrameAddr);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_ADDR: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("STORE_ADDR write returned false");
return new(new Exception("STORE_ADDR write returned false"));
}
}
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_END_WRITE_ADDR, FrameAddr + frameLength - 1);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_NUM: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("STORE_NUM write returned false");
return new(new Exception("STORE_NUM write returned false"));
}
}
{
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.EXPECTED_VH, ((uint)dvpVo) << 16 | ((uint)dvpHo * 2));
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write EXPECTED_VH: {ret.Error}");
return new(ret.Error);
}
if (!ret.Value)
{
logger.Error("EXPECTED_VH write returned false");
return new(new Exception("EXPECTED_VH write returned false"));
}
}
// 2. 配置I2C寄存器
var resolutionRegisters = new UInt16[][]
{
// H_START/V_START
[0x3800, unchecked((byte)((hStart >> 8) & 0xFF))],
[0x3801, unchecked((byte)(hStart & 0xFF))],
[0x3802, unchecked((byte)((vStart >> 8) & 0xFF))],
[0x3803, unchecked((byte)(vStart & 0xFF))],
// H_END/V_END
[0x3804, unchecked((byte)((hEnd >> 8) & 0xFF))],
[0x3805, unchecked((byte)(hEnd & 0xFF))],
[0x3806, unchecked((byte)((vEnd >> 8) & 0xFF))],
[0x3807, unchecked((byte)(vEnd & 0xFF))],
// 输出像素个数
[0x3808, unchecked((byte)((dvpHo >> 8) & 0xFF))],
[0x3809, unchecked((byte)(dvpHo & 0xFF))],
[0x380A, unchecked((byte)((dvpVo >> 8) & 0xFF))],
[0x380B, unchecked((byte)(dvpVo & 0xFF))],
// 总像素
[0x380C, unchecked((byte)((hts >> 8) & 0xFF))],
[0x380D, unchecked((byte)(hts & 0xFF))],
[0x380E, unchecked((byte)((vts >> 8) & 0xFF))],
[0x380F, unchecked((byte)(vts & 0xFF))],
// H_OFFSET/V_OFFSET
[0x3810, unchecked((byte)((hOffset >> 8) & 0xFF))],
[0x3811, unchecked((byte)(hOffset & 0xFF))],
[0x3812, unchecked((byte)((vOffset >> 8) & 0xFF))],
// Timing Voffset
[0x3813, unchecked((byte)(vOffset & 0xFF))]
};
var configResult = await ConfigureRegisters(resolutionRegisters, customDelayMs: 1);
if (!configResult.IsSuccessful)
{
logger.Error($"Failed to configure resolution registers: {configResult.Error}");
return configResult;
}
logger.Info($"Successfully configured resolution: {dvpHo}x{dvpVo}, HTS={hts}, VTS={vts}");
return true;
}
/// <summary>
/// 配置为640x480分辨率默认配置
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution640x480()
{
return await ConfigureResolution(
hStart: 0, vStart: 0,
dvpHo: 640, dvpVo: 480,
hts: 1700, vts: 1500,
hOffset: 16, vOffset: 4
);
}
/// <summary>
/// 配置为320x240分辨率
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution320x240()
{
return await ConfigureResolution(
hStart: 0, vStart: 0,
dvpHo: 320, dvpVo: 240,
hts: 850, vts: 750,
hOffset: 16, vOffset: 4,
hWindow: 750, vWindow: 650
);
}
/// <summary>
/// 配置为1280x720分辨率
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution1280x720()
{
return await ConfigureResolution(
hStart: 0, vStart: 250,
dvpHo: 1280, dvpVo: 720,
hts: 2844, vts: 1968,
hOffset: 16, vOffset: 4,
hWindow: 2624, vWindow: 1456
);
}
/// <summary>
/// 配置为1280x720分辨率
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution1280x960()
{
return await ConfigureResolution(
hStart: 0, vStart: 250,
dvpHo: 1280, dvpVo: 960,
hts: 2844, vts: 1968,
hOffset: 16, vOffset: 4,
hWindow: 2624, vWindow: 1456
);
}
/// <summary>
/// 配置为1920x1080分辨率
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureResolution1920x1080()
{
return await ConfigureResolution(
hStart: 0, vStart: 250,
dvpHo: 1920, dvpVo: 1080,
hts: 2844, vts: 1968,
hOffset: 16, vOffset: 4,
hWindow: 2624, vWindow: 1456
);
}
/// <summary>
/// 切换摄像头分辨率
/// </summary>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ChangeResolution(int width, int height)
{
try
{
// await WakeUp();
logger.Info($"正在切换摄像头分辨率到 {width}x{height}");
Result<bool> result;
switch ($"{width}x{height}")
{
case "640x480":
result = await ConfigureResolution640x480();
break;
case "1280x720":
result = await ConfigureResolution1280x720();
break;
case "1280x960":
result = await ConfigureResolution1280x960();
break;
case "1920x1080":
result = await ConfigureResolution1920x1080();
break;
default:
logger.Error($"不支持的分辨率: {width}x{height}");
return new(new ArgumentException($"不支持的分辨率: {width}x{height}"));
}
if (result.IsSuccessful)
{
_currentWidth = (UInt16)width;
_currentHeight = (UInt16)height;
_currentFrameLength = (UInt32)(width * height * 2 / 4); // RGB565格式按4字节对齐
logger.Info($"摄像头分辨率已切换到 {width}x{height}");
}
// await Sleep();
return result;
}
catch (Exception ex)
{
logger.Error(ex, $"切换分辨率到 {width}x{height} 时发生错误");
return new(ex);
}
}
/// <summary>
/// 获取当前分辨率
/// </summary>
/// <returns>当前分辨率(宽度, 高度)</returns>
public (int Width, int Height) GetCurrentResolution()
{
return (_currentWidth, _currentHeight);
}
/// <summary>
/// 获取当前帧长度
/// </summary>
/// <returns>当前帧长度</returns>
public UInt32 GetCurrentFrameLength()
{
return _currentFrameLength;
}
/// <summary>
/// 复位摄像头
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> Reset()
{
var resetRegisters = new UInt16[][]
{
[0x3103, 0x11],// system clock from pad, bit[1]
[0x3008, 0x82] // 复位命令
};
return await ConfigureRegisters(resetRegisters, customDelayMs: 50); // 复位后等待5ms
}
/// <summary>
/// 设置摄像头为休眠模式
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> Sleep()
{
var sleepRegisters = new UInt16[][]
{
[0x3008, 0x42] // 休眠命令
};
return await ConfigureRegisters(sleepRegisters, customDelayMs: 50);
}
/// <summary>
/// 配置基础寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureBasicRegisters()
{
var basicRegisters = new UInt16[][]
{
[0x3103, 0x03], // system clock from pad, bit[1] //02
[0x3017, 0xff],
[0x3018, 0xff],
[0x3037, 0x13],
[0x3108, 0x01]
};
return await ConfigureRegisters(basicRegisters);
}
/// <summary>
/// 配置传感器控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureSensorControl()
{
var sensorRegisters = new UInt16[][]
{
[0x3630, 0x36],
[0x3631, 0x0e],
[0x3632, 0xe2],
[0x3633, 0x12],
[0x3621, 0xe0]
};
return await ConfigureRegisters(sensorRegisters);
}
/// <summary>
/// 配置模拟控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureAnalogControl()
{
var analogRegisters = new UInt16[][]
{
[0x3704, 0xa0],
[0x3703, 0x5a],
[0x3715, 0x78],
[0x3717, 0x01],
[0x370b, 0x60],
[0x3705, 0x1a]
};
return await ConfigureRegisters(analogRegisters);
}
/// <summary>
/// 配置时钟控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureClockControl()
{
var clockRegisters = new UInt16[][]
{
[0x3905, 0x02],
[0x3906, 0x10],
[0x3901, 0x0a]
};
return await ConfigureRegisters(clockRegisters);
}
/// <summary>
/// 配置PSRAM控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigurePSRAMControl()
{
var psramRegisters = new UInt16[][]
{
[0x3731, 0x12],
[0x3600, 0x08],
[0x3601, 0x33],
[0x302d, 0x60],
[0x3620, 0x52],
[0x371b, 0x20],
[0x471c, 0x50]
};
return await ConfigureRegisters(psramRegisters);
}
/// <summary>
/// 配置DVP时序寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureDVPTiming()
{
var dvpRegisters = new UInt16[][]
{
[0x3a13, 0x43],
[0x3a18, 0x00],
[0x3a19, 0xf8],
[0x3635, 0x13],
[0x3636, 0x03],
[0x3634, 0x40],
[0x3622, 0x01]
};
return await ConfigureRegisters(dvpRegisters);
}
/// <summary>
/// 配置基础控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureBaseControl()
{
var baseRegisters = new UInt16[][]
{
[0x3c01, 0x34],
[0x3c04, 0x28],
[0x3c05, 0x98],
[0x3c06, 0x00],
[0x3c07, 0x07],
[0x3c08, 0x00],
[0x3c09, 0x1c],
[0x3c0a, 0x9c],
[0x3c0b, 0x40],
[0x3708, 0x64]
};
return await ConfigureRegisters(baseRegisters);
}
/// <summary>
/// 配置图像格式寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureImageFormat()
{
var formatRegisters = new UInt16[][]
{
[0x4001, 0x02],
[0x4005, 0x1a],
[0x3000, 0x00],
[0x3004, 0xff],
[0x4300, 0x6F], // RGB565:first byte:{g[2:0],b[4:0]],second byte:{r[4:0],g[5:3]}
[0x501f, 0x01], // Format: ISP RGB
[0x440e, 0x00]
};
return await ConfigureRegisters(formatRegisters);
}
/// <summary>
/// 配置ISP控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureISPControl()
{
var ispRegisters = new UInt16[][]
{
[0x5000, 0xA7], // ISP控制
[0x5001, 0xA3] // ISP控制
};
return await ConfigureRegisters(ispRegisters);
}
/// <summary>
/// 配置AEC自动曝光控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureAEC()
{
var aecRegisters = new UInt16[][]
{
[0x3a0f, 0x30], // AEC控制;stable range in high //78
[0x3a10, 0x28], // AEC控制;stable range in low //68
[0x3a1b, 0x30], // AEC控制;stable range out high //78
[0x3a1e, 0x26], // AEC控制;stable range out low //68
[0x3a11, 0x60], // AEC控制; fast zone high //D0
[0x3a1f, 0x14], // AEC控制; fast zone low //40
[0x3b07, 0x0a] // 帧曝光模式
};
return await ConfigureRegisters(aecRegisters);
}
/// <summary>
/// 配置LENC镜头校正寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureLENC()
{
var lencRegisters = new UInt16[][]
{
[0x5800, 0x23], [0x5801, 0x14], [0x5802, 0x0f],
[0x5803, 0x0f], [0x5804, 0x12], [0x5805, 0x26],
[0x5806, 0x0c], [0x5807, 0x08], [0x5808, 0x05],
[0x5809, 0x05], [0x580a, 0x08], [0x580b, 0x0d],
[0x580c, 0x08], [0x580d, 0x03], [0x580e, 0x00],
[0x580f, 0x00], [0x5810, 0x03], [0x5811, 0x09],
[0x5812, 0x07], [0x5813, 0x03], [0x5814, 0x00],
[0x5815, 0x01], [0x5816, 0x03], [0x5817, 0x08],
[0x5818, 0x0d], [0x5819, 0x08], [0x581a, 0x05],
[0x581b, 0x06], [0x581c, 0x08], [0x581d, 0x0e],
[0x581e, 0x29], [0x581f, 0x17], [0x5820, 0x11],
[0x5821, 0x11], [0x5822, 0x15], [0x5823, 0x28],
[0x5824, 0x46], [0x5825, 0x26], [0x5826, 0x08],
[0x5827, 0x26], [0x5828, 0x64], [0x5829, 0x26],
[0x582a, 0x24], [0x582b, 0x22], [0x582c, 0x24],
[0x582d, 0x24], [0x582e, 0x06], [0x582f, 0x22],
[0x5830, 0x40], [0x5831, 0x42], [0x5832, 0x24],
[0x5833, 0x26], [0x5834, 0x24], [0x5835, 0x22],
[0x5836, 0x22], [0x5837, 0x26], [0x5838, 0x44],
[0x5839, 0x24], [0x583a, 0x26], [0x583b, 0x28],
[0x583c, 0x42], [0x583d, 0xce]
};
return await ConfigureRegisters(lencRegisters);
}
/// <summary>
/// 配置摄像头AWB自动白平衡寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureAWB()
{
var awbRegisters = new UInt16[][]
{
[0x5180, 0xff], [0x5181, 0xf2], [0x5182, 0x00],
[0x5183, 0x14], [0x5184, 0x25], [0x5185, 0x24],
[0x5186, 0x09], [0x5187, 0x09], [0x5188, 0x09],
[0x5189, 0x75], [0x518a, 0x54], [0x518b, 0xe0],
[0x518c, 0xb2], [0x518d, 0x42], [0x518e, 0x3d],
[0x518f, 0x56], [0x5190, 0x46], [0x5191, 0xf8],
[0x5192, 0x04], [0x5193, 0x70], [0x5194, 0xf0],
[0x5195, 0xf0], [0x5196, 0x03], [0x5197, 0x01],
[0x5198, 0x04], [0x5199, 0x12], [0x519a, 0x04],
[0x519b, 0x00], [0x519c, 0x06], [0x519d, 0x82],
[0x519e, 0x38]
};
return await ConfigureRegisters(awbRegisters, customDelayMs: 2);
}
/// <summary>
/// 配置摄像头Gamma校正寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureGamma()
{
var gammaRegisters = new UInt16[][]
{
[0x5480, 0x01], [0x5481, 0x08], [0x5482, 0x14],
[0x5483, 0x28], [0x5484, 0x51], [0x5485, 0x65],
[0x5486, 0x71], [0x5487, 0x7d], [0x5488, 0x87],
[0x5489, 0x91], [0x548a, 0x9a], [0x548b, 0xaa],
[0x548c, 0xb8], [0x548d, 0xcd], [0x548e, 0xdd],
[0x548f, 0xea], [0x5490, 0x1d]
};
return await ConfigureRegisters(gammaRegisters);
}
/// <summary>
/// 配置CMX色彩矩阵寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureCMX()
{
var cmxRegisters = new UInt16[][]
{
[0x5381, 0x1e], [0x5382, 0x5b], [0x5383, 0x08],
[0x5384, 0x0a], [0x5385, 0x7e], [0x5386, 0x88],
[0x5387, 0x7c], [0x5388, 0x6c], [0x5389, 0x10],
[0x538a, 0x01], [0x538b, 0x98]
};
return await ConfigureRegisters(cmxRegisters);
}
/// <summary>
/// 配置SDE特殊数字效果寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureSDE()
{
var sdeRegisters = new UInt16[][]
{
[0x5580, 0x06], [0x5583, 0x40], [0x5584, 0x10],
[0x5589, 0x10], [0x558a, 0x00], [0x558b, 0xf8],
[0x501d, 0x40]
};
return await ConfigureRegisters(sdeRegisters);
}
/// <summary>
/// 配置CIP颜色插值处理寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureCIP()
{
var cipRegisters = new UInt16[][]
{
[0x5300, 0x08], [0x5301, 0x30], [0x5302, 0x10],
[0x5303, 0x00], [0x5304, 0x08], [0x5305, 0x30],
[0x5306, 0x08], [0x5307, 0x16], [0x5309, 0x08],
[0x530a, 0x30], [0x530b, 0x04], [0x530c, 0x06],
[0x5025, 0x00]
};
return await ConfigureRegisters(cipRegisters);
}
/// <summary>
/// 配置时序控制寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureTimingControl()
{
var timingRegisters = new UInt16[][]
{
[0x3035, 0x21], // 60fps
[0x3036, PLL_MUX],// PLL倍频
[0x3c07, 0x08],
[0x3820, 0x40], // vflip
[0x3821, 0x01], // mirror
[0x3814, 0x11], // timing X inc
[0x3815, 0x11] // timing Y inc
};
return await ConfigureRegisters(timingRegisters);
}
/// <summary>
/// 配置测试模式和闪光灯寄存器
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> ConfigureTestAndFlash()
{
var testRegisters = new UInt16[][]
{
[0x3618, 0x00],
[0x3612, 0x29],
[0x3709, 0x52],
[0x370c, 0x03],
[0x4004, 0x02], // BLC(背光) 2 lines
[0x4713, 0x03], // JPEG mode 3
[0x4407, 0x04], // 量化标度
[0x460c, 0x00],
[0x3a02, 0x17], // 60Hz max exposure
[0x3a03, 0x10], // 60Hz max exposure
[0x3a14, 0x17], // 50Hz max exposure
[0x3a15, 0x10], // 50Hz max exposure
[0x4837, 0x22], // DVP CLK divider
[0x3824, 0x02], // DVP CLK divider
// 彩条测试禁用
[0x503d, 0x00],
[0x4741, 0x00],
// 闪光灯配置
[0x3016, 0x02],
[0x301c, 0x02],
[0x3019, 0x02], // 开启闪光灯
[0x3019, 0x00] // 关闭闪光灯
};
return await ConfigureRegisters(testRegisters);
}
/// <summary>
/// 开始流媒体传输
/// </summary>
/// <returns>配置结果</returns>
public async ValueTask<Result<bool>> WakeUp()
{
var startRegisters = new UInt16[][]
{
[0x3008, 0x02] // 开始流
};
return await ConfigureRegisters(startRegisters, customDelayMs: 50);
}
#region
/// <summary>
/// OV5640 自动对焦固件数据
/// </summary>
private static readonly UInt16[] OV5640_AF_FIRMWARE =
[
0x02, 0x0f, 0xd6, 0x02, 0x0a, 0x39, 0xc2, 0x01, 0x22, 0x22, 0x00, 0x02, 0x0f, 0xb2, 0xe5, 0x1f, //0x8000,
0x70, 0x72, 0xf5, 0x1e, 0xd2, 0x35, 0xff, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe4, 0xf6, 0x08, //0x8010,
0xf6, 0x0f, 0xbf, 0x34, 0xf2, 0x90, 0x0e, 0x93, 0xe4, 0x93, 0xff, 0xe5, 0x4b, 0xc3, 0x9f, 0x50, //0x8020,
0x04, 0x7f, 0x05, 0x80, 0x02, 0x7f, 0xfb, 0x78, 0xbd, 0xa6, 0x07, 0x12, 0x0f, 0x04, 0x40, 0x04, //0x8030,
0x7f, 0x03, 0x80, 0x02, 0x7f, 0x30, 0x78, 0xbc, 0xa6, 0x07, 0xe6, 0x18, 0xf6, 0x08, 0xe6, 0x78, //0x8040,
0xb9, 0xf6, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xbf, 0x76, 0x33, 0xe4, 0x08, 0xf6, 0x78, //0x8050,
0xb8, 0x76, 0x01, 0x75, 0x4a, 0x02, 0x78, 0xb6, 0xf6, 0x08, 0xf6, 0x74, 0xff, 0x78, 0xc1, 0xf6, //0x8060,
0x08, 0xf6, 0x75, 0x1f, 0x01, 0x78, 0xbc, 0xe6, 0x75, 0xf0, 0x05, 0xa4, 0xf5, 0x4b, 0x12, 0x0a, //0x8070,
0xff, 0xc2, 0x37, 0x22, 0x78, 0xb8, 0xe6, 0xd3, 0x94, 0x00, 0x40, 0x02, 0x16, 0x22, 0xe5, 0x1f, //0x8080,
0xb4, 0x05, 0x23, 0xe4, 0xf5, 0x1f, 0xc2, 0x01, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x78, //0x8090,
0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x90, 0x30, 0x28, 0xf0, //0x80a0,
0x75, 0x1e, 0x10, 0xd2, 0x35, 0x22, 0xe5, 0x4b, 0x75, 0xf0, 0x05, 0x84, 0x78, 0xbc, 0xf6, 0x90, //0x80b0,
0x0e, 0x8c, 0xe4, 0x93, 0xff, 0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x78, //0x80c0,
0xbc, 0xe6, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0xef, 0x12, 0x0f, 0x0b, //0x80d0,
0xd3, 0x78, 0xb7, 0x96, 0xee, 0x18, 0x96, 0x40, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xb9, 0xf6, 0x78, //0x80e0,
0xb6, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x8c, 0xe4, 0x93, 0x12, 0x0f, 0x0b, 0xc3, 0x78, //0x80f0,
0xc2, 0x96, 0xee, 0x18, 0x96, 0x50, 0x0d, 0x78, 0xbc, 0xe6, 0x78, 0xba, 0xf6, 0x78, 0xc1, 0xa6, //0x8100,
0x06, 0x08, 0xa6, 0x07, 0x78, 0xb6, 0xe6, 0xfe, 0x08, 0xe6, 0xc3, 0x78, 0xc2, 0x96, 0xff, 0xee, //0x8110,
0x18, 0x96, 0x78, 0xc3, 0xf6, 0x08, 0xa6, 0x07, 0x90, 0x0e, 0x95, 0xe4, 0x18, 0x12, 0x0e, 0xe9, //0x8120,
0x40, 0x02, 0xd2, 0x37, 0x78, 0xbc, 0xe6, 0x08, 0x26, 0x08, 0xf6, 0xe5, 0x1f, 0x64, 0x01, 0x70, //0x8130,
0x4a, 0xe6, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xdf, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, 0x39, 0x12, //0x8140,
0x0f, 0x02, 0x40, 0x04, 0x7f, 0xfe, 0x80, 0x02, 0x7f, 0x02, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, //0x8150,
0xe6, 0x24, 0x03, 0x78, 0xbf, 0xf6, 0x78, 0xb9, 0xe6, 0x24, 0xfd, 0x78, 0xc0, 0xf6, 0x12, 0x0f, //0x8160,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6, //0x8170,
0x07, 0x75, 0x1f, 0x02, 0x78, 0xb8, 0x76, 0x01, 0x02, 0x02, 0x4a, 0xe5, 0x1f, 0x64, 0x02, 0x60, //0x8180,
0x03, 0x02, 0x02, 0x2a, 0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x08, //0x8190,
0x12, 0x0e, 0xda, 0x50, 0x03, 0x02, 0x02, 0x28, 0x12, 0x0f, 0x02, 0x40, 0x04, 0x7f, 0xff, 0x80, //0x81a0,
0x02, 0x7f, 0x01, 0x78, 0xbd, 0xa6, 0x07, 0x78, 0xb9, 0xe6, 0x04, 0x78, 0xbf, 0xf6, 0x78, 0xb9, //0x81b0,
0xe6, 0x14, 0x78, 0xc0, 0xf6, 0x18, 0x12, 0x0f, 0x04, 0x40, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, //0x81c0,
0x00, 0x78, 0xbf, 0xa6, 0x07, 0xd3, 0x08, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x40, 0x04, 0xe6, 0xff, //0x81d0,
0x80, 0x02, 0x7f, 0x00, 0x78, 0xc0, 0xa6, 0x07, 0xc3, 0x18, 0xe6, 0x64, 0x80, 0x94, 0xb3, 0x50, //0x81e0,
0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xbf, 0xa6, 0x07, 0xc3, 0x08, 0xe6, 0x64, 0x80, //0x81f0,
0x94, 0xb3, 0x50, 0x04, 0xe6, 0xff, 0x80, 0x02, 0x7f, 0x33, 0x78, 0xc0, 0xa6, 0x07, 0x12, 0x0f, //0x8200,
0x02, 0x40, 0x06, 0x78, 0xc0, 0xe6, 0xff, 0x80, 0x04, 0x78, 0xbf, 0xe6, 0xff, 0x78, 0xbe, 0xa6, //0x8210,
0x07, 0x75, 0x1f, 0x03, 0x78, 0xb8, 0x76, 0x01, 0x80, 0x20, 0xe5, 0x1f, 0x64, 0x03, 0x70, 0x26, //0x8220,
0x78, 0xbe, 0xe6, 0xff, 0xc3, 0x78, 0xc0, 0x12, 0x0e, 0xe0, 0x40, 0x05, 0x12, 0x0e, 0xda, 0x40, //0x8230,
0x09, 0x78, 0xb9, 0xe6, 0x78, 0xbe, 0xf6, 0x75, 0x1f, 0x04, 0x78, 0xbe, 0xe6, 0x75, 0xf0, 0x05, //0x8240,
0xa4, 0xf5, 0x4b, 0x02, 0x0a, 0xff, 0xe5, 0x1f, 0xb4, 0x04, 0x10, 0x90, 0x0e, 0x94, 0xe4, 0x78, //0x8250,
0xc3, 0x12, 0x0e, 0xe9, 0x40, 0x02, 0xd2, 0x37, 0x75, 0x1f, 0x05, 0x22, 0x30, 0x01, 0x03, 0x02, //0x8260,
0x04, 0xc0, 0x30, 0x02, 0x03, 0x02, 0x04, 0xc0, 0x90, 0x51, 0xa5, 0xe0, 0x78, 0x93, 0xf6, 0xa3, //0x8270,
0xe0, 0x08, 0xf6, 0xa3, 0xe0, 0x08, 0xf6, 0xe5, 0x1f, 0x70, 0x3c, 0x75, 0x1e, 0x20, 0xd2, 0x35, //0x8280,
0x12, 0x0c, 0x7a, 0x78, 0x7e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xa6, 0x09, 0x18, 0x76, //0x8290,
0x01, 0x12, 0x0c, 0x5b, 0x78, 0x4e, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8b, 0xe6, 0x78, 0x6e, //0x82a0,
0xf6, 0x75, 0x1f, 0x01, 0x78, 0x93, 0xe6, 0x78, 0x90, 0xf6, 0x78, 0x94, 0xe6, 0x78, 0x91, 0xf6, //0x82b0,
0x78, 0x95, 0xe6, 0x78, 0x92, 0xf6, 0x22, 0x79, 0x90, 0xe7, 0xd3, 0x78, 0x93, 0x96, 0x40, 0x05, //0x82c0,
0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x93, 0xe7, 0x78, 0x90, 0x96, 0xff, 0x78, 0x88, 0x76, //0x82d0,
0x00, 0x08, 0xa6, 0x07, 0x79, 0x91, 0xe7, 0xd3, 0x78, 0x94, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, //0x82e0,
0x80, 0x08, 0xc3, 0x79, 0x94, 0xe7, 0x78, 0x91, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x79, 0x92, 0xe7, //0x82f0,
0xd3, 0x78, 0x95, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, 0x08, 0xc3, 0x79, 0x95, 0xe7, 0x78, //0x8300,
0x92, 0x96, 0xff, 0x12, 0x0c, 0x8e, 0x12, 0x0c, 0x5b, 0x78, 0x8a, 0xe6, 0x25, 0xe0, 0x24, 0x4e, //0x8310,
0xf8, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x78, 0x8a, 0xe6, 0x24, 0x6e, 0xf8, 0xa6, 0x09, 0x78, 0x8a, //0x8320,
0xe6, 0x24, 0x01, 0xff, 0xe4, 0x33, 0xfe, 0xd3, 0xef, 0x94, 0x0f, 0xee, 0x64, 0x80, 0x94, 0x80, //0x8330,
0x40, 0x04, 0x7f, 0x00, 0x80, 0x05, 0x78, 0x8a, 0xe6, 0x04, 0xff, 0x78, 0x8a, 0xa6, 0x07, 0xe5, //0x8340,
0x1f, 0xb4, 0x01, 0x0a, 0xe6, 0x60, 0x03, 0x02, 0x04, 0xc0, 0x75, 0x1f, 0x02, 0x22, 0x12, 0x0c, //0x8350,
0x7a, 0x78, 0x80, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0x12, 0x0c, 0x7a, 0x78, 0x82, 0xa6, 0x06, 0x08, //0x8360,
0xa6, 0x07, 0x78, 0x6e, 0xe6, 0x78, 0x8c, 0xf6, 0x78, 0x6e, 0xe6, 0x78, 0x8d, 0xf6, 0x7f, 0x01, //0x8370,
0xef, 0x25, 0xe0, 0x24, 0x4f, 0xf9, 0xc3, 0x78, 0x81, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x50, //0x8380,
0x0a, 0x12, 0x0c, 0x82, 0x78, 0x80, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, //0x8390,
0x8c, 0xe6, 0xc3, 0x97, 0x50, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8c, 0xf6, 0xef, 0x25, //0x83a0,
0xe0, 0x24, 0x4f, 0xf9, 0xd3, 0x78, 0x83, 0xe6, 0x97, 0x18, 0xe6, 0x19, 0x97, 0x40, 0x0a, 0x12, //0x83b0,
0x0c, 0x82, 0x78, 0x82, 0xa6, 0x04, 0x08, 0xa6, 0x05, 0x74, 0x6e, 0x2f, 0xf9, 0x78, 0x8d, 0xe6, //0x83c0,
0xd3, 0x97, 0x40, 0x08, 0x74, 0x6e, 0x2f, 0xf8, 0xe6, 0x78, 0x8d, 0xf6, 0x0f, 0xef, 0x64, 0x10, //0x83d0,
0x70, 0x9e, 0xc3, 0x79, 0x81, 0xe7, 0x78, 0x83, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0x78, 0x84, //0x83e0,
0xf6, 0x08, 0xa6, 0x07, 0xc3, 0x79, 0x8c, 0xe7, 0x78, 0x8d, 0x96, 0x08, 0xf6, 0xd3, 0x79, 0x81, //0x83f0,
0xe7, 0x78, 0x7f, 0x96, 0x19, 0xe7, 0x18, 0x96, 0x40, 0x05, 0x09, 0xe7, 0x08, 0x80, 0x06, 0xc3, //0x8400,
0x79, 0x7f, 0xe7, 0x78, 0x81, 0x96, 0xff, 0x19, 0xe7, 0x18, 0x96, 0xfe, 0x78, 0x86, 0xa6, 0x06, //0x8410,
0x08, 0xa6, 0x07, 0x79, 0x8c, 0xe7, 0xd3, 0x78, 0x8b, 0x96, 0x40, 0x05, 0xe7, 0x96, 0xff, 0x80, //0x8420,
0x08, 0xc3, 0x79, 0x8b, 0xe7, 0x78, 0x8c, 0x96, 0xff, 0x78, 0x8f, 0xa6, 0x07, 0xe5, 0x1f, 0x64, //0x8430,
0x02, 0x70, 0x69, 0x90, 0x0e, 0x91, 0x93, 0xff, 0x18, 0xe6, 0xc3, 0x9f, 0x50, 0x72, 0x12, 0x0c, //0x8440,
0x4a, 0x12, 0x0c, 0x2f, 0x90, 0x0e, 0x8e, 0x12, 0x0c, 0x38, 0x78, 0x80, 0x12, 0x0c, 0x6b, 0x7b, //0x8450,
0x04, 0x12, 0x0c, 0x1d, 0xc3, 0x12, 0x06, 0x45, 0x50, 0x56, 0x90, 0x0e, 0x92, 0xe4, 0x93, 0xff, //0x8460,
0x78, 0x8f, 0xe6, 0x9f, 0x40, 0x02, 0x80, 0x11, 0x90, 0x0e, 0x90, 0xe4, 0x93, 0xff, 0xd3, 0x78, //0x8470,
0x89, 0xe6, 0x9f, 0x18, 0xe6, 0x94, 0x00, 0x40, 0x03, 0x75, 0x1f, 0x05, 0x12, 0x0c, 0x4a, 0x12, //0x8480,
0x0c, 0x2f, 0x90, 0x0e, 0x8f, 0x12, 0x0c, 0x38, 0x78, 0x7e, 0x12, 0x0c, 0x6b, 0x7b, 0x40, 0x12, //0x8490,
0x0c, 0x1d, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x18, 0x75, 0x1f, 0x05, 0x22, 0xe5, 0x1f, 0xb4, 0x05, //0x84a0,
0x0f, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0xd2, 0x36, //0x84b0,
0x22, 0xef, 0x8d, 0xf0, 0xa4, 0xa8, 0xf0, 0xcf, 0x8c, 0xf0, 0xa4, 0x28, 0xce, 0x8d, 0xf0, 0xa4, //0x84c0,
0x2e, 0xfe, 0x22, 0xbc, 0x00, 0x0b, 0xbe, 0x00, 0x29, 0xef, 0x8d, 0xf0, 0x84, 0xff, 0xad, 0xf0, //0x84d0,
0x22, 0xe4, 0xcc, 0xf8, 0x75, 0xf0, 0x08, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xec, 0x33, 0xfc, //0x84e0,
0xee, 0x9d, 0xec, 0x98, 0x40, 0x05, 0xfc, 0xee, 0x9d, 0xfe, 0x0f, 0xd5, 0xf0, 0xe9, 0xe4, 0xce, //0x84f0,
0xfd, 0x22, 0xed, 0xf8, 0xf5, 0xf0, 0xee, 0x84, 0x20, 0xd2, 0x1c, 0xfe, 0xad, 0xf0, 0x75, 0xf0, //0x8500,
0x08, 0xef, 0x2f, 0xff, 0xed, 0x33, 0xfd, 0x40, 0x07, 0x98, 0x50, 0x06, 0xd5, 0xf0, 0xf2, 0x22, //0x8510,
0xc3, 0x98, 0xfd, 0x0f, 0xd5, 0xf0, 0xea, 0x22, 0xe8, 0x8f, 0xf0, 0xa4, 0xcc, 0x8b, 0xf0, 0xa4, //0x8520,
0x2c, 0xfc, 0xe9, 0x8e, 0xf0, 0xa4, 0x2c, 0xfc, 0x8a, 0xf0, 0xed, 0xa4, 0x2c, 0xfc, 0xea, 0x8e, //0x8530,
0xf0, 0xa4, 0xcd, 0xa8, 0xf0, 0x8b, 0xf0, 0xa4, 0x2d, 0xcc, 0x38, 0x25, 0xf0, 0xfd, 0xe9, 0x8f, //0x8540,
0xf0, 0xa4, 0x2c, 0xcd, 0x35, 0xf0, 0xfc, 0xeb, 0x8e, 0xf0, 0xa4, 0xfe, 0xa9, 0xf0, 0xeb, 0x8f, //0x8550,
0xf0, 0xa4, 0xcf, 0xc5, 0xf0, 0x2e, 0xcd, 0x39, 0xfe, 0xe4, 0x3c, 0xfc, 0xea, 0xa4, 0x2d, 0xce, //0x8560,
0x35, 0xf0, 0xfd, 0xe4, 0x3c, 0xfc, 0x22, 0x75, 0xf0, 0x08, 0x75, 0x82, 0x00, 0xef, 0x2f, 0xff, //0x8570,
0xee, 0x33, 0xfe, 0xcd, 0x33, 0xcd, 0xcc, 0x33, 0xcc, 0xc5, 0x82, 0x33, 0xc5, 0x82, 0x9b, 0xed, //0x8580,
0x9a, 0xec, 0x99, 0xe5, 0x82, 0x98, 0x40, 0x0c, 0xf5, 0x82, 0xee, 0x9b, 0xfe, 0xed, 0x9a, 0xfd, //0x8590,
0xec, 0x99, 0xfc, 0x0f, 0xd5, 0xf0, 0xd6, 0xe4, 0xce, 0xfb, 0xe4, 0xcd, 0xfa, 0xe4, 0xcc, 0xf9, //0x85a0,
0xa8, 0x82, 0x22, 0xb8, 0x00, 0xc1, 0xb9, 0x00, 0x59, 0xba, 0x00, 0x2d, 0xec, 0x8b, 0xf0, 0x84, //0x85b0,
0xcf, 0xce, 0xcd, 0xfc, 0xe5, 0xf0, 0xcb, 0xf9, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, //0x85c0,
0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xeb, 0x33, 0xfb, 0x10, 0xd7, 0x03, 0x99, 0x40, 0x04, 0xeb, //0x85d0,
0x99, 0xfb, 0x0f, 0xd8, 0xe5, 0xe4, 0xf9, 0xfa, 0x22, 0x78, 0x18, 0xef, 0x2f, 0xff, 0xee, 0x33, //0x85e0,
0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xc9, 0x33, 0xc9, 0x10, 0xd7, 0x05, 0x9b, 0xe9, 0x9a, //0x85f0,
0x40, 0x07, 0xec, 0x9b, 0xfc, 0xe9, 0x9a, 0xf9, 0x0f, 0xd8, 0xe0, 0xe4, 0xc9, 0xfa, 0xe4, 0xcc, //0x8600,
0xfb, 0x22, 0x75, 0xf0, 0x10, 0xef, 0x2f, 0xff, 0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xcc, 0x33, //0x8610,
0xcc, 0xc8, 0x33, 0xc8, 0x10, 0xd7, 0x07, 0x9b, 0xec, 0x9a, 0xe8, 0x99, 0x40, 0x0a, 0xed, 0x9b, //0x8620,
0xfd, 0xec, 0x9a, 0xfc, 0xe8, 0x99, 0xf8, 0x0f, 0xd5, 0xf0, 0xda, 0xe4, 0xcd, 0xfb, 0xe4, 0xcc, //0x8630,
0xfa, 0xe4, 0xc8, 0xf9, 0x22, 0xeb, 0x9f, 0xf5, 0xf0, 0xea, 0x9e, 0x42, 0xf0, 0xe9, 0x9d, 0x42, //0x8640,
0xf0, 0xe8, 0x9c, 0x45, 0xf0, 0x22, 0xe8, 0x60, 0x0f, 0xec, 0xc3, 0x13, 0xfc, 0xed, 0x13, 0xfd, //0x8650,
0xee, 0x13, 0xfe, 0xef, 0x13, 0xff, 0xd8, 0xf1, 0x22, 0xe8, 0x60, 0x0f, 0xef, 0xc3, 0x33, 0xff, //0x8660,
0xee, 0x33, 0xfe, 0xed, 0x33, 0xfd, 0xec, 0x33, 0xfc, 0xd8, 0xf1, 0x22, 0xe4, 0x93, 0xfc, 0x74, //0x8670,
0x01, 0x93, 0xfd, 0x74, 0x02, 0x93, 0xfe, 0x74, 0x03, 0x93, 0xff, 0x22, 0xe6, 0xfb, 0x08, 0xe6, //0x8680,
0xf9, 0x08, 0xe6, 0xfa, 0x08, 0xe6, 0xcb, 0xf8, 0x22, 0xec, 0xf6, 0x08, 0xed, 0xf6, 0x08, 0xee, //0x8690,
0xf6, 0x08, 0xef, 0xf6, 0x22, 0xa4, 0x25, 0x82, 0xf5, 0x82, 0xe5, 0xf0, 0x35, 0x83, 0xf5, 0x83, //0x86a0,
0x22, 0xd0, 0x83, 0xd0, 0x82, 0xf8, 0xe4, 0x93, 0x70, 0x12, 0x74, 0x01, 0x93, 0x70, 0x0d, 0xa3, //0x86b0,
0xa3, 0x93, 0xf8, 0x74, 0x01, 0x93, 0xf5, 0x82, 0x88, 0x83, 0xe4, 0x73, 0x74, 0x02, 0x93, 0x68, //0x86c0,
0x60, 0xef, 0xa3, 0xa3, 0xa3, 0x80, 0xdf, 0x90, 0x38, 0x04, 0x78, 0x52, 0x12, 0x0b, 0xfd, 0x90, //0x86d0,
0x38, 0x00, 0xe0, 0xfe, 0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x10, //0x86e0,
0x12, 0x0b, 0x92, 0x90, 0x38, 0x06, 0x78, 0x54, 0x12, 0x0b, 0xfd, 0x90, 0x38, 0x02, 0xe0, 0xfe, //0x86f0,
0xa3, 0xe0, 0xfd, 0xed, 0xff, 0xc3, 0x12, 0x0b, 0x9e, 0x90, 0x38, 0x12, 0x12, 0x0b, 0x92, 0xa3, //0x8700,
0xe0, 0xb4, 0x31, 0x07, 0x78, 0x52, 0x79, 0x52, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x14, 0xe0, 0xb4, //0x8710,
0x71, 0x15, 0x78, 0x52, 0xe6, 0xfe, 0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, //0x8720,
0xf9, 0x79, 0x53, 0xf7, 0xee, 0x19, 0xf7, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x31, 0x07, 0x78, 0x54, //0x8730,
0x79, 0x54, 0x12, 0x0c, 0x13, 0x90, 0x38, 0x15, 0xe0, 0xb4, 0x71, 0x15, 0x78, 0x54, 0xe6, 0xfe, //0x8740,
0x08, 0xe6, 0x78, 0x02, 0xce, 0xc3, 0x13, 0xce, 0x13, 0xd8, 0xf9, 0x79, 0x55, 0xf7, 0xee, 0x19, //0x8750,
0xf7, 0x79, 0x52, 0x12, 0x0b, 0xd9, 0x09, 0x12, 0x0b, 0xd9, 0xaf, 0x47, 0x12, 0x0b, 0xb2, 0xe5, //0x8760,
0x44, 0xfb, 0x7a, 0x00, 0xfd, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x5a, 0xa6, 0x06, 0x08, 0xa6, //0x8770,
0x07, 0xaf, 0x45, 0x12, 0x0b, 0xb2, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x56, 0xa6, //0x8780,
0x06, 0x08, 0xa6, 0x07, 0xaf, 0x48, 0x78, 0x54, 0x12, 0x0b, 0xb4, 0xe5, 0x43, 0xfb, 0xfd, 0x7c, //0x8790,
0x00, 0x12, 0x04, 0xd3, 0x78, 0x5c, 0xa6, 0x06, 0x08, 0xa6, 0x07, 0xaf, 0x46, 0x7e, 0x00, 0x78, //0x87a0,
0x54, 0x12, 0x0b, 0xb6, 0xad, 0x03, 0x7c, 0x00, 0x12, 0x04, 0xd3, 0x78, 0x58, 0xa6, 0x06, 0x08, //0x87b0,
0xa6, 0x07, 0xc3, 0x78, 0x5b, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, 0x00, //0x87c0,
0x08, 0x76, 0x08, 0xc3, 0x78, 0x5d, 0xe6, 0x94, 0x08, 0x18, 0xe6, 0x94, 0x00, 0x50, 0x05, 0x76, //0x87d0,
0x00, 0x08, 0x76, 0x08, 0x78, 0x5a, 0x12, 0x0b, 0xc6, 0xff, 0xd3, 0x78, 0x57, 0xe6, 0x9f, 0x18, //0x87e0,
0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5a, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x57, 0x12, 0x0c, 0x08, //0x87f0,
0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0x78, 0x5e, 0x12, 0x0b, 0xbe, 0xff, 0xd3, 0x78, 0x59, 0xe6, //0x8800,
0x9f, 0x18, 0xe6, 0x9e, 0x40, 0x0e, 0x78, 0x5c, 0xe6, 0x13, 0xfe, 0x08, 0xe6, 0x78, 0x59, 0x12, //0x8810,
0x0c, 0x08, 0x80, 0x04, 0x7e, 0x00, 0x7f, 0x00, 0xe4, 0xfc, 0xfd, 0x78, 0x62, 0x12, 0x06, 0x99, //0x8820,
0x78, 0x5a, 0x12, 0x0b, 0xc6, 0x78, 0x57, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0x78, 0x66, 0x12, //0x8830,
0x0b, 0xbe, 0x78, 0x59, 0x26, 0xff, 0xee, 0x18, 0x36, 0xfe, 0xe4, 0xfc, 0xfd, 0x78, 0x6a, 0x12, //0x8840,
0x06, 0x99, 0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x08, //0x8850,
0x12, 0x0b, 0xce, 0x78, 0x66, 0x12, 0x06, 0x99, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12, //0x8860,
0x06, 0x8c, 0xd3, 0x12, 0x06, 0x45, 0x40, 0x0a, 0x78, 0x54, 0x12, 0x0b, 0xd0, 0x78, 0x6a, 0x12, //0x8870,
0x06, 0x99, 0x78, 0x61, 0xe6, 0x90, 0x60, 0x01, 0xf0, 0x78, 0x65, 0xe6, 0xa3, 0xf0, 0x78, 0x69, //0x8880,
0xe6, 0xa3, 0xf0, 0x78, 0x55, 0xe6, 0xa3, 0xf0, 0x7d, 0x01, 0x78, 0x61, 0x12, 0x0b, 0xe9, 0x24, //0x8890,
0x01, 0x12, 0x0b, 0xa6, 0x78, 0x65, 0x12, 0x0b, 0xe9, 0x24, 0x02, 0x12, 0x0b, 0xa6, 0x78, 0x69, //0x88a0,
0x12, 0x0b, 0xe9, 0x24, 0x03, 0x12, 0x0b, 0xa6, 0x78, 0x6d, 0x12, 0x0b, 0xe9, 0x24, 0x04, 0x12, //0x88b0,
0x0b, 0xa6, 0x0d, 0xbd, 0x05, 0xd4, 0xc2, 0x0e, 0xc2, 0x06, 0x22, 0x85, 0x08, 0x41, 0x90, 0x30, //0x88c0,
0x24, 0xe0, 0xf5, 0x3d, 0xa3, 0xe0, 0xf5, 0x3e, 0xa3, 0xe0, 0xf5, 0x3f, 0xa3, 0xe0, 0xf5, 0x40, //0x88d0,
0xa3, 0xe0, 0xf5, 0x3c, 0xd2, 0x34, 0xe5, 0x41, 0x12, 0x06, 0xb1, 0x09, 0x31, 0x03, 0x09, 0x35, //0x88e0,
0x04, 0x09, 0x3b, 0x05, 0x09, 0x3e, 0x06, 0x09, 0x41, 0x07, 0x09, 0x4a, 0x08, 0x09, 0x5b, 0x12, //0x88f0,
0x09, 0x73, 0x18, 0x09, 0x89, 0x19, 0x09, 0x5e, 0x1a, 0x09, 0x6a, 0x1b, 0x09, 0xad, 0x80, 0x09, //0x8900,
0xb2, 0x81, 0x0a, 0x1d, 0x8f, 0x0a, 0x09, 0x90, 0x0a, 0x1d, 0x91, 0x0a, 0x1d, 0x92, 0x0a, 0x1d, //0x8910,
0x93, 0x0a, 0x1d, 0x94, 0x0a, 0x1d, 0x98, 0x0a, 0x17, 0x9f, 0x0a, 0x1a, 0xec, 0x00, 0x00, 0x0a, //0x8920,
0x38, 0x12, 0x0f, 0x74, 0x22, 0x12, 0x0f, 0x74, 0xd2, 0x03, 0x22, 0xd2, 0x03, 0x22, 0xc2, 0x03, //0x8930,
0x22, 0xa2, 0x37, 0xe4, 0x33, 0xf5, 0x3c, 0x02, 0x0a, 0x1d, 0xc2, 0x01, 0xc2, 0x02, 0xc2, 0x03, //0x8940,
0x12, 0x0d, 0x0d, 0x75, 0x1e, 0x70, 0xd2, 0x35, 0x02, 0x0a, 0x1d, 0x02, 0x0a, 0x04, 0x85, 0x40, //0x8950,
0x4a, 0x85, 0x3c, 0x4b, 0x12, 0x0a, 0xff, 0x02, 0x0a, 0x1d, 0x85, 0x4a, 0x40, 0x85, 0x4b, 0x3c, //0x8960,
0x02, 0x0a, 0x1d, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x85, 0x40, 0x31, 0x85, 0x3f, 0x30, 0x85, 0x3e, //0x8970,
0x2f, 0x85, 0x3d, 0x2e, 0x12, 0x0f, 0x46, 0x80, 0x1f, 0x75, 0x22, 0x00, 0x75, 0x23, 0x01, 0x74, //0x8980,
0xff, 0xf5, 0x2d, 0xf5, 0x2c, 0xf5, 0x2b, 0xf5, 0x2a, 0x12, 0x0f, 0x46, 0x85, 0x2d, 0x40, 0x85, //0x8990,
0x2c, 0x3f, 0x85, 0x2b, 0x3e, 0x85, 0x2a, 0x3d, 0xe4, 0xf5, 0x3c, 0x80, 0x70, 0x12, 0x0f, 0x16, //0x89a0,
0x80, 0x6b, 0x85, 0x3d, 0x45, 0x85, 0x3e, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xe5, 0x45, 0xc3, //0x89b0,
0x9f, 0x50, 0x02, 0x8f, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, 0xe5, 0x46, 0xc3, 0x9f, 0x50, 0x02, //0x89c0,
0x8f, 0x46, 0xe5, 0x47, 0xc3, 0x13, 0xff, 0xfd, 0xe5, 0x45, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, //0x89d0,
0x44, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, 0x44, 0x9f, 0xf5, 0x45, 0xe5, 0x48, 0xc3, 0x13, 0xff, //0x89e0,
0xfd, 0xe5, 0x46, 0x2d, 0xfd, 0xe4, 0x33, 0xfc, 0xe5, 0x43, 0x12, 0x0f, 0x90, 0x40, 0x05, 0xe5, //0x89f0,
0x43, 0x9f, 0xf5, 0x46, 0x12, 0x06, 0xd7, 0x80, 0x14, 0x85, 0x40, 0x48, 0x85, 0x3f, 0x47, 0x85, //0x8a00,
0x3e, 0x46, 0x85, 0x3d, 0x45, 0x80, 0x06, 0x02, 0x06, 0xd7, 0x12, 0x0d, 0x7e, 0x90, 0x30, 0x24, //0x8a10,
0xe5, 0x3d, 0xf0, 0xa3, 0xe5, 0x3e, 0xf0, 0xa3, 0xe5, 0x3f, 0xf0, 0xa3, 0xe5, 0x40, 0xf0, 0xa3, //0x8a20,
0xe5, 0x3c, 0xf0, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0xc0, //0x8a30,
0xd0, 0x90, 0x3f, 0x0c, 0xe0, 0xf5, 0x32, 0xe5, 0x32, 0x30, 0xe3, 0x74, 0x30, 0x36, 0x66, 0x90, //0x8a40,
0x60, 0x19, 0xe0, 0xf5, 0x0a, 0xa3, 0xe0, 0xf5, 0x0b, 0x90, 0x60, 0x1d, 0xe0, 0xf5, 0x14, 0xa3, //0x8a50,
0xe0, 0xf5, 0x15, 0x90, 0x60, 0x21, 0xe0, 0xf5, 0x0c, 0xa3, 0xe0, 0xf5, 0x0d, 0x90, 0x60, 0x29, //0x8a60,
0xe0, 0xf5, 0x0e, 0xa3, 0xe0, 0xf5, 0x0f, 0x90, 0x60, 0x31, 0xe0, 0xf5, 0x10, 0xa3, 0xe0, 0xf5, //0x8a70,
0x11, 0x90, 0x60, 0x39, 0xe0, 0xf5, 0x12, 0xa3, 0xe0, 0xf5, 0x13, 0x30, 0x01, 0x06, 0x30, 0x33, //0x8a80,
0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x09, 0x30, 0x02, 0x06, 0x30, 0x33, 0x03, 0xd3, 0x80, 0x01, //0x8a90,
0xc3, 0x92, 0x0a, 0x30, 0x33, 0x0c, 0x30, 0x03, 0x09, 0x20, 0x02, 0x06, 0x20, 0x01, 0x03, 0xd3, //0x8aa0,
0x80, 0x01, 0xc3, 0x92, 0x0b, 0x90, 0x30, 0x01, 0xe0, 0x44, 0x40, 0xf0, 0xe0, 0x54, 0xbf, 0xf0, //0x8ab0,
0xe5, 0x32, 0x30, 0xe1, 0x14, 0x30, 0x34, 0x11, 0x90, 0x30, 0x22, 0xe0, 0xf5, 0x08, 0xe4, 0xf0, //0x8ac0,
0x30, 0x00, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x08, 0xe5, 0x32, 0x30, 0xe5, 0x12, 0x90, 0x56, //0x8ad0,
0xa1, 0xe0, 0xf5, 0x09, 0x30, 0x31, 0x09, 0x30, 0x05, 0x03, 0xd3, 0x80, 0x01, 0xc3, 0x92, 0x0d, //0x8ae0,
0x90, 0x3f, 0x0c, 0xe5, 0x32, 0xf0, 0xd0, 0xd0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, //0x8af0,
0x0e, 0x7e, 0xe4, 0x93, 0xfe, 0x74, 0x01, 0x93, 0xff, 0xc3, 0x90, 0x0e, 0x7c, 0x74, 0x01, 0x93, //0x8b00,
0x9f, 0xff, 0xe4, 0x93, 0x9e, 0xfe, 0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0xab, //0x8b10,
0x3b, 0xaa, 0x3a, 0xa9, 0x39, 0xa8, 0x38, 0xaf, 0x4b, 0xfc, 0xfd, 0xfe, 0x12, 0x05, 0x28, 0x12, //0x8b20,
0x0d, 0xe1, 0xe4, 0x7b, 0xff, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0xb3, 0x12, 0x0d, 0xe1, 0x90, 0x0e, //0x8b30,
0x69, 0xe4, 0x12, 0x0d, 0xf6, 0x12, 0x0d, 0xe1, 0xe4, 0x85, 0x4a, 0x37, 0xf5, 0x36, 0xf5, 0x35, //0x8b40,
0xf5, 0x34, 0xaf, 0x37, 0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0xa3, 0x12, 0x0d, 0xf6, 0x8f, 0x37, //0x8b50,
0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0xe5, 0x3b, 0x45, 0x37, 0xf5, 0x3b, 0xe5, 0x3a, 0x45, 0x36, //0x8b60,
0xf5, 0x3a, 0xe5, 0x39, 0x45, 0x35, 0xf5, 0x39, 0xe5, 0x38, 0x45, 0x34, 0xf5, 0x38, 0xe4, 0xf5, //0x8b70,
0x22, 0xf5, 0x23, 0x85, 0x3b, 0x31, 0x85, 0x3a, 0x30, 0x85, 0x39, 0x2f, 0x85, 0x38, 0x2e, 0x02, //0x8b80,
0x0f, 0x46, 0xe0, 0xa3, 0xe0, 0x75, 0xf0, 0x02, 0xa4, 0xff, 0xae, 0xf0, 0xc3, 0x08, 0xe6, 0x9f, //0x8b90,
0xf6, 0x18, 0xe6, 0x9e, 0xf6, 0x22, 0xff, 0xe5, 0xf0, 0x34, 0x60, 0x8f, 0x82, 0xf5, 0x83, 0xec, //0x8ba0,
0xf0, 0x22, 0x78, 0x52, 0x7e, 0x00, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x02, 0x04, 0xc1, 0xe4, 0xfc, //0x8bb0,
0xfd, 0x12, 0x06, 0x99, 0x78, 0x5c, 0xe6, 0xc3, 0x13, 0xfe, 0x08, 0xe6, 0x13, 0x22, 0x78, 0x52, //0x8bc0,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0xe4, 0xfc, 0xfd, 0x22, 0xe7, 0xc4, 0xf8, 0x54, 0xf0, 0xc8, 0x68, //0x8bd0,
0xf7, 0x09, 0xe7, 0xc4, 0x54, 0x0f, 0x48, 0xf7, 0x22, 0xe6, 0xfc, 0xed, 0x75, 0xf0, 0x04, 0xa4, //0x8be0,
0x22, 0x12, 0x06, 0x7c, 0x8f, 0x48, 0x8e, 0x47, 0x8d, 0x46, 0x8c, 0x45, 0x22, 0xe0, 0xfe, 0xa3, //0x8bf0,
0xe0, 0xfd, 0xee, 0xf6, 0xed, 0x08, 0xf6, 0x22, 0x13, 0xff, 0xc3, 0xe6, 0x9f, 0xff, 0x18, 0xe6, //0x8c00,
0x9e, 0xfe, 0x22, 0xe6, 0xc3, 0x13, 0xf7, 0x08, 0xe6, 0x13, 0x09, 0xf7, 0x22, 0xad, 0x39, 0xac, //0x8c10,
0x38, 0xfa, 0xf9, 0xf8, 0x12, 0x05, 0x28, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0xab, //0x8c20,
0x37, 0xaa, 0x36, 0xa9, 0x35, 0xa8, 0x34, 0x22, 0x93, 0xff, 0xe4, 0xfc, 0xfd, 0xfe, 0x12, 0x05, //0x8c30,
0x28, 0x8f, 0x37, 0x8e, 0x36, 0x8d, 0x35, 0x8c, 0x34, 0x22, 0x78, 0x84, 0xe6, 0xfe, 0x08, 0xe6, //0x8c40,
0xff, 0xe4, 0x8f, 0x37, 0x8e, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0x22, 0x90, 0x0e, 0x8c, 0xe4, 0x93, //0x8c50,
0x25, 0xe0, 0x24, 0x0a, 0xf8, 0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe6, 0xfe, 0x08, 0xe6, 0xff, //0x8c60,
0xe4, 0x8f, 0x3b, 0x8e, 0x3a, 0xf5, 0x39, 0xf5, 0x38, 0x22, 0x78, 0x4e, 0xe6, 0xfe, 0x08, 0xe6, //0x8c70,
0xff, 0x22, 0xef, 0x25, 0xe0, 0x24, 0x4e, 0xf8, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0x22, 0x78, 0x89, //0x8c80,
0xef, 0x26, 0xf6, 0x18, 0xe4, 0x36, 0xf6, 0x22, 0x75, 0x89, 0x03, 0x75, 0xa8, 0x01, 0x75, 0xb8, //0x8c90,
0x04, 0x75, 0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x15, 0x75, 0x37, 0x0d, 0x12, 0x0e, 0x9a, //0x8ca0,
0x12, 0x00, 0x09, 0x12, 0x0f, 0x16, 0x12, 0x00, 0x06, 0xd2, 0x00, 0xd2, 0x34, 0xd2, 0xaf, 0x75, //0x8cb0,
0x34, 0xff, 0x75, 0x35, 0x0e, 0x75, 0x36, 0x49, 0x75, 0x37, 0x03, 0x12, 0x0e, 0x9a, 0x30, 0x08, //0x8cc0,
0x09, 0xc2, 0x34, 0x12, 0x08, 0xcb, 0xc2, 0x08, 0xd2, 0x34, 0x30, 0x0b, 0x09, 0xc2, 0x36, 0x12, //0x8cd0,
0x02, 0x6c, 0xc2, 0x0b, 0xd2, 0x36, 0x30, 0x09, 0x09, 0xc2, 0x36, 0x12, 0x00, 0x0e, 0xc2, 0x09, //0x8ce0,
0xd2, 0x36, 0x30, 0x0e, 0x03, 0x12, 0x06, 0xd7, 0x30, 0x35, 0xd3, 0x90, 0x30, 0x29, 0xe5, 0x1e, //0x8cf0,
0xf0, 0xb4, 0x10, 0x05, 0x90, 0x30, 0x23, 0xe4, 0xf0, 0xc2, 0x35, 0x80, 0xc1, 0xe4, 0xf5, 0x4b, //0x8d00,
0x90, 0x0e, 0x7a, 0x93, 0xff, 0xe4, 0x8f, 0x37, 0xf5, 0x36, 0xf5, 0x35, 0xf5, 0x34, 0xaf, 0x37, //0x8d10,
0xae, 0x36, 0xad, 0x35, 0xac, 0x34, 0x90, 0x0e, 0x6a, 0x12, 0x0d, 0xf6, 0x8f, 0x37, 0x8e, 0x36, //0x8d20,
0x8d, 0x35, 0x8c, 0x34, 0x90, 0x0e, 0x72, 0x12, 0x06, 0x7c, 0xef, 0x45, 0x37, 0xf5, 0x37, 0xee, //0x8d30,
0x45, 0x36, 0xf5, 0x36, 0xed, 0x45, 0x35, 0xf5, 0x35, 0xec, 0x45, 0x34, 0xf5, 0x34, 0xe4, 0xf5, //0x8d40,
0x22, 0xf5, 0x23, 0x85, 0x37, 0x31, 0x85, 0x36, 0x30, 0x85, 0x35, 0x2f, 0x85, 0x34, 0x2e, 0x12, //0x8d50,
0x0f, 0x46, 0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x72, 0x12, 0x0d, 0xea, 0x12, 0x0f, 0x46, //0x8d60,
0xe4, 0xf5, 0x22, 0xf5, 0x23, 0x90, 0x0e, 0x6e, 0x12, 0x0d, 0xea, 0x02, 0x0f, 0x46, 0xe5, 0x40, //0x8d70,
0x24, 0xf2, 0xf5, 0x37, 0xe5, 0x3f, 0x34, 0x43, 0xf5, 0x36, 0xe5, 0x3e, 0x34, 0xa2, 0xf5, 0x35, //0x8d80,
0xe5, 0x3d, 0x34, 0x28, 0xf5, 0x34, 0xe5, 0x37, 0xff, 0xe4, 0xfe, 0xfd, 0xfc, 0x78, 0x18, 0x12, //0x8d90,
0x06, 0x69, 0x8f, 0x40, 0x8e, 0x3f, 0x8d, 0x3e, 0x8c, 0x3d, 0xe5, 0x37, 0x54, 0xa0, 0xff, 0xe5, //0x8da0,
0x36, 0xfe, 0xe4, 0xfd, 0xfc, 0x78, 0x07, 0x12, 0x06, 0x56, 0x78, 0x10, 0x12, 0x0f, 0x9a, 0xe4, //0x8db0,
0xff, 0xfe, 0xe5, 0x35, 0xfd, 0xe4, 0xfc, 0x78, 0x0e, 0x12, 0x06, 0x56, 0x12, 0x0f, 0x9d, 0xe4, //0x8dc0,
0xff, 0xfe, 0xfd, 0xe5, 0x34, 0xfc, 0x78, 0x18, 0x12, 0x06, 0x56, 0x78, 0x08, 0x12, 0x0f, 0x9a, //0x8dd0,
0x22, 0x8f, 0x3b, 0x8e, 0x3a, 0x8d, 0x39, 0x8c, 0x38, 0x22, 0x12, 0x06, 0x7c, 0x8f, 0x31, 0x8e, //0x8de0,
0x30, 0x8d, 0x2f, 0x8c, 0x2e, 0x22, 0x93, 0xf9, 0xf8, 0x02, 0x06, 0x69, 0x00, 0x00, 0x00, 0x00, //0x8df0,
0x12, 0x01, 0x17, 0x08, 0x31, 0x15, 0x53, 0x54, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, 0x13, 0x01, //0x8e00,
0x10, 0x01, 0x56, 0x40, 0x1a, 0x30, 0x29, 0x7e, 0x00, 0x30, 0x04, 0x20, 0xdf, 0x30, 0x05, 0x40, //0x8e10,
0xbf, 0x50, 0x03, 0x00, 0xfd, 0x50, 0x27, 0x01, 0xfe, 0x60, 0x00, 0x11, 0x00, 0x3f, 0x05, 0x30, //0x8e20,
0x00, 0x3f, 0x06, 0x22, 0x00, 0x3f, 0x01, 0x2a, 0x00, 0x3f, 0x02, 0x00, 0x00, 0x36, 0x06, 0x07, //0x8e30,
0x00, 0x3f, 0x0b, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x40, 0xbf, 0x30, 0x01, 0x00, //0x8e40,
0xbf, 0x30, 0x29, 0x70, 0x00, 0x3a, 0x00, 0x00, 0xff, 0x3a, 0x00, 0x00, 0xff, 0x36, 0x03, 0x36, //0x8e50,
0x02, 0x41, 0x44, 0x58, 0x20, 0x18, 0x10, 0x0a, 0x04, 0x04, 0x00, 0x03, 0xff, 0x64, 0x00, 0x00, //0x8e60,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x06, 0x06, 0x00, 0x03, 0x51, 0x00, 0x7a, //0x8e70,
0x50, 0x3c, 0x28, 0x1e, 0x10, 0x10, 0x50, 0x2d, 0x28, 0x16, 0x10, 0x10, 0x02, 0x00, 0x10, 0x0c, //0x8e80,
0x10, 0x04, 0x0c, 0x6e, 0x06, 0x05, 0x00, 0xa5, 0x5a, 0x00, 0xae, 0x35, 0xaf, 0x36, 0xe4, 0xfd, //0x8e90,
0xed, 0xc3, 0x95, 0x37, 0x50, 0x33, 0x12, 0x0f, 0xe2, 0xe4, 0x93, 0xf5, 0x38, 0x74, 0x01, 0x93, //0x8ea0,
0xf5, 0x39, 0x45, 0x38, 0x60, 0x23, 0x85, 0x39, 0x82, 0x85, 0x38, 0x83, 0xe0, 0xfc, 0x12, 0x0f, //0x8eb0,
0xe2, 0x74, 0x03, 0x93, 0x52, 0x04, 0x12, 0x0f, 0xe2, 0x74, 0x02, 0x93, 0x42, 0x04, 0x85, 0x39, //0x8ec0,
0x82, 0x85, 0x38, 0x83, 0xec, 0xf0, 0x0d, 0x80, 0xc7, 0x22, 0x78, 0xbe, 0xe6, 0xd3, 0x08, 0xff, //0x8ed0,
0xe6, 0x64, 0x80, 0xf8, 0xef, 0x64, 0x80, 0x98, 0x22, 0x93, 0xff, 0x7e, 0x00, 0xe6, 0xfc, 0x08, //0x8ee0,
0xe6, 0xfd, 0x12, 0x04, 0xc1, 0x78, 0xc1, 0xe6, 0xfc, 0x08, 0xe6, 0xfd, 0xd3, 0xef, 0x9d, 0xee, //0x8ef0,
0x9c, 0x22, 0x78, 0xbd, 0xd3, 0xe6, 0x64, 0x80, 0x94, 0x80, 0x22, 0x25, 0xe0, 0x24, 0x0a, 0xf8, //0x8f00,
0xe6, 0xfe, 0x08, 0xe6, 0xff, 0x22, 0xe5, 0x3c, 0xd3, 0x94, 0x00, 0x40, 0x0b, 0x90, 0x0e, 0x88, //0x8f10,
0x12, 0x0b, 0xf1, 0x90, 0x0e, 0x86, 0x80, 0x09, 0x90, 0x0e, 0x82, 0x12, 0x0b, 0xf1, 0x90, 0x0e, //0x8f20,
0x80, 0xe4, 0x93, 0xf5, 0x44, 0xa3, 0xe4, 0x93, 0xf5, 0x43, 0xd2, 0x06, 0x30, 0x06, 0x03, 0xd3, //0x8f30,
0x80, 0x01, 0xc3, 0x92, 0x0e, 0x22, 0xa2, 0xaf, 0x92, 0x32, 0xc2, 0xaf, 0xe5, 0x23, 0x45, 0x22, //0x8f40,
0x90, 0x0e, 0x5d, 0x60, 0x0e, 0x12, 0x0f, 0xcb, 0xe0, 0xf5, 0x2c, 0x12, 0x0f, 0xc8, 0xe0, 0xf5, //0x8f50,
0x2d, 0x80, 0x0c, 0x12, 0x0f, 0xcb, 0xe5, 0x30, 0xf0, 0x12, 0x0f, 0xc8, 0xe5, 0x31, 0xf0, 0xa2, //0x8f60,
0x32, 0x92, 0xaf, 0x22, 0xd2, 0x01, 0xc2, 0x02, 0xe4, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, //0x8f70,
0x33, 0xd2, 0x36, 0xd2, 0x01, 0xc2, 0x02, 0xf5, 0x1f, 0xf5, 0x1e, 0xd2, 0x35, 0xd2, 0x33, 0x22, //0x8f80,
0xfb, 0xd3, 0xed, 0x9b, 0x74, 0x80, 0xf8, 0x6c, 0x98, 0x22, 0x12, 0x06, 0x69, 0xe5, 0x40, 0x2f, //0x8f90,
0xf5, 0x40, 0xe5, 0x3f, 0x3e, 0xf5, 0x3f, 0xe5, 0x3e, 0x3d, 0xf5, 0x3e, 0xe5, 0x3d, 0x3c, 0xf5, //0x8fa0,
0x3d, 0x22, 0xc0, 0xe0, 0xc0, 0x83, 0xc0, 0x82, 0x90, 0x3f, 0x0d, 0xe0, 0xf5, 0x33, 0xe5, 0x33, //0x8fb0,
0xf0, 0xd0, 0x82, 0xd0, 0x83, 0xd0, 0xe0, 0x32, 0x90, 0x0e, 0x5f, 0xe4, 0x93, 0xfe, 0x74, 0x01, //0x8fc0,
0x93, 0xf5, 0x82, 0x8e, 0x83, 0x22, 0x78, 0x7f, 0xe4, 0xf6, 0xd8, 0xfd, 0x75, 0x81, 0xcd, 0x02, //0x8fd0,
0x0c, 0x98, 0x8f, 0x82, 0x8e, 0x83, 0x75, 0xf0, 0x04, 0xed, 0x02, 0x06, 0xa5 //0x8fe0
];
/// <summary>
/// 写入自动对焦固件到摄像头
/// </summary>
/// <returns>写入结果</returns>
private async ValueTask<Result<bool>> WriteFocusFirmware()
{
logger.Info("开始写入OV5640自动对焦固件");
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
// 组装固件写入命令:地址 + 所有固件数据
UInt16 firmwareAddr = 0x8000;
var firmwareCommand = new UInt16[1 + OV5640_AF_FIRMWARE.Length];
firmwareCommand[0] = firmwareAddr;
// 将固件数据复制到命令数组中
for (int i = 0; i < OV5640_AF_FIRMWARE.Length; i++)
{
firmwareCommand[i + 1] = OV5640_AF_FIRMWARE[i];
}
var result = await ConfigureRegisters([firmwareCommand]);
if (!result.IsSuccessful)
{
logger.Error($"固件写入失败: {result.Error}");
return result;
}
logger.Info($"OV5640自动对焦固件写入完成总共写入 {OV5640_AF_FIRMWARE.Length} 字节");
return true;
}
/// <summary>
/// 初始化自动对焦功能
/// </summary>
/// <returns>初始化结果</returns>
public async ValueTask<Result<bool>> InitAutoFocus()
{
logger.Info("开始初始化OV5640自动对焦功能");
// 步骤1: 启动MCU
var mcuResult = await ConfigureRegisters([[0x3000, 0x20]]);
if (!mcuResult.IsSuccessful) return mcuResult;
// 步骤2: 写入自动对焦固件
var firmwareResult = await WriteFocusFirmware();
if (!firmwareResult.IsSuccessful) return firmwareResult;
// 步骤3: 配置对焦相关寄存器
var focusRegisters = new UInt16[][]
{
[0x3022, 0x00], // 清除指令寄存器
[0x3023, 0x00], // 清除状态寄存器
[0x3024, 0x00], // 清除参数寄存器
[0x3025, 0x00],
[0x3026, 0x00],
[0x3027, 0x00],
[0x3028, 0x00],
[0x3029, 0xFF], // 状态寄存器
[0x3000, 0x00], // 启动MCU
[0x3004, 0xFF] // 使能中断
};
var result = await ConfigureRegisters(focusRegisters);
if (!result.IsSuccessful) return result;
// // 读取寄存器判断初始化是否完毕
// for (int iteration = 1000; iteration > 0; iteration--)
// {
// var readResult = await ReadRegister(0x3029);
// if (!readResult.IsSuccessful)
// {
// logger.Error($"读取自动对焦初始化状态失败: {readResult.Error}");
// return new(readResult.Error);
// }
// logger.Debug($"自动对焦初始化状态检查, state=0x{readResult.Value:X2}");
// if (readResult.Value != 0x7F)
// {
// break; // 初始化完成
// }
// if (iteration == 1)
// {
// logger.Error($"自动对焦初始化状态检查超时!! state=0x{readResult.Value:X2}");
// return new(new Exception($"自动对焦初始化状态检查超时, state=0x{readResult.Value:X2}"));
// }
// await Task.Delay(1);
// }
logger.Info("OV5640自动对焦功能初始化完成");
return true;
}
/// <summary>
/// 执行一次自动对焦
/// </summary>
/// <returns>对焦结果</returns>
public async ValueTask<Result<bool>> PerformAutoFocus()
{
logger.Info("开始执行一次自动对焦");
// 步骤1: 将 0x3022 寄存器写为 0x03开始单点对焦过程
var focusCmdResult = await ConfigureRegisters([[0x3022, 0x03]]);
if (!focusCmdResult.IsSuccessful)
{
logger.Error($"写入对焦命令失败: {focusCmdResult.Error}");
return focusCmdResult;
}
logger.Info("已发送单点对焦命令 (0x3022 = 0x03)");
// 步骤2: 读取寄存器 0x3029 的状态,如果返回值为 0x10代表对焦已完成
for (int iteration = 5000; iteration > 0; iteration--)
{
var readResult = await ReadRegister(0x3029);
if (!readResult.IsSuccessful)
{
logger.Error($"读取对焦状态寄存器(0x3029)失败: {readResult.Error}");
return new(readResult.Error);
}
if (readResult.Value == 0x10)
{
logger.Info("对焦已完成 (0x3029 = 0x10)");
break;
}
if (iteration == 1)
{
logger.Error($"自动对焦超时,状态: 0x{readResult.Value:X2}");
return new(new Exception($"自动对焦超时,状态: 0x{readResult.Value:X2}"));
}
await Task.Delay(100);
}
// 步骤3: 写寄存器 0x3022 为 0x06暂停对焦过程使镜头将保持在此对焦位置
var pauseResult = await ConfigureRegisters([[0x3022, 0x06]]);
if (!pauseResult.IsSuccessful)
{
logger.Error($"暂停对焦过程失败: {pauseResult.Error}");
return pauseResult;
}
logger.Info("自动对焦完成并暂停,镜头保持在对焦位置");
return true;
}
#endregion
}