Step to UEFI (177)Setup String ITEM 的 Default Value

有时候,我们需要在Setup 中指定 String 的 Default值,例如:

如果你想直接给他指定一个字符串作为Default,需要添加如下的代码:

1. \MdeModulePkg\Universal\DriverSampleDxe\Vfr.vfr

    //
    // Define a string (EFI_IFR_STRING)
    //
    string    varid    = MyIfrNVData.MyStringData,
              prompt   = STRING_TOKEN(STR_MY_STRING_PROMPT2),
              help     = STRING_TOKEN(STR_MY_STRING_HELP2),
              
              flags    = INTERACTIVE,
              key      = 0x1236,
              minsize  = 6,
              maxsize  = 40,
              inconsistentif prompt = STRING_TOKEN(STR_STRING_CHECK_ERROR_POPUP),
                pushthis != stringref(STRING_TOKEN(STR_STRING_CHECK))
              endif;
              default  = STRING_TOKEN(STR_STRING_CHECK),
endstring;

2. \MdeModulePkg\Universal\DriverSampleDxe\VfrStrings.uni

#string STR_TEST        #language en-US  "LAB-Z"
                        #language fr-FR  "LAB-Z"

编译结果:

比较诡异的是default  = STRING_TOKEN(STR_TEST), 只能放在这个定义的末尾,如果放在中间,比如:

              help     = STRING_TOKEN(STR_MY_STRING_HELP2),
              default  = STRING_TOKEN(STR_TEST),
              flags    = INTERACTIVE,

     在编译的时候会出现下面的错误,不得不不说非常诡异。也正是因为这个原因,这个功能我研究了很久很久…….

Teensy 3.X 使用 UcgLib 的问题

最近在 Teensy 3.2 上使用 ILI9341 的液晶屏,在编译的时候会出现两个错误导致无法完成编译。

1.关于 _NOP 的错误。经过搜索,在 https://github.com/olikraus/ucglib/issues/65 找到有人解决过这样的问题。原帖写的是:

Ucglib.cpp, line 786 – change the …defined(__arm__)… to …defined(__NOT_arm__)
Ucglib.cpp, line 833, add the following:

#ifndef __NOP
#define __NOP __asm__ __volatile__("NOP");
#endif

有可能是因为版本的差别,我在Ucglib.cpp 中修改如下:

Line783:

#if defined(__PIC32MX) || defined(__NOT_arm__) || defined(ESP8266) || defined(ARDUINO_ARCH_ESP8266) || defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_ESP32)

Line 830:

#ifndef __NOP
#define __NOP __asm__ __volatile__("NOP");
#endif

之后即可解决这个问题。

2. u8g_data_port 的错误。修改方法是将 Ucglib.cpp

#if defined(__PIC32MX) || defined(__arm__) || defined(ESP8266) || defined(ARDUINO_ARCH_ESP8266) || defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_ESP32)
/* CHIPKIT PIC32 */
static volatile uint32_t *u8g_data_port[9];
static uint32_t u8g_data_mask[9];
#else

中的 static volatile uint32_t *u8g_data_port[9]; 修改为 static volatile uint8_t *u8g_data_port[9]; 。 这样有可能降低 UcgLib 的通用性,但是编译没问题。

代码中按照下面两种接线和定义都是能够工作正常的

Ucglib_ILI9341_18x240x320_SWSPI ucg(/*sclk=*/ 13, /*data=*/ 11, /*cd=*/ 9, /*cs=*/ 10, /*reset=*/ 8);

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/ 9, /*cs=*/ 10, /*reset=*/ 8);

推荐 HWSPI,速度快很多。

Step to UEFI (176)memset的实现方法

之前的文章“哪里来的的 memset”【参考1】提到过因为编译器擅作主张使用memset优化引起了很诡异的问题。可以通过关闭编译优化来避免错误,这里从代码的角度分析 EDK2 是如何实现 memset 功能的。

  1. \MdePkg\Library\BaseMemoryLib\MemLibGeneric.c 提供了三个函数

InternalMemSetMem16

 InternalMemSetMem32

InternalMemSetMem64

以 InternalMemSetMem16  为例:

