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&lt;===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x01  -> Direction: OUT - EndpointID: 1
bmAttributes:                      0x02  -> Bulk Transfer Type
wMaxPacketSize:                  0x0200 = 0x200 max bytes
bInterval:                         0x00

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

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

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

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

          ===>Endpoint Descriptor&lt;===
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 &amp; 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)(&amp;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) &amp; ~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 &amp; 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) &amp; ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_NAK;
                            for(i=0; i&lt;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 &amp; ~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 &amp; 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) &amp; ~USBHS_UEP_R_RES_MASK) | USBHS_UEP_R_RES_NAK;
                            for(i=0; i&lt;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 &amp; ~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);
                    }
                }
            }
        }
    }
}

NAudio 枚举音频输入输出设备

使用 VS2019 C#,安装 NAudio库后在  Console 下面枚举 Audio Input 和 Output 的代码:

完整代码:

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

using NAudio;
using NAudio.CoreAudioApi;
using MMDevice = NAudio.CoreAudioApi.MMDevice;
using MMDeviceEnumerator = NAudio.CoreAudioApi.MMDeviceEnumerator;

using NAudio.Wave;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            //定义一个 dataFlow对象
            MMDeviceEnumerator enumerator = new MMDeviceEnumerator();

            IEnumerable<MMDevice> playBackList;
            //获取声音输出设备
            playBackList = enumerator.EnumerateAudioEndPoints(DataFlow.Render, NAudio.CoreAudioApi.DeviceState.Active).ToArray();

            Console.WriteLine("Output device list:");
            foreach (var ad in playBackList)
            {
                Console.WriteLine("   "+ad.FriendlyName.ToString());
            }

            IEnumerable < MMDevice > captureList;
            captureList = enumerator.EnumerateAudioEndPoints(DataFlow.Capture, NAudio.CoreAudioApi.DeviceState.Active).ToArray();;
            Console.WriteLine("Input device list:");
            foreach (var ad in captureList)
            {
                Console.WriteLine("   " + ad.FriendlyName.ToString());
            }

            // 更简单的方法
for (int n = 0; n < WaveOut.DeviceCount; n++)
            {
                Console.WriteLine(n + " : " + WaveOut.GetCapabilities(n).ProductName);
            }
            for (int n = 0; n < WaveIn.DeviceCount; n++)
            {
                Console.WriteLine(n + " : " + WaveIn.GetCapabilities(n).ProductName);
            }


            Console.ReadLine();

        }
    }
}

运行结果:

在系统中直接查看:

参考:

  1. https://blog.csdn.net/u011465910/article/details/127859286   C# Audio全自动化测试——1. 枚举Audio设备
  2. https://cloud.tencent.com/developer/information/%E5%9C%A8%E6%B7%BB%E5%8A%A0%2F%E5%88%A0%E9%99%A4%E5%A3%B0%E9%9F%B3%E8%AE%BE%E5%A4%87%E5%90%8E%EF%BC%8C%E5%A6%82%E4%BD%95%E5%9C%A8NAudio%E4%B8%AD%E9%80%89%E6%8B%A9%E6%AD%A3%E7%A1%AE%E7%9A%84%E5%A3%B0%E9%9F%B3%E8%BE%93%E5%87%BA%E8%AE%BE%E5%A4%87%EF%BC%9F 在添加/删除声音设备后,如何在NAudio中选择正确的声音输出设备?
  3. https://blog.csdn.net/LiChangGG/article/details/100132901

ESP32Sx USB Host 发送数据

第一步,使用 usb_host_transfer_alloc() 函数创建准备数据的结构体,函数原型如下:

esp_err_tusb_host_transfer_alloc(size_t data_buffer_size, int num_isoc_packets, usb_transfer_t **transfer)

第一个参数是数据大小;第二个是 ISO 数据包的个数,如果不用音频视频这里为0;第三个是数据结构体。

第二步,填写usb_transfer_t对应的结构体;

第三步,发送。可以使用 usb_host_transfer_submit_control 和 usb_host_transfer_submit。

需要特别注意的是,如果你使用 usb_host_transfer_submit_control 发送 Setup数据,那么它的 Size计算上比较特别。例如,我要发送下面 05 83 00 00 00 00 作为有效数据,那么 esp_err_tusb_host_transfer_alloc给的size_t 参数必须是 8+6,而发送的缓冲区中是 21 09 05 03 01 00 06 00 05 83 00 00 00 00。

