最近学习了一下 MIT App Inventor 2 ,这是一种非常简单的可以方便编写 Android APP 的程序。
原理很简单,首先设计自己的二维码(比如:字符串 men2 表示“门”;deng1表示灯),打印出来之后贴在物品上,然后用自己编写的APP去扫描这些二维码,播放事先录制好的声音。
工作的视频:
http://www.tudou.com/programs/view/-r_TY3ll7dc/?resourceId=0_06_02_99
本文提到的二维码和AIA工程文件下载
最近学习了一下 MIT App Inventor 2 ,这是一种非常简单的可以方便编写 Android APP 的程序。
原理很简单,首先设计自己的二维码(比如:字符串 men2 表示“门”;deng1表示灯),打印出来之后贴在物品上,然后用自己编写的APP去扫描这些二维码,播放事先录制好的声音。
工作的视频:
http://www.tudou.com/programs/view/-r_TY3ll7dc/?resourceId=0_06_02_99
本文提到的二维码和AIA工程文件下载
一些情况下,我们需要在 Shell 下面使用文件进行测试,这次编写一个工具,生成使用随机数填充的文件。为了校验方便,文件的末尾有一个 checksum,按照 32Bits 的 UINTN ,整个文件的和应该是 0 .
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
//copied from \StdLib\Include\stdlib.h
/** Expands to an integer constant expression that is the maximum value
returned by the rand function.
The value of the RAND_MAX macro shall be at least 32767.
**/
#define RAND_MAX 0x7fffffff
//Copied from \StdLib\LibC\StdLib\Rand.c
static UINT32 next = 1;
/** Compute a pseudo-random number.
*
* Compute x = (7^5 * x) mod (2^31 - 1)
* without overflowing 31 bits:
* (2^31 - 1) = 127773 * (7^5) + 2836
* From "Random number generators: good ones are hard to find",
* Park and Miller, Communications of the ACM, vol. 31, no. 10,
* October 1988, p. 1195.
**/
UINT32
rand()
{
INT32 hi, lo, x;
/* Can't be initialized with 0, so use another value. */
if (next == 0)
next = 123459876;
hi = next / 127773;
lo = next % 127773;
x = 16807 * lo - 2836 * hi;
if (x < 0)
x += 0x7fffffff;
return ((next = x) % ((UINT32)RAND_MAX + 1));
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
UINTN FileSize=0;
EFI_STATUS Status;
EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
EFI_FILE_PROTOCOL *FileHandle=0;
UINTN *HandleBuffer;
UINT32 i,t,sum=0;
if (Argc>1) {
FileSize=StrDecimalToUintn(Argv[1])*1024;
//Print(L"%d",FileSize);
}
else
{
FileSize=1024*1024*10;
}
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,
(CHAR16 *) L"ztest.bin",
EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
0);
if (EFI_ERROR(Status) || (FileHandle==0)) {
Print(L"Open error \r\n");
return Status;
}
HandleBuffer = AllocateZeroPool(FileSize);
if (HandleBuffer == NULL) {
Print(L"Not enough memory!\n");
return Status;
}
for (i=0;i<(FileSize-4)/4;i++)
{
t=rand();
*(HandleBuffer+i)=t;
sum=sum+t;
}
*(HandleBuffer+(FileSize-4)/4)=(UINT32)(0-sum);
Status = FileHandle -> Write(FileHandle, &FileSize, HandleBuffer);
Print(L"Write Done \r\n");
FreePool(HandleBuffer);
Status = FileHandle -> Close (FileHandle);
return EFI_SUCCESS;
}
除了生成文件的工具,还有一个用来校验文件的工具,他的作用是将文件全部内容相加,查看结果是否为0.
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_FILE_HANDLE FileHandle;
RETURN_STATUS Status;
EFI_FILE_INFO *FileInfo = NULL;
UINT32 *HandleBuffer=NULL;
UINTN ReadSize;
UINT32 i,sum=0;
//Check if there is a parameter
if (Argc == 1) {
Print(L"Usage: crctest [filename]\n");
return 0;
}
//Open the file given by the parameter
Status = ShellOpenFileByName(Argv[1], (SHELL_FILE_HANDLE *)&FileHandle,
EFI_FILE_MODE_READ , 0);
if(Status != RETURN_SUCCESS) {
Print(L"OpenFile failed!\n");
return EFI_SUCCESS;
}
//Get file size
FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);
//if the file size is not the multiple of 4, we don't check it!
if ((UINTN) FileInfo-> FileSize % 4 !=0 ) {
Print(L"We can't check this file as the filesize is wrong!\n");
}
//Allocate a memory buffer
HandleBuffer = AllocateZeroPool((UINTN) FileInfo-> FileSize);
if (HandleBuffer == NULL) {
return (SHELL_OUT_OF_RESOURCES); }
ReadSize=(UINTN) FileInfo-> FileSize;
//Load the whole file to the buffer
Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer);
for (i=0;i< FileInfo-> FileSize / 4;i++)
{
sum=sum+*(HandleBuffer+i);
}
if (sum==0) {
Print(L"Pass!\n");
}
else
{
Print(L"Fail!\n");
}
FreePool(HandleBuffer);
FileHandle -> Close (FileHandle);
return EFI_SUCCESS;
}
完整的代码和 EFI 文件下载:
TestFileGen
Chker
EFI 在设计之初就考虑了多语言的支持,使用HII可以轻松的实现汉字的显示。本篇文章介绍获得汉字字形的其他方法,掌握这种方法之后可以在没有HII支持的情况下显示汉字。当然,程序只是为了演示原理,介绍如何读取16×16的汉字字形信息,没有转为图形。
比如:“宋”字查询到的区位码是4346 【参考3】,意思是区码为43,位码是46。计算这个字在字库中的方法是:((43-1)*94+(46-1))*32=6828。之后,在字库文件的 6828偏移处连续读取32个字节即可。
代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>
extern EFI_BOOT_SERVICES *gBS;
#define FONT_SIZE (16)
#define HZ_INDEX(hz) ((hz[0] - 1) * 94 + (hz[1] -1))*32
#define DOTS_BYTES (FONT_SIZE * FONT_SIZE / 8)
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_FILE_HANDLE FileHandle;
RETURN_STATUS Status;
EFI_FILE_INFO *FileInfo = NULL;
EFI_HANDLE *HandleBuffer=NULL;
UINTN ReadSize;
UINTN i,j;
UINT8 HZChar[2] = {43,46};
CHAR8 *c;
CHAR8 k;
//Open the file given by the parameter
Status = ShellOpenFileByName(L"HZK16K.BIN",
(SHELL_FILE_HANDLE *)&FileHandle,
EFI_FILE_MODE_READ ,
0);
if(Status != RETURN_SUCCESS) {
Print(L"OpenFile failed!\n");
return EFI_SUCCESS;
}
//Get file size
FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);
//Allocate a memory buffer
HandleBuffer = AllocateZeroPool((UINTN) FileInfo-> FileSize);
if (HandleBuffer == NULL) {
return (SHELL_OUT_OF_RESOURCES); }
ReadSize=(UINTN) FileInfo-> FileSize;
//Load the whole file to the buffer
Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer);
if(Status != RETURN_SUCCESS) {
Print(L"ReadFile failed!\n");
return EFI_SUCCESS;
}
for (i=0;i<DOTS_BYTES;i++)
{
c=((UINT8*)HandleBuffer)+HZ_INDEX(HZChar)+i;
k=*c;
for (j=0;j<8;j++)
{
if (0 == (k & 0x80))
{
Print(L" ");
}
else
{
Print(L"OO");
}
k=k<<1;
}
if ((i+1)%2==0) {Print(L"\n");}
}
FreePool(HandleBuffer);
ShellCloseFile((SHELL_FILE_HANDLE *)&FileHandle);
}
运行结果(特别注意要把字库文件放在Fsnt0:这样的目录下):
完整的代码下载:
HZ
最后,关于【参考1】的代码多说两句。其中有unsigned char word[3] = “我”; 这样直接的定义,这是因为很久很久之前,为了编便于 PC处理汉字定义一个汉字由两个大于127的ASCII码组成。组成的规则是:区码+A0,位码+A0。比如,我在中文环境下定义一个“宋”,

