在 LattePanda BIOS 插入自定义模块

UEFI 的一个优点是模块化,意思是你可以编写一个模块,编译后能够插入到已有的BIOS中并且执行。在此之前,每个 IVB 实现方式差异巨大,除了按照 PCI ROM 这种特别形式插入几乎没有办法实现通用。这次演示的就是在 EDK2环境下编写一个DXE 模块,然后插入到LattePanda中。

首先进行代码的设计,仿照\OvmfPkg\8254TimerDxe这个模块就可以写好。

代码非常简单,入口是InitializezMessage () 函数,进入之后注册OnMyReadyToBoot函数,当ReadyToBootEvent事件时触发之。代码如下:

EFI_STATUS
EFIAPI
InitializezMessage (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status=EFI_SUCCESS;
  EFI_EVENT   ReadyToBootEvent;

  DEBUG ((DEBUG_INFO, "UPassword DXE Loaded\n"));

  //
  // Register the event to reclaim variable for OS usage.
  //
  EfiCreateEventReadyToBootEx (
    TPL_NOTIFY,              
    OnMyReadyToBoot, 
    NULL,
    &ReadyToBootEvent  
    );  

  return Status;
}

这样,当BIOS 准备启动的时候就是跳入OnMyReadyToBoot() 函数,执行,在函数中只是循环显示一段字符串。

/**
  On Ready To Boot Services Event notification handler.

  Notify SMM variable driver about the event.

  @param[in]  Event     Event whose notification function is being invoked
  @param[in]  Context   Pointer to the notification function's context

**/
VOID
EFIAPI
OnMyReadyToBoot (
  IN      EFI_EVENT                         Event,
  IN      VOID                              *Context
  )
{
  DEBUG ((EFI_D_INFO, "Invoke OnMyReadyToBoot\n"));
  
  for (UINTN i=0;i<5;i++) {
	gST->ConOut->OutputString(gST->ConOut,L"\r\n delay from www.lab-z.com\r\n");
	gBS->Stall(1000000UL);
  }
	
  gBS->CloseEvent (Event);
}

然后将其加入编译,在\OvmfPkg\OvmfPkgX64.dsc 修改如下:

!ifdef $(CSM_ENABLE)
  OvmfPkg/8259InterruptControllerDxe/8259.inf
  OvmfPkg/8254TimerDxe/8254Timer.inf
!else
  OvmfPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf
!endif
  #LABZ_Debug_Start
  OvmfPkg/ zMessage / zMessage.inf
  #LABZ_Debug_End 
  OvmfPkg/IncompatiblePciDeviceSupportDxe/IncompatiblePciDeviceSupport.inf
  OvmfPkg/PciHotPlugInitDxe/PciHotPlugInit.inf

在 \OvmfPkg\OvmfPkgX64.fdf 修改如下:

!ifdef $(CSM_ENABLE)
  INF  OvmfPkg/8259InterruptControllerDxe/8259.inf
  INF  OvmfPkg/8254TimerDxe/8254Timer.inf
!else
  INF  OvmfPkg/LocalApicTimerDxe/LocalApicTimerDxe.inf
!endif
#LABZ_Debug_Start
INF  OvmfPkg/ zMessage / zMessage.inf
#LABZ_Debug_End
INF  OvmfPkg/IncompatiblePciDeviceSupportDxe/IncompatiblePciDeviceSupport.inf
INF  OvmfPkg/PciHotPlugInitDxe/PciHotPlugInit.inf
INF  MdeModulePkg/Bus/Pci/PciHostBridgeDxe/PciHostBridgeDxe.inf

使用如下命令在 QEMU 上测试:

qemu-system-x86_64 -bios ovmf.fd  -net none 

可以看到在启动过程中屏幕上输出了定义的字符串。