此外,如果使用 usb_host_transfer_submit(这个功能是对非0的端点发送数据),esp_err_tusb_host_transfer_alloc就无需考虑这么多,要发送多少直接作为参数即可。

参考:

1. https://docs.espressif.com/projects/esp-idf/zh_CN/v5.1/esp32s3/api-reference/peripherals/usb_host.html

Step to UEFI (293)DSDT 是如何打包到 UEFI 中的

UEFI 中的 AML 文件是如何打包的

以OvmfPkg 中的 Bhyve 为研究对象,编译命令:

build -a X64 -p OvmfPkg\Bhyve\BhyveX64.dsc -t VS2019

在\OvmfPkg\Bhyve\AcpiTables\ 目录下我们能看到 DSDT.ASL 这样的文件。对应的,在 \Build\BhyveX64\DEBUG_VS2019\X64\OvmfPkg\Bhyve\AcpiTables\AcpiTables\Makefile 中可以看到编译方法。首先是从 asl生成 aml, 然后是使用 GenSec 成成 RAW 的 SECTION

if exist $(OUTPUT_DIR)\Dsdt.aml GenSec -s EFI_SECTION_RAW -o c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC2.1.raw $(OUTPUT_DIR)\Dsdt.aml

例如:

7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.2.raw : $(OUTPUT_DIR)\Facs.acpi
7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.3.raw : $(OUTPUT_DIR)\Hpet.acpi
7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.4.raw : $(OUTPUT_DIR)\Madt.acpi
7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.5.raw : $(OUTPUT_DIR)\Mcfg.acpi
7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.6.raw : $(OUTPUT_DIR)\Spcr.acpi
7E374E25-8E01-4FEE-87F2-390C23C606CDSEC2.1.raw $(OUTPUT_DIR)\Dsdt.aml

最终使用 GenFFs 把他们打包到一个  FFS 中

	GenFfs -t EFI_FV_FILETYPE_FREEFORM -g 7E374E25-8E01-4FEE-87F2-390C23C606CD -o c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CD.ffs -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.1.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.2.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.3.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.4.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.5.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.6.raw -oi c:\buildbs\edk2202302\edk2\Build\BhyveX64\DEBUG_VS2019\FV\Ffs\7E374E25-8E01-4FEE-87F2-390C23C606CDPlatformAcpiTables\7E374E25-8E01-4FEE-87F2-390C23C606CDSEC2.1.raw

3.2.3.1. EFI_FFS_FILE_HEADER

Summary

Each file begins with a header that describes the state and contents of the file. The header is 8-byte aligned with respect to the beginning of the firmware volume.

Prototype

typedef struct {
  EFI_GUID                 Name;
  EFI_FFS_INTEGRITY_CHECK  IntegrityCheck; //UINT16
  EFI_FV_FILETYPE          Type;           //UINT8
  EFI_FFS_FILE_ATTRIBUTES  Attributes;     //UINT8
  UINT8                    Size[3];
  EFI_FFS_FILE_STATE       State;          //UINT8
} EFI_FFS_FILE_HEADER;

typedef struct {
  EFI_GUID                 Name;
  EFI_FFS_INTEGRITY_CHECK  IntegrityCheck;
  EFI_FV_FILETYPE          Type;
  EFI_FFS_FILE_ATTRIBUTES  Attributes;
  UINT8                    Size[3];
  EFI_FFS_FILE_STATE       State;
  UINT64                   ExtendedSize;
} EFI_FFS_FILE_HEADER2;

上述的区别在于“EFI_FIRMWARE_FILE_SYSTEM3_GUID indicates support for FFS_ATTRIB_LARGE_SIZE and thus support for files 16MB or larger. EFI_FIRMWARE_FILE_SYSTEM2_GUID volume does not contain large files. Files 16 MB or larger use a EFI_FFS_FILE_HEADER2 and smaller files use EFI_FFS_FILE_HEADER.EFI_FIRMWARE_FILE_SYSTEM2_GUID allows backward compatibility with previous versions of this specification”,这里因为文件小,肯定是EFI_FFS_FILE_HEADER。

