VC 重复宏定义 Warning

如果我们在代码中重复定义一个宏(macro redefine),例如:

#define SUM(a,b) a+b
#define SUM(a,b) a*b

会遇到下面的错误提示:

MRedef.c(20): error C2220: warning treated as error - no 'object' file generated
MRedef.c(20): warning C4005: 'SUM': macro redefinition
MRedef.c(19): note: see previous definition of 'SUM'

解决方法是在文件头部加入 Disable 这个 Warning 的指令如下:

#pragma warning (disable : 4005)

完整的代码如下:

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

#pragma warning (disable : 4005)

/*
MRedef.c(20): error C2220: warning treated as error - no 'object' file generated
MRedef.c(20): warning C4005: 'SUM': macro redefinition
MRedef.c(19): note: see previous definition of 'SUM'
*/

#define SUM(a,b) a+b
#define SUM(a,b) a*b


/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        int c =3,d=4;

  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");

  Print(L"Macro test %d\n",SUM(c,d));
  
  return(0);
}

参考:

1.https://stackoverflow.com/questions/25201724/fix-macro-redefinition-in-c

Step to UEFI (234)不定参数函数的测试

一些情况下,调用函数的参数并不确定,显而易见的一个例子是 printf,在调用的时候后面可能接多个参数,对于这种情况我们可以使用 VA_LIST 来解决。在 Base.h 中有定义:

//
//  Support for variable argument lists in freestanding edk2 modules.
//
//  For modules that use the ISO C library interfaces for variable
//  argument lists, refer to "StdLib/Include/stdarg.h".
//
//  VA_LIST  - typedef for argument list.
//  VA_START (VA_LIST Marker, argument before the ...) - Init Marker for use.
//  VA_END (VA_LIST Marker) - Clear Marker
//  VA_ARG (VA_LIST Marker, var arg type) - Use Marker to get an argument from
//    the ... list. You must know the type and pass it in this macro.  Type
//    must be compatible with the type of the actual next argument (as promoted
//    according to the default argument promotions.)
//  VA_COPY (VA_LIST Dest, VA_LIST Start) - Initialize Dest as a copy of Start.
//
//  Example:
//
//  UINTN
//  EFIAPI
//  ExampleVarArg (
//    IN UINTN  NumberOfArgs,
//    ...
//    )
//  {
//    VA_LIST Marker;
//    UINTN   Index;
//    UINTN   Result;
//
//    //
//    // Initialize the Marker
//    //
//    VA_START (Marker, NumberOfArgs);
//    for (Index = 0, Result = 0; Index < NumberOfArgs; Index++) {
//      //
//      // The ... list is a series of UINTN values, so sum them up.
//      //
//      Result += VA_ARG (Marker, UINTN);
//    }
//
//    VA_END (Marker);
//    return Result;
//  }
//
//  Notes:
//  - Functions that call VA_START() / VA_END() must have a variable
//    argument list and must be declared EFIAPI.
//  - Functions that call VA_COPY() / VA_END() must be declared EFIAPI.
//  - Functions that only use VA_LIST and VA_ARG() need not be EFIAPI.
//

根据上面的代码,编写测试例子如下:

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

UINTN
EFIAPI
VarSum (
  IN UINTN  NumberOfArgs,
  ...
  )
{
  VA_LIST Marker;
  UINTN   Index;
  UINTN   Result;

  //
  // Initialize the Marker
  //
  VA_START (Marker, NumberOfArgs);
  for (Index = 0, Result = 0; Index < NumberOfArgs; Index++) {
    //
    // The ... list is a series of UINTN values, so sum them up.
    //
    Result += VA_ARG (Marker, UINTN);
  }

  VA_END (Marker);
  return Result;
}

UINTN
EFIAPI
VarString (
  IN UINTN  NumberOfArgs,
  ...
  )
{
  VA_LIST Marker;
  UINTN   Index;
  UINTN   Result;

  //
  // Initialize the Marker
  //
  VA_START (Marker, NumberOfArgs);
  for (Index = 0, Result = 0; Index < NumberOfArgs; Index++) {
    //
    // The ... list is a series of UINTN values, so sum them up.
    //
    //Result += VA_ARG (Marker, UINTN);
    Print(L"String %d: %s\n",Index,VA_ARG (Marker, CHAR16*));
  }

  VA_END (Marker);
  return Result;
}

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{

  Print(L"VarSum(2,1,2)=%d\n",VarSum(2,1,2));
  Print(L"VarSum(3,1,2,3)=%d\n",VarSum(3,1,2,3));
  Print(L"VarSum(4,1,2,3,4)=%d\n",VarSum(4,1,2,3,4));
  
  Print(L"VarString(4,L\"abcde\",L\"1234\",L\"efgh\",L\"5678\")\n",
                VarString(4,L"abcde",L"1234",L"efgh",L"5678"));
  return(0);
}