/**
  Fills a target buffer with a 16-bit value, and returns the target buffer.

  @param  Buffer  The pointer to the target buffer to fill.
  @param  Length  The count of 16-bit value to fill.
  @param  Value   The value with which to fill Length bytes of Buffer.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem16 (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Length,
  IN      UINT16                    Value
  )
{
  for (; Length != 0; Length--) {
    ((UINT16*)Buffer)[Length - 1] = Value;
  }
  return Buffer;
}

看起来for (; Length != 0; Length–) 这样的定义足够“迷惑”编译器避免优化。

2. \MdePkg\Library\BaseMemoryLib\SetMem.c 提供了InternalMemSetMem()

/**
  Set Buffer to Value for Size bytes.

  @param  Buffer   The memory to set.
  @param  Length   The number of bytes to set.
  @param  Value    The value of the set operation.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Length,
  IN      UINT8                     Value
  )
{
  //
  // Declare the local variables that actually move the data elements as
  // volatile to prevent the optimizer from replacing this function with
  // the intrinsic memset()
  //
  volatile UINT8                    *Pointer8;
  volatile UINT32                   *Pointer32;
  volatile UINT64                   *Pointer64;
  UINT32                            Value32;
  UINT64                            Value64;

  if ((((UINTN)Buffer & 0x7) == 0) && (Length >= 8)) {
    // Generate the 64bit value
    Value32 = (Value << 24) | (Value << 16) | (Value << 8) | Value;
    Value64 = LShiftU64 (Value32, 32) | Value32;

    Pointer64 = (UINT64*)Buffer;
    while (Length >= 8) {
      *(Pointer64++) = Value64;
      Length -= 8;
    }

    // Finish with bytes if needed
    Pointer8 = (UINT8*)Pointer64;
  } else if ((((UINTN)Buffer & 0x3) == 0) && (Length >= 4)) {
    // Generate the 32bit value
    Value32 = (Value << 24) | (Value << 16) | (Value << 8) | Value;

    Pointer32 = (UINT32*)Buffer;
    while (Length >= 4) {
      *(Pointer32++) = Value32;
      Length -= 4;
    }

    // Finish with bytes if needed
    Pointer8 = (UINT8*)Pointer32;
  } else {
    Pointer8 = (UINT8*)Buffer;
  }
  while (Length-- > 0) {
    *(Pointer8++) = Value;
  }
  return Buffer;
}

避免被编译器优化的方法和上面的类似,此外还可以看出这个函数特地用 8 bytes填充提升效率。

3. \MdePkg\Library\UefiMemoryLib\MemLib.c 中的InternalMemSetMem 函数直接调用 gBS 提供的服务

/**
  Fills a target buffer with a byte value, and returns the target buffer.

  This function wraps the gBS->SetMem().

  @param  Buffer    Memory to set.
  @param  Size      The number of bytes to set.
  @param  Value     Value of the set operation.

  @return Buffer.

**/
VOID *
EFIAPI
InternalMemSetMem (
  OUT     VOID                      *Buffer,
  IN      UINTN                     Size,
  IN      UINT8                     Value
  )
{
  gBS->SetMem (Buffer, Size, Value);
  return Buffer;
}

4. 通过volatile 申明变量避免编译器的优化,简单粗暴,很前面2提到的没有本质差别。volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。【参考2】

 \EdkCompatibilityPkg\Foundation\Library\EdkIIGlueLib\Library\BaseMemoryLib\Ebc\SetMem.c

/**
  Set Buffer to Value for Size bytes.

  @param  Buffer Memory to set.
  @param  Size Number of bytes to set
  @param  Value Value of the set operation.

  @return Buffer

**/
VOID *
EFIAPI
InternalMemSetMem (
  IN      VOID                      *Buffer,
  IN      UINTN                     Size,
  IN      UINT8                     Value
  )
{
  //
  // Declare the local variables that actually move the data elements as
  // volatile to prevent the optimizer from replacing this function with
  // the intrinsic memset()
  //
  volatile UINT8                    *Pointer;

  Pointer = (UINT8*)Buffer;
  while (Size-- != 0) {
    *(Pointer++) = Value;
  }
  return Buffer;
}

5.汇编语言实现

\EdkCompatibilityPkg\Foundation\Library\CompilerStub\X64\memset.asm

\EdkCompatibilityPkg\Foundation\Library\CompilerStub\Ia32\memset.asm

IA32汇编的实现

    .686
    .model  flat,C
    .mmx
    .code

