//============================================================================ // 自定义实时时钟外设 R17V1 (Custom_RTC_R17V1) //============================================================================ // 功能说明: // 为卫星软件提供高精度实时时钟(RTC),支持微秒级和秒级时间读取 // // 关键特性: // - 使用左移寻址方式(REG << 8)而非乘法寻址 // - 支持时间锁定功能(写入 0xBB 到 CMD 寄存器) // - 微秒寄存器:20 位(US_LOW 16位 + US_HIGH 4位),范围 0-999,999 // - 秒寄存器:32 位(SEC_LOW 16位 + SEC_HIGH 16位),范围 0-4,294,967,295 // - 基于系统真实时间(DateTime.Now) // // 地址映射(重要!): // 驱动代码使用:WRITEREG_32(base, REG< /// 自定义实时时钟外设 - 使用左移寻址的 RTC 模块 /// public class Custom_RTC_R17V1 : BasicDoubleWordPeripheral, IKnownSize { /// /// 构造函数 - 初始化 RTC 外设 /// /// Renode 虚拟机实例 public Custom_RTC_R17V1(IMachine machine) : base(machine) { // 【关键修复】先设置启动时间对齐到整秒,避免定时器同步问题 // 软件期望在整秒时刻(run_count % 4 == 0)微秒值接近 0 或接近 1,000,000 var now = DateTime.Now; // 向下取整到最近的整秒 startTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, DateTimeKind.Local); DefineRegisters(); Reset(); // Reset() 会再次设置 startTime,但我们在 Reset() 中保留构造函数设置的值 this.Log(LogLevel.Warning, "RTC 实时时钟已初始化,基地址:0x20202000"); this.Log(LogLevel.Warning, " 启动时间已对齐到整秒:{0}", startTime.ToString("HH:mm:ss.fff")); this.Log(LogLevel.Warning, " 使用左移寻址模式:CMD=0x{0:X}, US_LOW=0x{1:X}, US_HIGH=0x{2:X}", 0x100, 0x200, 0x300); } /// /// 复位 RTC - 重置所有计数器和状态 /// public override void Reset() { base.Reset(); // 注意:不重置 startTime,保留构造函数中设置的对齐时间 // 如果需要完全重置,请在外部重新创建外设实例 baseOffsetUs = 0; // 清零微秒偏移 baseOffsetSec = 0; // 清零秒偏移 isTimeLocked = false; // 解除时间锁定 lockedUs = 0; // 清零锁定的微秒值 lockedSec = 0; // 清零锁定的秒值 } /// /// 外设内存大小 - 需要足够大以覆盖 0x500 偏移 /// public long Size => 0x1000; // 4KB,确保覆盖所有寄存器地址 /// /// 定义寄存器映射 - 配置所有 RTC 寄存器的行为 /// private void DefineRegisters() { // ================================================================ // 注意:实际地址使用左移计算(REG << 8)而非乘法(REG * 8) // ================================================================ // ---- CMD 命令寄存器(偏移 0x100)---- // 地址计算:0x01 << 8 = 0x100 → 实际地址 0x20202100 // 支持的命令: // 0xBB - 锁定时间(保存当前时间快照到锁定变量) // // 注意:匹配 kx11 硬件行为,仅支持锁定,无显式解锁命令 // 锁定后时间保持不变,直到下次读取或复位 Registers.Cmd.Define(this) .WithValueField(0, 32, FieldMode.Write, name: "cmd", writeCallback: (_, val) => { this.Log(LogLevel.Warning, "CMD 写入:0x{0:X}(偏移 0x100)", val); if(val == 0xBB) // 魔数 0xBB 表示锁定时间 { LockTime(); this.Log(LogLevel.Warning, "时间已锁定:微秒=0x{0:X}, 秒=0x{1:X}", lockedUs, lockedSec); } else { // 其他命令值仅记录,不执行操作 // kx11 硬件中 0xAA 用于均值校时后向,此处暂不实现 this.Log(LogLevel.Debug, "未实现的命令:0x{0:X}", val); } }); // ---- US_LOW 微秒低 16 位寄存器(偏移 0x200)---- // 地址计算:0x02 << 8 = 0x200 → 实际地址 0x20202200 Registers.UsLow.Define(this) .WithValueField(0, 32, FieldMode.Read, name: "us_low", valueProviderCallback: _ => { var val = GetMicrosecondsLow(); this.Log(LogLevel.Warning, "US_LOW 读取(偏移 0x200):返回 0x{0:X8}", val); return val; }); // ---- US_HIGH 微秒高 16 位寄存器(偏移 0x300)---- // 地址计算:0x03 << 8 = 0x300 → 实际地址 0x20202300 Registers.UsHigh.Define(this) .WithValueField(0, 32, FieldMode.Read, name: "us_high", valueProviderCallback: _ => { var val = GetMicrosecondsHigh(); this.Log(LogLevel.Warning, "US_HIGH 读取(偏移 0x300):返回 0x{0:X8}", val); return val; }); // ---- SEC_LOW 秒低 16 位寄存器(偏移 0x400)---- // 地址计算:0x04 << 8 = 0x400 → 实际地址 0x20202400 Registers.SecLow.Define(this) .WithValueField(0, 32, FieldMode.Read, name: "sec_low", valueProviderCallback: _ => { var val = GetSecondsLow(); this.Log(LogLevel.Warning, "SEC_LOW 读取(偏移 0x400):返回 0x{0:X8}", val); return val; }); // ---- SEC_HIGH 秒高 16 位寄存器(偏移 0x500)---- // 地址计算:0x05 << 8 = 0x500 → 实际地址 0x20202500 // 重要:读取此寄存器后自动解锁(模拟硬件行为) Registers.SecHigh.Define(this) .WithValueField(0, 32, FieldMode.Read, name: "sec_high", valueProviderCallback: _ => { var val = GetSecondsHigh(); this.Log(LogLevel.Warning, "SEC_HIGH 读取(偏移 0x500):返回 0x{0:X8}", val); // 模拟硬件行为:读取完最后一个时间寄存器后自动解锁 if(isTimeLocked) { isTimeLocked = false; this.Log(LogLevel.Warning, " 硬件自动解锁(读取 SEC_HIGH 后)"); } return val; }); // ---- STATUS 状态寄存器(偏移 0x900)---- // 地址计算:0x09 << 8 = 0x900 → 实际地址 0x20202900 // 当前实现:始终返回 0(保留功能) Registers.Status.Define(this) .WithValueField(0, 32, FieldMode.Read, name: "status", valueProviderCallback: _ => 0); } //==================================================================== // 时间锁定功能 //==================================================================== /// /// 锁定当前时间 - 将当前时间保存到锁定变量中 /// /// 功能说明: /// 锁定后,读取寄存器将返回锁定时刻的时间,确保多次读取的一致性。 /// 这对于读取 64 位时间戳(需要分 4 次读取 16 位寄存器)至关重要。 /// /// 硬件行为(通过分析 kx11 代码推断): /// 1. 软件写 0xBB 到 CMD 寄存器 → 硬件锁定时间 /// 2. 软件读取 4 个时间寄存器(US_LOW, US_HIGH, SEC_LOW, SEC_HIGH) /// 3. **硬件自动解锁**:读取完 SEC_HIGH 寄存器后自动清除锁定状态 /// 4. kx11 软件代码中没有显式解锁命令,证明硬件自动解锁 /// /// 解锁方式: /// - 自动解锁:读取 SEC_HIGH 寄存器后(正常流程) /// - 手动解锁:调用 Reset() 方法(复位时) /// private void LockTime() { lockedUs = GetCurrentMicroseconds(); lockedSec = GetCurrentSeconds(); isTimeLocked = true; } //==================================================================== // 寄存器读取辅助函数 //==================================================================== /// /// 获取微秒时间戳的低 16 位(返回 32 位寄存器值,数据在低 16 位) /// private uint GetMicrosecondsLow() { ulong us = isTimeLocked ? lockedUs : GetCurrentMicroseconds(); // 微秒范围:0-999,999,最大需要 20 位 // 返回低 16 位(bit 0-15) return (uint)(us & 0xFFFF); } /// /// 获取微秒时间戳的高位(返回 32 位寄存器值) /// 硬件文档标注为 4 位有效,但软件使用 16 位掩码读取 /// 实际微秒值 0-999,999 只需要 20 位,bit 16-19 共 4 位 /// private uint GetMicrosecondsHigh() { ulong us = isTimeLocked ? lockedUs : GetCurrentMicroseconds(); // 取 bit 16-19(微秒值最大 999,999 = 0xF423F,需要 20 位) // 软件用 & 0xFFFF 读取,所以返回值高 12 位会是 0 return (uint)((us >> 16) & 0xF); // 只返回 4 位有效数据 } /// /// 获取秒时间戳的低 16 位 /// private uint GetSecondsLow() { ulong sec = isTimeLocked ? lockedSec : GetCurrentSeconds(); return (uint)(sec & 0xFFFF); // 取低 16 位 } /// /// 获取秒时间戳的高 16 位 /// private uint GetSecondsHigh() { ulong sec = isTimeLocked ? lockedSec : GetCurrentSeconds(); return (uint)((sec >> 16) & 0xFFFF); // 取中间 16 位(bit 16-31) } //==================================================================== // 时间计算函数 //==================================================================== /// /// 获取当前微秒时间戳 - 基于系统真实时间 /// /// 当前秒内的微秒部分(0-999,999) private ulong GetCurrentMicroseconds() { var elapsed = DateTime.Now - startTime; // 安全处理:确保 elapsed 不是负数(防止系统时间回退) if (elapsed.TotalSeconds < 0) { this.Log(LogLevel.Error, "⚠️ 检测到系统时间回退!重置 startTime"); startTime = DateTime.Now; elapsed = TimeSpan.Zero; } // 计算总秒数(包括偏移) double totalSeconds = elapsed.TotalSeconds + (double)baseOffsetSec; // 提取秒的小数部分(0.0 - 0.999999...) double fractionalSeconds = totalSeconds - Math.Floor(totalSeconds); // 转换为微秒(0 - 999,999) ulong microseconds = (ulong)(fractionalSeconds * 1_000_000.0); // 确保不超过 999,999(防止浮点精度问题) if (microseconds >= 1_000_000) { microseconds = 999_999; } return microseconds; } /// /// 获取当前秒时间戳 - 基于系统真实时间 /// /// 从启动时刻到现在经过的秒数 private ulong GetCurrentSeconds() { var elapsed = DateTime.Now - startTime; // 安全处理:确保 elapsed 不是负数(防止系统时间回退) if (elapsed.TotalSeconds < 0) { return baseOffsetSec; // 返回偏移值,避免负数转换错误 } return baseOffsetSec + (ulong)elapsed.TotalSeconds; } //==================================================================== // 私有成员变量 //==================================================================== private DateTime startTime; // RTC 启动时的系统时间 private ulong baseOffsetUs; // 微秒时间偏移(用于调整初始值) private ulong baseOffsetSec; // 秒时间偏移(用于调整初始值) private bool isTimeLocked; // 时间锁定标志 private ulong lockedUs; // 锁定的微秒时间戳 private ulong lockedSec; // 锁定的秒时间戳 /// /// 寄存器枚举 - 定义各寄存器的偏移地址 /// 注意:这些是相对于基地址 0x20202000 的偏移 /// private enum Registers { Cmd = 0x100, // 命令寄存器(0x01 << 8) UsLow = 0x200, // 微秒低位(0x02 << 8) UsHigh = 0x300, // 微秒高位(0x03 << 8) SecLow = 0x400, // 秒低位(0x04 << 8) SecHigh = 0x500, // 秒高位(0x05 << 8) Status = 0x900, // 状态寄存器(0x09 << 8) } } }