SlimBootLoader 在 LattePanda Mu 上启动 Ubuntu

这次带来是全网唯一的开源 X86 BIOS 方案,通过它能够在 Latte Panda Mu上引导启动 Ubuntu。

为了在Latte Panda Mu使用SlimBootLoader,还需要额外准备如下硬件:

  1. 烧写IFWI的硬件工具,比如:SF100烧录器;
  2. 查看串口Log 的工具,比如:CH343 串口转 USB工具,用于配置安装Ubuntu;SBL 的Shell 没有提供字符或者图形界面,所有的数据都是通过串口进行交互的,所以需要准备串口工具;
  3. 准备2个U盘,一个用于制作 Ubuntu安装盘,我这边使用64GB 的金邦U盘,使用DiskGuinus格式化为 FAT32(注意不要使用 exFat)。将 Ubuntu 解压后全部放入 U盘根目录即可;另外U盘一个用于制作引导脚本。

第一步,生成能够启动的 Latte Panda Mu 的 IFWI。根据【参考1】,配置编译环境,首先确保能够编译通过 QEMU。

a.编译 BIOS 代码:

Python BuildLoader.py build adln

b.生成IFWI,这里没有使用 mFIT 而是直接使用代码替换原代码中的 BIOS Region。

python Platform/AlderlakeBoardPkg/Script/StitchLoader.py -i LattePanda.bin -s Outputs/adln/SlimBootloader.bin -o LattePanda_ifwi.bin -p 0xAA000007

上述的命令中,LattePanda.bin 是DFRobot 提供的原始的IFWI,其中的BIOS是AMI CodeBase;SlimBootloader.bin 是前面从 SBL Build出来的BIOS;最后的LattePanda_ifwi.bin就是生成的新的 IFWI。

d.关机状态下,将USB 转串口工具连接到LattePanda Mu 的PCH UART0 上

c.使用 SF100或者其他烧录器将LattePanda_ifwi.bin烧录到 LattePanda Mu上

d.启动LattePanda Mu后,会很快完成启动显示SBL Logo。 这时候 SBL 并不会从屏幕和键盘进行交互,而是通过串口。在另外一台电脑上打开串口工具即可看到一个文本的Shell界面,输入 boot 命令,然后调整从USB 设备启动例如:

至此,Firmware 方面的准备已经完成。

第二步,安装 Ubuntu 以及设定【参考2】。

  1. 其中需要一些时间,请耐心等待,之后进入下面这个界面。左边是直接在U盘提供的Live Linux 中工作,右边是进行安装,这里需要选择左侧按钮“Try Ubuntu”

从左上角的  Activities 打开一个 Terminal

e.使用 lsblk列出当前的设备,LattePanda Mu 内置了 eMMC,这里是mmcblk0设备

c.使用 sudo disk /dev/mmcblk0擦除硬盘,命令如下:

sudo gdisk /dev/mmcblk0

x

z

y

y

d. 创建GPT分区

sudo gdisk /dev/mmcblk0

o

y

w

y

e.接下来即可进行Ubuntu安装了

f.分区需要特别处理,划分出一个500MB 的FAT32分区用于引导

g.剩余容量减去4GB 左右,设定为  Ext4 分区

h.最后创建一个 4GB 的分区作为  SWAP 分区

i.接下来都选择 Continue

j.安装结束后,选择 Continue Testing

k.接下来使用 lsblk

l.输入如下命令:

Mkdir root
Sudo mount /dev/mmcblk0  root/

m.插入另外的U盘其中放入三个脚本以及内容如下的 cmdline.txt

echo "root=/dev/ mmcblk0  ro quiet splash" > cmdline.txt

n.运行如下批处理(其中的PEM文件来自编译SBL的代码中 )。

python3 GenContainer.py create -cl CMDL:cmdline.txt KRNL:./root/boot/vmlinuz-<kernel-version> INRD:./root/boot/initrd.img-<kernel-version> -k ./SblKeys/OS1_TestKey_Priv_RSA3072.pem -a RSA3072_PKCS1_SHA2_384 -t CLASSIC -o sbl_os
sudo cp sbl_os ./root/boot
sudo umount ./root

