initial commit

This commit is contained in:
2026-06-25 12:19:29 +03:00
commit 1160477945
19 changed files with 898 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
using RTCSync.Utils;
namespace RTCSync.Models;
public class TimeModel(ReadOnlySpan<byte> rtcBytes)
{
private static int CalculateHours(ReadOnlySpan<byte> rtcBytes)
{
var hours = 0;
if (TimeModelBI.TwentyHourBI.Take(rtcBytes) == 1)
{
hours = 20;
}
else if (TimeModelBI.TenHourBI.Take(rtcBytes) == 1)
{
hours = 10;
}
hours += TimeModelBI.HourBI.Take(rtcBytes);
return hours;
}
public DateTime Time = new DateTime(
2000 + TimeModelBI.TenYearBI.Take(rtcBytes) * 10 + TimeModelBI.YearBI.Take(rtcBytes),
TimeModelBI.TenMonthBI.Take(rtcBytes) * 10 + TimeModelBI.MonthBI.Take(rtcBytes),
TimeModelBI.TenDateBI.Take(rtcBytes) * 10 + TimeModelBI.DateBI.Take(rtcBytes),
CalculateHours(rtcBytes),
TimeModelBI.TenMinutesBI.Take(rtcBytes) * 10 + TimeModelBI.MinutesBI.Take(rtcBytes),
TimeModelBI.TenSecondsBI.Take(rtcBytes) * 10 + TimeModelBI.SecondsBI.Take(rtcBytes),
DateTimeKind.Local
);
}
+54
View File
@@ -0,0 +1,54 @@
using RTCSync.Utils;
namespace RTCSync.Models;
public class TimeModelBI
{
// 6 5 4 | 3 2 1 0
// 10 Seconds | Seconds
// 00 - 59
public static BitIndex[] SecondsBI { get; } = [new(1, 0..3)];
public static BitIndex[] TenSecondsBI { get; } = [new(1, 4..6)];
// 6 5 4 | 3 2 1 0
// 10 Minutes | Minutes
// 00 - 59
public static BitIndex[] MinutesBI { get; } = [new(2, 0..3)];
public static BitIndex[] TenMinutesBI { get; } = [new(2, 4..6)];
// 6 | 5 | 4 | 3 2 1 0
// 12/24 | 20 hour | 10 hour | Hour
// 1-12 + AM/PM
// 00 - 23
public static BitIndex[] HourBI { get; } = [new(3, 0..3)];
public static BitIndex[] TenHourBI { get; } = [new(3, 4..4)];
public static BitIndex[] TwentyHourBI { get; } = [new(3, 5..5)];
// When high - 12 hour format is selected
public static BitIndex[] HoursFormatBI { get; } = [new(3, 6..6)];
// 2 1 0
// Day
// 1 - 7
// 1 = Saturday, 2 = Monday
public static BitIndex[] DayOfWeekBI { get; } = [new(4, 0..3)];
// 5 4 | 3 2 1 0
// 10 Date | Date
// 01 - 31
public static BitIndex[] DateBI { get; } = [new(5, 0..4)];
public static BitIndex[] TenDateBI { get; } = [new(5, 4..5)];
// 7 | 4 | 3 2 1 0
// Century | 10 Month | Month
// 01 - 12 + Century
public static BitIndex[] MonthBI { get; } = [new(6, 0..3)];
public static BitIndex[] TenMonthBI { get; } = [new(6, 4..4)];
// High when the years register overflows from 99 to 00
public static BitIndex[] CenturyBI { get; } = [new(6, 7..7)];
// 7 6 5 4 | 3 2 1 0
// 10 Year | Year
// 0-99
public static BitIndex[] YearBI { get; } = [new(7, 0..3)];
public static BitIndex[] TenYearBI { get; } = [new(7, 4..7)];
}
+15
View File
@@ -0,0 +1,15 @@
namespace RTCSync.Options;
public interface IOption
{
string Description { get; }
List<string> OptionNames { get; }
string OptionValues { get; }
void Execute(OptionArgs args);
}
public class OptionArgs
{
public Dictionary<string, string> OptionValues { get; set; }
public List<string> Arguments { get; set; } = [];
}
+145
View File
@@ -0,0 +1,145 @@
namespace RTCSync.Options;
public class OptionDispatcher
{
private readonly Dictionary<string, IOption> _registredOptions = new();
public void Register(IOption option)
{
foreach (var optionName in option.OptionNames)
{
_registredOptions[optionName] = option;
}
}
public void Execute(string[] args)
{
if (args.Length == 0)
{
PrintHelp();
return;
}
var flag = args[0];
if (!_registredOptions.TryGetValue(flag, out var command))
{
Console.WriteLine($"Неизвестный параметр: {flag}");
PrintHelp();
return;
}
// Парсим все остальные аргументы как опции
var commandArgs = ParseArguments(args.Skip(1).ToArray());
command.Execute(commandArgs);
}
private static OptionArgs ParseArguments(string[] args)
{
var optionValues = new Dictionary<string, string>();
for (var i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith("--"))
{
optionValues.Add("unnamed", args[i]);
continue;
}
optionValues[args[i]] = args[i + 1];
i++;
}
return new OptionArgs
{
OptionValues = optionValues,
Arguments = []
};
}
private static void PrintOption(string optionLine, string description)
{
var terminalWidth = Console.WindowWidth;
// Console.WriteLine(terminalWidth);
// for (var i = 0; i < terminalWidth; i++)
// Console.Write("_");
// Console.WriteLine();
const int columnWidth = 44;
var descriptionWidth = terminalWidth - columnWidth;
// Если название опции слишком длиное, перенести описание на следующую строку
if (optionLine.Length > columnWidth)
{
Console.WriteLine(optionLine);
var lines = SplitText(description, descriptionWidth);
foreach (var line in lines)
{
Console.WriteLine($"{new string(' ', 2)}{line}");
}
return;
}
var formatted = optionLine.PadRight(columnWidth);
var descLines = SplitText(description, descriptionWidth);
// Первая строка с опцией и началом описания
Console.WriteLine($"{formatted}{descLines[0]}");
// Остальные строки описания с выравниванием
for (var i = 1; i < descLines.Count; i++)
{
Console.WriteLine($"{new string(' ', columnWidth)}{descLines[i]}");
}
Console.WriteLine();
}
private static List<string> SplitText(string text, int maxWidth)
{
var lines = new List<string>();
if (maxWidth < 1) return [text];
var words = text.Split(' ');
var current = "";
foreach (var word in words)
{
if ((current + word + " ").Length <= maxWidth)
{
current += word + " ";
}
else
{
if (!string.IsNullOrEmpty(current))
lines.Add(current.TrimEnd());
current = word + " ";
}
}
if (!string.IsNullOrEmpty(current))
lines.Add(current.TrimEnd());
return lines.Count > 0 ? lines : [text];
}
private void PrintHelp()
{
Console.WriteLine("RTCSync - утилита для работы с часами реального времени DS3231, подключенными по шине I^2C через CH431. Требует winusb драйвер.\n");
Console.WriteLine("Использование: RTCSync.cli.exe <параметр> [опции]\n");
Console.WriteLine("Параметры:");
foreach (var cmd in _registredOptions.Values.Distinct())
{
var optionNames = cmd.OptionNames.Aggregate((current, next) => current + ", " + next);
var optionLine = $" {optionNames} {cmd.OptionValues}";
PrintOption(optionLine, cmd.Description);
// Console.WriteLine($" {cmd.OptionNames.Aggregate((current, next) => current + $" {cmd.OptionValues}, " + next + $" {cmd.OptionValues}"), -54} {cmd.Description}");
}
Console.WriteLine("\nПримеры:");
Console.WriteLine(" RTCSync.cli.exe --set-time");
Console.WriteLine(" RTCSync.cli.exe -s");
Console.WriteLine(" RTCSync.cli.exe --set-time 0");
Console.WriteLine(" RTCSync.cli.exe -s 0");
}
}
+25
View File
@@ -0,0 +1,25 @@
using RTCSync.Services;
using RTCSync.Utils;
namespace RTCSync.Options;
public class ReadStatusOption : IOption
{
public string Description =>
"Чтение регистров status, control и температуры с часов реального времени.";
public List<string> OptionNames => ["-i", "--read-status"];
public string OptionValues => "";
public void Execute(OptionArgs args)
{
// bind and init CH431
var device = DeviceDispatcher.SetUpDevice();
if (device == null)
return;
DeviceReaders.PrintControlValues(device);
DeviceReaders.PrintStatusValues(device);
DeviceReaders.PrintTemperatureValue(device);
}
}
+30
View File
@@ -0,0 +1,30 @@
using RTCSync.Models;
using RTCSync.Services;
using RTCSync.Utils;
namespace RTCSync.Options;
public class ReadTimeOption : IOption
{
public string Description =>
"Чтение времени с часов реального времени.";
public List<string> OptionNames => ["-r", "--read-time"];
public string OptionValues => "";
public void Execute(OptionArgs args)
{
// bind and init CH431
var device = DeviceDispatcher.SetUpDevice();
if (device == null)
return;
while (true)
{
DeviceReaders.PrintTimeRTC(device);
Thread.Sleep(500);
}
device.ReleaseInterface(0);
}
}
+98
View File
@@ -0,0 +1,98 @@
using System.Runtime.InteropServices.ComTypes;
using RTCSync.Models;
using RTCSync.Services;
using RTCSync.Utils;
using static RTCSync.Utils.BinaryUtils;
namespace RTCSync.Options;
public class SetTimeOption : IOption
{
public string Description =>
"Установка времени как в системе, на часах реального времени. Часы будут хранить локальное время (не UTC!). Можно сбросить время до минимального значения используя опцию \"0\".";
public List<string> OptionNames => ["-s", "--set-time"];
public string OptionValues => "[0]";
public void Execute(OptionArgs args)
{
// Console.WriteLine("Вы уверены, что хотите перенастроить время на модуле реального времени? (Д/н)\n");
// var isConfirmed = false;
// while (!isConfirmed)
// {
// var proof = Console.ReadLine();
// switch (proof)
// {
// case "Н" or "н" or "N" or "n":
// return;
// case "Д" or "д" or "Y" or "y":
// isConfirmed = true;
// break;
// }
// }
var device = DeviceDispatcher.SetUpDevice();
if (device == null)
return;
// ставим текущее время в RTC модуль. Записывается не с первого раза, поэтому хак
// важно каждый раз пытаться забить актуальное время
// TODO: убрать хак
if (args.OptionValues.TryGetValue("unnamed", out var value) && value is "0")
{
for (var i = 0; i < 3; i++)
{
var dt = DateTime.MinValue;
DeviceWriters.SetTime(device, dt);
}
}
else
{
for (var i = 0; i < 3; i++)
{
// FIXME: вызывает падение на windows 7
var dt = DateTime.Now;
DeviceWriters.SetTime(device, dt);
}
}
Console.WriteLine("Время установлено");
// читаем статус
var status = DeviceReaders.GetStatusRegister(device);
DeviceReaders.PrintStatusValues(status);
// Сбрасываем OSF (Oscillator Stop Flags) бит 7 регистра 0x0F
// Иначе часы будут считаться ненадёжными
Console.WriteLine("Сбрасываем OSF");
DeviceWriters.ResetOSF(device, status);
// заново выводим статус для визуального контроля
DeviceReaders.PrintStatusValues(device);
// читаем данные контроля
var control = DeviceReaders.GetControlRegister(device);
DeviceReaders.PrintControlValues(control);
// если осциллятор не включен, то включаем
if ((control[0] & 0x80) != 0)
{
Console.WriteLine("Включаем осциллятор");
DeviceWriters.EnableEOSC(device, control);
// заново выводим данные контроля для визуального контроля
DeviceReaders.PrintControlValues(device);
}
// все сделали - выводим время
while (true)
{
DeviceReaders.PrintTimeRTC(device);
Thread.Sleep(500);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using RTCSync.Services;
using RTCSync.Utils;
namespace RTCSync.Options;
public class SyncTimeOption : IOption
{
public string Description => "Синхронизирует системное время со временем модуля реального времени. Если часы хранят UTC время, то используйте опцию universal";
public List<string> OptionNames => ["-S", "--sync-time"];
public string OptionValues => "{local, l, universal, u}";
public void Execute(OptionArgs args)
{
var device = DeviceDispatcher.SetUpDevice();
if (device == null)
return;
Console.WriteLine("Время на модуле:");
DeviceReaders.PrintTimeRTC(device);
var timeModel = DeviceReaders.GetTimeRTC(device);
// время в формате понятном для kernel32
WindowsTimeUtils.SystemTime systime;
if (args.OptionValues.TryGetValue("unnamed", out var value) && value is "universal" or "u")
systime = new WindowsTimeUtils.SystemTime(timeModel.Time);
else if (value is "local" or "l")
systime = new WindowsTimeUtils.SystemTime(timeModel.Time.ToUniversalTime());
else
systime = new WindowsTimeUtils.SystemTime(timeModel.Time.ToUniversalTime());
Console.WriteLine(WindowsTimeUtils.SetSystemTime(ref systime)
? "Время установлено в системе"
: "Ошибка: нет прав администратора");
}
}
+14
View File
@@ -0,0 +1,14 @@
using RTCSync.Options;
internal class Program
{
static void Main(string[] args)
{
var dispatcher = new OptionDispatcher();
dispatcher.Register(new ReadTimeOption());
dispatcher.Register(new SetTimeOption());
dispatcher.Register(new SyncTimeOption());
dispatcher.Register(new ReadStatusOption());
dispatcher.Execute(args);
}
}
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latestmajor</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<RootNamespace>RTCSync</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LibUsbDotNet" Version="3.0.224" />
</ItemGroup>
</Project>
+33
View File
@@ -0,0 +1,33 @@
using LibUsbDotNet;
using LibUsbDotNet.LibUsb;
using RTCSync.Utils;
namespace RTCSync.Services;
public abstract class DeviceDispatcher
{
public static IUsbDevice? SetUpDevice()
{
var context = new UsbContext();
context.SetDebugLevel(LogLevel.Info); // уровень логгирования winusb
var list = context.List();
Console.WriteLine($"Всего USB устройств: {list.Count}");
foreach (var d in list)
Console.WriteLine($" VID={d.VendorId:X4} PID={d.ProductId:X4}");
var device = list.FirstOrDefault(d => d.VendorId == 0x1A86 && d.ProductId == 0x5512);
if (device == null)
{
Console.WriteLine("Не найдено");
return null;
}
Console.WriteLine("Найдено, открываем...");
device.Open();
device.ClaimInterface(0);
Console.WriteLine("Открыто успешно");
I2CUtils.CH341Init(device);
return device;
}
}
+96
View File
@@ -0,0 +1,96 @@
using LibUsbDotNet.LibUsb;
using RTCSync.Models;
using RTCSync.Utils;
namespace RTCSync.Services;
public class DeviceReaders
{
public static TimeModel GetTimeRTC(IUsbDevice device)
{
// Читаем все 7 байт времени начиная с регистра 0x00
var time = I2CUtils.ReadWithRetry(device, 0x68, 0x00, 8);
var timeModel = new TimeModel(time);
return timeModel;
}
public static void PrintTimeRTC(IUsbDevice device)
{
var timeModel = GetTimeRTC(device);
PrintTimeRTC(timeModel);
}
public static void PrintTimeRTC(TimeModel timeModel)
{
Console.WriteLine(timeModel.Time.ToString("HH:mm:ss dd.MM.yyyy"));
}
public static byte[] GetStatusRegister(IUsbDevice device)
{
return I2CUtils.ReadWithRetry(device, 0x68, 0x0F, 1);
}
public static void PrintStatusValues(IUsbDevice device)
{
var status = GetStatusRegister(device);
PrintStatusValues(status);
}
public static void PrintStatusValues(byte[] status)
{
var statusBits = string.Concat(status.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')));
Console.WriteLine("Status Register (0Fh):");
Console.WriteLine("| OSF | 0 | 0 | 0 | EN32kHZ | BSY | A2F | A1F |");
Console.WriteLine($"| {statusBits[0], -3} | {statusBits[1]} | {statusBits[2]} | {statusBits[3]} | {statusBits[4], -7} | {statusBits[5], -3} | {statusBits[6], -3} | {statusBits[7], -3} |");
Console.WriteLine();
}
public static byte[] GetControlRegister(IUsbDevice device)
{
return I2CUtils.ReadWithRetry(device, 0x68, 0x0E, 1);
}
public static void PrintControlValues(IUsbDevice device)
{
var control= GetControlRegister(device);
PrintControlValues(control);
}
public static void PrintControlValues(byte[] control)
{
var controlBits = string.Concat(control.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')));
Console.WriteLine("Control Register (0Eh):");
Console.WriteLine("| EOSC | BBSQW | CONV | RS2 | RS1 | INTCN | A2IE | A1IE |");
Console.WriteLine($"| {controlBits[0], -4} | {controlBits[1], -5} | {controlBits[2], -4} | {controlBits[3], -3} | {controlBits[4], -3} | {controlBits[5], -5} | {controlBits[6], -4} | {controlBits[7], -4} |");
Console.WriteLine();
}
public static byte[] GetTemperatureRegisters(IUsbDevice device)
{
return I2CUtils.ReadWithRetry(device, 0x68, 0x11, 2);
}
public static void PrintTemperatureValue(IUsbDevice device)
{
var temp = GetTemperatureRegisters(device);
PrintTemperatureValue(temp);
}
public static void PrintTemperatureValue(byte[] temp)
{
float tempValue = temp[0];
if (temp[1] == 0x40)
tempValue += 0.25F;
else if (temp[1] == 0x80)
tempValue += 0.5F;
else if (temp[1] == 0xC0)
tempValue += 0.75F;
Console.WriteLine("Температура:");
Console.WriteLine(tempValue);
Console.WriteLine();
}
}
+47
View File
@@ -0,0 +1,47 @@
using LibUsbDotNet.LibUsb;
using RTCSync.Utils;
using static RTCSync.Utils.BinaryUtils;
namespace RTCSync.Services;
public class DeviceWriters
{
public static void SetTime(IUsbDevice device, DateTime dt)
{
var data = new byte[]
{
ToBcd(dt.Second), // 0x00 секунды
ToBcd(dt.Minute), // 0x01 минуты
ToBcd(dt.Hour), // 0x02 часы, бит6=0 → 24h
(byte)(dt.DayOfWeek + 1), // 0x03 день недели (в dt 0=вс, 1=пн..., а в DS3231 на 1 больше)
ToBcd(dt.Day), // 0x04 число
ToBcd(dt.Month), // 0x05 месяц
ToBcd(dt.Year % 100) // 0x06 год (последние 2 цифры)
};
I2CUtils.Write(device, 0x68, 0x00, data);
}
public static void ResetOSF(IUsbDevice device, byte[]? status)
{
status ??= DeviceReaders.GetStatusRegister(device);
status[0] &= 0x7F; // очистить бит 7
// TODO: убрать хак
I2CUtils.Write(device, 0x68, 0x0F, status);
I2CUtils.Write(device, 0x68, 0x0F, status);
I2CUtils.Write(device, 0x68, 0x0F, status);
}
public static void EnableEOSC(IUsbDevice device, byte[]? control)
{
control ??= DeviceReaders.GetControlRegister(device);
control[0] &= 0x7F; // EOSC = 0 (осциллятор включён) — бит 7 регистра 0x0E
// TODO: убрать хак
I2CUtils.Write(device, 0x68, 0x0E, control);
I2CUtils.Write(device, 0x68, 0x0E, control);
I2CUtils.Write(device, 0x68, 0x0E, control);
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace RTCSync.Utils;
public class BinaryUtils
{
public static byte ToBcd(int value) => (byte)(((value / 10) << 4) | (value % 10));
}
+67
View File
@@ -0,0 +1,67 @@
using System;
using System.Diagnostics;
namespace RTCSync.Utils;
public readonly struct BitIndex
{
private readonly ushort _byteIdx;
private readonly byte _startBitIdx0;
private readonly byte _endBitIdx0;
// порядок указывания байт в структуре
// сначала старший, потом младше
// 1<-------------------------------41
// порядок указывания бит в структуре
// сначала младший, потом старше
// 7<------0
// таким образом все биты вписываются по порядку справа налево
// BitIndex[new(3, 0..7), new(2, 0..7), new(1, 0..6)]
public BitIndex(int byteIdx1, Range bits)
{
_byteIdx = (ushort)(byteIdx1 - 1);
Debug.Assert(!bits.Start.IsFromEnd);
Debug.Assert(!bits.End.IsFromEnd);
_startBitIdx0 = (byte)bits.Start.Value;
_endBitIdx0 = (byte)bits.End.Value;
}
public int Length => _endBitIdx0 - _startBitIdx0 + 1;
public int Take(ReadOnlySpan<byte> bytes)
{
// example: Len=3, 0b00001000 - 1 = 0b00000111
var mask = (1U << (_endBitIdx0 - _startBitIdx0 + 1)) - 1U;
return (int)((bytes[_byteIdx] >> _startBitIdx0) & mask);
}
public static int Take(BitIndex[] bis, ReadOnlySpan<byte> bytes)
{
var shift = 0;
var result = 0;
foreach (var bi in bis)
{
result |= (bi.Take(bytes) << shift);
shift += bi.Length;
}
return result;
}
}
public static class BitIndexV2Extensions
{
public static int Take(this BitIndex[] bis, ReadOnlySpan<byte> bytes)
{
var shift = 0;
var result = 0;
foreach (var bi in bis)
{
result |= (bi.Take(bytes) << shift);
shift += bi.Length;
}
return result;
}
}
+134
View File
@@ -0,0 +1,134 @@
using LibUsbDotNet.LibUsb;
using LibUsbDotNet.Main;
namespace RTCSync.Utils;
public static class I2CUtils
{
private const int VID = 0x1A86;
private const int PID = 0x5512; // CH341T
// Команды CH341 для I2C
private const byte CH341_CMD_I2C_STREAM = 0xAA;
private const byte CH341_CMD_I2C_STM_STA = 0x74; // START
private const byte CH341_CMD_I2C_STM_STO = 0x75; // STOP
private const byte CH341_CMD_I2C_STM_OUT = 0x80; // write byte (len в младших битах)
private const byte CH341_CMD_I2C_STM_IN = 0xC0; // read byte
private const byte CH341_CMD_I2C_STM_END = 0x00; // конец стрима
public static void ScanDevice(IUsbDevice device)
{
var writer = device.OpenEndpointWriter(WriteEndpointID.Ep02);
var reader = device.OpenEndpointReader(ReadEndpointID.Ep02);
Console.WriteLine("Сканирование I2C шины (0x03 - 0x77)...");
for (byte addr = 0x03; addr < 0x78; addr++)
{
// Пробуем послать START + адрес + STOP
// Если устройство есть — придёт ACK, если нет — NACK
var cmd = new byte[]
{
CH341_CMD_I2C_STREAM, // CH341_CMD_I2C_STREAM
CH341_CMD_I2C_STM_STA, // START
CH341_CMD_I2C_STM_OUT | 1, // OUT, 1 байт
(byte)(addr << 1), // адрес + write bit
CH341_CMD_I2C_STM_STO, // STOP
CH341_CMD_I2C_STM_END // END
};
writer.Write(cmd, 500, out int written);
var buf = new byte[4];
reader.Read(buf, 200, out int read);
// CH341 возвращает статус: 0x00 = ACK получен (устройство есть)
// Первый байт ответа — статус последней I2C операции
if (read > 0 && buf[0] == 0x00)
{
Console.WriteLine($" Найдено устройство: 0x{addr:X2}");
}
}
Console.WriteLine("Сканирование завершено.");
}
public static void CH341Init(IUsbDevice device)
{
Console.WriteLine("Пытаемся инициализировать устройство");
var writer = device.OpenEndpointWriter(WriteEndpointID.Ep02);
// Установка скорости I2C: 0x00=20kHz, 0x01=100kHz, 0x02=400kHz, 0x03=750kHz
var cmd = new byte[]
{
CH341_CMD_I2C_STREAM,
0x60 | 0x01, // CH341_CMD_I2C_STM_SET | 100kHz
CH341_CMD_I2C_STM_END
};
writer.Write(cmd, 1000, out _);
}
public static byte[] Read(IUsbDevice device, byte address, byte reg, int length)
{
// Формируем пакет: START → write addr+reg → RESTART → read → STOP
var cmd = new byte[]
{
CH341_CMD_I2C_STREAM,
CH341_CMD_I2C_STM_STA,
CH341_CMD_I2C_STM_OUT | 2, // 2 байта: адрес + регистр
(byte)(address << 1), // write
reg,
CH341_CMD_I2C_STM_STA, // repeated START
CH341_CMD_I2C_STM_OUT | 1,
(byte)((address << 1) | 1), // read
(byte)(CH341_CMD_I2C_STM_IN | (length - 1)),
CH341_CMD_I2C_STM_STO,
CH341_CMD_I2C_STM_END
};
var writer = device.OpenEndpointWriter(WriteEndpointID.Ep02);
var reader = device.OpenEndpointReader(ReadEndpointID.Ep02);
writer.Write(cmd, 1000, out _);
var buf = new byte[length];
reader.Read(buf, 1000, out var transferred);
return buf;
}
public static byte[] ReadWithRetry(IUsbDevice device, byte address, byte reg, int length)
{
for (var attempt = 0; attempt < 100; attempt++)
{
var result = Read(device, address, reg, length);
if (result[0] != 0x03)
return result;
Thread.Sleep(10);
}
throw new Exception("Ошибка чтения CH431 после 100 попыток");
}
public static void Write(IUsbDevice device, byte address, byte reg, byte[] data)
{
// START → write addr → write reg → write data[0..n] → STOP
// CH341_CMD_I2C_STM_OUT | N означает "записать N байт"
// N = 1 (адрес) + 1 (регистр) + data.Length
var cmd = new byte[5 + data.Length + 2];
int i = 0;
cmd[i++] = CH341_CMD_I2C_STREAM;
cmd[i++] = CH341_CMD_I2C_STM_STA;
cmd[i++] = (byte)(CH341_CMD_I2C_STM_OUT | (2 + data.Length)); // адрес + регистр + данные
cmd[i++] = (byte)(address << 1); // write bit = 0
cmd[i++] = reg;
foreach (var b in data)
cmd[i++] = b;
cmd[i++] = CH341_CMD_I2C_STM_STO;
cmd[i] = CH341_CMD_I2C_STM_END;
var writer = device.OpenEndpointWriter(WriteEndpointID.Ep02);
writer.Write(cmd, 1000, out _);
}
}
+26
View File
@@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
namespace RTCSync.Utils;
public abstract class WindowsTimeUtils
{
// c# datetime set
[DllImport("kernel32.dll")]
public static extern bool SetSystemTime(ref SystemTime time);
[DllImport("kernel32.dll")]
public static extern void GetSystemTime(ref SystemTime time);
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime(DateTime dt)
{
public ushort Year = (ushort)dt.Year;
public ushort Month = (ushort)dt.Month;
public ushort DayOfWeek = (ushort)dt.DayOfWeek;
public ushort Day = (ushort)dt.Day;
public ushort Hour = (ushort)dt.Hour;
public ushort Minute = (ushort)dt.Minute;
public ushort Second = (ushort)dt.Second;
public ushort Milliseconds = (ushort)dt.Millisecond;
}
}