Step to UEFI (214)Ps2键盘的LED 分析

之前研究过Shell下如何控制USB 键盘上的 LED【参考1】,这次研究如何实现 PS2 键盘的 LED 控制。

和 USB 键盘一样,PS2 键盘同样需要实现Shell 下键盘 LED 的同步。意思是如果系统中有一个 USB 键盘和一个PS2键盘,当USB键盘 Num 键按下后,键盘LED状态也要同步到PS2 键盘上。

PS2键盘

对应的代码可以在\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c 的 UpdateStatusLights 函数中看到。根据代码基本想法如下在这个头文件中有如下定义\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2Keyboard.h:

typedef struct {
  UINTN                               Signature;

  EFI_HANDLE                          Handle;
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL      ConIn;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL   ConInEx;

  EFI_EVENT                           TimerEvent;

  UINT32                              DataRegisterAddress;
  UINT32                              StatusRegisterAddress;
  UINT32                              CommandRegisterAddress;

  BOOLEAN                             LeftCtrl;
  BOOLEAN                             RightCtrl;
  BOOLEAN                             LeftAlt;
  BOOLEAN                             RightAlt;
  BOOLEAN                             LeftShift;
  BOOLEAN                             RightShift;
  BOOLEAN                             LeftLogo;
  BOOLEAN                             RightLogo;
  BOOLEAN                             Menu;
  BOOLEAN                             SysReq;

  BOOLEAN                             CapsLock;
  BOOLEAN                             NumLock;
  BOOLEAN                             ScrollLock;

  BOOLEAN                             IsSupportPartialKey;
  //
  // Queue storing key scancodes
  //
  SCAN_CODE_QUEUE                     ScancodeQueue;
  EFI_KEY_QUEUE                       EfiKeyQueue;
  EFI_KEY_QUEUE                       EfiKeyQueueForNotify;

  //
  // Error state
  //
  BOOLEAN                             KeyboardErr;

  EFI_UNICODE_STRING_TABLE            *ControllerNameTable;

  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  //
  // Notification Function List
  //
  LIST_ENTRY                          NotifyList;
  EFI_EVENT                           KeyNotifyProcessEvent;
} KEYBOARD_CONSOLE_IN_DEV;

先找到系统中的全部  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL ,检查 Signature 确定后再使用下面的宏即可获得KEYBOARD_CONSOLE_IN_DEV结构体

#define KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a)  CR (a, KEYBOARD_CONSOLE_IN_DEV, ConIn, KEYBOARD_CONSOLE_IN_DEV_SIGNATURE)
#define TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a) \
  CR (a, \
      KEYBOARD_CONSOLE_IN_DEV, \
      ConInEx, \
      KEYBOARD_CONSOLE_IN_DEV_SIGNATURE \
      )

之后即可用UpdateStatusLights() 函数实现更改LED 状态。

最终完整代码如下:

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

#include  "Ps2Keyboard.h"

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

EFI_GUID gEfiSimpleTextInputExProtocolGuid = 
  {0xdd9e7534, 0x7762, 0x4698, 
      { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } };
                       
UINTN
EFIAPI
MicroSecondDelay (
  IN      UINTN                     MicroSeconds
  )
{
  gBS->Stall(MicroSeconds);
  return MicroSeconds;
}

/**
  Write data register.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

**/
VOID
KeyWriteDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  IoWrite8 (ConsoleIn->DataRegisterAddress, Data);
}

/**
  Read data register .

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return return the value

**/
UINT8
KeyReadDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )

{
  return IoRead8 (ConsoleIn->DataRegisterAddress);
}

/**
  Read status register.

  @param ConsoleIn  Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return value in status register

**/
UINT8
KeyReadStatusRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  return IoRead8 (ConsoleIn->StatusRegisterAddress);
}

