FireBeetle 制作一个LED矩阵

这次介绍的项目是通过 FireBeetle ESP32 实现一个 8*16的单色LED矩阵,可以在上面实现一些简单的图形和动画效果。

在开始之前,首先介绍LED 的静态驱动和动态驱动的概念。当我们在一个发光二极管两端加上一个电压的时候,发光二极管即可工作。理论上,如果驱动N个共阴极的LED那么需要N个提供正电压。这种带来一个问题,如果需要驱动大量的LED,那么就同样需要同样数量的引脚作为正极。对于单片机来说,会遇到IO引脚不够的问题。

发光二极管/LED符号

这种直接驱动的方式称作“静态显示驱动”。与之相对,还可以通过构成矩阵的方式来进行驱动。以3×3的LED矩阵为例,通过6个IO 引脚可以驱动9个LED。

3×3 LED 矩阵

可以看到这种电路,我们可以一次性点亮一行或者一列上的LED,但是如果要点亮的位于不同的行列就会出现问题。例如:我们希望在矩阵上点亮LED0和LED6, 那么需要Y0、Y2为高,X0 为低,这种情况比较简单;但是如果需要同时点亮LED0和 LED4 问题就变得麻烦。因为 LED0 要求Y0 为高X0为低才能点亮,LED4 要求Y1为高X1为低,但是Y1为高X0为低时LED3也会同时亮起。因此,这里需要引入一个分时点亮的方法,比如,先设置Y0 为高X0为低点亮LED0,再设置Y0为低熄灭LED0,设置Y1为高X1为低点亮LED4,只要点亮速度足够快眼睛无法分辨出他们不是同时点亮的。这就是所谓的“动态扫描”。

如果使用 Arduino 编写代码,通常需要使用一个定时中断:

Void timerInterrupt()
{
熄灭上一次的行,点亮第x行
x=x+1
}

可以看出,这样的方法会使得程序复杂度上升,同样的,对于N 个灯需要根号N 个 IO。经过研究发现一个好玩的 IC: WCH 的 CH423。它是IC I/O 扩展芯片,功能如下【参考1】:

CH423 功能
  • 通过两线串行接口远程扩展出8 个通用输入输出引脚GPIO 和16 个通用输出引脚GPO。
  • 内置电流驱动级,连续驱动电流不小于15mA,OC 引脚输出1/16 脉冲灌电流不小于120mA。
  • 静态显示驱动方式支持24 只发光管LED 或者3 位共阳数码管。
  • 分时动态扫描显示驱动方式支持128 只发光管LED 或者16 位共阴数码管,支持亮度控制。
  • 双向I/O 引脚在输入方式下具有输入电平变化时产生中断的功能,中断输出低电平有效。
  • 16 个通用输出引脚可以选择推挽输出或者开漏输出。
  • 支持3V~5V 电源电压,支持低功耗睡眠,可以被输入电平变化唤醒。
  • 高速2 线串行接口,时钟速度从0 到1MHz,兼容两线I2C 总线,节约引脚。
  • 提供SDIP28 和SOP28 两种无铅封装,兼容RoHS。

这次的试验就使用这个芯片来实现一个 8×16的LED点阵。

首先进行电路设计:

控制电路

芯片是I2C 接口,控制线路非常简单:SCl和SDA就好了。CH423是SOP28封装,为了便于试验我从淘宝购买了一个SOP28转DIP的小PCB,焊接之后将CH423插入到PCB上。接下来是LED矩阵的设计:

LED 矩阵 8×16

其中 SE[N] 信号能够输出高低电平,DEG[M] 只用作吸收电流使用。对CH423 发送命令,告知我现在要做动态扫描使用,然后告知SE[N]和DEG[M] 的组合即可。例如:告知SE1 输出高,DIG0 吸收电流和SE7输出高,DIG15吸收电流,之后芯片本身会动态控制,肉眼看起来就是 L00和L7F 点亮【参考2】。

因为都是低速信号,没有太多限制,摆放好 LED后直接使用立创自动布线走的通即可。

PCB设计

焊接后的样子:

焊接后的实物

编写一个测试代码如下:

#include <Wire.h>
 
// CH423接口定义
#define     CH423_I2C_ADDR1     0x20         // CH423的地址
#define     CH423_I2C_MASK      0x3E         // CH423的高字节命令掩码
 
#define CH423_SYSON1    0x0417    //开启自动扫描显示
 
unsigned char CH423_buf[16];    //定义16个数码管的数据映象缓存区
const unsigned char BCD_decode_tab[ 0x10 ] = { 0X3F, 0X06, 0X5B, 0X4F, 0X66, 0X6D, 0X7D, 0X07, 0X7F, 0X6F, 0X77, 0X7C, 0X58, 0X5E, 0X79, 0X71 };
 
 
void CH423_Write( uint32_t cmd )    // 写命令
{
  Serial.print("Address ");
  Serial.print(( unsigned char )(cmd >> 8), HEX);
  Serial.print("  command  ");
  Serial.print(( unsigned char ) (cmd & 0xff), HEX);
  Wire.beginTransmission (( unsigned char )(cmd >> 8));
  Wire.write( ( unsigned char ) (cmd & 0xff) );  // 发送数据
  // 结束总线
  if (Wire.endTransmission() == 0) {
    Serial.println(" I2C Success!");
  } else {
    Serial.println("I2C error!");
  }
}
 
// 向CH423输出数据或者操作命令,自动建立数据映象
void CH423_buf_write( uint32_t cmd )
{
  if ( cmd & 0x1000 )
  { // 加载数据的命令,需要备份数据到映象缓冲区
    CH423_buf[ (unsigned char)( cmd >> 8 ) & 0x0F ] = (unsigned char)( cmd & 0xFF );    // 备份数据到相应的映象单元
  }
  CH423_Write( cmd );    // 发出
}
 
void setup() {
  Serial.begin (115200);
  Wire.begin (21, 22);   // sda= GPIO_21 /scl= GPIO_22.
  /* INTENS [00-11]
     OD_EN 使能开漏
     X_INT 0x08
     DEC_H 0x04
     DEC_L 0x02
     IO_OE 0x01
  */
  CH423_buf_write( 0x2417 );
  /* OC_L_DAT  OC7-OC0 电平控制
  */
  CH423_buf_write( 0x2200 );
  /* OC_H_DAT  OC15-OC8 电平控制
  */
  CH423_buf_write( 0x2300 );
 
  // 初始化时保持全灭
  uint32_t i;
  for (i = 0; i < 16; i++) {
    CH423_buf_write(((0x30 + i) << 8) + 0x00);
  }
}
 
// 要显示的字符取模, DFRobot 字样
// 来自 https://www.zhetao.com/fontarray.html
const unsigned char bitmap_bit_bytes[] = {
  0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
  0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
  0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
  0b11111000, 0b11111100, 0b11111100, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
  0b01000100, 0b01000010, 0b01000010, 0b00000000, 0b11000000, 0b00000000, 0b00000000,
  0b01000010, 0b01001000, 0b01000010, 0b00000000, 0b01000000, 0b00000000, 0b00010000,
  0b01000010, 0b01001000, 0b01000010, 0b00000000, 0b01000000, 0b00000000, 0b00010000,
  0b01000010, 0b01111000, 0b01111100, 0b00111100, 0b01011000, 0b00111100, 0b01111100,
  0b01000010, 0b01001000, 0b01001000, 0b01000010, 0b01100100, 0b01000010, 0b00010000,
  0b01000010, 0b01001000, 0b01001000, 0b01000010, 0b01000010, 0b01000010, 0b00010000,
  0b01000010, 0b01000000, 0b01000100, 0b01000010, 0b01000010, 0b01000010, 0b00010000,
  0b01000010, 0b01000000, 0b01000100, 0b01000010, 0b01000010, 0b01000010, 0b00010000,
  0b01000100, 0b01000000, 0b01000010, 0b01000010, 0b01100100, 0b01000010, 0b00010010,
  0b11111000, 0b11100000, 0b11100011, 0b00111100, 0b01011000, 0b00111100, 0b00001100,
  0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
  0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
};
 
