ASCII数字

因为 UEFI Shell 是 CUI 界面,很多测试需要用 ASCII 显示结果。所以有些时候我们需要足够大的字来展示。

最近偶然发现了这个 ASCII 字体的网站,可以用来取得一些 ASCII 拼凑出来的字形。例如:

░▒▓█▓▒░       ░▒▓██████▓▒░░▒▓███████▓▒░░▒▓████████▓▒░ 
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░      ░▒▓█▓▒░ 
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░    ░▒▓██▓▒░  
░▒▓█▓▒░      ░▒▓████████▓▒░▒▓███████▓▒░   ░▒▓██▓▒░    
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓██▓▒░      
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░        
░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░ 
                                                      

 __      __    ____      ____ 
(  )    /__\  (  _ \ ___(_   )
 )(__  /(__)\  ) _ <(___)/ /_ 
(____)(__)(__)(____/    (____)

     ___       ___           ___           ___     
     /\__\     /\  \         /\  \         /\  \    
    /:/  /    /::\  \       /::\  \        \:\  \   
   /:/  /    /:/\:\  \     /:/\:\  \        \:\  \  
  /:/  /    /::\~\:\  \   /::\~\:\__\        \:\  \ 
 /:/__/    /:/\:\ \:\__\ /:/\:\ \:|__| _______\:\__\
 \:\  \    \/__\:\/:/  / \:\~\:\/:/  / \::::::::/__/
  \:\  \        \::/  /   \:\ \::/  /   \:\~~\~~    
   \:\  \       /:/  /     \:\/:/  /     \:\  \     
    \:\__\     /:/  /       \::/__/       \:\__\    
     \/__/     \/__/         ~~            \/__/    

有兴趣的朋友可以在这里看到:

http://www.patorjk.com/software/taag/#p=testall&f=Alpha&t=LAB-Z

UEFI TIPS:Print=UnicodeSPrint+ ConOut

最近有在屏幕输出数据的需求,但是无法直接使用 Print 。经过对于 PrintLib 的一番研究,得出了结论:

UEFI Shell 下的Print 的实现可以看作两个动作,一个根据输入格式化得到字符串,另外一个是使用 gST 进行输出。简单的说就是:

Print=UnicodeSPrint+ gST->ConOut->OutputString

编写测试代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <Library/PrintLib.h>
extern EFI_SYSTEM_TABLE			 *gST;
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16					Buffer[32];
  
  UnicodeSPrint((CHAR16 *)Buffer,sizeof(Buffer),L"%x\n",2024);
  gST->ConOut->OutputString(gST->ConOut,Buffer);
 
  return(0);
}

运行结果:

如果你在调试 Application 或者Driver遇到无法直接使用 Print 的情况,不妨考虑本文提到的方法。当然,如果你有从串口或者其他设备输出调试数据的时候,也可以考虑UnicodeSPrint()来实现数据格式化方便阅读调试。

一些Unicode 数字符号定义

1.圈中带有数字

⓪ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㊱ ㊲ ㊳ ㊴ ㊵
㊶ ㊷ ㊸ ㊹ ㊺ ㊻ ㊼ ㊽ ㊾ ㊿

2.实心方块中空圆形数字

         
         

3.实心方块数字

         

4.中文

         

来源:http://xahlee.info/comp/unicode_circled_numbers.html

此外,在 https://altcodeunicode.com/alt-codes-circled-number-letter-symbols-enclosed-alphanumerics/ 还有一些圆圈的英文序号

制作全自动安装的 Windows 11 ISO

前面提 到通过应答文件来实现自动安装【参考1】,使用这个方法在虚拟机上测试正常,但是实体机上在分区拷贝完成之后会进入OOBE的界面要求手工选择,因此并非全自动安装。

针对这个问题进行了更深入的研究,原来除了在安装盘下面放置autounattend.xml 之外,在对应的 WinPE 盘,Windows\Panther 目录下还需要放置一个 unattend.xml 应答文件。更具体的操作是,安装 ADK

1。解压Windows 11 安装 ISO

2.取出其中的 boot.wim 文件

3.在 “Deployment and Imaging Tools Environment”中使用如下命令解压 Boot.WIM 到 m1 目录下

dism /mount-wim /wimfile:c:\LABZ\boot.wim /index:1 /mountdir:c:\m1

4.将unattend.xml拷贝到 m1\Windows\Panther目录下

5.将修改回写到 boot.wim 中

dism /unmount-wim /MountDir:c:\m1 /Commit