其中 0x1B92=7058 就是这个FFS 文件的大小。接下来的就是一个 Section 的内容了。

typedef struct {
  UINT8                    Size[3];
  EFI_SECTION_TYPE         Type;
} EFI_COMMON_SECTION_HEADER;

typedef struct {
  UINT8                    Size[3];
  EFI_SECTION_TYPE         Type;
  UINT32                   ExtendedSize;
} EFI_COMMON_SECTION_HEADER2;

从内容上来说,就是7E374E25-8E01-4FEE-87F2-390C23C606CDSEC1.1.raw 这个文件的内容:

有了上述的分析,这里编写一个从 FFS 解析 AML 的工具。使用 VC 编写,VS2019 编译通过

#include <windows.h>  
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 

#pragma warning(disable:4996)

#pragma pack(1)
//
// Basical data type definitions introduced in UEFI.
//
typedef struct {
    UINT32  Data1;
    UINT16  Data2;
    UINT16  Data3;
    UINT8   Data4[8];
} EFI_GUID;

//
// Used to verify the integrity of the file.
//
typedef union {
    struct {
        UINT8   Header;
        UINT8   File;
    } Checksum;
    UINT16    Checksum16;
} EFI_FFS_INTEGRITY_CHECK;

typedef UINT8 EFI_FV_FILETYPE;
typedef UINT8 EFI_FFS_FILE_ATTRIBUTES;
typedef UINT8 EFI_FFS_FILE_STATE;

typedef struct {
    EFI_GUID                 Name;
    EFI_FFS_INTEGRITY_CHECK  IntegrityCheck; //UINT16
    EFI_FV_FILETYPE          Type;           //UINT8
    EFI_FFS_FILE_ATTRIBUTES  Attributes;     //UINT8
    UINT8                    Size[3];
    EFI_FFS_FILE_STATE       State;          //UINT8
} EFI_FFS_FILE_HEADER;

typedef UINT8 EFI_SECTION_TYPE;

typedef struct {
    UINT8                    Size[3];
    EFI_SECTION_TYPE         Type;
} EFI_COMMON_SECTION_HEADER;


#pragma pack()

void SaveBufferToFile(UINT8 Index, char* p, UINT16 Len) {
    FILE* SaveTo;
    char NumBuf[20];
    sprintf(NumBuf, "%d.rom",Index);
    SaveTo=fopen(NumBuf, "wb");
    fwrite(p,1,Len, SaveTo);
    fclose(SaveTo);
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        printf("Please input the FFS name\n");
        return 1;
    }

    FILE* file;
    char* buffer;
    long fileLen;

    // 打开文件
    file = fopen(argv[1], "rb");  // 以二进制模式读取文件
    if (!file) {
        printf("Unable to open file %s\n", argv[1]);
        return 2;
    }

    // 获取文件大小
    fseek(file, 0, SEEK_END);  // 移动到文件末尾
    fileLen = ftell(file);     // 当前位置即文件大小
    fseek(file, 0, SEEK_SET);  // 移动回文件开头
    printf("%s file size is %d\n", argv[1], fileLen);

    // 分配内存
    buffer = (char*)malloc(fileLen);
    if (!buffer) {
        printf("Memory allocation failed\n");
        fclose(file);
        return 3;
    }

    // 读取文件内容到内存
    fread(buffer, fileLen, 1, file);
    fclose(file);  // 关闭文件

    char* pFile= &buffer[sizeof(EFI_FFS_FILE_HEADER)];
    EFI_COMMON_SECTION_HEADER* pSection;
    UINT8 Index = 0;

    while (pFile - buffer<fileLen) {
        pSection = (EFI_COMMON_SECTION_HEADER*) pFile;
        // 输出起始位置,长度
        printf("Section start at %x %x\n", 
            pFile- buffer,
            (pSection->Size[0])+ (pSection->Size[1]<<8)+(pSection->Size[2]<<16));
        // 这里我们要保存去掉头的 Section
        SaveBufferToFile(Index, &buffer[pFile - buffer]+4, (pSection->Size[0]) + (pSection->Size[1] << 8) + (pSection->Size[2] << 16)-4);
        Index++;
        if ((pFile - buffer + (pSection->Size[0]) + (pSection->Size[1] << 8) + (pSection->Size[2] << 16)) % 4 != 0) {
            pFile = &buffer[((pFile - buffer + (pSection->Size[0]) + (pSection->Size[1] << 8) + (pSection->Size[2] << 16))/4+1)*4];
        }
        else {
            pFile = &buffer[pFile - buffer + (pSection->Size[0]) + (pSection->Size[1] << 8) + (pSection->Size[2] << 16)];
        }
        
    }

    return 0;
}

