时间RTC外设仿真源码

This commit is contained in:
liuwb
2026-02-08 21:57:19 +08:00
parent f3d160ee01
commit 24abcbc8bf

View File

@@ -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<<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
}
}
}