;------------------------------------------------------------------------------
;  VOID *
;  memset (
;    OUT VOID   *Buffer,
;    IN  UINT8  Value,
;    IN  UINTN  Count
;    )
;------------------------------------------------------------------------------
memset   PROC    USES    edi
    mov     al, [esp + 12]
    mov     ah, al
    shrd    edx, eax, 16
    shld    eax, edx, 16
    mov     ecx, [esp + 16]             ; ecx <- Count
    cmp     ecx, 0                      ; if Count == 0, do nothing
    je      @SetDone
    mov     edi, [esp + 8]              ; edi <- Buffer
    mov     edx, ecx
    and     edx, 7
    shr     ecx, 3                      ; # of Qwords to set
    jz      @SetBytes
    add     esp, -10h
    movq    [esp], mm0                  ; save mm0
    movq    [esp + 8], mm1              ; save mm1
    movd    mm0, eax
    movd    mm1, eax
    psllq   mm0, 32
    por     mm0, mm1                    ; fill mm0 with 8 Value's
@@:
    movq    [edi], mm0
    add     edi, 8
    loop    @B
    movq    mm0, [esp]                  ; restore mm0
    movq    mm1, [esp + 8]              ; restore mm1
    add     esp, 10h                    ; stack cleanup
@SetBytes:
    mov     ecx, edx
    rep     stosb
@SetDone:    
    mov     eax, [esp + 8]              ; eax <- Buffer as return value
    ret
memset   ENDP

    END

上面就是实现 SetMem 函数的基本方法,如果在 Porting 代码到 UEFI时遇到 MemSet 的错误,不妨试试直接将上面的代码搬迁到程序中。

参考:

  1. http://www.lab-z.com/stu136/  Step to UEFI (136)哪里来的的 memset 
  2. https://baike.baidu.com/item/volatile/10606957?fr=aladdin volatile

Intel unofficial history?

野史看起来永远比正事更加精彩,最近偶然间知乎上看到王知先生编写的《纪念英特尔成立五十周年》。感觉很有意思。因为作者禁止转载,所以这里给出链接。有兴趣的朋友可以上去直接看看。

《纪念英特尔成立五十周年》 引言,讲述了 Intel 的成立过程

https://zhuanlan.zhihu.com/p/39805025

《篇一 阴差阳错》
https://zhuanlan.zhihu.com/p/39804154

《篇二 失而复得》

https://zhuanlan.zhihu.com/p/39805356

《篇三 力挽狂澜》
https://zhuanlan.zhihu.com/p/39806179

Step to UEFI (175)Shell 下读取 VBT

最近看到一篇文章“Intel ACPI IGD OpRegion Spec, and IntelSiliconPkg added to Tianocore”【参考1】,介绍的是Intel Jiewen Yao在开源项目 Tianocore 中加入了关于 IGD OpRegion相关内容。与这个文件相关有一份名为“Intel Integrated Graphics Device OpRegion Specification Driver Programmer’s Reference Manual” 的 datasheet【参考2】。简单的说这是介绍 Intel IGD 显卡控制接口的资料。上面提到的下面这个表格引起了我的兴趣:

就是说,如果我们能在内存中找到OpRegion 那么是有机会找到 VBT 的。如果能找到 VBT 那么就有机会 Dump 出来。更出人意料的是,这份Datasheet 直接给出了获得 OpRegion 的方法:

结合【参考1】的Code, IGD  OpRegion 结构如下(【参考2】也提到了下面的结构体,但是VBT 的 Offset 是 0x500和实际对不上,有可能是因为 Datasheet 太老):

///
	/// IGD OpRegion Structure
	///
	typedef struct {
	  IGD_OPREGION_HEADER Header; ///< OpRegion header (Offset 0x0, Size 0x100)
	  IGD_OPREGION_MBOX1  MBox1;  ///< Mailbox 1: Public ACPI Methods (Offset 0x100, Size 0x100)
	  IGD_OPREGION_MBOX2  MBox2;  ///< Mailbox 2: Software SCI Interface (Offset 0x200, Size 0x100)
	  IGD_OPREGION_MBOX3  MBox3;  ///< Mailbox 3: BIOS to Driver Notification (Offset 0x300, Size 0x100)
	  IGD_OPREGION_MBOX4  MBox4;  ///< Mailbox 4: Video BIOS Table (VBT) (Offset 0x400, Size 0x1800)
	  IGD_OPREGION_MBOX5  MBox5;  ///< Mailbox 5: BIOS to Driver Notification Extension (Offset 0x1C00, Size 0x400)
	} IGD_OPREGION_STRUCTURE;

