ch9350的测试小板

之前使用 WCH 家的 Ch9350 制作了一些板卡,但是这个芯片焊接对于我来说是有一些难度的。因此,这里专门设计了一个 ch9350的测试小板能够方便的进行原型验证。

电路图如下,相当于这个芯片的最小系统,外围软件只有2个电容:

PCB设计如下:

3D 预览如下:

焊接实物如下:

可以看到这个板子的优点如下:

  1. 外部线路简单,只预留了必要的引脚;
  2. 引出了2个USB端口,可以同时支持2个USB HID 设备
  3. 体型小巧便于在原型上使用

电路图和 PCB 下载:

LunarLake UART Shell 测试工具

这个工具是用来给 LunarLake Shell 下进行 Uart 测试的工具,它通过 PCH UART输出字符。

在使用之前,请保证:

  1. PCH 对应的 UART 已经 Enable, 在 Shell 下能够看到对应的PCI Controller;
  2. UART 对应的 GPIO 已经设置为 Native UART 功能。

使用方法:

zu4l <UART 编号>

例如, zu4r 0 将会从第一个 PCH UART对应的引脚,以 115200 波特率输出 www.lab-z.com。

程序在这里下载(无源代码)

ESP32-C3 Windows7 驱动

ESP32-C3 不支持 Windows 7, 意思是插上之后设备管理器中会出现Yellow Bang,无法识别串口。这样会给我们的开发和调试造成一定困难。经过研究可以通过修改Arduino Drivers 目录下面的 arduino-org.inf 来解决。

具体操作如下:

1.在[Strings]增加如下两行

LABZ.bootloader.name="Arduino ESP32-C3 bootloader"
LABZ.sketch.name="Arduiono ESP32-C3"

2.在 [DeviceList] 增加如下两行:

%LABZ.bootloader.name%=DriverInstall, USB\VID_2A03&PID_003A
%LABZ.sketch.name%=DriverInstall, USB\VID_2A03&PID_803A&MI_00

3.在[DeviceList.NTamd64]增加如下代码:

%LABZ.bootloader.name%=DriverInstall, USB\VID_303A&PID_1001
%LABZ.sketch.name%=DriverInstall, USB\VID_303A&PID_1001&MI_00

然后再像其他驱动一样安装即可:

修改后的 INF可以在这里下载:

MeteorLake-P UART Shell 测试工具

这个工具是用来给 MeteorLake-P Shell 下进行 Uart 测试的工具,它通过 PCH UART输出字符。

在使用之前,请保证:

  1. PCH 对应的 UART 已经 Enable, 在 Shell 下能够看到对应的PCI Controller;
  2. UART 对应的 GPIO 已经设置为 Native UART 功能。

使用方法:

mtlut <UART 编号>

例如, mtlut 0 将会从第一个 PCH UART对应的引脚,以 115200 波特率输出 www.lab-z.com。

程序在这里下载(无源代码)

Sound Wire

SoundWire 是 MIPI 协会推出的关于音频的规范,有时候会被缩写为 SNDW。这个规范定义的总线有如下好处:

  1. 线路简单,一个 Clock 和 一个 Data线即可进行传输,这样能够极大降低产品成本(布线更简单,节省PCB);
  2. 速度快(最高12.288 MHz),使用类似 DDR(Double)的传输方式;
  3. 可以扩展进行  Clock Scaling 或者多个 Data Lane,能够轻松增加带宽;
  4. 单个 master 最多可以支持 11个 Slave;
  5. 支持slave to slave的数据传输方式。

基本拓扑结构是【参考1】:其中的 ADC 可以理解为麦克风,DAC 可以理解为喇叭,下图就是一个 SoundWire Master 连接了2个麦克风和三个喇叭:

复杂一点:Multi Lanes拓扑如下, Clock 提供给 Codec/BT/DSP,然后多个Lane(Data[0]/Data[1]/Data[2]), 这样能够提升一个周期数据的传输数量。

因此,对于 PC 来说,SoundWire 是重新定义了 SoC 或者说南桥对 Codec 的数据传输协议,取代了之前的 HDA 总线。对于软件或者 BIOS 来说应该是透明的。

参考:

  1. https://elinux.org/images/8/80/Introduction-to-SoundWire-Vinod-Koul-Linaro.pdf
  1. https://zhuanlan.zhihu.com/p/535358005
  2. https://bbs.eetop.cn/thread-928626-1-1.html

UEFI TIPS: PciLib

如果你在编写代码时有使用到类似 PciRead8() 这样的函数,那么可以通过在 INF 中加入 PciLib:

[LibraryClasses]
  UefiLib
  ShellCEntryLib
  IoLib
  PciLib

然后在代码中使用 include 调用头文件即可:

 #include <Library/PciLib.h>

具体的 PciLib.h 定义如下:


#define PCI_LIB_ADDRESS(Bus, Device, Function, Register)   \
  (((Register) & 0xfff) | (((Function) & 0x07) << 12) | (((Device) & 0x1f) << 15) | (((Bus) & 0xff) << 20))

RETURN_STATUS
EFIAPI
PciRegisterForRuntimeAccess (
  IN UINTN  Address
  );