6.同样在 “Deployment and Imaging Tools Environment”中再次解压 Boot.WIM 到 m1 目录下(Boot 中有个 Index,上一次用的是 1,这次用的是 2)

dism /mount-wim /wimfile:c:\LABZ\boot.wim /index:2 /mountdir:c:\m1

7.将unattend.xml拷贝到 m1\Windows\Panther目录下

8.将修改回写到 boot.wim 中

dism /unmount-wim /MountDir:c:\m1 /Commit

这样就有了一个更换过 unattend.xml的Boot.WIM 其中是一个完整的 WinPE 环境,接下来使用工具将Boot.WIM替换回原始的安装 ISO。最终我们就有了一个全自动的Window安装盘。

这样的ISO通过 Refuse或者Ventoy 制作出来的启动U盘上都可以正常启动,并且能够做到完全自动的安装。

本文提到的制作好的 Windows 11 可以在这里下载(文件超过4G, 分割为2个ZIP 压缩文件)

文件1

文件2

对应的2个 xml 文件可以在这里下载:

参考:

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

测试的视频,在 LattePanda MU 上进行的测试:

基于Ch554 实现 USB 转 USB

最近设计了一个 USB 转USB 设备,它带有一个USB公头和一个USB母头,可以读取一个USB 设备发出来的数据,然后转换为另外的数据。

首先是硬件设计,使用了2个 CH554e芯片,这个芯片能够实现 USB Host和Device,二者通过串口进行通讯。这款芯片是南京WCH出品的,最高支持 24Mhz主频内置,16K程序存储器ROM和256字节内部iRAM以及1K 字节片内xRAM。Ch554e 是最小的封装形式,MOP10方便焊接。

硬件设计比较简单,2个5V供电的Ch554的最小系统,中间通过串口连接。

PCB 设计如下:

这样的设计刚好能够放入淘宝购买的透明外壳中。

两个 Ch554分别写入不同的固件。其中USB母头连接Ch554的需要写入 Keil编译的代码,用于实现 USB Host 功能。解析后的HID 数据会按照 Ch9350 格式通过串口输出,有兴趣的朋友可以在【参考1】看到实现。

USB 公头连接的Ch554代码使用 Arduino 进行编译。实现的功能是检测通过串口获得数据是否包含 wasd 这几个按键,如果有的话就转为对应的鼠标移动操作:

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

#include "src/userUsbHidKeyboardMouse/USBHIDKeyboardMouse.h"

#define DEBUGMODE 0

void setup() {
  // 串口通讯
  Serial1_begin(500000);
  USBInit();
  pinMode(14, OUTPUT);
  digitalWrite(14, LOW);
}

boolean FindKey(byte s, byte *p, byte len) {
  for (byte i = 0; i < len; i++) {
    if (p[i] == s) {
      return true;
    }
  }
  return false;
}
void loop() {
  //根据 CH9350 Spec 每次最多输出 72Bytes
  byte Data[72];
  byte CounterLast = Serial1_available();
  byte CounterCurrent = 0;

  // 如果当前串口有数据
  if (CounterLast != 0) {
    // 进行简单测试,如果当前还在传输数据那么持续接收
    while (CounterCurrent != CounterLast) {
      CounterLast = Serial1_available();
      delayMicroseconds(500);
      CounterCurrent = Serial1_available();
    }
  }

  if (CounterCurrent > 0) {
    // 一次性将数据收取下来
    //Serial1_readBytes(Data, CounterCurrent);
    for (byte i = 0; i < CounterCurrent; i++) {
      Data[i] = Serial1_read();
      //  USBSerial_print_ub(Data[i], 16);
    }

    unsigned int i = 0;
    unsigned int Length;
    while (i < CounterCurrent) {
      // 识别帧头
      if ((Data[i] == 0x57) && (Data[i + 1] == 0xAB)) {
        // 有效键值帧
        if (Data[i + 2] == 0x88) {
          // 获得数据长度
          Length = Data[i + 3];
          //如果是键盘
          if (Data[i + 4] == 0x10) {
            digitalWrite(14, HIGH);
            if (DEBUGMODE) {
              for (int j = 3; j < Length - 2; j++) {
                USBSerial_print_ub(Data[i + 3  + j], 16);
              }
              USBSerial_println_only();
            }
            if (FindKey(0x1A, Data, Length) == true) { // 'w'
              Mouse_move(0, -100);
            }
            if (FindKey(0x16, Data, Length) == true) { // 's'
              Mouse_move(0, 100);
            }
            if (FindKey(0x04, Data, Length) == true) { // 'a'
              Mouse_move(-100,0);
            }
            if (FindKey(0x07, Data, Length) == true) { // 'd'
              Mouse_move(100,0);
            }
            digitalWrite(14, LOW);
          } //if (Data[i + 4] == 0x10) {
        }
        i = i + 3 + Length;
      }
      i++;
    } // while (i < Counter)
  }
}