接下来在 Kabylake-R 的板子上验证一下:

  1. 取得OpRegion,地址为 0x2FEDC018

2.查看内存

3.到 +0x400的地方查看,可以看到 $VBT 的 Sign

该处内容和我代码中的 VBT 一致。接下来的问题是,如何得知VBT 的大小。在UDK2018 的Source Code 中有下面的文件 Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h,其中定义如下:

#pragma pack (1)
typedef struct {
  UINT8   HeaderSignature[20];
  UINT16  HeaderVersion;
  UINT16  HeaderSize;
  UINT16  HeaderVbtSize;
  UINT8   HeaderVbtCheckSum;
  UINT8   HeaderReserved;
  UINT32  HeaderOffsetVbtDataBlock;
  UINT32  HeaderOffsetAim1;
  UINT32  HeaderOffsetAim2;
  UINT32  HeaderOffsetAim3;
  UINT32  HeaderOffsetAim4;
  UINT8   DataHeaderSignature[16];
  UINT16  DataHeaderVersion;
  UINT16  DataHeaderSize;
  UINT16  DataHeaderDataBlockSize;
  UINT8   CoreBlockId;
  UINT16  CoreBlockSize;
  UINT16  CoreBlockBiosSize;
  UINT8   CoreBlockBiosType;
  UINT8   CoreBlockReleaseStatus;
  UINT8   CoreBlockHWSupported;
  UINT8   CoreBlockIntegratedHW;
  UINT8   CoreBlockBiosBuild[4];
  UINT8   CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()

于是我们也可以获得 VBT 的大小。完整的代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/SimpleFileSystem.h>
#include  <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include  <Library/ShellLib.h>

#include  "IgdOpRegion.h"

//Copied from UDK2018\Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h
#pragma pack (1)
typedef struct {
  UINT8   HeaderSignature[20];
  UINT16  HeaderVersion;
  UINT16  HeaderSize;
  UINT16  HeaderVbtSize;
  UINT8   HeaderVbtCheckSum;
  UINT8   HeaderReserved;
  UINT32  HeaderOffsetVbtDataBlock;
  UINT32  HeaderOffsetAim1;
  UINT32  HeaderOffsetAim2;
  UINT32  HeaderOffsetAim3;
  UINT32  HeaderOffsetAim4;
  UINT8   DataHeaderSignature[16];
  UINT16  DataHeaderVersion;
  UINT16  DataHeaderSize;
  UINT16  DataHeaderDataBlockSize;
  UINT8   CoreBlockId;
  UINT16  CoreBlockSize;
  UINT16  CoreBlockBiosSize;
  UINT8   CoreBlockBiosType;
  UINT8   CoreBlockReleaseStatus;
  UINT8   CoreBlockHWSupported;
  UINT8   CoreBlockIntegratedHW;
  UINT8   CoreBlockBiosBuild[4];
  UINT8   CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()

#define MmPciAddress(Bus, Device, Function, Register) \
  ((UINTN) 0xE0000000 + \
   (UINTN) (Bus << 20) + \
   (UINTN) (Device << 15) + \
   (UINTN) (Function << 12) + \
   (UINTN) (Register) \
  )
  
EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
                        
//                        
//Save memory address to a file                        
//
EFI_STATUS 
SaveToFile(
        IN UINT8 *FileData, 
        IN UINTN FileDataLength)
{
    EFI_STATUS          Status;
    EFI_FILE_PROTOCOL   *FileHandle;
    UINTN               BufferSize;
    EFI_FILE_PROTOCOL   *Root;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;

    Status = gBS->LocateProtocol(
                &gEfiSimpleFileSystemProtocolGuid, 
                NULL,
                (VOID **)&SimpleFileSystem);
                
    if (EFI_ERROR(Status)) {
            Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
            return Status;
    }

    Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"OpenVolume error \r\n");
        return Status;
    }
    Status = Root->Open(
                Root, 
                &FileHandle, 
                L"dumpvbt.bin",
                EFI_FILE_MODE_READ |
                EFI_FILE_MODE_WRITE | 
                EFI_FILE_MODE_CREATE, 
                0);
    if (EFI_ERROR(Status)){
        Print(L"Error Open NULL  [%r]\n",Status);
        return Status;
    }
    
    BufferSize = FileDataLength;
    Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
    if (EFI_ERROR(Status)){
        Print(L"Error write [%r]\n",Status);
        return Status;
    }
    else Print(L"VBT has been saved to 'dumpvbt.bin' \n");
    
    FileHandle->Close(FileHandle);
    
    return Status;
}

