Step to UEFI (294)手工给 EFI 文件插入代码的试验(上)

编译好的 EFI 文件本质上是一个 WinPE 文件,因此我们有机会在文件开始处加入一些我们需要的代码。这次介绍的就是一个手工在 EFI 入口处插入另外一个EFI 代码的试验。

这次进行一个特别的实验。基本的原理是:

  1. 编写一个在屏幕上输出字符串简单的程序,这样我们能得到一段EFI Shell下对屏幕输出字符串的机器码;
  2. 编写一个宿主程序,这个程序编译后的 EFI 文件使用 4K 对齐,这样话,存放代码的.text段会有足够的空间能够存放下步骤1生成的机器码;
  3. 修改生成的EFI文件的 Section Headers 中给出的.text 大小,保证足够放下我们增加的机器码;
  4. 将步骤1生成的代码,插入在步骤3生成的EFI中。

最终,我们得到一个新的 EFI 程序,运行之后它会先执行步骤1 的代码,然后再执行步骤2的代码。

步骤1:这里使用 NASM 汇编语言来完成。

根据之前的文章【参考1】,编写一个程序实现在屏幕上输出字符串的代码。代码有部分修改,主要是将输出的字符串和代码放在了一起:

bits 64
 
; contains the code that will run
section .text
 
; allows the linker to see this symbol
global _start
 
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
    .Signature    RESQ 1
    .Revision     RESD 1
    .HeaderSize   RESD 1
    .CRC32        RESD 1
    .Reserved     RESD 1
endstruc
 
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
struc EFI_SYSTEM_TABLE
    .Hdr                  RESB EFI_TABLE_HEADER_size
    .FirmwareVendor       RESQ 1
    .FirmwareRevision     RESD 1
    .ConsoleInHandle      RESQ 1
    .ConIn                RESQ 1
    .ConsoleOutHandle     RESQ 1
    .ConOut               RESQ 1
    .StandardErrorHandle  RESQ 1
    .StdErr               RESQ 1
    .RuntimeServices      RESQ 1
    .BootServices         RESQ 1
    .NumberOfTableEntries RESQ 1
    .ConfigurationTable   RESQ 1
endstruc
 
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016807
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    .Reset             RESQ 1
    .OutputString      RESQ 1
    .TestString        RESQ 1
    .QueryMode         RESQ 1
    .SetMode           RESQ 1
    .SetAttribute      RESQ 1
    .ClearScreen       RESQ 1
    .SetCursorPosition RESQ 1
    .EnableCursor      RESQ 1
    .Mode              RESQ 1
endstruc
 
_start:
 
    push rax    ;ConOut requires a push here. I don't know why
 
    ; reserve space for 4 arguments
    sub rsp, 4 * 8
 
    ; rdx points to the EFI_SYSTEM_TABLE structure
    ; which is the 2nd argument passed to us by the UEFI firmware
    ; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
    mov rcx, [rdx + 64]
 
    ; load the address of our string into rdx
    lea rdx, [rel strHello]
 
    ; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
    ; call OutputString on the value in rdx
    call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
     
    add rsp, 4 * 8
    pop rax     
    ret
	
strHello db __utf16__ `Hello World from LAB-Z.COM!\n\r\0`

codesize equ $ - $$
 
; contains nothing - but it is required by UEFI
section .reloc

编译命令如下:

c:\nasm\nasm -f win64 hello.asm -l hello.lst
link /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64  /DLL /ENTRY:_start  /SUBSYSTEM:EFI_APPLICATION /SAFESEH:NO /DRIVER Hello.obj

除了正常生成的 EFI 文件之外,还生成了 lst 文件,在其中能够看到代码生成的机器码用于对照参考:

在模拟器中测试可以正常执行。

根据【参考2】,具体对应如下:

.text:(代码段),可读、可执行
.data:(数据段),存放全局变量、全局常量等
.idata:(数据段),导入函数的代码段,存放外部函数地址。(当然还有 edata ,导出函数代码段,但不常用)
.rdata:(数据段),资源数据段,程序用到什么资源数据都在这里(包括自己打包的,还有开发工具打包的)

使用 CFF Explorer查看,我们需要的机器码就在.text段中:

步骤2:我们根据【参考3】,编写一个简单的代码,功能上只是向屏幕输出字符串,然后使用 EDK2 进行开发。因为默认情况下,.text 空余空调很小,所以在编译完成后我们再打开 下面这个 makefile文件

edk2\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\SimpleTest\SimpleTest\Makefile

将下面的一行中修改为 /ALIGN:0x1000

DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4281 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG

然后进入对应目录,输入 nmake 重新编译,这样就得到一个段以4K 对齐的 EFI 文件。