生成的FFS文件在\Build\OvmfX64\DEBUG_VS2019\FV\Ffs\00002237-2024-0513-A7F3-1449F9E0E4BDzMessage目录中(为了方便查找,我们在 INF中指定模块的 GUID 是 0000开头的)。文件名是 00002237-2024-0513-A7F3-1449F9E0E4BD.ffs。这就是我们编译后的模块。

接下来,使用 MMTOOL打开 LattePanda官方提供的 IFWI 文件:

我们选择在Volume 03:02-01 Index 137 的地方插入(理论上 UEFI模块在BIOS中的位置不影响加载顺序,这里只是为了方便查找随便选择的位置),下面就是完成后的结果:

最后,使用“Save Image as…”重新保存命名为 zMessage.fd,然后烧录到 LattePanda上即可。

本文提到的 UEFI 源代码:

UEFI 源代码生成的 FFS 文件:

最终加入模块的 LattePanda 的IFWI:

虚拟电池

之前我设计过一款虚拟电池【参考1】,美中不足的是这个虚拟电池是基于 WDTF。如果想使用就必须安装微软的 WDTF 框架,但是你无法知道WDTF 对系统有多少影响。

因此,最近请天杀帮忙重新设计了一款虚拟电池驱动,用于 Windows 10/11 。驱动可以在系统中虚拟出一块电池,配合应用程序可以设置是否有外部电源(AC/DC),以及设置当前电量。实现简单,可以广泛用于系统功耗测试和一些电源相关功能的评估。

使用方法:

1.运行 Driver 中 Install.bat 安装驱动
2.使用 Batset.exe 在命令下下设置虚拟电池属性。例如: BatSet AC 50 将会切换为 AC Mode, 50% 电量。BatSet DC 19 将会切换为 DC Mode, 19% 电量。
3.运行 Driver 下的 remove.bat 即可移除驱动

参考:
1.https://www.lab-z.com/zvb/

工作的测试视频:

https://www.bilibili.com/video/BV1ef421175X

WordPress上传图片错误:不是合法的JSON响应

最近更换服务器之后,在编写文章的时候上传图片经常会出现“WordPress上传图片错误:不是合法的JSON响应”的问题。虽然收到报错,但是在媒体中确实也能看到上传后的图片因此一直没有在意,对付着在用。

最近有点时间,于是静下来仔细研究了一下这个问题。

第一步,打开Wordpress 的 Debug log,但是发生现象的时候Log中没有 Error;

第二步,关闭 WordPress 的相关插件,现象仍然存在;

第三步,使用 Chrome 的调试功能,发现出现现象的时候,浏览器收到了 Server Internal 500 错误,因此,这个问题和 WordPress没有关系,应该和服务器有关系;

最终,找到了错误原因:PHP 设置的临时目录指向了错误的目录,修改之后就正常了。

遇到类似问题的朋友不妨试试看。

VirtualBox 关闭 Secure Boot

默认情况下 VirtualBox 创建的虚拟机 SecureBoot 是 Enabled。可以通过按住 ctrl 从菜单重启的方式进入 BIOS Setup

选择 “User a device”

选择 “UiApp” 就能够进入 Setup 界面了

具体选项在“Device Manager->Secure Boot Configuration->Attempt Secure Boot”,去掉对勾即可:

可以看到此时 Secure Boot 已经关闭了

此外,在创建虚拟机的地方也有一个选项,不过看起来没起作用。

记事本.LOG 的秘密

Windows 自带的记事本(Notepad)是一个非常方便易用的文本编辑工具。虽然我从Window3.1开就使用这个软件,但是最近刚知道它有一个特别的功能:如果在文本开头写入  .LOG ,那么保存之后每次使用记事本打开这个文件都会自动添加当前的日期和时间。

