forked from liuwenbo/simulation_Peripheral
时间RTC外设仿真源码
This commit is contained in:
340
Custom_RTC_R17V1_CORRECT_ADDR.cs
Normal file
340
Custom_RTC_R17V1_CORRECT_ADDR.cs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user