然后切换到英文环境下打开,看到的是2个ASCII码,
如果再切换到十六进制编辑,会看到 CB CE (前提是保存为 ANSI格式,如果你存为unicode,看到的又是另外的东西)
时代已经变了,对于 Windows 编程来说上述的知识都已经过时,如果你需要搞嵌入式开发,还是值得认真学习和理解。
另外,PC刚开始流行的时候,很长一段时间都有汉字不适合PC处理等等的言论,对于普通用户来说,汉字的输入也是很大的困扰。而最终的解决,我认为是人们强烈的交流的需求使得这样的问题很快被克服掉了。时至今日,我仍然能记得同一个寝室的胖子在他的 Nokia手机上,在十几个按键上运指如飞和各种MM聊得火热。很快,没人再认为汉字在PC的普及上是一个问题。
参考 :
1.https://blog.twofei.com/embedded/hzk.html HZK16汉字16*16点阵字库的使用及示例程序
2.http://blog.csdn.net/turingo/article/details/8191712 图灵狗的专栏
3.http://www.jscj.com/index/gb2312.php 汉字区位码查询系统 (具体)
==============================================================
2018年12月30日 补充: 在【参考1】的文章中提供了一个取得字模的代码,我在 Win10 下实验过,很好用:
代码如下:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fphzk = NULL;
int i, j, k, offset;
int flag;
unsigned char buffer[32];
unsigned char word[5];
unsigned char key[8] = {
0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
};
fphzk = fopen("hzk16", "rb");
if(fphzk == NULL){
fprintf(stderr, "error hzk16\n");
return 1;
}
while(1){
printf("输入要生成字模的汉字(多个):");
for(;;){
fgets((char*)word, 3, stdin);
if(*word == '\n')
break;
offset = (94*(unsigned int)(word[0]-0xa0-1)+(word[1]-0xa0-1))*32;
fseek(fphzk, offset, SEEK_SET);
fread(buffer, 1, 32, fphzk);
for(k=0; k<16; k++){
for(j=0; j<2; j++){
for(i=0; i<8; i++){
flag = buffer[k*2+j]&key[i];
printf("%s", flag?"●":"○");
}
}
printf("\n");
}
printf("uchar code key[32] = {");
for(k=0; k<31; k++){
printf("0x%02X,", buffer[k]);
}
printf("0x%02X};\n", buffer[31]);
printf("\n");
}
}
fclose(fphzk);
fphzk = NULL;
return 0;
}
如果你是英文的OS,需要先切换内码为 CP936
我们可以通过EFI_HII_FONT_PROTOCOL【参考1】 中的 GetGlyph来取得一些字符的字形定义。
GetGlyph 的原型可以在 \MdePkg\Include\Protocol\HiiFont.h 中找到:
/**
Convert the glyph for a single character into a bitmap.
@param This A pointer to the EFI_HII_FONT_PROTOCOL instance.
@param Char The character to retrieve.
@param StringInfo Points to the string font and color
information or NULL if the string should use
the default system font and color.
@param Blt This must point to a NULL on entry. A buffer will
be allocated to hold the output and the pointer
updated on exit. It is the caller's responsibility
to free this buffer.
@param Baseline The number of pixels from the bottom of the bitmap
to the baseline.
@retval EFI_SUCCESS The glyph bitmap created.
@retval EFI_OUT_OF_RESOURCES Unable to allocate the output buffer Blt.
@retval EFI_WARN_UNKNOWN_GLYPH The glyph was unknown and was
replaced with the glyph for
Unicode character code 0xFFFD.
@retval EFI_INVALID_PARAMETER Blt is NULL, or Width is NULL, or
Height is NULL
**/
typedef
EFI_STATUS
(EFIAPI *EFI_HII_GET_GLYPH)(
IN CONST EFI_HII_FONT_PROTOCOL *This,
IN CONST CHAR16 Char,
IN CONST EFI_FONT_DISPLAY_INFO *StringInfo,
OUT EFI_IMAGE_OUTPUT **Blt,
OUT UINTN *Baseline OPTIONAL
);
其中 Char 是你要取对应字形的文字(特别注意是单个的CHAR16),StringInfo 为 NULL时取得的是系统默认的字体,输出结果在 Blt 中。最后一项含义我不清楚……
再看一下输出结果是 EFI_IMAGE_OUTPUT。它的定义在 \MdePkg\Include\Protocol\HiiImage.h中。其中含有一个Union的定义,在我们这里使用时,会按照 EFI_GRAPHICS_OUTPUT_BLT_PIXEL 给出。
最后写一个简单的测试程序如下,功能是取得“z”字符的字形。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/HiiFont.h>
extern EFI_BOOT_SERVICES *gBS;
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status = 0;
UINTN BaseLine;
UINTN i,j;
EFI_HII_FONT_PROTOCOL *HiiFont = 0;
EFI_IMAGE_OUTPUT *Blt=NULL;
CHAR8 *c,*p;
Status = gBS->LocateProtocol (&gEfiHiiFontProtocolGuid, NULL, (VOID **) &HiiFont);
if (Status!=EFI_SUCCESS) {
Print(L"Error when LocateProtocol gEfiHiiFontProtocolGuid. Code[%r]\n",Status);
return EFI_SUCCESS;
}
Status = HiiFont->GetGlyph (
HiiFont, //Protocol instance
L'Z', //Show char 'Z'
NULL, //Use the defualt system font and color
&Blt, //GLYPH information
&BaseLine); //I don't know
Print(L"This is [%d]x[%d]\n",Blt->Width,Blt->Height);
c=(CHAR8*) Blt->Image.Bitmap;
for (j=0;j<Blt->Height;j++)
{
p=c;
for (i=0;i<Blt->Width;i++)
{
Print(L"%2X",(*c)&0xFF);
c=c+4;
}
Print(L" ");
c=p+1;
for (i=0;i<Blt->Width;i++)
{
Print(L"%2X",(*c)&0xFF);
c=c+4;
}
Print(L" ");
c=p+2;
for (i=0;i<Blt->Width;i++)
{
Print(L"%2X",(*c)&0xFF);
c=c+4;
}
c=p+(Blt->Width*4);
Print(L"\n");
}
c=(CHAR8*) Blt->Image.Bitmap;
for (j=0;j<Blt->Height;j++)
{
for (i=0;i<Blt->Width;i++)
{
if (*c!=0) {
Print(L"*");
}
else
{
Print(L" ");
}
c=c+4;
}
Print(L"\n");
}
return EFI_SUCCESS;
}
运行结果,首先输出的是 R G B 数组,隐隐约约能看到其中有一个形状
程序后面有一个判断,直接输出星号和空格,结果如下,这样就看到非常清楚了。
完整的代码下载
GetGlyph
唯一的问题是:我还不知道取得这个东西能有什么用途…….
参考:
1.UEFI spec 2.4 P1711
最近调试程序的时候遇到一个奇怪的 Warning ,查了一会才找到原因:
c:\edk\AppPkg\Applications\C4066\C4066.c(17) : warning C4066: characters beyond first in wide-character constant ignored
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
Print(L'[%d]',2015);
return EFI_SUCCESS;
}
错误产生的原因是:把双引号写成了单引号,编译器以为你要定义一个 CHAR16 的字符,所以要忽略一些东西。修改的方法是,改成双引号即可。
参考:
1.https://msdn.microsoft.com/en-us/library/aa748819(v=vs.60).aspx
Compiler Warning (level 3) C4066
Visual Studio 6.0
characters beyond first in wide-character constant ignored
The compiler processes only the first character of a wide-character constant.
串口是非常有效和廉价的Debug手段,在开发中,几乎所有的UEFI 主板都会支持串口,本文介绍如何在Shell下面实现 串口通讯。
与之相关的是 EFI_SERIAL_IO_PROTOCOL,这个 Protocol 的定义可以在 UEFI Spec【参考1】中看到:
代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/SerialIo.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
EFI_GUID gEfiSerialIoProtocolGuid = { 0xBB25CF6F, 0xF1D4, 0x11D2, { 0x9A, 0x0C, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0xFD }};
EFI_STATUS
DumpSetting(
IN UINT64 BaudRate,
IN UINT32 ReceiveFifoDepth,
IN UINT32 Timeout,
IN EFI_PARITY_TYPE Parity,
IN UINT8 DataBits,
IN EFI_STOP_BITS_TYPE StopBits)
{
Print(L" Timeout: [%d]\n",Timeout);
Print(L" BaudRate:[%ld]\n",BaudRate);
Print(L" DataBits:[%d]\n",DataBits);
Print(L" Parity: [%d]\n",Parity);
Print(L" StopBits:[%d]\n",StopBits);
Print(L" ReceiveFifoDepth:[%d]\n",ReceiveFifoDepth);
return EFI_SUCCESS;
}
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_STATUS Status;
EFI_SERIAL_IO_PROTOCOL *Serial;
CHAR8 *Textbuf1 = "www.lab-z.com waiting.........\n";
CHAR8 *Textbuf2 = "Continue............\n";
CHAR8 *Textbuf3 = "12345678";
CHAR16 *Textbuf4 = L" ";
UINTN BufferSize;
EFI_TIME TimeStart,TimeEnd;
Status = gBS->LocateProtocol(
&gEfiSerialIoProtocolGuid,
NULL,
(VOID **)&Serial);
if (EFI_ERROR(Status)) {
Print(L"Cannot find EFI_SERIAL_IO_PROTOCOL \r\n");
return Status;
}
Print(L"Current Settings:\n");
DumpSetting(
Serial->Mode->BaudRate,
Serial->Mode->ReceiveFifoDepth,
Serial->Mode->Timeout,
Serial->Mode->Parity,
Serial->Mode->DataBits & 0xFF,
Serial->Mode->StopBits);
// Baudrate 115200,Data Bits=8,Parity=None,Stop Bits=1,Flow Type= None
Status=Serial->SetAttributes( Serial,
115200,
Serial->Mode->ReceiveFifoDepth,
Serial->Mode->Timeout,
NoParity,
8,
OneStopBit);
if (Status!=EFI_SUCCESS) {
Print(L"[%d], %r",Status,Status);
}
Print(L"New Settings:\n");
DumpSetting(
Serial->Mode->BaudRate,
Serial->Mode->ReceiveFifoDepth,
Serial->Mode->Timeout,
Serial->Mode->Parity,
Serial->Mode->DataBits & 0xFF,
Serial->Mode->StopBits);
BufferSize=AsciiStrLen(Textbuf1);
Serial->Write(Serial,&BufferSize,Textbuf1);
gRT->GetTime(&TimeStart,NULL);
TimeEnd=TimeStart;
while ((TimeEnd.Hour - TimeStart.Hour) * 60 * 60
+ (TimeEnd.Minute - TimeStart.Minute)*60
+ (TimeEnd.Second - TimeStart.Second) < 30)
{
BufferSize=AsciiStrLen(Textbuf3);
Status=Serial->Read(Serial,&BufferSize,Textbuf3);
if ((Status==EFI_SUCCESS) && (BufferSize!=0))
{
Print(L"read [%d] %s\n",BufferSize,AsciiStrToUnicodeStr(Textbuf3,Textbuf4));
}
gRT->GetTime(&TimeEnd,NULL);
}
BufferSize=AsciiStrLen(Textbuf2);
Serial->Write(Serial,&BufferSize,Textbuf2);
return EFI_SUCCESS;
}
上面程序的基本流程:首先检查一下串口设置,打印在屏幕上,然后设置为我们通常使用的 115200。最后测试用 Write从 Shell 下发送数据出来,再尝试用Read接收数据。
程序运行结果:
完整代码下载:
特别注意:Shell 使用 Read 只能接收固定长度的数据。比如: Read(Serial,8,Textbuf) 那么只能接收8个字符,如果你只输入了7个bytes,不会有反应;如果输入了9个bytes,那么只能收到前面8个。目前不清楚为什么有这样的限制。
另外,对于普通的串口,使用GetControl 获得的当前的状态中并没有当前串口的发送接收状态。定义的 EFI_SERIAL_INPUT_BUFFER_EMPTY和EFI_SERIAL_OUTPUT_BUFFER_EMPTY 应该是给存在对应线路的串口使用的,是一种硬件线路的标志。如果你只用了 TX RX GND , 这里的状态是没有意义的。
参考:
1. UEFI Spec 2.4 P476