通过串口烧录,短接 DL1 或者  DL2 接口,插入 Windows主机后,设备管理器中会出现新的设备,然后使用 wchispstudio 即可烧写。对于USB Host 对应的Ch554需要一个 USB 公头转公头来实现转为USB公头然后进行烧写。

参考:

  1. https://mc.dfrobot.com.cn/thread-319462-1-1.html

本文提到的完整代码下载:

1.模拟键盘的代码(Arduino)

2. USB Host 代码

Step to UEFI (296)虚拟机下的假电池

电池作为现在笔记本必不可少的部件,通过 ACPI 和 Windows 进行交互。

对此,ACPI Spec 定义了几个 Table。换一句话说,Windows 只要能够正确读取出 Table,那么就可以根据上面的信息展示给客户一个电池。

第一个是 _BIX (Battery Information Extended) (特别注意ACPI 4.0定义的 _BIF (Battery Information)已经废止 ),其中给出了电池的信息。

偏移名称大小解释
RevisionDWORD目前版本号为 1
Power UnitDWORD电池容量单位: 0 – [mWh], 同时充放电速度将会以[mW]为单位 1 – [mAh], 同时充放电速度将会以[mA]为单位
Design CapacityDWORD设计容量,单位由上面的 Power Unit 给出 取值范围: 0x00000000-0x7FFF FFFF 0xFFFFFFFF 未知容量
Last Full Charge CapacityDWORD充满后的预期容量 取值范围: 0x00000000-0x7FFF FFFF 0xFFFFFFFF 未知容量
Battery TechnologyDWORD电池位置 0x0000 0000 主电池 0x0000 0001 第二块电池
Design VoltageDWORD设计电压, 取值范围 0x000000000 – 0x7FFFFFFF in [mV] 0xFFFFFFFF – 未知电压
Design Capacity of WarningDWORDOEM 设置的告警容量值 取值范围 0x000000000 – 0x7FFFFFFF in [mWh] or [mAh]
Design Capacity of LowDWORDOEM 设置的低容量值 取值范围 0x000000000 – 0x7FFFFFFF in [mWh] or [mAh]
Cycle CountDWORD充电循环次数 取值范围 0x000000000 – 0xFFFFFFFF
Measurement AccuracyDWORD电池容量测量准确度,以1/1000为单位,比如:80000表示80%
Max Sampling TimeDWORD_BST 中两次测量的最大间隔时间,比如,当前电池容量,放电速度或者剩余容量。以为毫秒单位。0xFFFFFFFF表示该位置无效。
Min Sampling TimeDWORD_BST 中两次测量的最小间隔时间。以为毫秒单位。0xFFFFFFFF表示该位置无效。
Max Averaging IntervalDWORD_BST 中两次测量的平均最大间隔时间。
Min Averaging IntervalDWORDBST 中两次测量的平均最小间隔时间。
Battery Capacity Granularity 1DWORD电池在告警容量值和低容量值之间的颗粒度
Battery Capacity Granularity 2DWORD电池在告警容量值和充满容量值之间的颗粒度
Model Number零结尾ASCII字符串OEM 定义的电池型号
Serial Number零结尾ASCII字符串OEM 定义的电池序列号
Battery Type零结尾ASCII字符串OEM 定义的电池类型
OEM Information零结尾ASCII字符串OEM 定义的在UI上展示的电池OEM信息
Battery Swapping CapabilityDWORD0x0 不可更换电池,例如,内部密封电池,用户无法接触到 0x1关机之后可更换电池 0x10 热插拔电池
上述根据ACPI Spec翻译,如果有错误欢迎指出,会进行订正

第二个是 _BST (Battery Status), 这个用于报告当前电池的状态信息。

