Step to UEFI (114)CHAR16大小写转换

最近编写程序,需要用到 CHAR16 的大小写转换,忽然惊奇的发现远比CHAR8的转化复杂,没有可以直接使用的函数。研究了一番,猜测产生这样问题的原因是 CHAR16 是定义给 Unicode 使用的。而Unicode 需要支持多种语言格式,比方说:’a’的大写是’A’,‘一’的大写就是‘壹’了,所以处理上也是复杂的。

从 UEFI Spec 来看,标准的做法要求使用EFI_UNICODE_COLLATION_PROTOCOL 。对应的头文件在 \MdePkg\Include\Protocol\UnicodeCollation.h 中。有下面几个功能:

///
/// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive 
/// comparisons of strings. 
///
struct _EFI_UNICODE_COLLATION_PROTOCOL {
  EFI_UNICODE_COLLATION_STRICOLL    StriColl;  
  EFI_UNICODE_COLLATION_METAIMATCH  MetaiMatch; 
  EFI_UNICODE_COLLATION_STRLWR      StrLwr;
  EFI_UNICODE_COLLATION_STRUPR      StrUpr;

  //
  // for supporting fat volumes
  //
  EFI_UNICODE_COLLATION_FATTOSTR    FatToStr;
  EFI_UNICODE_COLLATION_STRTOFAT    StrToFat;
  
  ///
  /// A Null-terminated ASCII string array that contains one or more language codes.
  /// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format.
  /// When it is used for UnicodeCollation, it is specified in ISO 639-2 format.
  ///
  CHAR8                             *SupportedLanguages;
};

 

StriColl 进行大小写敏感的字符串比较
MetaiMatch 判断字符串是否匹配一个带有通配符的模版
StrLwr 字符串转为小写
StrUpr 字符串转为大写
FatToStr 将8.3格式的OEM字符集的FAT文件名转为字符串
StrToFat 将字符串转为OEM定义的字符集
*SupportedLanguages ASCII给出的语言的列表

上述只是简单的介绍,我也并没有仔细验证,更详细的请阅读UEFI Spec Protocols – String Services 章节。
除了,上面介绍的方法,如果你确定字符串中只有 ASCII , 那么还可以直接处理字符串中的 ASCII ,下面的实例演示了上面的2种做法:

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

#include <Protocol/UnicodeCollation.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

VOID
EFIAPI
lowercase (
  IN OUT CHAR16	*Str
  )
{
	UINT32	i;
	CHAR8	*p;
	p=(CHAR8 *)Str;
	for (i=0;i<StrSize(Str);i++) {
		//Print(L"%d ",*p);
		if ((*p>='A')&&(*p<='Z')) {
			*p=*p+32;
		}
		p++;
	}
	
}	
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS	Status;
  CHAR16		*s1=L"Www.Lab-z.Com 1243";
  CHAR16		*s2=L"Www.Lab-z.Com 1243";
  
  if (mUnicodeCollation == NULL) {
		Status = gBS->LocateProtocol(
			&gEfiUnicodeCollation2ProtocolGuid,
			NULL,
			(VOID**)&mUnicodeCollation);

		if (EFI_ERROR(Status)) {
			Print(L"Can't find UnicodeCollation2 Protocol!\n"); 
			return EFI_SUCCESS;
		}
  }

  mUnicodeCollation->StrUpr(
    mUnicodeCollation,
    s1);
  Print(L"%s\n",s1);
  
  lowercase (s2);
  Print(L"%s\n",s2);  
  return EFI_SUCCESS;
}

 

运行结果:

ultest

完整的代码下载

ULTest

Step to UEFI (113)获得简单的输入的方法

很多时候,我们的程序需要和用户进行简单的交互,当然可以设计菜单之类的,但是从代码的角度来说依然很复杂,我们并不希望为了获得一个确认而大费周章。这时候,可以直接使用 Shell 提供的ShellPromptForResponse 函数。
函数原型在ShellLib.h 中:

/**
  Prompt the user and return the resultant answer to the requestor.

  This function will display the requested question on the shell prompt and then
  wait for an apropriate answer to be input from the console.

  If the SHELL_PROMPT_REQUEST_TYPE is SHELL_PROMPT_REQUEST_TYPE_YESNO, ShellPromptResponseTypeQuitContinue
  or SHELL_PROMPT_REQUEST_TYPE_YESNOCANCEL then *Response is of type SHELL_PROMPT_RESPONSE.

  If the SHELL_PROMPT_REQUEST_TYPE is ShellPromptResponseTypeFreeform then *Response is of type
  CHAR16*.

  In either case *Response must be callee freed if Response was not NULL;

  @param Type                     What type of question is asked.  This is used to filter the input
                                  to prevent invalid answers to question.
  @param Prompt                   The pointer to a string prompt used to request input.
  @param Response                 The pointer to Response, which will be populated upon return.

  @retval EFI_SUCCESS             The operation was successful.
  @retval EFI_UNSUPPORTED         The operation is not supported as requested.
  @retval EFI_INVALID_PARAMETER   A parameter was invalid.
  @return other                   The operation failed.
**/
EFI_STATUS
EFIAPI
ShellPromptForResponse (
  IN SHELL_PROMPT_REQUEST_TYPE   Type,
  IN CHAR16         *Prompt OPTIONAL,
  IN OUT VOID       **Response OPTIONAL
  );

 

简单的说这个函数提供了3种用法:

1. 使用ShellPromptResponseTypeFreeform 参数,接收全部输入的字符串;
2. 使用ShellPromptResponseTypeYesNo 这样,用户输入 y 表示 Yes, n 表示 No
3. 使用ShellPromptResponseTypeEnterContinue 进行按键等待的

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