这个功能是记事本代码实现的,在https://github.com/vxiiduu/NotepadEx 这个项目上可以看到(这个项目代码来自泄露的 Windows XP 代码)。在其中的 npfile.c 文件中可以看到检测 “.LOG”字样的代码:

       // null terminate it.  Safe even if nChars==0 because it is 1 TCHAR bigger
       *(lpch+nChars)= (TCHAR) 0;
   
       // Set 'fLog' if first characters in file are ".LOG"
       fLog= *lpch++ == TEXT('.') &amp;&amp; *lpch++ == TEXT('L') &amp;&amp;
             *lpch++ == TEXT('O') &amp;&amp; *lpch == TEXT('G');

之后,同样文件还有根据标志位进行动作的代码:

    /* If file starts with ".LOG" go to end and stamp date time */
    if (fLog)
    {
       SendMessage( hwndEdit, EM_SETSEL, (WPARAM)nChars, (LPARAM)nChars);
       SendMessage( hwndEdit, EM_SCROLLCARET, 0, 0);
       InsertDateTime(TRUE);
}

为了验证猜想,我对上述代码进行修改,判断条件之后除了增加时间再增加一段我自定义的字符串:

    /* If file starts with ".LOG" go to end and stamp date time */
    if (fLog)
    {
       SendMessage( hwndEdit, EM_SETSEL, (WPARAM)nChars, (LPARAM)nChars);
       SendMessage( hwndEdit, EM_SCROLLCARET, 0, 0);
       InsertDateTime(TRUE);
	   //LABZ_Debug_Start
	   TCHAR szMsg[] = TEXT("www.lab-z.com");
	   SendMessage(hwndEdit, EM_REPLACESEL, TRUE, (LPARAM)szMsg);
	   //LABZ_Debug_End
}

编译之后用生成的 EXE 打开 TEXT 文件,可以看到除了时间,还增加了我自定义的代码。

未修改的完整项目可以在这类下载(就是 Github 上的源代码):

工作的视频

https://www.bilibili.com/video/BV1Ym421H7zk

BMX60 旋转一只兔子

一直以来我想通过传感器让现实世界中的物体控制电脑中的物体姿态,最近入手了DFRobot的BMX160九轴加速度传感器模块【参考1】。

所谓9轴指的是三个加速度轴,三个地测轴和三个陀螺仪轴。简单的介绍一下需要了解的物理知识:首先,如果物体发生运动,那么在三个轴方向会出现加速度,那么我们可以知道它运动的指向,此外比较特别的是因为重力影响,所以一直会存在一个加速度G;接下来因为地磁的存在(指南针的基本原理),我们可以通过地磁轴得到相对于地磁的方向;最后陀螺因为惯性,它会一直保持指向,因此陀螺仪的轴可以用来反映当前时间和上一个时间姿态区别,得到诸如角速度这种方向。

很明显,如果单纯得到上面的9个参数,我们仍然难以用其中的一组描述物体当前的状态,因此,需要经过算法处理得到当前的状态。这个过程叫做“姿态解算”,同样也叫做姿态分析、姿态估计、姿态融合。姿态解算是根据IMU(惯性测量单元)数据(陀螺仪、加速度计、地磁计等)求解出飞行器的空中姿态,所以也叫做IMU数据融合。【参考2】

因为本人能力有限,无力应对复杂的数学,因此这里通过硬件和现成的库来解决。

DFRobot的BMX160引脚定义如下:

丝印功能描述
VCC电源正极
GND电源负极
SCLI2C时钟线
SDAI2C数据线
CSBBMX160协议选择引脚
INIT1BMX160外部中断1
INIT2BMX160外部中断2
ADDRI2C地址选择

需要特别注意的是 CSB 是一个 SPI 和 I2C 选择引脚,这次我们使用I2C, 因此只需要链接 VCC GND  SCL 和 SDA 引脚即可。我是用的是 ESP32 FireBeetle 进行测试,

连接好之后推荐使用 DFRobot 的BMX160库先进行测试【参考4】,确保串口能看到输出。

硬件部分非常简单,接下来要进行软件方面的编写。同样,还需要了解一些关于姿态描述的基本知识。描述姿态的主要方法有两种:欧拉角和四元数。下图是欧拉角的直观描述。三个参数分别是航向角、俯仰角和横滚角。

