编写一个APP教孩子认东西

最近学习了一下 MIT App Inventor 2 ,这是一种非常简单的可以方便编写 Android APP 的程序。

原理很简单,首先设计自己的二维码(比如:字符串 men2 表示“门”;deng1表示灯),打印出来之后贴在物品上,然后用自己编写的APP去扫描这些二维码,播放事先录制好的声音。

yHotaMFUwXU3OMcbJIRjJTF7g-HwZb0iSEhqVoA7ZLQeAgAACgMAAFBO

工作的视频:
http://www.tudou.com/programs/view/-r_TY3ll7dc/?resourceId=0_06_02_99

本文提到的二维码和AIA工程文件下载

app1

Step to UEFI (81) —–测试文件生成器

一些情况下,我们需要在 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

Step to UEFI (80) —–取得汉字的字形

EFI 在设计之初就考虑了多语言的支持,使用HII可以轻松的实现汉字的显示。本篇文章介绍获得汉字字形的其他方法,掌握这种方法之后可以在没有HII支持的情况下显示汉字。当然,程序只是为了演示原理,介绍如何读取16×16的汉字字形信息,没有转为图形。
比如:“宋”字查询到的区位码是4346 【参考3】,意思是区码为43,位码是46。计算这个字在字库中的方法是:((43-1)*94+(46-1))*32=6828。之后,在字库文件的 6828偏移处连续读取32个字节即可。

shz

代码如下:

#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:这样的目录下):

image002

更换一下区位码,我们还可以取得“我”的字形。
image004

完整的代码下载:
HZ

最后,关于【参考1】的代码多说两句。其中有unsigned char word[3] = “我”; 这样直接的定义,这是因为很久很久之前,为了编便于 PC处理汉字定义一个汉字由两个大于127的ASCII码组成。组成的规则是:区码+A0,位码+A0。比如,我在中文环境下定义一个“宋”,
image008

然后切换到英文环境下打开,看到的是2个ASCII码,

image010

如果再切换到十六进制编辑,会看到 CB CE (前提是保存为 ANSI格式,如果你存为unicode,看到的又是另外的东西)

image012

时代已经变了,对于 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

Step to UEFI (79) —–取得字形

我们可以通过EFI_HII_FONT_PROTOCOL【参考1】 中的 GetGlyph来取得一些字符的字形定义。

gly1

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 数组,隐隐约约能看到其中有一个形状

gly2

程序后面有一个判断,直接输出星号和空格,结果如下,这样就看到非常清楚了。

gly3

完整的代码下载
GetGlyph

唯一的问题是:我还不知道取得这个东西能有什么用途…….

参考:
1.UEFI spec 2.4 P1711

Step to UEFI —– TIPs

最近调试程序的时候遇到一个奇怪的 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.

Step to UEFI (78) —–SERIAL_IO_PROTOCOL

串口是非常有效和廉价的Debug手段,在开发中,几乎所有的UEFI 主板都会支持串口,本文介绍如何在Shell下面实现 串口通讯。
与之相关的是 EFI_SERIAL_IO_PROTOCOL,这个 Protocol 的定义可以在 UEFI Spec【参考1】中看到:

image001

代码如下:

#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接收数据。

程序运行结果:

image002

完整代码下载:

SerialTest

特别注意: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