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
LAB-Z.COM0

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

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

参考:

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

ASCII数字

因为 UEFI Shell 是 CUI 界面,很多测试需要用 ASCII 显示结果。所以有些时候我们需要足够大的字来展示。

最近偶然发现了这个 ASCII 字体的网站,可以用来取得一些 ASCII 拼凑出来的字形。例如:

░▒▓█▓▒░       ░▒▓██████▓▒░░▒▓███████▓▒░░▒▓████████▓▒░ 
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░      ░▒▓█▓▒░ 
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░    ░▒▓██▓▒░  
░▒▓█▓▒░      ░▒▓████████▓▒░▒▓███████▓▒░   ░▒▓██▓▒░    
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓██▓▒░      
░▒▓█▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░        
░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░ 
                                                      

 __      __    ____      ____ 
(  )    /__\  (  _ \ ___(_   )
 )(__  /(__)\  ) _ <(___)/ /_ 
(____)(__)(__)(____/    (____)

     ___       ___           ___           ___     
     /\__\     /\  \         /\  \         /\  \    
    /:/  /    /::\  \       /::\  \        \:\  \   
   /:/  /    /:/\:\  \     /:/\:\  \        \:\  \  
  /:/  /    /::\~\:\  \   /::\~\:\__\        \:\  \ 
 /:/__/    /:/\:\ \:\__\ /:/\:\ \:|__| _______\:\__\
 \:\  \    \/__\:\/:/  / \:\~\:\/:/  / \::::::::/__/
  \:\  \        \::/  /   \:\ \::/  /   \:\~~\~~    
   \:\  \       /:/  /     \:\/:/  /     \:\  \     
    \:\__\     /:/  /       \::/__/       \:\__\    
     \/__/     \/__/         ~~            \/__/    

有兴趣的朋友可以在这里看到:

http://www.patorjk.com/software/taag/#p=testall&f=Alpha&t=LAB-Z

UEFI TIPS:Print=UnicodeSPrint+ ConOut

最近有在屏幕输出数据的需求,但是无法直接使用 Print 。经过对于 PrintLib 的一番研究,得出了结论:

UEFI Shell 下的Print 的实现可以看作两个动作,一个根据输入格式化得到字符串,另外一个是使用 gST 进行输出。简单的说就是:

Print=UnicodeSPrint+ gST->ConOut->OutputString

编写测试代码:

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

#include  <Library/PrintLib.h>
extern EFI_SYSTEM_TABLE			 *gST;
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  CHAR16					Buffer[32];
  
  UnicodeSPrint((CHAR16 *)Buffer,sizeof(Buffer),L"%x\n",2024);
  gST->ConOut->OutputString(gST->ConOut,Buffer);
 
  return(0);
}

运行结果:

如果你在调试 Application 或者Driver遇到无法直接使用 Print 的情况,不妨考虑本文提到的方法。当然,如果你有从串口或者其他设备输出调试数据的时候,也可以考虑UnicodeSPrint()来实现数据格式化方便阅读调试。

一些Unicode 数字符号定义

1.圈中带有数字

⓪ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳
㉑ ㉒ ㉓ ㉔ ㉕ ㉖ ㉗ ㉘ ㉙ ㉚ ㉛ ㉜ ㉝ ㉞ ㉟ ㊱ ㊲ ㊳ ㊴ ㊵
㊶ ㊷ ㊸ ㊹ ㊺ ㊻ ㊼ ㊽ ㊾ ㊿

2.实心方块中空圆形数字

         
         

3.实心方块数字

         

4.中文

         

来源:http://xahlee.info/comp/unicode_circled_numbers.html

此外,在 https://altcodeunicode.com/alt-codes-circled-number-letter-symbols-enclosed-alphanumerics/ 还有一些圆圈的英文序号

制作全自动安装的 Windows 11 ISO

前面提 到通过应答文件来实现自动安装【参考1】,使用这个方法在虚拟机上测试正常,但是实体机上在分区拷贝完成之后会进入OOBE的界面要求手工选择,因此并非全自动安装。

针对这个问题进行了更深入的研究,原来除了在安装盘下面放置autounattend.xml 之外,在对应的 WinPE 盘,Windows\Panther 目录下还需要放置一个 unattend.xml 应答文件。更具体的操作是,安装 ADK

1。解压Windows 11 安装 ISO

2.取出其中的 boot.wim 文件

3.在 “Deployment and Imaging Tools Environment”中使用如下命令解压 Boot.WIM 到 m1 目录下

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

4.将unattend.xml拷贝到 m1\Windows\Panther目录下

5.将修改回写到 boot.wim 中

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

6.同样在 “Deployment and Imaging Tools Environment”中再次解压 Boot.WIM 到 m1 目录下(Boot 中有个 Index,上一次用的是 1,这次用的是 2)

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

7.将unattend.xml拷贝到 m1\Windows\Panther目录下

8.将修改回写到 boot.wim 中

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

这样就有了一个更换过 unattend.xml的Boot.WIM 其中是一个完整的 WinPE 环境,接下来使用工具将Boot.WIM替换回原始的安装 ISO。最终我们就有了一个全自动的Window安装盘。

这样的ISO通过 Refuse或者Ventoy 制作出来的启动U盘上都可以正常启动,并且能够做到完全自动的安装。

本文提到的制作好的 Windows 11 可以在这里下载(文件超过4G, 分割为2个ZIP 压缩文件)

文件1

文件2

对应的2个 xml 文件可以在这里下载:

参考:

1. https://www.lab-z.com/winut/

测试的视频,在 LattePanda MU 上进行的测试: