故乡的水塔

前一段看新闻,忽然意识到现在生活的城市已经很少见到这样的庞然巨物了。

我是出生和成长在黑龙江大庆市的。“大庆”这个城市名称始终和“工业学大庆”这个口号联系在一起的,同时和“大油田”深深的绑定。小时候有一段时间,我非常反感有文章用“小油苗”来称呼本地的少年儿童。甚至深深的怀疑第一个编造出这个称呼的人是没有见过“原油”的。那是一种黏糊糊散发着异常味道的物质,稍微有一些化学知识的人都会了解其中含有大量的“苯”“烃””烯“致癌成分,更不会有人产生可爱的联想。

长大之后,我去成都求学,在那里也曾经看到过很多水塔,通常都是下面飞盘造型,蓝白相间的颜色在阴郁的天空下会显得无比深沉。

大庆的水塔没有如此的艺术感,完全像一个木柄手榴弹,外立面不会有任何的装饰,赤裸着红砖努力展现着质朴和实用的主题。估计建设之初一定是本着“又不是不能用”的原则。

这样的水塔下方通常都是机房,靠近能听到其中机器的嘶吼,电力驱动的泵机将水送到顶层,用于维持这一片区供水压力。小时候站在下面,仰望水塔,会有一阵阵的眩晕感,高大的水塔仿佛随时会扑下来的巨人一般。

很长一段时间,大庆的居民楼,甚至是学校都是统一造型的。比如,下面这种样式就是典型的居民楼。从飞机上看下去都是类似的火柴盒。若干年后,我大约知道这种楼被称作“赫鲁晓夫楼”,是一种风格统一的工业化设计,能够在短期内满足改善当地人居住条件的建筑。

更早期大庆的典型住宅是被称作“干打垒”的房子。在东北,保暖性能是第一需要考虑的问题,冬季最冷的时候会达到零下三四十度。只有足够厚度的墙体才能抵御外部的寒冷。干打垒有很大一部分处于地以下,这样能够显著的节省建筑材料。也是因为这样的原因,听说早年间遇到特别大的雪天,早晨门会被积雪掩盖,通常需要从窗口跳出去,铲开门口的雪才能打开房门。

所有的学校也是相同的造型【参考1】。这是我初中的学校,之前原名是大庆市第二十四中学,原来自己独占一个大院的,后来挤进来五十六中学,再后来二者合并改名为“三十六中学”。

参考:

1.https://baike.baidu.com/item/%E5%A4%A7%E5%BA%86%E5%B8%82%E7%AC%AC%E4%B8%89%E5%8D%81%E5%85%AD%E4%B8%AD%E5%AD%A6/2428328

自制Windows 11精简版

先说最后制作出来的结果:镜像文件2.78GB.虚拟机中安装后内存占用1.6GB,硬盘占用 10GB

接下来介绍制作方法:

  1. 项目是来自https://github.com/ntdevlabs/tiny11builder , 项目通过运行脚本文件来提供Windows 11安装镜像中的文件再重新生成一个安装 ISO
  2. 运行方法是首先挂载一个 Windows 11安装镜像,比如,这里我挂接到 f:
  3. 以管理员权限打开Windows Power  Shell 首先运行如下命令
Set-ExecutionPolicy unrestricted

4.运行 tiny11Coremaker.ps1,这个过程中会要求你选择制作的类型,推荐选择  Windows 11 pro

5.接下来等待即可,结束之后会有提示

原来的项目中会需要联网下载 oscdimg.exe 文件的,这里我直接都打包在一起,可以离线使用。

Windows Service 服务框架例子

这里提供一个Service 的例子,实现对c:\log.txt 每隔一段写入当前时间。


// MyService.cpp : 定义控制台应用程序的入口点。
//


#include <Windows.h>
#include <tchar.h>
#include <iostream>

using namespace std;

/*
BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);
TCHAR szServiceName[] = _T("MyService");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;
SC_HANDLE hSCM;
SC_HANDLE hService;
*/

/*
OpenSCManager 用于打开服务控制管理器;
CreateService 用于创建服务;
OpenService用于打开已有的服务,返回该服务的句柄;
ControlService则用于控制已打开的服务状态,这里是让服务停止后才删除;
DeleteService 用于删除指定服务。
RegisterServiceCtrlHandler 注册服务控制
*/

//定义全局函数变量  
void Init();
BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);

TCHAR szServiceName[] = _T("MyService");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;

