CH55xduino CH554 DataFlash 使用的例子

CH554内置了128字节的 DataFlash ,掉电不会丢失,方便写入

这里展示了如何在 Ch55xDuino 环境下使用 DataFlash。

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include "src/CdcHidCombo/USBCDC.h" 
#include "DataFlash.H"

uint8_t lastValue;
      
void setup() {
  USBInit();
  // 读取
  Flash_Op_Check_Byte1 = 0x00;
  Flash_Op_Check_Byte2 = 0x00;
  ReadDataFlash(0,1,&lastValue);
  lastValue++;
  
  // 写入
  Flash_Op_Check_Byte1 = DEF_FLASH_OP_CHECK1;
  Flash_Op_Check_Byte2 = DEF_FLASH_OP_CHECK2;
  WriteDataFlash(0,&lastValue,1);

}

void loop() {
  USBSerial_println(lastValue-1);
  delay(3000);
}

实现的效果是:插入Ch554 ,打开 USB串口可以看到输出的数字。拔掉之后再次插入,输出的是前一次加一的数字。

FANGXIANG SSD 太垃圾

做测试借了一个崭新的,没开封PCIE5 2T 的NVME, 品牌是 Fanxiang。发现无法在 PCIE 5 上写入数据(读取可以)。但是在 PCIE 4 Port上面可以读写。猜测这是一个“假的PCIE 5 NVME”。

很多年前还被 Foresee 这个牌子的 M.2 SSD 坑过,它不支持 DEVSLP ,然后我用它测试 ModernStandby 一直搞不定。

Web Serial 的例子

一个简单的 Web Serial 的例子,页面上有两个按钮,一个是选择串口号,另外一个是连接串口之后按下会发送”Hello World!” 到这个串口。

<!DOCTYPE html>
<html>
<body>
    <button id="connectBtn">连接串口</button>
    <button id="sendBtn" disabled>发送 hello world</button>
</body>
<script>
    let port;  // 存储串口实例

    // 连接串口按钮事件
    document.getElementById('connectBtn').addEventListener('click', async () => {
        try {
            port = await navigator.serial.requestPort();
            await port.open({ baudRate: 9600 });  // 设置波特率
            document.getElementById('sendBtn').disabled = false;
        } catch (error) {
            console.error("端口打开失败:", error);
        }
    });

    // 发送按钮事件
    document.getElementById('sendBtn').addEventListener('click', async () => {
        if (!port?.writable) return;
        
        const writer = port.writable.getWriter();
        const encoder = new TextEncoder();
        
        try {
            await writer.write(encoder.encode('hello world!\r\n'));  // 发送数据
        } finally {
            writer.releaseLock();  // 释放写入器
        }
    });
</script>
</html>

ASCII 数字的例子

之前的文章【参考1】,介绍了 ASCII 的字体,这次用这个实现在 Console 下显示数值。使用 VS2019 VC 编译通过。

运行结果如下:

测试代码如下:

// AsciiFontTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <windows.h>  // 必须包含头文件
#include <iostream>

// 字体宽度
#define DOHWIDTH  20
#define DOHHEIGHT 16