// 显示一个动画效果
uint16_t buf[8] = {
  0b0100000000000000,
  0b0010000000000000,
  0b0001000000000000,
  0b0000100000000000,
  0b0000010000000000,
  0b0000100000000000,
  0b0001000000000000,
  0b0010000000000000,
};
 
void loop() {
  uint16_t i, j, m;
  char c, v;
  while (Serial.available()) {
    c = Serial.read();
    // 显示卡面定义的字符
    if (c == '1') {
      for (i = 0; i < 7 ; i++) { //一共有7个字符
        for (j = 0; j < 16; j++) { // 每个字符有16个1Byte数据
          CH423_buf_write( ((0x30 + j) << 8) + bitmap_bit_bytes[i + j * 7] );
        }
        delay(500);
      }
    }
    // 随机点亮测试
    if (c == '2') {
      for (i = 0; i < 16; i++) {
        for (j = 0; j < 16; j++) {
          CH423_buf_write( ((0x30 + j) << 8) + random(0, 256) );
        }
        delay(500);
      }
    }
    // 移动的动画效果
    if (c == '3') {
      for (m = 0; m < 32; m++) {
        // 显示 buf 定义的图形
        for (i = 0; i < 16; i++)
        {
          v = 0;
          for (j = 0; j < 8; j++) {
            if ((buf[j] & (1 << i)) == 0) {
              v = v << 1;
            }
            else {
              v = (v << 1) + 1;
            }
          }
          CH423_buf_write( ((0x30 + i) << 8) + v );
        }
        // 移动 buf 字符
        for (i = 0; i < 8; i++) {
            if ((buf[i]&1)!=0) {buf[i]=buf[i]|0x8000;}
            buf[i]=buf[i]>>1;
          }
        delay(100);  
 
         
      }
    }
  }
 
}

根据串口输入,进行不同的测试:

  1. 输入1 会逐个显示 DFRobot 字样
  2. 输入2会随机点亮
  3. 输入3会显示一个方向的简单动画效果。

参考:

  1. http://www.wch.cn/products/CH423.html
  2. 这部分在DataSheet有描述 “8.2. 动态显示驱动 CH423 的动态显示驱动方式用于驱动 128 只 LED 或者 16 只共阴数码管,由 IO7~IO0 引脚分别驱动共阴数码管的各个段引脚(各数码管并联),由 OC15~OC0 引脚分别驱动各个共阴数码管的公共端。单片机在加载完所有字数据后,开启 DEC_L 和 DEC_H 控制位由 CH423 自动地进行分时动态显示扫描。如果只需要驱动 8 只数码管,那么可以只开启 DEC_L 或者 DEC_H 其中的一个控制位,剩余的另CH423 中文手册”

工作的测试视频

MemTest86 Free 最新版

最近在测试 Memory 的时候偶然发现老版本的 MemTest86 在运行时会发生死机,新版本可以正常使用。于是,动手制作了一个精简版:

MemTest2023下载

解压之后得到 MemTest2023.IMG 文件,可以直接放在Ventoy 制作出来的启动盘上,开机启动之后从菜单选择这个文件即可进入 Memtest86。

参考:

1.https://www.memtest86.com/ 官方网站

C# 读取物理硬盘的例子

本质上和VC 写的没有区别,都是使用 CreateFile 打开 PhysicalDrive 然后进行操作。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.InteropServices;
 
namespace RW_PyhsicalDisk
{
    class Program
    {
        [DllImport("kernel32", SetLastError = true)]
        static extern IntPtr CreateFile(
        string FileName,
        uint DesiredAccess,
        uint ShareMode,
        IntPtr SecurityAttributes,
        uint CreationDisposition,
        int FlagsAndAttributes,
        IntPtr hTemplate
        );
 