查看 .text段,这里有足够的空间插入我们的代码:

步骤3,修改Section Headers 中的.text Size,这里我们修改为 1600.

步骤4,我们手工插入。

1.SimpleTestM.EFI的入口地址在 0x400处:

开始处对应的是 _ModuleEntryPoint() 函数,我们程序代码是从ShellAppMain() 函数开始的。

打开hello.efi ,拷贝这一段:

插入(Paste Write)到 SimpleTest.efi 中:

然后手工修改SimpleTest.efi 如下:

入口处修改为跳转指令, 这里使用的是一个相对跳转指令【参考4】

运行之后结果如下:

就是说我们通过 SimpleTestM 运行了hello.efi 中的内容。

当然这里和我们的预期还有差别

当然,这样的代码并不是我们期待的最终结果,我们期望两个程序能够同时运行。

这次只做到了在一个A.EFI中插入另外一个B.EFI 文件,然后运行A.EFI 实际执行B.EFI的代码。

参考:

  1. https://www.lab-z.com/stu207/
  2. https://blog.csdn.net/Simon798/article/details/96876910
  3. https://www.lab-z.com/stu260/
  4. https://www.felixcloutier.com/x86/jmp

给 EXE 加入 Resource

最近忽然想起来一个问题:如何给一个做好的 EXE 加入其他的内容?比如,我编写一个 EXE 需要更改内容而又不想重新 Build 代码。

经过研究,可以通过给EXE 添加 Resource 的方法来实现这一目标。在 https://github.com/tc-hib/go-winres 这里有一个从命令行给 EXE 添加Resource 的项目。配合这个项目可以实现前述目标。

首先,编写一个测试代码,使用 VC 编写在 VS2019 下编译通过:

#include <windows.h>
#include <iostream>

// 回调函数用于枚举资源
BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam) {
    // 每找到一个资源,就增加计数
    (*(int*)lParam)++;
    return TRUE; // 继续枚举
}

int main()
{
    HMODULE hModule = GetModuleHandle(NULL); // 获取当前模块句柄
    int resourceCount = 0; // 用于计数的变量

    // 枚举所有RT_RCDATA类型的资源
    EnumResourceNames(hModule, RT_RCDATA, EnumResNameProc, (LONG_PTR)&resourceCount);

    std::cout << "Number of RT_RCDATA resources: " << resourceCount << std::endl;

    return 0;
}

代码非常简单,单纯的输出当前EXE RT_RCDATA类型的 Resource 数量。

接下来将go-winres.exe放在同一个目录下,然后运行下面的命令

go-winres.exe init

对应的会生成 winres 目录,其中有下面三个文件