需要注意的是对于欧拉角有两种:动态和静态欧拉角。动态欧拉角指的是轴跟着物体姿态旋转,比如,下图中物体圆转之后蓝色XYZ坐标系发生了变化,变成了红色的XYZ,如果再次旋转,那么航向角、俯仰角和横滚角都是要相对于红色XYZ的。静态欧拉角的话,就是一直使用蓝色 XYZ。从资料上看,通常我们提到的欧拉角都是动态欧拉角。但是很多三维设计软件使用的是静态欧拉角。

欧拉角非常直观容易理解。但是在使用中,这种描述方法会存在“万向节死锁(gimbal lock)”的问题。就是说在一些特别的角度,两个轴重合了,然后再也不会分开,继续旋转时欧拉角只有2个参数会变。B站有很多视频讲述这个问题有兴趣的朋友可以看看。

为了解决这个问题,引入了四元数的概念。四元数没有欧拉角那么直观,也非常难以理解。

这次的目标是使用 Adafruit 提供的库和网站来直观的实现旋转一只兔子。

首先安装 BMX160 和 Adafruit_Sensor库;之后,烧录下面的程序:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Mahony_BMX160.h>
#include <Madgwick_BMX160.h>
#include <DPEng_BMX160.h>


// Create sensor instance.
DPEng_BMX160 dpEng = DPEng_BMX160(0x160A, 0x160B, 0x160C);

// Mag calibration values are calculated via ahrs_calibration example sketch results.
// These values must be determined for each baord/environment.
// See the image in this sketch folder for the values used
// below.

// Offsets applied to raw x/y/z mag values
float mag_offsets[3]            = { 9.83F, 4.42F, -6.97F };

// Soft iron error compensation matrix
float mag_softiron_matrix[3][3] = { {  0.586,  0.006,  0.001 },
                                    {  0.006,  0.601, -0.002 },
                                    {  0.001,  -0.002,  2.835 } };

float mag_field_strength        = 56.33F;

// Offsets applied to compensate for gyro zero-drift error for x/y/z
float gyro_zero_offsets[3]      = { 0.0F, 0.0F, 0.0F };

// Mahony is lighter weight as a filter and should be used
// on slower systems
Mahony_BMX160 filter;
//Madgwick_BMX160 filter;

void setup()
{
  Serial.begin(115200);

  // Wait for the Serial Monitor to open (comment out to run without Serial Monitor)
  // while(!Serial);

  Serial.println(F("DPEng AHRS Fusion Example")); Serial.println("");

  // Initialize the sensors.
  if(!dpEng.begin(BMX160_ACCELRANGE_4G, GYRO_RANGE_250DPS))
  {
    /* There was a problem detecting the BMX160 ... check your connections */
    Serial.println("Ooops, no BMX160 detected ... Check your wiring!");
    while(1);
  }
  
  filter.begin();
}