        static class DESIREDACCESS
        {
            public const uint GENERIC_READ = 0x80000000;
            public const uint GENERIC_WRITE = 0x40000000;
            public const uint GENERIC_EXECUTE = 0x20000000;
            public const uint GENERIC_ALL = 0x10000000;
        }
        /// <summary>
        /// Sharing mode of the file or object
        ///</summary>
        static class SHAREMODE
        {
            public const uint FILE_SHARE_READ = 0x00000001;
            public const uint FILE_SHARE_WRITE = 0x00000002;
            public const uint FILE_SHARE_DELETE = 0x00000004;
        }
        /// <summary>
        /// Action to take on files that exist, and which action to take when files do not exist.
        /// </summary>
        static class CREATIONDISPOSITION
        {
            public const uint CREATE_NEW = 1;
            public const uint CREATE_ALWAYS = 2;
            public const uint OPEN_EXISTING = 3;
            public const uint OPEN_ALWAYS = 4;
            public const uint TRUNCATE_EXISTING = 5;
        }
        /// <summary>
        /// File attributes and flags for the file.
        /// </summary>
        static class FLAGSANDATTRIBUTES
        {
            public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
            public const uint FILE_FLAG_OVERLAPPED = 0x40000000;
            public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
            public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000;
            public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
            public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
            public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
            public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000;
            public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
            public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
            public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
        }
 
        static void Main(string[] args)
        {
            IntPtr handle = CreateFile(@"\\.\PHYSICALDRIVE1",
                            DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE,
                            SHAREMODE.FILE_SHARE_READ | SHAREMODE.FILE_SHARE_WRITE,
                            IntPtr.Zero,
                            CREATIONDISPOSITION.OPEN_EXISTING,
                           0,
                IntPtr.Zero);
            FileStream disk = new FileStream(handle, FileAccess.ReadWrite);
            byte[] bt = new byte[512];
            disk.Seek(0, SeekOrigin.Begin);
            disk.Read(bt, 0, 512);
            for (int i = 0; i < bt.Length; i++)
            {
                Console.Write(bt[i].ToString("x2") + " ");
            }
 
            Console.ReadLine();
        }
    }
}

运行结果:

EDK2 202302 来了

今年的3月3日,edk2-stable202302 版本正式发布,需要的朋友可以在下面的链接下载到

https://github.com/tianocore/edk2/releases/tag/edk2-stable202302

新增了如下功能:

看起来和我们 X86 UEFI 开发者关系不大。

前面介绍过下载之后手工补充编译一些第三方依赖库的方法,此外,可以使用下面的方法直接下载完整的代码:

git clone -b edk2-stable202302 https://github.com/tianocore/edk2.git --recursive

这里放置一个下载好的完整代码还包括了AppPkg,第一次运行时最好先使用下面的命令重新编译Build代码所需工具:

edksetup.bat ForceRebuild

我测试了如下几个 Package 都可以正常编译通过:

build -a X64 -t VS2019 -p AppPkg\AppPkg.dsc 
build -a X64 -t VS2019 -p OvmfPkg\OvmfPkgX64.dsc
build -a X64 -t VS2019 -p EmulatorPkg\EmulatorPkg.dsc

这里放一个完整版本,有需要的朋友可以取用。

链接: https://pan.baidu.com/s/1vm1SDSe7PB_MtntsZIZyiQ?pwd=LABZ 提取码: LABZ

CH32V208 USB上手指南

本文介绍 CH32V208 评估板、开发板如何进行 USB 方面的测试。CH32V208 支持2个USB 接口,可以独立工作,其中一个可以作为Host和Device(USB2),另外一个只能作为Device 使用(USB1)。

一.硬件接口介绍。板子上的 USB 接口分布如下:

CH32V208 评估板

需要注意的是如下几点:

  1. P5和J1 是连通的(USB2),换句话它两个实际上是同一个。虽然 P5 是USB 母头,但是可以通过转接为公头作为设备使用:
CH32V208 J1和P5 是连通的

2.P1(USB1),虽然是USB母头,但是只能当作 USB Device 使用,不支持 USB Host功能;

CH32V208 P1 USB TypeA Port

两个USB 都可以作为USB下载接口使用。

二.代码的下载

官方提供了下面的例程:

|-- USBD

|      |-- CH372:模拟自定义USB设备(CH372设备),端点1,3下传,2,4上传,端点1下传的数据从端点3上传,不取反,端点2下传的数据从端点4上传,取反。

|      |-- Compatibility_HID:模拟HID设备,数据通过串口上下传

|      |-- CompositeKM:模拟键盘和鼠标,使用IO模拟按键,同时模拟数据可以通过串口2上传。