#include  "ShellLib.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

SHELL_PROMPT_RESPONSE
EFIAPI
Resp()
{
	
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR16 *InputStr = NULL;
 
	ShellPromptForResponse(
			ShellPromptResponseTypeFreeform, 
			L"Please input->", 
			(VOID**)&InputStr);
	Print(L"Get input by freeform %s\n",InputStr);

	ShellPromptForResponse(
			ShellPromptResponseTypeEnterContinue, 
			L"Press Enter to continue...",
			NULL);

	ShellPromptForResponse(
			ShellPromptResponseTypeAnyKeyContinue, 
			L"Press any key to continue...",
			NULL);			
			
	SHELL_PROMPT_RESPONSE *Resp=NULL;			
	ShellPromptForResponse(ShellPromptResponseTypeYesNo, L"Type 'y' or 'n'->",(VOID**)&Resp);
    switch (*(SHELL_PROMPT_RESPONSE*)Resp) {
          case ShellPromptResponseNo:
            Print(L"Choose No\n");
            break;
          case ShellPromptResponseYes:
            Print(L"Choose Yes\n");
            break;	
		  default:
			break;
	}

	
	
	return EFI_SUCCESS;
}

 

运行结果:

sinput

可以看到,这个函数还可以用来实现回车键继续或者任意键继续的功能。
关于这个函数,具体的实现代码可以在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中看到,有兴趣的朋友可以深入研究一下。

完整的代码下载
InputTest

好用的UEFI GUID工具

前面提到过在编写UEFI 程序的时候不可避免的需要引用PROTOCOL 等等的 GUID。最近在网上看到了一个Python 编写的工具,作者是Xiang Lian【参考1】,可以将项目中定义的 GUID都提取到一个文件中个,这样便于查找和使用。我实验了一下,挺好用的推荐给大家。

#!/c:\python27\python.exe
#
#      Filename:   guidxref.py
#       Written:   Xiang Lian
#
#
# This python script scans through UDK2010 build tree and saves all GUID definitions into
# an output file. Refer list TargetFileTypes for currently suported source file types.
#
#-------------------------------------------------------------------------------------------------
#
#   Rev history:   rev 1.0    10/07/2012
#                       - Guid_In_h had missed some lower-cased GUIDs;
#
#                  rev 1.1    10/07/2012
#                       - Added captured groups in Guid_In_h, refer RegFormatOutput;
#
#                  rev 1.2    10/08/2012
#                       - Swapped content in output line so that GUID strings always come first
#
#                  rev 1.5    10/08/2012
#                       - Simplified os.walk logic to significantly reduce the redundant scans
#                       - Added summary to report total number of files scanned by type
#
#                  rev 1.6    10/08/2012
#                       - Added logging module, to turn on logging output:
#                             . Uncomment the logging.basicConfig line
#                             . Choose level in basicConfig
#                       - Always save result into a newly create output file
#
#                  rev 2.0    10/09/2012
#                       - Created function NormalizeGuidString to replace the buggy RegFormatOutput pattern.
#                         Now all output GUIDs have a fixed length and are made uppercase
#
#                  rev 2.2    10/11/2012
#                       - Added list ExcludedDirs which folders will not be scanned
#                       - Added lambda function toU to conver registry format GUIDs to uppercase
#                       - Output filenames are now including timestamp strings
#                       - Logging filenames are always saved into a newly created file (suffixed with seq#)
#
#                  rev 3.0    10/11/2012
#                       - Added filter to remove some invalid lines in the output
#                       - Output lines are sorted and all duplicates removed
#
#                  rev 3.1    10/19/2012
#                       - Collected all user customizable values/configs into class UserConfig
#                       - Minor adjustment on summary output
#
#                  rev 3.2    10/25/2012
#                       - Modified Guid_In_h pattern to match wider range of GUID formats (in EDK)
#
#                  rev 3.3    10/30/2012
#                       - Defined VimScriptMode switch to output result in vim script format
#
#                  rev 3.4    10/31/2012
#                       - Added .c into the scan file list (TargetFileTypes)
#                       - Added (?i) in Guid_In_h: This makes the entire pattern case in-sensitive
#                       - Resolved lines like: EFI_GUID  PciIoProtocolInstallEventGuid = {...};
#                       - Resolved lines containing tabs
#                       - No more year info in result file name
#
#                  rev 3.5    10/31/2012
#                       - Added NormalizedGuidLine for final validity check
#
#
#
#
#
#
# Cases currently cannot be handled:
#
#    EFI_GUID  gEfiUsbKeyboardDriverGuid = {
#      0xa05f5f78, 0xfb3, 0x4d10, 0x90, 0x90, 0xac, 0x4, 0x6e, 0xeb, 0x7c, 0x3c
#    };
# 
#----------------------------------------------------------------------------------------------------
#
import os, re, string, sys
import logging
import datetime


#
# Global variables
#
class UserConfig:
  """ This class defines a set of constants and configurations which can be customized by 
      the script user. 
  """
  ScriptRev = " Ver 3.5"

  # To generate logging output, change this to 1
  LoggingEnable = 0

  # Set this to 1 to generate the result file in vim script format
  VimScriptMode = 0

  # The maximum character width of the console output line
  MAXLINEWIDTH = 110

  # This list provides all the file types to be scanned
  TargetFileTypes = {'.h' : 0, '.dec' : 0, '.inf' : 0, '.dsc' : 0, '.c' : 0}

  # Directories to be excluded from the scan
  ExcludedDirs = ('.svn', 'Build', 'uefi64native')

  # Base file name for result file
  BaseOutputName = "guidxref_"

  # Base file name for logging output
  BaseLogName = ".\debug"