运行结果:

不定参数函数运行结果

可以看到VarSum()能够实现将可变个数值相加,VarString()能够实现在屏幕输出可变个字符串变量的功能。

C# 取得设备 Bus reported device description 的方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Management;
using System.Collections;

namespace ConsoleApplication29
{
    class Program
    {
        private static void GatherUsbInformation()
        {
            var mos = new ManagementObjectSearcher("select DeviceID from Win32_PnPEntity");
            var mbos = new ArrayList(mos.Get());
            var data = new Dictionary<string, string[]>();

            for (var i = 0; i < mbos.Count; i++)
            {
                var managementBaseObject = mbos[i] as ManagementBaseObject;

                if (managementBaseObject == null)
                {
                    continue;
                }

                var deviceId = managementBaseObject.Properties["DeviceID"].Value as string;

                if (deviceId == null || !deviceId.StartsWith("USB"))
                {
                    continue;
                }

                if (!data.ContainsKey(deviceId))
                {
                    data.Add(deviceId, new string[8]);
                }
                else if (data.ContainsKey(deviceId))
                {
                    continue;
                }

                var mo = managementBaseObject as ManagementObject;
                var inParams = mo.GetMethodParameters("GetDeviceProperties");

                var result = mo.InvokeMethod(
                    "GetDeviceProperties",
                    inParams,
                    new InvokeMethodOptions()
                );

                if (result?.Properties["deviceProperties"].Value == null)
                {
                    continue;
                }

                foreach (var deviceProperties in result.Properties["deviceProperties"].Value as ManagementBaseObject[])
                {
                    var keyName = deviceProperties.Properties["KeyName"].Value as string;
                    var value = deviceProperties.Properties["Data"].Value as string;

                    if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(keyName))
                    {
                        //MachineInformationGatherer.Logger.LogTrace(
                        //    $"KeyName {keyName} or Value {value} was null or whitespace for device ID {deviceId}");
                        continue;
                    }
                    Console.WriteLine(keyName);
                    Console.WriteLine(value);
                    switch (keyName)
                    {
                        case "DEVPKEY_Device_BusReportedDeviceDesc":
                            {
                                data[deviceId][0] = value;
                                break;
                            }
                        case "DEVPKEY_Device_DriverDesc":
                            {
                                data[deviceId][1] = value;
                                break;
                            }
                        case "DEVPKEY_Device_DriverVersion":
                            {
                                data[deviceId][2] = value;
                                break;
                            }
                        case "DEVPKEY_Device_DriverDate":
                            {
                                var year = int.Parse(value.Substring(0, 4));
                                var month = int.Parse(value.Substring(4, 2));
                                var day = int.Parse(value.Substring(6, 2));
                                var hour = int.Parse(value.Substring(8, 2));
                                var minute = int.Parse(value.Substring(10, 2));
                                var second = int.Parse(value.Substring(12, 2));

                                data[deviceId][3] =
                                    new DateTime(year, month, day, hour, minute, second).ToString();
                                break;
                            }
                        case "DEVPKEY_Device_Class":
                            {
                                data[deviceId][4] = value;
                                break;
                            }
                        case "DEVPKEY_Device_DriverProvider":
                            {
                                data[deviceId][5] = value;
                                break;
                            }
                        case "DEVPKEY_NAME":
                            {
                                data[deviceId][6] = value;
                                break;
                            }
                        case "DEVPKEY_Device_Manufacturer":
                            {
                                data[deviceId][7] = value;
                                break;
                            }
                        case "DEVPKEY_Device_Children":
                            {
                                var children = deviceProperties.Properties["DEVPKEY_Device_Children"];
                                if (children.Value != null)
                                {
                                    if (children.IsArray)
                                    {
                                        foreach (var child in children.Value as string[])
                                        {
                                            mos.Query = new ObjectQuery(
                                                $"select * from Win32_PnPEntity where DeviceID = {child}");
                                            var childs = mos.Get();

                                            foreach (var child1 in childs)
                                            {
                                                mbos.Add(child1);
                                            }
                                        }
                                    }
                                }

                                break;
                            }
                    }
                }
            }
        }