// 定义二维数组,每行预留足够空间
char DOH[10][DOHHEIGHT*10][DOHWIDTH+1] = {
// char "0"
{
"     000000000      ",
"   00:::::::::00    ",
" 00:::::::::::::00  ",
"0:::::::000:::::::0 ",
"0::::::0   0::::::0 ",
"0:::::0     0:::::0 ",
"0:::::0     0:::::0 ",
"0:::::0 000 0:::::0 ",
"0:::::0 000 0:::::0 ",
"0:::::0     0:::::0 ",
"0:::::0     0:::::0 ",
"0::::::0   0::::::0 ",
"0:::::::000:::::::0 ",
" 00:::::::::::::00  ",
"   00:::::::::00    ",
"     000000000      "
},
// char "1"
{
"       1111111      ",
"      1::::::1      ",
"     1:::::::1      ",
"     111:::::1      ",
"        1::::1      ",
"        1::::1      ",
"        1::::1      ",
"        1::::l      ",
"        1::::l      ",
"        1::::l      ",
"        1::::l      ",
"        1::::l      ",
"     111::::::111   ",
"     1::::::::::1   ",
"     1::::::::::1   ",
"     111111111111   "
},
// char "2"
{
" 222222222222222    ",
"2:::::::::::::::22  ",
"2::::::222222:::::2 ",
"2222222     2:::::2 ",
"            2:::::2 ",
"            2:::::2 ",
"         2222::::2  ",
"    22222::::::22   ",
"  22::::::::222     ",
" 2:::::22222        ",
"2:::::2             ",
"2:::::2             ",
"2:::::2       222222",
"2::::::2222222:::::2",
"2::::::::::::::::::2",
"22222222222222222222",
},
// char "3"
{
" 333333333333333    ",
"3:::::::::::::::33  ",
"3::::::33333::::::3 ",
"3333333     3:::::3 ",
"            3:::::3 ",
"            3:::::3 ",
"    33333333:::::3  ",
"    3:::::::::::3   ",
"    33333333:::::3  ",
"            3:::::3 ",
"            3:::::3 ",
"            3:::::3 ",
"3333333     3:::::3 ",
"3::::::33333::::::3 ",
"3:::::::::::::::33  ",
" 333333333333333    ",
},
// char "4"
{
"        444444444   ",
"       4::::::::4   ",
"      4:::::::::4   ",
"     4::::44::::4   ",
"    4::::4 4::::4   ",
"   4::::4  4::::4   ",
"  4::::4   4::::4   ",
" 4::::444444::::444 ",
" 4::::::::::::::::4 ",
" 4444444444:::::444 ",
"           4::::4   ",
"           4::::4   ",
"           4::::4   ",
"         44::::::44 ",
"         4::::::::4 ",
"         4444444444 ",
},
// char "5"
{
" 555555555555555555 ",
" 5::::::::::::::::5 ",
" 5::::::::::::::::5 ",
" 5:::::555555555555 ",
" 5:::::5            ",
" 5:::::5            ",
" 5:::::5555555555   ",
" 5:::::::::::::::5  ",
" 555555555555:::::5 ",
"             5:::::5",
"             5:::::5",
" 5555555     5:::::5",
" 5::::::55555::::::5",
"  55:::::::::::::55 ",
"    55:::::::::55   ",
"      555555555     ",
},
// char "6"
{
"         66666666   ",
"        6::::::6    ",
"       6::::::6     ",
"      6::::::6      ",
"     6::::::6       ",
"    6::::::6        ",
"   6::::::6         ",
"  6::::::::66666    ",
" 6::::::::::::::66  ",
" 6::::::66666:::::6 ",
" 6:::::6     6:::::6",
" 6:::::6     6:::::6",
" 6::::::66666::::::6",
"  66:::::::::::::66 ",
"    66:::::::::66   ",
"      666666666     ",
},
// char "7"
{
"77777777777777777777",
"7::::::::::::::::::7",
"7::::::::::::::::::7",
"777777777777:::::::7",
"           7::::::7 ",
"          7::::::7  ",
"         7::::::7   ",
"        7::::::7    ",
"       7::::::7     ",
"      7::::::7      ",
"     7::::::7       ",
"    7::::::7        ",
"   7::::::7         ",
"  7::::::7          ",
" 7::::::7           ",
"77777777            ",
},
// char "8"
{
"      888888888     ",
"    88:::::::::88   ",
"  88:::::::::::::88 ",
" 8::::::88888::::::8",
" 8:::::8     8:::::8",
" 8:::::8     8:::::8",
"  8:::::88888:::::8 ",
"   8:::::::::::::8  ",
"  8:::::88888:::::8 ",
" 8:::::8     8:::::8",
" 8:::::8     8:::::8",
" 8:::::8     8:::::8",
" 8::::::88888::::::8",
"  88:::::::::::::88 ",
"    88:::::::::88   ",
"      888888888     "
},
// char "9"
{
"      999999999     ",
"    99:::::::::99   ",
"  99:::::::::::::99 ",
" 9::::::99999::::::9",
" 9:::::9     9:::::9",
" 9:::::9     9:::::9",
"  9:::::99999::::::9",
"   99::::::::::::::9",
"     99999::::::::9 ",
"          9::::::9  ",
"         9::::::9   ",
"        9::::::9    ",
"       9::::::9     ",
"      9::::::9      ",
"     9::::::9       ",
"    99999999        "
}
};

// 计算 n 的位数
// 比如: 123 返回2, 1 返回0
int count_digits(int n) {
    // 处理特殊情况:0的位数为1
    if (n == 0) return 0;  // [^1]

    int count = 0;
    n = abs(n);  // 处理负数

    while (n != 0) {
        n /= 10;  // 每次循环移除最后一位
        count++;
    }
    return count-1;
}

/**
 * @brief 获取整数指定位的数字
 * @param v 目标整数(支持负数)
 * @param index 位索引(0表示个位,1表示十位,依此类推)
 * @return int 对应位的数字(0-9)
 *
 * @example
 * GetValueOf(1234, 0) -> 4
 * GetValueOf(-987, 2) -> 9
 */
int GetValueOf(int v, int index) {
    if (index < 0) return 0;  // 非法索引处理
    v = abs(v);               // 处理负数情况[^1]

    long long divisor = 1;    // 使用long long防止溢出
    for (int i = 0; i < index; ++i) {
        divisor *= 10;
        if (divisor > INT_MAX) return 0; // 大索引保护机制[^2]
    }

    return (v / divisor) % 10;
}

char* Int2Ascii(int value) {
    // 计算最终的宽度
    int StrWitdh = (count_digits(value)+1) * DOHWIDTH + 1; // 最后加入回车换行字符

    char *str =(char*) malloc(StrWitdh * DOHHEIGHT + 1); //末尾使用 \0 作为标记
    if (str == NULL) {
        return NULL;
    } else  memset(str, '*', StrWitdh * DOHHEIGHT + 1);
    str[StrWitdh * DOHHEIGHT + 1 - 1] = NULL;

    // 赋值换行
    for (int i = 1; i < DOHHEIGHT+1; i++) {
        str[i * StrWitdh - 1] = '\n';
    }

    // 从 Value 的最高位开始搬移
    for (int i = 0; i<count_digits(value)+1; i++)
    {
        printf("GetValueOf[%d,%d]=%d\n", value, count_digits(value) - i, GetValueOf(value,count_digits(value)-i));
        for (int j = 0; j < DOHHEIGHT; j++) {
            memcpy(&str[j* StrWitdh+ DOHWIDTH*i], &DOH[GetValueOf(value, count_digits(value)-i)][j][0], DOHWIDTH);
            //printf("copy DOH[%d][%d][0] > str[%d]\n", GetValueOf(value, count_digits(value) - i), j, j * StrWitdh + DOHWIDTH * i);
           
        }
    }


    
    return str;
}
int main()
{
    for (int i = 0; i < 100; i++) {
        char* p = Int2Ascii(i);
        printf("%s\r\n", p);
        free(p);
        Sleep(500);
    }        
    
}

