使用 CH567 实现 USB1 串口

这次的目标是实现一个 USB 转串口的设备,参考的是Arduino Leonardo 的 USB CDC。这个串口是标准USB串口,在Windows 下无需驱动。首先抓取描述符如下:

USB Composite Device

Connection StatusDevice connected
Current Configuration1
SpeedFull (12 Mbit/s)
Device Address4
Number Of Open Pipes3

Device Descriptor Arduino Leonardo

OffsetFieldSizeValueDescription
0bLength112h
1bDescriptorType101hDevice
2bcdUSB20200hUSB Spec 2.0
4bDeviceClass1EFhMiscellaneous
5bDeviceSubClass102hCommon Class
6bDeviceProtocol101hInterface Association Descriptor
7bMaxPacketSize0140h64 bytes
8idVendor22341h
10idProduct28036h
12bcdDevice20100h1.00
14iManufacturer101h“Arduino LLC”
15iProduct102h“Arduino Leonardo”
16iSerialNumber103h
17bNumConfigurations101h

Configuration Descriptor 1 Bus Powered, 500 mA

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType102hConfiguration
2wTotalLength2004Bh
4bNumInterfaces102h
5bConfigurationValue101h
6iConfiguration100h
7bmAttributes1A0hBus Powered, Remote Wakeup
4..0: Reserved…00000 
5: Remote Wakeup..1….. Yes
6: Self Powered.0…… No, Bus Powered
7: Reserved (set to one)
(bus-powered for 1.0)
1……. 
8bMaxPower1FAh500 mA

Interface Association Descriptor Abstract Control Model

OffsetFieldSizeValueDescription
0bLength108h
1bDescriptorType10BhInterface Association
2bFirstInterface100h
3bInterfaceCount102h
4bFunctionClass102hCDC Control
5bFunctionSubClass102hAbstract Control Model
6bFunctionProtocol100h
7iFunction100h

Interface Descriptor 0/0 CDC Control, 1 Endpoint

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType104hInterface
2bInterfaceNumber100h
3bAlternateSetting100h
4bNumEndpoints101h
5bInterfaceClass102hCDC Control
6bInterfaceSubClass102hAbstract Control Model
7bInterfaceProtocol100h
8iInterface100h

Header Functional Descriptor

OffsetFieldSizeValueDescription
0bFunctionLength105h
1bDescriptorType124hCS Interface
2bDescriptorSubtype100hHeader
3bcdCDC20110h1.10

Call Management Functional Descriptor

OffsetFieldSizeValueDescription
0bFunctionLength105h
1bDescriptorType124hCS Interface
2bDescriptorSubtype101hCall Management
3bmCapabilities101h
7..2: Reserved000000.. 
1: Data Ifc Usage……0. Call management only over Comm Ifc
0: Call Management…….1 Handles call management itself
4bDataInterface101h

Abstract Control Management Functional Descriptor

OffsetFieldSizeValueDescription
0bFunctionLength104h
1bDescriptorType124hCS Interface
2bDescriptorSubtype102hAbstract Control Management
3bmCapabilities106h
7..4: Reserved0000…. 
3: Connection….0… 
2: Send Break…..1.. Send Break request supported
1: Line Coding……1. Line Coding requests and Serial State notification supported
0: Comm Features…….0 

Union Functional Descriptor

OffsetFieldSizeValueDescription
0bFunctionLength105h
1bDescriptorType124hCS Interface
2bDescriptorSubtype106hUnion
3bControlInterface100h
4bSubordinateInterface0101hCDC Data

Endpoint Descriptor 81 1 In, Interrupt, 64 ms

OffsetFieldSizeValueDescription
0bLength107h
1bDescriptorType105hEndpoint
2bEndpointAddress181h1 In
3bmAttributes103hInterrupt
1..0: Transfer Type……11 Interrupt
7..2: Reserved000000.. 
4wMaxPacketSize20010h16 bytes
6bInterval140h64 ms

Interface Descriptor 1/0 CDC Data, 2 Endpoints

OffsetFieldSizeValueDescription
0bLength109h
1bDescriptorType104hInterface
2bInterfaceNumber101h
3bAlternateSetting100h
4bNumEndpoints102h
5bInterfaceClass10AhCDC Data
6bInterfaceSubClass100h
7bInterfaceProtocol100h
8iInterface100h

Endpoint Descriptor 02 2 Out, Bulk, 64 bytes

OffsetFieldSizeValueDescription
0bLength107h
1bDescriptorType105hEndpoint
2bEndpointAddress102h2 Out
3bmAttributes102hBulk
1..0: Transfer Type……10 Bulk
7..2: Reserved000000.. 
4wMaxPacketSize20040h64 bytes
6bInterval100h

Endpoint Descriptor 83 3 In, Bulk, 64 bytes

OffsetFieldSizeValueDescription
0bLength107h
1bDescriptorType105hEndpoint
2bEndpointAddress183h3 In
3bmAttributes102hBulk
1..0: Transfer Type……10 Bulk
7..2: Reserved000000.. 
4wMaxPacketSize20040h64 bytes
6bInterval100h

This report was generated by USBlyzer

实现了上面的描述符之后,就能保证插入系统后 Windows 设备管理器上不会出现惊叹号。Windows 支持的标准 CDC 动作有下面8个【参考1】

  1. SET_LINE_CODING  用于主机对设备设置波特率,停止位,奇偶校验和位数
  2. GET_LINE_CODING用于主机取得设备当前波特率,停止位,奇偶校验和位数
  3. SET_CONTROL_LINE_STATE 用于产生 RS-232/V.24 标准的控制信号
  4. SEND_BREAK
  5. SERIAL_STATE  返回状态信息,比如:奇偶校验错误
  6. SEND_ENCAPSULATED_COMMAND
  7. GET_ENCAPSULATED_RESPONSE
  8. RESPONSE_AVAILABLE

从实际验证的结果看起来(就是前面提到的使用 Arduino Leonardo 作为验证对象),实现 1-3 的支持外加 2个Endpoint Bulk传输即可实现通讯。

 1.SET_LINE_CODING  的实现。收到 bRequestType ==0x21, bRequest== SET_LINE_CODING  即可判定这个操作;之后用 ENDPOINT 0 的OUT 中返回当前的LineInfo;最后再通过 ENDPOINT 0 的 IN 返回0字节

2. GET_LINE_CODING  的实现。收到 bRequestType ==0xA1, bRequest== GET_LINE_CODING  即可判定这个操作;之后直接返回当前的LineInfo;最后再通过 ENDPOINT 0 的 IN 返回0字节

3. SET_CONTROL_LINE_STATE 的实现。收到 bRequestType ==0x21, bRequest== 0x22  即可判定这个操作;之后直接通过ENDPOINT 0 的 IN 返回0字节。

实现上面的操作之后,即可使用串口工具打开设备产生的串口了。接下来实现串口传输的模拟:

  1. 从Windows(HOST) 对CH567通过串口工具发送数据。数据会出现在 endpoint2 OUT上,我们将收到的数据送到CH567的串口上,然后再通过一个额外的串口转USB即可看到。具体代码是:
                        if(intstatus == (UIS_TOKEN_OUT|2))             /* endpoint 2 下传 */
                        {
                                if(R8_USB1_INT_ST&bUIS_TOG_OK)
                                {

                                        // 下传是 HOST -> DEVICE
                                        // 用串口工具打开设备对应的串口,然输入的内容可以在 Debug 串口上看到
                                        for (i=0; i<R16_USB1_RX_LEN; i++)
                                        {
                                                printf("%X ",UsbEp2OUTBuf[i]);
                                        }
                                        printf("\n");
                                }
                        }