# This defines the continuation character at end of line
ContinuingEol = "\\\n"

# Define directive in GUID definition (usually in .h and .c files)
DefineDirective = "#define"

# Header part of the GUID definition line (usually in INF or DSC files)
RegGuidDef = "^.*\=\s*"          #note: "^\(.*\)\=\s*" doesn't work!

# GUID Definitive format - Below pattern matches lines like: 
#      { 0xbf9d5465, 0x4fc3, 0x4021, {0x92, 0x5, 0xfa, 0x5d, 0x3f, 0xe2, 0xa5, 0x95}}
Guid_In_h = "(?i)\{\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*{?\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+)\s*}?\s*\}.*"

#This is buggy, has already been replaced by NormalizeGuidString2
RegFormatOutput = r"\1-\2-\3-\4\5-\6\7\8\9\10\11"   # Note: have to add prefix 'r' to make it raw here

# GUID Registry format - Below pattern matches lines like:  FILE_GUID = A5102DBA-C528-47bd-A992-A2906ED0D22B
Guid_In_Inf = "[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+"

# Normalized GUID lines like: A5102DBA-C528-47bd-A992-A2906ED0D22B  xxxx
NormalizedGuidLine = r"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}  [^#]+"


#################################### Functions Definition #################################################

def NormalizeGuidString (matchobj):
  """ Definitive format GUID string normalization - Prefixing with 0s for every captured group
      
      Parameter matchobj is a MatchObject instance which contains one or more subgroups of the pattern
      match done by the re.sub() method. It's same as the return object of re.search(). 
  """
  hex = [""]
  for i in range (1, 12):
    hex.append(matchobj.group(i))
  hex[1]  = format (int(hex[1],  16), '08x').upper()
  hex[2]  = format (int(hex[2],  16), '04x').upper()
  hex[3]  = format (int(hex[3],  16), '04x').upper()
  hex[4]  = format (int(hex[4],  16), '02x').upper()
  hex[5]  = format (int(hex[5],  16), '02x').upper()
  hex[6]  = format (int(hex[6],  16), '02x').upper()
  hex[7]  = format (int(hex[7],  16), '02x').upper()
  hex[8]  = format (int(hex[8],  16), '02x').upper()
  hex[9]  = format (int(hex[9],  16), '02x').upper()
  hex[10] = format (int(hex[10], 16), '02x').upper()
  hex[11] = format (int(hex[11], 16), '02x').upper()
  return hex[1]+'-'+hex[2]+'-'+hex[3]+'-'+hex[4]+hex[5]+'-'+hex[6]+hex[7]+hex[8]+hex[9]+hex[10]+hex[11]