void loop(void)
{
  sensors_event_t accel_event;
  sensors_event_t gyro_event;
  sensors_event_t mag_event;

  // Get new data samples
  dpEng.getEvent(&accel_event, &gyro_event, &mag_event);

  // Apply mag offset compensation (base values in uTesla)
  float x = mag_event.magnetic.x - mag_offsets[0];
  float y = mag_event.magnetic.y - mag_offsets[1];
  float z = mag_event.magnetic.z - mag_offsets[2];

  // Apply mag soft iron error compensation
  float mx = x * mag_softiron_matrix[0][0] + y * mag_softiron_matrix[0][1] + z * mag_softiron_matrix[0][2];
  float my = x * mag_softiron_matrix[1][0] + y * mag_softiron_matrix[1][1] + z * mag_softiron_matrix[1][2];
  float mz = x * mag_softiron_matrix[2][0] + y * mag_softiron_matrix[2][1] + z * mag_softiron_matrix[2][2];

  // Apply gyro zero-rate error compensation
  float gx = gyro_event.gyro.x + gyro_zero_offsets[0];
  float gy = gyro_event.gyro.y + gyro_zero_offsets[1];
  float gz = gyro_event.gyro.z + gyro_zero_offsets[2];

  // Update the filter
  filter.update(gx, gy, gz,
                accel_event.acceleration.x, accel_event.acceleration.y, accel_event.acceleration.z,
                mx, my, mz,
				mag_event.timestamp);

  // Print the orientation filter output
  // Note: To avoid gimbal lock you should read quaternions not Euler
  // angles, but Euler angles are used here since they are easier to
  // understand looking at the raw values. See the ble fusion sketch for
  // and example of working with quaternion data.
  float roll = filter.getRoll();
  float pitch = filter.getPitch();
  float heading = filter.getYaw();
  //Serial.print(millis());
  Serial.print("Orientation: ");
  Serial.print(heading);
  Serial.print(",");
  Serial.print(pitch);
  Serial.print(",");
  Serial.println(roll);

  delay(100);
}

在串口管理器中看到输出表明工作正常之后就可以进行下一步。

我们需要网页读取串口数据,在 Chrome 上地址栏输入 chrome://flags,打开 Experimental Web Platform features:

之后,打开 https://adafruit.github.io/Adafruit_WebSerial_3DModelViewer/ 页面,设置波特率为 115200 , 同时选择“Euler Angles”:

接下来选择使用的串口(记得要关闭 Arduino 的串口,否则无法打开)

然后你就可以随心所欲的旋转,屏幕上的兔子会跟着你的板卡转动。

参考:

  1. https://www.dfrobot.com.cn/goods-2947.html
  2. https://www.cnblogs.com/codeit/p/15832606.html
  3. https://wiki.dfrobot.com.cn/_SKU_SEN0373_BMX160_9-axis_sensor_module
  4. https://github.com/DFRobot/DFRobot_BMX160
  5. https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/webserial-visualizer

LattePanda 主板更换开机Logo 方法

LattePand使用 AMI BIOS , 使用官方工具可以方便的更换开机 Logo。

第一步:从官方网站上下载IFWI 文件(BIOS+CSME, 16MB)。接下来的操作都是基于这个版本进行的;

第二步,使用AMI Change Logo工具,Load Image 选中上面提到的IFWI 文件,然后通过Save Logo 保存内置的Logo。

保存后的 Logo是 BMP 格式。

查看属性这是一个800X400,24Bits的BMP 图片,通常情况下,BIOS能够支持 BMP和 JPEG 格式,但是保险起见,最好使用和之前的Logo相同的格式。例如,这里我们就更换图片为双鸭山大学的Logo,分辨率相同,同样是24Bit BMP格式。

之后继续使用Change Logo 软件,使用Browse 选择上面这个图片。最终使用 Save Image As 保存为另外一个文件就制作好了替换Logo的IFWI。

第三步,我们使用FPTW 进行刷新,同样这个是LattePanda官方提供的软件。在管理员模式下的 cmd 窗口使用fptw -f newlogo.bin 即可刷写​:

​特别注意:刷写 IFWI 有风险,最好提前准备 ​SPINOR 烧录器,如果烧写失败随时可以挽救回来

常用测试工具以及软件下载

最近购买了一个网盘,可以直接下载。如果直接点击无法打开,请拷贝链接然后在新窗口中打开。

HE_HE_v1.25.03.16_Full 好用的硬件访问读取工具,完全版 (186.38M)

HE_HE_v1.25.03.16_Lite 好用的硬件访问读取工具,标准版 (4.73MB)

AutoWim 一款帮助你安装 Wim 文件的 WinPE

WinPELabz.zip 用于全盘备份的工具

LattePanda 官方资料 来自 GitHub 的LattePanda 资料 (2024年4月30日)