        static void Main(string[] args)
        {
            GatherUsbInformation();
            Console.ReadKey();
        }
    }
}

运行结果:

运行结果

代码来自 https://github.com/L3tum/HardwareInformation/blob/master/HardwareInformation/Providers/WindowsInformationProvider.cs

Ch340B修改字符串等设备信息的实验

CH340B 是 CH34X USB 转串口芯片的一员,这个家族芯片之间的差别主要在于:

1.是否需要外部晶振(不需要的可以节省PCB空间);

2.封装尺寸差别;

CH340 封装

3.支持速度有差别,比如, CH340R 最高只支持 115200

CH340B 最大的特点在于内置了 EEPROM 可以修改默认的 USB 设备参数。

运行界面如下(我的操作系统是英文,所以一些位置出现乱码)。

CH340B 设定工具

可以看到,能够修改的有3个参数:

  1. PID/VID
  2. Product String
  3. Serial Numbers

接下来逐个介绍上面的参数。首先是 PID/VID。这个参数是主机用来识别 USB的最重要参数。比如,我将VID 修改为 0x8888,那么之前安装好的 CH340 驱动将无法使用(因为驱动的 INF 中找不到 VID=0x8888 PID=0x7523对应的项目):

修改 CH340 的 PID 和 VID

接下来修改 CH340 的驱动文件,手工添加新的项目:

这样修改之后, 驱动中签名会出现问题,如果想安装必须先 Disable Secure Boot 功能,安装时会出现下面的提示信息:

提示当前驱动签名有问题

安装之后再打开 Secure Boot,设备仍然能正常工作。但是如果始终打开 Secure Boot,那就一直无法安装。

之后再 Enable SecureBoot 设备驱动还是能够正常工作的

接下来介绍一下 Product String,这个修改之后,没有安装之前这个字符串会显示在设备上:

未安装驱动

安装之后会显示为驱动定义的名称:

安装驱动之后

同样,这个信息会显示在设备的“Bus reported device description”中:

“Bus reported device description”

最后,说一下Serial Numbers,修改这个项目之后,例如,修改这个项目为 20210705 之后:

修改 Serial Numbers 信息

在 Device instance path 中PID/VID 字符串的后面可以看到:

可以看到我们新加的 Serial Number出现在“Device instance path”属性中

本文提到的 CH340B 修改工具可以在这里下载:

UEFI Shell Helper

在编写调试 UEFI Shell 下的 Application 的时候,经常需要在实体机上进行测试。但是每次需要使用U盘来回拷贝,感觉非常麻烦。为了解决这个问题,最近几个月,制作了一个调试助手: UEFI Shell Helper 缩写 USH。

UEFI Shell Helper 缩写 USH

上面有2个 USB 接口,一个是 CH340 提供的 USB 串口(USB Type-A 公头),另外一个是 ESP32-S2 模拟出来的 U盘(USB Type-B 母头)。这个模拟 U盘中还内置了一个 UEFI Shell ,可以直接启动到 Shell 下。使用时,将母头端接入被测机,然后公头端接入Windows 主机端。之后可以通过主机对设备发送数据,发送完成后运行 Shell 下的应用程序即可取得数据;反之被测试端也可以发送数据给主机端。这样在调试的时候就可以免除插拔拷贝之苦。

工作的视频:

https://www.bilibili.com/video/BV1T64y1e7ay/

这是一个开源设备,具体设计方案可以在下面的链接看到(涉及电路图,PCB 设计, C# 编程和 Arduino 代码开发,前后搞了3个月,感觉挺复杂):

https://diy.szlcsc.com/p/Zoologist/uefi-shell#

有兴趣的朋友可以看看,不过目前还有1个问题尚未解决:

1.串口传输最高只能达到 230400 baud rate (115200*2),选用的 CH340B 应该可以达到 2000000;目前正在找 WCH Debug。

后面会介绍 USH 牵涉到的 UEFI 内容.

ESP32 I2C 设备扫描代码

有时候在调试 I2C 设备的时候,需要确定设备的地址,可以使用下面的代码来完成:

// ESP32 I2C Scanner
// Based on code of Nick Gammon  http://www.gammon.com.au/forum/?id=10896
// ESP32 DevKit - Arduino IDE 1.8.5
// Device tested PCF8574 - Use pullup resistors 3K3 ohms !
// PCF8574 Default Freq 100 KHz 

#include <Wire.h>

void setup()
{
  Serial.begin (115200);  
  Wire.begin (21, 22);   // sda= GPIO_21 /scl= GPIO_22
}

void Scanner ()
{
  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;

  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);          // Begin I2C transmission Address (i)
    if (Wire.endTransmission () == 0)  // Receive 0 = success (ACK response) 
    {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);     // PCF8574 7 bit address
      Serial.println (")");
      count++;
    }
  }
  Serial.print ("Found ");      
  Serial.print (count, DEC);        // numbers of devices
  Serial.println (" device(s).");
}

void loop()
{
  Scanner ();
  delay (100);
}

代码来自 https://www.esp32.com/viewtopic.php?p=55303

ESP32 和 C# 的 CRC16

CRC-16 有很多种,这里使用的是CRC-16/CCITT-FALSE。可以用这个在线工具进行验证(注意下图是16进制数值):

https://crccalc.com/

1.

static unsigned short crc16(const unsigned char *buf, unsigned long count)
{
  unsigned short crc = 0xFFFF;
  int i;

  while(count--) {
    crc = crc ^ *buf++ << 8;

    for (i=0; i<8; i++) {
      if (crc & 0x8000) crc = crc << 1 ^ 0x1021;
      else crc = crc << 1;
    }
  }
  return crc;
}

void setup() {
        Serial.begin(115200);
}

void loop() {
        byte data[11]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00,0x11};
        Serial.println(crc16(data,11),HEX);
        delay(10000);
}

2.C# 代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace CRC16Test
{
    class Program
    {


        static void Main(string[] args)
        {
            byte[] data = new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,0x11};
            /*           for (int i = 0; i < 10; i++)
                       {
                           data[i] = (byte)(i % 256);
                       }
           */
            string hex = Crc16Ccitt(data).ToString("x2");
            Console.WriteLine(hex);

            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);
        }

        //
        // CRC-16/CCITT-FALSE
        // https://crccalc.com/
        //
        public static ushort Crc16Ccitt(byte[] bytes)
        {
            const ushort poly = 0x1021;
            ushort[] table = new ushort[256];
            ushort initialValue = 0xffff;
            ushort temp, a;
            ushort crc = initialValue;
            for (int i = 0; i < table.Length; ++i)
            {
                temp = 0;
                a = (ushort)(i << 8);
                for (int j = 0; j < 8; ++j)
                {
                    if (((temp ^ a) & 0x8000) != 0)
                        temp = (ushort)((temp << 1) ^ poly);
                    else
                        temp <<= 1;
                    a <<= 1;
                }
                table[i] = temp;
            }
            for (int i = 0; i < bytes.Length; ++i)
            {
                crc = (ushort)((crc << 8) ^ table[((crc >> 8) ^ (0xff & bytes[i]))]);
            }
            return crc;
        }

    }
}

中文版和英文版的超级终端

从 Windows XP 的中文版和英文版中拷贝而来的超级终端程序。最主要是它提供了 Xmodem,YModem和ZModem 文件传输协议,可以用来进行测试。

Windows XP 的超级终端

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

2023年1月13日更新

上述的超级终端在 Win10 下可以正常使用,但是运行之后不会出现图标。经过研究这个问题可以通过添加hticons.dll来解决。有需要的朋友可以在下面下载这个文件,放到超级终端同一个目录下即可。

FireBeetle “软”USB Host Shield

喜欢军事的朋友一定经常听过这句话,这就是“只要推力大,板砖也能飞上天”。实际上,这句话并非玩笑之语,确有出处。当年,F-22是从“先进战术战斗机”(简称ATF)计划发展而来的。早期,好几家公司一起竞标,分别是波音,通用动力,洛克希德,诺思罗普,和麦道公司。大家提概念设计方案。结果,可能是因为洛克希德曾经设计出F-117隐形飞机的缘故,走老路当然是最轻松的,所以,洛克希德最初提出的F-22前身方案,它就像是F-117的放大版。设计跟F-117这个怪胎一样,为了隐身性能,以牺牲空气动力学为代价,飞机设计总重36吨。后来,有个人是这么评价这种早期设计的,他就是洛克希德在ATF计划中的项目经理巴特·奥斯本(bart osborne),1998年,一家杂志对奥斯本进行采访【参考1】,而他是这样说的:“我们知道,这个设计将会存在严重的超音速问题,我们的设计当然可以超音速飞行,但肯定蠢得像猪。显然,只要推力大,板砖也能飞上天(With enough power, you can make a brick fly)”