/**
  wait for a specific value to be presented on
  8042 Data register by keyboard and then read it,
  used in keyboard commands ack

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Value     the value wanted to be waited.

  @retval EFI_TIMEOUT Fail to get specific value in given time
  @retval EFI_SUCCESS Success to get specific value in given time.

**/
EFI_STATUS
KeyboardWaitForValue (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Value
  )
{
  UINT8   Data;
  UINT32  TimeOut;
  UINT32  SumTimeOut;
  UINT32  GotIt;

  GotIt       = 0;
  TimeOut     = 0;
  SumTimeOut  = 0;

  //
  // Make sure the initial value of 'Data' is different from 'Value'
  //
  Data = 0;
  if (Data == Value) {
    Data = 1;
  }
  //
  // Read from 8042 (multiple times if needed)
  // until the expected value appears
  // use SumTimeOut to control the iteration
  //
  while (1) {
    //
    // Perform a read
    //
    for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
      if (KeyReadStatusRegister (ConsoleIn) & 0x01) {
        Data = KeyReadDataRegister (ConsoleIn);
        break;
      }

      MicroSecondDelay (30);
    }

    SumTimeOut += TimeOut;

    if (Data == Value) {
      GotIt = 1;
      break;
    }

    if (SumTimeOut >= KEYBOARD_WAITFORVALUE_TIMEOUT) {
      break;
    }
  }
  //
  // Check results
  //
  if (GotIt == 1) {
    return EFI_SUCCESS;
  } else {
    return EFI_TIMEOUT;
  }

}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  write key to keyboard

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

  @retval EFI_TIMEOUT   The input buffer register is full for putting new value util timeout
  @retval EFI_SUCCESS   The new value is sucess put into input buffer register.

**/
EFI_STATUS
KeyboardWrite (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  UINT32  TimeOut;
  UINT32  RegEmptied;

  TimeOut     = 0;
  RegEmptied  = 0;

  //
  // wait for input buffer empty
  //
  for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
    if ((KeyReadStatusRegister (ConsoleIn) & 0x02) == 0) {
      RegEmptied = 1;
      break;
    }

    MicroSecondDelay (30);
  }

  if (RegEmptied == 0) {
    return EFI_TIMEOUT;
  }
  //
  // Write it
  //
  KeyWriteDataRegister (ConsoleIn, Data);

  return EFI_SUCCESS;
}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  Show keyboard status lights according to
  indicators in ConsoleIn.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return status of updating keyboard register

**/
EFI_STATUS
UpdateStatusLights (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  EFI_STATUS  Status;
  UINT8       Command;

  //
  // Send keyboard command
  //
  Status = KeyboardWrite (ConsoleIn, 0xed);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);

  //
  // Light configuration
  //
  Command = 0;
  if (ConsoleIn->CapsLock) {
    Command |= 4;
  }

  if (ConsoleIn->NumLock) {
    Command |= 2;
  }

  if (ConsoleIn->ScrollLock) {
    Command |= 1;
  }

  Status = KeyboardWrite (ConsoleIn, Command);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);
  return Status;
}


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS    Status;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL       *ConIn;
  KEYBOARD_CONSOLE_IN_DEV              *ConsoleIn;
  UINTN         HandleIndex, HandleCount;
  EFI_HANDLE    *DevicePathHandleBuffer = NULL;
  UINTN         i;
  //
  //Get all the Handles that have SimpleTextInputEx Protocol
  //
  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiSimpleTextInputExProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);
  //
  //Open SimpleTextInputEx Protocol on each device
  //
  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
    Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiSimpleTextInputExProtocolGuid,
                      (VOID**)&ConIn);
    if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open ConIn fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }
    
    //Get KEYBOARD_CONSOLE_IN_DEV by SimpleTextInputEx Protocol    
    ConsoleIn = TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS (ConIn);
    
    //Check the signature if it's what we want    
    if (ConsoleIn->Signature==KEYBOARD_CONSOLE_IN_DEV_SIGNATURE) {
            // Turn Off all LEDs
            ConsoleIn->CapsLock=0;
            ConsoleIn->NumLock=0;
            ConsoleIn->ScrollLock=0;
            UpdateStatusLights(ConsoleIn);
                    
            for (i=0;i<20;i++) {
                    ConsoleIn->NumLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->NumLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->CapsLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->CapsLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->ScrollLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->ScrollLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                }
   } //if (ConsoleIn->Signature
  } //for (HandleIndex = 0;

  gBS->FreePool(DevicePathHandleBuffer);  
  return Status;
}

完整代码和X64 EFI 下载(因为含有 IO 代码,所以必须在实体机上运行)

工作的视频:

参考:

  1. https://www.lab-z.com/stu120uk/  UEFI 下控制USB键盘 LED
  2. http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm The PS/2 Keyboard Interface
  3. https://wiki.osdev.org/PS/2_Keyboard PS/2 Keyboard

Arduino 上通过串口控制CH376

CH37X 系列是南京沁恒出品的一系列USB控制类的芯片,主要用途是用作 USB Host 实现 USB 设备的控制。具体列表如下【参考1】:

上面不同型号主要差别在于提供的接口,目前支持三种对单片机的接口,分别是并口 SPI和串口。此外就是是否硬件集成文件系统。如果集成了,那么可以通过简单的命令来实现文件系统级别的操作,否则需要主控芯片自己来实现,这对于主控的内存要求比较高,整体程序也会复杂得多。

从上面可以看到,CH378 是当前最强的,但是因为封装更加负责以及比较新的原因(缺少资料),所以市面上最常见的还是 CH376,淘宝上的价格在20以内,应该也是比较容易接受的。这次试验就是基于 CH376 模块,特别注意是下面这种带有一个 2X3跳线的:

这款电路图如下:

这个模块默认情况下使用串口通讯,跳线顺序如下:

P_S: GND TXD(CH376) TXD

    S:  RXD  GND  TXD

这个跳线决定初始时串口通讯速度,意思是如果能够通讯,那么可以通过修改寄存器的方式修改波特率从而实现更高的速度。

  跳线指南【参考2】

这次试验就是如何进行串口通讯 ,P_S 位置跳线是 TXD(CH376)和 TXD 短路。试验使用的是Arduino Leonardo,因为他带有一个额外的硬件串口(软件串口通常在 115200波特率下接收会有问题)。接线如下:

Arduino LeonardoCH376模块
5VVCC
GNDGND
Pin0 RXTXD
Pin1 TXRXD

测试代码是库中自带的 basicUsageHwSerial测试程序:

/*------------------------------------------------------------------------------------------------------------------
 *    Author: György Kovács                                                                                         |
 *    Created: 28 Mar 2019                                                                                          |
 *    Description: Basic usage of CH376 with hardware serial                                                        |
 *    Thanks for the idea to Scott C , https://arduinobasics.blogspot.com/2015/05/ch376s-usb-readwrite-module.html  |
 *------------------------------------------------------------------------------------------------------------------
 */


#include <Ch376msc.h>

//..............................................................................................................................
// Leave the default jumper settings for the baud rate (9600) on the CH376, the library will set it up the chosen speed(HW serial only)
Ch376msc flashDrive(Serial1, 115200); // Ch376 object with hardware Serial1 on arduino mega baudrate: 9600, 19200, 57600, 115200
//..............................................................................................................................
 // buffer for reading