这里提供一个下载方便直接测试:

o.最终,拔掉U盘,即可启动

上述操作的完整视频:

这里只是简单的第一步,自行编译BIOS并且启动了X86的 Ubuntu,很多功能还没有调试,可能在使用中遇到问题(比如, X4 PCIE 支持, Audio 的支持)。如果你对性能和体积有一些要求,或者是对于BIOS定制有一些特别要求,不妨考虑 DFRobot推出的 LattePanda MU,它由一块核心板以及底板构成,厂家提供了电路图以及对应接口,可以很方便的实现定制。

本文提到的代码可以在 https://gitee.com/willok/slimbootloader 下载到,由天杀提供。

参考:

  1. https://www.lab-z.com/sblb/
  2. https://slimbootloader.github.io/how-tos/boot-ubuntu.html#setup-spn-os-container-boot

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;
        }


    }
}

CH32V305 模拟 Ch372的例子

前面介绍过如何使用 Arduino 环境进行 Ch32V305 的开发,这次带来的是一个 CH32V305 Arduino 实现模拟 Ch372的例子,参考的是 Ch32v307EVT 中的HS Device的代码。根据Exam中的CH32V30x_List.txt描述,这个CH372例子是模拟自定义USB设备(CH372设备),端点1,3下传,2,4上传,端点1下传的数据从端点3上传,不取反,端点2下传的数据从端点4上传,取反。但是,应该是描述存在错误,实际代码不是这样。

首先,改造代码,然后烧写到板子上。使用 USBView 查看,端点1 有一个 OUT 和 IN; 端点3是OUT,端点4是 IN, 端点5是OUT, 端点6是IN.

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x01  -> Direction: OUT - EndpointID: 1
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x81  -> Direction: IN - EndpointID: 1
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x03  -> Direction: OUT - EndpointID: 3
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x84  -> Direction: IN - EndpointID: 4
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x05  -> Direction: OUT - EndpointID: 5
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x86  -> Direction: IN - EndpointID: 6
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

对应处理的代码在 ch32v30x_usbhs_device.c 文件中。下面对代码进行研读。