这句话出处就是这么来的,他当时想表达的本意是这样的:我们设计的这种飞机, 在高空高速上肯定会存在很多问题,当然,这并不是说,这种设计不能超音速飞行,完全不是,因为只要推力,你也能让砖头飞上天,也能它超音速。

巴特·奥斯本的评价很中肯,洛克希德提交这个概念设计方案后,美国空军对这种设计的评价最差,在其他7个项目设计中,排倒数第一。【参考1】

对于我们来说,只要 CPU 足够快,GPIO 足够快,能够模拟出各种总线,最常见的是软串口,再稍微复杂一些的是I2C, SPI。最近有一个项目使用 ESP32 来模拟 USB Host,当前因为 GPIO 不够快,目前只能解析键盘鼠标这样的低速设备(Low Speed)。项目地址是:https://github.com/sdima1357/esp32_usb_soft_host 当然,对于Arduino 玩家来说可以直接使用下面这个库 :https://github.com/tobozo/ESP32-USB-Soft-Host

库可以同时支持4个 USB Host, 这里我设计一个 FireBeetle Shield 提供了 2个 USB Host。具体电路如下:

PCB 如下:

Soft USB Host 和 FireBeetle

可以看到板子上有2个 USB 母头和1个USB 公头,其中母头是给 USB Host使用的,公头是为了在供电不足情况下通过充电宝之类直接对设备供电。库可以支持4组USB Host ,经过实验在FireBeetle上可以使用下面的组合:

16131925
17151826

测试代码:

#include <ESP32-USBSoftHost.hpp>
#include "usbkbd.h" // KeyboardReportParser

  // default pins tested on FireBeetle
  // +  16 13 19 25
  // -  17 15 18 26
  #define DP_P0  25  // always enabled
  #define DM_P0  26  // always enabled
  #define DP_P1  19 // -1 to disable
  #define DM_P1  18 // -1 to disable
  #define DP_P2  -1 // -1 to disable
  #define DM_P2  -1 // -1 to disable
  #define DP_P3  -1 // -1 to disable
  #define DM_P3  -1 // -1 to disable



static void my_USB_DetectCB( uint8_t usbNum, void * dev )
{
  sDevDesc *device = (sDevDesc*)dev;
  printf("New device detected on USB#%d\n", usbNum);
  printf("desc.bcdUSB             = 0x%04x\n", device->bcdUSB);
  printf("desc.bDeviceClass       = 0x%02x\n", device->bDeviceClass);
  printf("desc.bDeviceSubClass    = 0x%02x\n", device->bDeviceSubClass);
  printf("desc.bDeviceProtocol    = 0x%02x\n", device->bDeviceProtocol);
  printf("desc.bMaxPacketSize0    = 0x%02x\n", device->bMaxPacketSize0);
  printf("desc.idVendor           = 0x%04x\n", device->idVendor);
  printf("desc.idProduct          = 0x%04x\n", device->idProduct);
  printf("desc.bcdDevice          = 0x%04x\n", device->bcdDevice);
  printf("desc.iManufacturer      = 0x%02x\n", device->iManufacturer);
  printf("desc.iProduct           = 0x%02x\n", device->iProduct);
  printf("desc.iSerialNumber      = 0x%02x\n", device->iSerialNumber);
  printf("desc.bNumConfigurations = 0x%02x\n", device->bNumConfigurations);
  // if( device->iProduct == mySupportedIdProduct && device->iManufacturer == mySupportedManufacturer ) {
  //   myListenUSBPort = usbNum;
  // }
}


static void my_USB_PrintCB(uint8_t usbNum, uint8_t byte_depth, uint8_t* data, uint8_t data_len)
{
  // if( myListenUSBPort != usbNum ) return;
  printf("in: ");
  for(int k=0;k<data_len;k++) {
    printf("0x%02x ", data[k] );
  }
  printf("\n");
}

usb_pins_config_t USB_Pins_Config =
{
  DP_P0, DM_P0,
  DP_P1, DM_P1,
  DP_P2, DM_P2,
  DP_P3, DM_P3
};


void setup()
{

  Serial.begin(115200);
  delay(200);
  Serial.println("USB Test");
  delay(1000);

  USH.init( USB_Pins_Config, my_USB_DetectCB, my_USB_PrintCB );

}

void loop()
{
  vTaskDelete(NULL);
}


运行结果, 上面是输出的Descriptor,下面是收到的鼠标数据:

目前这个库还存在一些兼容性问题,并非所有的鼠标都能正常工作。我测试了微软的IO1.01 是可以正常工作的。