UINT8
EFIAPI
PciRead8 (
  IN      UINTN  Address
  );

UINT8
EFIAPI
PciWrite8 (
  IN      UINTN  Address,
  IN      UINT8  Value
  );

UINT8
EFIAPI
PciOr8 (
  IN      UINTN  Address,
  IN      UINT8  OrData
  );

UINT8
EFIAPI
PciAnd8 (
  IN      UINTN  Address,
  IN      UINT8  AndData
  );

UINT8
EFIAPI
PciAndThenOr8 (
  IN      UINTN  Address,
  IN      UINT8  AndData,
  IN      UINT8  OrData
  );

UINT8
EFIAPI
PciBitFieldRead8 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit
  );

UINT8
EFIAPI
PciBitFieldWrite8 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit,
  IN      UINT8  Value
  );

UINT8
EFIAPI
PciBitFieldOr8 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit,
  IN      UINT8  OrData
  );

UINT8
EFIAPI
PciBitFieldAnd8 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit,
  IN      UINT8  AndData
  );

UINT8
EFIAPI
PciBitFieldAndThenOr8 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit,
  IN      UINT8  AndData,
  IN      UINT8  OrData
  );

UINT16
EFIAPI
PciRead16 (
  IN      UINTN  Address
  );

UINT16
EFIAPI
PciWrite16 (
  IN      UINTN   Address,
  IN      UINT16  Value
  );

UINT16
EFIAPI
PciOr16 (
  IN      UINTN   Address,
  IN      UINT16  OrData
  );

UINT16
EFIAPI
PciAnd16 (
  IN      UINTN   Address,
  IN      UINT16  AndData
  );

UINT16
EFIAPI
PciAndThenOr16 (
  IN      UINTN   Address,
  IN      UINT16  AndData,
  IN      UINT16  OrData
  );

UINT16
EFIAPI
PciBitFieldRead16 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit
  );

UINT16
EFIAPI
PciBitFieldWrite16 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT16  Value
  );

UINT16
EFIAPI
PciBitFieldOr16 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT16  OrData
  );

UINT16
EFIAPI
PciBitFieldAnd16 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT16  AndData
  );

UINT16
EFIAPI
PciBitFieldAndThenOr16 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT16  AndData,
  IN      UINT16  OrData
  );

UINT32
EFIAPI
PciRead32 (
  IN      UINTN  Address
  );

UINT32
EFIAPI
PciWrite32 (
  IN      UINTN   Address,
  IN      UINT32  Value
  );

UINT32
EFIAPI
PciOr32 (
  IN      UINTN   Address,
  IN      UINT32  OrData
  );

UINT32
EFIAPI
PciAnd32 (
  IN      UINTN   Address,
  IN      UINT32  AndData
  );

UINT32
EFIAPI
PciAndThenOr32 (
  IN      UINTN   Address,
  IN      UINT32  AndData,
  IN      UINT32  OrData
  );

UINT32
EFIAPI
PciBitFieldRead32 (
  IN      UINTN  Address,
  IN      UINTN  StartBit,
  IN      UINTN  EndBit
  );

UINT32
EFIAPI
PciBitFieldWrite32 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT32  Value
  );

UINT32
EFIAPI
PciBitFieldOr32 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT32  OrData
  );

UINT32
EFIAPI
PciBitFieldAnd32 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT32  AndData
  );

UINT32
EFIAPI
PciBitFieldAndThenOr32 (
  IN      UINTN   Address,
  IN      UINTN   StartBit,
  IN      UINTN   EndBit,
  IN      UINT32  AndData,
  IN      UINT32  OrData
  );

UINTN
EFIAPI
PciReadBuffer (
  IN      UINTN  StartAddress,
  IN      UINTN  Size,
  OUT     VOID   *Buffer
  );

UINTN
EFIAPI
PciWriteBuffer (
  IN      UINTN  StartAddress,
  IN      UINTN  Size,
  IN      VOID   *Buffer
  );

MCP4922 SPI DAC 在 FireBeetle ESP32 上的测试

之前有介绍过,ESP32 没有 DAC 引脚,但是可以通过 PWM 来进行模拟,这次入手了 MCP4922 这款双通道 DAC 芯片。它使用 SPI 接口,提供12Bit 的输出精度。芯片引脚定义如下:

引脚编号名称介绍
1Vdd供电 2.7-5.5V
2NC
3CS#SPI CS 引脚
4SCKSPI Clock
5SDISPI MOSI
6NC
7NC
8LDAC#锁存,只有为低时,芯片才会从 Vouta和Voutb 输出电压
9SHDN#关闭,为低芯片停止工作
10Voutb输出B电压引脚
11Vrefb输出B 的参考电压,可以接入Vss到Vdd 以内的电压.这次测试接入了Vdd(3.3V)
12Vss
13Vrefa输出A 的参考电压,,可以接入Vss到Vdd 以内的电压.这次测试接入了Vdd(3.3V)
14Vouta输出A的电压引脚

这次测试基于 https://github.com/michd/Arduino-MCP492X 提供的库文件,代码如下:

#include <MCP492X.h> // Include the library