|      |-- MSC_U-Disk: 模拟简单U盘,可选使用片内Flash或外部SPI-Flash

|-- USBFS

|      |-- DEVICE

|      |      |-- CH372:模拟自定义USB设备(CH372设备),端点1,3下传,2,4上传,端点1下传的数据从端点3上传,不取反,端点2下传的数据从端点4上传,取反。

|      |      |-- Compatibility_HID:模拟HID设备,数据通过串口上下传。

|      |      |-- CompositeKM:模拟键盘和鼠标,使用IO模拟按键,同时模拟数据可以通过串口2上传。

|      |      |-- MSC_U-Disk: 模拟简单U盘,可选使用片内Flash或外部SPI-Flash

|      |      |-- SimulateCDC:模拟一个CDC串口,使用串口1收发。

|      |      |-- SimulateCDC-HID:模拟一个CDC串口,使用串口1收发,HID中断端点下发数据取反上传。

|      |-- HOST_IAP

|      |      |-- APP: 与HOST_IAP配套使用的APP,工程修改了程序起始位置,编译后需自行将文件转化为bin文件并重命名为APP.bin

|      |      |-- HOST_IAP:  基于U盘读取文件例程整理的主机U盘IAP例程,从U盘内读取名称位APP.bin的文件,写入内部flash,校验后自动跳转。

|      |-- HOST_KM: 主机操作键鼠,获取键鼠上传的端点的数据并打印,支持U口下1级hub

|      |-- HOST_MTP_FileSystem:枚举过程的USB主机到一个支持MTP和PTP协议的设备,支持MTP和PTP协议,并读取其文件

|      |-- Udisk_Lib:U盘文件系统库文件

可以根据需要使用  IDE 打开,编译完成后即可进行烧写,个人推荐方法如下:

  1. 使用USB线连接主机到任意一个USB接口;
  2. 使用关闭开发板供电开关
CH32V208 电源开关

3.按住Download按钮,拨动开关重新上电

CH32V208 下载按钮

4.这时设备中就会出现如下设备

5.使用WCHISPTOOL 选中 CH32V208,需要注意第一次需要去掉保护,之后重复上面步骤2-5即可下载

WCHISPTool 下载工具

下载之后,重启评估板即可工作

Step2FPGA(1) 环境的搭建

一直有学习 FPGA的想法,这次下定决心花时间来学习FPGA。因为 FPGA 相关知识能够帮助更好的理解硬件知识,同时可以使用FPGA来实现验证自己的想法。我选择的开发板和教材是 “至芯携手特权同学Altera Cyclone IV EP4CE6 FPGA开发板NIOSII”:

勇敢的心 伴你玩转 Altera FPGA 书籍和开发板

教材是《勇敢的芯伴你玩转 Altera FPGA》《例说FPGA》《FPGA设计 实战演练(逻辑篇)》,作者都是吴厚航先生(特权同学)。

勇敢的心 伴你玩转 Altera FPGA 和其余两本内容上有所重复

这套书籍和套件是我在 2018年购买的,但是一直没有坚持下去。和学习单片机一样,最大的敌人并不是内容的难度,而是自己是否能够不断坚持学习。因此,这次开始新的系列。

教材上使用的软件比较老(Quartus 13),在新的操作系统上有问题必须升级,这次选择 Quartus 18 的版本。安装文件是QuartusLiteSetup-18.1.0.625-windows.exe

Quartus 18 安装界面

 推荐使用默认路径进行安装,一路Next即可

安装占用大约7G 的空间。

安装选项,推荐默认选项即可

点击 “Finish” 按钮后会自动继续安装 USB-Blaster(下载器):

下载器安装,我购买开发板也选购了配套的下载器

接下来可以从 Windows 菜单中启动 Quartus:

安装后从 Start Menu 上启动

启动后会提示目前没有安装任何 Devices ,需要安装Device Package(这个类似于 Keil 安装好之后,需要安装某一个型号的单片机的支持文件;又好比在 Arduino IDE 上使用 ESP32 需要先从 Board Manger上安装 ESP32支持包):

提示需要安装 Device

关闭上面的软件再回到 Start Menu 选择“Device Installer”