偏移名称大小解释
Battery StateDWORDBit0 为1表示正在放电 Bit1  为1表示正在充电 Bit2  为1表示电池预警  
Battery Present RateDWORD电池充放电速度 取值范围 0x000000000 – 0x7FFFFFFF以[mW]或者[mA]为单位 0xFFFFFFFF – 未知速度    
Battery Remaining CapacityDWORD电池剩余容量 取值范围 0x000000000 – 0x7FFFFFFF以[mWh]或者[mAh]为单位 0xFFFFFFFF – 未知容量  
Battery Present VoltageDWORD电池电压 取值范围 0x000000000 – 0x7FFFFFFF以[mV]为单位 0xFFFFFFFF – 未知电压  

以本人的电脑(HP 840 G6)为例,设备管理器中可以看到电池:

使用 HE 直接读取 ACPI Table:

根据上面的整理出两个对应的Table, 放在 BAT0 设备中

Device (BAT0)
        {
            Name (_HID, EisaId ("PNP0C0A") /* Control Method Battery */)  // _HID: Hardware ID
            Name (_UID, One)  // _UID: Unique ID
            Method (_DSM, 4, Serialized)  // _DSM: Device-Specific Method
            {
                If (LEqual (Arg0, ToUUID ("4c2067e3-887d-475c-9720-4af1d3ed602e") /* Battery Thermal Limit */))
                {
                    Switch (ToInteger (Arg2))
                    {
                        Case (0x03)
                        {
                            Return (Package (0x01)
                            {
                                0x1E
                            })
                        }

                    }
                }
                Else
                {
                    Return (Package (0x01)
                    {
                        Zero
                    })
                }
            }

            Method (_STA, 0, NotSerialized)  // _STA: Status
            {
                Return (0x1F)
            }

            Method (_BIX, 0, NotSerialized)  // _BIX: Battery Information Extended
            {
                Return ( Package (0x15)
						{
							1, 
							1, 
							20000, 
							20000, 
							0, 
							4300, 
							2000, 
							1000, 
							10, 
							80000, 
							1000, 
							500, 
							750, 
							500, 
							0x64, 
							0x64, 
							"LABZBAT0", 
							"202410", 
							"MODOL1", 
							"LABZBAT0", 
							One
						})
            }

            Method (_BST, 0, NotSerialized)  // _BST: Battery Status
            {
                Return ( Package (0x04)	{
               			 1, 
               			 100, 
                		 10000, 
                		 4200
           			   })
            }

        }

     }

接下来选择使用 VirtualBox 虚拟机,根据【参考1】,替换内部的 ACPI Table, 最终效果如下:

就是说,我们成功的在这个虚拟机中安装了一块电量为 50% 的电池。

本文提到的修改后的 ACPI 源代码可以在这里下载:

参考:

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

CH554 USB Host配合 ESP32-C3实现USB键盘转蓝牙

之前使用 Ch9350制作过一个 USB Host Shield 【参考1】,能够读取USB键盘鼠标的输入。最近在研究 Ch554 ,使用Ch554e制作了一个同样功能的Shield,配合ESP32-C3 能够实现USB 键盘转蓝牙的功能。

使用 Ch554 的有优点如下:

  1. 价格较低,相对于Ch9350 10元的价格,最便宜的 Ch554e 只要不到1.5元;
  2. 焊接友好,对于 TSSOP-20/SOP-16或者MSOP-10普通人都能够很好的进行焊接;
  3. 如果你的设计对于体积敏感,可以选择MSOP-10 封装的 Ch554e;
  4. 外围电路简单,只需要2个电容和1个电阻

缺点:

  1. 需要自己使用 keil 编写程序;
  2. 兼容性比不上 Ch9350,可能出现无法驱动的USB设备;

这次带来就是基于 Ch554e的设计。硬件部分设计如下:

下方就是CH554e的最小系统,外部配合2个0.1uf电容,以及1个10K电阻即可工作。下载方法是:上电之前短接 DL 位置,然后再上电使用WCHISPStudio即可。不过在研发阶段建议专门准备一个开发板便于操作。同时,官方的例子都是用第一个UART作为调试输出,而Ch554只有第2个 Uart可供使用。

根据上述电路设计的PCB如下:

这是一个底板,上面直接连接 DFRobot ESP32-C3即可。焊接后的板卡如下:

直接安装在 ESP32-C3上即可使用:

接下来开始代码的设计,首先设计的是 Ch554的代码,这里直接使用官方的代码进行简单修改。

为了便于使用我们使用和Ch9350相同的输出格式:

0-157 AB数据头,固定数值
0288表示有效帧值
03NN后续数据长度,从04开始到最后的校验和
0410  固定值 [7:6]:00 – 保留 [5:4]:01 – 鼠标 [3]  :0  – 保留 [2:1]:00 – 未知 [0]  :0  – 端口1
05-AA BB CC…..MM键盘数据,例如:08 00 00 00 00 00 00 00
XXNum帧序列号
XXCheckSum校验和,从05开始的数据和

例如:实际发送的一个数据:

57 AB 88 0B 10 08 00 00 00 00 00 00 00 00 08

代码是基于WCH 官方修改而来的,基本原理是:比较每一次收到的数据(RxBuffer)是否和上一次(LastBuffer)相同,如果不同,那么进行上报。使用上面介绍的数据报文格式:

                        IsSame=TRUE;
                        for ( i = 0; i < len; i ++ ){
                            if (LastBuffer[i]!=RxBuffer[i]) {
                                IsSame=FALSE;
                                LastBuffer[i]=RxBuffer[i];
                            }
                        }
                        //只有与前一次不同才进行输出
                        if (IsSame==FALSE) {
                            checksum=0x00;
                            CH554UART1SendByte(0x57);CH554UART1SendByte(0xAB);CH554UART1SendByte(0x88);CH554UART1SendByte(len+3);CH554UART1SendByte(0x10);
                            for ( i = 0; i < len; i ++ ){
                                CH554UART1SendByte(RxBuffer[i]);
                                checksum=checksum+RxBuffer[i];
                            }
                            checksum=checksum+counter;
                            CH554UART1SendByte(counter); CH554UART1SendByte(checksum);
                            counter++;
                        }

代码使用 Keil4 编译通过。

ESP32-C3代码如下:

#include <Arduino.h>
#include <BleKeyboard.h>

BleKeyboard bleKeyboard;

#define DEBUGMODE 0

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200, SERIAL_8N1, RX, TX);

  bleKeyboard.begin();

}

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '1') {
      Serial.println("get1");
    }
    if (c == '3') {
      ESP.restart();
    }
  }

  //根据 CH9350 Spec 每次最多输出 72Bytes
  byte Data[72];
  unsigned int CounterLast = Serial1.available();
  unsigned int CounterCurrent = 0;


  // 如果当前串口有数据
  if (CounterLast != 0) {
    // 进行简单测试,如果当前还在传输数据那么持续接收
    while (CounterCurrent != CounterLast) {
      CounterLast = Serial1.available();
      delayMicroseconds(500);
      CounterCurrent = Serial1.available();
    }
  }

  if (CounterCurrent > 0) {
    // 一次性将数据收取下来
    Serial1.readBytes(Data, CounterCurrent);
    unsigned int i = 0;
    unsigned int Length;
    while (i < CounterCurrent) {
      // 识别帧头
      if ((Data[i] == 0x57) && (Data[i + 1] == 0xAB)) {
        // 有效键值帧
        if (Data[i + 2] == 0x88) {
          // 获得数据长度
          Length = Data[i + 3];
          if (DEBUGMODE) {
            //Serial.print("Ln:");Serial.print(Length);
            for (int j = 1; j < Length + 1; j++) {
              if (Data[i + 3  + j] < 16) {
                Serial.print("0");
              }
              Serial.print(Data[i + 3  + j], HEX);
              Serial.print(" ");
            }
            Serial.println(" ");
          }

          //如果是键盘
          if (Data[i + 4] == 0x10) {
            if (DEBUGMODE) {
              Serial.print("Key");
              for (int j = 1; j < Length + 1; j++) {
                Serial.print(Data[i + 3  + j], HEX);
                Serial.print(" ");
              }
              Serial.println(" ");
            }
            
            //判断为Dostyle键盘
            if (Data[i + 3  + 1] == 0x10) { 
              if (bleKeyboard.isConnected() == true) {
                bleKeyboard.sendReport((KeyReport*)(&Data[i + 3  + 2]));
              }
            }
          }
          i = i + 3 + Length;
        } else if (Data[i + 2] == 0x82) {
          i = i + 3; // 跳过
        }
      }
      i++;
    } // while (i < Counter)
  }

}

使用时,只需要给ESP32-C3供电,连接好USB键盘后就可以搜索蓝牙键盘进行连接使用了。工作的测试视频在:

https://www.bilibili.com/video/BV1zi421a7NM/?vd_source=cf6121716e06cb669a27c10276f9c920

 Ch554 的代码:

ESP32 C3 代码:

参考:

1. https://mc.dfrobot.com.cn/thread-316678-1-1.html

又一次研究JY901心得

又一次尝试使用 Jy901 模块,没有成功应用,但是通过实验有一些心得记录如下。