UINT32
EFIAPI
MmioRead32 (
  IN      UINTN                     Address
  )
{
  UINT32                            Value;

  Value = *(volatile UINT32*)Address;

  return Value;
}


/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        IGD_OPREGION_STRUCTURE  *IGDRegion;
        VBIOS_VBT_STRUCTURE     *VBT;
        
        // If can't find IGD, print a error message
        if ((UINT16)(MmPciAddress(0,2,0,0)==0xFFFF)) {
                Print(L"No IGF found! \n");
                return 0;
        }
        
        // Get IGD OPRegion
        IGDRegion = (IGD_OPREGION_STRUCTURE*) MmioRead32((UINTN)(MmPciAddress(0,2,0,0xFC)));
        Print(L"OpRegion Address [0x%X] \n",IGDRegion);
        
        //Get VBT address
        VBT=(VBIOS_VBT_STRUCTURE *) &IGDRegion->MBox4;
        
        Print(L"     VBT Address [0x%X] \n",VBT);
        Print(L"     VBT Size    [0x%X] \n",VBT->HeaderVbtSize);
        
        if ((VBT->HeaderVbtSize>8*1024)||(VBT->HeaderVbtSize==0)) 
                {
                        Print(L"VBT Data too big, ERROR! \n");
                }
        else 
          SaveToFile((UINT8*)VBT,VBT->HeaderVbtSize);
        
        return(0);
}

HDK  KBL-R  上的运行结果如下:

比较也会发现 dump VBT 和压入的相同。

完整代码下载:

参考:

1. https://firmwaresecurity.com/2016/06/01/intel-acpi-igd-opregion-spec-and-intelsiliconpkg-added-to-tianocore/

2. https://01.org/sites/default/files/documentation/acpi_igd_opregion_spec_0.pdf

UEFI TIPS:edk2-stable201903

Krishna 推荐EDK2中集成的  ACPI Command,研究过程中我发现今年3月有一份新Release 的 UDK 版本,可以在https://github.com/tianocore/edk2/releases/tag/edk2-stable201903 看到。从我的感觉上这个版本比前面 201811 的要稳定一些,推荐有更新需求的朋友们试试。

这个版本更新如下:

Release Date 2019-03-08 (UTC – 8 12PM)

New Features

其中引人注意的是 “Upgrade OpenSSL to 1.1.0j”,在编译过程中我发现如果没有 OpenSSL NT32Pkg 也是无法正常编译的。这里放置一个补充 OpenSSL 1.1.0J 能够正常编译的 Package,有需要的朋友可以在这里下载:

链接: https://pan.baidu.com/s/12pXFsgaskEIt1Y6dfuHtGg 提取码: ymra

FireBeetle打造腕表式心率计

心率是人类重要的生理指标,中国古人更是相信能够凭借脉搏诊断身体情况。

之前介绍过心率带和心率带接收模块的使用【参考1】。这次使用 DFRobot 的FireBeelte 和 12864 小屏幕制作一个腕表式心率计。

主控板 ESP32
配套 12864 显示板

从前面的实验可以得知,心率带接收模块使用串口输出。FireBeelte 核心是 ESP32 ,支持3个硬件串口【参考2】。其中0号是给烧写程序使用了(其实也可以用,只是为了调试方便而不选择使用);1号串口在 IO9/10和 12864 控制Pin冲突;所以只能使用2号串口,位于IO16/17。对我们来说只需要接收即可。

供电部分这次直接使用USB提供5V, 使用板载的 3.3V接口为心率接收模块供电。FireBeelte 设计上带有电池插口(3.7V),这次设计为了简单起见并未使用。

12864 屏幕自带了字库,因此可以直接调用库来完成汉字的现实,对于汉字显示需求来说方便很多。通过函数OLED.disStr(x, y, “要显示的汉字”) 即可直接显示。

代码很简单,就是从一个串口输入,找到心率数据,显示在 12864屏幕即可。

完整代码如下:

#include "DFRobot_OLED12864.h"