前面2个是可以作为EXE 的图标的,winres.json是配置文件。例如,我们对这个目录放置一个 png 文件,然后修改如下,增加 RT_RCDATA的部分:

  "RT_GROUP_ICON": {
    "APP": {
      "0000": [
        "icon.png",
        "icon16.png"
      ]
    }
  },
  "RT_RCDATA": {
    "OTHER": {
      "0000": "2.png"
    }
  },  
  "RT_MANIFEST": {
    "#1": {
      "0409": {
        "identity": {
          "name": "",
          "version": ""

运行如下命令:

go-winres.exe patch ResourceTest.exe

工具会自动给 ResourceTest.exe 添加内容,之后再次运行:

如果使用 CFF 工具还可以看到多了一个 Resource。这样,你可以在代码中先判断Resource数量,然后再进行动作。

官方提供的版本(和 Github上的相同,0.3.3版本)

VC 编写的WAVE测试文件生成器

最近为了调试,写了一个 WAVE 的生成器,能够生成指定采样率,指定格式的 WAVE文件,这样可以在输出端直接查看输出是否是指定的数据。特别注意的是,WAVE 中,使用 int16 (有符号十六进制)来表示当前的信号,更具体来说,负数使用补码来表示。代码如下:

// WaveGenerator.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#pragma pack(push, 1)
struct Chunk1 {          // 'R', 'I', 'F', 'F'
	char chunk_id[4];
	uint32_t chunk_size;
	char format[4];      // 'W', 'A', 'V', 'E'
};

struct Chunk2 {
	char chunk_id[4];
	uint32_t chunk_size;
	int16_t wFormatTag;
	int16_t nChannels;
	int32_t nSamplesPerSec;
	int32_t nAvgBytesPerSec;
	int16_t nBlockAlign;  // => num_channels * bits_per_sample / 8
	int16_t wBitsPerSample;
};

struct Chunk3 {
	char chunk_id[4];
	uint32_t chunk_size;
};
#pragma pack(pop) // 恢复到之前的对齐设置

#define AUDIODURATION  5  // 音频时长,以秒为单位
#define SAMPLEBITS  16  // 采样位
#define SAMPLERATE  96000  // 采样率
#define CHANNELNUM  2  // Channel 数量
int main()
{
	Chunk1 chunk1;
	chunk1.chunk_id[0] = 'R'; chunk1.chunk_id[1] = 'I'; chunk1.chunk_id[2] = 'F'; chunk1.chunk_id[3] = 'F';
	// 数据长度= Chunk123 总长 + RAW 数据长度(双声道,SAMPLEBITS Bits)
	chunk1.chunk_size = sizeof(Chunk1)-8 + sizeof(Chunk2) + sizeof(Chunk3)+ AUDIODURATION* CHANNELNUM *(SAMPLEBITS/8)* SAMPLERATE;
	chunk1.format[0] = 'W'; chunk1.format[1] = 'A'; chunk1.format[2] = 'V'; chunk1.format[3] = 'E';

	Chunk2 chunk2;

	chunk2.chunk_id[0] = 'f'; chunk2.chunk_id[1] = 'm'; chunk2.chunk_id[2] = 't'; chunk2.chunk_id[3] = ' ';
	chunk2.chunk_size = sizeof(Chunk2)-8;
	chunk2.wFormatTag = 0x0001; //PCM 格式
	chunk2.nChannels = CHANNELNUM;
	chunk2.nSamplesPerSec = SAMPLERATE;
	chunk2.nAvgBytesPerSec = CHANNELNUM * SAMPLERATE * (SAMPLEBITS / 8);
	chunk2.nBlockAlign = CHANNELNUM * (SAMPLEBITS / 8);
	chunk2.wBitsPerSample = SAMPLEBITS;

	Chunk3 chunk3;
	chunk3.chunk_id[0] = 'd'; chunk3.chunk_id[1] = 'a'; chunk3.chunk_id[2] = 't'; chunk3.chunk_id[3] = 'a';
	chunk3.chunk_size = AUDIODURATION * CHANNELNUM * SAMPLERATE * (SAMPLEBITS / 8);

	// 打开文件用于写入,以二进制模式
	FILE* file = fopen("C:\\WaveGenerator\\Debug\\example.wav", "wb");
	fwrite(&chunk1, sizeof(chunk1), 1, file);
	fwrite(&chunk2, sizeof(chunk2), 1, file);
	fwrite(&chunk3, sizeof(chunk3), 1, file);
	short int s=0,e=0;
	for (int i = 0; i < AUDIODURATION* SAMPLERATE; i++) {
		//s = sin(i * 2 * 3.1415 / (SAMPLERATE/1000)) * (65535 / 2);
		// 左声道
		s++;
		fwrite(&s, sizeof(s), 1, file);
		// 右声道
		e = 0; 
		fwrite(&e, sizeof(e), 1, file);
	}

	fclose(file);
	//getchar();
}

小米2代体脂秤维修

最近给一个朋友维修了一下小米2代体脂秤。他遇到的问题是:插入电池后电池发热。拿到手后,看到了现象:装入电池后,电池组和线都会发烫。 

使用万用表进行测量,直接测量电池盒两端电阻为 170欧姆,装入电池后测量发现输出电流达到3A左右,这就是发热原因。 

根据部分资料【参考1】,上图中红色框芯片为CST34M96,用于蓝牙通讯;绿色框中芯片为CS1256,这是用于获得体脂数据并且处理的单片机;紫色方框中是稳压(降压)芯片,型号未知丝印D3E1,照片是拆除这个芯片后拍摄。 

不上电测量正常,上电后有问题猜测是电源芯片出了问题。于是拆除了稳压芯片。这部分电路如下: 

VDD 是电池盒输入;经过芯片降压输出为 AVCC,最终提供给后端使用。万用表测试表明AGND 和 GND 是连通的。 

拆除芯片后,从 AVCC 送入 3.3V,数码管会有动作,证明后面的芯片都是能够正常工作的,所以选择一个稳压芯片替换。 

最终,替换稳压/降压芯片后,外加更换C4电容(测量发现C4短路了,可能被击穿了?)后,可以正常工作。

参考: 

1. https://www.mydigit.cn/forum.php?mod=viewthread&tid=286533&page=1 

Timed GPIO

Timed GPIO 或者称作 Timed I/O 是基于系统时钟产生或者接收信号的功能。从 EHL/TGL(11代)平台开始,Intel 引入了这个功能。

这个功能的背景是:一些时候我们需要产生精确的 GPIO 时序,但是因为系统CPU分配的原因,这一点难以实现。比如,我们在代码中每隔0.001s需要反转一次GPIO,如果使用代码来驱动的话,无法保证CPU每隔0.001s执行一次,这样会导致实际生成的信号会有“抖动”(Jitter)。因此,引入了 Time-Aware GPIO(TGPIO)。直接从硬件上保证能够在指定的时间产生信号。除了能够输出信号(GPO),还能够作为 GPI,这样可以精确的测量两个信号的间隔。

有兴趣的朋友可以更深入的进行研究。

参考:

1.https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/servers/platforms/intel-pentium-silver-and-intel-celeron-processors-datasheet-volume-1-of-2/005/timed-gpio-time-sync/

2.https://edc.intel.com/content/www/us/en/design/products/platforms/processor-and-core-i3-n-series-datasheet-volume-1-of-2/001/timed-gpio/

3.https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/servers/platforms/intel-pentium-silver-and-intel-celeron-processors-datasheet-volume-1-of-2/005/timed-gpio-time-sync/

WIM安装解决方案

很多年前,Ghost 是通用的安装方案,但是随着时代的进步,Ghost 方案逐渐被淘汰。取而代之的是WIM 的方案,最新的 OS 和 Driver 都使用 WIM 的方式进行分发。需要注意的是:市面上很多能够实现 WIM 安装的 Windows PE 盘会在安装过程中对安装后的Windows 插入应用程序或者驱动程序。这样的结果可能导致影响你的测试结果。因此,这次制作了Windows PE 盘以及一个WIM 安装工具。这一套具有如下优点:

  1. 安装过程中不会添加任何额外软件;
  2. 方便使用,启动WindowPE 之后只需要键盘即可选择 WIM 文件进行安装;
  3. 体积较小,包括 WindowsPE 镜像,整体不超过350MB。

接下来介绍如何部署和使用这一套软件。

首先,使用 Ventoy 制作一个 USB 启动盘。需要注意的是:这个U盘上的内容会被完全清除掉。

安装完成 Ventoy 之后,需要手工将Winpe062907.iso和AutoWim目录拷贝到U盘Ventoy 的 exFAT分区上。同时,将需要安装的 WIM 同样放置于根目录下。

之后,使用这个U盘进行启动。

启动Ventoy后从菜单上选择从 Winpe062907.iso 启动。

启动之后,这个 WinPE 会自动执行 AutoWim 目录中的InWim.bat批处理。自动列出U盘根目录下的所有 WIM 请用户选择,选择之后会对硬盘进行自动分区,然后部署 WIM 内容。最后会执行 bcdboot 修复ESP 分区。

相信这个工具会成为你测试的得力助手,有兴趣的朋友不妨试试看。

ISO 镜像下载

AutoWim 目录下载

工作的视频可以在下面看到:

Intel Microcode PDB 格式转INC工具

通常情况下,Intel Microcode 都是以 INC 文件 Release 的,并且官方提供了INC转PDB的工具。但是一些情况下,我们拿到的是 PDB 格式,这时候可以使用这里提供的反向转换工具,将PDB 重新转为 INC格式。

有兴趣的朋友可以先找一个INC文件,然后转为 PDB文件,再使用这里的工具将PDB转为 INC ,然后比较前后文件内容以便验证工具正确性。

Arduino 开发的 CH32V307 IIS 音频输出测试

这次介绍的是如何在 CH32V307上通过 Arduino 编程,从 IIS 接口输出一个正弦波然后通过 NS4168 播放出来。关于 NS4168的介绍,可以在【参考1】看到。

线路方面:

B12(I2S2_WS)连接到 LRCLK

B13(I2S2_CK) 连接到 BCKL

B15(I2S2_SD)连接到  SDAT

 同时特别提醒 NS4168 的CTRL 需要连接 3.3V

之后,给 Ch32v307烧录如下代码:

//#include "ch32v30x_spi.h"
/* Global Variable */
#define  Len    48*16*2
u16 I2S2_Tx[Len] = {
16383,16383,20660,20660,24864,24864,28922,28922,32767,32767,-29206,-29206,-25983,-25983,-23157,-23157,-20776,-20776,-18880,-18880,-17502,-17502,-16666,-16666,-16385,-16385,-16665,-16665,-17501,-17501,-18879,-18879,-20774,-20774,-23155,-23155,-25981,-25981,-29203,-29203,-32767,-32767,28925,28925,24867,24867,20663,20663,16386,16386,12109,12109,7906,7906,3847,3847,3,3,-3560,-3560,-6783,-6783,-9610,-9610,-11991,-11991,
-13887,-13887,-15266,-15266,-16102,-16102,-16383,-16383,-16104,-16104,-15268,-15268,-13891,-13891,-11996,-11996,-9615,-9615,-6790,-6790,-3568,-3568,-4,-4,3838,3838,7897,7897,12100,12100,16377,16377,20654,20654,24858,24858,28917,28917,32761,32761,-29210,-29210,-25987,-25987,-23161,-23161,-20779,-20779,-18882,-18882,-17504,-17504,-16667,-16667,-16385,-16385,-16665,-16665,-17500,-17500,-18876,-18876,-20771,-20771,
-23152,-23152,-25977,-25977,-29198,-29198,-32762,-32762,28931,28931,24873,24873,20669,20669,16392,16392,12115,12115,7911,7911,3852,3852,8,8,-3556,-3556,-6779,-6779,-9606,-9606,-11988,-11988,-13885,-13885,-15264,-15264,-16102,-16102,-16383,-16383,-16104,-16104,-15270,-15270,-13893,-13893,-11999,-11999,-9619,-9619,-6794,-6794,-3572,-3572,-10,-10,3833,3833,7891,7891,12094,12094,16371,16371,
20648,20648,24852,24852,28911,28911,32756,32756,-29215,-29215,-25992,-25992,-23164,-23164,-20782,-20782,-18885,-18885,-17505,-17505,-16667,-16667,-16385,-16385,-16664,-16664,-17498,-17498,-18874,-18874,-20768,-20768,-23148,-23148,-25972,-25972,-29194,-29194,-32756,-32756,28937,28937,24879,24879,20675,20675,16398,16398,12121,12121,7917,7917,3858,3858,13,13,-3551,-3551,-6775,-6775,-9602,-9602,-11985,-11985,
-13883,-13883,-15262,-15262,-16101,-16101,-16383,-16383,-16105,-16105,-15271,-15271,-13896,-13896,-12002,-12002,-9623,-9623,-6798,-6798,-3577,-3577,-15,-15,3827,3827,7885,7885,12088,12088,16365,16365,20642,20642,24846,24846,28906,28906,32751,32751,-29220,-29220,-25996,-25996,-23168,-23168,-20785,-20785,-18887,-18887,-17507,-17507,-16668,-16668,-16385,-16385,-16663,-16663,-17497,-17497,-18872,-18872,-20765,-20765,
-23144,-23144,-25968,-25968,-29189,-29189,-32751,-32751,28942,28942,24884,24884,20681,20681,16405,16405,12127,12127,7923,7923,3864,3864,18,18,-3546,-3546,-6770,-6770,-9598,-9598,-11982,-11982,-13880,-13880,-15261,-15261,-16100,-16100,-16383,-16383,-16106,-16106,-15273,-15273,-13898,-13898,-12005,-12005,-9626,-9626,-6803,-6803,-3582,-3582,-20,-20,3822,3822,7879,7879,12082,12082,16359,16359,
20636,20636,24840,24840,28900,28900,32746,32746,-29225,-29225,-26000,-26000,-23172,-23172,-20788,-20788,-18889,-18889,-17508,-17508,-16669,-16669,-16385,-16385,-16662,-16662,-17495,-17495,-18870,-18870,-20762,-20762,-23140,-23140,-25964,-25964,-29184,-29184,-32746,-32746,28948,28948,24890,24890,20687,20687,16411,16411,12133,12133,7929,7929,3869,3869,24,24,-3541,-3541,-6766,-6766,-9595,-9595,-11979,-11979,
-13878,-13878,-15259,-15259,-16099,-16099,-16383,-16383,-16107,-16107,-15274,-15274,-13900,-13900,-12008,-12008,-9630,-9630,-6807,-6807,-3587,-3587,-25,-25,3816,3816,7873,7873,12076,12076,16353,16353,20630,20630,24835,24835,28894,28894,32740,32740,-29230,-29230,-26005,-26005,-23176,-23176,-20791,-20791,-18892,-18892,-17510,-17510,-16670,-16670,-16385,-16385,-16661,-16661,-17493,-17493,-18867,-18867,-20759,-20759,
-23137,-23137,-25960,-25960,-29179,-29179,-32741,-32741,28953,28953,24896,24896,20693,20693,16417,16417,12139,12139,7935,7935,3875,3875,29,29,-3536,-3536,-6762,-6762,-9591,-9591,-11976,-11976,-13876,-13876,-15258,-15258,-16098,-16098,-16383,-16383,-16107,-16107,-15276,-15276,-13902,-13902,-12011,-12011,-9634,-9634,-6811,-6811,-3592,-3592,-31,-31,3810,3810,7867,7867,12070,12070,16347,16347,
20624,20624,24829,24829,28889,28889,32735,32735,-29235,-29235,-26009,-26009,-23179,-23179,-20794,-20794,-18894,-18894,-17512,-17512,-16671,-16671,-16385,-16385,-16661,-16661,-17492,-17492,-18865,-18865,-20756,-20756,-23133,-23133,-25955,-25955,-29174,-29174,-32735,-32735,28959,28959,24902,24902,20699,20699,16423,16423,12145,12145,7941,7941,3880,3880,34,34,-3532,-3532,-6757,-6757,-9587,-9587,-11973,-11973,
-13873,-13873,-15256,-15256,-16098,-16098,-16383,-16383,-16108,-16108,-15277,-15277,-13905,-13905,-12014,-12014,-9637,-9637,-6815,-6815,-3597,-3597,-36,-36,3805,3805,7862,7862,12064,12064,16341,16341,20618,20618,24823,24823,28883,28883,32730,32730,-29239,-29239,-26013,-26013,-23183,-23183,-20797,-20797,-18896,-18896,-17513,-17513,-16671,-16671,-16385,-16385,-16660,-16660,-17490,-17490,-18863,-18863,-20753,-20753,
-23129,-23129,-25951,-25951,-29170,-29170,-32730,-32730,28965,28965,24908,24908,20705,20705,16429,16429,12152,12152,7947,7947,3886,3886,39,39,-3527,-3527,-6753,-6753,-9584,-9584,-11970,-11970,-13871,-13871,-15255,-15255,-16097,-16097,-16383,-16383,-16109,-16109,-15279,-15279,-13907,-13907,-12017,-12017,-9641,-9641,-6820,-6820,-3601,-3601,-41,-41,3799,3799,7856,7856,12058,12058,16335,16335,
20612,20612,24817,24817,28878,28878,32724,32724,-29244,-29244,-26018,-26018,-23187,-23187,-20800,-20800,-18899,-18899,-17515,-17515,-16672,-16672,-16385,-16385,-16659,-16659,-17489,-17489,-18860,-18860,-20750,-20750,-23126,-23126,-25947,-25947,-29165,-29165,-32725,-32725,28970,28970,24914,24914,20711,20711,16435,16435,12158,12158,7953,7953,3892,3892,45,45,-3522,-3522,-6749,-6749,-9580,-9580,-11967,-11967,
-13869,-13869,-15253,-15253,-16096,-16096,-16383,-16383,-16110,-16110,-15280,-15280,-13909,-13909,-12020,-12020,-9645,-9645,-6824,-6824,-3606,-3606,-46,-46,3794,3794,7850,7850,12052,12052,16329,16329,20606,20606,24811,24811,28872,28872,32719,32719,-29249,-29249,-26022,-26022,-23190,-23190,-20803,-20803,-18901,-18901,-17516,-17516,-16673,-16673,-16385,-16385,-16658,-16658,-17487,-17487,-18858,-18858,-20747,-20747,
-23122,-23122,-25942,-25942,-29160,-29160,-32720,-32720,28976,28976,24920,24920,20717,20717,16441,16441,12164,12164,7958,7958,3897,3897,50,50,-3517,-3517,-6745,-6745,-9576,-9576,-11964,-11964,-13866,-13866,-15251,-15251,-16095,-16095,-16383,-16383,-16111,-16111,-15282,-15282,-13912,-13912,-12023,-12023,-9648,-9648,-6828,-6828,-3611,-3611,-52,-52,3788,3788,7844,7844,12046,12046,16323,16323,
20600,20600,24805,24805,28866,28866,32714,32714,-29254,-29254,-26026,-26026,-23194,-23194,-20806,-20806,-18903,-18903,-17518,-17518,-16674,-16674,-16385,-16385,-16657,-16657,-17486,-17486,-18856,-18856,-20744,-20744,-23118,-23118,-25938,-25938,-29155,-29155,-32714,-32714,28981,28981,24925,24925,20723,20723,16447,16447,12170,12170,7964,7964,3903,3903,55,55,-3512,-3512,-6740,-6740,-9573,-9573,-11961,-11961,
-13864,-13864,-15250,-15250,-16094,-16094,-16383,-16383,-16111,-16111,-15284,-15284,-13914,-13914,-12026,-12026,-9652,-9652,-6833,-6833,-3616,-3616,-57,-57,3782,3782,7838,7838,12040,12040,16316,16316,20594,20594,24799,24799,28861,28861,32709,32709,-29259,-29259,-26030,-26030,-23198,-23198,-20809,-20809,-18906,-18906,-17519,-17519,-16675,-16675,-16385,-16385,-16657,-16657,-17484,-17484,-18853,-18853,-20741,-20741,
-23115,-23115,-25934,-25934,-29150,-29150,-32709,-32709,28987,28987,24931,24931,20729,20729,16453,16453,12176,12176,7970,7970,3909,3909,60,60,-3507,-3507,-6736,-6736,-9569,-9569,-11958,-11958,-13862,-13862,-15248,-15248,-16094,-16094,-16383,-16383,-16112,-16112,-15285,-15285,-13916,-13916,-12029,-12029,-9656,-9656,-6837,-6837,-3621,-3621,-62,-62,3777,3777,7832,7832,12034,12034,16310,16310,
20588,20588,24793,24793,28855,28855,32703,32703,-29264,-29264,-26035,-26035,-23202,-23202,-20812,-20812,-18908,-18908,-17521,-17521,-16675,-16675,-16385,-16385,-16656,-16656,-17483,-17483,-18851,-18851,-20738,-20738,-23111,-23111,-25930,-25930,-29145,-29145,-32704,-32704,28993,28993,24937,24937,20735,20735,16459,16459,12182,12182,7976,7976,3914,3914,66,66,-3503,-3503,-6732,-6732,-9565,-9565,-11955,-11955,
-13859,-13859,-15247,-15247,-16093,-16093,-16383,-16383,-16113,-16113,-15287,-15287,-13919,-13919,-12032,-12032,-9659,-9659,-6841,-6841,-3625,-3625,-67,-67,3771,3771,7826,7826,12028,12028,16304,16304,20582,20582,24788,24788,28850,28850,32698,32698,-29268,-29268,-26039,-26039,-23205,-23205,-20815,-20815,-18910,-18910,-17523,-17523,-16676,-16676,-16385,-16385,-16655,-16655,-17481,-17481,-18849,-18849,-20735,-20735,
-23107,-23107,-25925,-25925,-29141,-29141,-32698,-32698,28998,28998,24943,24943,20741,20741,16465,16465,12188,12188,7982,7982,3920,3920,71,71,-3498,-3498,-6727,-6727,-9561,-9561,-11952,-11952,-13857,-13857,-15245,-15245,-16092,-16092,-16383,-16383,-16114,-16114,-15288,-15288,-13921,-13921,-12035,-12035,-9663,-9663,-6845,-6845,-3630,-3630,-73,-73,3766,3766,7821,7821,12022,12022,16298,16298,
20576,20576,24782,24782,28844,28844,32693,32693,-29273,-29273,-26043,-26043,-23209,-23209,-20818,-20818,-18913,-18913,-17524,-17524,-16677,-16677,-16385,-16385,-16654,-16654,-17479,-17479,-18846,-18846,-20732,-20732,-23104,-23104,-25921,-25921,-29136,-29136,-32693,-32693,29004,29004,24949,24949,20747,20747,16471,16471,12194,12194,7988,7988,3925,3925,76,76,-3493,-3493,-6723,-6723,-9558,-9558,-11949,-11949,
-13855,-13855,-15243,-15243,-16091,-16091,-16383,-16383,-16115,-16115,-15290,-15290,-13923,-13923,-12038,-12038,-9667,-9667,-6850,-6850,-3635,-3635,-78,-78,3760,3760,7815,7815,12016,12016,16292,16292,20570,20570,24776,24776,28838,28838,32688,32688,-29278,-29278,-26048,-26048,-23213,-23213,-20821,-20821,-18915,-18915,-17526,-17526,-16678,-16678,-16385,-16385,-16654,-16654,-17478,-17478,-18844,-18844,-20729,-20729,
-23100,-23100,-25917,-25917,-29131,-29131,-32688,-32688,29009,29009,24955,24955,20753,20753,16477,16477,12200,12200,7994,7994,3931,3931,82,82,-3488,-3488,-6719,-6719,-9554,-9554,-11946,-11946,-13852,-13852,-15242,-15242,-16090,-16090,-16383,-16383,-16115,-16115,-15291,-15291,-13926,-13926,-12041,-12041,-9670,-9670,-6854,-6854,-3640,-3640,-83,-83,3754,3754,7809,7809,12010,12010,
};

//#define  Len    10

//u16 I2S2_Tx[Len] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888, 0x9999, 0xAAAA };

/*********************************************************************
 * @fn      I2S2_Init
 *
 * @brief   Init I2S2
 *
 * @return  none
 */
void I2S2_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    I2S_InitTypeDef  I2S_InitStructure={0};

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
    I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;
    I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
    I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;//I2S_MCLKOutput_Disable;
    I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_48k;
    I2S_InitStructure.I2S_CPOL = I2S_CPOL_High;
    I2S_Init(SPI2, &I2S_InitStructure);

    SPI_I2S_DMACmd( SPI2, SPI_I2S_DMAReq_Tx, ENABLE );
    I2S_Cmd(SPI2, ENABLE);
}

/*********************************************************************
 * @fn      DMA_Tx_Init
 *
 * @brief   Initializes the DMAy Channelx configuration.
 *
 * @param   DMA_CHx - x can be 1 to 7.
 *          ppadr - Peripheral base address.
 *          memadr - Memory base address.
 *          bufsize - DMA channel buffer size.
 *
 * @return  none
 */
void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
    DMA_InitTypeDef DMA_InitStructure={0};

    RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

    DMA_DeInit(DMA_CHx);

    DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
    DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = bufsize;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init( DMA_CHx, &DMA_InitStructure );
}

void setup() {
    SPI_I2S_DeInit(SPI2);

    I2S2_Init();


}

void loop() {
    DMA_Tx_Init( DMA1_Channel5, (u32)&SPI2->DATAR, (u32)I2S2_Tx, Len);  
    DMA_Cmd( DMA1_Channel5, ENABLE );
    while( (!DMA_GetFlagStatus(DMA1_FLAG_TC5))){};
    DMA_Cmd( DMA2_Channel1, DISABLE );
}

即可工作。
I2S2_Tx 里面定义的是一个正弦波数据,通过如下C# 代码生成:

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


namespace SinAudio
{
    class Program
    {
        public const int LOOPS = 16;

        static void Main(string[] args)
        {
            double sin,cos;
            for (int i = 0; i < LOOPS * 48; i++) {
                sin = (0.5 + Math.Sin(i * 2 * 3.1415 / 48))* 65535/2;
                //cos = (0.5 + Math.Cos(i * 2 * 3.1415 / 48)) * 65535 / 2;
                Console.Write("{0},{1},", (Int16)sin, (Int16)sin);// (Int16)() * );
                if (i % 32 == 0) {
                    Console.WriteLine("");
                }
            }
            Console.ReadLine();
        }
    }
}

数据可以看成是 48K 16Bits 采样结果,数据是int16 (负数使用补码形式表示)

工作的测试视频如下:

https://www.bilibili.com/video/BV1Yi421m7n8/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54

WordPress 标题中文检测工具

因为设置上的原因,Wordpress 最好不要使用中文作为标题。我的网站设置上支持中文标题,但是因为更换服务器的缘故,中文标题的文章又出现了无法访问的问题。于是,编写一个工具扫描网站上的所有文章,通过检查标题的 url 是否带有“%”来判断是否有中文。

扫描全部页面的方式是通过扫描全部日期归档链接实现的,类似”https://www.lab-z.com/2022/03/”,发送 http 请求之后会对返回的结果进行分析,取出其中的 http://www.lab-z.com/ 为开头的 URL,然后进行检测,如果其中带有 “%”,那么就是带有汉字了。然后就可以根据指示在 WordPress 中手工进行修改。

代码比较简单,有兴趣的可以试试。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Text.RegularExpressions;

namespace WordPressUrlChecker
{
    class Program
    {
        private static readonly HttpClient client = new HttpClient();
        // 定义并初始化一个字符串列表
        private static List<string> UrlToCheck = new List<string> {
                "https://www.lab-z.com/2024/04/",
                // 全部日期
         "https://www.lab-z.com/2005/10/"
        };

        static async Task Main(string[] args)
        {
            string url = "https://www.lab-z.com/";
            //Console.WriteLine(responseBody);
            //string pattern = @"(http|https)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?";
            //https://www.lab-z.com/2006/05/
            string pattern = @"https://www\.lab-z\.com/[^\s]*";

            foreach (string uString in UrlToCheck) {
                Console.WriteLine("Checking in "+uString.ToString());
                string responseBody = await GetRequest(uString);
                MatchCollection matches = Regex.Matches(responseBody, pattern);
                foreach (Match match in matches)
                {
                    string getUrl = match.Value;
                    // 如果字符串中带有 " 那么就截取它之前的所有字符串
                    if (getUrl.IndexOf('\"')!=-1) {
                        getUrl= getUrl.Substring(0, getUrl.IndexOf('\"'));
                        //Console.WriteLine(getUrl);
                    }
                    
                    if (ContainsChinese(getUrl))
                    {
                        Console.WriteLine(getUrl);
                    }
                    
                }
            }
            /*
           
            */

            Console.ReadLine();
        }

        // 发送 HTTP 请求并且得到服务器返回
        static async Task<string> GetRequest(string url)
        {
            HttpResponseMessage response = await client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            string responseBody = await response.Content.ReadAsStringAsync();
            return responseBody;
        }

        // 检查字符串是否含有汉字
        static bool ContainsChinese(string input)
        {
            for (int i = 0; i < input.Length; i++)
            {
                if (input[i] == '%')
                    return true;
            }
            return false;
        }


    }
}