def SearchGuidsFromList (SrcList, filename):
  """ This function searchs for GUID definitions from a given string list.
  """
  GuidLines = []
  for n,line in enumerate(SrcList):
    while line.endswith(ContinuingEol):
      line = line.rstrip(ContinuingEol)
      #this doesnt work?? line = re.sub("\\\n", "", line)
      MergeLine = [line, SrcList[n+1]]
      line = "".join(MergeLine)      # This converts a list to a string
      del SrcList[n+1]
      #logging.debug ("  Merged line #%d, %s", n, line)
  
    # Now start searching for GUID pattern

    # Process .inf and .dsc files
    match = re.search(Guid_In_Inf, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a registry format GUID")
      line = re.sub(RegGuidDef, filename + "  ", line)       # Trim out useless part
      line = re.sub(Guid_In_Inf, lambda toU: toU.group().upper(), line) # Convert GUID to uppercase
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = line.lstrip()
      line = re.sub("\A(.*?)\s+(.*)", r"\2  \1", line)       # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

    # Process .h and .dec files
    match = re.search(Guid_In_h, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a definitive GUID")
      line = re.sub(DefineDirective, "", line)             # Trim out useless part
      line = re.sub(Guid_In_h, NormalizeGuidString, line)  # Convert to registry format
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = re.sub(r"\A(.*?)EFI_GUID\s+", "", line)   # Trim EFI_GUID 
      line = line.lstrip()
      line = re.sub("\A(.*?)[ =\t]+(.*)", r"\2  \1", line)   # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

  return GuidLines


def main():

  # Configure the logging module to send debug messages to file
  #
  # Refer:
  #    logging.debug (msg, *args)
  #    logging.info (msg, *args)
  #    logging.warning (msg, *args)
  #    logging.error (msg, *args)
  #    logging.critical (msg, *args)
  #    logging.setLevel (level)
  #    logging.disable (level)
  #
  # Valid values to set for level (by severity) in basicConfig are: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
  #
  #full format: logging.basicConfig(level=logging.DEBUG, filename='debug.log', format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
  if UserConfig.LoggingEnable:
    for seq in range (0, 99):
      LogFileName = UserConfig.BaseLogName + format (seq, '02d') + ".log"
      if not os.path.exists(LogFileName):
        break
      seq += 1
    logging.basicConfig(level=logging.DEBUG, filename=LogFileName, format='%(levelname)s: %(message)s')

  # Ensure input parameter is valid
  if len(sys.argv) < 2:
    print "Usage - ", sys.argv[0], " <Directory>\n"
    exit(0)

  RootDir = sys.argv[1]
  if (os.path.exists(RootDir) == False):
    print "Invalid directory name, please try again!"
    exit(1)
  
  # Determine output file
  now = datetime.datetime.now()
  ##suffix = now.strftime ("%Y_%m_%d_%H_%M_%S")
  suffix = now.strftime ("%m_%d_%H_%M_%S")

  if UserConfig.VimScriptMode == 0:
    ofile = open(UserConfig.BaseOutputName + suffix + ".txt", "w")
    # Print header message
    ofile.write("Generated by " + sys.argv[0] + UserConfig.ScriptRev + "\n")
    ofile.write("=" * 40 + "\n\n")
  else:
    ofile = open(UserConfig.BaseOutputName + suffix + ".vim", "w")
    # Print header option settings
    ofile.write(":set mm=2000000\n")
    ofile.write(":set undolevels=-1\n")

  # Traverse the root directory path for required source files
  TotalGuids = 0
  TempBuffer = []
  for root, dirs, files in os.walk(RootDir):
    # Check directories to be excluded from the scan
    for folder in UserConfig.ExcludedDirs:
      if folder in dirs:
        dirs.remove(folder)

    logging.debug ('  root = %s', root)
    #logging.debug ('  dirs = %s', dirs)
    #for file in files:
    #logging.debug ('  file = %s', file)
  
    for file in files:
      for type in UserConfig.TargetFileTypes.keys():
        if file.endswith(type):
          logging.info ("Found needed files = %s\\%s", root, file)
          UserConfig.TargetFileTypes[type] += 1

          # Scan the file for GUID strings
          try:
            fullpath = os.path.join(root, "".join(file))
            ifile = open(fullpath, "r")
          except:
            print folder, "\\", file, " could not be opened, abort!"
            sys.exit(2)
          
          logging.debug ('Opening source file ........%s', fullpath)
          AllLines = ifile.readlines()
          GuidList = SearchGuidsFromList (AllLines, "".join(file))
          if (len(GuidList) > 0):
            OutputLineWidth = UserConfig.MAXLINEWIDTH - len(fullpath)
            print fullpath, "." * OutputLineWidth, len(GuidList)
            for line in GuidList:
              # Remove some invalid lines
              if not re.search (r"\/\/$", line, re.MULTILINE):    #lx: "\Z" and "\z" don't work. ??
                TempBuffer.append(line)

          ifile.close()

  # Remove duplicates from the list
  #
  # [Python.docs] A set object is an **unordered** collection of distinct hashable objects. Common uses 
  # include membership testing, removing duplicates from a sequence, and computing mathematical operations 
  # such as intersection, union, difference, and symmetric difference.
  #
  TempBuffer = list(set(TempBuffer))

  # Now sort the list
  #
  # [Python.docs] sorted (iterable [, key][, reverse])
  #
  #   key - specifies a function of one argument that is used to extract a comparison key from
  #         each list element: key=str.lower. The default value is None (compare the elements directly)
  #   reverse - a boolean value. If set to True, the list elements are sorted as if each comparison 
  #             were reversed.
  #
  TempBuffer = sorted(TempBuffer)
  for line in TempBuffer:
    if re.match(NormalizedGuidLine, line):
      #
      # Ideally we don't need to add this extra validity check on each processed line. However there
      # are always some corner cases or various special cases which cannot be well handled by current
      # code logic. These filtered lines here can later be investigated when I have spare time.
      # 
      TotalGuids += 1
      if UserConfig.VimScriptMode:
        line = re.sub(r"\A([^ ]*)  (.*)$", r":%s/\1/\2/e", line)
      ofile.write(line)
    else:
      #lx-Adding following lines will unexpectedly remove the next line as well, why?
      #TempBuffer.remove(line)
      print "Invalid line: ", line

  ofile.close()

  # Print summary
  print "\n", "-" * 50, "Summary", "-" * 55
  for type in UserConfig.TargetFileTypes.keys():
    print "Scanned ", format (UserConfig.TargetFileTypes[type], '04d'), format (type, "4s"), " files"

  print "\nTotal number of GUIDs found: ", TotalGuids 


#
# Why do we need this?
# A .py file can be interpreted by Python as either standalone program to execute directly,
# or a module to be imported into other .py files. 
#   1) Standalone program - __name__ equals to "__main__"; 
#   2) imported as a module - __name__ equals to something else, therefore contents behind
#      the if statement won't get executed.
#
if __name__ == "__main__":
  main()

 

下载:

guidxref

参考:
1. https://github.com/simonlian/guidref/blob/master/guidxref.py

Step to UEFI (112)FSNTx和FSx

之前的文章提到过,我注意到UDK2014自带的Shell 使用的是 Fsnt0 这样的盘符,而我重新编译生成的却是 Fs0 这样的盘符。今天研究和实验相关代码,解开了困扰已久的问题。

首先,这个盘符的概念不是UEFI 的,而是Shell 这个程序自己创建和分配的。于是在代码中搜索,在UefiShellCommandLib.c 找到了下面的代码:

/**
  Function to generate the next default mapping name.

  If the return value is not NULL then it must be callee freed.

  @param Type                   What kind of mapping name to make.

  @retval NULL                  a memory allocation failed.
  @return a new map name string
**/
CHAR16*
EFIAPI
ShellCommandCreateNewMappingName(
  IN CONST SHELL_MAPPING_TYPE Type
  )
{
  CHAR16  *String;
  ASSERT(Type < MappingTypeMax);

  String = NULL;

  String = AllocateZeroPool(PcdGet8(PcdShellMapNameLength) * sizeof(String[0]));
  UnicodeSPrint(
    String,
    PcdGet8(PcdShellMapNameLength) * sizeof(String[0]),
    Type == MappingTypeFileSystem?L"FS%d:":L"BLK%d:",
    Type == MappingTypeFileSystem?mFsMaxCount++:mBlkMaxCount++);

  return (String);
}

 

可以看到,使用的是FS%d 这样的来进行分配,用重新编译Shell的方式确定这个位置。

然后我们修改代码,增加一个字符作为标记:

Type == MappingTypeFileSystem?L"FSz%d:":L"BLK%d:",

 

根据【参考1】进行编译,当然我们已经工作在UDK2015上,但是之前的方法仍然是可行的。编译好Shell.efi 之后,我们还要查看一下 \Nt32Pkg\Nt32Pkg.fdf 对于 Shell 的调用。看起来和之前不同了,我们猜测使用了UefiShell.inf 。

################################################################################
#
# FILE statements are provided so that a platform integrator can include
# complete EFI FFS files, as well as a method for constructing FFS files
# using curly "{}" brace scoping. The following three FILEs are
# for binary shell, binary fat and logo module.
#
################################################################################
!ifndef $(USE_OLD_SHELL)
INF  ShellBinPkg/UefiShell/UefiShell.inf
!else
INF  EdkShellBinPkg/FullShell/FullShell.inf
!endif

INF FatBinPkg/EnhancedFatDxe/Fat.inf

FILE FREEFORM = PCD(gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdLogoFile) {
    SECTION RAW = MdeModulePkg/Logo/Logo.bmp
  }

 

在打开UefiShell.inf 根据指引替换了 Shell.efi 为新编译出来的。再 build nt32和 build run。 得到的结果如下,可以看到,盘符发生了变化:

image001

我们再用十六进制工具打开 shell.efi ,搜索 fsz (特别注意这是unicode不是 ascii)

image003

因此,盘符的前缀就是在这里生成的。

然后我们再返回到 UDK2014中,打开模拟器运行结果如下:

image005

这就是我们一直疑惑的 FsntX: 直接使用十六进制编辑工具打开 Shell_Full.efi ,搜索 Fsnt 。找到之后我们修改为 Fsnz,再保存,重新build Nt32,然后再次运行编译器,结果就发生了变化:

image007
于是,可以解释我们之前的疑惑了:他们 Publish 的 Shell.efi 和我们拿到的并非一套代码,一些细节存在差别。
本文只相当于定性的分析,后面我们还会进行更详细的分析,到底盘符是如何来的,就是这样。

参考:
1. http://www.lab-z.com/how2buildshell/ How to build Shell.efi

Arduino 101 可以用来生成 1-1Mhz的方波

最近在查看资料【参考1】的时候,发现 101 提供了定时器输出 PWM 的功能。

“定时器输出PWM
除了作中断源使用,定时器也可以用作PWM输出,CurieTimerOne提供的pwmStart函数可以输出PWM。
在之前的章节中使用的analogWrite函数输出的PWM,周期固定,占空比可调,可用作LED调光;tone函数输出的PWM,周期不变,占空比可调,可用作无源蜂鸣器发声;而pwmStart输出的PWM周期和占空比都可调,更具灵活性,适用场合更广。
需注意的是pwmStart是重载函数,其有两种重载方式:

pwmStart(unsigned int outputPin, double dutyPercentage, unsigned int periodUsec);

pwmStart(unsigned int outputPin, int dutyRange, unsigned int periodUsec);

参数outputPin为输出PWM的引脚编号,periodUsec为每个周期的时间,单位为微秒。
而第二个参数可以为double 型,也可以为int型。当参数为double 型时,编译器会以dutyPercentage进行重载,参数以百分比形式表示PWM占空比;当参数为int型时,编译器会以dutyRange进行重载,参数以0到1023的形式表示PWM占空比;”

正好,手上有示波器,随手测量一下,发现效果不错

下面是生成的 1K 结果:
1K

2.5K 结果:
25k

25K 结果:
205k

参考:
1.http://www.arduino.cn/thread-42007-1-1.html 【Arduino101教程】定时器的使用

检查当前是否支持 DEVSLP 的软件

DEVSLP 是 Intel 推出的用于 SATA硬盘的省电功能。在PCH上,有一个Pin,当HIGH时,表示要求硬盘进入省电模式,否则处于Active模式。

下面是一段介绍,来自【参考1】

DEVSLP is a signal that is sent to a SATA disk drive to tell it to enter a very low power state. The SATA specification has always allowed for the drive to enter a low power state, but in the past the drive had to keep its high speed transceiver circuitry powered up in order to receive the signal to wake up again. DEVSLP removes this power hungry requirement by using a seperate low speed pin.

The ability to enter an ultra low power state is crucial for ultrabooks and other battery powered devices. Using DEVSLP, manufacturers are able to produce devices with power consumption of micro Watts compared to milli Watts in previous generation drives

Where did they find the extra pin?

There were no spare pins in the SATA conector standard for 2.5″ and 3.5″ drives (SFF-8482). Revision 3.2 assigns pin P3 as DEVSLP. Pins P1,P2 and P3 used to be a 3.3V power supply to the drive, very few drives (if any) and very few systems ever implemented 3.3V power so the pins were re-designated. If you should happen to put a DEVSLP enabled drive in a system that provides 3.3V power, the drive will go into low power mode and stay there.

如果想实现这个功能,首先需要硬盘支持,在ATAPI的ID命令中有定义对应的位置(Device Sleep Supported)。然后需要在BIOS中打开对应的DEVSLP 功能。
推荐使用 TxBench工具【参考2】能够直接检测当前的硬盘是否支持以及系统中这个功能是否打开。

下图是BIOS中打开DEVSLP 的截图。 1标记位置是当前硬盘是否支持,2标记位置是当前系统是否支持DEVSLP。可以看到都是支持的。

image001

下图是BIOS中关闭DEVSLP的截图,可以对照上面查看。

image002

下面是对这块硬盘(Samsung 750 EVO )的简单性能测试:

image003

如果你是专门为 Connected Standby 选购硬盘的话,个人建议最好入手 Intel SSD最保险。

参考:
1. http://www.storageinterface.com/articles/12-sata-devslp
2. http://www.texim.jp/txbenchus.html

Step to UEFI (111)Shell下面的HDD菜单

最近在尝试编写Shell下面的一些关于磁盘工具,设计了一个Shell 下面硬盘的选择菜单。这个程序会枚举当前系统中的全部硬盘,然后以菜单的形式提供给客户操作。
程序流程介绍,首先,枚举系统中全部Block Io 的 Handle,之后检查每个 Block Io 的Media,看看是否为物理硬盘,因为我们的操作目标是完整的硬盘而不是某个分区。接下来,再针对每个Handle枚举上面的 DevicePath Protocol, 我们需要这个 Protocol 转化出来的String作为给用户的提示信息。最后,结合之前我们编写过的一个菜单程序的框架,将HDDx的选择和提示信息展示在屏幕上。用户通过上下键和回车键来选择希望操作的硬盘。
具体代码如下,其中包括了一些简单的注视,还有一些用于Debug的位置。

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

#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Protocol/DevicePath.h>
#include <Protocol/DevicePathToText.h>

extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_HANDLE					 gImageHandle;

EFI_GUID gEfiDevicePathToTextProtocolGuid =
		{ 0x8B843E20, 0x8132, 0x4852, 
			{ 0x90, 0xCC, 0x55, 0x1A, 0x4E, 0x4A, 0x7F, 0x1C }};
EFI_GUID	gEfiBlockIoProtocolGuid = 
		{ 0x964E5B21, 0x6459, 0x11D2, 
			{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
			
#define MAXDEVICEPATH  255	
typedef struct  {
	CHAR16		Name[6];
	CHAR16  	DevicePathString[MAXDEVICEPATH];
	EFI_HANDLE	Controller;
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
} tItem;

#define NUM  9
tItem	Items[]=	{
	{L"HDD-0",L"",NULL,NULL},
	{L"HDD-1",L"",NULL,NULL},
	{L"HDD-2",L"",NULL,NULL},
	{L"HDD-3",L"",NULL,NULL},
	{L"HDD-4",L"",NULL,NULL},
	{L"HDD-5",L"",NULL,NULL},
	{L"HDD-6",L"",NULL,NULL},
	{L"HDD-7",L"",NULL,NULL},
	{L"HDD-8",L"",NULL,NULL}
};	

void DrawMenu(UINTN x1,UINTN y1,UINTN Count)
{
	UINTN	i;
	EFI_INPUT_KEY	Key;
	EFI_STATUS		Status;
	int current=0;
	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1  ,L"%S",L"Choose the DISK you want to ERASE");	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1+1,L"%S",L"Arrow Up/Down, Enter Select, ESC Quit");
	ShellPrintEx((UINT32)x1,(UINT32)y1,L"%H%S",Items[0].Name);
	for (i=1;i<Count;i++) {
		ShellPrintEx((UINT32)x1,(UINT32)(y1+i),L"%N%S",Items[i].Name);
	}
	ShellPrintEx(
		0,(UINT32)(y1+Count),
		L"%s \n",
		Items[current].DevicePathString
	);	
	
	Key.ScanCode=SCAN_NULL;
	while (SCAN_ESC!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%N%S",Items[current].Name);
			if (SCAN_UP == Key.ScanCode) {current = (UINT32)(current-1+Count)%Count;}
			if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%Count;}
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%H%S",Items[current].Name);			
			/*
			ShellPrintEx(
					x1,y1+Count,
					L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r",
					current,
					Key.ScanCode,
					Key.UnicodeChar);		
			*/
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L" ");							
			for (i=0;i<MAXDEVICEPATH-1;i++)	{
				Print(L" ");				
			}
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L"%s \n",
					Items[current].DevicePathString
					);
					
		}
		if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) {
			//ShellPrintEx(x1,y1+Count,L"You have chosen: %N%S",Items[current].Name);
			break;
		}
	};	

}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS				Status;
    UINTN					HandleCount,HandleIndex,ItemIndex=0;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   			*BlockIo;
	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL	*Device2TextProtocol = 0;
	
	//Get the Protcol for DevicePath to String
    Status = gBS->LocateProtocol(
            &gEfiDevicePathToTextProtocolGuid,
            NULL,
            (VOID**)&Device2TextProtocol
            );
	
	//Enumate all the handle which has Block Io Protocol
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
			//Get all the Block Io Protocol
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//Check if it's a logical disk, 
			//we only operate on a physical disk		
			if (BlockIo->Media->LogicalPartition == 0) {
				EFI_DEVICE_PATH_PROTOCOL*	DiskDevicePath=NULL;   
				//Get the Block Io Protocol
				Status = gBS->HandleProtocol(
					BlockControllerHandles[HandleIndex],
					&gEfiDevicePathProtocolGuid, 
					(VOID*)&DiskDevicePath
					);
				if (EFI_ERROR(Status)) {
					Print(L"Get device path error [%r]\n",Status);
					continue;
				} 
				{
					CHAR16*	TextDevicePath = 0;
					//Convert Disk path to a human readable string
					TextDevicePath = 
						Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, FALSE, TRUE); 
					//Print(L"%s\n", TextDevicePath);
					
					//Save all the data to a struct
					Items[ItemIndex].Controller=BlockControllerHandles[HandleIndex];
					Items[ItemIndex].BlockIo=BlockIo;
					StrCpy(Items[ItemIndex].DevicePathString,TextDevicePath);
					ItemIndex++;
					
					if(TextDevicePath)gBS->FreePool(TextDevicePath);				
				}
			}//if (BlockIo->Media->LogicalPartition == 0)
        }	//for (HandleIndex = 0;
		
		//You can output all the data you have gotten here
		//UINT32	i;
		//for (i=0;i<ItemIndex;i++)	{
		//	Print(L"%d %s %s\n",i,Items[i].Name,Items[i].DevicePathString);
		//}
		//Print(L"[%d]",ItemIndex);
		
		gST->ConOut->ClearScreen(gST->ConOut);	
		//Set our menu from (0,0) to (21,6), and there should be ItemIndex items
		DrawMenu(0,0,ItemIndex);
		
        gBS->FreePool(BlockControllerHandles);
	
    }			
	
  return EFI_SUCCESS;
}

 

程序在模拟其中运行结果如下,同样的我也在 Intel ApolloLake HDK 平台上试验过,工作也是正常的。

hddmenu

最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载:

hddmenu

Lenorade的按键值

对于 Leonardo 来说,最大的特点就是可以直接模拟USB键盘鼠标。
一般来说,当我们需要发送某一个按键的时候可以直接使用Keyboard.print(‘a’)这样的方式,或者在Keyboard.h 中找到定义好的键值,比如:#define KEY_F1 0xC2。
但是最近我遇到一个问题:这个文件中定义的键值我无法在其他资料上找到,让人非常恼火,例如:没有一份资料上说 CAPS LOCK值为0xC1,但是使用Keyboard.h中定义的 #define KEY_CAPS_LOCK 0xC1 确实是好用的。最后才想起来应该去看一下对应的代码(开源的优点)。最终,找到的代码如下:

size_t Keyboard_::press(uint8_t k) 
{
	uint8_t i;
	if (k >= 136) {			// it's a non-printing key (not a modifier)
		k = k - 136;
	} else if (k >= 128) {	// it's a modifier key
		_keyReport.modifiers |= (1<<(k-128));
		k = 0;
	} else {				// it's a printing key
		k = pgm_read_byte(_asciimap + k);
		if (!k) {
			setWriteError();
			return 0;
		}
		if (k & 0x80) {						// it's a capital letter or other character reached with shift
			_keyReport.modifiers |= 0x02;	// the left shift modifier
			k &= 0x7F;
		}
	}
	
	// Add k to the key report only if it's not already present
	// and if there is an empty slot.
	if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && 
		_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
		_keyReport.keys[4] != k && _keyReport.keys[5] != k) {
		
		for (i=0; i<6; i++) {
			if (_keyReport.keys[i] == 0x00) {
				_keyReport.keys[i] = k;
				break;
			}
		}
		if (i == 6) {
			setWriteError();
			return 0;
		}	
	}
	sendReport(&_keyReport);
	return 1;
}

 