Start menu 启动 “Device Installer”

在这个页面选择存放着 Devices 支持文件的路径(文件后缀是 .qdz)

接下来需要选择要安装的功能如下:

  1. Cyclone IV (其中有开发板的FPGA型号)
  2.  ModelSim (模拟仿真工具,Starter Edition 是免费的,下面的是需要额外购买的收费版本)
这个两个功能会占用硬盘 4.5G 的空间。

再次启动 Quartus,选择 File->Open  Project 打开开发板例子 cy4ex2 项目 cy4.qpf 文件。项目中的各种设置都已经准备好了,使用 Processing -> Start Compilation 即可直接编译:

开始编译
编译成功

接下来使用 “Programmer” 功能将编译后的结果下载到开发板中:

选择这里的 programmer
第一次使用需要用 Setup进行设置
在弹出的界面下拉菜单选择 USB-Blaster
点击 Start 即可下载,右上角会提示当前进度

特别注意,烧写时需要给开发板上电。成功之后,板子上的蜂鸣器一直会发出Beep声。

至此,已经踏出了第一步开始了 FPGA之旅。

一个非常简单的UEFI Application开发框架

通常情况下,我们需要使用 EDK2 来进行 UEFI Shell Application 的开发。这次介绍的是一个用来开发 UEFI Shell Application的框架。项目地址如下:

https://github.com/VioletGiraffe/UEFI-Bootloader

从名字可以看出,作者的目标是用来开发BootLoader,类似的我们可以用来开发UEFI Shell Application。

相比EDK2,这种方法的优点主要是:

  1. 小巧简单。容量只有十几兆,编译起来非常快,另外方便我们研究具体的实现;
  2. 编译环境时 Visual Studio,方便我们阅读代码

具体使用方法是:

1.在 https://github.com/VioletGiraffe/UEFI-Bootloader 下载代码

2.在https://github.com/VioletGiraffe/UEFI-CPP-headers 下载需要的头文件

3.将2下载的内容解压到 UEFI-CPP-headers目录下

4.Visual Studio 打开bootloader.sln文件

5.这个项目作者使用 VS2017,所以需要修改一下 Platform Toolset 为你当前使用的编译器。这里我是用的是 VS2019,修改如下:

6.直接 Build即可生成 BootX64.efi

在WinHost 模拟环境中运行这个程序可以看到屏幕上输出了信息:

有兴趣的朋友可以直接去前面提到的项目主页或者在这里直接下载:

UEFI-CPP-headers-master下载

UEFI-Bootloader-master下载

Step to UEFI (271)资源打包再研究

在之前的文章中介绍过直接将 BMP文件存放在EFI 文件中 【参考1】,本文继续研究如何将其他格式的文件放在EFI 中的方法。

首先,我搜索了一下,没有发现直接将其他格式按照类似的方法放在EFI文件中的方法(从Win32编程的角度来说,这个是以Resource/资源文件的方式存放)。其次,如果将文件直接修改后缀为BMP,在编译过程中,工具会对资源文件进行检查,如果不是BMP将会报错。对于这种,可以将二进制文件通过加文件头的方式伪装成一个BMP文件。但是这样做估计会比较麻烦。

在阅读代码的过程中,处理BMP文件的代码位于 \BaseTools\Source\Python\AutoGen\GenC.py 文件中,可以看到支持三种图形格式,分别是PNG、JPG和BMP。对于后两者,都有针对这种类型的文件分析动作。

