diff --git a/Custom_RTC_R17V1_CORRECT_ADDR.cs b/Custom_RTC_R17V1_CORRECT_ADDR.cs new file mode 100644 index 0000000..7f98bfa --- /dev/null +++ b/Custom_RTC_R17V1_CORRECT_ADDR.cs @@ -0,0 +1,340 @@ +//============================================================================ +// 自定义实时时钟外设 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) + } + } +}