就是说,当我们使用KEY_CAPS_LOCK ==0xC1,会先有一个 193-136=57的动作,最后发送出去的实际上是 57(0x39),在USB HID Usage Tables 有下面的定义:

keydef

同样的文档中我们可以查到 Scroll Lock == 71(0x47) Num Lock == 83 (0x53)。于是,定义如下:

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

 

最终,我们编写一个代码,能让键盘上的三个LED逐次亮灭

#include "Keyboard.h"

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

void setup() {
  pinMode(A0, INPUT_PULLUP);
  Keyboard.begin();
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
  
}

void loop() {
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
   
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);

  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  
}

 

Step to UEFI (110)在 Shell 下面使用 Unicode 的制表符

在DOS时代,可以用Ascii制表符做出好看的界面。

tabm

同样的 Unicode 中也有制表符【参考1】

tabn

下面的代码就是枚举输出UEFI 下面定义的全部制表符。

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


extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR16 ChrSide[2] = {0,0};
	
ChrSide[0]=BOXDRAW_HORIZONTAL;
Print(L"BOXDRAW_HORIZONTAL [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_VERTICAL;
Print(L"BOXDRAW_VERTICAL [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_DOWN_RIGHT;
Print(L"BOXDRAW_DOWN_RIGHT [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_DOWN_LEFT;
Print(L"BOXDRAW_DOWN_LEFT [%s]\n",ChrSide);                  
ChrSide[0]=BOXDRAW_UP_RIGHT;
Print(L"BOXDRAW_UP_RIGHT [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_UP_LEFT;
Print(L"BOXDRAW_UP_LEFT [%s]\n",ChrSide);                    
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT;
Print(L"BOXDRAW_VERTICAL_RIGHT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_LEFT;
Print(L"BOXDRAW_VERTICAL_LEFT [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOWN_HORIZONTAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_HORIZONTAL;
Print(L"BOXDRAW_UP_HORIZONTAL [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL [%s]\n",ChrSide);        
ChrSide[0]=BOXDRAW_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_HORIZONTAL [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOWN_RIGHT_DOUBLE;
Print(L"BOXDRAW_DOWN_RIGHT_DOUBLE [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_RIGHT;
Print(L"BOXDRAW_DOWN_DOUBLE_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_RIGHT;
Print(L"BOXDRAW_DOUBLE_DOWN_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_LEFT_DOUBLE;
Print(L"BOXDRAW_DOWN_LEFT_DOUBLE [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_LEFT;
Print(L"BOXDRAW_DOWN_DOUBLE_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_LEFT;
Print(L"BOXDRAW_DOUBLE_DOWN_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_UP_RIGHT_DOUBLE;
Print(L"BOXDRAW_UP_RIGHT_DOUBLE [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_DOUBLE_RIGHT;
Print(L"BOXDRAW_UP_DOUBLE_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOUBLE_UP_RIGHT;
Print(L"BOXDRAW_DOUBLE_UP_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_LEFT_DOUBLE;
Print(L"BOXDRAW_UP_LEFT_DOUBLE [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_UP_DOUBLE_LEFT;
Print(L"BOXDRAW_UP_DOUBLE_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_DOUBLE_UP_LEFT;
Print(L"BOXDRAW_DOUBLE_UP_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_RIGHT_DOUBLE [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_RIGHT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_RIGHT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_LEFT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_LEFT_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_LEFT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_LEFT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_DOWN_HORIZONTAL_DOUBLE [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOWN_DOUBLE_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_DOWN_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_UP_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_UP_HORIZONTAL_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_UP_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_UP_DOUBLE_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_UP_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_UP_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BLOCKELEMENT_FULL_BLOCK;
Print(L"BLOCKELEMENT_FULL_BLOCK [%s]\n",ChrSide);            
ChrSide[0]=BLOCKELEMENT_LIGHT_SHADE;
Print(L"BLOCKELEMENT_LIGHT_SHADE [%s]\n",ChrSide);           
ChrSide[0]=GEOMETRICSHAPE_UP_TRIANGLE;
Print(L"GEOMETRICSHAPE_UP_TRIANGLE [%s]\n",ChrSide);         
ChrSide[0]=GEOMETRICSHAPE_RIGHT_TRIANGLE;
Print(L"GEOMETRICSHAPE_RIGHT_TRIANGLE [%s]\n",ChrSide);      
ChrSide[0]=GEOMETRICSHAPE_DOWN_TRIANGLE;
Print(L"GEOMETRICSHAPE_DOWN_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=GEOMETRICSHAPE_LEFT_TRIANGLE;
Print(L"GEOMETRICSHAPE_LEFT_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=ARROW_LEFT;
Print(L"ARROW_LEFT [%s]\n",ChrSide);                         
ChrSide[0]=ARROW_UP;
Print(L"ARROW_UP [%s]\n",ChrSide);                           
ChrSide[0]=ARROW_RIGHT;
Print(L"ARROW_RIGHT [%s]\n",ChrSide);                        
ChrSide[0]=ARROW_DOWN;
Print(L"ARROW_DOWN [%s]\n",ChrSide);                         

  return EFI_SUCCESS;
}

 

上述代码运行结果:

tab4

tab3

tab2

tab1

完整的代码下载:

tabtest

使用上面的代码,绘制的一个方框:

tab5

参考:
1. https://en.wikipedia.org/wiki/Box-drawing_character

给Arduino 设计一个序列号

之前看到很多朋友询问过如何实现“序列号”的功能。就是对于每一个 Arduino 都有独立的编码,这样可以实现一些身份识别之类的功能。
最容易实现的就是每次都修改程序中的定义。但是这样用起来耗时很长,并且必须有源程序才行,琢磨了一下,提出一种软件的实现方法,欢迎大家一起讨论。
本文是纯软件的序列号实现,因此通用性会很好,这里使用 Arduino Uno 来实现。
第一个问题是:如何在命令行下进行刷写。首先将IDE中的上传调试功能打开

image001

这样你可以看到IDE具体使用了什么上传命令(Arduino IDE 只是一个外壳,具体的编译和上传是由其他程序实现的。IDE只是调用和重定向了运行的结果)

image002

具体的话,上面图片中的烧写代码如下:

C:\Users\labz\Documents\arduino\hardware\tools\avr/bin/avrdude -CC:\Users\ labz \Documents\arduino\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -PCOM18 -b115200 -D -Uflash:w:C:\Users\ labz \AppData\Local\Temp\build3ee88a1b99cef46fdbee27900bbb7d73.tmp/Blink.ino.hex:i

知道了这一行命令,我们可以直接在CMD窗口中输入这个命令从而完成上传的动作。
第二步,直接修改生成的 HEX文件。这里制作一个带有序列号的DEMO。我们定义程序的序列号为 20170101,从串口输出这个编号以便检查,代码如下:

String sign="LABZ";
String number="20170101";

// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
  Serial.print(number);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(2000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

 

运行结果如下
image003

我们的目标就是直接用工具修改这个序列号,然后上传到 Arduino 上。同样是根据前面的命令行,我们可以知道上传的HEX文件位置:
image004

用文本编辑软件即可打开这个文件,搜索 4C 41 42 5A (ASCII:LABZ, 这就是为什么我们要在代码中定义String Sign,它能帮助我们定位后面的序列号的位置。其实不用这个作为定位手段直接搜索代码中定义的序列号也是可以的)。

仔细查看,后面的数据 32 30 31 37 30 36 就是我们的序列号 “2017”。之后又遇到的问题是,这个HEX文件是有简单的格式的。根据【参考1】,我们可以得知每行最后一个数值是这一行的校验和。例如

:100CE400FB000F019B014C41425A00323031373036
image005

其中的最后一个 36 是校验和计算方法如下:

10+0C+E4+00+FB+00+0F+01+9B+01+4C+41+42+5A+00+32+30+31+37+30=0x4CA 校验和的意思是加上一个值之后最后2位会变成00,0xCA+0x36=0x100

假设,我们的目标是将序列号修改为 19731023 = 30 39 37 33 31 30 32 33

经过分析,我们修改如下
:100CE400FB000F019B014C41425A0031393733312B
:040CF4003032330067
其中最后的校验和是通过前面的数值计算得到的。当然,更简单的办法是根据第一步,直接烧写,烧录程序会提醒你的校验和不匹配。例如,我使用错误的检验和,0x36 在烧写的时候收到提示,当前计算结果为 0x2B:

image006

修改完成之后,再次查看,序列号就是我新定义的了。
image007

可以看出,使用手工修改还是比较麻烦的,但是掌握了方法,编写自动化的工具也就是分分钟的事情了。
参考:
1. http://www.manysj.com/thread-13501-1-1.html HEX文件格式详解