char adatBuffer[255];// max length 255 = 254 char + 1 NULL character
//..............................................................................................................................
// strings for writing to file
char adat[]="Vivamus nec nisl molestie, blandit diam vel, varius mi. Fusce luctus cursus sapien in vulputate.\n";
char adat2[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis efficitur ac est eu pharetra. \n";
//..............................................................................................................................
unsigned long totSect = 0;
unsigned long freeSect = 0;
byte percentg = 0;
byte tmpCommand; //used to store data coming from serial port
boolean readMore;
static char helpString[]= {"h:Print this help\n\n1:Create\n2:Append\n3:Read\n4:Read date/time\n"
            "5:Modify date/time\n6:Delete\n7:List dir\n8:Print free space"
            "\n9:Open/Create folder(s)/subfolder(s)"};

void setup() {
  Serial.begin(115200);
  flashDrive.init();
  printInfo(helpString);
}

void loop() {
	if(flashDrive.checkIntMessage()){
		if(flashDrive.getDeviceStatus()){
			Serial.println(F("Flash drive attached!"));
		} else {
			Serial.println(F("Flash drive detached!"));
		}
	}
  if(Serial.available()){
    tmpCommand = Serial.read();                      //read incoming bytes from the serial monitor
    if(((tmpCommand > 48)&&(tmpCommand < 58)) && !flashDrive.driveReady()){ // if the data is ASCII 1 - 9 and no flash drive are attached
       printInfo("Attach flash drive first!");
      tmpCommand = 10; // change the command byte
    }
     switch (tmpCommand) {

      case 49: //1
        printInfo("COMMAND1: Create and write data to file : TEST1.TXT");    // Create a file called TEST1.TXT
          flashDrive.setFileName("TEST1.TXT");  //set the file name
          flashDrive.openFile();                //open the file

          for(int a = 0; a < 20; a++){          //write text from string(adat) to flash drive 20 times
            flashDrive.writeFile(adat, strlen(adat)); //string, string length
          }
          flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 50: //2
        printInfo("COMMAND2: Append data to file: TEST1.TXT");               // Append data to the end of the file.
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        if(flashDrive.openFile() == ANSW_USB_INT_SUCCESS){               //open the file
        	flashDrive.moveCursor(CURSOREND);     //if the file exist, move the "virtual" cursor at end of the file, with CURSORBEGIN we actually rewrite our old file
        	//flashDrive.moveCursor(flashDrive.getFileSize()); // is almost the same as CURSOREND, because we put our cursor at end of the file
        }
        for(int a = 0; a < 20; a++){          //write text from string(adat) to flash drive 20 times
        	if(flashDrive.getFreeSectors()){ //check the free space on the drive
        		flashDrive.writeFile(adat2, strlen(adat2)); //string, string length
        	} else {
        		printInfo("Disk full");
        	}
        }
        flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 51: //3
        printInfo("COMMAND3: Read File: TEST1.TXT");                         // Read the contents of this file on the USB disk, and display contents in the Serial Monitor
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.openFile();                //open the file
        readMore = true;
                //read data from flash drive until we reach EOF
        while(readMore){ // our temporary buffer where we read data from flash drive and the size of that buffer
        	readMore = flashDrive.readFile(adatBuffer, sizeof(adatBuffer));
        	Serial.print(adatBuffer);          //print the contents of the temporary buffer
        }
        flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 52: //4
        printInfo("COMMAND4: Read File date/time: TEST1.TXT");      // Read the date and time of file, default 2004.01.01 - 00:00:00
        flashDrive.setFileName("TEST1.TXT");            //set the file name
        flashDrive.openFile();                          //open the file
                //print informations about the file
          Serial.println(flashDrive.getFileName());
          Serial.print(flashDrive.getYear());
          Serial.print("y\t");
          Serial.print(flashDrive.getMonth());
          Serial.print("m\t");
          Serial.print(flashDrive.getDay());
          Serial.print("d\t");
          Serial.print(flashDrive.getHour());
          Serial.print("h\t");
          Serial.print(flashDrive.getMinute());
          Serial.print("m\t");
          Serial.print(flashDrive.getSecond());
          Serial.println('s');
        flashDrive.closeFile();                         //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 53: //5
        printInfo("COMMAND5: Modify File date/time: TEST1.TXT");    // Modify the file date/time and save
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.openFile();                //open the file

          flashDrive.setYear(2019);
          flashDrive.setMonth(12);
          flashDrive.setDay(19);
          flashDrive.setHour(03);
          flashDrive.setMinute(38);
          flashDrive.setSecond(42);

          flashDrive.saveFileAttrb();           //save the changed data
        flashDrive.closeFile();               //and yes again, close the file after when you don`t use it
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 54: //6
        printInfo("COMMAND6: Delete File: TEST1.TXT");                       // Delete the file named TEST1.TXT
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.deleteFile();              //delete file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 55: //7
        printInfo("COMMAND7: List directory");                          //Print all file names in the current directory
          while(flashDrive.listDir()){ // reading next file
            if(flashDrive.getFileAttrb() == ATTR_DIRECTORY){//directory
              Serial.print('/');
              Serial.println(flashDrive.getFileName()); // get the actual file name
            } else {
              Serial.print(flashDrive.getFileName()); // get the actual file name
              Serial.print(" : ");
              Serial.print(flashDrive.getFileSize()); // get the actual file size in bytes
              Serial.print(" >>>\t");
              Serial.println(flashDrive.getFileSizeStr()); // get the actual file size in formatted string
            }
          }
          printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 56: //8
    	  totSect = flashDrive.getTotalSectors(); // get the total sector number
    	  freeSect = flashDrive.getFreeSectors(); // get the available sector number
    	  percentg = map(freeSect,totSect,0,0,100); 		// convert it to percentage (0-100)
    	  Serial.print("Disk size in bytes: ");
    	  /*if the sector number is more than 8388607 (8388607 * 512 = 4294966784 byte = 4Gb (fits in a 32bit variable) )
    	    							 e.g. 8388608 * 512 = 4294967296 byte (32bit variable overflows) */
    	  if(totSect > 8388607){
    		  Serial.print(">4Gb");
    	  } else {
        	  Serial.print(totSect * SECTORSIZE);
    	  }
    	  Serial.print("\tFree space in bytes: ");
    	  if(freeSect > 8388607){
    		  Serial.print(">4Gb");
    	  } else {
        	  Serial.print(freeSect * SECTORSIZE);
    	  }
    	  Serial.print(F("\tDisk usage :"));
    	  Serial.print(percentg);
    	  Serial.print(F("%"));
    	  switch (flashDrive.getFileSystem()) { //1-FAT12, 2-FAT16, 3-FAT32
			case 1:
				Serial.println(F("\tFAT12 partition"));
				break;
			case 2:
				Serial.println(F("\tFAT16 partition"));
				break;
			case 3:
				Serial.println(F("\tFAT32 partition"));
				break;
			default:
				Serial.println(F("\tNo valid partition"));
				break;
		}
    	 break;
//*****************************************************************************************************************************************************
      case 57: //9
        switch(flashDrive.cd("/DIR1/DIR2/DIR3",1)){
          case ERR_LONGFILENAME: //0x01
            Serial.println(F("Directory name is too long"));
          break;

          case ANSW_USB_INT_SUCCESS: //0x14
          Serial.println(F("Directory created successfully"));
          break;

          case ANSW_ERR_OPEN_DIR: //0x41
          Serial.println(F("Directory opened successfully"));
          break;

          case ANSW_ERR_MISS_FILE: //0x42
          Serial.println(F("Directory doesn't exist"));
          break;

          case ANSW_ERR_FOUND_NAME: //0x43
          Serial.println(F("File exist with the given name"));
          break;

          default:

          break;
        }
      break;
//*****************************************************************************************************************************************************
      case 104: //h
    	  printInfo(helpString);
        break;
      default:
        break;
    }//end switch

  }//endif serial available

}//end loop

//Print information
void printInfo(char info[]){
  char * infoPtr = info;
  int infoLength = 0;
    while(*infoPtr){
      infoPtr++;
      infoLength++;
      if(infoLength > 40) break;
    }
    Serial.print(F("\n\n"));
    for(int a = 0; a < infoLength; a++){
      Serial.print('*');
    }
   Serial.println();
   Serial.println(info);
   for(int a = 0; a < infoLength; a++){
      Serial.print('*');
    }
   Serial.print(F("\n\n"));
}

运行结果:插拔U盘有提示,输入 h 显示菜单,可以进行U盘读写等等文件操作

最后吐槽一下:CH37X  系列推出十多年了,很多年前我也入手过,但是不得不说这货太难用了,官网提供的资料看起来很全,但上手之后会发现缺少核心部分。现在能用是因为南京沁恒后来公布了一些具体 COMMAND (之前应该只有大客户才能拿到),这样玩家才有机会应用在 Arduino 上。相比之下,国外的芯片资料一直都很全。这也是为什么很多产品在第一版设计时不会考虑国产方案的原因。希望未来国产芯片在这方面能有所改观。

参考:

  1. http://www.wch.cn/products/category/1.html
  2. https://github.com/djuseeq/Ch376msc

查看 PMC Firmware版本的工具

很多时候,我们需要得知当前系统中的 PMC Firmware版本。最简单的方法是进入 Setup 查看。但是不幸的是很多时候BIOS会主动隐藏这个选项。因此,编写一个 Shell 下的工具。用户可以直接获得当前系统 PMC版本号:

显示PMC版本的 Shell 工具

当然,除此之外还可以使用 MEInfo.efi (在 CSME Release Package 中)看到 PMC 的版本信息:

MEINFO 显示PMC版本信息

Arduino USB Host Shield PL2302 Debug Message

问题:当我运行USB Host Shield Library 中关于 Pl2303 的例子(比如 pl2303_gprs_terminal)的时候,在串口会有下面的 Debug 信息:

Start
0000: 09 02 27 00 01 01 00 80 32 09 04 00 00 03 FF 00 
0010: 00 00 07 05 81 03 0A 00 01 07 05 02 02 40 00 00 
0020: 07 05 83 02 40 00 00

经过研究,产生的位置在  Usb.cpp 下面函数中

uint8_t USB::ctrlReq(uint8_t addr, uint8_t ep, uint8_t bmReqType, uint8_t bRequest, uint8_t wValLo, uint8_t wValHi, uint16_t wInd, uint16_t total, uint16_t nbytes, uint8_t* dataptr, USBReadParser *p) {

下面这个代码处:

// Invoke callback function if inTransfer completed successfully and callback function pointer is specified
   if(!rcode && p)
     ((USBReadParser*)p)->Parse(read, dataptr, total - left);

但是很明显,这里是调用设定的 callback 函数。

经过查找,最终确定实际生效的代码是hexdump.h 文件中下面的代码:

template <class BASE_CLASS, class LEN_TYPE, class OFFSET_TYPE>
void HexDumper<BASE_CLASS, LEN_TYPE, OFFSET_TYPE>::Parse(const LEN_TYPE len, const uint8_t *pbuf, const OFFSET_TYPE &offset __attribute__((unused))) {
        if(UsbDEBUGlvl >= 0x80) { // Fully bypass this block of code if we do not debug.
                for(LEN_TYPE j = 0; j < len; j++, byteCount++, byteTotal++) {
                        if(!byteCount) {
                                PrintHex<OFFSET_TYPE > (byteTotal, 0x80);
                                E_Notify(PSTR(": "), 0x80);
                        }
                        PrintHex<uint8_t > (pbuf[j], 0x80);
                        E_Notify(PSTR(" "), 0x80);

                        if(byteCount == 15) {
                                E_Notify(PSTR("\r\n"), 0x80);
                                byteCount = 0xFF;
                        }
                }
        }
}

因为其他地方对 UsbDEBUGlvl 赋值为 0x80,所以会输出一下信息用于Debug。

最近阅读 USB Host Shield 感觉风格不是很统一,比如,用于 Debug 的定义开关有很多处,估计是因为后来的代码是很多人合作的结果。

参考:

1.http://www.lab-z.com/atu9/

Windows10 Lite 版本

最近下载了一个 Win 10 lite 版本,专门用在虚拟机中配合 PHM 查看 Log (我不建议在工作机上安装 PHM ,因为在安装过程中会安装后一些服务会拖慢系统,导致系统降低,因此建议大家用额外的机器安装PHM 查看Log)。镜像来自下面的链接:

https://www.majorgeeks.com/files/details/windows_10_lite.html

这个版本具体的参数如下:

Technical Setup Details

Full NameWindows 10 Lite
Full Setup Size2.4 GB (For 32 Bit), 2.6 GB (For 64 Bit)
CompatibilityCompatible with 32 Bit (x86) / 64 Bit (x64)
Setup TypeOffline Installer / Full Standalone Setup
DevelopersMicrosoft

Minimum System Requirements

Memory (RAM)Minimum 1 GB
HDD (Hard Disk Drive)Minimum 16 GB Free Space Required
ProcessorIntel Pentium 4 Or Advance

在虚拟机中安装之后,还需要安装微软的 Edge 浏览器,之后就可以正常使用PHM。

虚拟机中的 Win10 Lite

链接: https://pan.baidu.com/s/1vlYXOvFDoDgfEBvbQKJ0-w 提取码: h6hn

参考:

1.https://www.lab-z.com/phm/ PowerHouse Mountain 的安装

ESP32 上使用 USB Host Mini

之前介绍过祖国版的 USB Host Mini 【参考1】,这里介绍一些如何在 ESP32 上使用USB Host 功能。试验的板子是 DFRobot 的 FireBeelte:

来自【参考2】

USB  Host Mini 上有24个Pin, 但是实际用到的只有9个,按照下图将它和 FireBeelte 接起来即可:

USB Host Library 直接兼容 ES32 ,. 建议先运行 例子中的Board_QC 来验证板子是否工作正常。

下面是取得 USB 描述符的例子运行结果:

=========================================================

20201027 补充在 ESP32-WROOM-32 上的实验

连接方式:

库中代码修改:

1.USB_Host_Shield_Library_2.0\avrpins.h

// Pinout for ESP32 dev module

MAKE_PIN(P0, 0);
MAKE_PIN(P1, 1); // TX0
MAKE_PIN(P10, 10); // TX1
MAKE_PIN(P3, 3); // RX0
MAKE_PIN(P21, 21); // SDA
MAKE_PIN(P22, 22); // SCL
MAKE_PIN(P19, 19); // MISO
MAKE_PIN(P23, 23); // MOSI
MAKE_PIN(P18, 18); // SCK
//LabZ_Start
MAKE_PIN(P13, 13); // SS
MAKE_PIN(P26, 26); // INT
//LabZ_End

2.USB_Host_Shield_Library_2.0\UsbCore.h

#elif defined(ESP8266)
typedef MAX3421e&lt;P15, P5> MAX3421E; // ESP8266 boards
#elif defined(ESP32)
//LABZ_Debug typedef MAX3421e&lt;P5, P17> MAX3421E; // ESP32 boards
typedef MAX3421e&lt;P13, P26> MAX3421E; // ESP32 boards  //LABZ_Debug
#else
typedef MAX3421e&lt;P10, P9> MAX3421E; // Official Arduinos (UNO, Duemilanove, Mega, 2560, Leonardo, Due etc.), Intel Edison, Intel Galileo 2 or Teensy 2.0 and 3.x
#endif

3.USB_Host_Shield_Library_2.0\usbhost.h

#elif defined(ESP8266)
typedef SPi&lt; P14, P13, P12, P15 > spi;
#elif defined(ESP32)
//LABZ_Debug typedef SPi&lt; P18, P23, P19, P5 > spi;
typedef SPi&lt; P18, P23, P19, P13 > spi; //LABZ_Debug 
#else
#error "No SPI entry in usbhost.h"
#endif

参考:

  1. https://www.lab-z.com/cuhm/
  2. http://wiki.dfrobot.com.cn/index.php?title=%E6%96%87%E4%BB%B6:DFR0478%E5%BC%95%E8%84%9A%E5%9B%BE01.png

WPA 的安装

WPA 是 Windows Performance Analyzer的缩写,主要用途是用来分析代码性能【参考1】。在Debug Modern Standby S0 相关问题的时候非常有用,比如:​它可以告诉你谁在占用 CPU 使其处于S0状态,或者哪个中断将CPU 唤醒到S0。本文介绍一下这个工具的安装:

  1. 这个工具在 SDK 中,另外在ADK 中也有。

 2.选择路径

3.选择不要发送

4.选中 “Windows Performance Toolkit” 这就是我们需要的 WPA。从提示可以看出,其中包括 Windows Performance Recorder (记录器),Windows Performance Analyzer (分析器)和 Xperf (分析单个程序性能的工具)

5.下面只需要等待安装完成即可

6.输入 WPA 即可启用

如果你系统中有 WPA,但是无法打开 etl 那么请检查你安装的版本是否和 etl 的相匹配。

参考:

1. https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-analyzer

Arduino ESP32 安装指南

理论上 Arduino 支持 ESP32 的主控板是非常简单的事情,在IDE 中操作就可以了。但是因为众所周知的原因经常会出现错误,这里就介绍一下另外的方法。

  1. 在首选项中“附加开发板管理器网址”中加入下面这个网址:

https://dl.espressif.com/dl/package_esp32_index.json

2.这里设置好之后可以直接到开发板管理器中搜索 ESP32,如果能够正常搜索到ESP32,然后顺利完成下载,那么久无需阅读下面部分。

上面Step2 失败,出现错误,需要手工操作如下:

A. 将 package_esp32_index.json 放在C:\Users\[用户名]\AppData\Local\Arduino15 下面。比如,我当前登录账号是 Administrator ,name 对应路径是

C:\Users\Administrator\AppData\Local\Arduino15;

B. 找到 C:\Users\[用户名]\AppData\Local\Arduino15\preferences.txt 文件中的下面两行修改为:

target_package=esp32
target_platform=esp32

C. 再次来到开发板管理器中,同样会出错,但是这次可以搜索到 esp32 的开发板了

D. 在安装之前,将开发板Package和编译工具放置在下面的路径下:

C:\Users\[用户名Data\Local\Arduino15\staging\packages

E.选择你需要的版本,即可安装

F. 从原理上将,Step A告诉Arduino 我要加入新板子,新板子的描述在 一个JSON文件中。Arduino 会根据描述在开发板管理器中加入这个板子的型号,同时尝试去JSON文件给出的网址下载开发包等等工具,同时因为 JSON 文件给出了这些文件的 Checksum,当 Arduino 发现目录下存在对应的文件后,不会再次下载而是会去校验,校验结果Pass即可继续安装,从而顺利完成 Arduino ESP32的安装。

所以上述动作能够“骗过” Arduino 在没有网络下载的情况下正常安装。

本文提到的安装包有如下文件

esp32-1.0.0.zip
esp32-1.0.1.zip
esp32-1.0.2.zip
esp32-1.0.3.zip
esp32-1.0.4.zip
esptool-2.3.1-windows.zip
esptool-2.6.0-windows.zip
esptool-2.6.1-windows.zip
mkspiffs-0.2.3-arduino-esp32-win32.zip
xtensa-esp32-elf-win32-1.22.0-80-g6c4433a-5.2.0.zip

可以在这里下载链接:https://pan.baidu.com/s/19GoaTbeSAGZ4ourMcWiCOg

提取码:sxsr