if File.Ext.upper() == '.PNG':
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.JPG':
        ImageType, = struct.unpack('4s', Buffer[6:10])
        if ImageType != b'JFIF':
            EdkLogger.error("build", FILE_TYPE_MISMATCH, "The file %s is not a standard JPG file." % File.Path)
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_JPEG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.BMP':
        TempBuffer, TempPalette = BmpImageDecoder(File, Buffer, PaletteIndex, FileObj.TransParent)
        if len(TempPalette) > 1:
            PaletteIndex += 1
            NewPalette = pack('H', len(TempPalette))
            NewPalette += TempPalette
            PaletteBuffer += NewPalette
            PaletteStr = WriteLine(PaletteStr, '// %s: %s: %s' % (DecToHexStr(PaletteIndex - 1, 4), ID, DecToHexStr(PaletteIndex - 1, 4)))
            TempPaletteList = AscToHexList(NewPalette)
            PaletteStr = WriteLine(PaletteStr, CreateArrayItem(TempPaletteList, 16) + '\n')
    ImageBuffer += TempBuffer
    BufferStr = WriteLine(BufferStr, '// %s: %s: %s' % (DecToHexStr(Index, 4), ID, DecToHexStr(Index, 4)))
    TempBufferList = AscToHexList(TempBuffer)
    BufferStr = WriteLine(BufferStr, CreateArrayItem(TempBufferList, 16) + '\n')
 
    StringH.Append(Line)
    Index += 1

于是,首先将【参考1】中的BMP文件后缀修改为 PNG ,然后重新编译。我们知道BMP文件开头的数值是 42 4D 76 6B,使用工具直接查看:

打包后的分拣分析,注意ASCII 的 BM 是 BMP的头

结合之前的知识,结合前面 PNG 打包的如下代码:

TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)
                                    TempBuffer += pack('I', len(Buffer))
                                    TempBuffer += Buffer

猜测格式应该是:

EFI_HII_PACKAGE_LIST_HEADER
EFI_HII_IMAGE_PACKAGE_HDR
Type(EFI_HII_IIBT_IMAGE_PNG==0x19)
Length(len(Buffer)) //这里也就是我们放入的 BMP 的大小

针对上面的内容,编写如下代码进行验证:

/** @file
  Logo DXE Driver, install Edkii Platform Logo protocol.
 
Copyright (c) 2016 - 2017, Intel Corporation. All rights reserved.&lt;BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php
 
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 
**/
 
 
#include &lt;Uefi.h>
#include &lt;Library/UefiLib.h>
#include &lt;Library/ShellCEntryLib.h>
#include &lt;Protocol/HiiDatabase.h>
#include &lt;Protocol/GraphicsOutput.h>
#include &lt;Protocol/HiiImageEx.h>
#include &lt;Protocol/PlatformLogo.h>
#include &lt;Protocol/HiiPackageList.h>
#include &lt;Library/UefiBootServicesTableLib.h>
#include &lt;Library/DebugLib.h>
#include &lt;Library/PrintLib.h>
#include &lt;Library/MemoryAllocationLib.h>
#include &lt;Library/BaseLib.h>
#include &lt;Library/UefiApplicationEntryPoint.h>
#include  &lt;Library/ShellLib.h>
 
#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
  { \
    0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a } \
  }
   
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
 
//DO NOT REMOVE IMAGE_TOKEN (IMG_LOGO)
 