int APIENTRY _tWinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPTSTR    lpCmdLine,
	int       nCmdShow)
{
	Init();
	dwThreadID = ::GetCurrentThreadId();
	SERVICE_TABLE_ENTRY st[] =
	{
		{ szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
		{ NULL, NULL }
	};

	if (_tcscmp(lpCmdLine, _T("/install")) == 0)
	{
		Install();
	}
	else if (_tcscmp(lpCmdLine, _T("/uninstall")) == 0)
	{
		Uninstall();
	}
	else
	{
		if (!::StartServiceCtrlDispatcher(st))
		{
			LogEvent(_T("Register Service Main Function Error!"));
		}
	}

	return 0;
}

//初始化
void Init()
{
	hServiceStatus = NULL;
	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
	status.dwCurrentState = SERVICE_START_PENDING;
	status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	status.dwWin32ExitCode = 0;
	status.dwServiceSpecificExitCode = 0;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
}

//服务主函数,这在里进行控制对服务控制的注册
void WINAPI ServiceMain()
{
	status.dwCurrentState = SERVICE_START_PENDING;
	status.dwControlsAccepted = SERVICE_ACCEPT_STOP;

	//注册服务控制  
	hServiceStatus = RegisterServiceCtrlHandler(szServiceName, ServiceStrl);
	if (hServiceStatus == NULL)
	{
		LogEvent(_T("Handler not installed"));
		return;
	}
	SetServiceStatus(hServiceStatus, &status);

	status.dwWin32ExitCode = S_OK;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
	status.dwCurrentState = SERVICE_RUNNING;
	SetServiceStatus(hServiceStatus, &status);

	//模拟服务的运行。应用时将主要任务放于此即可  
	//可在此写上服务需要执行的代码,一般为死循环  
	while (1)
	{
		FILE* p=NULL;
		errno_t  err =_tfopen_s(&p,_T("c:\\log.txt"), _T("ab+"));
		if (err != 0) {
			TCHAR errMsg[256];
			_wcserror_s(errMsg, 256, err);
			_tprintf(_T("err! %s"), errMsg);
			return ;
		}
		SYSTEMTIME st;
		GetSystemTime(&st);
		TCHAR time[100] = { 0 };
		_stprintf_s(time, 100, _T("%4d-%02d-%02d %02d:%02d:%02d\r\n"), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
		if (p == NULL) {
			return;
		}
		fwrite(time, sizeof(TCHAR), _tcsclen(time), p);
		fclose(p);

		Sleep(5000);
	}
	status.dwCurrentState = SERVICE_STOPPED;
	SetServiceStatus(hServiceStatus, &status);
}

//Description:          服务控制主函数,这里实现对服务的控制,  
//                      当在服务管理器上停止或其它操作时,将会运行此处代码  
void WINAPI ServiceStrl(DWORD dwOpcode)
{
	switch (dwOpcode)
	{
	case SERVICE_CONTROL_STOP:
		status.dwCheckPoint = 1;
		status.dwCurrentState = SERVICE_STOP_PENDING;
		SetServiceStatus(hServiceStatus, &status);
		Sleep(500);
		status.dwCheckPoint = 0;
		status.dwCurrentState = SERVICE_STOPPED;
		SetServiceStatus(hServiceStatus, &status);

		PostThreadMessage(dwThreadID, WM_CLOSE, 0, 0);
		break;
	case SERVICE_CONTROL_PAUSE:
		break;
	case SERVICE_CONTROL_CONTINUE:
		break;
	case SERVICE_CONTROL_INTERROGATE:
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		exit(0);
		break;
	default:
		LogEvent(_T("Bad service request"));
	}
}

//判断服务是否已经被安装
BOOL IsInstalled()
{ 
	BOOL bResult = FALSE;

	//打开服务控制管理器  
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

	if (hSCM != NULL)
	{
		//打开服务  
		SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_QUERY_CONFIG);
		if (hService != NULL)
		{
			bResult = TRUE;
			::CloseServiceHandle(hService);
		}
		::CloseServiceHandle(hSCM);
	}
	return bResult;
}

//安装服务函数
BOOL Install()
{
	//检测是否安装过
	if (IsInstalled())
		return TRUE;

	//打开服务控制管理器  
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCM == NULL)
	{
		MessageBox(NULL, _T("Couldn't open service manager"), szServiceName, MB_OK);
		return FALSE;
	}

	//获取程序目录
	TCHAR szFilePath[MAX_PATH];
	::GetModuleFileName(NULL, szFilePath, MAX_PATH);

	//创建服务  
	SC_HANDLE hService = ::CreateService(hSCM, szServiceName, szServiceName,
		SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
		szFilePath, NULL, NULL, _T(""), NULL, NULL);

	//检测创建是否成功
	if (hService == NULL)
	{
		::CloseServiceHandle(hSCM);
		MessageBox(NULL, _T("Couldn't create service"), szServiceName, MB_OK);
		return FALSE;
	}

	//释放资源
	::CloseServiceHandle(hService);
	::CloseServiceHandle(hSCM);
	return TRUE;
}

//删除服务函数
BOOL Uninstall()
{
	//检测是否安装过
	if (!IsInstalled())
		return TRUE;

	//打开服务控制管理器
	SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCM == NULL)
	{
		MessageBox(NULL, _T("Couldn't open service manager"), szServiceName, MB_OK);
		return FALSE;
	}

	//打开具体服务
	SC_HANDLE hService = ::OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);
	if (hService == NULL)
	{
		::CloseServiceHandle(hSCM);
		MessageBox(NULL, _T("Couldn't open service"), szServiceName, MB_OK);
		return FALSE;
	}

	//先停止服务
	SERVICE_STATUS status;
	::ControlService(hService, SERVICE_CONTROL_STOP, &status);

	//删除服务  
	BOOL bDelete = ::DeleteService(hService);
	::CloseServiceHandle(hService);
	::CloseServiceHandle(hSCM);

	if (bDelete)  return TRUE;
	LogEvent(_T("Service could not be deleted"));
	return FALSE;
}

//记录服务事件
void LogEvent(LPCTSTR pFormat, ...)
{
	TCHAR    chMsg[256];
	HANDLE  hEventSource;
	LPTSTR  lpszStrings[1];
	va_list pArg;

	va_start(pArg, pFormat);
	_vsntprintf_s(chMsg, sizeof(chMsg),_TRUNCATE,pFormat, pArg);
	va_end(pArg);

	lpszStrings[0] = chMsg;

	hEventSource = RegisterEventSource(NULL, szServiceName);
	if (hEventSource != NULL)
	{
		ReportEvent(hEventSource, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, (LPCTSTR*)&lpszStrings[0], NULL);
		DeregisterEventSource(hEventSource);
	}
}

完整代码:

参考:

1.https://cloud.tencent.com/developer/article/1857390

2https://learn.microsoft.com/zh-cn/windows/win32/services/writing-a-service-program-s-main-function

3.https://blog.csdn.net/hsy12342611/article/details/133557759

招商价值成长混合C 投资盘点

2021 年8月买了招商价值成长混合C (012004) 8万元,2024年10月抛了,剩下 52000,亏了35%。

 好奇心驱使下,查了一下资料:https://finance.sina.com.cn/fund/quotes/012004/bc.shtml,这个基金几乎从开始就一直在亏损:

基金经理是 郭锐,盈利能力 74% 的意思是超过 74% 的基金经理。

关于他的公开信息在 https://stock.finance.sina.com.cn/manager/view/mInfo.php?mid=30176884,看历史业绩(不知道为什么没有列出来我买的这支),2020年之后的基本上都在亏钱。

结论:基金有风险,投资需谨慎。投资之前最好看看以往业绩。

PVD:Battery 虚拟电池

前面的 PVD(Physical Virtual Device)设计过普通鼠标,绝对值鼠标, 这次带来的是一个虚拟电池的设计。在进行功耗和性能测试的时候,电池状态(AC/DC)对于Windows性能释放有着很大的影响。因此需要有手段来虚拟电池,之前我设计过2款虚拟电池软件的,但是这种软件是通过驱动来实现的,在具体使用时会有很大局限性。

这次带来的是使用 CH554模拟的USB HID 设备,它将自身报告为一个 UPS 设备,然后通过 USB 接口将当前电池信息报告给 Windows。代码是 Arduino 写成的,通俗易懂,只需要有 USB 知识就可以掌握。整体框架来自另外一个基于 Leonardo 的Arduino 项目。

硬件部分非常简单,就是一个 CH554e的最小系统(MSOP10)封装,非常适于制作小型设备。

#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"
#include "src/CdcHidCombo/PowerDevice.h"
#include "src/CdcHidCombo/USBconstant.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 MINUPDATEINTERVAL   26000UL
#define OnBoardLED       0x03

const byte bDeviceChemistry = IDEVICECHEMISTRY;
const byte bOEMVendor = IOEMVENDOR;

uint16_t iPresentStatus = 0, iPreviousStatus = 0;

byte bRechargable = 1;
byte bCapacityMode = 0;  // units are in mWh

// Physical parameters
const uint16_t iConfigVoltage = 1380;
uint16_t iVoltage = 1300, iPrevVoltage = 0;
uint16_t iRunTimeToEmpty = 0, iPrevRunTimeToEmpty = 0;
uint16_t iAvgTimeToFull = 7200;
uint16_t iAvgTimeToEmpty = 7200;
uint16_t iRemainTimeLimit = 600;
int16_t  iDelayBe4Reboot = -1;
int16_t  iDelayBe4ShutDown = -1;

byte iAudibleAlarmCtrl = 2; // 1 - Disabled, 2 - Enabled, 3 - Muted


// Parameters for ACPI compliancy
uint8_t iDesignCapacity = 0xFF;
byte iWarnCapacityLimit = 10; // warning at 10%
byte iRemnCapacityLimit = 5; // low at 5%
const byte bCapacityGranularity1 = 1;
const byte bCapacityGranularity2 = 1;
uint8_t iFullChargeCapacity = 0xFF;

uint8_t iRemaining = 0xFF, iPrevRemaining = 0;

int iRes = 0;
unsigned long iIntTimer=0;
// 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;

uint8_t FeatureBuffer[256];
FeatureType FeatureList[32];
uint8_t FeatureRecord = 0;
uint16_t iManufacturerDate = 0,bCycles=20;

void setFeature(uint8_t id, uint8_t* Data, int Len)
{
  /*
      Serial0_print("ID:");
      Serial0_print(id);
      Serial0_print_c(' ');
      Serial0_print(Data[0]);
      Serial0_print_c(' ');
      if (Len>1) {
          Serial0_print(Data[1]);
          Serial0_print_c(' ');
        }
      Serial0_print(Len);
      Serial0_println_c(' ');
  */
  FeatureList[id].Index = FeatureRecord;
  FeatureList[id].Size = Len;
  for (uint8_t i = 0; i < Len; i++) {
    FeatureBuffer[FeatureRecord] = Data[i];
    FeatureRecord++;
  }
}

void setup() {
  Serial0_begin(500000);
  delay(1000);
  Serial0_println("st");
  uint8_t strIndex;
  strIndex = 5;
  setFeature(HID_PD_IPRODUCT, &strIndex, sizeof(strIndex));
  strIndex = 6;
  setFeature(HID_PD_MANUFACTURER, &strIndex, sizeof(strIndex));
  strIndex = 7;
  setFeature(HID_PD_SERIAL, &strIndex, sizeof(strIndex));
  strIndex = 8;
  setFeature(HID_PD_IDEVICECHEMISTRY, &strIndex, sizeof(strIndex));

  setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));

  setFeature(HID_PD_RUNTIMETOEMPTY, &iRunTimeToEmpty, sizeof(iRunTimeToEmpty));
  setFeature(HID_PD_AVERAGETIME2FULL, &iAvgTimeToFull, sizeof(iAvgTimeToFull));
  setFeature(HID_PD_AVERAGETIME2EMPTY, &iAvgTimeToEmpty, sizeof(iAvgTimeToEmpty));
  setFeature(HID_PD_REMAINTIMELIMIT, &iRemainTimeLimit, sizeof(iRemainTimeLimit));
  setFeature(HID_PD_DELAYBE4REBOOT, &iDelayBe4Reboot, sizeof(iDelayBe4Reboot));
  setFeature(HID_PD_DELAYBE4SHUTDOWN, &iDelayBe4ShutDown, sizeof(iDelayBe4ShutDown));

  setFeature(HID_PD_RECHARGEABLE, &bRechargable, sizeof(bRechargable));
  setFeature(HID_PD_CAPACITYMODE, &bCapacityMode, sizeof(bCapacityMode));
  setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
  setFeature(HID_PD_VOLTAGE, &iVoltage, sizeof(iVoltage));

  setFeature(HID_PD_IOEMINFORMATION, &bOEMVendor, sizeof(bOEMVendor));

  setFeature(HID_PD_AUDIBLEALARMCTRL, &iAudibleAlarmCtrl, sizeof(iAudibleAlarmCtrl));

  setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));
  setFeature(HID_PD_FULLCHRGECAPACITY, &iFullChargeCapacity, sizeof(iFullChargeCapacity));
  setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
  setFeature(HID_PD_WARNCAPACITYLIMIT, &iWarnCapacityLimit, sizeof(iWarnCapacityLimit));
  setFeature(HID_PD_REMNCAPACITYLIMIT, &iRemnCapacityLimit, sizeof(iRemnCapacityLimit));
  setFeature(HID_PD_CPCTYGRANULARITY1, &bCapacityGranularity1, sizeof(bCapacityGranularity1));
  setFeature(HID_PD_CPCTYGRANULARITY2, &bCapacityGranularity2, sizeof(bCapacityGranularity2));
  setFeature(HID_PD_CYCLECOUNT,&bCycles,sizeof(bCycles));
  setFeature(HID_PD_CONFIGVOLTAGE, &iConfigVoltage, sizeof(iConfigVoltage));
  iManufacturerDate = (2025 - 1980) * 512 + 1 * 32 + 1;
  setFeature(HID_PD_MANUFACTUREDATE, &iManufacturerDate, sizeof(iManufacturerDate));
  /*
    for (uint8_t i=0;i<32;i++) {
      FeatureList[i].Index=i;
      FeatureList[i].Size=1;
      FeatureBuffer[i]=i;
    }
    for (uint8_t i=0;i<32;i++) {
        Serial0_print(i);
        Serial0_print_c(' ');
        Serial0_print(FeatureList[i].Index);
        Serial0_print_c(' ');
        Serial0_print(FeatureList[i].Size);
        Serial0_println_c(' ');
      }
    for (uint8_t i=0;i<FeatureRecord;i++) {
        Serial0_print(FeatureBuffer[i]); Serial0_print_c(' ');
      }
  */
  USBInit();

  bitSet(iPresentStatus, PRESENTSTATUS_CHARGING);
  bitSet(iPresentStatus, PRESENTSTATUS_ACPRESENT);
  bitSet(iPresentStatus , PRESENTSTATUS_BATTPRESENT);
  bitSet(iPresentStatus , PRESENTSTATUS_PRIMARYBATTERY);
  recvStr[0] = HID_PD_PRESENTSTATUS;
  recvStr[1] = iPresentStatus & 0xFF;
  recvStr[2] = (iPresentStatus >> 8) & 0xFF;;
  USB_EP3_send(recvStr, 3);
  setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));