代码中Ch32端点1 OUT 收到的数据直接放到端点1 IN中。

                    /* end-point 1 data out interrupt */
                    case USBHS_UIS_TOKEN_OUT | DEF_UEP1:
                        if ( intst & USBHS_UIS_TOG_OK )
                        {
                            /* Write In Buffer */
                            USBHSD->UEP1_RX_CTRL ^= USBHS_UEP_R_TOG_DATA1;
                            RingBuffer_Comm.PackLen[RingBuffer_Comm.LoadPtr] = USBHSD->RX_LEN;
                            RingBuffer_Comm.LoadPtr ++;
                            if(RingBuffer_Comm.LoadPtr == DEF_Ring_Buffer_Max_Blks)
                            {
                                RingBuffer_Comm.LoadPtr = 0;
                            }
                            USBHSD->UEP1_RX_DMA = (uint32_t)(&Data_Buffer[(RingBuffer_Comm.LoadPtr) * DEF_USBD_HS_PACK_SIZE]);
                            RingBuffer_Comm.RemainPack ++;
                            if(RingBuffer_Comm.RemainPack >= DEF_Ring_Buffer_Max_Blks-DEF_RING_BUFFER_REMINE)
                            {
                                USBHSD->UEP1_RX_CTRL = ((USBHSD->UEP1_RX_CTRL) & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_NAK;
                                RingBuffer_Comm.StopFlag = 1;
                            }
                        }
                        break;

端点3收到的数据取反后放到端点4上。

                  /* end-point 3 data out interrupt */
                    case USBHS_UIS_TOKEN_OUT | DEF_UEP3:
                        if ( intst & USBHS_UIS_TOG_OK )
                        {
                            len = (uint16_t)(USBHSD->RX_LEN);
                            USBHSD->UEP3_RX_CTRL ^= USBHS_UEP_R_TOG_DATA1;
                            USBHSD->UEP3_RX_CTRL = ((USBHSD->UEP3_RX_CTRL) & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_NAK;
                            for(i=0; i<len; i++)
                            {
                                USBHS_EP4_Tx_Buf[i] = ~USBHS_EP3_Rx_Buf[i];
                            }
                            USBHSD->UEP4_TX_LEN = len;
                            USBHSD->UEP4_TX_CTRL = (USBHSD->UEP4_TX_CTRL & ~USBHS_UEP_T_RES_MASK) | USBHS_UEP_T_RES_ACK;
                        }
                        break;

类似的端点5收到的数据取反,通过端点6上传

                    /* end-point 5 data out interrupt */
                    case USBHS_UIS_TOKEN_OUT | DEF_UEP5:
                        if ( intst & USBHS_UIS_TOG_OK )
                        {
                            len = (uint16_t)(USBHSD->RX_LEN);
                            USBHSD->UEP5_RX_CTRL ^= USBHS_UEP_R_TOG_DATA1;
                            USBHSD->UEP5_RX_CTRL = ((USBHSD->UEP5_RX_CTRL) & ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_NAK;
                            for(i=0; i<len; i++)
                            {
                                USBHS_EP6_Tx_Buf[i] = ~USBHS_EP5_Rx_Buf[i];
                            }
                            USBHSD->UEP6_TX_LEN = len;
                            USBHSD->UEP6_TX_CTRL = (USBHSD->UEP6_TX_CTRL & ~USBHS_UEP_T_RES_MASK) | USBHS_UEP_T_RES_ACK;
                        }
                        break;

从代码上可以看出,Exam中的描述是存在一些问题的。

之后,再编写一个VC 代码,进行速度测试:

代码来自 Ch569 的EVT  Package, 有部分修改:

// 2003.09.08, 2003.12.28
//****************************************
//**  Copyright  (C)  W.ch  1999-2005   **
//**  Web:  http://www.winchiphead.com  **
//****************************************
//**  DLL for USB interface chip CH375  **
//**  C, VC5.0                          **
//****************************************
//
// USB总线接口芯片CH375的数据块测试程序 V1.0
// 南京沁恒电子有限公司  作者: W.ch 2003.12
// CH375-BLK  V1.0
// 运行环境: Windows 98/ME, Windows 2000/XP
// support USB chip: CH372/CH375
//

#include	<windows.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<conio.h>
#include	<winioctl.h>

#include	"CH375DLL.H"			
#pragma comment(lib,"CH375DLL")
#define		TEST_DATA_LEN		4096
#define		TEST_NUM     		1000
unsigned char	mReadBuf[TEST_DATA_LEN];
unsigned char	mWriteBuf[TEST_DATA_LEN];
//程序入口
void main (int argc,char **argv )
{
	unsigned long mLength, mTestCount, mErrCnt,mArg,mFirstTick,mLastTick;
	long long mTotal=0;
	double          speed;
	USHORT          mCount = 0;
	printf( "\nCH372/CH375 Bulk Data Test Program V1.1 ,   Copyright (C) W.ch 2004.12\n" );
	printf( "test data correctness \n" );
	mArg = TEST_DATA_LEN;

// 需要使用DLL则需要先加载,没有此句则会自动加载
	printf( "*** CH375OpenDevice: 0# \n" );
	if ( CH375OpenDevice( 0 ) == INVALID_HANDLE_VALUE ) return;  /* 使用之前必须打开设备 */

	memset(mWriteBuf, 0xFF, sizeof(mWriteBuf));
	
	mErrCnt=0;

	printf( "*** CH375ReadData: 1000 times 4M Byte ***\n" );

	mTotal = 0.0;
	for ( mTestCount=0; mTestCount < TEST_NUM; ++mTestCount )  // 循环测试
	{
		if(mTestCount == 0)
		{
			mFirstTick=GetTickCount();
		}
		mLength = mArg;
		if (CH375WriteEndP(0, 1, mWriteBuf, &mLength))  // 写入成功
		{
			mTotal += mLength;
			if (mLength == 0)
			{
				Sleep(0);  //放弃当前线程的时间片,防止CPU出现100%情况
			}
		}
		else
		{  // 写操作失败
			printf("S1-T%0ld-C%ld CH375WriteEndP return error, length=%d\n", mTestCount, mTestCount, mTotal);
		}

		mLength = mArg;
		if (CH375ReadEndP(0, 1, mReadBuf, &mLength))  // 接收成功
		{
			mTotal += mLength;
			if(mLength == 0 )
			{
				Sleep(0);  //放弃当前线程的时间片,防止CPU出现100%情况
			} 
		}
		else 
		{
			
			printf( "S1-T%0ld-C%ld CH375ReadData return error, length=%d\n", mTestCount, mTestCount, mTotal );
		}


		
	}
	
	mLastTick =GetTickCount();
	mLastTick = mLastTick - mFirstTick;
	speed=1000;
	speed=speed*mTotal/mLastTick;
	printf( "*** average speed = %7.1f MBytes/Sec, total=%lld bytes\n", speed/1000/1000, mTotal);
	
	CH375CloseDevice( 0 );

	printf( "\nExit.\n" );
	_getch();
	
}

完整的源代码和可执行 EXE, 建议有需要的朋友重新编译。

完整的 Arduino 代码:

清醒鼠标

在有些情况下,比如:功耗测试。我们需要让系统一直处于 S0 状态,最好的方法莫过于摇晃鼠标。这次的作品就是一款基于 Dell 廉价鼠标的扩展方案,它每隔10秒摇晃一次鼠标,让你的系统不会休眠。

整体方案设计思路非常简单:鼠标的USB进入CH334 USB HUB 芯片之后,转出来2路USB信号,一路给PAW3515芯片,这是一个鼠标芯片;另外一路给CH552。我们通过编程,让Ch552将自身模拟为鼠标和键盘设备。Ch552键盘设备用于接收主机发过来的键盘LED控制信号;Ch552鼠标设备则是用于模拟鼠标的动作。

电路图如下,左上角是PAW3515鼠标的最小系统,右上角是Ch334 USB Hub芯片的最小系统,下方则是Ch552的最小系统。

PCB 设计如下:


这个的尺寸和戴尔MS116-T 的有线光电鼠标内部的PCB完全相同,用我们的这个PCB替换掉原版即可。焊接之后如下:

编写代码如下:

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include "src/userUsbHidKeyboardMouse/USBHIDKeyboardMouse.h"

// 10秒触发一次
#define INTERVAL 10000UL
// 每次动作间隔 20ms
#define ACTION 50

// 触发计时
unsigned long int Elsp = 0;
// 记录当前是否已经触发过
boolean StageAssert[4] = {false, false, false, false};

uint8_t LastLed;
unsigned long LEDAssertElsp = 0;
// 触发状态标志
boolean StartWaken = false;

void setup() {
  USBInit();
  Serial0_begin(115200);
  delay(3000);
  Serial0_println("start");
  LastLed = LedStatus;
}

// 判断是否满足条件
boolean MoveCondition(byte stage) {
  // 条件1. 大于 INTERVAL 给出的时间
  // 条件2. 小于 INTERVAL + (stage + 1)*ACTION 给出的时间
  // 条件3. 之前没有触发过
  if ((millis() - Elsp > INTERVAL + stage * ACTION) &&
      (millis() - Elsp < INTERVAL + (stage + 1)*ACTION) &&
      (StageAssert[stage] == false)) {
    // 标记已经触发过
    StageAssert[stage] = true;
    return true;
  }
  return false;
}


void loop() {
  // 如果发生了 LED 切换
  if (LastLed != LedStatus) {
    Serial0_println("B");
    // 如果 1秒内发生了切换
    if (millis() - LEDAssertElsp < 1000) {
      // 触发状态反转
      StartWaken = !StartWaken;
      Serial0_print(StartWaken);
      Serial0_println("C");
      if (StartWaken == false) {
        // 重置
        for (byte i = 0; i < 4; i++) {
          StageAssert[i] = false;
        }
      } else {
        Elsp = millis();
      }
    }
    // 记录切换时间
    LEDAssertElsp = millis();
    LastLed = LedStatus;
    Serial0_println("E");

  }
  if (StartWaken != false) {
    if (MoveCondition(0) == true) {
      // 向右移动
      Mouse_move(100, 0);
    } else   if (MoveCondition(1) == true) {
      // 向下移动
      Mouse_move(0, 100);
    } else   if (MoveCondition(2) == true) {
      // 向左移动
      Mouse_move(-100, 0);
    } else   if (MoveCondition(3) == true) {
      // 向上移动
      Mouse_move(0, -100);
      // 重置
      for (byte i = 0; i < 4; i++) {
        StageAssert[i] = false;
      }
      Elsp = millis();
    }
  }
}

对应的功能有一个开关,使用连续按下键盘上的 Caps/NumLock/Scroll 两次即可触发。这里使用到了USB键盘的一个有趣的特性:操作系统会在全部的键盘中进行同步。比如,系统中有3个USB键盘,当你在其中一个键盘按下 Caps 按键之后,操作系统会通知其余两个键盘要求更改 Caps LED。使用USB抓包软件可以看到,下图就是 Windows主机端用于通知 Ch552 键盘要求更改LED的命令,我按下2次,Byte0 是Report ID, Byte1 是键盘LED的状态。

对应的代码在 \Ch552MSWaken\src\userUsbHidKeyboardMouse\USBHIDKeyboardMouse.c ,收到来自 EndPoint 1 的 Out 数据后,会更改   LedStatus 数值,以便主程序进行处理。

void USB_EP1_OUT() {

  //Serial0_println("A");

  LedStatus=Ep1Buffer[1]; //LABZ_Debug

  if (U_TOG_OK) // Discard unsynchronized packets

  {

  }

}

经过改造,你得到的是一个表面上看起来和正经鼠标一摸一样的鼠标,同时它也有着和正经鼠标一摸一样的功能,但是当你触发之后,它会每隔10秒晃动一次。

电路图和PCB 下载

完整代码下载

工作的测试视频可以在这里看到

https://www.bilibili.com/video/BV1zz421m7Xc

一种制作 Memtest86 启动盘的方法

Memtest86 是一个非常优秀的内存测试软件,可以用来测试内存的稳定性。美中不足的是它自带的U盘制作软件存在一些缺陷。

  Memtest86 官方启动盘制作界面

制作完成界面

分区结构

可以看到,对于 64G U盘只使用了最前面的 256MB,后面的完全浪费掉了。

为了避免这种情况,经过研究,可以手工制作 MemTest86 的光盘镜像,然后配合 Ventoy 制作启动盘,在使用时,先启动到 Ventoy ,然后选择启动 MemTest86 的镜像即可。

这样做出来的U盘不会浪费空间,你可以在上面继续放置 Windows 安装文件等等。

当然,这次介绍的方法还是有一定局限性的:因为模拟为光盘,是只读设备,因为无法保存 Log 或者测试结果。如果你有保存结果或者截图的需求,那么还是需要用官方提供的方法。

工作的测试视频

本文提到的 Memtest86 ISO 可以在这里下载:

NAudio 编写指定播放音频的设备

这个例子实现了在用户指定的设备上播放音频。比如,可以选择从耳机中播放。

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

using NAudio;
using NAudio.Wave;

namespace ConsoleApp1
{
    class Program
    {
        
        static void Main(string[] args)
        {
            for (int n = 0; n < WaveOut.DeviceCount; n++)
            {
                Console.WriteLine(n + " : " + WaveOut.GetCapabilities(n).ProductName);
            }
            while (true)
            {
                int i = 0;
                do
                {
                    Console.WriteLine("Choose device:");
                } while (!int.TryParse(Console.ReadLine(), out i) || i >= WaveOut.DeviceCount || i < 0);

                WaveOutEvent waveOutEvent = new WaveOutEvent();
                waveOutEvent.DeviceNumber = i;

                Console.WriteLine("Play on "+WaveOut.GetCapabilities(i).ProductName);

                using (var audioFileReader = new AudioFileReader(@"c:\temp\1990.mp3"))
                {
                    // Play mp3 in the device
                    waveOutEvent.Init(audioFileReader);
                    waveOutEvent.Play();

                    // Wait until the end
                    while (waveOutEvent.PlaybackState == PlaybackState.Playing)
                    {
                        System.Threading.Thread.Sleep(100);
                    }
                }
            }
        }
    }
}