参考:

1.https://www.lab-z.com/asciiart/

Step to memory 006 DDR外形和自刷新

继续之前的话题,这里来到了《内存的秘密 第二部分》

https://www.bit-tech.net/reviews/tech/memory/the_secrets_of_pc_memory_part_2/1

第一章 紧凑型 DDR

SO-DIMM(小外形双列直插式内存模块,small outline dual in-line memory module)是最常见的移动内存类型:其尺寸几乎是标准 DIMM 的一半,并且不同代之间的连接引脚数量也有所不同。它最常用于笔记本电脑、嵌入式系统、打印机和高端网络设备。除此之外紧凑型和移动型 DDR 还有其他外形尺寸,包括 Mini DIMM 和 Micro DIMM,其区别如下表所示。

引脚数量

尺寸

移动 DDR(例如采用 SO-DIMM 外形尺寸的 DDR)具有一些动态功率调节功能,可以降低自身功耗降尽可能的延长待机时间。这些功能包括部分阵列自刷新 (Partial Array Self-Refresh ,PASR)、温度补偿自刷新 (Temperature Compensated Self-Refresh,TCSR) 和深度省电 (Deep Power Down,DPD)。移动内存设计人员需要努力平衡功耗与性能关系。

个别 DRAM 制造商可能也拥有专有的节能技术,例如,Elpida Memory 就拥有一项名为超级自刷新 (Super Self-Refresh,SSR) 的技术,可将 DDR1 自刷新电流降低 95%。【注释:SSR 是一种新型电路技术,它与嵌入式纠错电路 (ECC) 协同工作,显著延长了内部刷新间隔。ECC 会在退出自刷新周期时检查并纠正数据。SSR 功能取代了传统 SDRAM 中常见的自刷新功能。SSR 利用片上温度传感器(通常称为自动温度补偿自刷新 (ATCSR)),自动调整自刷新时间,以补偿内部温度变化。这是 2005年的技术。】
为了理解移动内存功耗的本质,我们只需使用如下基本公式:

总功率=核心功率+IO功率

DDR GDDR和 移动DDR 内存功耗比较

需要注意的是:DIMM和SO-DIMM使用的DDR芯片通常都是相同的。此外,还有一种 LPDDR (Low Power Double Data Rate SDRAM)的内存芯片,这种和 DDR 芯片差异较大需要特别注意。

DRAM自刷新技术

即使在没有读取数据时,内存(RAM) 也需要持续进行刷新以保持电容器中的电荷。因为DRAM使用电容器来记录数据,其中的电荷会随着时间的推移逐渐泄漏。为了确保数据的可靠必须进行刷新操作,这个操作简单的说就是对于电容再次充电。

这是 RAM (易失性存储器)与非易失性存储器(例如,只读存储器 ROM 或闪存NAND 或 NOR)的主要区别。在笔记本电脑和手持设备等移动设备中,这种自刷新特性不断改进,努力降低功耗,从而延长设备的待机时间。

以下章节介绍了移动 DDR 使用的四种不同的节能方法。

  1. 温度补偿自刷新
  2. 部分阵列自刷新
  3. 深度断电
  4. 时钟停止模式

当计算机处于休眠或睡眠模式时,在关闭自刷新功能之前,内存内容会被复制到硬盘驱动器,这样内存不消耗电力同时数据也得到了保存。

温度补偿自刷新 (TCSR)

温度补偿自刷新技术在各种内存上都有使用。当DRAM 进行刷新操作时,功耗会增加。此外执行刷新操作时无法进行读写操作。因此,从技术角度厂商都在想办法降低内存刷新的频率。

DDR1 和 DDR2只有一种自刷新模式,而 DDR3 则增强了这项技术,新增了两种温度敏感型自刷新模式。当 DDR3 温度低于 85˚C (185˚F) 时,刷新间隔设置为 7.8µs。如果工作温度介于 85˚C (185˚F) 和 95˚C (203˚F) 之间,则刷新间隔需要降至 3.9µs(速度提高一倍)。

该技术基于以下原理:DRAM 保留数据的时间与工作温度直接相关。较高的温度会导致 DRAM 因电荷泄漏而更快地丢失数据。因此,温度升高后需要增加刷新频率才能保证数据不丢失。

当温度较低时,自刷新周期可以更长,根据这个特性,当 DDR3 在 85˚C (185˚F) 以下运行时,降低刷新频率可以在功耗方面节省近 50%。对于台式机内存模块,因为通常有足够的的空间进行散热, DIMM 的温度很少会超过 60˚C (140˚F)。不同DRAM 制造商的刷新间隔可能略有不同 , 这取决于各个公司使用的电路和芯片制造技术,但这种差异也可能导致移动内存和设备 BIOS 之间的一些兼容性问题。一些制造商也可能根据他们放置或使用温度传感器的位置而略微不同地实现此功能;有些温度传感器位于片上或 DIMM 上,前者专门检测内存核心温度,而位于DIMM 上的传感器更多用于检测环境温度。

部分阵列自刷新 (Partial Array Self-Refresh ,PASR)

DRAM 芯片内部包含由列和行构成的内存条,类似于 Microsoft Excel 等电子表格程序中的工作表。PASR 技术预先编程了 DRAM 的某些自刷新行为,用于寻址这些列和行,以降低功耗。

例如:

  • 完整阵列:组 0、1​​、2 和 3
  • 半阵列:组 0 和 1
  • 1/4 阵列:Bank 0
  • 1/8 阵列:Bank 0,行地址 MSB = 0
  • 1/16 阵列:Bank 0,行地址 MSB 和 MSB-1 均等于零

为了降低自刷新期间的功耗,移动 DDR 可以选择性刷新某些bank中的数据而不刷新其他bank。

部分阵列自刷新示意图 来源:Elpida Memory

不同 的PASR 配置对应的不同电流消耗 来源:Micron Technology

当需要刷新的阵列数量较少时,自刷新电流会下降;但当温度升高时,电流消耗会自动增加,因为阵列需要更频繁地刷新。为此,PASR功能通常使用芯片内部嵌入的热传感器来确定其精确温度。

深度断电 (Deep Power Down , DPD)

在深度掉电模式下,内部电源关闭,所有刷新操作暂停。因此,进入深度掉电模式后数据将无法保留。
在正常运行情况下,单个活动存储体的功耗通常低于 80mA。进行刷新操作时,功耗约为正常运行时的三倍,但在深度掉电模式下,电流消耗降至约 10µA。

自刷新和深度断电模式概述 来源: Elpida Memory

时钟停止模式 (Clock Stop Mode)

此功能通过减少时钟路径上的转换来降低功耗。镁光科技称,主要有两种方法可以实现此目的:

  • 当数据传输需要不同的速度时,改变时钟频率
  • 在整个时钟停止模式期间,保持 CKE 为高电平,CK 为低电平,CK 为高电平

所采用的方法取决于具体产品的要求。移动 DDR 内存可以在运行过程中更改时钟频率,但只有在满足所有时序和内存刷新要求的情况下才可以这样做。
根据 JEDEC 的规定,在以下条件下甚至可以完全停止时钟:

  • 最后一个命令(ACTIVE、READ、WRITE、PRECHARGE、AUTO REFRESH 或 MODE REGISTER SET)已执行完成,包括读取突发期间的任何数据输出;每个访问命令的时钟脉冲数取决于设备的 AC 时序参数和时钟频率。
  • 相关时序条件(tRCD、tWR、tRP、tRFC、tMRD)已满足。
  • CKE 处于高位。

时钟停止模式的进入和退出 来源: JEDEC

XML 本地格式化工具

最近在研究 KML文件(也是一种XML格式),忽然发现很多工具都是在线的,使用起来并不顺手,因此编写一个本地的 XML 格式化工具。能够帮助将KML变成容易阅读的格式。

使用方法:

XMLLocalFormatter 输入文件名

之后会生成一个“输入文件名_for.XML”的新文件。

代码如下:

#include <iostream>
#include <fstream>
#include <string>
#include "tinyxml2.h"

using namespace std;
using namespace tinyxml2;

int main(int argc, char* argv[]) {
    if (argc != 2) {
        cerr << "Usage: xml_formatter input_file" << endl;
        return EXIT_FAILURE;
    }

    string inputFile = argv[1];

    // Load the XML file using TinyXML2.
    XMLDocument doc;
    XMLError error = doc.LoadFile(inputFile.c_str());
    if (error != XML_SUCCESS) {
        cerr << "Error loading file: " << inputFile << ". Error code: " << error << "." << endl;
        return EXIT_FAILURE;
    }

    // 查找最后一个 '.' 的位置以分离扩展名
    size_t dotPosition = inputFile.find_last_of('.');
    if (dotPosition == std::string::npos || dotPosition == 0) {
        std::cerr << "Invalid file name format.\n";
        return 1;
    }

    // 构造新的文件名
    std::string baseName = inputFile.substr(0, dotPosition);
    std::string extension = inputFile.substr(dotPosition);

    std::string newFileName = baseName + "_for" + extension;
    const char* output = newFileName.c_str();

    // Use PrettyPrint option for formatting.
    doc.SaveFile(output,false);

    cout << "Formatted XML saved successfully to: " << newFileName << endl;

    return EXIT_SUCCESS;
}

运行:

左边是格式化之前的,右边是格式化之后的

有需要的朋友可以试试。

可执行程序(建议自行编译):

源代码工程:

Intel xHCI

xHCI 是 eXtensible Host Controller Interface Controller的缩写。简单的理解这个是一个 USB Host控制器,能连接外部的诸如USB键盘鼠标U盘等等USB Device ,进行数据的交互传输等等。

例如,在【参考1】描述如下:

The eXtensible Host Controller Interface (xHCI) allows data transfer speed up to 10 Gb/s for USB 3.2 Gen 2×1 ports, and 5 Gb/s for USB 3.2 Gen 1×1 ports. The xHCI supports SuperSpeed USB 10 Gbps, SuperSpeed USB 5 Gbps, High-Speed (HS), Full-Speed (FS) and Low-Speed (LS) traffic on the bus. The xHCI supports USB Debug port on all the USB ports. The xHCI also supports USB Attached SCIS Protocol (UASP).