#define PIN_SPI_CHIP_SELECT_DAC 25 // Or any pin you'd like to use

MCP492X myDac(PIN_SPI_CHIP_SELECT_DAC);

void setup() {
  // put your setup code here, to run once:
  myDac.begin();
}

void loop() {
  for (int i=0;i<4096;i++) {
      myDac.analogWrite(0, i);
      myDac.analogWrite(1, 4095-i);
      delay(1);
}

代码运行后使用示波器进行测试,结果如下:

修改代码,loop如下:

void loop() {
   myDac.analogWrite(0, 0); 
   myDac.analogWrite(0, 4095);
}

可以看到波形如下:

参考:

  1. https://www.microchip.com/en-us/product/MCP4922#
  2. https://ua726.co.uk/2012/12/22/testing-the-mcp4922-with-an-arduino/

本文使用的库:

MCP492X Datasheet

Ch32v305 上手指南

最近拿到一块 Ch32V305 的 EVT 板子,芯片的具体型号是CH32V305FBP6 ,它使用TSSOP20 封装,对于DIY 非常友好。这里介绍如何烧录和运行一个 USB KBMS 的代码。

代码来自官方的 EVT Package: CH32V307EVT\EVT\EXAM\USB\USBHS\DEVICE\CompositeKM

特别注意:和其他芯片不同,这款芯片必须使用 WCK LinkE 进行烧录。

MounRiver Studio 自带的烧录软件

需要特别注意的是在《CH32V30x 评估板说明及应用参考》上有提到还可以使用WCH-LinkUtility.exe 进行下载,但是实验中我发现这个会和 MounRiver Studio 自带的烧录软件冲突。打开WCH-LinkUtility后会提示你需要升级 WCH LinkE ,否则不能进行烧录,升级之后再使用MounRiver Studio ,它又会提示需要升级WCH LinkE ,否则不能进行烧录。

最后放上一个独立版本的键盘鼠标代码。

目录比较差异提取工具

起因:每次维护网站都需要进行全站备份,目前使用压缩后下载全部文件的方法。但是这样每次都要下载很大的文件,费时费力。因此需要一种方法能够让我只下载有差异的部分。为此编写了一个目录比较并且提取差异的工具,例如:我基于 edk2202308 修改出来了 edk2202308modified ,然后用工具进行比较,比较后的差别会自动提取到 100916 目录下。

需要注意有如下几点:

  1. 文件只是比较大小,并没有对内容进行比较;
  2. 比较后放置差异的目录是根据当前时间生成的,每次运行目录不同;
  3. 工具不是很完善,没有处理文件被删除掉的情况
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace ComparePatch
{
    class Program
    {
        static void Printusage()
        {
            Console.WriteLine("Directory compare and patch utility");
            Console.WriteLine("Usage:");
            Console.WriteLine("cap Latest LastBackup PatchDir");
            Console.WriteLine("");
        }

        static void CopyFileTo(String Filename, String TargetDir)
        {

        }

        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                Printusage();
                Environment.Exit(0);
            }
            // 指定要枚举的目录
            string Latest, LastBackup, PatchDir;
            Latest = args[0];
            LastBackup = args[1];
            if (Latest[Latest.Length - 1] != '\\')
            {
                Latest = Latest + '\\';
            }
            if (LastBackup[LastBackup.Length - 1] != '\\')
            {
                LastBackup = LastBackup + '\\';
            }
            PatchDir = Directory.GetCurrentDirectory() + "\\" + DateTime.Now.ToString("HHmmss") + "\\";
            Console.WriteLine("Compare " + LastBackup + " with " + Latest);
            Console.WriteLine("And put files into " + PatchDir);

            try
            {
                // 使用Directory类的GetFiles方法获取所有文件,包括所有子目录
                string[] filesInLastBackup = Directory.GetFiles(LastBackup, "*", SearchOption.AllDirectories);
                Console.WriteLine("Files in " + LastBackup + " and all subdirectories:");

                // 遍历所有文件并打印
                foreach (string file in filesInLastBackup)
                {
                    // 情况1
                    if (File.Exists(file.Replace(LastBackup, Latest)))
                    {
                        // 新目录下存在该文件
                        // 创建一个FileInfo对象
                        FileInfo NewFileInfo = new FileInfo(file);
                        FileInfo OldFileInfo = new FileInfo(file.Replace(LastBackup, Latest));
                        String PatchFileName = file.Replace(LastBackup, PatchDir);
                        if (NewFileInfo.Length != OldFileInfo.Length)
                        {
                            // 文件大小不同,判定为不同
                            Console.WriteLine("=========================");
                            Console.Write("New file would be copy to");
                            Console.WriteLine(PatchFileName);
                            if (!Directory.Exists(Path.GetDirectoryName(PatchFileName))) {
                                Directory.CreateDirectory(Path.GetDirectoryName(PatchFileName));
                            }
                            File.Copy(file, PatchFileName); 
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("The process failed: {0}", e.ToString());
            }

            // Console.ReadKey();
        }
    }
}

可执行文件:

如果你有更专业的需求,可以考虑直接使用 GIT 这种来进行维护。