为了更好的进行交流,近期计划进行服务器升级,因此可能出现服务不稳定的情况。
无需担心,本站会一直存在。
如果你在使用中遇到问题,欢迎留言指出。
2025年3月15日 Z.t
为了更好的进行交流,近期计划进行服务器升级,因此可能出现服务不稳定的情况。
无需担心,本站会一直存在。
如果你在使用中遇到问题,欢迎留言指出。
2025年3月15日 Z.t
本文根据 https://github.com/apop2/GopRotate 的代码编写,这个在之前的【参考1】和【参考2】中有介绍过。
本文介绍了如何编写一个 UEFI Shell Command 的方法,然后给出了一个基本的框架,有需要的朋友可以方便的扩展定制自己所需的 UEFI Shell。代码在 edk2 202411 下实验成功。
以WinHost 模拟器为例,介绍例子的测试方法:
1.解压到 \ShellPkg\Library 下面
2.修改 \EmulatorPkg\EmulatorPkg.dsc文件
BcfgCommandLib|ShellPkg/Library/UefiShellBcfgCommandLib/UefiShellBcfgCommandLib.inf
IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
##LABZDEBUG_Start
NULL|ShellPkg/Library/MyShellCommandLib/MyShellCommandLib.inf
##LABZDEBUG_End
<PcdsFixedAtBuild>
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000
3.重新编译,使用 build -a X64 即可
运行 WinHost测试如下:
这里定了一个 mycmd 命令,然后可以接受一个参数。MyCommand.c 中的MyShellCommand() 函数负责实际执行 ShellPkg\Library\MyShellCommandLib\MyShellCommandLib.c 中的MyShellCommandLibConstructor() 函数负责向 Shell 注册 mycmd 命令,同时注册一个帮助文本,通过 STR_GET_MYCMD_HELP 定义。
EFI_STATUS EFIAPI MyShellCommandLibConstructor(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
gMyShellCommandHiiHandle = HiiAddPackages (&gMyShellCommandGuid, ImageHandle, MyShellCommandLibStrings, NULL);
if(gMyShellCommandHiiHandle != NULL)
{
ShellCommandRegisterCommandName(L"mycmd", MyShellCommand, MyShellCommandGetFileName, 0, L"", FALSE, gMyShellCommandHiiHandle, STRING_TOKEN(STR_GET_MYCMD_HELP));
}
return EFI_SUCCESS;
}
有兴趣的朋友可以使用这个框架自行扩展 UEFI Shell。
参考:
最近在测试的时候多次遇到了文件无法删除的情况
经过研究可以使用下面的方法进行删除:
1.运行Regfix.reg 文件将设置导入注册表
2.选中文件或者目录后,鼠标右键菜单会出现“Take Ownership”的选项,选中执行即可
3.运行UninstallRegfix.reg 可以去掉上述的菜单选项
下载:
亲测有效,推荐有需要的朋友试试。
参考:
1.https://thegeekpage.com/how-to-delete-files-protected-by-trustedinstaller-in-windows-10/
去年11月份,EDK2 202411正式发布在:
https://github.com/tianocore/edk2/releases/tag/edk2-stable202411
从 History 来看,只是有一些功能性改进:
和之前类似,这里放上一个完整版,补全了所有的三方库,大小是467MB 左右。下载之后,运行过一次 edksetup.bat forcerebuild 编译了需要用的工具,否则直接使用可能会遇到一些工具无法找到的错误。
链接:https://pan.baidu.com/s/1JjJOr7MWxxUsj0ip1Leg2Q?pwd=LABZ
提取码: LABZ
个人建议:除非有特别明确的目的,否则没有必要追求最新的版本, 所谓 “小车能跑只管推”。
我在三亚居住的酒店距离大海非常近,早晨起来我一个人出门去了海边。春节时间到三亚最重要的问题是保暖,千万不要因为感觉一下子暖和起来就减去衣物。作为一个东北人,我对温度还是非常敏感的,稍微感觉寒冷就会添加衣物。而我的老婆则是“非常抗冻”的湖北人,于是到达三亚的第二天,她穿的很少和我们一起去了一下南山寺。刚一下车她就开始后悔穿的过少。我都戏称门上的“二不”是专门说她的。第三天,她就开始不舒服头疼,后面更是在九所的宾馆一直“趴窝”了。
沙滩的沙子很细腻,这边远离市区,加上比较早,所以没有多少人。
太阳还没有升起,云高海阔如是:
沙滩上有无数小洞穴:
洞很圆,很有规则的形状,是小螃蟹挖出来的。打一个洞,然后藏身于此。海滩上有小朋友拿着铲子对准洞穴,斜着挖下去,运气好的话马上就会有小螃蟹慌张的逃出来:
刚好在海滩上遇到一群人,腰上缠绕着绳子,绳子末端带有一个圆球。起初我以为是要进行什么游戏。
等他们列队开工成我才知道原来是一种古老的捕鱼方式。首先,海里面有一艘渔船,下网,然后网的一头捆扎住绳子交给岸上的人。接下来这些人用腰间的绳子连接到这个网的绳子上。缠绕的方式也非常简单:用球在绳子上一丢很快就缠绕好了,然后用腰部的力量拉拽着网绳。拉拽的过程中完全凭借腰部和身体的力量完全不用手,可以一边吸烟一边工作着。前面的人用力拉拽,向着远离大海的方向退去,退倒最后再将带着球的绳子解下来,再走到最前面缠绳子上重复这样的动作。这样我怀疑最早的拔河比赛是否也发源于此。后来查了一下,一百年前,拔河是奥运比赛项目,后来因为英国人创造性的作弊导致这个项目最终被取消了。
渔网很长,我看了半个多小时也没有到头的意思,后面就回去酒店了。路上偶然看到了波罗蜜,原来它是这样长在树上的:
细枝硕果:
我们通常使用的鼠标发送的都是相对坐标,比如:输出 X=100,Y=200, 意思是通知系统将鼠标从目前的位置移动(100,200)个单位。此外,Windows 还有一个鼠标优化机制,比如,你的鼠标输出范围是 +/- 127, 如果你的屏幕宽度是 1920, 经过优化之后无需滑动 1920/127 次才能将鼠标指针从最左边移动到最右边。
这次带来的PVD (Physical Virtual Device)是一款绝对值鼠标,它模拟了一个绝对值鼠标,能够将从USB串口输入的数据转化为绝对值鼠标数据发送出来。这样,可以方便的实现鼠标点击屏幕上的任意位置。
硬件部分使用和上一次设计相同【参考1】,核心是 WCH出品的 CH554e单片机。
代码是 Arduino 来实现的,屏幕左上角是 (0,0), 输出坐标为 0-32767。实践表明:送出来的数据和屏幕分辨率存在定比例关系。比如,在 1920*1024屏幕上,如果想让鼠标移动到 (300, 200)的位置,那么需要设备输出(300/1920 *32768, 200/1024 *32768)这样的数据。
主程序代码如下:
#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif
#include <WS2812.h>
#include "src/CdcHidCombo/USBCDC.h"
#include "src/CdcHidCombo/USBHIDKeyboardMouse.h"
#define NUM_LEDS 1
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)
__xdata uint8_t ledData[NUM_BYTES];
#define KeyboardReportID 0x01
#define rMouseReportID 0x02
#define aMouseReportID 0x03
#define OnBoardLED 0x04
// Data format
// Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
// Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
uint8_t recvStr[9];
uint8_t recvStrPtr = 0;
unsigned long Elsp;
void setup() {
USBInit();
Serial0_begin(115200);
delay(1000);
Serial0_print("start");
Elsp=0;
}
void loop() {
while (USBSerial_available()) {
uint8_t serialChar = USBSerial_read();
recvStr[recvStrPtr++] = serialChar;
if (recvStrPtr == 10) {
for (uint8_t i = 0; i < 9; i++) {
Serial0_write(recvStr[i]);
}
if (recvStr[0] == KeyboardReportID) { // Keyboard
USB_EP3_send(recvStr, 9);
}
if (recvStr[0] == rMouseReportID) {
USB_EP3_send(recvStr, 5); // Relative Mouse
}
if (recvStr[0] == aMouseReportID) {
USB_EP3_send(recvStr, 7); // Absolute Mouse
}
if (recvStr[0] == OnBoardLED) {
set_pixel_for_GRB_LED(ledData, 0, recvStr[1], recvStr[2], recvStr[3]);
neopixel_show_P1_5(ledData, NUM_BYTES);
}
recvStrPtr = 0;
}
Elsp=millis();
}
// If there is no data in 100ms, clear the receive buffer
if (millis()-Elsp>100) {
recvStrPtr = 0;
Elsp=millis();
}
}
如果USB CDC 收到以 aMouseReportID 起始的9个字节,那么会将数据直接转到绝对值鼠标端点然后发送给主机。
VS2019 编写一个上位机程序进行测试,代码如下:
// CDC_vKBMSTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <windows.h>
#include <SetupAPI.h>
#include <tchar.h>
#include <iostream>
#include <cstring>
#include <atlstr.h>
#pragma comment(lib, "Setupapi.lib")
#define MY_USB_PID_VID _T("VID_1209&PID_C55C")
#define SCREENWIDTH 1920
#define SCREENHEIGHT 1200
int Port;
class ComPortException : public std::exception {
public:
ComPortException(DWORD errorCode) : errorCode(errorCode) {}
DWORD getErrorCode() const {
return errorCode;
}
private:
DWORD errorCode;
};
// 对串口portName 发送数据
// 无法打开串口返回 1
// 无法设置串口参数 2
void SendToComPort(const int port, const char* data) {
int Result = 0;
HANDLE hCom = INVALID_HANDLE_VALUE;
TCHAR portName[10]; // 用于存储 "COMp" 字符串
// 将整数 p 转换为 "COMp" 字符串
#ifdef UNICODE
swprintf(portName, 10, _T("\\\\.\\COM%d"), port);
#else
sprintf(portName, "COM%d", port);
#endif
try {
// 打开串口
hCom = CreateFile(portName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hCom == INVALID_HANDLE_VALUE) {
throw ComPortException(GetLastError());
}
// 设置串口参数
DCB dcb;
SecureZeroMemory(&dcb, sizeof(DCB));
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(hCom, &dcb)) {
throw ComPortException(GetLastError());
}
dcb.BaudRate = CBR_115200; // 波特率
dcb.ByteSize = 8; // 数据位
dcb.StopBits = ONESTOPBIT; // 停止位
dcb.Parity = NOPARITY; // 校验位
if (!SetCommState(hCom, &dcb)) {
throw ComPortException(GetLastError());
}
// 设置超时参数
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hCom, &timeouts)) {
throw ComPortException(GetLastError());
}
// 发送数据
DWORD bytesWritten;
if (!WriteFile(hCom, data, 10, &bytesWritten, NULL)) {
std::cerr << "Failed to write to COM port." << std::endl;
}
else {
std::cout << "Successfully sent data to COM port: [" << port << "]" << std::endl;
}
for (int i = 0; i < 10; i++) {
printf("%02x ", data[i]&0xFF);
}
printf("\n");
// 关闭串口
if (hCom != INVALID_HANDLE_VALUE) {
CloseHandle(hCom);
}
}
catch (const ComPortException& ex) {
std::cerr << "Error: " << ex.getErrorCode() << std::endl;
if (hCom != INVALID_HANDLE_VALUE) {
CloseHandle(hCom);
}
}
}
/************************************************************************/
/* 根据USB描述信息字符串中读取
/************************************************************************/
int MTGetPortFromVidPid(CString strVidPid)
{
// 获取当前系统所有使用的设备
int nPort = -1;
int nStart = -1;
int nEnd = -1;
int i = 0;
CString strTemp, strName;
DWORD dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
SP_DEVINFO_DATA sDevInfoData;
TCHAR szDis[2048] = { 0x00 };// 存储设备实例ID
TCHAR szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
DWORD nSize = 0;
// 准备遍历所有设备查找USB
hDevInfo = SetupDiGetClassDevs(NULL, L"USB", NULL, dwFlag);
if (INVALID_HANDLE_VALUE == hDevInfo)
goto STEP_END;
// 开始遍历所有设备
memset(&sDevInfoData, 0x00, sizeof(SP_DEVICE_INTERFACE_DATA));
sDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
{
nSize = 0;
// 无效设备
if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
goto STEP_END;
// 根据设备信息寻找VID PID一致的设备
strTemp.Format(_T("%s"), szDis);
strTemp.MakeUpper();
if (strTemp.Find(strVidPid, 0) == -1)
continue;
// 查找设备属性
nSize = 0;
SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
SPDRP_FRIENDLYNAME,
0, (PBYTE)szFN,
sizeof(szFN),
&nSize);
// "XXX Virtual Com Port (COM7)"
strName.Format(_T("%s"), szFN);
_tprintf(_T("%s"), szFN);
if (strName.IsEmpty())
//goto STEP_END;
continue;
// 寻找串口信息
nStart = strName.Find(_T("(COM"), 0);
nEnd = strName.Find(_T(")"), 0);
if (nStart == -1 || nEnd == -1)
//goto STEP_END;
continue;
strTemp = strName.Mid(nStart + 4, nEnd - nStart - 4);
nPort = _ttoi(strTemp);
}
STEP_END:
// 关闭设备信息集句柄
if (hDevInfo != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(hDevInfo);
hDevInfo = INVALID_HANDLE_VALUE;
}
return nPort;
}
int ScreenX2Abs(int X) {
return (X* 32768 / SCREENWIDTH);
}
int ScreenY2Abs(int Y) {
return (Y * 32768 / SCREENHEIGHT);
}
void ChangeColor(
int X,
int Y
) {
char Data[10];
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(X) & 0xFF;
Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(Y) & 0xFF;
Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 0; // 点击
Data[2] = ScreenX2Abs(X) & 0xFF;
Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(Y) & 0xFF;
Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
}
void LedColor(
int Color
) {
char Data[10];
memset(Data, 0, sizeof(Data));
Data[0] = 4; // LED
Data[1] = Color&0xFF; // 点击
Data[2] = (Color>>8) & 0xFFF;
Data[3] = (Color >>16) & 0xFF;
SendToComPort(Port, (char*)&Data);
}
void DrawRect(
int StartX,
int StartY,
int Width,
int Height,
int Dx,
int Dy
) {
char Data[10];
//从左到右横线
//line(StartX, StartY, StartX + Width, StartY);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX) & 0xFF;
Data[3] = (ScreenX2Abs(StartX) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY) & 0xFF;
Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX+Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY) & 0xFF;
Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 0; // 抬起
Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY) & 0xFF;
Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
//从上到下,竖线
//line(StartX + Width, StartY,StartX+Width,StartY+Height);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY) & 0xFF;
Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 0; // 抬起
Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
//从右到左,横线
//line(StartX + Width, StartY + Height,StartX+Dx,StartY+Height);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 0; // 抬起
Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
//从下到上竖线
//line(StartX + Dx, StartY + Height, StartX + Dx, StartY + Dy);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 1; // 点击
Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
Data[0] = 3; // 绝对值鼠标
Data[1] = 0; // 抬起
Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(100);
}
int main()
{
printf("%d[%x] %d[%x]\n", ScreenX2Abs(100), ScreenX2Abs(100), ScreenY2Abs(300), ScreenY2Abs(300));
printf("Virutal KB MS Test\n");
Port = MTGetPortFromVidPid(MY_USB_PID_VID);
if (Port == -1) {
printf("No device is found\n");
goto EndProgram;
}
else {
printf("Found COM%d\n",Port);
}
Sleep(5000);
for (int i = 0; i < 8; i++) {
DrawRect(100+20*i, 300+20*i, SCREENWIDTH - 200-20*i*2, SCREENHEIGHT - 500-20*i*2, 20, 20);
if ((i % 3) == 0) {
LedColor(0x01);
}
if ((i % 3) == 1) {
LedColor(0x0100);
}
if ((i % 3) == 2) {
LedColor(0x010000);
}
}
ChangeColor(1329,125);
for (int i = 8; i < 18; i++) {
DrawRect(100 + 20 * i, 300 + 20 * i, SCREENWIDTH - 200 - 20 * i * 2, SCREENHEIGHT - 500 - 20 * i * 2, 20, 20);
if ((i % 3) == 0) {
LedColor(0x01);
}
if ((i % 3) == 1) {
LedColor(0x0100);
}
if ((i % 3) == 2) {
LedColor(0x010000);
}
}
EndProgram:
printf("End\n");
getchar();
}
这个代码的作用是在画笔中绘制蛇形线,在绘制过程中还有更换颜色的动作。
工作的视频: 【PVD 计划:绝对值鼠标测试】
参考:
离开九所之后,我们继续行程。乘坐高铁到达棋子湾站,然后乘车前往了昌江黎族自治县下面的石碌镇。这里距离棋子湾站大约30多公里。石碌镇在地图上有火车线,应该是货运线,并不能载客。
当地气温比三亚九所要低一些,阴天的次数也多一些。我母亲在当地租住的房屋一个完全向西的8楼民居,靠近当地的民族中学,房租每月1500。
小区是高层,最高24楼。正好是春节,大多数店铺都歇业回家过节去了。
对于我们来说,这里仍然是热带风情
这是一颗芒果树
尚未成熟的芒果:
居住的小区旁边是文化公园,吃过饭去转转,一条街划出了一个夜市,人头攒动有卖各种水果的小贩。
母亲提到这边晚上虽然热闹,但是不知道什么路灯给人昏暗的感觉。我走在街上确实也有这样的感觉。每个摊贩都准备了自己的台灯,照亮着摊位。
听母亲介绍我还去了一下当地的昌江黎族自治县宣传文化中心。当地的刊物《昌江文艺》:
其中有一篇《铁城旧事》作者“林元康”,简单介绍了石碌的历史。“在清代乾隆嘉年间,此地采铜,铜矿石氧化后变绿,而绿与碌同音,因此得名。”文化和经济是密切相关的,能出版这样的刊物从侧面也可以证明当地的经济水平应该不错。
我的父亲是一名文字爱好者,他的专业是地质测绘,但是参加工作没多久就转去做宣传口,也算是得偿所愿。之前我并不理解,因为在某些意义上来说Propaganda是一个带有贬义的词语。但是随着年纪的增长,我逐渐意识到,这样工作的意义在于构建一个想象的共同体增加凝聚力,是现实文化的一部分,甚至意味着重新定义规则。比如,韩国人注册了“端午节”,当中国人质疑的时候他们就会说申请的是当地的“端午节”。但这个并不会影响他们会对外介绍这是正宗“端午节”,让外国人将这个节日和韩国联系在一起。再比如,在韩国人和越南人的支持下,越来越多的国家使用 “Lunar New Year”来替代“Chinese New Year” 的称呼,更可以看作是以削弱中国文化影响力为目标的行为。
不过想想,对于韩国人来说,这样的操作并不意外:
这个设备是VGA 转USB 的采集卡, 对应的软件和驱动是放在一起的。
来自:
https://www.epiphan.com/support/vga2usb-software-documentation/
本文来自 https://igor-blue.github.io/2021/02/04/secure-boot.html
上电后首先启动的是PMC,它是位于SoC 内部的一个 IP。PMC 内部有一个 ROM,存放了一些初始化代码(这部分代码是SoC制造时固化在里面的),上电之后就开始运行。这个阶段SoC其余部分处于 RESET状态。
接下来CSME 开始运行,CSME 运行的固件可以看作是两部分,一部分是ROM,同样的,其中代码是SoC制造时固化在里面的。比如:客户的一些检验Key; 另外一部分存放在 SPINOR上。CSME 本身运行在一个 Intel 486 的CPU上,它位于 PCH中。在这个过程中 CSME 还会检查当前是否处于调试模式下。如果当前调试模式打开,它会进行特别处理(我猜测是进行诸如XDP这种)。CSME 的 SRAM 也是在这个过程初始化的。
接下来CSME 的 DMA 初始化完成,进入 Rom Boot Extension(缩写 RBE)中。在这个过程中 CSME 会通过 SPI 总线读取主板 SPINOR中的内容。同时使用Key 进行校验。比如,A公司有自己的公钥和私钥。A公司可以将密钥交给Intel,在生产的时候会将对应的公钥写入CSME ROM中。A公司在Build BIOS时通过私钥对BIOS进行签名,打开Boot Guard功能,上电过程中CSME即可使用CSME ROM 中的公钥对SPI NOR中的 IFWI进行校验。如果启动时发现IFWI 并非他们自家制作,那么就不会启动。具体来说,用户会看到一会上电一会又下电的现象。也是因为这个原因所以在 Power On 新板子的时候,务必关闭 这个功能,避免由于签名问题耽误时间。(上述描述中对于密钥描述并不准确,只是简单使用概念)。读取到 SPI NOR 中的内容后,CSME 专用的CPU 就开始加载运行其中的代码了。
当 CSME 加载起来它内部的OS 之后,就开始执行”IBL processes”。CSME 提供的加密、TPM 等等功能也在这个阶段开始。
最终 CSME 完成了它的初始化,接下来从 Reset 模式下释放CPU (PLTRST),CPU开始执行 BIOS 代码。
广大工矿企业在日常生产生活中,经常会遇到需要虚拟键盘和鼠标的场景。通常的解决方法是使用软件进行模拟。但是软件模拟经常会遇到安全软件误杀等等情况。为了解决这种问题,这次带来的制作是一个于 CH552开发的虚拟键盘鼠标项目。它是基于CH554 Arduino 环境开发的设备,插上之后,系统 中会出现一个 USB 串口,一个USB 键盘,一个USB 鼠标,我们将数据从串口送进设备,然后设备将收到的串口数据直接转发到键盘鼠标对应的端口上,从而实现鼠标键盘操作。
首先进行硬件的设计,电路图如下:
图片中上方是一个 CH554 的最小电路图,他是WCH 出品的一款兼容MCS51 指令集的增强型E8051内核单片机。带有256 字节内部iRAM,可以用于快速数据暂存以及堆栈;1KB 片内xRAM,可以用于大量数据暂存以及DMA直接内存存取。16KB 容量的可多次编程的非易失存储器ROM,可以全部用于程序存储空间;或者可以分为14KB 程序存储区和2KB引导代码BootLoader/ISP程序区。更特别的是其内嵌USB 控制器和USB 收发器,支持USB-Host 主机模式和USB-Device 设备模式,支持USB type-C主从检测,支持USB 2.0全速12Mbps或者低速1.5Mbps。支持最大64字节数据包,内置FIFO,支持DMA。
这里使用的型号为CH554E , MSOP-10 封装,体积非常小便于整体设备小型化。
为了调试方便,预留了P2 是UART输出。此外,还有一个 WS2812B LED 可以实现多种颜色的灯效。
PCB 设计如下:
3D 预览如下
图片3 正面预览
在实际使用中,只需要焊接CH554最小系统部分即可实现USB 串口转键盘鼠标功能,其余部分可以不上件。
这次的设计尺寸是根据透明U盘外壳来的,在制作时选择 0.8mm PCB, 刚好能够放入外壳中。
硬件设计完成之后就可以进行代码的编写了。主要代码如下:
#include <WS2812.h>
#include "src/CdcHidCombo/USBCDC.h"
#include "src/CdcHidCombo/USBHIDKeyboardMouse.h"
#define NUM_LEDS 1
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)
__xdata uint8_t ledData[NUM_BYTES];
#define KeyboardReportID 0x01
#define MouseReportID 0x02
#define OnBoardLED 0x03
// Data format
// Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
// Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
uint8_t recvStr[9];
uint8_t recvStrPtr = 0;
unsigned long Elsp;
void setup() {
USBInit();
Serial0_begin(115200);
delay(1000);
Serial0_print("start");
Elsp=0;
}
void loop() {
while (USBSerial_available()) {
uint8_t serialChar = USBSerial_read();
recvStr[recvStrPtr++] = serialChar;
if (recvStrPtr == 10) {
for (uint8_t i = 0; i < 9; i++) {
Serial0_write(recvStr[i]);
}
if (recvStr[0] == KeyboardReportID) { // Keyboard
USB_EP3_send(recvStr, 9);
}
if (recvStr[0] == MouseReportID) {
USB_EP3_send(recvStr, 5); // Mouse
}
if (recvStr[0] == OnBoardLED) {
set_pixel_for_GRB_LED(ledData, 0, recvStr[0], recvStr[1], recvStr[2]);
neopixel_show_P1_5(ledData, NUM_BYTES);
}
recvStrPtr = 0;
}
Elsp=millis();
}
// If there is no data in 100ms, clear the receive buffer
if (millis()-Elsp>100) {
recvStrPtr = 0;
Elsp=millis();
}
}
每次收取10字节串口数据,如果第一字节为KeyboardReportID,那么直接将9个字节从端点3发送给主机;如果第一字节是MouseReportID,那么直接将5个字节从端点3发送给主机;如果第一字节为 OnBoardLED ,那么将后续3个字节合成为一个颜色信息,然后通过neopixel_show_P1_5() 函数将数据从 P1.5 引脚发送给WS2812。
USB 设备信息在USBconstant.c 文件中有描述。其中包含了 USB CDC 设备/USB 键盘/USB鼠标的描述符。其中的USB 键盘/USB鼠标使用端点3 OUTPUT。
对于键盘鼠标的 HID 描述符,在USBconstant.c文件的ReportDescriptor[]的结构体中。使用的是标准的键盘鼠标描述符。
键盘数据为8字节长:
Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
特殊按键 | NA | 数据0 | 数据1 | 数据2 | 数据3 | 数据4 | 数据5 |
鼠标数据为4字节长:
Byte0 | Byte1 | Byte2 | Byte3 |
左右中键 | X轴数据 | Y轴数据 | 滚轮数据 |
需要特别注意的是:键盘鼠标都是通过端点3发送给主机的,他们使用 Report ID 进行区分,在 ReportDescriptor[] 中有如下定义:
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
…….
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x85, 0x02, // REPORT_ID (2)
0x05, 0x09, // USAGE_PAGE (Button)
就是说,设备通过端点3发送给主机的数据,如果是 0x01 + 8个字节的数据,主机会当作键盘数据来处理;如果是0x02+4个字节的数据,主机则会当作鼠标数据来处理。
上面的代码中,USB串口,收到数据之后,将9字节和5字节发送给主机即可实现键盘和鼠标的操作了。
端点3的发送代码在 USB_EP3_send() 函数中,可以看到对于 CH554 来说,USB的发送操作非常明确简单,要发送的数据直接填充到Ep3Buffer[]中,然后设定 UEP3_T_LEN 寄存器要发送的数据长度,再设定 UEP3_CTRL 寄存器就完成了了发送。这对于USB初学者非常友好,代码直接对应硬件动作,通俗易懂。
// 将所有数据放入 EP3 准备发送
for (__data uint8_t i = 0; i < Len; i++) { // load data for upload
Ep3Buffer[i] = Data[i];
}
UEP3_T_LEN = Len; // data length
UpPoint3_Busy = 1;
UEP3_CTRL = UEP3_CTRL & ~MASK_UEP_T_RES |
UEP_T_RES_ACK; // upload data and respond ACK
上位机代码:
// CDC_vKBMSTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <windows.h>
#include <SetupAPI.h>
#include <tchar.h>
#include <iostream>
#include <cstring>
#include <atlstr.h>
#pragma comment(lib, "Setupapi.lib")
#define MY_USB_PID_VID _T("VID_1209&PID_C55C")
class ComPortException : public std::exception {
public:
ComPortException(DWORD errorCode) : errorCode(errorCode) {}
DWORD getErrorCode() const {
return errorCode;
}
private:
DWORD errorCode;
};
// 对串口portName 发送数据
// 无法打开串口返回 1
// 无法设置串口参数 2
void SendToComPort(const int port, const char* data) {
int Result = 0;
HANDLE hCom = INVALID_HANDLE_VALUE;
TCHAR portName[10]; // 用于存储 "COMp" 字符串
// 将整数 p 转换为 "COMp" 字符串
#ifdef UNICODE
swprintf(portName, 10, _T("\\\\.\\COM%d"), port);
#else
sprintf(portName, "COM%d", port);
#endif
try {
// 打开串口
hCom = CreateFile(portName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if (hCom == INVALID_HANDLE_VALUE) {
throw ComPortException(GetLastError());
}
// 设置串口参数
DCB dcb;
SecureZeroMemory(&dcb, sizeof(DCB));
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(hCom, &dcb)) {
throw ComPortException(GetLastError());
}
dcb.BaudRate = CBR_115200; // 波特率
dcb.ByteSize = 8; // 数据位
dcb.StopBits = ONESTOPBIT; // 停止位
dcb.Parity = NOPARITY; // 校验位
if (!SetCommState(hCom, &dcb)) {
throw ComPortException(GetLastError());
}
// 设置超时参数
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hCom, &timeouts)) {
throw ComPortException(GetLastError());
}
// 发送数据
DWORD bytesWritten;
if (!WriteFile(hCom, data, 10, &bytesWritten, NULL)) {
std::cerr << "Failed to write to COM port." << std::endl;
}
else {
std::cout << "Successfully sent data to COM port: [" << port << "]" << std::endl;
}
// 关闭串口
if (hCom != INVALID_HANDLE_VALUE) {
CloseHandle(hCom);
}
}
catch (const ComPortException& ex) {
std::cerr << "Error: " << ex.getErrorCode() << std::endl;
if (hCom != INVALID_HANDLE_VALUE) {
CloseHandle(hCom);
}
}
}
/************************************************************************/
/* 根据USB描述信息字符串中读取
/************************************************************************/
int MTGetPortFromVidPid(CString strVidPid)
{
// 获取当前系统所有使用的设备
int nPort = -1;
int nStart = -1;
int nEnd = -1;
int i = 0;
CString strTemp, strName;
DWORD dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
HDEVINFO hDevInfo = INVALID_HANDLE_VALUE;
SP_DEVINFO_DATA sDevInfoData;
TCHAR szDis[2048] = { 0x00 };// 存储设备实例ID
TCHAR szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
DWORD nSize = 0;
// 准备遍历所有设备查找USB
hDevInfo = SetupDiGetClassDevs(NULL, L"USB", NULL, dwFlag);
if (INVALID_HANDLE_VALUE == hDevInfo)
goto STEP_END;
// 开始遍历所有设备
memset(&sDevInfoData, 0x00, sizeof(SP_DEVICE_INTERFACE_DATA));
sDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
{
nSize = 0;
// 无效设备
if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
goto STEP_END;
// 根据设备信息寻找VID PID一致的设备
strTemp.Format(_T("%s"), szDis);
strTemp.MakeUpper();
if (strTemp.Find(strVidPid, 0) == -1)
continue;
// 查找设备属性
nSize = 0;
SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
SPDRP_FRIENDLYNAME,
0, (PBYTE)szFN,
sizeof(szFN),
&nSize);
// "XXX Virtual Com Port (COM7)"
strName.Format(_T("%s"), szFN);
if (strName.IsEmpty())
//goto STEP_END;
continue;
// 寻找串口信息
nStart = strName.Find(_T("(COM"), 0);
nEnd = strName.Find(_T(")"), 0);
if (nStart == -1 || nEnd == -1)
//goto STEP_END;
continue;
strTemp = strName.Mid(nStart + 4, nEnd - nStart - 2);
nPort = _ttoi(strTemp);
}
STEP_END:
// 关闭设备信息集句柄
if (hDevInfo != INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(hDevInfo);
hDevInfo = INVALID_HANDLE_VALUE;
}
return nPort;
}
int main()
{
int Port;
char Data[10];
printf("Virutal KB MS Test\n");
Port = MTGetPortFromVidPid(MY_USB_PID_VID);
if (Port == -1) {
printf("No device is found\n");
goto EndProgram;
}
else {
printf("Found COM%d\n",Port);
}
Sleep(5000);
// 测试鼠标移动
memset(Data,0,sizeof(Data));
Data[0] = 2; // 鼠标
Data[2] = 50;
SendToComPort(Port,(char *)&Data);
Sleep(300);
Data[2] = 0; Data[3] = 50;
SendToComPort(Port, (char*)&Data);
Sleep(300);
Data[2] = -50; Data[3] = 0x00;
SendToComPort(Port, (char*)&Data);
Sleep(300);
Data[2] = 0; Data[3] = -50;
SendToComPort(Port, (char*)&Data);
Sleep(300);
// 测试键盘数据
memset(Data, 0, sizeof(Data));
Data[0] = 1; // 键盘
// 发送GUI信息
Data[1] = 0x08;
SendToComPort(Port, (char*)&Data);
Sleep(1000);
memset(Data, 0, sizeof(Data));
Data[0] = 1; // 键盘
SendToComPort(Port, (char*)&Data);
Sleep(300);
// 测试键盘数据
memset(Data, 0, sizeof(Data));
Data[0] = 1; // 键盘
// 发送按键信息
Data[2] = 0x04;
Data[3] = 0x0F; //'l'
Data[4] = 0x04; //'a '
Data[5] = 0x05; //'b'
Data[6] = 0x1d; //'z'
SendToComPort(Port, (char*)&Data);
Sleep(300);
memset(Data, 0, sizeof(Data));
Data[0] = 1; // 键盘
// 抬起按键
SendToComPort(Port, (char*)&Data);
Sleep(300);
// 测试LED
memset(Data, 0, sizeof(Data));
Data[0] = 3; // LED
Data[1] = 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(500);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // LED
Data[2] = 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(500);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // LED
Data[3] = 0xFF;
SendToComPort(Port, (char*)&Data);
Sleep(500);
memset(Data, 0, sizeof(Data));
Data[0] = 3; // LED
SendToComPort(Port, (char*)&Data);
Sleep(500);
EndProgram:
printf("End\n");
}
工作的测试视频在
电路图:
Arduino代码:
VC2019工程