其中提到了USB Attached SCIS Protocol (UASP),我查了一下资料【参考2】:

UAS(USB Attached SCSI)是一种位于SCSI协议框架下传输层的一种协议,其作用是通过基于USB的应用层协议约定,将SCSI的协议数据(Protocol Data Unit)用USB进行封装,从而实现使用USB物理连接进行SCSI协议通信的方式。

UAS实际上定义了两个规范,第一个是规定UAS本身使用方式的USB Attached SCSI,另一个是定义了UAS设备类型的Universal Serial Bus Mass Storage Class - USB Attached SCSI Protocol (UASP)。

大约的意思是:这个接口可以驱动符合 USB 协议个U盘这种,走 USB mass Storage 协议。此外,还可以直接走 SCSI 协议。后者传输速度更快效率更高(我个人理解)。这种有点像 ThunderBolt 的 TypeC 接口,可以输出视频信号,但是实现方法有两种:1.USB协议 2.DP 协议。Type-C 内部有一个Mux(切换)芯片,如果发现对方设备握手是一个显示器,那么就直接将DP的视频信号输出,实际上工作的是 Intel 显卡。

上述都是个人理解,如有不妥,欢迎指出。


参考:

1.https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/tiger-lake-mobile-y/intel-500-series-chipset-family-on-package-platform-controller-hub-datasheet-v/003/extensible-host-controller-interface-xhci-controller/

2.https://blog.csdn.net/polley88/article/details/130845520

Step to UEFI (299)替换ReadKeyStroke的实验

这次的代码是根据之前的文章【参考1】修改而来,将当前系统中 Simple Simple Input Protocol 中的ReadKeyStroke()函数替换为自定的。为了便于验证,如果当前返回了 ‘z’ ,那么会将这个替换为’a’。

完整的代码如下:

#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <PiDxe.h>
#include <Protocol/SimpleFileSystem.h>

extern EFI_SYSTEM_TABLE *gST;