例如,使用这个工具分解 QEMU中Bhyve 的7E374E25-8E01-4FEE-87F2-390C23C606CD.ffs:

可以生成 0.rom-6.rom,就是打包起来的 AML 文件:

本文提到的可执行程序源代码:

本文提到的编译后的EXE 和FFS文件:

参考:

  1. https://uefi.org/specs/PI/1.8/V3_Code_Definitions.html#firmware-file

Arduino on Ch32V305

这篇介绍如何使用 Arduino 在Ch32V305上编写代码,不过需要明确的一点是:目前对于Ch32V30X系列 Arduino 支持还并不完善,在使用时会遇到各种各样的问题。

项目地址在  https://github.com/openwch/arduino_core_ch32

首先,将这个项目加入到”Additional Boards Managers URLs” 中:

接下来在 Board Manger 中搜索安装这个板子:

之后就可以进行使用了。

原始的库并不支持 USB 设备,这里需要我们直接编程:

#include "src\\userUsbKB\\ch32v30x_usbhs_device.h"
#include "src\\userUsbKB\\usbd_composite_km.h"

uint8_t  KeyPress[8] = {0x08, 0, 0, 0, 0, 0, 0, 0};
uint8_t  KeyRelease[8] = {0, 0, 0, 0, 0, 0, 0, 0};

void setup() {
  Serial.begin(115200);
  Serial.println("Start");
  pinMode(D18, INPUT_PULLUP);
  /* Initialize system configuration */
  SystemCoreClockUpdate( );
  NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
  //Delay_Init( );

  /* Initialize USBHS interface to communicate with the host  */
  USBHS_RCC_Init( );
  USBHS_Device_Init( ENABLE );
  USB_Sleep_Wakeup_CFG( );
}

unsigned long PressElsp = 0;
unsigned long ReleaseElsp = 0;

void loop() {
  if ( USBHS_DevEnumStatus )
  {

    /* Handle keyboard lighting */
    KB_LED_Handle( );

    if ((digitalRead(D18) == LOW) && (PressElsp == 0) &&(millis()-ReleaseElsp>100)) {
      USBHS_Endp_DataUp( DEF_UEP1, KeyPress, sizeof( KeyPress ), DEF_UEP_CPY_LOAD );
      PressElsp = millis();
      Serial.println("a");
    }

    if ((PressElsp != 0) && (millis() - PressElsp > 1000)) {
      USBHS_Endp_DataUp( DEF_UEP1, KeyRelease, sizeof( KeyRelease ), DEF_UEP_CPY_LOAD );
      PressElsp = 0;
      ReleaseElsp=millis();
      Serial.println("b");
    }
  }
}

关键的代码如下,简单的说就是如果D18 触发,那么就发送键盘数据给PC,相当于按下了 Win键;1秒之后,再发送全为0的数据包,这个相当于发送抬起信息:

    if ((digitalRead(D18) == LOW) && (PressElsp == 0) &&(millis()-ReleaseElsp>100)) {
      USBHS_Endp_DataUp( DEF_UEP1, KeyPress, sizeof( KeyPress ), DEF_UEP_CPY_LOAD );
      PressElsp = millis();
      Serial.println("a");
    }

    if ((PressElsp != 0) && (millis() - PressElsp > 1000)) {
      USBHS_Endp_DataUp( DEF_UEP1, KeyRelease, sizeof( KeyRelease ), DEF_UEP_CPY_LOAD );
      PressElsp = 0;
      ReleaseElsp=millis();
      Serial.println("b");
}

有兴趣的朋友不妨尝试一下这个芯片,主要的优点是:速度足够快(最高 144Mhz),资源比较多(128KB Flash,32K 内存),体积小(TSSOP20封装),内置了USB High Speed (真 2.0)。目前的缺点是 Arduino开发环境并不完善,很多需要自己摸索。