/**
  Entrypoint of this module.
 
  This function is the entrypoint of this module. It installs the Edkii
  Platform Logo protocol.
 
  @param  ImageHandle       The firmware allocated handle for the EFI image.
  @param  SystemTable       A pointer to the EFI System Table.
 
  @retval EFI_SUCCESS       The entry point is executed successfully.
 
**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  EFI_STATUS                  Status;
   
   
   
  EFI_HII_PACKAGE_LIST_HEADER *PackageListHeader;
  EFI_HII_IMAGE_PACKAGE_HDR   *ImageHeader;
  UINT8                       *ImageData;
  UINTN                       FileSize;
  EFI_FILE_HANDLE             FileHandle;
   
        //Step1. Get Package List Header Address
         
        //
        // Retrieve HII package list from ImageHandle
        //
        Status = gBS->OpenProtocol (
                        ImageHandle,
                        &amp;gEfiHiiPackageListProtocolGuid,
                        (VOID **) &amp;PackageListHeader,
                        ImageHandle,
                        NULL,
                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                        );
        if (EFI_ERROR (Status)) {
          Print(L"HII Image Package with logo not found in PE/COFF resource section\n");
          return Status;
        }
                         
 
        Print(L"PackageList :\nGUID=[%g] Length=[%X]\n",
                      PackageListHeader->PackageListGuid,
                      PackageListHeader->PackageLength);
 
        //Step2. Parser HII Image
        ImageHeader=(EFI_HII_IMAGE_PACKAGE_HDR*)(PackageListHeader+1);
        ImageData=(UINT8 *)(ImageHeader+1);
         
        FileSize=ImageData[1]+(ImageData[2]&lt;&lt;8)+(ImageData[3]&lt;&lt;16)+(ImageData[4]&lt;&lt;24);
        Print(L"Type: 0x%x  Size:%d\n",ImageData[0],FileSize);
 
        //Create a new file
        Status = ShellOpenFileByName(L"dump.bin", 
                                        (SHELL_FILE_HANDLE *)&amp;FileHandle,
                                        EFI_FILE_MODE_READ |
                                        EFI_FILE_MODE_WRITE|
                                        EFI_FILE_MODE_CREATE, 
                                    0);  
        if(Status != RETURN_SUCCESS) {
                Print(L"CreatFile failed [%r]!\n",Status);
                return EFI_SUCCESS;
            }   
        Status = ShellWriteFile(FileHandle,
                        &amp;FileSize,
                        &amp;ImageData[5]);
   
        //Close the source file
        ShellCloseFile(&amp;FileHandle);        
         
        Print(L"Dump.bin is generated!\n");
         
  return Status;
}

运行结果:

打包之后运行EFI 就释放资源文件

使用 Beyond Compare 可以看到dump.bin 和TestImage.png内容完全相同。

本文提到的完整文件:

HIIImageTest2下载

参考:

1. https://www.lab-z.com/stu169bmp/  BMP 放在 EFI 文件中(下)

Step to UEFI (270)EDK2: Win Host卡死的问题的解决方法

古语有云:“内事不决问百度,外事不决问谷歌”。对于我来说 Windows 下面有搞不懂的问题就直接问Windows专家天杀了。

最近偶然用到EDK2自带的模拟环境,偶尔会遇到运行WinHost.exe之后卡死,键盘无法输入的问题。之前也有热心的朋友提到过,这是因为输入法导致的。果真,在运行WinHost.exe 之前关闭输入法即可解决问题。

这两个都要关闭

很明显问题是WinHost  Windows下输入法有冲突,于是请教天杀,经过一夜的分析天杀很快给出了建议:

产生问题的原因是在代码在处理WM_IME_SETCONTEXT时出问题了,自绘制窗口需要响应输入法的这个消息。在模拟器中,用户没有输入中文的需求,因此直接禁用输入法是最简单的解决方法。

代码修改的方法是在\EmulatorPkg\Win\Host\WinGopScreen.c 文件中首先使用LoadLibraryEx()函数加载ImmDisableIME函数,然后调用之即可。

typedef WINBASEAPI BOOL  (WINAPI *ImmDisableIMEProc) (DWORD unnamedParam1);
typedef WINBASEAPI HIMC  (WINAPI *ImmAssociateContextProc) (HWND unnamedParam1, HIMC unnamedParam2);
 
/**
  This thread simulates the end of WinMain () application. Each Window needs
  to process its events. The messages are dispatched to
  WinNtGopThreadWindowProc ().
  Be very careful since WinNtGopThreadWinMain () and WinNtGopThreadWindowProc ()
  are running in a separate thread. We have to do this to process the events.
 
  @param  lpParameter            Handle of window to manage.
 
  @return if a WM_QUIT message is returned exit.
 
**/
DWORD
WINAPI
WinNtGopThreadWinMain (
  LPVOID  lpParameter
  )
{
  MSG                    Message;
  GRAPHICS_PRIVATE_DATA  *Private;
  RECT                   Rect;
   
//LABZ_Debug_Start
  HMODULE               Module;
  ImmDisableIMEProc     ImmDisable;
   
  Module = LoadLibraryEx (L"imm32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
  ImmDisable = (ImmDisableIMEProc)GetProcAddress(Module, "ImmDisableIME");
 
  if (ImmDisable)
      ImmDisable(GetCurrentThreadId ());
//LABZ_Debug_End
 
  Private = (GRAPHICS_PRIVATE_DATA *)lpParameter;
  ASSERT (NULL != Private);

经过测试,上述代码运行之后不会有卡死的问题。有兴趣的朋友可以自行实验。