参考:

  1. http://www.codeonemagazine.com/f22_article.html?item_id=179 Design Evolution Of The F-22 Raptor
  2. http://www.nanshannews.cn/cj/cj9059.html

CH55x 串口速度测试

使用上次的工具来测试 Ch55x(Ch551/2/4)开发板的串口速度。

实际测试的是 CH554 ,使用 Ch55xduino 编写代码如下:

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include "src/userUsbCdc/USBCDC.h"

//This is a fairly large array, store it in external memory with keyword __xdata
__xdata char recvStr[64];

// 代码中出现的发送和接收
// 测试 Package 的大小
#define MAX_N 256

// 2秒超时
#define TIMEOUT  2000

// 接收数据 Buffer
// 设置为实际的2倍大小避免接收超过Buffer 的问题
__xdata byte receiveBuffer[MAX_N];

void setup() {
  USBInit();
}

// 状态机记录
byte currentStatus = 0;

// 收到的数量
int counter = 0;
// 超时计时器
unsigned long Elsp;
// 发送标记,True 时发送,反之不发送
boolean sendOnce = true;
// 发生错误的数量
int failCount;

void loop() {
  switch (currentStatus) {
    case 0:
      // 初始状态
      while (USBSerial_available()) {
        byte c = USBSerial_read();
        // q命令查询当前状态(用于调试)
        if (c == 'q') {
          USBSerial_print_s("Current 0");
          USBSerial_flush();
        }
        // 1(接收模式) 2(发送模式)
        if ((c == '1') || (c == '2')) {
          // 转移到下一个状态
          currentStatus = c - '0';
          // 如果是接收模式,那么开始计时
          if (c == '1') {
            Elsp = millis();
            counter = 0;
          }
        }
      }
      break;

    case 1: //接收模式
      // 如果接收模式超时,那么直接返回到状态0
      if (millis() - Elsp > TIMEOUT) {
        // 返回状态 0
        currentStatus = 0;
        // 发送 "aaa" 表示当前超时
        USBSerial_write('a'); USBSerial_write('a'); USBSerial_write('a');
        USBSerial_flush();
      }

      // 接收数据
      while (USBSerial_available()) {
        byte c = USBSerial_read();
        receiveBuffer[counter] = c;
        // 统计接收到的数据量
        counter++;

        // 如果接收到足够的数据,那么转移到状态3 进行校验
        if (counter == MAX_N) {
          currentStatus = 3;
          //Serial.print("St3");
        }
      }
      break;
    case 3:
      // 校验接收模式收到的数据,数据是 0x55 0xaa 0x55....
      failCount = 0;
      for (int i = 0; i < MAX_N; i = i + 2) {
        if (receiveBuffer[i] != 0x55) {
          failCount++;
        }
        if (receiveBuffer[i + 1] != 0xAA) {
          failCount++;
        }
      }
      // 检查是否有错误
      if (failCount == 0) {
        // 返回"ppp" 表示校验通过
        USBSerial_write('p'); USBSerial_write('p'); USBSerial_write('p');
        USBSerial_flush();
      }
      else {
        // 校验失败,返回 'f'+发生错误的数量错误数据
        USBSerial_write('f');
        USBSerial_write((byte)(failCount & 0xFF));
        USBSerial_write((byte)(failCount >> 8));

      }
      counter = 0;
      //Serial.println("Switch to status 1");
      Elsp = millis();
      currentStatus = 1;
      break;
    case 2:
      // 发送模式,直接发送 MAX_N 个 0x55 0xaa 0x55 ....
      if (sendOnce) {
        for (int i = 0; i < MAX_N / 2; i++) {
          USBSerial_write(0x55);
          USBSerial_write(0xAA);
          USBSerial_flush();
        }
        sendOnce = false;
      }
      while (USBSerial_available()) {
        char c = USBSerial_read();
        if (c == '0') {
          currentStatus = 0;
          //Serial.print("Switch to 0");
        }
        if (c == '2') {
          sendOnce = true;
        }
        // 查询状态
        if (c == 'q') {
          USBSerial_print_s("Current 2");
          USBSerial_flush();
        }
      }
      break;
    default: {
        USBSerial_println_s("Unknown status");
        USBSerial_flush();
      }
  }

1.PC端接收,Ch554发送,可以看到速度能达到37K/S左右

2.PC端发送,Ch554接收,可以看到速度能达到97K/S左右

从结果可以看出来,比 32U4 这样的芯片还是快一点点的(测试模式是 5V 24Mhz)