EFI_GUID gEfiSimpleTextInputProtocolGuid = {
    0x387477c1,
    0x69c7,
    0x11d2,
    {0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};

// EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *SimpleInput;
EFI_INPUT_READ_KEY OldReadKeyStroke;

// This one will replace the Read function of Read in Simple File System
EFI_STATUS
EFIAPI
MyReadKeyStroke(IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
                OUT EFI_INPUT_KEY *Key) {
  //EFI_INPUT_KEY IKey;
  EFI_TPL OldTpl;
  EFI_STATUS Status;
  //
  // Enter critical section
  //
  OldTpl = gBS->RaiseTPL(TPL_NOTIFY);

  Status = (*OldReadKeyStroke)(SimpleInput, Key);
  if (Key->UnicodeChar==0x7A) {
  	 Key->UnicodeChar=0x61;
  }

  //
  // Leave critical section and return
  //
  gBS->RestoreTPL(OldTpl);

  return Status;
}

EFI_STATUS
EFIAPI
MyEntryPoint(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
  EFI_STATUS Status;

  // Look for one Simple Simple Input Protocol
  Status =
      gBS->LocateProtocol(&gEfiSimpleTextInputProtocolGuid, NULL, &SimpleInput);
  if (EFI_ERROR(Status)) {
    gST->ConOut->OutputString(gST->ConOut,
                              L"Can't find Simple Input PROTOCOL\n");
    return Status;
  }
  OldReadKeyStroke = SimpleInput->ReadKeyStroke;  
  SimpleInput->ReadKeyStroke = MyReadKeyStroke;


  return Status;
}

需要注意的上述代码是驱动:

1.在 MdeModulePkg 下面编译通过,在\MdeModulePkg\MdeModulePkg.dsc 添加如下:

  MdeModulePkg/Universal/RegularExpressionDxe/RegularExpressionDxe.inf
  MdeModulePkg/Universal/SmmCommunicationBufferDxe/SmmCommunicationBufferDxe.inf
  MdeModulePkg/Universal/Disk/RamDiskDxe/RamDiskDxe.inf
  MdeModulePkg/KSTest/KSTest.inf

[Components.X64]
  MdeModulePkg/Universal/CapsulePei/CapsuleX64.inf

[BuildOptions]

2.编译使用

build -a X64 -p MdeModulePkg\MdeModulePkg.dsc -t VS2019

3.运行时使用如下命令加载

load kst.efi

参考:

1.https://www.lab-z.com/stu163rp/ 替换已经存在Protocol中函数的实验

EasyX 用点画椭圆

椭圆的参数方程:

源代码:

#include <graphics.h>  // EasyX图形库头文件
#include <conio.h>     // 用于_getch()
#include <math.h>    

#define a 100
#define b 50

int main()
{
    int x,y;
    // 初始化640x480像素的图形窗口
    initgraph(640, 480);

    for (int i = 0; i < 360; i++) {
        x = 320 + a * cos(i * 3.1415 / 180); // sin 用弧度做参数
        y=  240+ b * sin(i * 3.1415 / 180); // cos 用弧度做参数
        putpixel(x, y, RED);  
    }

    // 保持窗口显示
    _getch();

    // 关闭图形窗口
    closegraph();
    return 0;
}

运行结果:

和之前画圆类似,直接用点的方法计算:

#include <graphics.h>  // EasyX图形库头文件
#include <conio.h>     // 用于_getch()
#include <math.h>    
#include <stdio.h>  

#define Xcenter 320
#define Ycenter 240

// 长轴
#define A 50
// 短轴
#define B 30

// 焦点F1坐标
#define Xf1 (Xcenter-(int)(sqrt(A*A-B*B)))
#define Yf1 Ycenter
// 焦点F2坐标
#define Xf2 (Xcenter+(int)(sqrt(A*A-B*B)))
#define Yf2 Ycenter

// 计算 (x,y) 到焦点F1和F2的距离之和
double CalculateDistance(int x, int y)
{
    double f1 = sqrt((x - Xf1) * (x - Xf1) + (y - Yf1) * (y - Yf1));
    double f2 = sqrt((x - Xf2) * (x - Xf2) + (y - Yf2) * (y - Yf2));
    return (f1+f2);
}

// 找到下一个点位
// 输入当前点位坐标 (Xcurrent,Ycurrent)
// 前一个点位坐标 (Xlast,Ylast
void FindNextPoint(int Xcurrent, int Ycurrent, int Xlast, int Ylast, int *Xnext, int *Ynext)
{
    double gap= 1000000000.0;
    double tmp;
    // 左
    if ((Xcurrent - 1 != Xlast) || (Ycurrent != Ylast)) {
        tmp = fabs(CalculateDistance(Xcurrent - 1, Ycurrent) - 2 * A);
        if (gap > tmp) {
            gap = fabs(CalculateDistance(Xcurrent - 1, Ycurrent) - 2 * A);
            *Xnext = Xcurrent - 1;
            *Ynext = Ycurrent;
        }
    }
    // 左上
    if ((Xcurrent-1 != Xlast) || (Ycurrent-1!= Ylast)) {
        tmp = fabs(CalculateDistance(Xcurrent - 1, Ycurrent - 1) - 2 * A);
        if (gap > tmp) {
            gap = fabs(CalculateDistance(Xcurrent - 1, Ycurrent-1) - 2 * A);
            *Xnext = Xcurrent - 1;
            *Ynext = Ycurrent-1; 
        }
    }
    // 上
    if ((Xcurrent!= Xlast) || (Ycurrent - 1 != Ylast)) {
        tmp = fabs(CalculateDistance(Xcurrent, Ycurrent - 1) - 2 * A);
        if (gap > tmp) {
            gap = fabs(CalculateDistance(Xcurrent, Ycurrent - 1) - 2 * A);
            *Xnext = Xcurrent;
            *Ynext = Ycurrent - 1;
        }
    }
    // 右上
    if ((Xcurrent+1 != Xlast) || (Ycurrent - 1 != Ylast)) {
        if (gap > fabs(CalculateDistance(Xcurrent+1, Ycurrent - 1) - 2 * A)) {
            gap = fabs(CalculateDistance(Xcurrent+1, Ycurrent - 1) - 2 * A);
            *Xnext = Xcurrent+1;
            *Ynext = Ycurrent - 1;
        }
    }
    // 右
    if ((Xcurrent + 1 != Xlast) || (Ycurrent != Ylast)) {
        if (gap > fabs(CalculateDistance(Xcurrent + 1, Ycurrent) - 2 * A)) {
            gap = fabs(CalculateDistance(Xcurrent + 1, Ycurrent) - 2 * A);
            *Xnext = Xcurrent + 1;
            *Ynext = Ycurrent;
        }
    }
    // 右下
    if ((Xcurrent + 1 != Xlast) || (Ycurrent+1 != Ylast)) {
        if (gap > fabs(CalculateDistance(Xcurrent + 1, Ycurrent+1) - 2 * A)) {
            gap = fabs(CalculateDistance(Xcurrent + 1, Ycurrent+1) - 2 * A);
            *Xnext = Xcurrent + 1;
            *Ynext = Ycurrent + 1;
        }
    }
    // 下
    if ((Xcurrent != Xlast) || (Ycurrent +1!= Ylast)) {
        if (gap > fabs(CalculateDistance(Xcurrent, Ycurrent+1) - 2 * A)) {
            gap = fabs(CalculateDistance(Xcurrent, Ycurrent+1) - 2 * A);
            *Xnext = Xcurrent;
            *Ynext = Ycurrent+1;
        }
    }
    // 左下
    if ((Xcurrent-1 != Xlast) || (Ycurrent + 1 != Ylast)) {
        if (gap > fabs(CalculateDistance(Xcurrent-1, Ycurrent + 1) - 2 * A)) {
            gap = fabs(CalculateDistance(Xcurrent-1, Ycurrent + 1) - 2 * A);
            *Xnext = Xcurrent-1;
            *Ynext = Ycurrent + 1;
        }
    }

}
int main()
{
    int x=0, y= 0;
    int Xlast = Xcenter - A, Ylast = Ycenter;
    int Xcurrent = Xcenter - A, Ycurrent = Ycenter;

    // 初始化640x480像素的图形窗口
    initgraph(640, 480);

    for (int i = 0; i < 255; i++) {
        FindNextPoint(Xcurrent, Ycurrent,Xlast,Ylast,&x,&y);

        //printf("%d %d\n", x- Xcenter, y-Ycenter);
        putpixel(Xcurrent, Ycurrent, RED);
        Xlast = Xcurrent;
        Ylast = Ycurrent;
        Xcurrent = x;
        Ycurrent = y;

        if ((i != 0) && (Xcurrent == Xcenter - A) && (Ycurrent == Ycenter)) {
            printf("-->%d \n",i);
        }

    }

    // 保持窗口显示
    _getch();

    // 关闭图形窗口
    closegraph();
    return 0;
}

之前提到计算周长的问题,椭圆周长公式【参考】:L=T(r+R),其中的 T 是短轴长轴的比例。比如,这里我们 A=50, B=30 因此,T=3.190874858, 计算结果是255.2,上述代码运行之后会输出 “–>231” 意思是在 234 的时候到了起始位置,结果和预期有可比性。

参考:

1。https://baike.baidu.com/item/%E6%A4%AD%E5%9C%86/684466

Step to memory 005 延迟和流程的演变

原文在 https://www.bit-tech.net/reviews/tech/memory/the_secrets_of_pc_memory_part_1/8/

延迟

DDR 的每一代发展都提供了更快的数据速率和更高的容量,但其代价是稳定性和信号准确性的下降。因此,每一代 DDR 都需要适应更高的频率和更高的信号延迟。

延迟可以理解为为时间上的暂停或延迟。当 DDR 信号命令发生变化时,内存子系统需要在不同的内存命令之间暂停。这就像一列火车驶入车站,然后停下来让乘客上下车。

延迟有很多种类型。CAS 延迟通常被认为是最重要的延迟之一,然而随着 DDR 内存的更新换代,它本身的重要性逐渐降低,而多个延迟值的组合则更为重要。许多内存模块将 CAS 延迟表示为“CL”或简称为“C”。例如,CAS 延迟为 3 个时钟周期的内存模块通常被标记为CL3 或 C3。相关知识将在后续文章中讨论。

演化过程 在更高数据吞吐量需求的推动下,DDR 相关的各种技术经历了一系列的演进,同时尽可能保持经济性。

提高内存数据传输频率面临的两​​大挑战是信号噪声水平和时序精度。这通常被称为有效数据窗口 (Valid Data Window , DVW)。有效数据窗口有时也称为数据有效窗口(Data-Valid Window,DVW)或简称为“眼图”。它是决定信号可靠性主要因素。

为了降低信号反射,。例如:DDR1 采用主板上添加终端电阻方法,DDR2 则采用片上终端电阻 (On-Die Termination,ODT) 方法。DDR3 进一步扩展了 ODT 技术,允许根据情况动态调整 ODT 值。结合各种信号校准技术,可以合理地管理数据完整性,从而实现更快的传输速率。所有内存模块和主板都需要在设计和后期生产过程中进行信号准确性测试和验证,以确保各种内置校准方案正常运行。

这里提到的信号反射指的是高速信号中的反射问题【参考1】https://blog.csdn.net/hs977986979/article/details/142762703

在硬件电路中,高频信号的反射是一个非常重要的现象。当电磁波在传输线上传播时,如果遇到阻抗不连续点(如传输线的末端、拐角、过孔、元件引脚、线宽变化等),就会发生反射。反射波的大小和方向取决于入射波的幅度、相位以及阻抗不连续点的性质。

反射现象会导致信号轮廓失真,产生过冲、欠冲和振荡等问题。这些问题会影响电路的性能和稳定性。为了减小反射,通常需要在传输线的末端添加适当的终端匹配电阻,以确保信号的完整传输。
为了实现更高的效率和更低的散热,需要降低内存电压。数据中心运营商、台式机和笔记本电脑消费者对低功耗计算机越来越感兴趣,原因包括更环保、更长的使用时间以及总体运营成本的降低。

现代数据中心使用大量空调来维持运行。例如,卢卡斯影业 (LucasFilm) 的数据中心在 32 台空调机组中使用了 25 吨冷却剂来维持其系统的运行。功率效率通常以每瓦性能来计算,因此在保持相同性能的情况下,瓦数的降低对行业来说都是利好消息。

计算机内存系统如果没有多年的预先规划和全行业协商,就不会发生重大的范式转变。成员们会将一次又一次的修订提交给像 JEDEC 这样的设计管理机构,然后由一个合作伙伴委员会监督整个设计和批准过程。

制造过程中涉及的制造和测试设备成本高昂,这极大地阻碍了变革,因为自动测试设备 (Automatic Test Equipment,ATE) 价格极其昂贵,通常每台设备的成本高达数百万美元。MOSAID Systems 的 Brad Snoulten 认为,主要挑战有两个:

  • 内存制造商无力购买或更换价值数百万美元的测试仪来满足不断增长的生产或工程测试需求。

  • 内存ATE供应商面临着设计经济实惠且能有效抵御长期淘汰的解决方案的挑战。内存裕度的不断下降和设备复杂性的不断增加加剧了这些问题。

进步是一个渐进的演变过程,而不会出现跳跃式的进步。

第九章 GDDR,QDR 和 XDR

如果您在过去三年内购买组装一个台式机,它很可能至少有一个 PCI Express 图形扩展接口。这些显卡拥有一种专用的 DDR,即图形 DDR (GDDR) 内存,容量从最小的几十兆字节到超过上千兆字节甚至更多。

GDDR通常用于对带宽要求极高的高性能显卡。需要主意它的架构与 DDR 截然不同, 在JEDEC 的规范中GDDR 与 DDR 标准分属不同规范。

最新的 GDDR 技术已发展到第五代,简称为 GDDR5。不同代之间的主要区别在于性能和带宽。与 DDR 相比,GDDR 具有更高的性能,但在制造成本和功耗方面也明显更高。GDDR3的工作电压为 2.0V,而 DDR3 的工作电压为 1.5V,额外的电压有助于 GDDR 更快地运行,但也使其更容易泄漏电流,从而产生更多热量。这与大型图形运算核心组合意味着显卡产生的热量通常比主板中的 CPU 和内存加起来还要多。

Nvidia 8800GT 上的奇梦达 GDDR3

GDDR 具有较宽的数据接口和更大的帧缓冲区。与 DDR 设备相比,显卡等 GDDR 设备的单位容量较低;这会导致功耗更高且价格更高。增加设备的 GDDR 显存容量会相应增加功耗和设备价格。

QDR和XDR

四倍数据速率 (QDR) 内存系统始于 1999 年,由Cypress 导体公司、IDT 和 NEC 共同开发。此后,包括美光科技、瑞萨电子、三星电子和日立在内的多家公司都以某种形式参与其中。

我们预计, QDR 内存取代 DDR 的可能性不高,最重要的原因是QDR无法实现低成本的量产。其次,此举相当于内存技术的一次重大转向,出于经济性和实用性的考虑,很多制造商对此非常反对。

虽然 QDR 的数据频率和有效数据窗口显著提高,并具有超低延迟,但其内存容量相对于 DDR 而言相对较低。当 DDR3 达到每模块 8GB 时,QDR3 内存容量标准仍然以 MB 为单位。QDR 架构是特地为高性能通信应用而设计。

Lattice半导体公司表示,与 QDR 相比,DDR 技术存在以下缺点:

  • 写入和读取共享一条双向数据总线,因此总带宽与 QDR 架构相比减少了一半。这在写入与读取比例接近 1:1 的情况下意义重大。
  • 刷新需要中断数据传输。
  • 访问延迟相对较高。
  • 需要在上电后进行初始化,并在访问之前/之后激活/预充电行(内存接口简化了这一点)。

值得注意的是,目前市场上已经有了QDR内存甚至ODR内存(Octal-Data Rate)。索尼 PlayStation 3 采用 Rambus 的 XDR(极限数据速率)设计,该设计能够在每个时钟周期发送 8 位数据,而 DDR 只能在每个时钟周期发送 2 位数据。三星、奇梦达、尔必达、IBM、东芝、AMD 等公司目前都采用 XDR 设计。这些内存系统价格极其昂贵,而且不像 DDR 那样普及。

有一种罕见的 DDR2 类型,称为 DDR2+(或增强型 DDR2),其核心频率可达 333MHz,而 DDR2 标准频率为 200MHz。DDR2+ 也是由 QDR 联盟联合开发的。

下次我们将研究移动 DDR 和底层 DDR 技术,如温度补偿自刷新、部分阵列自刷新、深度断电和时钟停止模式以及 DRAM 封装和堆叠技术。

======================================================

讲个好玩的事情,三星内存的“反周期策略”:

三星充分利用了存储器行业的强周期特点,在价格下跌、生产过剩、其他企业削减投资的时候,逆势疯狂扩产,通过大规模生产进一步下杀产品价格,从而逼竞争对手退出市场甚至直接破产,世人称之为“反周期定律”。

在存储器这个领域,三星一共祭出过三次“反周期定律”,前两次分别发生在80年代中期和90年代初,让三星从零开始,做到了存储器老大的位置。但三星显然觉得玩的还不够大,于是在2008年金融危机前后,第三次举起了“反周期”屠刀。2007 年初,微软推出了狂吃内存的Vista操作系统,DRAM厂商判断内存需求会大增,于是纷纷上产能,结果Vista 销量不及预期,DRAM 供过于求价格狂跌,加上08 年金融危机的雪上加霜,DRAM 颗粒价格从2.25 美金雪崩至0.31 美金。

就在此时,三星做出令人瞠目结舌的动作:将2007 年三星电子总利润的118%投入DRAM 扩张业务,故意加剧行业亏损,给艰难度日的对手们,加上最后一根稻草。效果是显著的。DRAM价格一路飞流直下,08年中跌破了现金成本,08年底更是跌破了材料成本。2009年初,第三名德系厂商奇梦达首先撑不住,宣布破产,欧洲大陆的内存玩家就此消失。2012年初,第五名尔必达宣布破产,曾经占据DRAM市场50%以上份额的日本,也输掉了最后一张牌。在尔必达宣布破产当晚,京畿道的三星总部彻夜通明,次日股价大涨,全世界都知道韩国人这次又赢了。至此,DRAM领域最终只剩三个玩家:三星、海力士和镁光。尔必达破产后的烂摊子,在2013年被换了新CEO的镁光以20多亿美金的价格打包收走。20亿美金实在是个跳楼价,5年之后,镁光市值从不到100亿美元涨到460亿,20亿美元差不多是它市值一天的振幅。

上述来自雪球 作者:潇潇小鱼 链接:https://xueqiu.com/2691707350/179978325