1.模块默认使用输出 9600Hz 波特率通讯,10Hz回报;

2.恢复模块默认配置的方法有两种,一种是短接,另外一种是串口命令

3.一些串口配置的方法:

a.	ff aa 03 03 00  设置回传速率为1Hz
b.	ff aa 02 08 00  设置只输出0x53包(手册上提到JY901无法输出四元数)
c.	ff aa 00 00 00 保存当前设置(比如,进行了上述设定之后,需要保存之后下一次上电才能继续使用)

4.使用的轴如下图所示(不要看模块PCB上的标注,是错的)

5.输出范围:X轴±180;Y轴±90;Z轴±180

6.DataSheet上描述的角度输出如下:

其中的计算方法有问题,按照它的方法不会有正负的区别:

例如: 55 53 A0 A1 B0 B1 C0 C1 T0 T1 SUM

其中的 0xA1A0 输出范围是 0000-7FFF ,对应着 -180~+180;0xB1B0 输出范围是 0000-7FFF ,对应着 -90~+90;0xC1C0 输出范围是 0000-7FFF ,对应着 -180~+180.

因此实际可以选择如下处理方法:

  fX = JY901.stcAngle.Angle[0] - 0x4000;
  fX = fX * 180.0 / 0x4000;

  fY = JY901.stcAngle.Angle[1] - 0x4000;
  fY = fY * 90.0 / 0x4000;

  fZ = JY901.stcAngle.Angle[2] - 0x4000;
  fZ = fZ * 180.0 / 0x4000;

符号和方向满足右手原则:拇指指向轴方向,然后四个手指方向是正,相反是负。

参考:

1. https://wenku.baidu.com/view/13665ba8b307e87100f69630.html?ind=1&fr=wenchuang&_wkts_=1719042668522&bdQuery=jy901+%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F

WebSerial 3DModelViewer 四元数处理格式

在下面这个网页可以以3D 模式直接展示当前姿态:

https://adafruit.github.io/Adafruit_WebSerial_3DModelViewer

其中以欧拉角模式来演示的例子可以在【参考1】看到。如果想看到以四元数为参数的姿态演示,可以使用下面的格式输出:

  Serial.print("Quaternion: ");
  dtostrf(W, 1, 3, str);
  Serial.print(W,4);
  Serial.print(", ");
  dtostrf(X, 1, 2, str);
  Serial.print(X,4);
  Serial.print(", ");
  dtostrf(Y, 1, 2, str);
  Serial.print(Y,4);
  Serial.print(", ");
  dtostrf(Z, 1, 2, str);
  Serial.println(Z,4);

参考:

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

获得物理硬盘 PID 的方法

#include <windows.h>
#include <iostream>

// 功能:获取指定物理驱动器的ProductId
std::string GetStorageDeviceProductId(const std::string& drivePath) {
    // 打开物理驱动器
    HANDLE hDevice = CreateFileA(drivePath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hDevice == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open device: " << drivePath << std::endl;
        return "";
    }

    STORAGE_PROPERTY_QUERY query = {};
    query.PropertyId = StorageDeviceProperty;
    query.QueryType = PropertyStandardQuery;

    // 分配足够大的缓冲区来存储STORAGE_DEVICE_DESCRIPTOR及其附加数据
    BYTE buffer[1024] = {};
    DWORD bytesReturned = 0;

    // 查询存储设备属性
    BOOL result = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &buffer, sizeof(buffer), &bytesReturned, NULL);
    if (!result) {
        std::cerr << "Failed to query storage device properties." << std::endl;
        CloseHandle(hDevice);
        return "";
    }

    // 获取STORAGE_DEVICE_DESCRIPTOR结构
    STORAGE_DEVICE_DESCRIPTOR* deviceDescriptor = reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(buffer);

    // ProductId是一个以NULL结尾的字符串,位于STORAGE_DEVICE_DESCRIPTOR之后
    // 确保ProductIdOffset不为0
    std::string productId = "";
    if (deviceDescriptor->ProductIdOffset != 0) {
        productId = reinterpret_cast<const char*>(buffer + deviceDescriptor->ProductIdOffset);
    }

    CloseHandle(hDevice);
    return productId;
}

int main() {
    // 示例:获取PhysicalDrive0的ProductId
    std::string productId = GetStorageDeviceProductId("\\\\.\\PhysicalDrive0");
    std::cout << "ProductId: " << productId << std::endl;
    return 0;
}