// 使用第三组串口
HardwareSerial Serial2(2);

// 使用12864 LCD
const uint8_t I2C_addr = 0x3c;
const uint8_t pin_SPI_cs = D5;

DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);

void setup(void)
{
  // 串口0 用于下载代码 
  Serial.begin(115200);
  // 串口2 用于接收心率带接收器发过来的数据
  Serial2.begin(9600);  
  
  // 初始化 12864
  OLED.init();
  OLED.flipScreenVertically();
}

  char Recv[40];
  char BPM[40]="----BPM";
  int  count=0;
  int  idx;
void loop(void)
{
  // 一直在串口2 接收数据 
  while (Serial2.available())
  {
    Recv[count]=Serial2.read();
    count++;
  }
  
  // 心率带数据以  0xDD 起始,例如: DD 22 05 28 4D 
  if ((Recv[0]==0xDD)&&(count==5)) {
      // 将心率数据格式化为字符串  
      for (int i=0;i<sizeof(BPM);i++) BPM[i]=' ';
      sprintf(BPM, "%d BPM   ", Recv[4]);
  }
  
  // 接收到足够的数据的话,将数据送到串口0 便于Debug
  if (count>=5) {
    for (int i=0;i<count;i++)
      Serial.write(Recv[i]);
    //清空接收 Buffer  
    count=0;
  } 
   
   for (idx=0;idx<20;idx++) {
     OLED.clear();
     // 将心率信息显示在 12864 上 
     OLED.disStr(0, 0, "当前心率");
     OLED.disStr(40, 30, BPM);  
     
     // 为了便于查看除了心率数据,还实现一个圆形的动态显示   
     for (int16_t i=0; i<abs(10-idx); i++) {
       OLED.drawCircle(116, 52, i);
     }  
    
     //显示内容到 12864 屏幕上
     OLED.display();  
     delay(30);
   }
   
}    

成品照片:

工作的视频可以在知乎专栏看到:

https://zhuanlan.zhihu.com/p/58751665

参考:

1. Arduino获得心率带数据

https://www.arduino.cn/forum.php?mod=viewthread&tid=84101&fromuid=36850

2. FireBeelte 多串口通讯的实现

https://www.arduino.cn/forum.php?mod=viewthread&tid=84390&fromuid=36850

Step to UEFI (174)UEFI Shell 下的贪吃蛇游戏

最近在  https://github.com/AlexClazrey/uefi-snake-game 发现一个UEFI 下的贪吃蛇游戏。下载之后在 UDk2018 下测试编译,修改Warning 后可以正常编译运行。有兴趣的朋友可以下载研究一下。

工作的图片:

编译后的 X64 EFI,可以在 NT32 模拟环境中运行:

原始代码:

修改后的代码:

Step to UEFI (173)UEFI Shell 下的 ACPI Dump工具

之前推荐过一个 Shell 下的 ACPI Table Dump工具【参考1】,没有 Source Code。从原理上来说没什么难度,只是比较麻烦一些而已。最近在https://github.com/andreiw 上看到他写的一些 UEFI Shell 工具组感觉很有意思,编译试验后逐次介绍给大家。这次介绍的是他写的 ACPI Dump 工具,在 【参考2】可以看到。

作者(Andrei Warkentin)对于这个工具的介绍如下:

# AcpiDump

AcpiDump will dump all of the ACPI tables to the same volume

the tool is located on. This is sometimes useful, but keep

in mind that some UEFI implementations can patch ACPI tables

on the way out during ExitBootServices, meaning you might

not get the same (correct) data if you dumped tables from

the OS proper.

An optional parameter specifies the path, relative to the

volume root, where to place the tables. E.g.:

    fs16:> AcpiDump.efi

    fs16:> AcpiDump.efi MyFunkySystem

The files produced can be loaded by [AcpiLoader](../AcpiLoader).

经过一些修改,他的工具可以独立在 UDK AppPkg 下编译通过,主程序如下:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include "UtilsLib.h"

#include <IndustryStandard/Acpi.h>
#include <Protocol/LoadedImage.h>
#include <Library/PrintLib.h>
#include <Guid/Acpi.h>

#include <Library/ShellCEntryLib.h>

static RANGE_CHECK_CONTEXT RangeCheck;

static
EFI_STATUS
TableSave (
           IN EFI_HANDLE Handle,
           IN CHAR16 *VolSubDir,
           IN EFI_ACPI_DESCRIPTION_HEADER *Table
           )
{
  EFI_STATUS Status;
  static unsigned Index = 0;
  CHAR16 Path[3 + 4 + 1 + 3 + 1];

  if (Table == NULL) {
    Print(L"<skipping empty SDT entry\n");
    return EFI_NOT_FOUND;
  }

  Status = RangeIsMapped(&RangeCheck, (UINTN) Table, sizeof(EFI_ACPI_DESCRIPTION_HEADER));
  if (Status != EFI_SUCCESS) {
    Print(L"<could not validate mapping of table header: %r>\n", Status);
    return Status;
  }

  Print(L"Table %.4a @ %p (0x%x bytes)\n", &Table->Signature, Table, Table->Length);

  Status = RangeIsMapped(&RangeCheck, (UINTN) Table, Table->Length);
  if (Status != EFI_SUCCESS) {
    Print(L"<could not validate mapping of full table: %r>\n", Status);
    return Status;
  }

  Index = Index % 100;
  Path[0] = (CHAR16) ('0' + Index / 10);
  Path[1] = (CHAR16) ('0' + Index % 10);
  Path[2] = '-';
  Path[3] = ((CHAR8 *) &Table->Signature)[0];
  Path[4] = ((CHAR8 *) &Table->Signature)[1];
  Path[5] = ((CHAR8 *) &Table->Signature)[2];
  Path[6] = ((CHAR8 *) &Table->Signature)[3];
  Path[7] = L'.';
  Path[8] = L'a';
  Path[9] = L'm';
  Path[10] = L'l';
  Path[11] = L'\0';
  Index++;

  Status = FileSystemSave(Handle, VolSubDir, Path, Table, Table->Length);
  if (Status != EFI_SUCCESS) {
    //
    // FileSystemSave already does sufficient logging. We only
    // return failure if there was something wrong with the
    // table itself.
    //
    return EFI_SUCCESS;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain (
          IN EFI_HANDLE ImageHandle,
          IN EFI_SYSTEM_TABLE *SystemTable
          )
{
  UINTN i;
  UINTN Argc;
  CHAR16 **Argv;
  EFI_STATUS Status;
  UINTN SdtEntrySize;
  EFI_PHYSICAL_ADDRESS SdtTable;
  EFI_PHYSICAL_ADDRESS SdtTableEnd;
  EFI_ACPI_DESCRIPTION_HEADER *Rsdt;
  EFI_ACPI_DESCRIPTION_HEADER *Xsdt;
  EFI_LOADED_IMAGE_PROTOCOL *ImageProtocol;
  EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp;
  CHAR16 *VolSubDir;

  EFI_GUID AcpiGuids[2] = {
    EFI_ACPI_20_TABLE_GUID,
    ACPI_10_TABLE_GUID
  };

  VolSubDir = L".";
  Status = GetShellArgcArgv(ImageHandle, &Argc, &Argv);
  if (Status == EFI_SUCCESS && Argc > 1) {
    VolSubDir = Argv[1];
  }

  Status = InitRangeCheckContext(TRUE, TRUE, &RangeCheck);
  if (EFI_ERROR(Status)) {
    Print(L"Couldn't initialize range checking: %r\n", Status);
    return Status;
  }

  Print(L"Dumping tables to '\\%s'\n", VolSubDir);

  Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid,
                                (void **) &ImageProtocol);
  if (Status != EFI_SUCCESS) {
    Print(L"Could not get loaded image device handle: %r\n", Status);
    goto done;
  }

  for (i = 0; i < 2; i++) {
    Rsdp = GetTable(&AcpiGuids[i]);
    if (Rsdp != NULL) {
      break;
    }
  }

  if (Rsdp == NULL) {
    Print(L"No ACPI support found\n");
    goto done;
  }

  if (Rsdp->Revision >= EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) {
    Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Rsdp->XsdtAddress;
  } else {
    Xsdt = NULL;
  }

  Status = EFI_NOT_FOUND;
  Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Rsdp->RsdtAddress;
  if (Xsdt != NULL &&
      (Status = RangeIsMapped(&RangeCheck, (UINTN) Xsdt,
                              sizeof(EFI_ACPI_DESCRIPTION_HEADER)))
      == EFI_SUCCESS) {
    SdtTable = (UINTN) Xsdt;
    SdtTableEnd = SdtTable + Xsdt->Length;
    SdtEntrySize = sizeof(UINT64);
  } else if (Rsdt != NULL &&
             (Status = RangeIsMapped(&RangeCheck, (UINTN) Rsdt,
                                     sizeof(EFI_ACPI_DESCRIPTION_HEADER)))
             == EFI_SUCCESS) {
    SdtTable = (UINTN) Rsdt;
    SdtTableEnd = SdtTable + Rsdt->Length;
    SdtEntrySize = sizeof(UINT32);
  } else {
    Print(L"No valid RSDT/XSDT: %r\n", Status);
    goto done;
  }

  Status = RangeIsMapped(&RangeCheck, SdtTable, SdtTableEnd - SdtTable);
  if (Status != EFI_SUCCESS) {
    Print(L"Could not validate RSDT/XSDT mapping: %r\n", Status);
    goto done;
  }

  for (SdtTable += sizeof(EFI_ACPI_DESCRIPTION_HEADER);
       SdtTable < SdtTableEnd;
       SdtTable += SdtEntrySize) {
    EFI_ACPI_DESCRIPTION_HEADER *TableHeader;

    if (SdtEntrySize == sizeof(UINT32)) {
      TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) *(UINT32 *) SdtTable;
    } else {
      TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) *(UINT64 *) SdtTable;
    }

    Status = TableSave(ImageProtocol->DeviceHandle, VolSubDir, TableHeader);

    if (Status == EFI_SUCCESS &&
        TableHeader->Signature == EFI_ACPI_5_1_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
      EFI_ACPI_DESCRIPTION_HEADER *DsdtHeader;
      EFI_ACPI_DESCRIPTION_HEADER *FacsHeader;

      EFI_ACPI_5_1_FIXED_ACPI_DESCRIPTION_TABLE *Fadt = (void *) TableHeader;
      if (TableHeader->Revision >= 3 && Fadt->XDsdt != 0) {
        DsdtHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->XDsdt;
      } else {
        DsdtHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->Dsdt;
      }

      if (TableHeader->Revision >= 3 && Fadt->XFirmwareCtrl != 0) {
        FacsHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->XFirmwareCtrl;
      } else {
        FacsHeader = (EFI_ACPI_DESCRIPTION_HEADER *) (UINTN) Fadt->FirmwareCtrl;
      }

      if (DsdtHeader != NULL) {
        TableSave(ImageProtocol->DeviceHandle, VolSubDir, DsdtHeader);
      } else {
        Print(L"No DSDT\n");
      }

      if (FacsHeader != NULL) {
        TableSave(ImageProtocol->DeviceHandle, VolSubDir, FacsHeader);
      } else {
        Print(L"No FACS\n");
      }
    }
  }

  Print(L"All done!\n");

done:
  CleanRangeCheckContext(&RangeCheck);
  return Status;
}

在KBL-R HDK 上的运行结果:

源代码和编译后的 EFI 程序可以在这里下载到:

参考:

  1. http://www.lab-z.com/acpishll/  介绍一个好用的 ACPIDUMP 工具
  2. https://github.com/andreiw/UefiToolsPkg/tree/master/Applications

Arduino 供电 Shield

Arduino Uno 可以使用 USB 供电或者 12V供电,但是对于展示来说,这样还是不太方便。为此,我设计了一个装有充放电模块的Shield,相当于集成了一个充电宝,可以对4.2v 电池进行充放电管理,同时提供5V电源输出。
本打算使用充电宝的方案进行制作,搜索一番发现零售的很少,估计是因为这种东西都是走量的。即便有芯片方案,也比买成品模块偏移不了多少。最终选择了下面这种模块,价格是 12.8元。

连接很简单,需要特别注意的是其中左侧有一个按钮,这是唤醒整个模块开始供电的开关。对于Arduino 控制来说,将这个位置拉低>100ms就是供电。比如,你的整体负载<100ma,这个模块会在20秒后停止供电,因此,你可以用一个IO每隔20秒拉低一次一次实现持续供电。

电路图如下:

PCB 如下

最终的实物:

需要注意的地方:
1.这里预留一个跳线帽的位置,使用时需要加一个跳线,这样的设计是为了如果很长时间不使用防止电量耗光

2.下面这个位置和 Uno 的USB口有冲突,在使用时务必小心短路

附件是电路图和PCB。
ZChargerShield