/*
  iRemaining = 40;
  recvStr[0] = HID_PD_REMAININGCAPACITY;
  recvStr[1] = iRemaining & 0xFF;
  USB_EP3_send(recvStr, 2);
  setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
  */
}

void loop() {
  while (USBSerial_available()) {
    uint8_t serialChar = USBSerial_read();
    recvStr[recvStrPtr++] = serialChar;
    if (recvStrPtr == 4) {
      /*
        for (uint8_t i = 0; i < 9; i++) {
        Serial0_write(recvStr[i]);
        }
      */


      if (recvStr[0] == HID_PD_PRESENTSTATUS) {
        //USB_EP3_send(recvStr, 3);
        iPresentStatus=recvStr[1]+(recvStr[2]<<8);
        Serial0_print("ps:");
        Serial0_println(iPresentStatus);
      }
      if (recvStr[0] == HID_PD_REMAININGCAPACITY) {
        iRemaining=recvStr[1];
        Serial0_print("rm:");
        Serial0_println(iRemaining);
      }
      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();
  }

  if((iPresentStatus != iPreviousStatus) || (iRemaining!=iPrevRemaining) || (millis()-iIntTimer>MINUPDATEINTERVAL) ) {
    recvStr[0]=HID_PD_REMAININGCAPACITY;
    recvStr[1]=iRemaining;
    USB_EP3_send(recvStr, 2);
    setFeature(HID_PD_REMAININGCAPACITY, &iRemaining, sizeof(iRemaining));
      
    recvStr[0]=HID_PD_PRESENTSTATUS;
    recvStr[1]=iPresentStatus&0xFF;
    recvStr[2]=(iPresentStatus>>8)&0xFF;
    USB_EP3_send(recvStr, 3);
    setFeature(HID_PD_PRESENTSTATUS, &iPresentStatus, sizeof(iPresentStatus));
    
    Serial0_println("a:");

    iPreviousStatus=iPresentStatus;
    iPrevRemaining=iRemaining;
    iIntTimer=millis();
  }
}

简单的说,开始之后,通过 HID Descriptor 报告当前设备属性,其中有很多 Feature项目。之后 Arduino 代码通过下面这种将描述符中的 Report ID 和 数值关联起来。后面,当Ch554收到 Feature Request 之后就根据前面的注册信息返回对应值。

  setFeature(HID_PD_DESIGNCAPACITY, &iDesignCapacity, sizeof(iDesignCapacity));

除了USB HID 设备,Ch554还实现了一个 USB CDC 设备,在 loop 中我们接收来自USB 串口的数据,如果是以HID_PD_PRESENTSTATUS 开头的,或者HID_PD_REMAININGCAPACITY开头的,那么直接更改状态,然后从HID 对应的 EndPoint中发送出去,这样 Windows 接收到后会更新电池状态。

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

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            string targetVid = "VID_1209";
            string targetPid = "PID_C55C";
            string portName = FindUsbDevicePort(targetVid, targetPid);

            if (!string.IsNullOrEmpty(portName))
            {
                Console.WriteLine($"Found device on port: {portName}");
            }
            else
            {
                Console.WriteLine("Device not found.");
            }
            Console.ReadKey();
        }

        static string FindUsbDevicePort(string vid, string pid)
        {
            string query = "SELECT * FROM Win32_PnPEntity WHERE DeviceID LIKE '%" + vid + "&" + pid + "%'";
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
            {
                foreach (ManagementObject device in searcher.Get())
                {
                    string deviceId = device["DeviceID"]?.ToString();
                    if (deviceId != null && deviceId.Contains(vid) && deviceId.Contains(pid))
                    {
                        string caption = device["Caption"]?.ToString();
                        if (caption != null && caption.Contains("(COM"))
                        {
                            int startIndex = caption.IndexOf("(COM") + 1;
                            int endIndex = caption.IndexOf(")", startIndex);
                            return caption.Substring(startIndex, endIndex - startIndex);
                        }
                    }
                }
            }
            return null;
        }
    }
}

完整的 Arduino 代码:

完整的 C#代码:

网站故障说明

2015年1月8日早晨,我和平时一样打开浏览器,准备登录网站查看留言,结果发现无法登录。开始想着是偶尔的维护,过一会又试验了一下发现仍然没有恢复。只好登录辰讯云(https://www.chenxunyun.com/)联系客服,随后得知了一个消息:服务器硬盘损坏,数据正在恢复中。大约到了中午,再次询问的时候,得到的消息是完全损坏,全部丢失。

不幸中的万幸是每个月我都会手工备份,上一次的备份时2024年12月中旬,于是等到再次上线只好重装系统和一干软件,重新搭建服务器所需内容。盘点下来网站内容丢失不多。

如果你在使用中遇到网站任何问题,欢迎在本文留言。

ASCII 一览表

工作需要经常查看 ASCII 数值,索性弄了一个列表和在线查询的功能:

这是在线查询的功能,使用 JavaScript 实现,有兴趣的朋友也可以直接保存到本地使用:

ASCII control characters (character code 0-31)

The first 32 characters in the ASCII-table are unprintable control codes and are used to control peripherals such as printers.

DECOCTHEXBINSymbolHTML NumberHTML NameDescription
00000000000000NUL&#00; Null character
10010100000001SOH&#01; Start of Heading
20020200000010STX&#02; Start of Text
30030300000011ETX&#03; End of Text
40040400000100EOT&#04; End of Transmission
50050500000101ENQ&#05; Enquiry
60060600000110ACK&#06; Acknowledge
70070700000111BEL&#07; Bell, Alert
80100800001000BS&#08; Backspace
90110900001001HT&#09; Horizontal Tab
100120A00001010LF&#10; Line Feed
110130B00001011VT&#11; Vertical Tabulation
120140C00001100FF&#12; Form Feed
130150D00001101CR&#13; Carriage Return
140160E00001110SO&#14; Shift Out
150170F00001111SI&#15; Shift In
160201000010000DLE&#16; Data Link Escape
170211100010001DC1&#17; Device Control One (XON)
180221200010010DC2&#18; Device Control Two
190231300010011DC3&#19; Device Control Three (XOFF)
200241400010100DC4&#20; Device Control Four
210251500010101NAK&#21; Negative Acknowledge
220261600010110SYN&#22; Synchronous Idle
230271700010111ETB&#23; End of Transmission Block
240301800011000CAN&#24; Cancel
250311900011001EM&#25; End of medium
260321A00011010SUB&#26; Substitute
270331B00011011ESC&#27; Escape
280341C00011100FS&#28; File Separator
290351D00011101GS&#29; Group Separator
300361E00011110RS&#30; Record Separator
310371F00011111US&#31; Unit Separator

ASCII printable characters (character code 32-127)

Codes 32-127 are common for all the different variations of the ASCII table, they are called printable characters, represent letters, digits, punctuation marks, and a few miscellaneous symbols. You will find almost every character on your keyboard. Character 127 represents the command DEL.

DECOCTHEXBINSymbolHTML NumberHTML NameDescription
320402000100000SP&#32; Space
330412100100001!&#33;&excl;Exclamation mark
340422200100010&#34;&quot;Double quotes (or speech marks)
350432300100011#&#35;&num;Number sign
360442400100100$&#36;&dollar;Dollar
370452500100101%&#37;&percnt;Per cent sign
380462600100110&&#38;&amp;Ampersand
390472700100111&#39;&apos;Single quote
400502800101000(&#40;&lparen;Open parenthesis (or open bracket)
410512900101001)&#41;&rparen;Close parenthesis (or close bracket)
420522A00101010*&#42;&ast;Asterisk
430532B00101011+&#43;&plus;Plus
440542C00101100,&#44;&comma;Comma
450552D00101101&#45; Hyphen-minus
460562E00101110.&#46;&period;Period, dot or full stop
470572F00101111/&#47;&sol;Slash or divide
4806030001100000&#48; Zero
4906131001100011&#49; One
5006232001100102&#50; Two
5106333001100113&#51; Three
5206434001101004&#52; Four
5306535001101015&#53; Five
5406636001101106&#54; Six
5506737001101117&#55; Seven
5607038001110008&#56; Eight
5707139001110019&#57; Nine
580723A00111010:&#58;&colon;Colon
590733B00111011;&#59;&semi;Semicolon
600743C00111100<&#60;&lt;Less than (or open angled bracket)
610753D00111101=&#61;&equals;Equals
620763E00111110>&#62;&gt;Greater than (or close angled bracket)
630773F00111111?&#63;&quest;Question mark
641004001000000@&#64;&commat;At sign
651014101000001A&#65; Uppercase A
661024201000010B&#66; Uppercase B
671034301000011C&#67; Uppercase C
681044401000100D&#68; Uppercase D
691054501000101E&#69; Uppercase E
701064601000110F&#70; Uppercase F
711074701000111G&#71; Uppercase G
721104801001000H&#72; Uppercase H
731114901001001I&#73; Uppercase I
741124A01001010J&#74; Uppercase J
751134B01001011K&#75; Uppercase K
761144C01001100L&#76; Uppercase L
771154D01001101M&#77; Uppercase M
781164E01001110N&#78; Uppercase N
791174F01001111O&#79; Uppercase O
801205001010000P&#80; Uppercase P
811215101010001Q&#81; Uppercase Q
821225201010010R&#82; Uppercase R
831235301010011S&#83; Uppercase S
841245401010100T&#84; Uppercase T
851255501010101U&#85; Uppercase U
861265601010110V&#86; Uppercase V
871275701010111W&#87; Uppercase W
881305801011000X&#88; Uppercase X
891315901011001Y&#89; Uppercase Y
901325A01011010Z&#90; Uppercase Z
911335B01011011[&#91;&lsqb;Opening bracket
921345C01011100\&#92;&bsol;Backslash
931355D01011101]&#93;&rsqb;Closing bracket
941365E01011110^&#94;&Hat;Caret – circumflex
951375F01011111_&#95;&lowbar;Underscore
961406001100000`&#96;&grave;Grave accent
971416101100001a&#97; Lowercase a
981426201100010b&#98; Lowercase b
991436301100011c&#99; Lowercase c
1001446401100100d&#100; Lowercase d
1011456501100101e&#101; Lowercase e
1021466601100110f&#102; Lowercase f
1031476701100111g&#103; Lowercase g
1041506801101000h&#104; Lowercase h
1051516901101001i&#105; Lowercase i
1061526A01101010j&#106; Lowercase j
1071536B01101011k&#107; Lowercase k
1081546C01101100l&#108; Lowercase l
1091556D01101101m&#109; Lowercase m
1101566E01101110n&#110; Lowercase n
1111576F01101111o&#111; Lowercase o
1121607001110000p&#112; Lowercase p
1131617101110001q&#113; Lowercase q
1141627201110010r&#114; Lowercase r
1151637301110011s&#115; Lowercase s
1161647401110100t&#116; Lowercase t
1171657501110101u&#117; Lowercase u
1181667601110110v&#118; Lowercase v
1191677701110111w&#119; Lowercase w
1201707801111000x&#120; Lowercase x
1211717901111001y&#121; Lowercase y
1221727A01111010z&#122; Lowercase z
1231737B01111011{&#123;&lcub;Opening brace
1241747C01111100|&#124;&verbar;Vertical bar
1251757D01111101}&#125;&rcub;Closing brace
1261767E01111110~&#126;&tilde;Equivalency sign – tilde
1271777F01111111DEL&#127; Delete

The extended ASCII codes (character code 128-255)

There are several different variations of the 8-bit ASCII table. The table below is according to Windows-1252 (CP-1252) which is a superset of ISO 8859-1, also called ISO Latin-1, in terms of printable characters, but differs from the IANA’s ISO-8859-1 by using displayable characters rather than control characters in the 128 to 159 range. Characters that differ from ISO-8859-1 is marked by light blue color.

DECOCTHEXBINSymbolHTML NumberHTML NameDescription
1282008010000000&#8364;&euro;Euro sign
1292018110000001 Unused
1302028210000010&#130;&sbquo;Single low-9 quotation mark
1312038310000011ƒ&#131;&fnof;Latin small letter f with hook
1322048410000100&#132;&bdquo;Double low-9 quotation mark
1332058510000101&#133;&hellip;Horizontal ellipsis
1342068610000110&#134;&dagger;Dagger
1352078710000111&#135;&Dagger;Double dagger
1362108810001000ˆ&#136;&circ;Modifier letter circumflex accent
1372118910001001&#137;&permil;Per mille sign
1382128A10001010Š&#138;&Scaron;Latin capital letter S with caron
1392138B10001011&#139;&lsaquo;Single left-pointing angle quotation
1402148C10001100Œ&#140;&OElig;Latin capital ligature OE
1412158D10001101 Unused
1422168E10001110Ž&#142;&Zcaron;Latin capital letter Z with caron
1432178F10001111 Unused
1442209010010000 Unused
1452219110010001&#145;&lsquo;Left single quotation mark
1462229210010010&#146;&rsquo;Right single quotation mark
1472239310010011&#147;&ldquo;Left double quotation mark
1482249410010100&#148;&rdquo;Right double quotation mark
1492259510010101&#149;&bull;Bullet
1502269610010110&#150;&ndash;En dash
1512279710010111&#151;&mdash;Em dash
1522309810011000˜&#152;&tilde;Small tilde
1532319910011001&#153;&trade;Trade mark sign
1542329A10011010š&#154;&scaron;Latin small letter S with caron
1552339B10011011&#155;&rsaquo;Single right-pointing angle quotation mark
1562349C10011100œ&#156;&oelig;Latin small ligature oe
1572359D10011101 Unused
1582369E10011110ž&#158;&zcaron;Latin small letter z with caron
1592379F10011111Ÿ&#159;&Yuml;Latin capital letter Y with diaeresis
160240A010100000NBSP&#160;&nbsp;Non-breaking space
161241A110100001¡&#161;&iexcl;Inverted exclamation mark
162242A210100010¢&#162;&cent;Cent sign
163243A310100011£&#163;&pound;Pound sign
164244A410100100¤&#164;&curren;Currency sign
165245A510100101¥&#165;&yen;Yen sign
166246A610100110¦&#166;&brvbar;Pipe, broken vertical bar
167247A710100111§&#167;&sect;Section sign
168250A810101000¨&#168;&uml;Spacing diaeresis – umlaut
169251A910101001©&#169;&copy;Copyright sign
170252AA10101010ª&#170;&ordf;Feminine ordinal indicator
171253AB10101011«&#171;&laquo;Left double angle quotes
172254AC10101100¬&#172;&not;Negation
173255AD10101101­SHY&#173;&shy;Soft hyphen
174256AE10101110®&#174;&reg;Registered trade mark sign
175257AF10101111¯&#175;&macr;Spacing macron – overline
176260B010110000°&#176;&deg;Degree sign
177261B110110001±&#177;&plusmn;Plus-or-minus sign
178262B210110010²&#178;&sup2;Superscript two – squared
179263B310110011³&#179;&sup3;Superscript three – cubed
180264B410110100´&#180;&acute;Acute accent – spacing acute
181265B510110101µ&#181;&micro;Micro sign
182266B610110110&#182;&para;Pilcrow sign – paragraph sign
183267B710110111·&#183;&middot;Middle dot – Georgian comma
184270B810111000¸&#184;&cedil;Spacing cedilla
185271B910111001¹&#185;&sup1;Superscript one
186272BA10111010º&#186;&ordm;Masculine ordinal indicator
187273BB10111011»&#187;&raquo;Right double angle quotes
188274BC10111100¼&#188;&frac14;Fraction one quarter
189275BD10111101½&#189;&frac12;Fraction one half
190276BE10111110¾&#190;&frac34;Fraction three quarters
191277BF10111111¿&#191;&iquest;Inverted question mark
192300C011000000À&#192;&Agrave;Latin capital letter A with grave
193301C111000001Á&#193;&Aacute;Latin capital letter A with acute
194302C211000010Â&#194;&Acirc;Latin capital letter A with circumflex
195303C311000011Ã&#195;&Atilde;Latin capital letter A with tilde
196304C411000100Ä&#196;&Auml;Latin capital letter A with diaeresis
197305C511000101Å&#197;&Aring;Latin capital letter A with ring above
198306C611000110Æ&#198;&AElig;Latin capital letter AE
199307C711000111Ç&#199;&Ccedil;Latin capital letter C with cedilla
200310C811001000È&#200;&Egrave;Latin capital letter E with grave
201311C911001001É&#201;&Eacute;Latin capital letter E with acute
202312CA11001010Ê&#202;&Ecirc;Latin capital letter E with circumflex
203313CB11001011Ë&#203;&Euml;Latin capital letter E with diaeresis
204314CC11001100Ì&#204;&Igrave;Latin capital letter I with grave
205315CD11001101Í&#205;&Iacute;Latin capital letter I with acute
206316CE11001110Î&#206;&Icirc;Latin capital letter I with circumflex
207317CF11001111Ï&#207;&Iuml;Latin capital letter I with diaeresis
208320D011010000Ð&#208;&ETH;Latin capital letter ETH
209321D111010001Ñ&#209;&Ntilde;Latin capital letter N with tilde
210322D211010010Ò&#210;&Ograve;Latin capital letter O with grave
211323D311010011Ó&#211;&Oacute;Latin capital letter O with acute
212324D411010100Ô&#212;&Ocirc;Latin capital letter O with circumflex
213325D511010101Õ&#213;&Otilde;Latin capital letter O with tilde
214326D611010110Ö&#214;&Ouml;Latin capital letter O with diaeresis
215327D711010111×&#215;&times;Multiplication sign
216330D811011000Ø&#216;&Oslash;Latin capital letter O with slash
217331D911011001Ù&#217;&Ugrave;Latin capital letter U with grave
218332DA11011010Ú&#218;&Uacute;Latin capital letter U with acute
219333DB11011011Û&#219;&Ucirc;Latin capital letter U with circumflex
220334DC11011100Ü&#220;&Uuml;Latin capital letter U with diaeresis
221335DD11011101Ý&#221;&Yacute;Latin capital letter Y with acute
222336DE11011110Þ&#222;&THORN;Latin capital letter THORN
223337DF11011111ß&#223;&szlig;Latin small letter sharp s – ess-zed
224340E011100000à&#224;&agrave;Latin small letter a with grave
225341E111100001á&#225;&aacute;Latin small letter a with acute
226342E211100010â&#226;&acirc;Latin small letter a with circumflex
227343E311100011ã&#227;&atilde;Latin small letter a with tilde
228344E411100100ä&#228;&auml;Latin small letter a with diaeresis
229345E511100101å&#229;&aring;Latin small letter a with ring above
230346E611100110æ&#230;&aelig;Latin small letter ae
231347E711100111ç&#231;&ccedil;Latin small letter c with cedilla
232350E811101000è&#232;&egrave;Latin small letter e with grave
233351E911101001é&#233;&eacute;Latin small letter e with acute
234352EA11101010ê&#234;&ecirc;Latin small letter e with circumflex
235353EB11101011ë&#235;&euml;Latin small letter e with diaeresis
236354EC11101100ì&#236;&igrave;Latin small letter i with grave
237355ED11101101í&#237;&iacute;Latin small letter i with acute
238356EE11101110î&#238;&icirc;Latin small letter i with circumflex
239357EF11101111ï&#239;&iuml;Latin small letter i with diaeresis
240360F011110000ð&#240;&eth;Latin small letter eth
241361F111110001ñ&#241;&ntilde;Latin small letter n with tilde
242362F211110010ò&#242;&ograve;Latin small letter o with grave
243363F311110011ó&#243;&oacute;Latin small letter o with acute
244364F411110100ô&#244;&ocirc;Latin small letter o with circumflex
245365F511110101õ&#245;&otilde;Latin small letter o with tilde
246366F611110110ö&#246;&ouml;Latin small letter o with diaeresis
247367F711110111÷&#247;&divide;Division sign
248370F811111000ø&#248;&oslash;Latin small letter o with slash
249371F911111001ù&#249;&ugrave;Latin small letter u with grave
250372FA11111010ú&#250;&uacute;Latin small letter u with acute
251373FB11111011û&#251;&ucirc;Latin small letter u with circumflex
252374FC11111100ü&#252;&uuml;Latin small letter u with diaeresis
253375FD11111101ý&#253;&yacute;Latin small letter y with acute
254376FE11111110þ&#254;&thorn;Latin small letter thorn
255377FF11111111ÿ&#255;&yuml;Latin small letter y with diaeresis

上述来自: https://www.ascii-code.com/

一个简单的计数批处理文件

@echo off
setlocal enabledelayedexpansion

:: 定义计数文件
set counter_file=counter.txt

:: 检查计数文件是否存在
if not exist %counter_file% (
    echo 0 > %counter_file%
)

:: 从文件中读取当前计数值
set /p counter=<%counter_file%

:: 显示当前计数值
echo Current counter value: %counter%

:: 将计数值加1
set /a counter+=1

:: 将新的计数值写入文件
echo %counter% > %counter_file%

:: 显示新的计数值
echo New counter value: %counter%

::为了看的清楚,这里加入一个 Pause
pause

endlocal

MAX98357 I2S功放模块测试

之前购买了一个MAX98357 I2S功放模块,这次编写简单的代码进行测试。

硬件连接如下:

MAX98357ESP32S3用途
SPK+/- 连接喇叭 连接喇叭正负极,喇叭输出
DIN  48从 ESP32S3 发送的 I2S数据
BCLK45从 ESP32S3 发送的 I2S Clock
LRC 35从 ESP32S3 发送的 I2S 左右声道选择信号
GNDGND
VCC5V供电

按照上述方案连接好后,烧录如下代码:

#include <I2S.h>
const int frequency = 440; // frequency of square wave in Hz
const int amplitude = 32000; // amplitude of square wave
const int sampleRate = 8000; // sample rate in Hz
const int bps = 16;

const int halfWavelength = (sampleRate / frequency); // half wavelength of square wave

short sample = amplitude; // current sample value
int count = 0;

i2s_mode_t mode = I2S_PHILIPS_MODE; // I2S decoder is needed
// i2s_mode_t mode = ADC_DAC_MODE; // Audio amplifier is needed

// Mono channel input
// This is ESP specific implementation -
//   samples will be automatically copied to both channels inside I2S driver
//   If you want to have true mono output use I2S_PHILIPS_MODE and interlay
//   second channel with 0-value samples.
//   The order of channels is RIGH followed by LEFT
//i2s_mode_t mode = I2S_RIGHT_JUSTIFIED_MODE; // I2S decoder is needed

void setup() {
  Serial.begin(115200);
  Serial.println("I2S simple tone");
  delay(5000);

    //setAllPins(int sckPin, int fsPin, int sdPin, int outSdPin, int inSdPin);
  I2S.setAllPins(45        , 35       , 48       , 48          , -1);

  // start I2S at the sample rate with 16-bits per sample
  if (!I2S.begin(mode, sampleRate, bps)) {
    
    Serial.println("Failed to initialize I2S!");
    while (1); // do nothing
  }
}

void loop() {

   while (Serial.available()) {
    char c = Serial.read();
    if (c == '3') {
      ESP.restart();
    }
    // 主机端发送 l, 回复 z 用于识别串口
    if (c == '1') {
      Serial.print('z');
    }
    // 主机端发送 l, 回复 z 用于识别串口
    if (c == '2') {
      Serial.printf("getSckPin:%d getFsPin:%d getDataPin:%d",
                      I2S.getSckPin(),
                      I2S.getFsPin(),
                      I2S.getDataPin());
    }    
  }

  
    if (count % halfWavelength == 0 ) {
      // invert the sample every half wavelength count multiple to generate square wave
      sample = -1 * sample;
    }

    if(mode == I2S_PHILIPS_MODE || mode == ADC_DAC_MODE){ // write the same sample twice, once for Right and once for Left channel
      I2S.write(sample); // Right channel
      I2S.write(sample); // Left channel
    }else if(mode == I2S_RIGHT_JUSTIFIED_MODE || mode == I2S_LEFT_JUSTIFIED_MODE){
      // write the same only once - it will be automatically copied to the other channel
      I2S.write(sample);
    }

    // increment the counter for the next sample
    count++;
}

测试的视频在下面可以看到:

WinPE下面制造一个蓝屏

正常的 Windows下面蓝屏很常见,实际上 WinPE 下面也是可以出现蓝屏的。这次介绍通过修改 WinPE 的注册表,实现USB键盘上按下右 Ctrl+快速按下Scroll键即可触发蓝屏 【参考1】。

最关键的步骤是修改 WinPE的注册表,打开这个触发蓝屏的功能。

1.将 Windows安装盘中的 Boot 解压,放在 c:\labz 目录下,然后运行如下命令解包之:

dism /mount-wim /wimfile:c:\labz\boot.wim /index:1 /mountdir:c:\m1

2.打开本机的注册表工具,先选中 HKEY_USERS,然后在菜单上选择 Load Hive

3.在对话框上选择 c:\m1\windows\system32\config\system 文件

4.设置一个加载点

这样操作之后可以看到挂载到如下位置了

5.我们在LABZ\ControlSet001\Services\kbhid\Parameters 下面创建 CrashOnCtrlScroll 并且赋值为1

6.选中注册表上的 “LABZ”,然后菜单选择 Unload Hive,这样就从注册表中卸载了WinPE的注册表

7.之后 关闭注册表编辑器,然后使用如下命令写入 WIM(特别注意,实践中发现有时候无法正常写入,错误信息是 Windows有占用目录下的文件,这种情况下重启操作系统再运行一次即可)

dism /unmount-wim /MountDir:c:\m1 /Commit

8.接下来再次解包 Boot.WIM (原因和之前” 制作全自动安装的 Windows 11 ISO”文章提到的一样,BOOT.WIM 中有2个WindowPE环境)

dism /mount-wim /wimfile:c:\labz\boot.wim /index:2  /mountdir:c:\m1

之后同样执行2-7步骤,最终我们就得到了一个修改后的 BOOT.WIM

使用,ISO 编辑工具将BOOT.WIM 写入 ISO 之后,我们就得到了一个测试 Windows安装镜像文件。在安装过程中使用 USB 键盘,按下右侧Ctrl然后快速按下2测 Scoll 键就能够触发蓝屏了。

可以看到蓝屏发生后,显示一段之后会自动重启。

最后讲个有意思的事情。很多年前,我碰到过一个奇怪的问题:工厂那边反映我们 Release 的BIOS有问题,会导致生产过程中重启。负责这个事情的BIOS工程师是一个妹子,被这个问题折磨很久。问题发生之后,她不修改任何代码,只是重新编译一次BIOS,发给产线使用问题就会消失。但是后面不知道什么时候问题又会出来。出于好奇后来我接手这个问题进行研究。所有的整机无论是笔记本还是台式机,在生产的过程中都会有灌装系统运行产测软件的步骤。在这个过程中,会检查一些基本的功能,比如:是否会有声音,屏幕键盘鼠标能否工作正常等等。这个测试通常需要一气呵成完成的,每个测试都会收集测试结果,然后上报到服务器中的,因此如果发生意外重启会打断整个流程。测试结束后,会重新安装一个新的操作系统然后再交给客户。作为BIOS工程师,我是不太相信BIOS会导致这样的问题。因此,我和工厂要了一下他们的产测软件在实验室进行研究。产线使用 WinPE环境,我手上没有,只能在普通 Windows上试验。试验几次之后,我发现其中的测试软件会出现Windows的报错对话框。于是,将关注点放在了这个软件上。咨询产线得知这个软件的作用是在0xF0000 中搜索一个字符串。听到这里我心里就有了大概,Windows下访问物理内存,出现错误是很重要的。之后和产线要到的源代码,简单读了了一下很快就定位了问题。这个软件需要在物理内存中搜索,于是作者调用了一个 Window API 做了一下映射,将 0xF0000开始的64K物理内存映射到应用程序的内存上,然后用 memcmp 进行查找。发生问题的原因是,要查找的字符串刚好卡在结尾处,比如我们需要在内存中查找“LAB-Z.COM”这个字符串,但是刚好碰到这个字符串在内存中分布如下:

0xFFFFA0xFFFFB0xFFFFC0xFFFFD0xFFFFE0xFFFFF0x10 00000x10 00010x10 00020x10 0003
LABZ.COM0

当直接使用 memcpy 查找的时候,它会先找到 “LAB-Z”,但是继续比较字符串剩余部分的时候就碰到“指针出界”的问题。如果在正常的Windows下是可以抛出这个问题继续运行的,但是在WinPE环境下就直接变为重启。最终,经过努力这次的问题没有让BIOS工程师来背锅。

另外多说一句:即便相同的产线,相同的物料,白班和夜班的质量也会有差别。原因是白天人全,技术和后端都在,出了问题会有人处理;夜班的话,人不全,出了问题通常的处理方法是“小车不倒只管推”了。

参考:

1. https://www.lab-z.com/wdbg/  WinDBG 分析键盘生成的 Dump 文件