Files
simulation_Peripheral/Custom_RTC_R17V1_CORRECT_ADDR.cs
2026-02-08 21:57:19 +08:00

341 lines
16 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.
//============================================================================
// 自定义实时时钟外设 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<<offset, value)
// 其中 offset=8因此实际地址计算为REG << 8左移 8 位)
//
// 寄存器地址表:
// ┌─────────────┬──────────────┬─────────────┬─────────────┐
// │ 寄存器名 │ REG 值 │ 偏移计算 │ 实际地址 │
// ├─────────────┼──────────────┼─────────────┼─────────────┤
// │ CMD │ 0x01 │ 0x01 << 8 │ 0x20202100 │
// │ US_LOW │ 0x02 │ 0x02 << 8 │ 0x20202200 │
// │ US_HIGH │ 0x03 │ 0x03 << 8 │ 0x20202300 │
// │ SEC_LOW │ 0x04 │ 0x04 << 8 │ 0x20202400 │
// │ SEC_HIGH │ 0x05 │ 0x05 << 8 │ 0x20202500 │
// │ STATUS │ 0x09 │ 0x09 << 8 │ 0x20202900 │
// └─────────────┴──────────────┴─────────────┴─────────────┘
//
// 使用流程:
// 1. 软件写 0xBB 到 CMD 寄存器0x20202100锁定当前时间
// 2. 读取 US_LOW 和 US_HIGH 获得完整的微秒时间戳
// 3. 读取 SEC_LOW 和 SEC_HIGH 获得完整的秒时间戳
// 4. 锁定期间,时间值保持不变,确保读取的一致性
// 5. 重要:读取 SEC_HIGH 后,硬件自动解锁(无需软件发送解锁命令)
//
// 作者liuwenbo
// 版本R17V1 (修正地址版本)
//============================================================================
using System;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Time;
using Antmicro.Renode.Peripherals;
namespace Antmicro.Renode.Peripherals.Timers
{
/// <summary>
/// 自定义实时时钟外设 - 使用左移寻址的 RTC 模块
/// </summary>
public class Custom_RTC_R17V1 : BasicDoubleWordPeripheral, IKnownSize
{
/// <summary>
/// 构造函数 - 初始化 RTC 外设
/// </summary>
/// <param name="machine">Renode 虚拟机实例</param>
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);
}
/// <summary>
/// 复位 RTC - 重置所有计数器和状态
/// </summary>
public override void Reset()
{
base.Reset();
// 注意:不重置 startTime保留构造函数中设置的对齐时间
// 如果需要完全重置,请在外部重新创建外设实例
baseOffsetUs = 0; // 清零微秒偏移
baseOffsetSec = 0; // 清零秒偏移
isTimeLocked = false; // 解除时间锁定
lockedUs = 0; // 清零锁定的微秒值
lockedSec = 0; // 清零锁定的秒值
}
/// <summary>
/// 外设内存大小 - 需要足够大以覆盖 0x500 偏移
/// </summary>
public long Size => 0x1000; // 4KB确保覆盖所有寄存器地址
/// <summary>
/// 定义寄存器映射 - 配置所有 RTC 寄存器的行为
/// </summary>
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);
}
//====================================================================
// 时间锁定功能
//====================================================================
/// <summary>
/// 锁定当前时间 - 将当前时间保存到锁定变量中
///
/// 功能说明:
/// 锁定后,读取寄存器将返回锁定时刻的时间,确保多次读取的一致性。
/// 这对于读取 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() 方法(复位时)
/// </summary>
private void LockTime()
{
lockedUs = GetCurrentMicroseconds();
lockedSec = GetCurrentSeconds();
isTimeLocked = true;
}
//====================================================================
// 寄存器读取辅助函数
//====================================================================
/// <summary>
/// 获取微秒时间戳的低 16 位(返回 32 位寄存器值,数据在低 16 位)
/// </summary>
private uint GetMicrosecondsLow()
{
ulong us = isTimeLocked ? lockedUs : GetCurrentMicroseconds();
// 微秒范围0-999,999最大需要 20 位
// 返回低 16 位bit 0-15
return (uint)(us & 0xFFFF);
}
/// <summary>
/// 获取微秒时间戳的高位(返回 32 位寄存器值)
/// 硬件文档标注为 4 位有效,但软件使用 16 位掩码读取
/// 实际微秒值 0-999,999 只需要 20 位bit 16-19 共 4 位
/// </summary>
private uint GetMicrosecondsHigh()
{
ulong us = isTimeLocked ? lockedUs : GetCurrentMicroseconds();
// 取 bit 16-19微秒值最大 999,999 = 0xF423F需要 20 位)
// 软件用 & 0xFFFF 读取,所以返回值高 12 位会是 0
return (uint)((us >> 16) & 0xF); // 只返回 4 位有效数据
}
/// <summary>
/// 获取秒时间戳的低 16 位
/// </summary>
private uint GetSecondsLow()
{
ulong sec = isTimeLocked ? lockedSec : GetCurrentSeconds();
return (uint)(sec & 0xFFFF); // 取低 16 位
}
/// <summary>
/// 获取秒时间戳的高 16 位
/// </summary>
private uint GetSecondsHigh()
{
ulong sec = isTimeLocked ? lockedSec : GetCurrentSeconds();
return (uint)((sec >> 16) & 0xFFFF); // 取中间 16 位bit 16-31
}
//====================================================================
// 时间计算函数
//====================================================================
/// <summary>
/// 获取当前微秒时间戳 - 基于系统真实时间
/// </summary>
/// <returns>当前秒内的微秒部分0-999,999</returns>
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;
}
/// <summary>
/// 获取当前秒时间戳 - 基于系统真实时间
/// </summary>
/// <returns>从启动时刻到现在经过的秒数</returns>
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; // 锁定的秒时间戳
/// <summary>
/// 寄存器枚举 - 定义各寄存器的偏移地址
/// 注意:这些是相对于基地址 0x20202000 的偏移
/// </summary>
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
}
}
}