2.从CH567定时对 Windows 发送字符串,使用串口工具打开CH567端口后可以看到这个字符串。修改有2处,第一个是发送的代码,在main.c 中每隔5秒发送一次:

        while(1)
        {
                mDelaymS(5000);
                if (UsbConfig!=0)
                {
                        memcpy( UsbEp3INBuf, &Msg[0], sizeof( Msg ));
                        R16_UEP3_T_LEN1 =  sizeof( Msg );
                        R8_UEP3_TX_CTRL1 = (R8_UEP3_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                }
        };

另外一处是当CH567收到 Endpoint3 IN 中断时,使用0字节来回复给主机

 if(intstatus == (UIS_TOKEN_IN|3))             /* endpoint 3 上传 */
                        {
                                R16_UEP3_T_LEN1 =  0;
                                R8_UEP3_TX_CTRL1 = (R8_UEP3_TX_CTRL1 & ~ MASK_UEP_T_RES) | UEP_T_RES_ACK;
                        }

此外,还有一处需要特别注意的是:必须使用高波特率用于 printf 的串口输出(>1Mhz),实验中我使用的是 CH343 6Mhz的波特率,否则会发生丢失log的情况(实际上有跑到代码,但是对应那句话的 Log 不出现,这个问题我调试了2天,在USB 逻辑分析仪上看到了发送的数据包,但是串口 Log说没有)。

运行结果如下,左侧是用于调试的CH343产生的串口,右边是CH567模拟出来的串口。当我们对CH567发送”1234567”时,CH567收到后会从UART再次送出,因此我们在左侧能看到;此外,CH567每隔5秒发送一次”www.lab-z.com”字符串在右侧窗口可以看到。

完整代码下载:

参考:

1. https://www.silabs.com/documents/public/application-notes/AN758.pdf

命令行方式运行 CPU-Z

CPU-Z 是一款能够查看当前CPU 信息的工具,除了最常见的直接运行,在GUI界面上看到结果,还可以使用命令行的方式让它输出测试报告。

  1. -html 命令:生成 html 格式的报告

使用方法: -html=报告名称

例如:cpuz_x64.exe -html=labz 将会生成 labz.html文件

CPU-Z 生成HTML报告
CPU-Z 生成的HTML格式报告

打开 labz.html看到的报告如下:

浏览器打开生成的 HTML 报告

其中包括 SPD 和 PCI 设备等等信息。

2. -txt 命令:生成 txt 格式的报告

使用方法: -txt=报告名称

例如:cpuz_x64.exe -txt=labz 将会生成 labz.txt

3. -bench 命令:生成bench mark 的结果

使用方法: -bench -txt=文件名

例如:cpuz_x64.exe -txt=labz 将会生成 labz.txt和一个以当前计算机名称命名的Txt文件,从实际测试来看后面必须跟着 -txt 或者 -html 才能正确生成最后的结果,如果没有的话,不会生成分数文件。下面是一个例子,可以看到TXT文件中的分数和 GUI 界面测试的基本相同。

CPU-Z 生成的 Benchmark 结果对比

4. -console 命令

使用方法:  -console    

会直接将结果输出到当前 console 串口,其他程序可以使用这个参数获取 CPU-Z 的运行结果,并且直接捕获。

CPU-Z Console 输出

5. -Jsontxt 命令

使用方法:  -console=文件名

生成 Json格式的结果:

CPU-Z 生成 JSON 结果

此外,需要注意的是上面的命令执行之后需要稍等一会才能得到结果或者生成完整的文件,在使用中需要预留足够的时间。

命令行方式生成的报告信息完整,相比 GUI 显示更容易进行处理和分享,有需要的朋友可以尝试。​

启动 Win10 Camera 的方法

有时候,为了测试我们需要启动 Windows 10 下面的 Camera UWP 程序。经过研究可以使用下面的两种方法:

1.CMD 下面输入(注意后面有冒号):

start microsoft.windows.camera:

2. 在 C# 使用如下代码:

var ps = new ProcessStartInfo("microsoft.windows.camera:")
                {
                    UseShellExecute = true,
                    Verb = "open"
                };
 
                Process.Start(ps);

Intel Gop 测试工具

最近在调试 GOP 相关问题,偶然间发现 Intel 测试 GOP 的正式工具:Intel Gop Tester。

这是一款运行于 UEFI Shell 下的工具,基本的功能如下:

USAGE : IntelGopTester.efi [-b] [-i] [-m &lt;mode number>] [-l &lt;Log file name>] [-s &lt;handle>] [-e &lt;handle>] [-r &lt;brightness>] [-g] [-v &lt;vbt file name>]
[-b] : Enables page break
[-i] : Displays information about the driver
[-m &lt;mode number>] : Sets the specified mode number
[-r &lt;brightness>] : set the brightness level for LFP displays &lt;brightness value (Range: 0 - 255 )>
[-g] : get the current brightness value for Lfp displays
[-e &lt;handle>] : Get EDID data for the child controller if present
[-l &lt;Log file name>] : Specifies the log file
[-s &lt;handle>] : Performs display switch to the child device specified by handle number
[-v &lt;Vbt File name>] : Dumps vbt in the specified log file

我选择了几个常用的功能做了简单测试。

1.显示当前 GOP 基本信息:

IntelGopTester -i

在 ADL-M 平台上显示结果如下:

IntelGopTester Version: 1.0.1005
 
Driver Name using EFI_COMPONENT_NAME2_PROTOCOL:
[C5]: Intel(R) GOP Driver [21.0.1054]
Driver Name using EFI_COMPONENT_NAME2_EX_PROTOCOL:
[C5]: Intel(R) GOP Driver [21.0.1054]
Driver Version using EFI_COMPONENT_NAME2_EX_PROTOCOL:
[C5]: 21.0.1054
 
Controller Name:
[132]: Intel(R) Graphics Controller
 
Child Handles [1]:
[14A]: DP1[ACTIVE] [Active]
 
--------------------------
Graphics Mode details
--------------------------
Number of modes supported  : 7
 
CURRENT MODE               : 2
 
Mode number              : 0
Horizontal resolution    : 1920
Vertical resolution      : 1200
Pixels Per Scan Line     : 1920
Pixel format             : BGRX
 
Mode number              : 1
Horizontal resolution    : 640
Vertical resolution      : 480
Pixels Per Scan Line     : 640
Pixel format             : BGRX
 
Mode number              : 2
Horizontal resolution    : 800
Vertical resolution      : 600
Pixels Per Scan Line     : 800
Pixel format             : BGRX
 
Mode number              : 3
Horizontal resolution    : 1024
Vertical resolution      : 768
Pixels Per Scan Line     : 1024
Pixel format             : BGRX
 
Mode number              : 4
Horizontal resolution    : 1280
Vertical resolution      : 1024
Pixels Per Scan Line     : 1280
Pixel format             : BGRX
 
Mode number              : 5
Horizontal resolution    : 1600
Vertical resolution      : 1200
Pixels Per Scan Line     : 1600
Pixel format             : BGRX
 
Mode number              : 6
Horizontal resolution    : 1920
Vertical resolution      : 1080
Pixels Per Scan Line     : 1920
Pixel format             : BGRX
 
 
--------------------------
Platform GOP Policy Info
--------------------------
Platform GOP Policy Revision: 3
 
Platform Lid Status: Lid is open

2.保存系统当前的 VBT

IntelGopTester -v 文件名

3.读取当前的 EDID。其中的handle参数来自前面 -i 取得的handle。

IntelGopTester -v 14a
Size of EDID :        256 
Raw EDID Data : 
 
00 FF FF FF FF FF FF 00 22 F0 54 29 00 00 00 00 
26 16 01 04 A5 34 20 78 23 FC 81 A4 55 4D 9D 25 
12 50 54 21 08 00 D1 C0 81 C0 81 40 81 80 95 00 
A9 40 B3 00 01 01 28 3C 80 A0 70 B0 23 40 30 20 
36 00 06 44 21 00 00 1A 00 00 00 FD 00 18 3C 18 
50 11 00 0A 20 20 20 20 20 20 00 00 00 FC 00 48 
50 20 5A 52 32 34 34 30 77 0A 20 20 00 00 00 FF 
00 43 4E 34 32 33 38 30 4B 53 53 0A 20 20 01 19 
02 03 19 C1 4C 90 1F 05 14 04 13 03 02 07 06 12 
01 23 09 07 07 83 01 00 00 02 3A 80 18 71 38 2D 
40 58 2C 45 00 06 44 21 00 00 1E 02 3A 80 D0 72 
38 2D 40 10 2C 45 80 06 44 21 00 00 1E 01 1D 00 
72 51 D0 1E 20 6E 28 55 00 06 44 21 00 00 1E 01 
1D 00 BC 52 D0 1E 20 B8 28 55 40 06 44 21 00 00 
1E 8C 0A D0 8A 20 E0 2D 10 10 3E 96 00 06 44 21 
00 00 18 00 00 00 00 00 00 00 00 00 00 00 00 7B 
 
Detailed EDID data:
Manufacturer name : HWP 
Product ID : 2954
Monitor name : HP ZR2440w
   
********** Standard timings info ***********
1920 X 1080 @ 60 Hz
1280 X 720 @ 60 Hz
1280 X 960 @ 60 Hz
1280 X 1024 @ 60 Hz
1440 X 900 @ 60 Hz
1600 X 1200 @ 60 Hz
1680 X 1050 @ 60 Hz
********** Established timings I ***********
800 x 600 @ 60 Hz
640 x 480 @ 60 Hz
********** Established timings II ***********
1024 x 768 @ 60 Hz
********** Established timings III ***********
********** Native mode info ***********
Pixel clock : 15400
1920 X 1200 @  59 Hz

有兴趣的朋友可以在 Intel 平台上下载这个工具进行实验。

SF100 直刷 SPI NOR指南

通常情况下,我们都是直接将 SF100 通过连接线直接接到主板上进行刷写的,但是一些情况下我们需要直接将SF100连接到 SPI NOR 上进行刷写。本文介绍具体连线。

DediProg SF100
SF100SF100
I/O1I/O4
I/O2X
VCCGND
CSCLK
MISOMOSI
VPPI/O3
SCLSDA

SF100 引脚

SPI NOR引脚SF100 SF100SPI NOR引脚
1 CS#GND VCC8 VCC
2 MISOMISO VCC7 HOLD#/RESET#
3 WP#VCC SCLK6 SCLK
4 VSSGND MOSI5 MOSI

SPI NOR 连接

特别需要注意的是 SPI NOR 上面的 WP# 和 HOLD# 引脚,即便不用也必须接到VCC上而不能悬空。

CH32V208 评估板上手指南

熟悉我的朋友知道我一直在玩WCH 出品的双USB 接口CH567芯片【参考1】。最近正好报名参加“第二届RISC-V MCU创新应用大赛”【参考2】,主办方赠送了一块 CH32V208评估板,于是就开始上手研究这个芯片。

CH32V208 评估板、开发板

本文将介绍Ch32V208 开发板的编译和下载。在开始之前,硬件方面需要准备一根数据线:USB 公头对公头转接线或者USB TypeA 公头转TypeC 公头线。软件方面需要准备:

1.集成编译环境(特别注意:Windows 7 下面这个页面无法正常下载,需要 Windows 10,我不知道原因)

http://www.mounriver.com/download

2. 评估板说明和示例代码

https://www.wch.cn/downloads/CH32V20xEVT_ZIP.html

3. 芯片参考手册

https://www.wch.cn/downloads/CH32V208DS0_PDF.html

4.下载工具

https://www.wch.cn/downloads/WCHISPTool_Setup_exe.html

首先安装集成编译环境,之后在环境中打开示例代码,因为上面的代码是专门给这个芯片使用的,所以也无需配置打开工程后即可编译

接下来介绍一下下载方法。

最简单的下载方法是使用 Type-C 接口,线缆另外一端连接到主机上。插入之后确保2位置处是关闭的,之后按住1位置的按钮再打开2的开关,这样设备管理器中会出现一个新的设备

CH32V208 评估板 TypeC 接口下载
下载设备

接下来即可使用 WCHISPTool进行刷新,需要切换到 CH32Vx Series,然后 Chip Model 中选择 CH32V208 型号。特别注意,如果出现无法有保护无法写入的提示,还需要点击 “Remove protect”按钮。然后选择要烧写的文件,通过 Download 即可完成烧写。烧写之后,重新开关一次即可运行烧写后的代码。

WCHISPTool

除了使用板子上的TypeC接口进行刷新之外,还可以使用P1和P5处的 USB 母头进行代码烧写。CH32V208的2个USB接口都可以用于烧写。另外,从电路上可以看到TypeC 接口对应USB2DM 和P5 的 USB 是同一个信号。

CH32V208 评估版USB接口电路图

通过上述方法,已经可以在CH32V208板上运行你的代码,下面即可CH32V208的开发之旅。

参考:

1. https://www.lab-z.com/?s=ch567

2. https://mp.weixin.qq.com/s/N3m63mutCEb6NPKy_2bLhQ

ESP32S2 USB触摸屏作图

这次实验使用 ESP32 S2 模拟触摸屏的方式绘制一个心形和渐开线。

首先介绍的是“笛卡尔的爱情坐标公式”:心形函数r=a(1-sinθ),常被人当做表达爱和浪漫的一种方法。并且关于这个函数的由来有一个传播很广的故事。

笛卡尔在52岁时邂逅了当时瑞典的公主,当时他是公主的数学老师,不久公主就对笛卡尔产生了爱慕之情。然而,国王知道后,非常愤怒,将他流放回法国。在那里,笛卡尔给公主写的信都会被拦截。

在笛卡尔寄出第十三封信后,笛卡尔永远离开了这个世界。在最后的一封信上,笛卡尔只写了一个公式:r=a(1-sinΘ)

国王也看不懂,于是把这封信交给了公主。这就是我们知道的极坐标下的心型函数。

这封情书至今保存在欧洲笛卡尔纪念馆里。【这一段是“读者”体,真实情况如果用震惊体来描述的话就是“天才数学家竟被女王惨无人道的折磨”参考1】

在这个公式中有2个变量:a和θ。我们首先使用网页版的绘图工具【参考2】验证一下这个公式:

上述公式的参数方程形式为:

参数方程形式:

x= a*(2*sin(t)-sin(2*t))
y= a*(2*cos(t)-cos(2*t))
0&lt;=t&lt;=2*pi

根据上述公式,设计代码如下:

#include "USB.h"
#include "USBHID.h"
USBHID HID;

#define STEP 600
int STARTX=5000;
int STARTY=13000;
int STARTR=5000;
static const uint8_t report_descriptor[] = { // 8 TouchData
  0x05, 0x0D,
  0x09, 0x04,
  0xA1, 0x01,
  0x09, 0x22,
  0xA1, 0x02,
  0x09, 0x42,
  0x15, 0x00,
  0x25, 0x01,
  0x75, 0x01,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x30,
  0x25, 0x7F,
  0x75, 0x07,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x51,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x01,
  0x09, 0x30,
  0x09, 0x31,
  0x26, 0xFF, 0x7F,
  0x65, 0x00,
  0x75, 0x10,
  0x95, 0x02,
  0x81, 0x02,
  0xC0,
  0x05, 0x0D,
  0x27, 0xFF, 0xFF, 0x00, 0x00,
  0x75, 0x10,
  0x95, 0x01,
  0x09, 0x56,
  0x81, 0x02,
  0x09, 0x54,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x0D,
  0x09, 0x55,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0xB1, 0x02,
  0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
  public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
      {
        buffer[0]=0x0a;
        return 1;
      }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData[9];

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Device.begin();
  USB.begin();
}

void loop() {
  if (HID.ready()) {
    delay(1000);
    Serial.println("Finger");
    int iX,iY;
    iX=STARTX+(STARTR*(2*sin(0)-sin(0)))/3;
    iY=STARTY-(STARTR*(2*cos(0)-cos(0)));
    TouchData[0] = 0x81; 
    TouchData[1] = 0x02;
    TouchData[2] = (iX)&0xFF; 
    TouchData[3] = (iX)>>8&0xFF;
    TouchData[4] = (iY)&0xFF; 
    TouchData[5] = (iY)>>8&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    Device.send(TouchData);
    delay(20);
    TouchData[0] = 0x81; 
    TouchData[1] = 0x02;
    TouchData[2] = (iX+1)&0xFF; 
    TouchData[3] = (iX+1)>>8&0xFF;
    TouchData[4] = (iY)&0xFF; 
    TouchData[5] = (iY)>>8&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    Device.send(TouchData);
    delay(40);

    // touch report
    //  0: on/off + pressure
    //  1: contact id
    //  2: X lsb
    //  3: X msb
    //  4: Y lsb
    //  5: Y msb
    //  6: scan time lsb
    //  7: scan time msb
    //  8: contact count

    for (int i=0;i<STEP+1;i++) {
    TouchData[0] = 0x81; TouchData[1] = 0x01;
    iX=STARTX+(STARTR*(2*sin(2*PI*i/STEP)-sin(2*2*PI*i/STEP)))/3;
    iY=STARTY-(STARTR*(2*cos(2*PI*i/STEP)-cos(2*2*PI*i/STEP)));
    Serial.print(iX);Serial.print("  ");Serial.println(iY);
    TouchData[2] = ((int)(iX))&0xFF; 
    TouchData[3] = ((int)(iX))>>8&0xFF;
    TouchData[4] = ((int)(iY))&0xFF; 
    TouchData[5] = ((int)(iY))>>8&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    Device.send(TouchData);
    delay(20);
    }
    //每隔10秒
    delay(2000);
    STARTX=STARTX+6000;
    STARTY=STARTY;
    
  }

}

渐开线的参数方程:

iX=r*cos(b)+r*b*sin(b)
iY=r*sin(b)-r*b*cos(b)

完整代码:

#include "USB.h"
#include "USBHID.h"
USBHID HID;

#define STEP 100
int STARTX = 18000;
int STARTY = 18000;
int STARTR = 100;
static const uint8_t report_descriptor[] = { // 8 TouchData
  0x05, 0x0D,
  0x09, 0x04,
  0xA1, 0x01,
  0x09, 0x22,
  0xA1, 0x02,
  0x09, 0x42,
  0x15, 0x00,
  0x25, 0x01,
  0x75, 0x01,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x30,
  0x25, 0x7F,
  0x75, 0x07,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x51,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x01,
  0x09, 0x30,
  0x09, 0x31,
  0x26, 0xFF, 0x7F,
  0x65, 0x00,
  0x75, 0x10,
  0x95, 0x02,
  0x81, 0x02,
  0xC0,
  0x05, 0x0D,
  0x27, 0xFF, 0xFF, 0x00, 0x00,
  0x75, 0x10,
  0x95, 0x01,
  0x09, 0x56,
  0x81, 0x02,
  0x09, 0x54,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x0D,
  0x09, 0x55,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0xB1, 0x02,
  0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
  public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
    {
      buffer[0] = 0x0a;
      return 1;
    }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData[9];

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Device.begin();
  USB.begin();
}

void loop() {
  if (HID.ready()) {
    delay(1000);
    Serial.println("Finger");
    int iX, iY;
    /*
    iX = STARTX + (STARTR * (2 * sin(0) - sin(0))) / 3;
    iY = STARTY - (STARTR * (2 * cos(0) - cos(0)));
    TouchData[0] = 0x81;
    TouchData[1] = 0x02;
    TouchData[2] = (iX) & 0xFF;
    TouchData[3] = (iX) >> 8 & 0xFF;
    TouchData[4] = (iY) & 0xFF;
    TouchData[5] = (iY) >> 8 & 0xFF;
    TouchData[6] = (millis() * 10) & 0xFF; TouchData[7] = (millis() * 10 >> 8) & 0xFF;
    TouchData[8] = 0x01;
    Device.send(TouchData);
    delay(20);
    TouchData[0] = 0x81;
    TouchData[1] = 0x02;
    TouchData[2] = (iX + 1) & 0xFF;
    TouchData[3] = (iX + 1) >> 8 & 0xFF;
    TouchData[4] = (iY) & 0xFF;
    TouchData[5] = (iY) >> 8 & 0xFF;
    TouchData[6] = (millis() * 10) & 0xFF; TouchData[7] = (millis() * 10 >> 8) & 0xFF;
    TouchData[8] = 0x01;
    Device.send(TouchData);
    delay(40);
*/
    // touch report
    //  0: on/off + pressure
    //  1: contact id
    //  2: X lsb
    //  3: X msb
    //  4: Y lsb
    //  5: Y msb
    //  6: scan time lsb
    //  7: scan time msb
    //  8: contact count

    for (int i = 0; i < STEP*18; i++) {
      TouchData[0] = 0x81; TouchData[1] = 0x01;
      iX = STARTX+(STARTR * cos(2*PI*i/STEP) + STARTR * (2*PI*i/STEP) * sin(2*PI*i/STEP))*3/4;
      iY = STARTY+STARTR * sin(2*PI*i/STEP) - STARTR * (2*PI*i/STEP) * cos(2*PI*i/STEP);
      Serial.print(iX); Serial.print("  "); Serial.println(iY);
      TouchData[2] = ((int)(iX)) & 0xFF;
      TouchData[3] = ((int)(iX)) >> 8 & 0xFF;
      TouchData[4] = ((int)(iY)) & 0xFF;
      TouchData[5] = ((int)(iY)) >> 8 & 0xFF;
      TouchData[6] = (millis() * 10) & 0xFF; TouchData[7] = (millis() * 10 >> 8) & 0xFF;
      TouchData[8] = 0x01;
      Device.send(TouchData);
      delay(20);
    }
    //每隔10秒
    delay(2000);
    STARTX = STARTX + 3;
    STARTY = STARTY;

  }

}

运行结果:

参考:

1. https://baijiahao.baidu.com/s?id=1721580028060990553&wfr=spider&for=pc

2. https://zuotu.91maths.com/#W3sidHlwZSI6MSwiZXEiOiIyKigxLXNpbih0aGV0YSkpIiwiY29sb3IiOiIjMDA4MGNjIiwidGhldGFtaW4iOiIwIiwidGhldGFtYXgiOiIycGkiLCJ0aGV0YXN0ZXAiOiIwLjAxIn0seyJ0eXBlIjoxMDAwLCJ3aW5kb3ciOlsiLTguMjE1MTExOTk5OTk5OTkiLCI4LjAzNDg4Nzk5OTk5OTk5MiIsIi01LjI3ODUyNzk5OTk5OTk5NSIsIjQuNzIxNDcxOTk5OTk5OTk2Il0sImdyaWQiOlsiMSIsIjEiXX1d

ESP32S2 模拟USB触摸屏

参照Teensy的触摸【参考1】,在 ESP32 S2 上实现了触摸屏。最关键的步骤有2个:

  1. 正确的 HID Descriptor,下面是一个10指触摸的触摸屏幕的描述符
static const uint8_t report_descriptor[] = { // 8 TouchData
  0x05, 0x0D,
  0x09, 0x04,
  0xA1, 0x01,
  0x09, 0x22,
  0xA1, 0x02,
  0x09, 0x42,
  0x15, 0x00,
  0x25, 0x01,
  0x75, 0x01,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x30,
  0x25, 0x7F,
  0x75, 0x07,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x51,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x01,
  0x09, 0x30,
  0x09, 0x31,
  0x26, 0xFF, 0x7F,
  0x65, 0x00,
  0x75, 0x10,
  0x95, 0x02,
  0x81, 0x02,
  0xC0,
  0x05, 0x0D,
  0x27, 0xFF, 0xFF, 0x00, 0x00,
  0x75, 0x10,
  0x95, 0x01,
  0x09, 0x56,
  0x81, 0x02,
  0x09, 0x54,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x0D,
  0x09, 0x55,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0xB1, 0x02,
  0xC0,
};

对应发送的数据结构是:

    // touch report
    //  0: on/off + pressure
    //  1: contact id
    //  2: X lsb
    //  3: X msb
    //  4: Y lsb
    //  5: Y msb
    //  6: scan time lsb
    //  7: scan time msb
   //  8: contact count

其中 Byte0 Bit0 是按下标志,一直为1,Bit1-7 是按键压力;Byte1 是按键编号,从 0-255,可以理解为手指编号,比如:右手食指按下,编号为0;右手中指按下,编号为1; 右手抬起后再次按下,会重新分配一个编号。Byte2-3 按键的X坐标;Byte4-5 按键的Y坐标;Byte6-7 是按压发生的事件,是以 100us为单位;Byte8 是当前正在发生的按压事件中触摸点的数量。在【参考2】有一个例子:

Table 7 Report Sequence for Two Contacts with Separated Lift (Two-Finger Hybrid)

Report1234567891011
Contact Count22222211111
Contact 1 Tip Switch111110NRNRNRNRNR
Contact 1 X,YX₁,Y₁X₂,Y₂X₃,Y₃X₄,Y₄X₅,Y₅X₅,Y₅NRNRNRNRNR
Contact 2 Tip Switch11111111110
Contact 2 X,YX₁,Y₁X₂,Y₂X₃,Y₃X₄,Y₄X₅,Y₅X₆,Y₆X₇,Y₇X₈,Y₈X₉,Y₉X₁₀,Y₁₀X₁₀,Y₁₀

图中是2个手指

图中是2个手指进行触摸的例子,R1 会分别报告手指1和2移动的信息,同时 Byte8 的 Contract Count 会等于2;R6 的时候,因为手指1已经抬起,所以Contract Count会变成1.

2.另外一个重要的,容易被忽视的要求:Get Report 的处理。即使上面的描述符正确报告,然后数据也正常发送到Windows中,你的触摸屏依然无法正常工作,原因就是缺少了对Get Report的处理。更糟糕的是:你无法使用 USBlyzer 这样的工具抓到 Teensy 中的数据。

Teensy 例子中上位机发送 GET_REPORT 收到返回值0x0a

如果不在代码中特别处理,对于这个命令会 STALL

关于这个 COMMAND 的含义,目前没搞清楚【参考3】

对于我们来说,只要有一个返回值就能让它工作正常。最终一个可以工作的代码如下(这个会在屏幕上方的中间移动一个手指触摸):

#include "USB.h"
#include "USBHID.h"
USBHID HID;

static const uint8_t report_descriptor[] = { // 8 TouchData
  0x05, 0x0D,
  0x09, 0x04,
  0xA1, 0x01,
  0x09, 0x22,
  0xA1, 0x02,
  0x09, 0x42,
  0x15, 0x00,
  0x25, 0x01,
  0x75, 0x01,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x30,
  0x25, 0x7F,
  0x75, 0x07,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x51,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x01,
  0x09, 0x30,
  0x09, 0x31,
  0x26, 0xFF, 0x7F,
  0x65, 0x00,
  0x75, 0x10,
  0x95, 0x02,
  0x81, 0x02,
  0xC0,
  0x05, 0x0D,
  0x27, 0xFF, 0xFF, 0x00, 0x00,
  0x75, 0x10,
  0x95, 0x01,
  0x09, 0x56,
  0x81, 0x02,
  0x09, 0x54,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x0D,
  0x09, 0x55,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0xB1, 0x02,
  0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
  public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
      {
        buffer[0]=0x0A;
        return 0x01;
      }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData[9];

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Device.begin();
  USB.begin();
}

void loop() {
  if (HID.ready()) {
    Serial.println("Finger");
    // touch report
    //  0: on/off + pressure
    //  1: contact id
    //  2: X lsb
    //  3: X msb
    //  4: Y lsb
    //  5: Y msb
    //  6: scan time lsb
    //  7: scan time msb
    //  8: contact count
    for (int i=0;i<200;i+=100) {
    TouchData[0] = 0x81; TouchData[1] = 0x08;
    TouchData[2] = (16000)&0xFF; TouchData[3] = ((16000)>>8)&0xFF;
    TouchData[4] = (4000+i)&0xFF; TouchData[5] = ((4000+i)>>8)&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    Device.send(TouchData);
    delay(10);
    TouchData[0] = 0x81; TouchData[1] = 0x08;
    TouchData[2] = (16000)&0xFF; TouchData[3] = ((16000)>>8)&0xFF;
    TouchData[4] = (4000+i)&0xFF; TouchData[5] = ((4000+i)>>8)&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    delay(10);
    }
    //每隔10秒
    delay(5000);
  }
}

再复杂一点,做一个画圆的:

#include "USB.h"
#include "USBHID.h"
USBHID HID;

int STARTX=16000;
int STARTY=12000;
int STARTR=2000;
static const uint8_t report_descriptor[] = { // 8 TouchData
  0x05, 0x0D,
  0x09, 0x04,
  0xA1, 0x01,
  0x09, 0x22,
  0xA1, 0x02,
  0x09, 0x42,
  0x15, 0x00,
  0x25, 0x01,
  0x75, 0x01,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x30,
  0x25, 0x7F,
  0x75, 0x07,
  0x95, 0x01,
  0x81, 0x02,
  0x09, 0x51,
  0x26, 0xFF, 0x00,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x01,
  0x09, 0x30,
  0x09, 0x31,
  0x26, 0xFF, 0x7F,
  0x65, 0x00,
  0x75, 0x10,
  0x95, 0x02,
  0x81, 0x02,
  0xC0,
  0x05, 0x0D,
  0x27, 0xFF, 0xFF, 0x00, 0x00,
  0x75, 0x10,
  0x95, 0x01,
  0x09, 0x56,
  0x81, 0x02,
  0x09, 0x54,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0x81, 0x02,
  0x05, 0x0D,
  0x09, 0x55,
  0x25, 0x0A,
  0x75, 0x08,
  0x95, 0x01,
  0xB1, 0x02,
  0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
  public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
      {
        buffer[0]=0x0a;
        return 1;
      }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData[9];

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Device.begin();
  USB.begin();
}

void loop() {
  if (HID.ready()) {
    Serial.println("Finger");
    // touch report
    //  0: on/off + pressure
    //  1: contact id
    //  2: X lsb
    //  3: X msb
    //  4: Y lsb
    //  5: Y msb
    //  6: scan time lsb
    //  7: scan time msb
    //  8: contact count
    for (int i=0;i<101;i++) {
    TouchData[0] = 0x81; TouchData[1] = 0x08;
    TouchData[2] = ((int)(STARTX+STARTR*sin(2*PI*i/100)))&0xFF; 
    TouchData[3] = ((int)(STARTX+STARTR*sin(2*PI*i/100)))>>8&0xFF;
    TouchData[4] = ((int)(STARTY+STARTR*cos(2*PI*i/100)))&0xFF; 
    TouchData[5] = ((int)(STARTY+STARTR*cos(2*PI*i/100)))>>8&0xFF;
    TouchData[6] = (millis()*10)&0xFF; TouchData[7] = (millis()*10>>8)&0xFF;
    TouchData[8] = 0x01; 
    Device.send(TouchData);
    delay(10);
    }
    //每隔10秒
    delay(5000);
    STARTX=STARTX+300;
    STARTY=STARTY+300;
    
  }
}

工作的视频

https://www.bilibili.com/video/BV1e3411n7hm?share_source=copy_web

参考:

  1. https://www.arduino.cn/thread-107925-1-1.html
  2. https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collections
  3. https://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx

CH567 上USB0 HOST 实现

这次实现 CH567 USB0 的 USB Host 功能。基于 \EXAM\USB1_HOST 的代码进行修改。其中已经实现了 USB0 HOST HID 的枚举,我们只需要添加针对 ENDPOINT 的读取即可。

在Main中添加如下代码:

                        s=WaitU0HTransactTimes(1,USB_PID_IN,ctrltog,6000);
                        if( s == USB_INT_SUCCESS ) {
                                ctrltog  = ctrltog ? 0 : 1;
                                printf("in: ");   
                                for(i=0; i<R16_USB0_RX_LEN; i++){
                                printf("%02x ", UHBuffer1[i]);}
                                printf("\n");
                        }
                        mDelaymS(1);

其中的 mDelayms() 数值应该根据描述符中的数值进行填写,这里我偷懒了直接使用 1ms。这对于功能没有 影响,USB KB 如果没有数据会直接 NAK 这次的请求。

完整代码:

Power Shell 下获得当前CPU 频率

来自  https://qa.1r1g.com/sf/ask/4326169431/
$MaxClockSpeed = (Get-CimInstance CIM_Processor).MaxClockSpeed
$ProcessorPerformance = (Get-Counter -Counter "\Processor Information(_Total)\% Processor Performance").CounterSamples.CookedValue
$CurrentClockSpeed = $MaxClockSpeed*($ProcessorPerformance/100)
 
Write-Host "Current Processor Speed: " -ForegroundColor Yellow -NoNewLine
Write-Host $CurrentClockSpeed