FileZilla_3.66.5 一个 FTP 工具

Potplayer 一个视频播放软件,可以用于多种视频格式的播放

vokoscreenNG-4.0.0-win64 录制本地屏幕内容为视频的工具

合金弹头1-7模拟器 用于性能评估的游戏

DiskGenius 5.6.1 硬盘分区工具

ffmpeg-2025-03-06-git-696ea1c223-essentials_build.7z FFmpeg 工具

RaptorLake-P UART Shell 测试工具

这个工具是用来给 RaptorLake-P Shell 下进行 Uart 测试的工具,它通过 PCH UART输出字符。

在使用之前,请保证:

  1. PCH 对应的 UART 已经 Enable, 在 Shell 下能够看到对应的PCI Controller;
  2. UART 对应的 GPIO 已经设置为 Native UART 功能。

使用方法:

zu4r &lt;UART 编号>

例如, zu4r 0 将会从第一个 PCH UART对应的引脚,以 115200 波特率输出 www.lab-z.com。

程序在这里下载(无源代码)

Ch32x033 Arduino 环境USB 键盘开发

目前已经有 Ch32x035 的 Arduino 开发环境,在 https://github.com/openwch/arduino_core_ch32 可以看到。美中不足的是这套环境中没有提供 USB 的支持。经过研究可以在代码中加入官方示例代码来实现 USB 功能。这次演示的是在 Ch32X033 板子上实现USB 键盘每隔一段时间输入字符的功能。

第一步,按照上面提到的方法安装 ch32x035的Arduino 支持;

第二步,编写代码。这里参考了Ch55xduino 的方法,创建了 src 目录,然后在这个目录中再创建userUsbKB 目录,对于 USB 支持的代码都在其中。基本上相当于将 WCH 官方例子文件都放置在此。

最后,编写Arduino 代码。基本想法是:将按键数据放置在 Buffer 中,然后使用USBFS_Endp_DataUp() 函数即可发送出去。

#include "src\\userUsbKB\\usbdKBMS.h"

uint8_t  Buffer[ 8 ];
long int Elsp;
boolean Flag=true;

void setup() {
  Serial2.begin(115200);
  delay(500);
  /* Usb Init */
  USBFS_RCC_Init();
  USBFS_Device_Init( ENABLE , PWR_VDD_SupplyVoltage( ));
  USB_Sleep_Wakeup_CFG( );
  Elsp=millis();
}



void loop() {
  /* Determine if enumeration is complete, perform data transfer if completed */
//Serial.println(millis());
  if ( USBFS_DevEnumStatus )
  {
    //Serial.println(millis());
    /* Handle keyboard scan data */
    KB_Scan_Handle(  );

    /* Handle keyboard lighting */
    KB_LED_Handle( );

    /* Handle mouse scan data */
    MS_Scan_Handle( );

    /* Handle USART2 receiving data */
    USART2_Receive_Handle( );
    if ((millis()-Elsp>5000)&&(Flag)) {
        Buffer[2]=0x0F; //"L"
        Buffer[3]=0x04; //"A"
        Buffer[4]=0x05; //"B"
        Buffer[5]=0x2D; //"-"
        Buffer[6]=0x1D; //"Z"
        
        USBFS_Endp_DataUp( DEF_UEP1, Buffer, sizeof( Buffer ), DEF_UEP_CPY_LOAD );
        memset(Buffer,0,sizeof(Buffer));
        Serial2.println("Send press");
        Flag=false;
      } 
    if (millis()-Elsp>5010) {
        USBFS_Endp_DataUp( DEF_UEP1, Buffer, sizeof( Buffer ), DEF_UEP_CPY_LOAD );
        Serial2.println("Send Release");
        Elsp=millis();
        Flag=true;
      } 

  }
}

这个只是一个简单的Demo 还并不完善,最好的状态是类似 Arduino Leonardo ,用面向对象的方法将所需要的完整封装起来这样才更便于使用。