diff --git a/-ch32v208-ch32v208-ubuntu2204-makefile-vscode---milton----4cdfee.html b/-ch32v208-ch32v208-ubuntu2204-makefile-vscode---milton----4cdfee.html new file mode 100644 index 0000000..48fd055 --- /dev/null +++ b/-ch32v208-ch32v208-ubuntu2204-makefile-vscode---milton----4cdfee.html @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + 沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置 - Milton - 博客园 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ 沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置 - Milton - 博客园 +

+ +
+ + + + + 单片机 + + + + + + 环境 + + + +
+ +
+

目录

硬件部分

硬件环境与Windows下相同, 不详细介绍

软件部分

沁恒已经开源WCH-Link的协议, 因此这部分的选项将会很丰富, 这里还是以沁恒官方的定制版 RISC-V Embedded GCC OpenOCD 为例进行说明.

下载

http://mounriver.com/download 下载最新的工具链. 当前版本是 MRS_Toolchain_Linux_X64_V170.tar.xz, 对于 CH32V208, V1.60版本也能支持. 压缩包中包含 RISC-V Embedded GCCOpenOCD.

配置

解压工具链,

sudo tar -xvf MRS_Toolchain_Linux_X64_V170.tar.xz

在解压后的目录下有一个README, 这个文件比较重要. 因为沁恒每次出新版本都可能有一些变动, 导致前一个版本的 cfg 或者命令行无法使用, 这个 README 中会列举当前版本可用的烧录和debug命令, 需要留意.

将工具链移动到合适的位置, 并修改owner为root避免误修改

sudo mkdir -p /opt/gcc-riscv/
+sudo mv "MRS_Toolchain_Linux_x64_V1.70/RISC-V Embedded GCC" /opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70
+sudo chown -R root:root /opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70
+sudo mkdir -p /opt/openocd/
+sudo mv MRS_Toolchain_Linux_x64_V1.70/OpenOCD /opt/openocd/wch-openocd-v1.70
+sudo chown -R root:root /opt/openocd/wch-openocd-v1.70

额外的动态链接库, 在 beforeinstall/start.sh 里是直接复制到 /usr/lib, 稳妥起见, 还是单独建一个目录放进去

sudo mkdir -p /usr/lib/wch/
+sudo cp -P beforeinstall/lib* /usr/lib/wch/
+sudo ldconfig

配置设备权限

根据 start.sh 中执行的命令, 需要将两个规则文件复制到 /etc/udev/rules.d. +先检查一下 /etc/udev/ 下是否已经存在相关的配置, 如果有, 需要和这两个规则整合一下, 如果没有, 直接复制然后更新就可以了

sudo cp beforeinstall/50-wch.rules /etc/udev/rules.d
+sudo cp beforeinstall/60-openocd.rules  /etc/udev/rules.d
+# Reload rules
+sudo udevadm control  --reload-rules

验证

执行这两个命令应该能看到正确的输出, 如果有报错, 需要先排查问题

~$ /opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70/bin/riscv-none-embed-gcc --version
+riscv-none-embed-gcc (xPack GNU RISC-V Embedded GCC, 64-bit) 8.2.0
+Copyright (C) 2018 Free Software Foundation, Inc.
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+~$ /opt/openocd/wch-openocd-v1.70/bin/openocd --version
+Open On-Chip Debugger 0.11.0+dev-02415-gfad123a16-dirty (2023-02-22-15:09)
+Licensed under GNU GPL v2
+For bug reports, read
+	http://openocd.org/doc/doxygen/bugs.html

运行示例项目

基于 CH32V20x 的参考例程 https://www.wch.cn/downloads/CH32V20xEVT_ZIP.html, 调整结构并增加 Makefile, 已经提交至 GitHub, 可以直接导出进行编译和烧录.

项目地址: https://github.com/IOsetting/ch32v208-template

从 GitHub 导出项目

git clone https://github.com/IOsetting/ch32v208-template.git

根据自己的环境, 调整 Makefile 中的路径信息, 其它内容可以保持默认

##### Toolchains #######
+GCC_TOOCHAIN	?= /opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70/bin
+OPENOCD_PATH	?= /opt/openocd/wch-openocd-v1.70/bin

执行编译

make clean
+make

如果CH32V208评估板已经通过 WCH-Link 连接上PC, 可以执行下面的命令进行烧录

make flash

沁恒V1.60 的 openocd 不支持 CH32V208, 烧录不报错, 但是不运行, 要用 V1.70 版本才行.

GDB Debug

打开终端, 用沁恒定制的 openocd 启动 GDB Server, 注意要连上 WCH-Link, 不然 Server 会报错退出.

/opt/openocd/wch-openocd-v1.70/bin$ ./openocd -f wch-riscv.cfg 
+Open On-Chip Debugger 0.11.0+dev-02415-gfad123a16-dirty (2023-02-22-15:09)
+Licensed under GNU GPL v2
+For bug reports, read
+	http://openocd.org/doc/doxygen/bugs.html
+Info : only one transport option; autoselect 'sdi'
+Warn : Transport "sdi" was already selected
+Ready for Remote Connections
+Info : Listening on port 6666 for tcl connections
+Info : Listening on port 4444 for telnet connections
+Info : WCH-Link-CH549  mode:RV version 2.8 
+Info : wlink_init ok
+Info : clock speed 6000 kHz
+Info : [wch_riscv.cpu.0] datacount=2 progbufsize=8
+Info : [wch_riscv.cpu.0] Examined RISC-V core; found 1 harts
+Info : [wch_riscv.cpu.0]  XLEN=32, misa=0x40901105
+[wch_riscv.cpu.0] Target successfully examined.
+Info : starting gdb server for wch_riscv.cpu.0 on 3333
+Info : Listening on port 3333 for gdb connections

在第二个终端中, 启动 GDB Client

/opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70/bin/riscv-none-embed-gdb Build/app.elf
+GNU gdb (xPack GNU RISC-V Embedded GCC, 64-bit) 8.3
+Copyright (C) 2019 Free Software Foundation, Inc.
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+Type "show copying" and "show warranty" for details.
+This GDB was configured as "--host=x86_64-unknown-linux-gnu --target=riscv-none-embed".
+Type "show configuration" for configuration details.
+For bug reporting instructions, please see:
+<https://github.com/sifive/freedom-tools/issues>.
+Find the GDB manual and other documentation resources online at:
+    <http://www.gnu.org/software/gdb/documentation/>.
+For help, type "help".
+Type "apropos word" to search for commands related to "word"...
+Reading symbols from Build/app.elf...

设置GDB参数

(gdb) set mem inaccessible-by-default off
+(gdb) set remotetimeout unlimited
+(gdb) set architecture riscv:rv32
+The target architecture is assumed to be riscv:rv32

连接到GDB服务, load 载入程序, b 设置断点, c 继续执行, i r 查看寄存器, i local 查看全部局部变量, list 查看代码. c过程中可以用Ctrl+C暂停, quit 退出

(gdb) target remote localhost:3333
+Remote debugging using localhost:3333
+0x00000428 in Delay_Ms (n=n@entry=1000) at Debug/debug.c:74
+74	    while((SysTick->SR & (1 << 0)) != (1 << 0));
+(gdb) i r pc
+pc             0x428	0x428 <Delay_Ms+46>
+(gdb) load
+Loading section .init, size 0x38 lma 0x0
+Loading section .vector, size 0x148 lma 0x38
+Loading section .text, size 0x1e4c lma 0x180
+Loading section .data, size 0x88 lma 0x1fcc
+Start address 0x0, load size 8276
+Transfer rate: 4 KB/sec, 2069 bytes/write.
+(gdb) i r pc
+pc             0x0	0x0 <_start>
+(gdb) b main
+Breakpoint 1 at 0x25e: file User/main.c, line 55.
+(gdb) c
+Continuing.
+Note: automatically using hardware breakpoints for read-only addresses.
+Breakpoint 1, main () at User/main.c:55
+55	    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
+(gdb) i r pc
+pc             0x25e	0x25e <main>
+(gdb) list
+50	 */
+51	int main(void)
+52	{
+53	    u8 i = 0;
+54	
+55	    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
+56	    Delay_Init();
+57	    USART_Printf_Init(115200);
+58	    printf("SystemClk:%ld\r\n", SystemCoreClock);
+59	
+(gdb) 

配置 VSCode 开发环境

如果以上步骤都已经顺利完成, 直接在 VSCode 中打开这个项目目录就可以了. VSCode Makefile 扩展会自动识别对应的工具链和依赖库, 代码提示和高亮开箱即用.

需要配置的是编译和烧录的快捷命令, 可以通过 Ctrl+Shift+P 调出菜单, 用 Tasks:Configure Task 进行配置, 或者直接在 .vscode 目录下创建 tasks.json 进行配置

tasks.json 的例子

{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "clean & build",
+            "type": "shell",
+            "command": "make clean; make -j4",
+            "problemMatcher": []
+        },
+        {
+            "label": "build",
+            "type": "shell",
+            "command": "make -j4"
+        },
+        {
+            "label": "build & download",
+            "type": "shell",
+            "command": "make -j4; make flash"
+        }
+    ]
+}

配置之后, 可以通过 Alt + Shift + F10 调出 task 菜单, 选择对应的任务进行编译或下载.

配置 VSCode Cortex Debug

VSCodeCortex Debug 可以用于 debug CH32V208, 但是不能直接使用, 需要一些调整.

Cortex Debug 降级到 1.4.4

首先是 Cortex Debug 的版本, 当前版本是 1.10.0, 这个版本运行沁恒的 gdb client 会提示如下错误

ERROR: GDB major version should be >= 9, yours is 8; GDB could not start as expected. Bad installation or version mismatch. See if you can start gdb from a shell prompt and check its version (Must be >= 9)

沁恒定制的这个gcc已经8.3很久了, 等着沁恒升级不太现实, 只能将 Cortex Debug 降级到 1.4.4 使用, 这是支持gcc 8的最后一个版本. 在 VSCode 的扩展中打开 Cortex Debug 的介绍页, 在 Uninstall 右侧的小箭头点击展开, 能看到 Install Another Version 的菜单, 在里面选择 1.4.4 安装

配置文件 launch.json

在 .vscode 目录下新建文件 launch.json, 我使用的配置如下

{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Cortex Debug",
+            "cwd": "${workspaceFolder}",
+            "executable": "${workspaceFolder}/Build/app.elf",
+            "request": "launch",
+            "type": "cortex-debug",
+            "servertype": "openocd",
+            "serverpath": "/opt/openocd/wch-openocd-v1.70/bin/openocd",
+            "configFiles": [
+                "${workspaceFolder}/Misc/wch-riscv.cfg.v1.70"
+            ],
+            "runToEntryPoint": "main",
+            "runToMain": true,          // false: run to reset handler
+            "preLaunchTask": "build",   // task from tasks.json
+            // "preLaunchCommands": ["Build all"], if not using preLaunchTask
+            "showDevDebugOutput": "raw", // log level: parsed, raw, both(include parsed and raw)
+            "device": "CH32V208",
+            "svdFile": "${workspaceFolder}/Misc/ch32v208xx.svd",
+            "toolchainPrefix": "/opt/gcc-riscv/riscv-wch-embedded-gcc-v1.70/bin/riscv-none-embed"
+        }
+    ]
+}

关于配置项的说明:

  1. executable: 指向的是当前项目生成的 elf 文件
  2. servertype: 只能是 openocd
  3. serverpath: 这个很重要, 必须指向沁恒定制的 openocd 可执行文件
  4. configFiles: 当前的 openocd 版本是 1.70, 用仓库里的cfg, 或者用 openocd 同目录下的 wch-ricsv.cfg 都可以
  5. preLaunchTask: 填的是 tasks.json 中配置的任务, 如果找不到这个任务, 启动时会有提示
  6. showDevDebugOutput: 用于在下方的 DEBUG CONSOLE 输出 GDB 日志, 可以选 both, parsed, raw, none, 其中 raw是显示原始内容, parsed 是格式化过的, both 是两种都显示
  7. device: 对于 openocd 貌似可以随便填
  8. svdFile: 标识外设寄存器名称与地址关系的文件, 在debug时可以直接通过寄存器名称查看对应地址的值, 仓库中的 svd 是从沁恒的 MounRiver 开发环境中复制过来的.
  9. toolchainPrefix: 指向沁恒定制的 gcc, 注意是前缀, 不需要带后面的 -gcc

运行 Debug

配置完成后就可以开始 Debug了, 可以通过右侧的 Run And Debug 面板, 点绿色三角形启动, 也可以按 F5启动, 我使用的是 IntelliJ IDEA Keybinding, 所以debug快捷键和 IDEA 是一样的, 单步 F8, 继续 F9, 进入 F7. 在 Run And Debug 面板左侧可以观察变量和外设寄存器对应的值. 非常方便.

相关链接

使用公版 GCC

沁恒当前的 GCC 版本为8, 还没发布更高的版本.

公版GCC可以从这里下载 https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/

如果使用公版GCC 12编译,

  1. 需要将编译参数中的 -march=rv32imac 替换为 -march=rv32imac_zicsr, 参考 https://www.blaatschaap.be/on-riscv-bare-metal-toolchains/.
  2. 需要增加 _lseek, _close, _isatty 这些函数, 否则编译会报错.
  3. 注释掉 __attribute__((interrupt("WCH-Interrupt-fast"))), 这个中断类型在社区版 GCC 中无法识别
  4. 避免使用预编译的 .a 库文件, 用到库文件的地方会工作不正常

WCH-Link 的 OpenOCD 源码

仓库地址: https://github.com/Seneral/riscv-openocd-wch

这个验证过能编译, 编译命令

git clone https://github.com/Seneral/riscv-openocd-wch.git
+cd riscv-openocd-wch/
+git submodule update --init --recursive
+./bootstrap 
+./configure --disable-jlink --enable-wlink --disable-werror
+make -j8
+./src/openocd --version

和沁恒提供的 openocd 的对比

riscv-openocd-wch$ ./src/openocd --version
+Open On-Chip Debugger 0.11.0+dev-g395b49ca4 (2023-05-04-00:35)
+Licensed under GNU GPL v2
+For bug reports, read
+	http://openocd.org/doc/doxygen/bugs.html
+riscv-openocd-wch$ /opt/openocd/wch-openocd-v1.70/bin/openocd --version
+Open On-Chip Debugger 0.11.0+dev-02415-gfad123a16-dirty (2023-02-22-15:09)
+Licensed under GNU GPL v2
+For bug reports, read
+	http://openocd.org/doc/doxygen/bugs.html

如果用这个仓库代码编译的openocd下载, 要使用沁恒定制的V1.60版本的openocdcfg, V1.60对应的编程器名称是wlink, 在V1.70里改名为wlinke了.

+ + + + \ No newline at end of file diff --git a/AI-prompt.html b/AI-prompt.html new file mode 100644 index 0000000..68900e9 --- /dev/null +++ b/AI-prompt.html @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + 超详细AI提示词论文润色指南 | 413’s Website + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +

+ 超详细AI提示词论文润色指南 +

+ +
+ + Posted on Sun, May 19, 2024 + + + + + Refer + + + +
+ +
+

建议收藏:超详细ChatGPT(GPT 4.0)论文润色指南+最全提示词/咒语_江鸟1998的博客-CSDN博客

近期更新

最近更新的一些技巧和方法会置顶显示在最上面,一段时间之后放在合适的位置。

—2023-06-11更新:稳妥冗余表达—

有时候自己写的内容存在着冗余,但是如果单纯使用提示"more concise"或者"reduce redundancy",在有些情况下,会使得文章的信息丢失或者删减掉自己不希望被删减的内容。这时候可以用下面的提示,告诉ChatGPT要谨慎一些。

提示:请您在全力保持了原文的连贯性和意义的基础之上,降低冗余信息。

或者更加稳妥的方法,只是让它帮忙定位一个范围,具体的修改交给我们自己。

提示:请您在全力保持了原文的连贯性和意义的基础之上,给出如何降低内容冗余的针对性建议,具体到句子。

—2023-06-08更新:提供坐标—

如果你是一个像我一样的英文渣渣,每次修改的时候,只能修改中文,反向翻译为英文,那你一定需要下面的提示词。首先,你提供你希望修改的原始英文文本,然后,你给出你期待的中文译文。最后,请求ChatGPT根据你给出的中文内容,给出相应的英文版本。通过这种方式,避免了语言误解的可能性,同时最大程度保留专业名词和上下文信息。

这实际上是提供了一个坐标,使得ChatGPT的回答有了方向。

我英文不太好,我已对以下的内容进行了中文修改。 +[原始英文文本] +我希望其被翻译为以下的中文内容: +[期待的中文译文] +请根据我提供的中文版本,提供相应的英文翻译。

共性方法

慢思考提醒

它是个心直口快的AI,有时候需要你提醒它刻意进行慢思考。

不妨试试,在提示词后面,紧跟一句“请一步步思考”,“请一步步考虑”,“请务必认真回答”,“如果你认为无法根据已知信息安全修改论文,请回答否”,“请重新审视你输出的每一句话”,“仔细想想再说”之类的描述,可以使得其回答的质量和准确率大大提高。有时候在提示语后面给个提醒,它会变得完全不一样。

灵感参考 @木遥 3月25日的微博 https://weibo.com/1644684112/4883500941182314

也许仅仅是这一条启示,就能给我们莫大的启发。

角色设定

ChatGPT是无数语料喂出来的,可以把它想象成许多作家聚在一起,根据海量的文字资料来帮你写东西。如果你只给一个很一般性的要求,它就只能给你生成一个一般性的、用在哪里都行但是用在哪里都不是最恰当的内容。可是,如果你把要求说得更详细,给出的情景更具体,它就能创作出专门为你定制的内容,更符合你的需求。

所以在与ChatGPT展开对话之前,一个好的办法是可以先让它进入特定角色,尤其GPT-4有强大的角色扮演能力。

我比较常用的一个方法是

Prompt: You are now acting as an expert in the field of [Put professional fields here…]. From a professional point of view, do you think there is any need to modify the above content? Be careful not to modify the whole text, you need to point out the places that need to be modified one by one, and give revision opinions and recommended revision content. + +提示:你现在扮演一个[这里放你所研究的领域] 领域的专家,从专业的角度,您认为上面这些内容是否有需要修改的地方? 注意,不要全文修改,您需要一一指出需要修改的地方,并且给出修改意见以及推荐的修改内容。

节省空间

下面这种方式可以在一定程度上解决一次输出不完整与输出过程中断网的情况。

Prompt: [Put your requirements here…] , since your output length is limited, in order to save space. Please use ellipses for the parts you don’t think need to be modified.

提示:[这里放你的要求…],由于你的输出长度有限,为了节省空间。请你觉得没必要修改的部分,用省略号即可。

GPT指导Prompt

输入的质量直接决定输出的质量,如何更好的做提示词? 也许可以用GPT自己来完成这件事。

下面是prompt的修改器语句指令。

Prompt: I am trying to get good results from GPT-4 on the following prompt: ‘你的提示词.’ Could you write a better prompt that is more optimal for GPT-4 and would produce better results?

这里举个例子,扔给GPT一个随意点的中文提示,可以看出修改过的提示与原提示之间的区别!

当然,大家可以随意在此基础上进行发挥,比如说可以让它修改之后再返回英文结果,通常来说,GPT在英文上的表现要优于中文。

多版本参考

在润色过程中,ChatGPT可以提供多个版本的修改建议,以便对比和选择。

Prompt: Please provide multiple versions for reference.

提示:请提供多个版本用于参考。

及时反馈

如果ChatGPT理解错了你的问题,可以给它一个错误的反馈,让它重新回答

Prompt: Note that it is not …, but …

Re-answer the previous question based on what I have added.

提示:注意,不是…而是…

请根据我的补充,重新回答上个问题

如果认为回答的不够好,或者方向不对。可以要求重新回答,并且指明侧重方向。比如你只希望去除当前段落的冗余,并不想改动原意思。

Prompt:Still the above question, I think your answer is not good enough. Please answer again, this time focusing on removing redundancy from this passage.

提示:还是上面的问题,我认为你回答的不够好。请重新回答一次,这次你应该侧重于去除这段话中的冗余。

润色方向

根据自己的需求调整润色方式。以下列举了一些常用词汇,可与后文的示例结合使用。

控制程度

在使用ChatGPT的过程中,有时候我们并不希望AI对文本进行大幅修改,这时候可以要求它对润色的程度和方向进行限制,以下是一些可以有助于控制润色程度的口令,请大家尝试加入自己的提示词中。

下面是一个非常典型的应用场景。

假设你已经完成了Introduction内容的编写,但是此时又重新确定了论文的标题。

然后,我们可以用以下提示来让ChatGPT进行微小的改动:

Prompt: You are now acting as an expert in the field of [Insert your research field]. From a professional point of view, please make minor edits to the introduction to better match the title “[Insert your latest paper title here]”. Note, this is a subtle edit, don’t change the overall content and only adjust details to match the title.

提示:你现在扮演一个 [放置你的研究领域] 的专家,从专业的角度,你需要对上面这段引言进行微小的修改,使其更好地与标题“[放置你的最新论文标题]”相匹配。注意,这是一个微小的编辑,不要改变整体内容,只调整细节以匹配标题。

举例如下: +

可以看到,通过这种方式,我们就可以指导ChatGPT对Introduction进行微调,使其更好地匹配论文标题。

前后对比

如果文本还是过长不利于观察,让它回答具体修改了哪些地方。

Prompt:Note that in addition to giving the modified content, please also indicate which paragraphs and sentences have been modified in the revised version.

提示:注意,除了给出润色修改之后的内容,还请指明修订的版本中具体修改了哪些段落的哪几句话。

补充:这里最好是用一些在线的文本对比工具来观察,例如下面这种,更加一目了然。 +

段落润色

直接润色

Prompt: Polish the paragraph above to make it more logical, and academic.

提示:润色上面的内容,使其更加更合逻辑,更符合学术风格

有时,如果英文不够好或者对修改之后的句子感觉不合适,可以接着让它输出一句理由。然后自己再做最终的判断。

Prompt:For the sentence “[Before polished sentence]”, why did you polish it to be “[Polished sentence]”.

提示:对于“[润色前的句子]”这句话,为什么你润色为成“[润色后的句子]”。

同理,本文接下来的所有用法都可以配合着上面的方式进行追问。

特定要求

1)结合背景知识

相比于上面直接的润色,这种方式可能会让它输出一些更丰富的信息。

Prompt: According to your knowledge about XXX and XXX, is there a better way to write the above paragraph, please help to revise it so that it can be used in academic papers.

提示:上面这段话,根据你所掌握的关于XXX和XXX的知识,有没有更好的写法,请帮助润色修改,以便能够用于论文。

2)长句拆分

Prompt: This sentence is too long and complex. Consider breaking it up into multiple shorter sentences.

提示:这句话太长而复杂。考虑将其分解为多个较短的句子。

3)去除冗余

Prompt: This section seems repetitive. Please rephrase to avoid redundancy.

提示:本节似乎是重复的。请重塑以避免冗余。

语法句法

Prompt: This sentence is grammatically incorrect. Please revise.

提示:这句话在语法上是不正确的。请修改。

Prompt: The subject and verb do not agree in this sentence. Please correct.

提示:主语和动词在这句话中不一致。请改正。

Prompt: This phrase seems out of place. Please rephrase to improve clarity.

提示:这句话似乎不合适。请重新措辞以表达更清晰。

Prompt: I have used a passive voice in this sentence. Consider using an active voice instead.

提示:我在这句话中使用了被动语态。考虑改用主动语态。

场景举例

写论文的时候往往要贬低一下别人方法的局限性。可以让ChatGPT帮你列举一些有局限性的场景。

Prompt: Can you give a few examples to demonstrate the scenarios where the previous method has limitations, so that it can be used in academic papers.

提示:能否举几个例子来证明之前的方法在哪些场景下具有局限性,以便用于论文中。

期刊/会议风格

根据期刊会议(注意 期刊或者会议要足够著名)的风格,来润色内容。

Prompt: If I wish to publish a paper at a XXX conference, please polish the above content in the style of a XXX article.

提示:如果我希望将论文发表在XXX会议/期刊上,请按照XXX文章的风格,对上面的内容进行润色。

封装基本事实/原理/背景

润色的同时,修改基本逻辑错误。如果对内容的润色需要一些背景知识,可以在对话时主动告诉ChatGPT,比如XXX原理。

Prompt: Now, in order to help me better polish my thesis, I need you to remember the XXX principle: “…”

提示:现在,为了接下来能够帮我更好的润色论文,我需要你记住XXX原理:“…”

这样就相当于为一段内容,封装了一个函数名称,之后你再次提到XXX原理的时候,ChatGPT就能快速知道你说的是哪些基本事实了

Prompt: Polish and rewrite the above content to make it more in line with the style of academic papers, and at the same time, it can be more professional. If there are parts that do not conform to facts or logic, please refer to the part of xxxxx for the above content modification.

提示:润色并重写上面的内容,使其更加符合论文的风格,于此同时,又能更加专业化,如果有不符合事实或者逻辑的部分,请你参考XXX原理部分对上面的内容修改。

其他用法

内容续写

这个方法一般适合实在想不出什么内容,又希望增加字数的情况。

Prompt: Based on the knowledge you have mastered about [xxx], polish and continue writing the above content to make the content richer and more complete.

提示:根据你所掌握的关于[xxx]的知识,润色并续写上面的内容,使得内容更加丰富完整。

中英互译

可以直接将中文翻译成英语风格的英文 +注意,如果与ChatGPT的对话在同一个窗口内,交流了一段时间之后,那么此时,直接使用ChatGPT进行翻译的效果优于Google,尤其是对于专业术语的翻译,它会更懂你!

Prompt: Translate the above Chinese into the corresponding English, requiring the writing style of an academic paper

提示:将上面的中文,翻译成对应的英语,要求具有论文的写作风格

标题名称

可以向ChatGPT寻求为段落起标题,为方法起缩写名称等。

Prompt:What abbreviations can “XXX” have? Give several options, with reasons, for use in an academic paper.

提示:"XXX"可以有哪些缩写?请给出几种选择,并给出理由,以便用于论文中。

进阶使用

GPT4.0的使用体验,相比于GPT3.5有了完全不一样的提升… 尤其是在逻辑推理阶段。GPT3.5一个明显的特点是你只要对AI的回答进行反驳,它便会立刻改变立场并承认错误。而GPT 4.0则更多地基于事实进行回答,表现出更高的稳定性。

此外,GPT 4.0能够阅读更长的文本,拥有更长的记忆窗口,这使得它能够在通篇润色方面发挥更大作用。

逻辑论证辅助

GPT 4.0在逻辑推理方面有显著的提升,可以用于辅助构建更有说服力的论证。

Prompt: Please help me analyze and optimize the logical structure of this argument to make it more convincing.

提示:请帮我分析和优化这段论证的逻辑结构,以使其更具说服力。

长篇文本处理能力

由于GPT 4.0具有更长的记忆窗口,它可以更有效地进行长篇幅文本的润色。

Prompt: Please read and polish the entire paper to ensure consistency and coherence.

提示:请阅读并润色整篇论文,确保一致性和连贯性。(就是这么简单粗暴!)

当然,如果论文特别长,可以分为多次喂给它,像下面这样: + +给完第一部分之后: +

Prompt: I have written the XXX section, but I am not satisfied with its structure and coherence. Please help me reorganize the content and improve its coherence.

提示:我写了XXX部分,但我对其结构和连贯性不满意。请帮我重新组织内容,提高其连贯性。

Prompt: Please review and revise the entire literature review section of my paperensuring that it meets the standards of academic writing and the content iscoherent and well-structured.

提示:请审查并修改我论文的整个文献综述部分,确保符合学术写作标准,内容连贯且结构合理。

提供独特见解

Prompt: Please provide me with some unique insights that I can discuss in my paper, based on the latest research that you are aware of.

提示:请根据你所了解的最新研究,为我提供一些独特的见解以便我在论文中进行讨论。

深度分析与评估

Prompt: Please help me to conduct an in-depth analysis of these research methods and data, and provide me with an assessment of their advantages and disadvantages.

提示:请帮助我对这些研究方法和数据进行深度分析,并为我提供关于其优缺点的评估。

+ + + + \ No newline at end of file diff --git a/About.html b/About.html new file mode 100644 index 0000000..bc7065e --- /dev/null +++ b/About.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + About | 413’s Website + + + + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+ +

+ About +

+ +
+

Man

Q&A

+ + + + \ No newline at end of file diff --git a/CH549-note.html b/CH549-note.html new file mode 100644 index 0000000..a528a2a --- /dev/null +++ b/CH549-note.html @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + CH549的笔记 | 413’s Website + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +

+ CH549的笔记 +

+ +
+ + Posted on Thu, Dec 28, 2023 + + + + + 单片机 + + + +
+ +
+

SDCC的方法来编译 +

SSD1306和CH549的驱动 驱动程序编写(SPI)

SSD1603手册翻译

时钟时序线 的了解

+ + + + \ No newline at end of file diff --git a/Clash-Tun.html b/Clash-Tun.html new file mode 100644 index 0000000..75452cc --- /dev/null +++ b/Clash-Tun.html @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + Clash Tun 共享网络 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Clash Tun 共享网络 +

+ +
+ + Posted on Sat, Apr 13, 2024 + + + + + 环境 + + + +
+ +
+

本篇记录关于配置Clash-Tun的不太容易理解的点

配置防火墙

配置防火墙

配置相关的代理软件

这一步是配置防火墙

勾选网络用到的种类

Clash 配置

网卡配置

控制面板-网络共享-更改网卡配置

接下来的是关于你想共享的网卡,一般为热点,如果找不到,可以开关一下你的热点,然后可以确定你要用的那个热点网络

小结

目前这种方式还存在bug比如共享Wi-Fi 可能连接不上,需要很麻烦的配置。不过我用这个Tun 模式仅仅是为了Arc 浏览器。

+ + + + \ No newline at end of file diff --git a/ESP32-Addon.html b/ESP32-Addon.html new file mode 100644 index 0000000..ebf2356 --- /dev/null +++ b/ESP32-Addon.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + ESP32中的输出信息 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ ESP32中的输出信息 +

+ +
+ + Posted on Sun, May 5, 2024 + + + + + 单片机 + + + +
+ +
+

在写(shui)论文的时候发现,在ESP32输出调试信息关键很少,没有说服力

于是在boot.py 寻找相关的解决方法 (GPT)

启用 ESP32 MicroPython 调试信息

您提供的代码片段展示了如何禁用 ESP32 MicroPython 的调试信息。要启用调试信息,您需要进行以下操作:

import esp
+esp.osdebug(None)

2. (可选) 设置调试级别:

您可以使用 esp.osdebug() 函数设置调试信息的级别。默认级别为 0,表示不输出任何调试信息。

例如,要启用错误信息和警告,可以使用以下代码:

import esp
+esp.osdebug(2)

查看 ESP32 MicroPython 调试信息

启用 ESP32 MicroPython 调试信息后,您可以通过以下步骤查看这些信息:

1. 连接串口:

使用 USB 线将 ESP32 连接到您的电脑。 确保您已安装所需的 USB 驱动程序。

2. 确定串口

:您需要确定 ESP32 连接到哪个串口。 在 Windows 上,您可以使用设备管理器查看可用串口。 在 macOS 或 Linux 上,您可以使用 ls /dev/tty* 命令列出可用串口。

3. 选择串口监视器:

选择并打开一个串口监视器应用程序,例如:

关于监视器的选择

pyserial-miniterm -f direct 是一个Putty的替代方案

pip install pyserial 就可以安装,不过要设置一下相关的波特率,一般micropython也是115200

效果展示

相对于原来的显示内容变多了😈

+ + + + \ No newline at end of file diff --git a/LaTeX-Environment.html b/LaTeX-Environment.html new file mode 100644 index 0000000..828167d --- /dev/null +++ b/LaTeX-Environment.html @@ -0,0 +1 @@ +The template name has zero length, please check the "template" field in your Notion table. \ No newline at end of file diff --git a/LaTeX-Teach.html b/LaTeX-Teach.html new file mode 100644 index 0000000..828167d --- /dev/null +++ b/LaTeX-Teach.html @@ -0,0 +1 @@ +The template name has zero length, please check the "template" field in your Notion table. \ No newline at end of file diff --git a/Latex_refer.html b/Latex_refer.html new file mode 100644 index 0000000..828167d --- /dev/null +++ b/Latex_refer.html @@ -0,0 +1 @@ +The template name has zero length, please check the "template" field in your Notion table. \ No newline at end of file diff --git a/Note.html b/Note.html new file mode 100644 index 0000000..c012f48 --- /dev/null +++ b/Note.html @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + Note | 413’s Website + + + + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+ +

+ Note +

+ +
+

+ + + + \ No newline at end of file diff --git a/Rclone.html b/Rclone.html new file mode 100644 index 0000000..94078a1 --- /dev/null +++ b/Rclone.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rclone maigsik 本地音乐 | 413’s Website + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +

+ Rclone maigsik 本地音乐 +

+ +
+ + Posted on Wed, Nov 30, 2022 + + + + + Web + + + +
+ +
+

配置的内容:

test tets

PS E:\Dev\rclone-v1.62.2-windows-amd64> .\rclone authorize "onedrive" "6cxxxoqMTDWnJtWbqE"
+2023/03/22 12:18:45 NOTICE: Make sure your Redirect URL is set to "http://localhost:53682/" in your custom config.
+2023/03/22 12:18:45 NOTICE: If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=h-IKR1LgmsDyQUd_tz2x-Q
+2023/03/22 12:18:45 NOTICE: Log in and authorize rclone for access
+2023/03/22 12:18:45 NOTICE: Waiting for code...
+2023/03/22 12:18:49 NOTICE: Got code
+Paste the following into your remote machine --->
+ {"access_token":"","expiry":"2023-03-22T12:40:42.6524031+08:00"}
+PS E:\Dev\rclone-v1.62.2-windows-amd64> .\rclone config
+No remotes found, make a new one?
+n) New remote
+s) Set configuration password
+q) Quit config
+n/s/q> n
+
+Enter name for new remote.
+name> OneMusic
+
+Option Storage.
+Type of storage to configure.
+Choose a number from below, or type in your own value.
+ 1 / 1Fichier
+   \ (fichier)
+ 2 / Akamai NetStorage
+   \ (netstorage)
+ 3 / Alias for an existing remote
+   \ (alias)
+ 4 / Amazon Drive
+   \ (amazon cloud drive)
+ 5 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, DigitalOcean, Dreamhost, Huawei OBS, IBM COS, IDrive e2, IONOS Cloud, Liara, Lyve Cloud, Minio, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Tencent COS, Qiniu and Wasabi
+   \ (s3)
+ 6 / Backblaze B2
+   \ (b2)
+ 7 / Better checksums for other remotes
+   \ (hasher)
+ 8 / Box
+   \ (box)
+ 9 / Cache a remote
+   \ (cache)
+10 / Citrix Sharefile
+   \ (sharefile)
+11 / Combine several remotes into one
+   \ (combine)
+12 / Compress a remote
+   \ (compress)
+13 / Dropbox
+   \ (dropbox)
+14 / Encrypt/Decrypt a remote
+   \ (crypt)
+15 / Enterprise File Fabric
+   \ (filefabric)
+16 / FTP
+   \ (ftp)
+17 / Google Cloud Storage (this is not Google Drive)
+   \ (google cloud storage)
+18 / Google Drive
+   \ (drive)
+19 / Google Photos
+   \ (google photos)
+20 / HTTP
+   \ (http)
+21 / Hadoop distributed file system
+   \ (hdfs)
+22 / HiDrive
+   \ (hidrive)
+23 / In memory object storage system.
+   \ (memory)
+24 / Internet Archive
+   \ (internetarchive)
+25 / Jottacloud
+   \ (jottacloud)
+26 / Koofr, Digi Storage and other Koofr-compatible storage providers
+   \ (koofr)
+27 / Local Disk
+   \ (local)
+28 / Mail.ru Cloud
+   \ (mailru)
+29 / Mega
+   \ (mega)
+30 / Microsoft Azure Blob Storage
+   \ (azureblob)
+31 / Microsoft OneDrive
+   \ (onedrive)
+32 / OpenDrive
+   \ (opendrive)
+33 / OpenStack Swift (Rackspace Cloud Files, Memset Memstore, OVH)
+   \ (swift)
+34 / Oracle Cloud Infrastructure Object Storage
+   \ (oracleobjectstorage)
+35 / Pcloud
+   \ (pcloud)
+36 / Put.io
+   \ (putio)
+37 / QingCloud Object Storage
+   \ (qingstor)
+38 / SMB / CIFS
+   \ (smb)
+39 / SSH/SFTP
+   \ (sftp)
+40 / Sia Decentralized Cloud
+   \ (sia)
+41 / Storj Decentralized Cloud Storage
+   \ (storj)
+42 / Sugarsync
+   \ (sugarsync)
+43 / Transparently chunk/split large files
+   \ (chunker)
+44 / Union merges the contents of several upstream fs
+   \ (union)
+45 / Uptobox
+   \ (uptobox)
+46 / WebDAV
+   \ (webdav)
+47 / Yandex Disk
+   \ (yandex)
+48 / Zoho
+   \ (zoho)
+49 / premiumize.me
+   \ (premiumizeme)
+50 / seafile
+   \ (seafile)
+Storage> 31
+
+Option client_id.
+OAuth Client Id.
+Leave blank normally.
+Enter a value. Press Enter to leave empty.
+client_id> 6c1a28xxxxe0
+
+Option client_secret.
+OAuth Client Secret.
+Leave blank normally.
+Enter a value. Press Enter to leave empty.
+client_secret> WVQxxxxxzoqMTDWnJtWbqE
+
+Option region.
+Choose national cloud region for OneDrive.
+Choose a number from below, or type in your own string value.
+Press Enter for the default (global).
+ 1 / Microsoft Cloud Global
+   \ (global)
+ 2 / Microsoft Cloud for US Government
+   \ (us)
+ 3 / Microsoft Cloud Germany
+   \ (de)
+ 4 / Azure and Office 365 operated by Vnet Group in China
+   \ (cn)
+region> 1
+
+Edit advanced config?
+y) Yes
+n) No (default)
+y/n> n
+
+Use web browser to automatically authenticate rclone with remote?
+ * Say Y if the machine running rclone has a web browser you can use
+ * Say N if running rclone on a (remote) machine without web browser access
+If not sure try Y. If Y failed, try N.
+
+y) Yes (default)
+n) No
+y/n> n
+
+Option config_token.
+For this to work, you will need rclone available on a machine that has
+a web browser available.
+For more help and alternate methods see: https://rclone.org/remote_setup/
+Execute the following on the machine with the web browser (same rclone
+version recommended):
+        rclone authorize "onedrive" "eyJjbGllxxUxxbkp0V2JxRSJ9"
+Then paste the result.
+Enter a value.
+config_token>    {"access_token":"","expiry":"2023-03-22T12:40:42.6524031+08:00"}
+Option config_type.
+Type of connection
+Choose a number from below, or type in an existing string value.
+Press Enter for the default (onedrive).
+ 1 / OneDrive Personal or Business
+   \ (onedrive)
+ 2 / Root Sharepoint site
+   \ (sharepoint)
+   / Sharepoint site name or URL
+ 3 | E.g. mysite or https://contoso.sharepoint.com/sites/mysite
+   \ (url)
+ 4 / Search for a Sharepoint site
+   \ (search)
+ 5 / Type in driveID (advanced)
+   \ (driveid)
+ 6 / Type in SiteID (advanced)
+   \ (siteid)
+   / Sharepoint server-relative path (advanced)
+ 7 | E.g. /teams/hr
+   \ (path)
+config_type> 1
+
+Option config_driveid.
+Select drive you want to use
+Choose a number from below, or type in your own string value.
+Press Enter for the default (b!v83P4RgYYEWmnzOjKs3kRyJ-nHD7OyRIu8y4erYw7RmUsMPzXJQ-RZfn26kOtqlo).
+ 1 / OneDrive (business)
+   \ (b!v83P4RgYYEWmnzOjKs3kRyJ-nHD7OyRIu8y4erYw7RmUsMPzXJQ-RZfn26kOtqlo)
+config_driveid> 1
+
+Drive OK?
+
+Found drive "root" of type "business"
+URL: https://mdx-my.sharxxxxsoft_com/Documents
+
+y) Yes (default)
+n) No
+y/n> 1
+This value must be one of the following characters: y, n.
+y/n> y
+
+Configuration complete.
+Options:
+- type: onedrive
+- client_id: 6c1a285d-4fexxxxxx4109a6e0
+- client_secret: WVQ8Q~UAksxxxxxxqMTDWnJtWbqE
+- token: {"access_token":"","expiry":"2023-03-22T12:40:42.6524031+08:00"}
+- drive_id: b!v83P4RgYYEWmnzOjKs3kRxxxxxxsMPzXJQ-RZfn26kOtqlo
+- drive_type: business
+Keep this "OneMusic" remote?
+y) Yes this is OK (default)
+e) Edit this remote
+d) Delete this remote
+y/e/d> y
+
+Current remotes:
+
+Name                 Type
+====                 ====
+OneMusic             onedrive
+
+e) Edit existing remote
+n) New remote
+d) Delete remote
+r) Rename remote
+c) Copy remote
+s) Set configuration password
+q) Quit config
+e/n/d/r/c/s/q>

导出rclone.conf

path:C:\Users\Shaox\AppData\Roaming\rclone

其中我是配置Onedrive,访问速度看个人。

其中 .param的配置文件可以参看官方

我的配置global.param

NETCHK_ADDR=baidu.com
+CACHEMODE=minimal
+BINDSD=1
+DIRCACHETIME=30m0s
+ADD_PARAMS=--vfs-cache-max-size '2G' 
+--fast-list --allow-non-empty 
+--attr-timeout '30s' 
+--use-mmap --no-modtime --uid '0' --gid '1015' 
+--dir-perms '0775' --file-perms '0644'
+ --umask '002' 
+HTTP=0FTP=0
Rclone

Rclone syncs your files to cloud storage: Google Drive, S3, Swift, Dropbox, Google Cloud Storage, Azure, Box and many more.

可以查看VFS了解详细参数的配置

+ + + + \ No newline at end of file diff --git a/Rust1.html b/Rust1.html new file mode 100644 index 0000000..30a12fb --- /dev/null +++ b/Rust1.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + 安装 ESP32 Rust 开发工具链 esp-rs | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ 安装 ESP32 Rust 开发工具链 esp-rs +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

参考:https://esp-rs.github.io/book/introduction.https

安装前置依赖: 不需要先安装 esp-idf,后续构建 std 应用时会自动下载和安装 esp-idf

# 提供 cargo generate 子命令
+cargo install cargo-generate
+
+# A tool that forwards linker arguments to the actual linker that is also given as an argument to
+# ldproxy
+cargo install ldproxy
+
+# A tool that simplifies installing and maintaining the components required to develop Rust
+# applications for the Xtensa and RISC-V architectures.
+cargo install espup
+
+brew install libuv  # espflash 依赖它
+
+cargo install espflash
+cargo install cargo-espflash # 可选,后续可以直接使用命令 cargo espflash

espup 同时安装和维护 Xtensa and RISC-V architectures 两种 CPU 架构工具链

包括 esp fork 的 rust, GCC 和 LLVM 等。

Rust 官方编译器提供了对 RISC-V target 的 Tier2 支持,可以直接向官方 toolchain 添加对应 target:

rustup toolchain install nightly --component rust-src
+
+# For no_std (bare-metal) applications
+rustup target add riscv32imc-unknown-none-elf # For ESP32-C2 and ESP32-C3
+rustup target add riscv32imac-unknown-none-elf # For ESP32-C6 and ESP32-H2

ESP32-S3 使用 xtensa 处理器,不再 Rust 官方 toolchain 支持范围内,ESP fork Rust 编译器工具链来进行支持。

使用 espup 安装 esp channel

# 清理旧版本
+zj@a:~/esp$ espup uninstall
+[info]: Uninstalling the Espressif Rust ecosystem
+[info]: Uninstalling Xtensa LLVM
+[info]: Uninstalling GCC
+[info]: Uninstalling Xtensa Rust toolchain
+[info]: Uninstallation successfully completed!
+zj@a:~/esp$ rm -rf  ~/.rustup/toolchains/*
+
+# espup 是 Rust 程序,不会执行 python 代码,所以支持 socks 代理。开启代理后可以缩短下载时间。
+zj@a:~/esp$ enable_socks_proxy
+
+# 安装 ESP32 的 Rust toolchain
+zj@a:~/esp$ espup install -l debug
+[debug]: Creating export file
+[info]: Installation successfully completed!
+
+        To get started, you need to set up some environment variables by running: '. /Users/alizj/export-esp.sh'
+        This step must be done every time you open a new terminal.
+            See other methods for setting the environment in https://esp-rs.github.io/book/installation/riscv-and-xtensa.html#3-set-up-the-environment-variables
+
+# 修改自动生成的 ~/export-esp.sh 脚本
+zj@a:~$ cat ~/export-esp.sh
+# 删除 python venv 路径,防止后续 cargo build 安装 esp-idf 报错。
+export DIR_TO_REMOVE=/Users/alizj/.venv/bin
+PATH=$(echo "$PATH" | sed -e "s;:$DIR_TO_REMOVE;;" -e "s;$DIR_TO_REMOVE:;;" -e "s;$DIR_TO_REMOVE;;")
+# 解决 Mac M1 cargo build 报错的问题:https://github.com/rust-lang/cc-rs/issues/1005
+CRATE_CC_NO_DEFAULTS=1
+# cargo build 过程中会安装 esp-idf python venv ,不支持 SOCKS 代理,故切换为 HTTP 代理。
+enable_http_proxy
+
+export LIBCLANG_PATH="/Users/alizj/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-16.0.4-20231113/esp-clang/lib"
+export PATH="/Users/alizj/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin:$PATH"

为了方便使用,将 ~/export-esp.sh 脚本 mv 到 ~/esp 目录,同时添加两个 alias:

zj@a:~/esp$ mv ~/export-esp.sh ~/esp/
+zj@a:~/esp$ grep esp ~/.bashrc
+alias export_idf='. $HOME/esp/esp-idf/export.sh'
+alias export_esp='. $HOME/esp/export-esp.sh'

后续每次使用 esp-rs 前都需要 source ~/esp/export-esp.sh 脚本。

修正 esp toolchain 缺失 rust-analyzer 的问题

esp 是 espup 安装的自定义 toolchain,不包含 rust-analyzer,需要建一个软链接:

zj@a:~$ rustup component add rust-analyzer
+info: downloading component 'rust-analyzer'
+info: installing component 'rust-analyzer'
+
+zj@a:~/docs$ ls -l ~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/
+total 96M
+-rwxr-xr-x 1 alizj  27M  5  5 12:11 cargo*
+-rwxr-xr-x 1 alizj 1.1M  5  5 12:11 cargo-clippy*
+-rwxr-xr-x 1 alizj 1.5M  5  5 12:11 cargo-fmt*
+-rwxr-xr-x 1 alizj  11M  5  5 12:11 clippy-driver*
+-rwxr-xr-x 1 alizj  39M  5  5 12:30 rust-analyzer*
+-rwxr-xr-x 1 alizj  980  5  5 12:11 rust-gdb*
+-rwxr-xr-x 1 alizj 2.2K  5  5 12:11 rust-gdbgui*
+-rwxr-xr-x 1 alizj 1.1K  5  5 12:11 rust-lldb*
+-rwxr-xr-x 1 alizj 626K  5  5 12:11 rustc*
+-rwxr-xr-x 1 alizj  11M  5  5 12:11 rustdoc*
+-rwxr-xr-x 1 alizj 6.3M  5  5 12:11 rustfmt*
+
+zj@a:~/docs$ ln -sf ~/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer ~/.rustup/toolchains/esp/bin/rust-analyzer

也可以从 esp-rs/rust 源码编译安装 rust-analyer:

# 在 --tools 中指定 rust-analyzer 来编译安装 esp32 使用的 rust-analyzer
+./configure --experimental-targets=Xtensa --release-channel=nightly --enable-extended
+  --tools=clippy,cargo,rustfmt,rust-analyzer --enable-lld

espup 安装的内容位于 ~/.espup 目录下,主要包含如下内容:

# 安装了两个 toolchain,分别是 nightly-x86_64-apple-darwin 和 esp
+#
+# nightly-x86_64-apple-darwin 是 rust 官方工具链,支持 riscv32xx 和 x86_64-apple-darwin 等 4 个 targets
+# riscv32xx 是 esp32-c3 系列 RISC-V CPU 类型。
+zj@a:~/esp$ rustup show
+Default host: aarch64-apple-darwin
+rustup home:  /Users/alizj/.rustup
+
+installed toolchains
+--------------------
+
+stable-aarch64-apple-darwin (default) # 标准工具链
+nightly-aarch64-apple-darwin
+esp # 自定义 esp 工具链
+
+active toolchain
+----------------
+
+stable-aarch64-apple-darwin (default)
+rustc 1.78.0 (9b00956e5 2024-04-29)
+
+# espup 安装的 esp 工具链, esp 为 channel 名称
+zj@a:~/esp$ ls -l ~/.rustup/toolchains/esp/
+total 0
+drwxr-xr-x 12 alizj 384  5  5 12:12 bin/ # esp fork 的 rust 交叉编译工具链
+drwxr-xr-x  3 alizj  96  5  5 12:12 etc/
+drwxr-xr-x  5 alizj 160  5  5 12:13 lib/
+drwxr-xr-x  3 alizj  96  5  5 12:12 libexec/
+drwxr-xr-x  5 alizj 160  5  5 12:12 share/
+drwxr-xr-x  3 alizj  96  5  5 12:11 xtensa-esp-elf/ # esp fork 的支持 xtensa CPU 的 gcc 等工具链
+drwxr-xr-x  3 alizj  96  5  5 12:11 xtensa-esp32-elf-clang/ # esp fork 的 clang LLVM 工具链
+
+# esp fork 的 rust 交叉编译工具链
+zj@a:~/esp$ ls -l ~/.rustup/toolchains/esp/bin/
+total 63M
+-rwxr-xr-x 1 alizj  30M  5  5 12:12 cargo*
+-rwxr-xr-x 1 alizj 1.2M  5  5 12:12 cargo-clippy*
+-rwxr-xr-x 1 alizj 1.6M  5  5 12:12 cargo-fmt*
+-rwxr-xr-x 1 alizj  11M  5  5 12:12 clippy-driver*
+-rwxr-xr-x 1 alizj  980  5  5 12:12 rust-gdb*
+-rwxr-xr-x 1 alizj 2.2K  5  5 12:12 rust-gdbgui*
+-rwxr-xr-x 1 alizj 1.1K  5  5 12:12 rust-lldb*
+-rwxr-xr-x 1 alizj 584K  5  5 12:12 rustc*
+-rwxr-xr-x 1 alizj  12M  5  5 12:12 rustdoc*
+-rwxr-xr-x 1 alizj 7.1M  5  5 12:12 rustfmt*
+
+zj@a:~/docs$ ls -l ~/.rustup/toolchains/esp/bin/
+total 59M
+-rwxr-xr-x 1 zhangjun  28M  2  8 14:51 cargo*
+-rwxr-xr-x 1 zhangjun 1.1M  2  8 14:51 cargo-clippy*
+-rwxr-xr-x 1 zhangjun 1.6M  2  8 14:51 cargo-fmt*
+-rwxr-xr-x 1 zhangjun  11M  2  8 14:51 clippy-driver*
+lrwxr-xr-x 1 zhangjun   80  2  8 16:58 rust-analyzer -> /Users/zhangjun/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/rust-analyzer*  # 链接到官方工具链的 rust-analyzer
+-rwxr-xr-x 1 zhangjun  980  2  8 14:50 rust-gdb*
+-rwxr-xr-x 1 zhangjun 2.2K  2  8 14:50 rust-gdbgui*
+-rwxr-xr-x 1 zhangjun 1.1K  2  8 14:50 rust-lldb*
+-rwxr-xr-x 1 zhangjun 664K  2  8 14:50 rustc*
+-rwxr-xr-x 1 zhangjun  11M  2  8 14:50 rustdoc*
+-rwxr-xr-x 1 zhangjun 6.9M  2  8 14:51 rustfmt*
+
+# esp rustc 支持 x86_64/arm64/riscv64/xtensa-esp32s3-espidf/xtensa-esp32s3-none-elf target
+# 后续可以在项目的 rust-toolchain.toml 和 .cargo/config.toml 中指定使用 esp channel 和对应的 target。
+zj@a:~/docs$  ~/.rustup/toolchains/esp/bin/rustc --print target-list |grep -E 'xtensa|riscv'
+riscv32gc-unknown-linux-gnu
+riscv32gc-unknown-linux-musl
+riscv32i-unknown-none-elf
+riscv32im-unknown-none-elf
+riscv32imac-esp-espidf
+riscv32imac-unknown-none-elf
+riscv32imac-unknown-xous-elf
+riscv32imc-esp-espidf
+riscv32imc-unknown-none-elf
+riscv64-linux-android
+riscv64gc-unknown-freebsd
+riscv64gc-unknown-fuchsia
+riscv64gc-unknown-hermit
+riscv64gc-unknown-linux-gnu
+riscv64gc-unknown-linux-musl
+riscv64gc-unknown-netbsd
+riscv64gc-unknown-none-elf
+riscv64gc-unknown-openbsd
+riscv64imac-unknown-none-elf
+xtensa-esp32-espidf
+xtensa-esp32-none-elf
+xtensa-esp32s2-espidf
+xtensa-esp32s2-none-elf
+xtensa-esp32s3-espidf  # esp idf target,即 std 应用
+xtensa-esp32s3-none-elf # none-elf 为 non_std 应用
+xtensa-esp8266-none-elf
+
+zj@a:~/esp$ ls -l  ~/.espup/
+total 0
+lrwxr-xr-x 1 alizj 92  5  5 12:11 esp-clang -> /Users/alizj/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-16.0.4-20231113/esp-clang/lib/
+
+# ~/esp/export-esp.sh 脚本将 LIBCLANG_PATH 环境变量指向 esp fork 的 LLVM 目录,这样后续 rustc 在编
+# 译时自动链接 esp 的版本。(rustc 依赖 LLVM)。
+zj@a:~/esp$ ls -l  /Users/alizj/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-16.0.4-20231113/esp-clang/lib/
+total 117M
+drwxr-xr-x 3 alizj  96  5  5 12:11 clang/
+-rw-r--r-- 1 alizj 50M 11 14 23:46 libLLVM.dylib
+-rw-r--r-- 1 alizj 44M 11 14 23:46 libclang-cpp.dylib
+-rw-r--r-- 1 alizj 24M 11 14 23:46 libclang.dylib
+
+zj@a:~/esp$ cd ~/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin/
+zj@a:~/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin$ ls -l *esp32s3*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-addr2line*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-ar*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-as*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-c++*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-c++filt*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-cc*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-cpp*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-elfedit*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-g++*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcc*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcc-13.2.0*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcc-ar*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcc-nm*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcc-ranlib*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcov*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcov-dump*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gcov-tool*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-gprof*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-ld*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-ld.bfd*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-lto-dump*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-nm*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-objcopy*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-objdump*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-ranlib*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-readelf*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-size*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-strings*
+-rwxr-xr-x 1 alizj 361K  9 29  2023 xtensa-esp32s3-elf-strip*
+zj@a:~/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin$
+
+# export-esp.sh 将该 bin 目录添加 PATH 前面
+zj@a:~$ cd esp/
+zj@a:~/esp$  ls ~/.rustup/toolchains/esp/xtensa-esp-elf/esp-*/xtensa-esp-elf/xtensa-esp-elf/bin/
+ar*  as*  ld*  ld.bfd*  nm*  objcopy*  objdump*  ranlib*  readelf*  strip*
+
+# 这些工具是 crosstool-NG 的 esp 版本
+zj@a:~/docs$  ~/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/bin/ld --version
+GNU ld (crosstool-NG esp-13.2.0_20230928) 2.41
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+# 它们支持 elf32-xtensa target
+zj@a:~/docs$  ~/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/bin/ld --help |grep supp
+                              Enable support of non-contiguous memory regions
+/Users/zhangjun/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/bin/ld: supported targets: elf32-xtensa-le elf32-xtensa-be elf32-little elf32-big srec symbolsrec verilog tekhex binary ihex plugin
+/Users/zhangjun/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/xtensa-esp-elf/bin/ld: supported emulations: elf32xtensa
+
+# esp rustc 支持 x86_64/arm64/riscv64/xtensa-esp32s3-espidf/xtensa-esp32s3-none-elf target
+# 后续可以在项目的 rust-toolchain.toml 和 .cargo/config.toml 中指定使用 esp channel 和对应的 target。
+zj@a:~/docs$  ~/.rustup/toolchains/esp/bin/rustc --print target-list |grep -E 'xtensa|riscv'
+riscv32gc-unknown-linux-gnu
+riscv32gc-unknown-linux-musl
+riscv32i-unknown-none-elf
+riscv32im-unknown-none-elf
+riscv32imac-esp-espidf
+riscv32imac-unknown-none-elf
+riscv32imac-unknown-xous-elf
+riscv32imc-esp-espidf
+riscv32imc-unknown-none-elf
+riscv64-linux-android
+riscv64gc-unknown-freebsd
+riscv64gc-unknown-fuchsia
+riscv64gc-unknown-hermit
+riscv64gc-unknown-linux-gnu
+riscv64gc-unknown-linux-musl
+riscv64gc-unknown-netbsd
+riscv64gc-unknown-none-elf
+riscv64gc-unknown-openbsd
+riscv64imac-unknown-none-elf
+xtensa-esp32-espidf
+xtensa-esp32-none-elf
+xtensa-esp32s2-espidf
+xtensa-esp32s2-none-elf
+xtensa-esp32s3-espidf  # espidf 后缀的 target 为使用 eps-idf 的 std 应用
+xtensa-esp32s3-none-elf # none-elf 后缀的 target 为不使用 esp-idf 的 non_std 应用
+xtensa-esp8266-none-elf

更新 esp-rs 工具链:

zj@a:~/esp$ enable_socks_proxy
+zj@a:~/esp$ espup update

+ + + + \ No newline at end of file diff --git a/Rust10.html b/Rust10.html new file mode 100644 index 0000000..4eb4628 --- /dev/null +++ b/Rust10.html @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + 使用 USB-JTAG/probe-rs 调试应用 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 使用 USB-JTAG/probe-rs 调试应用 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

ESP32-S3 开发版一般有两个 USB 接口,一般标记为 UART 和 USB,后者是支持 USB-Serial-JTAG 调试的接口,该接口包含两个 USB 设备:

  1. USB-CDC-ACM:PC 识别为 USB 串口设备,可以用于下载固件和打印芯片输出的日志;
  2. JTAG 设备:可以被用来进行 JTAG 调试;

查看 MaxcOS USE-Serial-JTAG 设备信息:

Copy

zj@a:~/docs$ system_profiler SPUSBDataType | grep -A 11 "USB JTAG"
+        USB JTAG/serial debug unit:
+
+          Product ID: 0x1001
+          Vendor ID: 0x303a
+          Version: 1.01
+          Serial Number: 3C:84:27:04:FE:18
+          Speed: Up to 12 Mb/s
+          Manufacturer: Espressif
+          Location ID: 0x02100000 / 2
+          Current Available (mA): 500
+          Current Required (mA): 500
+          Extra Operating Current (mA): 0

对应的设备名称为 /dev/tty.usbmodem* 或 /dev/cu.usbmodem*:

Copy

zj@a:~/docs$ ls -l /dev/*.usbmodem*
+crw-rw-rw- 1 root 9, 11  5 10 21:38 /dev/cu.usbmodem2101
+crw-rw-rw- 1 root 9, 10  5 10 20:16 /dev/tty.usbmodem2101

Copy

zj@a:~/docs$ espflash board-info
+[2024-05-10T13:38:06Z INFO ] Detected 2 serial ports
+[2024-05-10T13:38:06Z INFO ] Ports which match a known common dev board are highlighted
+[2024-05-10T13:38:06Z INFO ] Please select a port
+[2024-05-10T13:38:19Z INFO ] Serial port: '/dev/cu.usbmodem2101'
+[2024-05-10T13:38:19Z INFO ] Connecting...
+[2024-05-10T13:38:21Z INFO ] Using flash stub
+Chip type:         esp32s3 (revision v0.2)
+Crystal frequency: 40 MHz
+Flash size:        16MB
+Features:          WiFi, BLE
+MAC address:       3c:84:27:04:fe:18
+
+# 使用 probe-rs 工具
+zj@a:~/docs$ probe-rs info
+Probing target via JTAG
+
+ WARN probe_rs::probe::espusbjtag: More than one TAP detected, defaulting to tap0
+No DAP interface was found on the connected probe. ARM-specific information cannot be printed.
+Error while reading RISC-V info: Connected target is not a RISC-V device.
+Xtensa Chip:
+  IDCODE: 00120034e5
+    Version:      1
+    Part:         8195
+    Manufacturer: 626 (Tensilica)
+
+Probing target via SWD
+
+Error identifying target using protocol SWD: Probe does not support SWD
+
+zj@a:~/docs$

由于 ESP32-S3 自带 USB-Serial-JTAG Controller,故不需要单独的外接 debug probe 硬件来进行烧写或调试。确认方法:

Copy

cargo espflash board-info
+# or
+espflash board-info

ESP32-S3 支持使用 probe-rs 或 ESP32 fork 的 OpenCDC 进行 JTAG 调试。

probe-rs 是一个使用 JTAG 接口进行芯片 flash 烧写和调试的工具,支持 RISC-V/ARM/Xtensor。

安装 probe-rs 和相关工具:

Copy

zj@a:~/docs$ cargo install probe-rs --features cli
+   Installed package `probe-rs v0.23.0` (executables `cargo-embed`, `cargo-flash`, `probe-rs`)

probe-rs 最重要的是 run 命令,可以集成到 cargo run 中:.cargo/config.toml

Copy

[target.<architecture-triple>]
+runner = 'probe-rs run --chip esp32s3'

Now you can execute cargo run in your project as you would for any native binaries and you will receive RTT and defmt logs in that very same console as if you wrote to standard out.

probe-rs attach works exactly like probe-rs run except that it does not issue a reset and does not flash the target on connecting to preserve the currently running state. This is great for inspecting a target - where you might not even have knowledge of the firmware - without altering its state.

probe-rs 实现了 DAP 协议,可以直接在 VS Code 中调试代码。

probe-rs 和 DAP 集成:https://probe.rs/docs/tools/debugger/

  1. 下载 probe-vs 的 vs-code 插件:页面右侧 Resouces 下的 Download Extension https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger
  2. 解压 vsix 文件到 emacs 目录,参考:https://github.com/svaante/dape
  3. 配置 dape;

probe-rs 示例:

Copy

zj@a:~/code/slint/examples$ ls -l /dev/*usbmodem*
+crw-rw-rw- 1 root 9, 9  5 11 17:38 /dev/cu.usbmodem2101
+crw-rw-rw- 1 root 9, 8  5 11 17:38 /dev/tty.usbmodem2101
+
+zj@a:~/code/slint/examples$ probe-rs  list
+The following debug probes were found:
+[0]: ESP JTAG (VID: 303a, PID: 1001, Serial: 3C:84:27:04:FE:18, EspJtag)
+
+zj@a:~/code/slint/examples$ probe-rs  info
+Probing target via JTAG
+
+ WARN probe_rs::probe::espusbjtag: More than one TAP detected, defaulting to tap0
+No DAP interface was found on the connected probe. ARM-specific information cannot be printed.
+Error while reading RISC-V info: Connected target is not a RISC-V device.
+Xtensa Chip:
+  IDCODE: 00120034e5
+    Version:      1
+    Part:         8195
+    Manufacturer: 626 (Tensilica)
+
+Probing target via SWD
+
+Error identifying target using protocol SWD: Probe does not support SWD
+
+
+zj@a:~/code/slint/examples$ probe-rs  chip list  |grep esp
+esp32c6
+        esp32c6
+esp32s2
+        esp32s2
+esp32h2
+        esp32h2
+esp32s3
+        esp32s3
+esp32
+        esp32-3.3v
+esp32c2
+        esp32c2
+esp32c3
+        esp32c3
+esp32c6_lp
+        esp32c6_lp
+
+zj@a:~/code/slint/examples$ probe-rs  chip info esp32s3
+esp32s3
+Cores (1):
+    - cpu0 (Xtensa)
+NVM: 0x00000000..0x04000000 (64.00 MiB)
+RAM: 0x3fc88000..0x3fcf0000 (416.00 KiB)
+RAM: 0x3fcf0000..0x3fd00000 (64.00 KiB)
+RAM: 0x40370000..0x40378000 (32.00 KiB)
+RAM: 0x40378000..0x403e0000 (416.00 KiB)
+NVM: 0x42000000..0x44000000 (32.00 MiB)
+NVM: 0x3c000000..0x3e000000 (32.00 MiB)
+ + + + \ No newline at end of file diff --git a/Rust11.html b/Rust11.html new file mode 100644 index 0000000..b2813dc --- /dev/null +++ b/Rust11.html @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rust 驱动 LCD - 显示中英文 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ Rust 驱动 LCD - 显示中英文 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

embedded-graphics 支持 BDF 和 MonoFont 字体:

The draw method for text drawables returns the position of the next character. This can be used to combine text with different character styles on a single line of text.

Copy

// https://docs.rs/embedded-graphics/latest/embedded_graphics/text/index.html#examples
+// Create a small and a large character style.
+let small_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
+let large_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
+
+// Draw the first text at (20, 30) using the small character style.
+let next = Text::new("small ", Point::new(20, 30), small_style).draw(&mut display)?;
+
+// Draw the second text after the first text using the large character style.
+let next = Text::new("large", next, large_style).draw(&mut display)?;

字体参考:https://github.com/olikraus/u8g2

1 embedded-graphics/bdf #

https://github.com/embedded-graphics/bdf/tree/master

该库可以从 bdf 字体文件中生成 embedded-graphics 能使用的:

  1. BDF Font:需要配合使用embedded-graphics/bdf/eg-bdf 库中提供的 BdfFont/BdfGlyph/BdfTextStyle 类型;
  2. MonoFont:embedded-graphics 可以直接导入使用;

先使用 otf2bdf 工具将 TTF(truetype font) 字体转换为 BDF(Bitmap Distribution Format) 类型:

Copy

brew install otf2bdf
+otf2bdf  -p 12 -r 75 -o LXGWWenKai_Regular.bdf  ~/Library/Fonts/LXGWWenKai-Regular.ttf

然后使用 eg-font-converter lib 来生成 BDF 或 MonoFont 字体:

生成 BDF 字体示例:

Copy

// /Users/zhangjun/codes/rust/bdf/eg-font-converter/src/bin/my-bdf-font.rs
+zj@a:~/codes/rust/bdf/eg-font-converter$ mkdir output
+zj@a:~/codes/rust/bdf/eg-font-converter$ ls
+Cargo.lock  Cargo.toml  output/  src/  tests/
+zj@a:~/codes/rust/bdf/eg-font-converter$ cat src/bin/my-bdf-font.rs
+use eg_font_converter::{FontConverter};
+
+// 中文 unicode 字体范围: https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
+// 使用 .glyphs() 来指定要生成的字符范围
+fn main() {
+    let font = FontConverter::new("/Users/zhangjun/docs/LXGWWenKai_Regular.bdf", "BDF_LXGWWenKai_Regular_FONT")
+        //.glyphs('a'..='z')
+        .glyphs('\u{0000}'..='\u{007F}') // ASCII
+        .glyphs('\u{4E00}'..='\u{9FFF}') // 常用中文字体范围
+        .glyphs('\u{2E80}'..='\u{2EF3}') // 常见繁体字范围
+        .missing_glyph_substitute('?') // 替代字符
+        .convert_eg_bdf()
+        .unwrap();
+    font.save("output/").unwrap();  // 结果写入 output 目录下, 存入 new(bdf_file, name) 的 小写 name.rs 文件中.
+}
+
+// 运行该程序
+zj@a:~/codes/rust/bdf/eg-font-converter$ cargo run --bin my-bdf-font
+    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
+     Running `/Users/zhangjun/codes/rust/bdf/target/debug/my-bdf-font`
+
+zj@a:~/codes/rust/bdf/eg-font-converter$ ls output/
+bdf_lxgwwenkai_regular_font.data  bdf_lxgwwenkai_regular_font.rs
+zj@a:~/codes/rust/bdf/eg-font-converter$ ls -l output/
+total 4.7M
+-rw-r--r-- 1 zhangjun 310K  2 17 15:34 bdf_lxgwwenkai_regular_font.data
+-rw-r--r-- 1 zhangjun 4.4M  2 17 15:34 bdf_lxgwwenkai_regular_font.rs
+zj@a:~/codes/rust/bdf/eg-font-converter$ head -20 output/bdf_lxgwwenkai_regular_font.rs
+const BDF_LXGWWenKai_Regular_FONT: ::eg_bdf::BdfFont = {
+    const fn rect(
+        x: i32,
+        y: i32,
+        width: u32,
+        height: u32,
+    ) -> ::embedded_graphics::primitives::Rectangle {
+        ::embedded_graphics::primitives::Rectangle::new(
+            ::embedded_graphics::geometry::Point::new(x, y),
+            ::embedded_graphics::geometry::Size::new(width, height),
+        )
+    }
+    ::eg_bdf::BdfFont {
+        data: include_bytes!("bdf_lxgwwenkai_regular_font.data"),
+        replacement_character: 63usize,
+        ascent: 12u32,
+        descent: 3u32,
+        glyphs: &[
+            BdfGlyph {
+                character: '\0',
+zj@a:~/codes/rust/bdf/eg-font-converter$

使用:

Copy

zj@a:~/codes/rust/bdf/eg-font-converter$ cp output/bdf_lxgwwenkai_regular_font.* ~/codes/esp32/st7735-lcd-examples/esp32c3-examples/src/
+
+# 添加本地依赖
+gzj@a:~/codes/esp32/st7735-lcd-examples/esp32c3-examples$ grep eg Cargo.toml
+eg-bdf = { path = "../../../rust/bdf/eg-bdf/" } // clone https://github.com/embedded-graphics/bdf 的本地目录

代码举例:

Copy

use eg_bdf::{BdfGlyph, BdfTextStyle};
+
+include!("./bdf_lxgwwenkai_regular_font.rs");
+
+fn main() {
+    let text = "Happy\u{AD}Loong\u{AD}Year!\
+      Happy Hacking!\
+      Happy Loong Year!\u{A0}新年快乐!\u{A0}-from Rust ESP32";
+    let bdf_style = BdfTextStyle::new(&BDF_LXGWWenKai_Regular_FONT, Rgb565::WHITE);
+    let textbox_style = TextBoxStyleBuilder::new()
+      .line_height(LineHeight::Pixels(12)) // 字体高度与生成 otf2bdf  -p 12 一致
+      .alignment(HorizontalAlignment::Justified)
+      .paragraph_spacing(0)
+      .build();
+    let mut bounds = Rectangle::new(Point::new(0, 0), Size::new(128, 160));
+    let mut text_box = TextBox::with_textbox_style(text, bounds, bdf_style, textbox_style);
+    let next = text_box.draw(&mut display).unwrap();
+}
+ + + + \ No newline at end of file diff --git a/Rust12.html b/Rust12.html new file mode 100644 index 0000000..408e74b --- /dev/null +++ b/Rust12.html @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rust 驱动 LCD - 显示图片 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Rust 驱动 LCD - 显示图片 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

目录

目录

rust-esp32 - 这篇文章属于一个选集。

§ 1: 安装 ESP32 C 开发框架 esp-idf

§ 2: 使用 C 和 esp-idf 开发 ESP32 项目

§ 3: 安装 ESP32 Rust 开发工具链 esp-rs

§ 4: 使用 Rust 开发 ESP32 应用

§ 5: 开发 Rust std 应用

§ 6: 开发 Rust no_std 应用

§ 7: 开发 Rust/C/C++ cmake 混合应用

§ 8: esp-rs 常见问题

§ 9: 配置 cargo workspace

§ 10: 使用 cargo run 和 espflash 烧写固件

§ 11: 分析 ESP32 固件和分区表

§ 12: 使用 USB-JTAG/probe-rs 调试应用

§ 13: Rust 驱动 LCD - 显示中英文

§ 14: 本文

§ 15: Rust 驱动 Touch - 触摸板

§ 16: Rust 驱动 Camera - 采集和播放

§ 17: Rust 驱动 Audio - 播放和录音

一个 LCD 小游戏:esp32-spooky-maze-game

另外一个 LCD 项目样例: https://github.com/georgik/esp32-rust-multi-target-template/blob/main/esp32-s3-usb-otg/src/main.rs

ESP Display Interface with SPI and DMA: https://github.com/georgik/esp-display-interface-spi-dma/blob/main/README.md

参考文档:

  1. https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html
  2. https://www.makerfabs.com/esp32-3-5-inch-tft-touch-capacitive-with-camera.html

LCD 和 Touch 一般集成在一块屏幕上(部分 Touch 还支持固定的触摸按钮),所以 esp-idf 提供了 lcd_panel 对象来综合管理 LCD 和 Touch。

常用 LCD 接口:

https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/lcd_guide.html

esp-idf/examples/peripherals/lcd/ 目录包含这些接口的 LCD 驱动:

esp-iot-solution/examples/display/lcd/ 目录也包含一些 LCD 例子:

1 显示 raw 图片 #

raw 图片是指 RGB888/RGB565 等格式的图片。

像素格式主要分为 YUV 和 RGB 两种大类:

  1. YUV422: 也称为 YCbCr:Y 亮度,U/Cb:蓝色浓度,V/Cr:红色浓度;
    • 为了节省带宽 YUV frame 一般使用采样,如 YUV422 表示 2:1的水平取样,垂直完全采样;
  2. RGB888:也称 24-bit RGB,各使用 8bit 来表示 red/green/blue;
  3. RGB666: 也称 18-bit RGB
  4. RGB565:也称 16-bit RGB,5 bits for the red channel, 6 bits for the green channel, and 5 bits for the blue channel. 相比 RGB888,更节省资源;

YUV 和 RGB 之间可以相互转换。

LCD 一般使用 RGB 像素格式,而且是比较节省空间的 16-bit 的 RGB565 像素格式:

需要将 JPEG 解码或 bmp 中的像素数据转换为 RGB888, RGB565 等格式的 raw picture 后,LCD 才能直接显示。

Copy

或者使用 LVGL 提供的在线转换工具:https://lvgl.io/tools/imageconverter

可以使用 OpenCV 库来显示 raw data picutre:

Copy

# https://github.com/espressif/esp-idf/blob/master/examples/peripherals/jpeg/jpeg_decode/open_raw_picture.py
+
+# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Unlicense OR CC0-1.0
+import argparse
+
+import cv2 as cv
+import numpy as np
+from numpy.typing import NDArray
+
+
+def open_picture(path):  # type: (str) -> list[int]
+    with open(path, 'rb') as f:
+        data = f.read()
+        f.close()
+    new_data = [int(x) for x in data]
+    return new_data
+
+
+def picture_show_rgb888(data, h, w):  # type: (list[int], int, int) -> None
+    data = np.array(data).reshape(h, w, 3).astype(np.uint8)
+    cv.imshow('data', data)
+    cv.waitKey()
+
+
+def picture_show_rgb565(data, h, w):  # type: (list[int], int, int) -> None
+
+    new_data = [0] * ((len(data) // 2) * 3)
+    for i in range(len(data)):
+        if i % 2 != 0:
+            new_data[3 * (i - 1) // 2 + 2] = (data[i] & 0xf8)
+            new_data[3 * (i - 1) // 2 + 1] |= (data[i] & 0x7) << 5
+        else:
+            new_data[3 * i // 2] = (data[i] & 0x1f) << 3
+            new_data[3 * i // 2 + 1] |= (data[i] & 0xe0) >> 3
+
+    new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_gray(data, h, w):  # type: (list[int], int, int) -> None
+    new_data = np.array(data).reshape(h, w, 1).astype(np.uint8)
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def convert_YUV_to_RGB(Y, U, V):  # type: (NDArray, NDArray, NDArray) -> tuple[NDArray, NDArray, NDArray]
+    B = np.clip(Y + 1.7790 * (U - 128), 0, 255).astype(np.uint8)
+    G = np.clip(Y - 0.3455 * (U - 128) - 0.7169 * (V - 128), 0, 255).astype(np.uint8)
+    R = np.clip(Y + 1.4075 * (V - 128), 0, 255).astype(np.uint8)
+
+    return B, G, R
+
+
+def picture_show_yuv420(data, h, w):  # type: (list[int], int, int) -> None
+    new_u = [0] * (h * w)
+    new_v = [0] * (h * w)
+    new_y = [0] * (h * w)
+
+    for i in range(int(h * w * 1.5)):
+        is_even_row = ((i // (w * 1.5)) % 2 == 0)
+        if is_even_row:
+            if (i % 3 == 0):
+                new_u[(i // 3) * 2] = data[i]
+                new_u[(i // 3) * 2 + 1] = data[i]
+        else:
+            if (i % 3 == 0):
+                new_u[(i // 3) * 2] = new_u[int((i - (w * 1.5)) // 3) * 2]
+                new_u[(i // 3) * 2 + 1] = new_u[int((i - (w * 1.5)) // 3) * 2 + 1]
+
+    for i in range(int(h * w * 1.5)):
+        if (i // (w * 1.5)) % 2 != 0 and (i % 3 == 0):
+            idx = (i // 3) * 2
+            new_v[idx] = data[i]
+            new_v[idx + 1] = data[i]
+
+    for i in range(int(h * w * 1.5)):
+        if (i // (w * 1.5)) % 2 == 0 and (i % 3 == 0):
+            idx = (i // 3) * 2
+            new_v[idx] = new_v[int((i + (w * 1.5)) // 3) * 2]
+            new_v[idx + 1] = new_v[int((i + (w * 1.5)) // 3) * 2 + 1]
+
+    new_y = [data[i] for i in range(int(h * w * 1.5)) if i % 3 != 0]
+
+    Y = np.array(new_y)
+    U = np.array(new_u)
+    V = np.array(new_v)
+
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+    new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_yuv422(data, h, w):  # type: (list[int], int, int) -> None
+    # Reshape the input data to a 2D array
+    data_array = np.array(data).reshape(h, w * 2)
+
+    # Separate Y, U, and V channels
+    Y = data_array[:, 1::2]
+    U = data_array[:, 0::4].repeat(2, axis=1)
+    V = data_array[:, 2::4].repeat(2, axis=1)
+
+    # Convert YUV to RGB
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_yuv444(data, h, w):  # type: (list[int], int, int) -> None
+    # Reshape the input data to a 2D array
+    data_array = np.array(data).reshape(h, w * 3)
+
+    # Separate Y, U, and V channels
+    Y = data_array[:, 2::3]
+    U = data_array[:, 1::3]
+    V = data_array[:, 0::3]
+
+    # Convert YUV to RGB
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def main():  # type: () -> None
+    parser = argparse.ArgumentParser(description='which mode need to show')
+
+    parser.add_argument(
+        '--pic_path',
+        type=str,
+        help='What is the path of your picture',
+        required=True)
+
+    parser.add_argument(
+        '--pic_type',
+        type=str,
+        help='What type you want to show',
+        required=True,
+        choices=['rgb565', 'rgb888', 'gray', 'yuv422', 'yuv420', 'yuv444'])
+
+    parser.add_argument(
+        '--height',
+        type=int,
+        help='the picture height',
+        default=480)
+
+    parser.add_argument(
+        '--width',
+        type=int,
+        help='the picture width',
+        default=640)
+
+    args = parser.parse_args()
+
+    height = args.height
+    width = args.width
+
+    data = open_picture(args.pic_path)
+    if (args.pic_type == 'rgb565'):
+        picture_show_rgb565(data, height, width)
+    elif (args.pic_type == 'rgb888'):
+        picture_show_rgb888(data, height, width)
+    elif (args.pic_type == 'gray'):
+        picture_show_gray(data, height, width)
+    elif (args.pic_type == 'yuv420'):
+        picture_show_yuv420(data, height, width)
+    elif (args.pic_type == 'yuv422'):
+        picture_show_yuv422(data, height, width)
+    elif (args.pic_type == 'yuv444'):
+        picture_show_yuv444(data, height, width)
+    else:
+        print('This type is not supported in this script!')
+
+
+if __name__ == '__main__':
+    main()

2 显示 png 图片 #

使用如下 python 代码将 png 图片转换为 raw RGB 565 format:

Copy

#!/usr/bin/python
+
+import sys
+from PIL import Image
+
+if len(sys.argv) == 3:
+    # print "\nReading: " + sys.argv[1]
+    out = open(sys.argv[2], "wb")
+elif len(sys.argv) == 2:
+    out = sys.stdout
+else:
+    print "Usage: png2fb.py infile [outfile]"
+    sys.exit(1)
+
+im = Image.open(sys.argv[1])
+
+if im.mode == "RGB":
+    pixelSize = 3
+elif im.mode == "RGBA":
+    pixelSize = 4
+else:
+    sys.exit('not supported pixel mode: "%s"' % (im.mode))
+
+pixels = im.tostring()
+pixels2 = ""
+for i in range(0, len(pixels) - 1, pixelSize):
+    pixels2 += chr(ord(pixels[i + 2]) >> 3 | (ord(pixels[i + 1]) << 3 & 0xe0))
+    pixels2 += chr(ord(pixels[i]) & 0xf8 | (ord(pixels[i + 1]) >> 5 & 0x07))
+out.write(pixels2)
+out.close()

3 显示 bmp 图片 #

bmp 图片是未经压缩的像素 bit 文件,包含 header + 像素数据,不需要解压缩和转码,可以直接读取,转换为 RGB 565 格式:

Copy

// https://github.com/Makerfabs/Project_Touch-Screen-Camera/blob/master/example/SD2TFT/SD2TFT.ino#L205
+
+int print_img(fs::FS &fs, String filename)
+{
+    SPI_ON_SD;
+    File f = fs.open(filename);
+    if (!f)
+    {
+        Serial.println("Failed to open file for reading");
+        return 0;
+    }
+
+    f.seek(54);
+    int X = 480;
+    int Y = 320;
+    uint8_t RGB[3 * X];
+    for (int row = 0; row < Y; row++)
+    {
+        f.seek(54 + 3 * X * row);
+        f.read(RGB, 3 * X);
+        SPI_OFF_SD;
+        SPI_ON_TFT;
+        for (int col = 0; col < X; col++)
+        {
+            tft.drawPixel(col, row, tft.color565(RGB[col * 3 + 2], RGB[col * 3 + 1], RGB[col * 3]));
+        }
+        SPI_OFF_TFT;
+        SPI_ON_SD;
+    }
+
+    f.close();
+    SPI_OFF_SD;
+    return 0;
+}

4 显示 jpeg 图片 #

JPEG 是压缩图片格式,在 LCD 显示前需要先对其进行解压缩和解码,转换为 LCD 上显示的 RGB 像素数据(raw picture),如 JPEG_RAW_TYPE_RGB565_BE:

Copy

// https://github.com/espressif/esp-box/blob/master/examples/usb_camera_lcd_display/main/main.c#L56
+
+// 将 JPEG 解码为 JPEG_RAW_TYPE_RGB565_BE 输出格式
+static jpeg_error_t esp_jpeg_decoder_one_picture(uint8_t *input_buf, int len, uint8_t *output_buf)
+{
+    esp_err_t ret = ESP_OK;
+    // Generate default configuration
+    jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
+    config.output_type = JPEG_RAW_TYPE_RGB565_BE;
+    // Empty handle to jpeg_decoder
+    jpeg_dec_handle_t jpeg_dec = NULL;
+
+    // Create jpeg_dec
+    jpeg_dec = jpeg_dec_open(&config);
+
+    // Create io_callback handle
+    jpeg_dec_io_t *jpeg_io = calloc(1, sizeof(jpeg_dec_io_t));
+    if (jpeg_io == NULL)
+    {
+        return ESP_FAIL;
+    }
+
+    // Create out_info handle
+    jpeg_dec_header_info_t *out_info = calloc(1, sizeof(jpeg_dec_header_info_t));
+    if (out_info == NULL)
+    {
+        return ESP_FAIL;
+    }
+    // Set input buffer and buffer len to io_callback
+    jpeg_io->inbuf = input_buf;
+    jpeg_io->inbuf_len = len;
+
+    // Parse jpeg picture header and get picture for user and decoder
+    ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
+    if (ret < 0)
+    {
+        goto _exit;
+    }
+
+    jpeg_io->outbuf = output_buf;
+    int inbuf_consumed = jpeg_io->inbuf_len - jpeg_io->inbuf_remain;
+    jpeg_io->inbuf = input_buf + inbuf_consumed;
+    jpeg_io->inbuf_len = jpeg_io->inbuf_remain;
+
+    // Start decode jpeg raw data
+    ret = jpeg_dec_process(jpeg_dec, jpeg_io);
+    if (ret < 0)
+    {
+        goto _exit;
+    }
+
+_exit:
+    // Decoder deinitialize
+    jpeg_dec_close(jpeg_dec);
+    free(out_info);
+    free(jpeg_io);
+    return ret;
+}
+
+
+static void _camera_display(uint8_t *lcd_buffer)
+{
+    bsp_display_lock(0);
+    // 使用 LVGL 显示解码后的 JPEG 数据
+    lv_canvas_set_buffer(camera_canvas, lcd_buffer, current_width, current_height, LV_IMG_CF_TRUE_COLOR);
+    lv_label_set_text_fmt(label, "#FF0000 %d*%d#", current_width, current_height);
+    bsp_display_unlock();
+}
+
+
+static void camera_frame_cb(uvc_frame_t *frame, void *ptr)
+{
+    ESP_LOGI(TAG, "uvc callback! frame_format = %d, seq = %" PRIu32 ", width = %" PRIu32 ", height = %" PRIu32 ", length = %u, ptr = %d",
+             frame->frame_format, frame->sequence, frame->width, frame->height, frame->data_bytes, (int)ptr);
+    if (current_width != frame->width || current_height != frame->height)
+    {
+        current_width = frame->width;
+        current_height = frame->height;
+        adaptive_jpg_frame_buffer(current_width * current_height * 2);
+    }
+
+    esp_jpeg_decoder_one_picture((uint8_t *)frame->data, frame->data_bytes, jpg_frame_buf); // 解码 JPEG
+    _camera_display(jpg_frame_buf); // 显示
+    vTaskDelay(pdMS_TO_TICKS(1));
+}

上面代码使用的 esp_jpeg 软件解码库 JPEG Decoder: TJpgDec -Tiny JPEG Decompressor

另一个 LCD tjpgd example:This example shows how to decode a jpeg image and display it on an SPI-interfaced LCD, and rotates the image periodically.

Copy

// https://github.com/espressif/esp-idf/blob/master/examples/peripherals/lcd/tjpgd/main/decode_image.c
+
+//Decode the embedded image into pixel lines that can be used with the rest of the logic.
+esp_err_t decode_image(uint16_t **pixels)
+{
+    *pixels = NULL;
+    esp_err_t ret = ESP_OK;
+
+    //Alocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines.
+    *pixels = calloc(IMAGE_H * IMAGE_W, sizeof(uint16_t));
+    ESP_GOTO_ON_FALSE((*pixels), ESP_ERR_NO_MEM, err, TAG, "Error allocating memory for lines");
+
+    //JPEG decode config
+    esp_jpeg_image_cfg_t jpeg_cfg = {
+        .indata = (uint8_t *)image_jpg_start,
+        .indata_size = image_jpg_end - image_jpg_start,
+        .outbuf = (uint8_t*)(*pixels),
+        .outbuf_size = IMAGE_W * IMAGE_H * sizeof(uint16_t),
+        .out_format = JPEG_IMAGE_FORMAT_RGB565,
+        .out_scale = JPEG_IMAGE_SCALE_0,
+        .flags = {
+            .swap_color_bytes = 1,
+        }
+    };
+
+    //JPEG decode
+    esp_jpeg_image_output_t outimg;
+    esp_jpeg_decode(&jpeg_cfg, &outimg);
+
+    ESP_LOGI(TAG, "JPEG image decoded! Size of the decoded image is: %dpx x %dpx", outimg.width, outimg.height);
+
+    return ret;
+err:
+    //Something went wrong! Exit cleanly, de-allocating everything we allocated.
+    if (*pixels != NULL) {
+        free(*pixels);
+    }
+    return ret;
+}

对于 JPEG 解码生成的可以 LCD 显示的 RGB888, RGB565 等格式的 raw picture, 可以使用 OpenCV 库来显示:

Copy

# https://github.com/espressif/esp-idf/blob/master/examples/peripherals/jpeg/jpeg_decode/open_raw_picture.py
+
+# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Unlicense OR CC0-1.0
+import argparse
+
+import cv2 as cv
+import numpy as np
+from numpy.typing import NDArray
+
+
+def open_picture(path):  # type: (str) -> list[int]
+    with open(path, 'rb') as f:
+        data = f.read()
+        f.close()
+    new_data = [int(x) for x in data]
+    return new_data
+
+
+def picture_show_rgb888(data, h, w):  # type: (list[int], int, int) -> None
+    data = np.array(data).reshape(h, w, 3).astype(np.uint8)
+    cv.imshow('data', data)
+    cv.waitKey()
+
+
+def picture_show_rgb565(data, h, w):  # type: (list[int], int, int) -> None
+
+    new_data = [0] * ((len(data) // 2) * 3)
+    for i in range(len(data)):
+        if i % 2 != 0:
+            new_data[3 * (i - 1) // 2 + 2] = (data[i] & 0xf8)
+            new_data[3 * (i - 1) // 2 + 1] |= (data[i] & 0x7) << 5
+        else:
+            new_data[3 * i // 2] = (data[i] & 0x1f) << 3
+            new_data[3 * i // 2 + 1] |= (data[i] & 0xe0) >> 3
+
+    new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_gray(data, h, w):  # type: (list[int], int, int) -> None
+    new_data = np.array(data).reshape(h, w, 1).astype(np.uint8)
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def convert_YUV_to_RGB(Y, U, V):  # type: (NDArray, NDArray, NDArray) -> tuple[NDArray, NDArray, NDArray]
+    B = np.clip(Y + 1.7790 * (U - 128), 0, 255).astype(np.uint8)
+    G = np.clip(Y - 0.3455 * (U - 128) - 0.7169 * (V - 128), 0, 255).astype(np.uint8)
+    R = np.clip(Y + 1.4075 * (V - 128), 0, 255).astype(np.uint8)
+
+    return B, G, R
+
+
+def picture_show_yuv420(data, h, w):  # type: (list[int], int, int) -> None
+    new_u = [0] * (h * w)
+    new_v = [0] * (h * w)
+    new_y = [0] * (h * w)
+
+    for i in range(int(h * w * 1.5)):
+        is_even_row = ((i // (w * 1.5)) % 2 == 0)
+        if is_even_row:
+            if (i % 3 == 0):
+                new_u[(i // 3) * 2] = data[i]
+                new_u[(i // 3) * 2 + 1] = data[i]
+        else:
+            if (i % 3 == 0):
+                new_u[(i // 3) * 2] = new_u[int((i - (w * 1.5)) // 3) * 2]
+                new_u[(i // 3) * 2 + 1] = new_u[int((i - (w * 1.5)) // 3) * 2 + 1]
+
+    for i in range(int(h * w * 1.5)):
+        if (i // (w * 1.5)) % 2 != 0 and (i % 3 == 0):
+            idx = (i // 3) * 2
+            new_v[idx] = data[i]
+            new_v[idx + 1] = data[i]
+
+    for i in range(int(h * w * 1.5)):
+        if (i // (w * 1.5)) % 2 == 0 and (i % 3 == 0):
+            idx = (i // 3) * 2
+            new_v[idx] = new_v[int((i + (w * 1.5)) // 3) * 2]
+            new_v[idx + 1] = new_v[int((i + (w * 1.5)) // 3) * 2 + 1]
+
+    new_y = [data[i] for i in range(int(h * w * 1.5)) if i % 3 != 0]
+
+    Y = np.array(new_y)
+    U = np.array(new_u)
+    V = np.array(new_v)
+
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+    new_data = np.array(new_data).reshape(h, w, 3).astype(np.uint8)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_yuv422(data, h, w):  # type: (list[int], int, int) -> None
+    # Reshape the input data to a 2D array
+    data_array = np.array(data).reshape(h, w * 2)
+
+    # Separate Y, U, and V channels
+    Y = data_array[:, 1::2]
+    U = data_array[:, 0::4].repeat(2, axis=1)
+    V = data_array[:, 2::4].repeat(2, axis=1)
+
+    # Convert YUV to RGB
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def picture_show_yuv444(data, h, w):  # type: (list[int], int, int) -> None
+    # Reshape the input data to a 2D array
+    data_array = np.array(data).reshape(h, w * 3)
+
+    # Separate Y, U, and V channels
+    Y = data_array[:, 2::3]
+    U = data_array[:, 1::3]
+    V = data_array[:, 0::3]
+
+    # Convert YUV to RGB
+    B, G, R = convert_YUV_to_RGB(Y, U, V)
+
+    # Merge channels
+    new_data = np.stack((B, G, R), axis=-1)
+
+    # Display the image
+    cv.imshow('data', new_data)
+    cv.waitKey()
+
+
+def main():  # type: () -> None
+    parser = argparse.ArgumentParser(description='which mode need to show')
+
+    parser.add_argument(
+        '--pic_path',
+        type=str,
+        help='What is the path of your picture',
+        required=True)
+
+    parser.add_argument(
+        '--pic_type',
+        type=str,
+        help='What type you want to show',
+        required=True,
+        choices=['rgb565', 'rgb888', 'gray', 'yuv422', 'yuv420', 'yuv444'])
+
+    parser.add_argument(
+        '--height',
+        type=int,
+        help='the picture height',
+        default=480)
+
+    parser.add_argument(
+        '--width',
+        type=int,
+        help='the picture width',
+        default=640)
+
+    args = parser.parse_args()
+
+    height = args.height
+    width = args.width
+
+    data = open_picture(args.pic_path)
+    if (args.pic_type == 'rgb565'):
+        picture_show_rgb565(data, height, width)
+    elif (args.pic_type == 'rgb888'):
+        picture_show_rgb888(data, height, width)
+    elif (args.pic_type == 'gray'):
+        picture_show_gray(data, height, width)
+    elif (args.pic_type == 'yuv420'):
+        picture_show_yuv420(data, height, width)
+    elif (args.pic_type == 'yuv422'):
+        picture_show_yuv422(data, height, width)
+    elif (args.pic_type == 'yuv444'):
+        picture_show_yuv444(data, height, width)
+    else:
+        print('This type is not supported in this script!')
+
+
+if __name__ == '__main__':
+    main()

除了软件解码外,esp32p4(目前只有该信号 MCU 支持) 也提供了 JPEG 的硬件编码和解码: https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/jpeg.html

  1. 硬件 jpeg decode (*.jpg -> *.rgb,如 RGB888, RGB565)
  2. 硬件 jpeg encode (*.rgb -> *.jpg)

对应的 ESP32 JPEG 硬件 codec engine driver: https://github.com/espressif/esp-idf/tree/master/components/esp_driver_jpeg

其他 jpeg encode:

  1. https://github.com/bitbank2/JPEGENC
  2. https://github.com/tobozo/ESP32-Raytracer/tree/master
  3. https://github.com/espressif/esp-adf-libs/blob/master/esp_codec/include/codec/esp_jpeg_enc.h
    • 没有提供 C 源代码,只提供了头文件和 lib 库;

5 显示 gif 图片 #

使用 Rust tinygif 库和系统定时器来显示 GIF 图片:

Copy

// https://github.com/MabezDev/mkey/blob/main/firmware/src/main.rs
+let image =
+        tinygif::Gif::<Rgb565>::from_slice(include_bytes!("../Ferris-240x240.gif")).unwrap();
+    let mut start = SystemTimer::now();
+    let mut frames = 0;
+    loop {
+        for frame in image.frames() {
+            let frame = Image::with_center(
+                &frame,
+                Point::new(WIDTH as i32 / 2, (HEIGHT as i32 / 2) - 40),
+            );
+            frame.draw(pixels).unwrap();
+
+            // TE_READY won't get set until we mark that we're ready to flush a buffer
+            critical_section::with(|cs| {
+                TE_READY.store(false, Ordering::SeqCst);
+                TE.borrow_ref_mut(cs).as_mut().unwrap().clear_interrupt();
+            });
+            // wait for next sync
+            while !TE_READY.load(Ordering::SeqCst) {}
+
+            let pixels = unsafe {
+                core::slice::from_raw_parts(
+                    pixels.data().as_ptr() as *const u8,
+                    pixels.data().len(),
+                )
+            };
+            let now = SystemTimer::now();
+            lcd_fill(&mut spi, pixels);
+            log::trace!(
+                "Time to fill display: {}ms",
+                (SystemTimer::now() - now) / (SystemTimer::TICKS_PER_SECOND / 1024)
+            );
+            frames += 1;
+            let now = SystemTimer::now();
+            if now.wrapping_sub(start) > SystemTimer::TICKS_PER_SECOND {
+                start = now;
+                log::info!("FPS: {}", frames);
+                frames = 0;
+            }
+        }
+    }

6 使用 LVGL 显示图片 #

https://docs.lvgl.io/8.2/widgets/core/img.html

Images are the basic object to display images from flash (as arrays) or from files. Images can display symbols (LV_SYMBOL_…) too.

Using the Image decoder interface custom image formats can be supported as well.

Image source:To provide maximum flexibility, the source of the image can be:

  1. a variable in code (a C array with the pixels).
  2. a file stored externally (e.g. on an SD card).
  3. a text with Symbols.

To set the source of an image, use lv_img_set_src(img, src).

To generate a pixel array from a PNG, JPG or BMP image, use the Online image converter tool and set the converted image with its pointer: lv_img_set_src(img1, &converted_img_var); To make the variable visible in the C file, you need to declare it with LV_IMG_DECLARE(converted_img_var).

To use external files, you also need to convert the image files using the online converter tool but now you should select the binary output format. You also need to use LVGL’s file system module and register a driver with some functions for the basic file operation. Go to the File system to learn more. To set an image sourced from a file, use lv_img_set_src(img, “S:folder1/my_img.bin”).

You can also set a symbol similarly to Labels. In this case, the image will be rendered as text according to the font specified in the style. It enables to use of light-weight monochrome “letters” instead of real images. You can set symbol like lv_img_set_src(img1, LV_SYMBOL_OK).

7 使用 embeded-graphics 显示图片 #

8 使用 slint 显示图片 #

使用 image crate 可以将各种照片转换称 RGBA8 格式:

Copy

let mut cat_image = image::open("cat.png").expect("Error loading cat image").into_rgba8();

Integration with OpenCV #2480: https://github.com/slint-ui/slint/discussions/2480

You would basically need to convert the pixel into SharedPixelBuffer and use the slint::Image constructor to convert it to an image. Then you can use a property to pass that image to the slint view.

Copy

export component View {
+   in property <image> img <=> i.source;
+   i := Image { }
+}

参考:

  1. https://github.com/opsnull/rust-slint-opencv
  2. https://releases.slint.dev/1.5.1/docs/rust/slint/struct.image

LCD 播放视频是通过连续播放静态图片来实现的,每张图片为一个 frame,播放图片的速率为 FPS。

影响 LCD FPS 快慢的因素:

  1. rendering:处理器生成一帧数据 image 数据的过程;
  2. transmission:处理其通过物理接口发送给 LCD 的速率,称为 interface frame rate;
  3. display:LCD 显示一帧图片的速率,称为 screen refresh rate;

interface 和 screen refresh rate 的关系:

  1. For LCDs with SPI/I80 interfaces, the screen refresh rate is determined by the LCD driver IC and can typically be set by sending specific commands, such as the ST7789 command FRCTRL2 (C6h).
  2. For LCDs with RGB interfaces, the screen refresh rate is determined by the main controller and is equivalent to the interface frame rate.

RGB 接口是微控制器 driver 控制的数据发送&传输;

rust-esp32 - 这篇文章属于一个选集。

§ 1: 安装 ESP32 C 开发框架 esp-idf

§ 2: 使用 C 和 esp-idf 开发 ESP32 项目

§ 3: 安装 ESP32 Rust 开发工具链 esp-rs

§ 4: 使用 Rust 开发 ESP32 应用

§ 5: 开发 Rust std 应用

§ 6: 开发 Rust no_std 应用

§ 7: 开发 Rust/C/C++ cmake 混合应用

§ 8: esp-rs 常见问题

§ 9: 配置 cargo workspace

§ 10: 使用 cargo run 和 espflash 烧写固件

§ 11: 分析 ESP32 固件和分区表

§ 12: 使用 USB-JTAG/probe-rs 调试应用

§ 13: Rust 驱动 LCD - 显示中英文

§ 14: 本文

§ 15: Rust 驱动 Touch - 触摸板

§ 16: Rust 驱动 Camera - 采集和播放

§ 17: Rust 驱动 Audio - 播放和录音

Rust 驱动 Audio - 播放和录音

2024-08-28·6323 字

Rust Esp32 Rust Esp32

Rust 驱动 Camera - 采集和播放

2024-08-28·7304 字

Rust Esp32 Rust Esp32

Rust 驱动 LCD - 显示中英文

2024-08-28·818 字

Rust Esp32 Rust Esp32

Rust 驱动 Touch - 触摸板

2024-08-28·1332 字

Rust Esp32 Rust Esp32

+ + + + \ No newline at end of file diff --git a/Rust13.html b/Rust13.html new file mode 100644 index 0000000..db327bb --- /dev/null +++ b/Rust13.html @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rust 驱动 Touch - 触摸板 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Rust 驱动 Touch - 触摸板 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

参考: https://docs.espressif.com/projects/espressif-esp-iot-solution/zh_CN/latest/input_device/touch_panel.html

在实际应用中,电阻触摸屏必须在使用前进行校准,而电容触摸屏则一般由控制芯片完成该工作,无需额外的校准步骤。驱动中已经集成了电阻触摸屏的校准算法,校准过程使用了三个点来校准,用一个点来验证,当最后验证的误差大于某个阈值将导致校准失败,然后自动重新进行校准,直到校准成功。

调用校准函数 calibration_run() 将会在屏幕上开始校准的过程,校准完成后,参数将 保存在 NVS 中用于下次启动,避免每次使用前的重复校准。

触摸屏按下:不论是电阻还是电容触摸屏,通常的触摸屏控制芯片会有一个用于 通知触摸事件的中断引脚 。但是驱动中没有使用该信号,一方面是因为对于有屏幕的应用需要尽量节省出 IO 给其他外设;另一方面是触摸控制器给出的该信号 不如程序通过寄存器数据判断的准确性高 。对于电阻触摸屏来说,判断按下的依据是 Z 方向的压力大于配置的阈值;对于电容触摸屏则是判断至少有一个触摸点存在。

触摸屏的旋转:触摸屏具有与显示屏一样的 8 个方向 ,定义在 touch_panel_dir_t 中。这里的旋转是通过软件换算来实现的,通常把二者的方向设置为相同。但这并不是一成不变的,例如:在使用电容触摸屏时,有可能触摸屏固有的方向与显示屏原始显示方向不一致,如果简单的将这两个方向设置为相同后,将无法正确的点击屏幕内容,这时需要根据实际情况调整。

触摸屏的分辨率设置也是很重要的,因为触摸屏旋转后的换算依赖于触摸屏的宽和高分辨率大小,设置不当将无法得到正确的旋转效果。

电阻触摸:需要校正;

电容触摸:不需要校正,支持多点触摸,而且有些支持固定速率的触摸按钮。

NS2009 电阻触摸:提供 press + position 功能; FT6X36: 电容触摸:只能提供 position 功能;

初始化:

Copy

// https://docs.espressif.com/projects/espressif-esp-iot-solution/zh_CN/latest/input_device/touch_panel.html#id5
+touch_panel_driver_t touch; // a touch panel driver
+
+i2c_config_t i2c_conf = {
+    .mode = I2C_MODE_MASTER,
+    .sda_io_num = 35,
+    .sda_pullup_en = GPIO_PULLUP_ENABLE,
+    .scl_io_num = 36,
+    .scl_pullup_en = GPIO_PULLUP_ENABLE,
+    .master.clk_speed = 100000,
+};
+i2c_bus_handle_t i2c_bus = i2c_bus_create(I2C_NUM_0, &i2c_conf);
+
+touch_panel_config_t touch_cfg = {
+    .interface_i2c = {
+        .i2c_bus = i2c_bus,
+        .clk_freq = 100000,
+        .i2c_addr = 0x38,
+    },
+    .interface_type = TOUCH_PANEL_IFACE_I2C,
+    .pin_num_int = -1,
+    .direction = TOUCH_DIR_LRTB,
+    .width = 800,
+    .height = 480,
+};
+
+/* Initialize touch panel controller FT5x06 */
+touch_panel_find_driver(TOUCH_PANEL_CONTROLLER_FT5X06, &touch);
+touch.init(&touch_cfg);
+
+/* start to run calibration */
+touch.calibration_run(&lcd, false);

获取触摸屏是否按下及其触点坐标:

Copy

touch_panel_points_t points;
+touch.read_point_data(&points);
+int32_t x = points.curx[0];
+int32_t y = points.cury[0];
+if(TOUCH_EVT_PRESS == points.event) {
+    ESP_LOGI(TAG, "Pressed, Touch point at (%d, %d)", x, y);
+}

ESP32 Flappy Bird:https://github.com/Makerfabs/Project_ESP32-Flappy-Bird/tree/master

  1. LCD 3.5 inch Amorphous-TFT-LCD (Thin Film Transistor Liquid Crystal Display) for mobile-phone or handy electrical equipments.
  2. NS2009 is A 4-wire resistive touch screen control circuit with I2C interface, which contains A 12-bit resolution A/D converter.
  3. The FT6X36 Series ICs are single-chip capacitive touch panel controller IC with a built-in 16 bit enhanced Micro-controller unit (MCU).

touch controller 一般是通过 I2C/SPI 接口来读取的:

NS2009: https://github.com/Makerfabs/Project_Touch-Screen-Camera/blob/master/example/touch_draw_v2/NS2009.cpp

Copy

#include "NS2009.h"
+
+//I2C receive
+void ns2009_recv(const uint8_t *send_buf, size_t send_buf_len, uint8_t *receive_buf,
+                 size_t receive_buf_len)
+{
+    Wire.beginTransmission(NS2009_ADDR);
+    Wire.write(send_buf, send_buf_len);
+    Wire.endTransmission();
+    Wire.requestFrom(NS2009_ADDR, receive_buf_len);
+    while (Wire.available())
+    {
+        *receive_buf++ = Wire.read();
+    }
+}
+
+//read 12bit data
+unsigned int ns2009_read(uint8_t cmd)
+{
+    uint8_t buf[2];
+    ns2009_recv(&cmd, 1, buf, 2);
+    return (buf[0] << 4) | (buf[1] >> 4);
+}
+
+//Press maybe not correct
+int ns2009_get_press()
+{
+    return ns2009_read(NS2009_LOW_POWER_READ_Z1);
+}
+
+int ns2009_pos(int pos[2])
+{
+    int press = ns2009_read(NS2009_LOW_POWER_READ_Z1);
+
+    int x, y = 0;
+
+    x = ns2009_read(NS2009_LOW_POWER_READ_X);
+    y = ns2009_read(NS2009_LOW_POWER_READ_Y);
+
+    pos[0] = x * SCREEN_X_PIXEL / 4096; //4096 = 2 ^ 12
+    pos[1] = y * SCREEN_Y_PIXEL / 4096;
+
+    //pos[0] = x;
+    //pos[1] = y;
+    return press;
+}

FT6236:

Copy

// https://github.com/Makerfabs/Makerfabs-ESP32-S3-Parallel-TFT-with-Touch/blob/main/example/touch_keyboard_v2/FT6236.cpp
+#include "FT6236.h"
+
+int readTouchReg(int reg)
+{
+    int data = 0;
+    Wire.beginTransmission(TOUCH_I2C_ADD);
+    Wire.write(reg);
+    Wire.endTransmission();
+    Wire.requestFrom(TOUCH_I2C_ADD, 1);
+    if (Wire.available())
+    {
+        data = Wire.read();
+    }
+    return data;
+}
+
+/*
+int getTouchPointX()
+{
+    int XL = 0;
+    int XH = 0;
+
+    XH = readTouchReg(TOUCH_REG_XH);
+    XL = readTouchReg(TOUCH_REG_XL);
+
+    return ((XH & 0x0F) << 8) | XL;
+}
+*/
+
+int getTouchPointX()
+{
+    int XL = 0;
+    int XH = 0;
+
+    XH = readTouchReg(TOUCH_REG_XH);
+    //Serial.println(XH >> 6,HEX);
+    if(XH >> 6 == 1)
+        return -1;
+    XL = readTouchReg(TOUCH_REG_XL);
+
+    return ((XH & 0x0F) << 8) | XL;
+}
+
+int getTouchPointY()
+{
+    int YL = 0;
+    int YH = 0;
+
+    YH = readTouchReg(TOUCH_REG_YH);
+    YL = readTouchReg(TOUCH_REG_YL);
+
+    return ((YH & 0x0F) << 8) | YL;
+}
+
+int ft6236_pos(int pos[2])
+{
+    int XL = 0;
+    int XH = 0;
+    int YL = 0;
+    int YH = 0;
+
+    XH = readTouchReg(TOUCH_REG_XH);
+    if(XH >> 6 == 1)
+    {
+        pos[0] = -1;
+        pos[1] = -1;
+        return 0;
+    }
+    XL = readTouchReg(TOUCH_REG_XL);
+    YH = readTouchReg(TOUCH_REG_YH);
+    YL = readTouchReg(TOUCH_REG_YL);
+
+    pos[0] = ((XH & 0x0F) << 8) | XL;
+    pos[1] = ((YH & 0x0F) << 8) | YL;
+    return 1;
+}

Makerfabs ESP32-S3 Parallel TFT with Touch: https://github.com/Makerfabs/Makerfabs-ESP32-S3-Parallel-TFT-with-Touch

ESP32 3.5" TFT Touch with Camera: https://wiki.makerfabs.com/ESP32_3.5_TFT_Touch_with_Camera.html

大量 LCD+Touch 的例子:https://github.com/Makerfabs/Project_Touch-Screen-Camera

+ + + + \ No newline at end of file diff --git a/Rust14.html b/Rust14.html new file mode 100644 index 0000000..1a75a6c --- /dev/null +++ b/Rust14.html @@ -0,0 +1,495 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rust 驱动 Camera - 采集和播放 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Rust 驱动 Camera - 采集和播放 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

esp32-camera 和 ov3660: https://shop.m5stack.com/products/esp32-psram-timer-camera-ov3660

OV3660(pdf datasheet) 是 3MP 像素的 CMOS 图像传感器,最高 30FPS 720P(2048*1536)的照片(OV2660 可以提供 2MP 像素)。

Camera 一般使用 RGB DVP 接口, 部分 LCD 也使用该 RGB 接口类型.

OV3660 引脚说明:

  1. VDDA, VDDD, VDDIO - 电源引脚,分别为模拟电源、数字电源和I/O电源。
  2. GND - 地(Ground),电源的负极。
  3. SCL, SDA - 用于I2C通信的串行时钟线(SCL)和串行数据线(SDA),用于传感器配置。
    • SIOC # SCCB input clock
    • SIOD # SCCB data
  4. VS, HS, PCLK - 视频同步信号(VS,垂直同步),水平同步信号(HS),像素时钟(PCLK)。
    • VS: video output vertical sync signal
    • HS: video output horizontal sync signal
    • PCLK: image output clock
  5. D0-D9 - 数字图像数据输出引脚,用于传输图像数据,具体位数根据传感器配置和模式可能有所不同。
  6. RESET_BAR - 复位引脚,通常用于硬件复位传感器。
  7. PWDN - 电源关闭(Power Down)控制,用于控制传感器的电源模式。
  8. XCLK - 外部时钟输入,提供给传感器的工作时钟,system input clock/scan clock input。对于 ESP32 是 10 MHz。

通过 I2C 等通信接口(也称为 SCCB,serial camera control bus),外部控制器可以读写 OV3660 的内部寄存器,以配置其工作模式、分辨率、曝光参数、增益等。

ATTR_HTML: :width 400 :align center

驱动程序通过发送 VSYNC 信号来从 OV3660 获取恒定的 frame rate 输出。

最大图片传输速率:maximum image transfer rate:

  1. 2048x1536: 15 fps (3MP 像素)
  2. 1080p: 20 fps
  3. 720p: 45 fps
  4. XGA (1024x768): 45 fps
  5. VGA (640x480): 60 fps
  6. QVGA (320x240): 120 fps

1 esp32-camera #

esp32-camera 项目是 ESP32 官方维护的摄像头驱动和 APIs 库:https://github.com/espressif/esp32-camera

主要原理:将摄像头像素输出设置为 JEPG 格式,一个 frame 为一个 JPEG 格式数据的 frame buffer,然后通过读取 frame buffer 来获得单张照片。通过 loop 获取 frame buffer 的数据来形成 video stream。

esp32-camera 摄像头驱动的配置参数:

Copy

// https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h#L115
+
+/**
+ * @brief Configuration structure for camera initialization
+ */
+typedef struct {
+    int pin_pwdn;                   /*!< GPIO pin for camera power down line */
+    int pin_reset;                  /*!< GPIO pin for camera reset line */
+    int pin_xclk;                   /*!< GPIO pin for camera XCLK line */
+    union {
+        int pin_sccb_sda;           /*!< GPIO pin for camera SDA line */
+        int pin_sscb_sda __attribute__((deprecated("please use pin_sccb_sda instead")));           /*!< GPIO pin for camera SDA line (legacy name) */
+    };
+    union {
+        int pin_sccb_scl;           /*!< GPIO pin for camera SCL line */
+        int pin_sscb_scl __attribute__((deprecated("please use pin_sccb_scl instead")));           /*!< GPIO pin for camera SCL line (legacy name) */
+    };
+    int pin_d7;                     /*!< GPIO pin for camera D7 line */
+    int pin_d6;                     /*!< GPIO pin for camera D6 line */
+    int pin_d5;                     /*!< GPIO pin for camera D5 line */
+    int pin_d4;                     /*!< GPIO pin for camera D4 line */
+    int pin_d3;                     /*!< GPIO pin for camera D3 line */
+    int pin_d2;                     /*!< GPIO pin for camera D2 line */
+    int pin_d1;                     /*!< GPIO pin for camera D1 line */
+    int pin_d0;                     /*!< GPIO pin for camera D0 line */
+    int pin_vsync;                  /*!< GPIO pin for camera VSYNC line */
+    int pin_href;                   /*!< GPIO pin for camera HREF line */
+    int pin_pclk;                   /*!< GPIO pin for camera PCLK line */
+
+    int xclk_freq_hz;               /*!< Frequency of XCLK signal, in Hz. EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode */
+
+    ledc_timer_t ledc_timer;        /*!< LEDC timer to be used for generating XCLK  */
+    ledc_channel_t ledc_channel;    /*!< LEDC channel to be used for generating XCLK  */
+
+    pixformat_t pixel_format;       /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG  */
+    framesize_t frame_size;         /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA  */
+
+    int jpeg_quality;               /*!< Quality of JPEG output. 0-63 lower means higher quality  */
+    size_t fb_count;                /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed)  */
+    camera_fb_location_t fb_location; /*!< The location where the frame buffer will be allocated */
+    camera_grab_mode_t grab_mode;   /*!< When buffers should be filled */
+#if CONFIG_CAMERA_CONVERTER_ENABLED
+    camera_conv_mode_t conv_mode;   /*!< RGB<->YUV Conversion mode */
+#endif
+
+    int sccb_i2c_port;              /*!< If pin_sccb_sda is -1, use the already configured I2C bus by number */
+} camera_config_t;
+
+
+/**
+ * @brief Camera frame buffer location
+ */
+typedef enum {
+    CAMERA_FB_IN_PSRAM,         /*!< Frame buffer is placed in external PSRAM */
+    CAMERA_FB_IN_DRAM           /*!< Frame buffer is placed in internal DRAM */
+} camera_fb_location_t;
+
+
+#if CONFIG_CAMERA_CONVERTER_ENABLED
+/**
+ * @brief Camera RGB\YUV conversion mode
+ */
+typedef enum {
+    CONV_DISABLE,
+    RGB565_TO_YUV422,
+
+    YUV422_TO_RGB565,
+    YUV422_TO_YUV420
+} camera_conv_mode_t;
+#endif

对于 RAW RGB 和 YUV 像素,一般使用 8-10 位并行数字接口(DVP)输出, 该接口和 RGB LCD 接口类型一致 ,所以 LCD 和 Camera 在 esp-hal 仓库中属于同一个 module:lcd_camera;

LCD 显示器一般使用 RGB 像素格式,而且是比较节省空间的 16-bit 的 RGB565 像素格式:

https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/lcd_cam/cam.rs LCD_CAM peripheral driver 支持 8/16 bit DVP 信号的 master 或 slave mode。在 master mode 时 LCD_CAM peripheral driver 为 camera 提供 master clock,反之在 slave mode 时,不提供。这可以通过 driver 的with_master_clock() 方法来设置。

Copy

// https://github.com/esp-rs/esp-hal/blob/main/esp-hal/src/lcd_cam/cam.rs
+
+//! ## Examples
+//!/Following code shows how to receive some bytes from an 8 bit DVP stream in
+//! master mode.
+//!
+//! ```no_run
+//! let mclk_pin = io.pins.gpio15;
+//! let vsync_pin = io.pins.gpio6;
+//! let href_pin = io.pins.gpio7;
+//! let pclk_pin = io.pins.gpio13;
+//! let data_pins = RxEightBits::new(
+//!     io.pins.gpio11,
+//!     io.pins.gpio9,
+//!     io.pins.gpio8,
+//!     io.pins.gpio10,
+//!     io.pins.gpio12,
+//!     io.pins.gpio18,
+//!     io.pins.gpio17,
+//!     io.pins.gpio16,
+//! );
+//!
+//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
+//! let mut camera = Camera::new(lcd_cam.cam, channel.rx, data_pins, 20u32.MHz(), &clocks)
+//!     .with_master_clock(mclk_pin) // Remove this for slave mode.
+//!     .with_ctrl_pins(vsync_pin, href_pin, pclk_pin);
+//! ```
+
+/// Generation of GDMA SUC EOF
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum EofMode {
+    /// Generate GDMA SUC EOF by data byte length
+    ByteLen,
+    /// Generate GDMA SUC EOF by the external enable signal
+    EnableSignal,
+}
+
+    /// Perform a DMA read.
+    ///
+    /// This will return a [RxDmaTransfer]
+    ///
+    /// The maximum amount of data is 32736 bytes when using [EofMode::ByteLen].
+    ///
+    /// It's only limited by the size of the DMA buffer when using
+    /// [EofMode::EnableSignal].

通过将 ESP32 设置为 camera 的 master 模式,可以由 esp32 发送 VSYNC 信号给摄像头来作为采集 frame buffer 开始的控制信号,当 VSYNC 采集结束时,通过中断的方式得到通知;

Copy

esp_err_t camera_capture(){
+    //acquire a frame
+    camera_fb_t * fb = esp_camera_fb_get();
+    if (!fb) {
+        ESP_LOGE(TAG, "Camera Capture Failed");
+        return ESP_FAIL;
+    }
+    //replace this with your own function
+    process_image(fb->width, fb->height, fb->format, fb->buf, fb->len);
+
+    //return the frame buffer back to the driver for reuse
+    esp_camera_fb_return(fb);
+    return ESP_OK;
+}

提供的 API 支持:

  1. 摄像头硬件设置(I2C 接口);
  2. JPEG HTTP Capture:提供一个 http handler,请求时获从 fb 获取一张 JPEG 图片并返回;
  3. JPEG HTTP Stream:提供一个 http handler,在 while true 循环中不断获取 fb 中 JPEG 图片,通过 multipart/x-mixed-replace 编码的方式返回给客户端。
    • 也称为 Motion JPEG主流浏览器,QuickTime,VLC 都支持 HTTP Stream 播放。
  4. BMP HTTP Capture:将捕获的 JPEG 转码为 BMP,再返回;

开发调试:My initial idea for this (which is the example I have in my project) was to dump the JPEG from the camera as HEX onto the console and use xxd -r -p uart.txt image.jpg to convert it to JPEG file. Somewhat tedious but it works haha.

将 esp-camera 驱动的摄像头进行 RTSP+RTP(Over UDP) 进行流式输出的例子:camera-streamer:Example for ESP32 TimerCam rebuilt using ESPP to stream video over the network. It uses RTSP + RTP (over UDP) to perform real-time streaming of the camera data over the network to multiple clients.

Copy

// https://github.com/esp-cpp/camera-streamer/blob/main/main/main.cpp
+
+// initialize camera
+/**
+ * @note display sizes supported:
+ * *  QVGA:  320x240
+ * *  WQVGA: 400x240
+ * *  HVGA:  480x320
+ * *  VGA:   640x480
+ * *  WVGA:  768x480
+ * *  FWVGA: 854x480
+ * *  SVGA:  800x600
+ * *  DVGA:  960x640
+ * *  WSVGA: 1024x600
+ * *  XGA:   1024x768
+ * *  WXGA:  1280x800
+ * *  WSXGA: 1440x900
+ * *  SXGA:  1280x1024
+ * *  UXGA:  1600x1200
+ */
+
+static camera_config_t camera_config = {
+	.pin_pwdn = -1,
+	.pin_reset = 15,
+	.pin_xclk = 27,
+	.pin_sccb_sda = 25,
+	.pin_sccb_scl = 23,
+
+	.pin_d7 = 19,
+	.pin_d6 = 36,
+	.pin_d5 = 18,
+	.pin_d4 = 39,
+	.pin_d3 = 5,
+	.pin_d2 = 34,
+	.pin_d1 = 35,
+	.pin_d0 = 32,
+	.pin_vsync = 22,
+	.pin_href = 26,
+	.pin_pclk = 21,
+
+	.xclk_freq_hz =	10000000, // EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode
+	.ledc_timer = LEDC_TIMER_0,
+	.ledc_channel = LEDC_CHANNEL_0,
+
+	.pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG
+	.frame_size = FRAMESIZE_QVGA,   // QVGA-UXGA, For ESP32, do not use sizes above QVGA when not
+	// JPEG. The performance of the ESP32-S series has improved a
+	// lot, but JPEG mode always gives better frame rates.
+
+	.jpeg_quality = 15, // 0-63, for OV series camera sensors, lower number means higher quality
+	.fb_count = 2, // When jpeg mode is used, if fb_count more than one, the driver will work in
+	// continuous mode.
+	.grab_mode =
+	CAMERA_GRAB_LATEST // CAMERA_GRAB_WHEN_EMPTY // . Sets when buffers should be filled
+};
+
+
+// create the camera and rtsp server, and the cv/m they'll use to communicate
+int server_port = CONFIG_RTSP_SERVER_PORT;
+logger.info("Creating RTSP server at {}:{}", server_address, server_port);
+espp::RtspServer rtsp_server({.server_address = server_address,
+		.port = server_port,
+		.path = "mjpeg/1",
+		.log_level = espp::Logger::Verbosity::WARN});
+rtsp_server.set_session_log_level(espp::Logger::Verbosity::WARN);
+rtsp_server.start();
+
+
+// initialize the camera
+logger.info("Creating camera task");
+auto camera_task_fn = [&rtsp_server, &logger](auto &m, auto &cv) -> bool {
+	// take image
+	static camera_fb_t *fb = NULL;
+	static size_t _jpg_buf_len;
+	static uint8_t *_jpg_buf;
+
+	fb = esp_camera_fb_get();  // 调用 esp camera 库的 esp_camera_fb_get() 来获得 JPEG 图片
+	if (!fb) {
+		logger.error("Camera capture failed");
+		return false;
+	}
+
+	_jpg_buf_len = fb->len;
+	_jpg_buf = fb->buf;
+
+	if (!(_jpg_buf[_jpg_buf_len - 1] != 0xd9 || _jpg_buf[_jpg_buf_len - 2] != 0xd9)) {
+		esp_camera_fb_return(fb);
+		return false;
+	}
+
+	espp::JpegFrame image(reinterpret_cast<const char *>(_jpg_buf), _jpg_buf_len);
+	rtsp_server.send_frame(image);  // 将 JPEG 图片发送给 rtsp_server 进行流式输出
+
+	esp_camera_fb_return(fb);
+	return false;
+};
+
+
+auto camera_task = espp::Task::make_unique({.name = "Camera Task", .callback = camera_task_fn, .priority = 10});
+camera_task->start(); // 启动 camera task

另一个支持 RTSP + HTTP JPEG Streamer + image Capture 的 强大的库https://github.com/rzeldent/esp32cam-rtsp/tree/main

可以使用 opencv 来显示 RTSP 内容:

Copy

# https://github.com/esp-cpp/camera-streamer/blob/main/opencv_rtsp_client.py
+import sys
+import cv2
+
+def stream(addr, port):
+    vcap = cv2.VideoCapture(f"rtsp://{addr}:{port}/mjpeg/1")
+    while(1):
+        ret, frame = vcap.read()
+        cv2.imshow('VIDEO', frame)
+        cv2.waitKey(1)
+
+if __name__ == "__main__":
+    if len(sys.argv) != 3:
+        print("Usage: python ./opencv_rtsp_client <address> <rtsp_port>")
+        sys.exit(1)
+    stream(sys.argv[1], sys.argv[2])

使用 camera-display 项目来从 RTSP+RTP 拉流显示 JPEG 图片:

  1. RTSP client that receives mjpeg frames split into RTP packets, turns them back into JPEG images, and pushes them into a queue.
  2. Display task, which pulls image data from the queue, decodes the jpeg, and displays it on the screen.

其他 Streaming JPEG 或任何文件的方式:

Copy

# 使用 Gstreamer 将 webcam 发送的串行 JPEG image 捕获为 mp4 视频输出:
+$ gst-launch-1.0 v4l2src ! jpegdec ! xvimagesink
+# Capture a single image and save it in JPEG format.
+$ gst-launch v4l2src num-buffers=1 ! jpegenc ! filesink location=/tmp/test.jpg
+# Stream video from a webcam.
+$ gst-launch v4l2src ! xvimagesink
+# if camera supports MJPG
+$ gst-launch-1.0 v4l2src num-buffers=1 ! image/jpeg,framerate=5/1,width=1280,height=960 ! jpegparse
+! filesink location=/tmp/test2.jpg
+# raw image
+$ gst-launch-1.0 v4l2src num-buffers=1 ! videoconvert ! 'video/x-raw,width=1280,height=960,format=RGBx' ! filesink location=image.raw

关于 Streaming 的解释: Playing media straight from the Internet without storing it locally is known as Streaming. We have been doing it throughout the tutorials whenever we used a URI starting with http://. This tutorial shows a couple of additional points to keep in mind when streaming. In particular:

information Embedding multiple streams inside a single file is called “multiplexing” or “muxing”, and such file is then known as a “container”. Common container formats are Matroska (.mkv), Quicktime (.qt, .mov, .mp4), Ogg (.ogg) or Webm (.webm).

Retrieving the individual streams from within the container is called “demultiplexing” or “demuxing”.

2 像素格式:YUV 和 RGB #

YUV 是一种颜色编码方法 。常使用在各个影像处理组件中。 YUV 在对照片或影片编码时,考虑到人类的感知能力,允许降低色度的带宽。

YUV是编译true-color颜色空间(color space)的种类, Y'UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV ,彼此有重叠。

Y′UV, YUV, YCbCr, YPbPr所指涉的范围,常有混淆或重叠的情况。从历史的演变来说,其中:

彩色图像记录的格式,常见的有 RGB、YUV、CMYK 等。彩色电视最早的构想是使用 RGB 三原色来同时传输。这种设计方式是原来黑白带宽的3倍,在当时并不是很好的设计。

RGB诉求于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度,Y代表的是亮度,UV代表的是彩度(因此黑白电影可省略UV,相近于RGB),分别用Cr和Cb来表示,因此YUV的记录通常以Y:UV的格式呈现。

为节省带宽起见,大多数YUV格式平均使用的每像素位数都少于24位。主要的抽样(subsample)格式有 YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1和YCbCr 4:4:4 。YUV的表示法称为A:B:C表示法:

最常用Y:UV记录的比重通常1:1或2:1,DVD-Video是以YUV 4:2:0的方式记录,也就是我们俗称的 I420,YUV4:2:0 并不是说只有U(即Cb), V(即Cr)一定为0,而是指U:V互相援引,时见时隐,也就是说对于每一个行,只有U或者V分量,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推。至于其他常见的YUV格式有 YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。

Y’代表明亮度(luma; brightness)而U与V存储色度(色讯; chrominance; color)部分; 亮度(luminance)记作Y,而 Y’的prime符号记作伽玛校正。

YUV Formats分成两个格式:

  1. 紧缩格式(packed formats):将Y、U、V值存储成Macro Pixels数组,和RGB的存放方式类似。
  2. 平面格式(planar formats):将Y、U、V的三个分量分别存放在不同的矩阵中。

紧缩格式(packed format)中的YUV是混合在一起的,对于YUV4:2:2格式而言,用紧缩格式很合适的,因此就有了 UYVY、YUYV等。平面格式(planar formats)是指每Y分量,U分量和V分量都是以独立的平面组织的,也就是说所有的U分量必须在Y分量后面,而V分量在所有的U分量后面,此一格式适用于采样(subsample)。平面格式(planar format)有I420(4:2:0)、YV12、IYUV等。

3 RTSP 协议:Real-Time Streaming Protocol #

https://antmedia.io/rtsp-explained-what-is-rtsp-how-it-works/

The Real-Time Streaming Protocol (RTSP) is an application-level network protocol designed for multiplexing and packetizing multimedia transport streams (such as interactive media, video and audio) over a suitable transport protocol. RTSP is used in entertainment and communications systems to control streaming media servers. The protocol is used for establishing and controlling media sessions between endpoints. Clients of media servers issue commands such as play, record and pause, to facilitate real-time control of the media streaming from the server to a client (video on demand) or from a client to the server (voice recording).

Like HTTP, RTSP uses TCP to maintain an end-to-end connection and, while most RTSP control messages are sent by the client to the server, some commands travel in the other direction (i.e. from server to client).

PLAY:

C->S: PLAY rtsp://example.com/media.mp4 RTSP/1.0 CSeq: 4 Range: npt=5-20 Session: 12345678

S->C: RTSP/1.0 200 OK CSeq: 4 Session: 12345678 RTP-Info: url=rtsp://example.com/media.mp4/streamid=0;seq=9810092;rtptime=3450012

PAUSE:

C->S: PAUSE rtsp://example.com/media.mp4 RTSP/1.0 CSeq: 5 Session: 12345678

S->C: RTSP/1.0 200 OK CSeq: 5 Session: 12345678

RTP:

The transmission of streaming data itself is not a task of RTSP. Most RTSP servers use the Real-time Transport Protocol (RTP) in conjunction with Real-time Control Protocol (RTCP) for media stream delivery . However, some vendors implement proprietary transport protocols. The RTSP server software from RealNetworks, for example, also used RealNetworks’ proprietary Real Data Transport (RDT).

RTP VS RTSP

RTSP is a realtime streaming protocol. Meaning, you can stream whatever you want in real time. So you can use it to stream LIVE content (no matter what it is, video, audio, text, presentation…). RTP is a transport protocol which is used to transport media data which is negotiated over RTSP.

You use RTSP to control media transmission over RTP. You use it to setup, play, pause, teardown the stream…

So, if you want your server to just start streaming when the URL is requested, you can implement some sort of RTP-only server. But if you want more control and if you are streaming live video, you must use RTSP, because it transmits SDP and other important decoding data.

总结:

  1. RTSP 提供了丰富的流媒体控制能力;
  2. 如果不需要控制,只是 stream,可以只实现 RTP server;

RTSP is widely used in IP camera, running as RTSP server in camera, so that user could play(pull) the RTSP stream from camera. It is a low cost solution, because we don’t need a central media server (think about thousands of camera streams). The arch is bellow:

IP Camera —-RTSP(pull)—> Player (RTSP server) (User Agent)

The RTSP protocol actually contains: A signaling over TCP, at port 554, used to exchange the SDP (also used in WebRTC), about media capabilities. UDP/TCP streams over serval ports, generally two ports, one for RTCP and one for RTP (also used in WebRTC).

Comparing to WebRTC, which is now available in H5:

A signaling over HTTP/WebSocket or exchange by any other protocols, used to exchange the SDP. UDP streams(RTP/RTCP) over one or many ports, generally bind to one port, to make cloud services load balancer happy.

In protocol view, RTSP and WebRTC are similar, but the use scenario is very different, because it’s off the topic, let’s grossly simplified, WebRTC is design for web conference, while RTSP is used for IP camera systems.

So it’s clear both RTSP and WebRTC are solution and protocol, used in different scenario. While RTP is transport protocol, also it can be used in live streaming by WebRTC.

服务端:

  1. Darwin Streaming Server: Open-sourced version of QuickTime Streaming Server maintained by Apple.
  2. GStreamer based RTSP Server and client.
  3. Many CCTV / Security cameras, often called IP cameras, support RTSP streaming too, especially those with ONVIF (the Open Network Video Interface Forum) profiles G, S, T.

使用 GStreamer 的 gst-launch-1.0 来做 RTP streaming (audio+video):

  1. 发送端:gst-launch-1.0 -v uridecodebin name=uridec uri=</C:\video.mp4> ! videoconvert ! x264enc noise-reduction=10000 tune=zerolatency byte-stream=true threads=4 key-int-max=15 intra-refresh=true ! mpegtsmux alignment=7 name=mux ! rtpmp2tpay ! queue ! udpsink host=127.0.0.1 port=5000 sync=true uridec. ! audioconvert ! voaacenc ! audio/mpeg ! queue ! mux.
  2. 接收端:
    1. gst-launch-1.0 -v playbin uri=udp://127.0.0.1:5000
    2. VLC:vlc rtp://@:5000, Open Network Stream (CTRL+N)

使用 GStraemer 的 gst-launch-1.0 将本地 jpg 图片转换为 RTP 流:

  1. gst-launch-1.0 multifilesrc location=“C:\\Pictures\\Photo.jpg” loop=true start-index=0 stop-index=0 ! image/jpeg,width=640,height=512,type=video,framerate=30/1 ! identity ! jpegdec ! videoscale !videoconvert ! x264enc ! h264parse ! mpegtsmux ! rtpmp2tpay ! udpsink host=127.0.0.1 port=5000

RTSP 是可以 streaming 任何数据的交互式控制协议。

关于RTSP_RTP_RTCP协议的深刻初步介绍: https://zhuanlan.zhihu.com/p/72917813

前记

作为一个软件工程师,特别是偏向安防应用或者互联网对接,都应该听说RTSP,RTP,RTCP等协议的概念。本篇博文详细介绍一下关于RTSP等协议,让读者更加方便的理解透彻。另外后续还会从RTSP的应用方面继续编写。三个协议简单描述

RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC标准。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或UDP完成数据传输。

Real-time Transport Protocol或简写RTP,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的。RTP协议详细说明了在互联网上传递音频和视频的标准数据包格式。它是创建在UDP协议上的。

Real-time Transport Control Protocol或RTP Control Protocol或简写RTCP)是实时传输协议(RTP)的一个姐妹协议。RTCP由RFC 3550定义(取代作废的RFC 1889)。RTP 使用一个 偶数 UDP port ;而RTCP 则使用 RTP 的下一个 port,也就是一个奇数 port。RTCP与RTP联合工作,RTP实施实际数据的传输,RTCP则负责将控制包送至电话中的每个人。其主要功能是就RTP正在提供的服务质量做出反馈。

简单的说,以上三个协议就是负责以下图片内容:

三个协议其实相辅相成,只是读完简单介绍其实并不能深刻理解其协议的深刻内涵或者使用方法以及其架构。下面我们对其协议进行详细拆分深刻挖掘。

参考:

4 Motion JPEG/webcam 和 HTTP Video Streaming #

Motion JPEG (M-JPEG or MJPEG) is a video compression format in which each video frame or interlaced field of a digital video sequence is compressed separately as a JPEG image.

Originally developed for multimedia PC applications, Motion JPEG enjoys broad client support: most major web browsers and players provide native support, and plug-ins are available for the rest. Software and devices using the M-JPEG standard include web browsers, media players, game consoles, digital cameras, IP cameras, webcams, streaming servers, video cameras, and non-linear video editors

M-JPEG is now used by video-capture devices such as digital cameras, IP cameras, and webcams, as well as by non-linear video editing systems. It is natively supported by the QuickTime Player, the PlayStation console, and web browsers such as Safari, Google Chrome, Mozilla Firefox and Microsoft Edge.

Video streaming

HTTP streaming separates each image into individual HTTP replies on a specified marker. HTTP streaming creates packets of a sequence of JPEG images that can be received by clients such as QuickTime or VLC. In response to a GET request for a MJPEG file or stream, the server streams the sequence of JPEG frames over HTTP .

A special mime-type content type multipart/x-mixed-replace;boundary=<boundary-name> informs the client to expect several parts (frames) as an answer delimited by . This boundary name is expressly disclosed within the MIME-type declaration itself. The TCP connection is not closed as long as the client wants to receive new frames and the server wants to provide new frames.

Two basic implementations of a M-JPEG streaming server are cambozola and MJPG-Streamer. The more robust ffmpeg-server also provides M-JPEG streaming support.

Native web browser support includes: Safari, Google Chrome, Microsoft Edge[8] and Firefox.[9] Other browsers, such as Internet Explorer can display M-JPEG streams with the help of external plugins. Cambozola is an applet that can show M-JPEG streams in Java-enabled browsers. M-JPEG is also natively supported by PlayStation and QuickTime. Most commonly, M-JPEG is used in IP based security cameras.[10]

Video4Linux: v4l2, v4l

Video4Linux (V4L for short) is a collection of device drivers and an API for supporting realtime video capture on Linux systems.[1] It supports many USB webcams, TV tuners, and related devices, standardizing their output, so programmers can easily add video support to their applications.

Video4Linux is responsible for creating V4L2 device nodes aka a device file (/dev/videoX, /dev/vbiX and /dev/radioX ) and tracking data from these nodes. The device node creation is handled by V4L device drivers using the video_device struct (v4l2-dev.h) and it can either be allocated dynamically or embedded in another larger struct.

Video4Linux was named after Video for Windows (which is sometimes abbreviated “V4W”), but is not technically related to it.[2][3]

+ + + + \ No newline at end of file diff --git a/Rust15.html b/Rust15.html new file mode 100644 index 0000000..a96720e --- /dev/null +++ b/Rust15.html @@ -0,0 +1,705 @@ + + + + + + + + + + + + + + + + + + + + + + + + Rust 驱动 Audio - 播放和录音 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Rust 驱动 Audio - 播放和录音 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

1 音频格式 #

PCM:脉冲编码调制(英语:Pulse-code modulation,缩写:PCM)是一种模拟信号的数字化方法。 A PCM stream has two basic properties that determine the stream’s fidelity to the original analog signal:

  1. the sampling rate, which is the number of times per second that samples are taken;
  2. and the bit depth, which determines the number of possible digital values that can be used to represent each sample.

The compact disc (CD) brought PCM to consumer audio applications with its introduction in 1982. The CD uses a 44,100 Hz sampling frequency and 16-bit resolution and stores up to 80 minutes of stereo audio per disc. stereo audio 是通过 two-channel 提供的。

LPCM 的解释:Linear pulse-code modulation (LPCM) 是一种数字信号的表示方法,主要用于音频信号。它通过将模拟信号定期采样并量化为线性级别的数字值来工作。LPCM是脉冲编码调制(PCM)的一种形式,特别强调了量化过程是线性的。这意味着模拟信号的每个采样值都直接转换成相应的数字值, 而这个转换过程不涉及任何非线性压缩 。LPCM的关键步骤包括采样、量化和编码:

  1. **采样**:这是将连续的模拟信号转换为离散信号的过程。根据奈奎斯特定理,为了避免混叠效应,采样频率应至少为信号最高频率的两倍。例如,CD音频以44.1kHz的频率采样,这意味着它可以准确地再现高达22.05kHz 的声音频率,覆盖了人耳可听范围。
  2. **量化**:量化过程涉及将每个采样点的振幅(即大小或强度)近似到一组有限的数值中。在LPCM中,这个过程是线性的,这意味着模拟信号的动态范围被均匀分配给量化级别。量化的精度通常用比特数表示,比如CD音质的LPCM采用16位量化,提供了65536(2^16)个不同的可能振幅级别。
  3. **编码**:最后,量化后的数值被编码为数字信号,可以存储或传输。在LPCM中,这些数值直接表示信号的振幅, 不进行任何额外的压缩或编码

LPCM 是一种无损的音频格式 ,因为它不涉及压缩过程中的信息丢失(尽管原始模拟信号在采样和量化过程中可能会有一定程度的近似)。由于它的这个特性, LPCM广泛用于需要高音质的应用中 ,如CD音频、DVD音频、蓝光音频和一些专业音频录制系统。

LPCM的主要优点包括简单、直接和高质量的音频表示, 但它也有一个缺点,即相对较高的数据率 。例如,未压缩的CD质量音频(使用44.1kHz的采样率和16位深度的立体声LPCM)的数据率约为1.4Mbps。相比之下, 许多现代音频压缩技术,如MP3或AAC ,通过去除人耳难以察觉的音频信息来大幅度减少所需的数据率,但这种压缩是有损的。

立体声和多声道 LPCM:

  1. 在立体声 LPCM 流中,左声道和右声道的采样值通常是 交错存储的 。例如,一个典型的存储序列可能是L1、 R1、L2、R2、…、Ln、Rn,其中L和R分别代表左声道和右声道的采样值,n是采样点的索引。
  2. 在多声道LPCM流中,各声道的采样值可以按不同方式组织。最常见的是交错方式,即按照采样时刻顺序依次存储各声道的采样值,比如L1、C1、R1、LS1、RS1、L2、C2、R2、LS2、RS2、…,其中L、C、R、LS、RS分别代表左前、中央、右前、左后和右后声道的采样值。

参考:https://planethifi.com/pcm-audio/

WAV(Waveform Audio File Format) 是一种音频文件格式,它通常用来 存储未压缩的音频数据 ,这些数据大多数情况下 使用Linear Pulse-Code Modulation (LPCM) 编码 。WAV格式由微软和IBM开发,最初是为Windows 3.1 操作系统设计的。由于其无损特性和广泛的兼容性,WAV格式成为了保存高质量音频的一种流行选择。

总结:WAV 文件和 LPCM 的关系:

总结: wav 文件不需要解码,可以直接读取 LPCM 编码数据 ,然后通过 I2S 接口发送给功放芯片播放。

// https://github.com/espressif/esp-box/blob/master/examples/watering_demo/main/app/app_audio.c
+
+static void audio_beep_task(void *pvParam)
+{
+    while (true) {
+        xSemaphoreTake(audio_sem, portMAX_DELAY);
+        b_audio_playing = true;
+        sr_echo_play("/spiffs/echo_en_wake.wav"); // 直接播放 wav 文件的音频数据
+        b_audio_playing = false;
+
+        /* It's useful if wake audio didn't finish playing when next wake word detetced */
+        // xSemaphoreTake(audio_sem, 0);
+    }
+}
+
+esp_err_t sr_echo_play(void *filepath)
+{
+    FILE *fp = NULL;
+    struct stat file_stat;
+    esp_err_t ret = ESP_OK;
+
+    const size_t chunk_size = 4096;
+    uint8_t *buffer = malloc(chunk_size);
+    ESP_GOTO_ON_FALSE(NULL != buffer, ESP_FAIL, EXIT, TAG, "buffer malloc failed");
+
+    ESP_GOTO_ON_FALSE(-1 != stat(filepath, &file_stat), ESP_FAIL, EXIT, TAG, "Failed to stat file");
+
+    fp = fopen(filepath, "r");
+    ESP_GOTO_ON_FALSE(NULL != fp, ESP_FAIL, EXIT, TAG, "Failed create record file");
+
+    wav_header_t wav_head;
+    int len = fread(&wav_head, 1, sizeof(wav_header_t), fp);
+    ESP_GOTO_ON_FALSE(len > 0, ESP_FAIL, EXIT, TAG, "Read wav header failed");
+
+    if (NULL == strstr((char *)wav_head.Subchunk1ID, "fmt") &&
+            NULL == strstr((char *)wav_head.Subchunk2ID, "data")) {
+        ESP_LOGI(TAG, "PCM format");
+        fseek(fp, 0, SEEK_SET);
+        wav_head.SampleRate = 16000;
+        wav_head.NumChannels = 2;
+        wav_head.BitsPerSample = 16;
+    }
+
+    ESP_LOGD(TAG, "frame_rate= %" PRIi32 ", ch=%d, width=%d", wav_head.SampleRate, wav_head.NumChannels, wav_head.BitsPerSample);
+    bsp_codec_set_fs(wav_head.SampleRate, wav_head.BitsPerSample, I2S_SLOT_MODE_STEREO);
+
+    bsp_codec_mute_set(true);
+    bsp_codec_mute_set(false);
+    bsp_codec_volume_set(100, NULL);
+
+    size_t cnt, total_cnt = 0;
+    do {
+        /* Read file in chunks into the scratch buffer */
+        len = fread(buffer, 1, chunk_size, fp);
+        if (len <= 0) {
+            break;
+        } else if (len > 0) {
+            bsp_i2s_write(buffer, len, &cnt, portMAX_DELAY);
+            total_cnt += cnt;
+        }
+    } while (1);
+    ESP_LOGI(TAG, "play end, %d K", total_cnt / 1024);
+
+EXIT:
+    if (fp) {
+        fclose(fp);
+    }
+    if (buffer) {
+        free(buffer);
+    }
+    return ret;
+}

There are three major groups of audio file formats :

  1. Uncompressed audio formats, such as WAV, AIFF, AU or raw header-less PCM; Note wav can also use compression as well.
  2. Formats with lossless compression, such as FLAC, Monkey's Audio (filename extension .ape), WavPack (filename extension .wv), TTA, ATRAC Advanced Lossless, ALAC (filename extension .m4a, Apple Lossless), MPEG-4 SLS, MPEG-4 ALS, MPEG-4 DST, Windows Media Audio Lossless (WMA Lossless), and Shorten (SHN).
    1. Formats with lossy compression, such as Opus, MP3, Vorbis, Musepack, AAC, ATRAC and Windows Media Audio Lossy (WMA lossy).

.m4a An audio-only MPEG-4 file, used by Apple for unprotected music downloaded from their iTunes Music Store. Audio within the m4a file is typically encoded with AAC, although lossless ALAC may also be used.

音频文件存储:

  1. WAV(Waveform Audio File Format):
  2. **普遍支持**:WAV是最广泛支持的音频文件格式之一,由微软开发,原生支持LPCM音频流。
  3. **无损质量**:WAV文件可以无损存储LPCM音频数据,保持原始音频质量。
  4. **元数据支持**:WAV格式支持存储关于音频流的详细信息,如采样率、位深度、声道数等。
  5. **文件大小**:由于WAV文件通常不使用压缩,文件大小可能会非常大,尤其是对于高采样率、高位深度、多声道音频。
  6. AIFF(Audio Interchange File Format)
  7. **类似WAV**:AIFF是苹果公司开发的一种音频文件格式,与WAV非常相似,提供无损音频质量和广泛的元数据支持。
  8. **跨平台**:虽然AIFF最初是为Macintosh系统设计的,但现在它在多个平台上都得到支持。
  9. **文件大小**:和WAV一样,AIFF文件也可能相当大,特别是当存储高质量的多声道LPCM音频时。
  10. FLAC(Free Lossless Audio Codec)
  11. **无损压缩**:FLAC提供无损压缩,能够在不损失音质的情况下减小文件大小,适用于LPCM音频数据。
  12. **标签和元数据**:FLAC支持丰富的标签和元数据,方便音乐管理和播放器识别。
  13. **广泛支持**:尽管主要用于立体声音频,FLAC格式也支持多达8个声道的音频,适用于多声道LPCM音频流的存储。
  14. Multichannel WAV/RF64
  15. **大型文件**:为了克服WAV文件对文件大小的限制(4GB),扩展格式如RF64被设计用来支持更大的文件,适合长时间的高质量多声道录音。
  16. **广泛兼容性**:这些格式保持了与标准WAV格式的向后兼容性,同时扩展了其能力,以支持更大的数据量。

存储过程:存储多声道LPCM音频流通常涉及以下步骤:

  1. **选择格式**:根据需要支持的声道数、对音质的要求以及对文件大小的考虑,选择合适的音频文件格式。
  2. **准备音频数据**:将LPCM编码的音频数据按照选择的格式要求(如声道排列、采样率、位深度等)进行组织。
  3. **写入文件**:将音频数据连同必要的元数据(如格式头信息)一起写入到文件中。
  4. **验证**:确保写入的音频文件符合所选格式的规范,并且可以被目标播放器或编辑软件正确读取。

使用适当的音频编辑或编码软件,你可以轻松地将多声道LPCM音频流保存到这些格式的文件中,无论是通过图形用户界面操作还是通过编程方式。

2 I2S 接口和播放声音 #

一般来说,一个语音提示文件的 MP3 格式的大小约 5KB,而未压缩的 wav 格式的大小则为 60KB 左右。如果拿 2MB FLASH 空间来存储 MP3 格式的语音提示文件,则其数量要远大于 WAV 格式。

而其他格式如 MP3, 需要通过软件或硬件解码为 PCM 格式 ,然后才能通过 I2S 数字音频接口发送给功放芯片。

  1. 使用I2C协议来配置WM8978模块
  2. 初始化ESP32I2S通信接口
  3. 建立数据缓冲,大于4096字节
  4. FLASH读取一个扇区(4096字节)
  5. 转为解码所需的stream比特流形式(如开源的 mad MP3 解码库 )
  6. 开始MP3解码
  7. 解码4096字节完成后,把 PCM 数据 通过I2S送入WM8978模块

综上:

  1. 使用 ESP32 播放 mp3 文件前,都需要解码,解码输出的格式为 PCM:
  2. 然后将解码后的 PCM 编码数据通过 I2S 接口发送给数字音频功放芯片(codec chip);
  3. 功放芯片进行 DAC 转换,驱动扬声器;
  4. 对于支持 MIC 输入的 codec chip,drvier 也通过 I2S 接口来读取 ADC 后的音频 PCM 数据,然后进一步处理,如 直接保存为未编码的 wav 格式文件 ,或经过压缩后编码为其他格式,如 mp3、aac 等来存储到 TF 卡,或者再发送给 codec chip 来播放;

注:I2S 接口是数字音频信号的传输协议(不一定是物理接口),而 PCM 是数字音频的编码格式,可以经过 DAC 直接转换为模拟信号。

大一统的 ESP32-audioI2S 解码播放示例:https://github.com/schreibfaul1/ESP32-audioI2S

// https://github.com/schreibfaul1/ESP32-audioI2S
+#include "Arduino.h"
+#include "WiFi.h"
+#include "Audio.h"
+#include "SD.h"
+#include "FS.h"
+
+// Digital I/O used
+#define SD_CS          5
+#define SPI_MOSI      23
+#define SPI_MISO      19
+#define SPI_SCK       18
+#define I2S_DOUT      25
+#define I2S_BCLK      27
+#define I2S_LRC       26
+
+Audio audio;
+
+String ssid =     "*******";
+String password = "*******";
+
+void setup() {
+    pinMode(SD_CS, OUTPUT);      digitalWrite(SD_CS, HIGH);
+    SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+    Serial.begin(115200);
+    SD.begin(SD_CS);
+    WiFi.disconnect();
+    WiFi.mode(WIFI_STA);
+    WiFi.begin(ssid.c_str(), password.c_str());
+    while (WiFi.status() != WL_CONNECTED) delay(1500);
+    audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+    audio.setVolume(21); // default 0...21
+//  or alternative
+//  audio.setVolumeSteps(64); // max 255
+//  audio.setVolume(63);
+//
+//  *** radio streams ***
+    audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/"); // aac
+//  audio.connecttohost("http://mcrscast.mcr.iol.pt/cidadefm");                                         // mp3
+//  audio.connecttohost("http://www.wdr.de/wdrlive/media/einslive.m3u");                                // m3u
+//  audio.connecttohost("https://stream.srg-ssr.ch/rsp/aacp_48.asx");                                   // asx
+//  audio.connecttohost("http://tuner.classical102.com/listen.pls");                                    // pls
+//  audio.connecttohost("http://stream.radioparadise.com/flac");                                        // flac
+//  audio.connecttohost("http://stream.sing-sing-bis.org:8000/singsingFlac");                           // flac (ogg)
+//  audio.connecttohost("http://s1.knixx.fm:5347/dein_webradio_vbr.opus");                              // opus (ogg)
+//  audio.connecttohost("http://stream2.dancewave.online:8080/dance.ogg");                              // vorbis (ogg)
+//  audio.connecttohost("http://26373.live.streamtheworld.com:3690/XHQQ_FMAAC/HLSTS/playlist.m3u8");    // HLS
+//  audio.connecttohost("http://eldoradolive02.akamaized.net/hls/live/2043453/eldorado/master.m3u8");   // HLS (ts)
+//  *** web files ***
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/Pink-Panther.wav");        // wav
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/Santiano-Wellerman.flac"); // flac
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/Olsen-Banden.mp3");        // mp3
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/Miss-Marple.m4a");         // m4a (aac)
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/Collide.ogg");             // vorbis
+//  audio.connecttohost("https://github.com/schreibfaul1/ESP32-audioI2S/raw/master/additional_info/Testfiles/sample.opus");             // opus
+//  *** local files ***
+//  audio.connecttoFS(SD, "/test.wav");     // SD
+//  audio.connecttoFS(SD_MMC, "/test.wav"); // SD_MMC
+//  audio.connecttoFS(SPIFFS, "/test.wav"); // SPIFFS
+
+//  audio.connecttospeech("Wenn die Hunde schlafen, kann der Wolf gut Schafe stehlen.", "de"); // Google TTS
+}
+
+void loop()
+{
+    audio.loop();
+}
+
+// optional
+void audio_info(const char *info){
+    Serial.print("info        "); Serial.println(info);
+}
+void audio_id3data(const char *info){  //id3 metadata
+    Serial.print("id3data     ");Serial.println(info);
+}
+void audio_eof_mp3(const char *info){  //end of file
+    Serial.print("eof_mp3     ");Serial.println(info);
+}
+void audio_showstation(const char *info){
+    Serial.print("station     ");Serial.println(info);
+}
+void audio_showstreamtitle(const char *info){
+    Serial.print("streamtitle ");Serial.println(info);
+}
+void audio_bitrate(const char *info){
+    Serial.print("bitrate     ");Serial.println(info);
+}
+void audio_commercial(const char *info){  //duration in sec
+    Serial.print("commercial  ");Serial.println(info);
+}
+void audio_icyurl(const char *info){  //homepage
+    Serial.print("icyurl      ");Serial.println(info);
+}
+void audio_lasthost(const char *info){  //stream URL played
+    Serial.print("lasthost    ");Serial.println(info);
+}
+void audio_eof_speech(const char *info){
+    Serial.print("eof_speech  ");Serial.println(info);
+}

对于数字音频功放芯片,一般也称为 codec chip

  1. PCM 数字音频解码,然后 DAC 转换为模型信号输出;
  2. MIC 收到的模拟声音信号经过 ADC 转换,然后编码为 PCM 数字比特流;
  3. driver 都是通过 I2S 接口来发送和接受 PCM 数字信号;

Wm8960 is a low power, high quality stereo CODEC, that provides two interface types: voice input and output. The communication between ESP32 and WM8960 is I2S.

一般 I2S 接口的数字音频功放芯片 codec chip,除了可以播放 PCM 编码格式的数字音频信号外,还提供控制(静音、音量大小等)和 MIC 输入功能,如 ES8374

示例:https://github.com/espressif/esp-box/blob/master/examples/usb_headset/main/src/usb_headset.c

如果需要更好的音频质量和更多的接口选项,可使用外部 I2S 编解码器来完成所有模拟输入和输出信号的处理。不同类型的编解码器芯片可提供不同的额外功能,如音频输入信号前置放大器、耳机输出放大器、多个模拟输入和输出、音效处理等。I2S 是音频编解码器芯片接口的行业标准,通常用于高速、连续传输音频数据。为了优化音频数据处理的性能,可能需要额外的内存。对于这种情况,请考虑使用集成 8 MB PSRAM 和 ESP32 芯片的 ESP32-WROVER-E 模组。

https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/project-design.html

ESP32 提供了乐鑫音频开发框架(ADF),支持常见的编解码格式: https://docs.espressif.com/projects/esp-adf/en/latest/index.html

I (397) PLAY_FLASH_MP3_CONTROL: [ 1 ] Start audio codec chip
+I (427) PLAY_FLASH_MP3_CONTROL: [ 2 ] Create audio pipeline, add all elements to pipeline, and subscribe pipeline event
+I (427) PLAY_FLASH_MP3_CONTROL: [2.1] Create mp3 decoder to decode mp3 file and set custom read callback
+I (437) PLAY_FLASH_MP3_CONTROL: [2.2] Create i2s stream to write data to codec chip
+I (467) PLAY_FLASH_MP3_CONTROL: [2.3] Register all elements to audio pipeline
+I (467) PLAY_FLASH_MP3_CONTROL: [2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]
+I (477) PLAY_FLASH_MP3_CONTROL: [ 3 ] Set up  event listener
+I (477) PLAY_FLASH_MP3_CONTROL: [3.1] Listening event from all elements of pipeline
+I (487) PLAY_FLASH_MP3_CONTROL: [ 4 ] Start audio_pipeline
+I (507) PLAY_FLASH_MP3_CONTROL: [ * ] Receive music info from mp3 decoder, sample_rates=44100, bits=16, ch=2
+I (7277) PLAY_FLASH_MP3_CONTROL: [ 5 ] Stop audio_pipeline

示例:https://github.com/espressif/esp-adf/tree/master/examples

3 记录声音 #

使用麦克风 Module INMP441 module 来将声音转换为数字信号(PCM 编码后的数字流),然后 ESP32 driver 通过 I2S 接口来获取数字音频。

如果是模拟 MIC 则可以使用 ESP32 的 ADC 引脚转换为 LPCM,然后再保存到 wav 文件中。

通过 I2S MIC 读取 PCM 数字音频后,以 wav 文件格式存入 SD 卡:

// https://www.makerfabs.com/blog/post/how-to-make-an-esp32-sound-recorder
+
+void WM8960_Record(String filename, char *buff, int record_time)
+{
+    int headerSize = 44;
+    byte header[headerSize];
+    int waveDataSize = record_time * 16000 * 16 * 2 / 8;
+    int recode_time = millis();
+    int part_time = recode_time;
+
+    File file = SD.open(filename, FILE_WRITE);
+    if (!file)
+        return;
+
+    Serial.println("Begin to record:");
+
+    for (int j = 0; j < waveDataSize / sizeof(buff); ++j)
+    {
+        I2S_Read(buff, sizeof(buff));
+        file.write((const byte *)buff, sizeof(buff));
+        if ((millis() - part_time) > 1000)
+        {
+            Serial.print(".");
+            part_time = millis();
+        }
+    }
+
+    file.seek(0);
+    CreateWavHeader(header, waveDataSize);
+    file.write(header, headerSize);
+
+    Serial.println("");
+    Serial.println("Finish");
+    Serial.println(millis() - recode_time);
+    file.close();
+}

播放 wav 文件:

// https://www.makerfabs.com/blog/post/how-to-make-an-esp32-sound-recorder
+
+void WM8960_Play (String filename, char *buff)
+{
+    File file = SD.open(filename);
+    if (! file)
+        return;
+    Serial.println("Begin to play:");
+    Serial.println(filename);
+    file.seek(44);  // 跳过 wav header
+    while (file.readBytes(buff, sizeof(buff)))
+    {
+        I2S_Write(buff, sizeof(buff));
+    }
+    Serial.println("Finish");
+    file.close();
+}

另一个使用 I2S MIC 读取数据,存入 wav 文件的例子: https://github.com/MhageGH/esp32_SoundRecorder/tree/master

#include "Arduino.h"
+#include <FS.h>
+#include "Wav.h"
+#include "I2S.h"
+#include <SD.h>
+
+
+//comment the first line and uncomment the second if you use MAX9814
+//#define I2S_MODE I2S_MODE_RX
+#define I2S_MODE I2S_MODE_ADC_BUILT_IN
+
+const int record_time = 10;  // second
+const char filename[] = "/sound.wav";
+
+const int headerSize = 44;
+const int waveDataSize = record_time * 88000;
+const int numCommunicationData = 8000;
+const int numPartWavData = numCommunicationData/4;
+byte header[headerSize];
+char communicationData[numCommunicationData];
+char partWavData[numPartWavData];
+File file;
+
+void setup() {
+  Serial.begin(115200);
+  if (!SD.begin()) Serial.println("SD begin failed");
+  while(!SD.begin()){
+    Serial.print(".");
+    delay(500);
+  }
+  CreateWavHeader(header, waveDataSize);
+  SD.remove(filename);
+  file = SD.open(filename, FILE_WRITE);
+  if (!file) return;
+  file.write(header, headerSize);
+  I2S_Init(I2S_MODE, I2S_BITS_PER_SAMPLE_32BIT);
+  for (int j = 0; j < waveDataSize/numPartWavData; ++j) {
+    I2S_Read(communicationData, numCommunicationData);
+    for (int i = 0; i < numCommunicationData/8; ++i) {
+      partWavData[2*i] = communicationData[8*i + 2];
+      partWavData[2*i + 1] = communicationData[8*i + 3];
+    }
+    file.write((const byte*)partWavData, numPartWavData);
+  }
+  file.close();
+  Serial.println("finish");
+}
+
+void loop() {
+}
+
+
+// wav 头文件
+#include "Wav.h"
+
+void CreateWavHeader(byte* header, int waveDataSize){
+  header[0] = 'R';
+  header[1] = 'I';
+  header[2] = 'F';
+  header[3] = 'F';
+  unsigned int fileSizeMinus8 = waveDataSize + 44 - 8;
+  header[4] = (byte)(fileSizeMinus8 & 0xFF);
+  header[5] = (byte)((fileSizeMinus8 >> 8) & 0xFF);
+  header[6] = (byte)((fileSizeMinus8 >> 16) & 0xFF);
+  header[7] = (byte)((fileSizeMinus8 >> 24) & 0xFF);
+  header[8] = 'W';
+  header[9] = 'A';
+  header[10] = 'V';
+  header[11] = 'E';
+  header[12] = 'f';
+  header[13] = 'm';
+  header[14] = 't';
+  header[15] = ' ';
+  header[16] = 0x10;  // linear PCM
+  header[17] = 0x00;
+  header[18] = 0x00;
+  header[19] = 0x00;
+  header[20] = 0x01;  // linear PCM
+  header[21] = 0x00;
+  header[22] = 0x01;  // monoral
+  header[23] = 0x00;
+  header[24] = 0x44;  // sampling rate 44100
+  header[25] = 0xAC;
+  header[26] = 0x00;
+  header[27] = 0x00;
+  header[28] = 0x88;  // Byte/sec = 44100x2x1 = 88200
+  header[29] = 0x58;
+  header[30] = 0x01;
+  header[31] = 0x00;
+  header[32] = 0x02;  // 16bit monoral
+  header[33] = 0x00;
+  header[34] = 0x10;  // 16bit
+  header[35] = 0x00;
+  header[36] = 'd';
+  header[37] = 'a';
+  header[38] = 't';
+  header[39] = 'a';
+  header[40] = (byte)(waveDataSize & 0xFF);
+  header[41] = (byte)((waveDataSize >> 8) & 0xFF);
+  header[42] = (byte)((waveDataSize >> 16) & 0xFF);
+  header[43] = (byte)((waveDataSize >> 24) & 0xFF);
+}

除了 I2S 接口的数字 MIC 外,常见的还有 模拟输出的 MIC ,这时可以使用 ESP32 的 ADC 引脚来进行模数转换 ,将结果以 LPCM 编码的 wav 文件保存:

// https://github.com/AlirezaSalehy/WAVRecorder/blob/main/library/library.ino
+#include <SD.h>
+#include <SPI.h>
+
+#include "src/WAVRecorder.h"
+#include "src/AudioSystem.h"
+#include "src/SoundActivityDetector.h"
+
+#define SAMPLE_RATE 16000
+#define SAMPLE_LEN 8
+
+// Hardware SPI's CS pin which is different in each board
+#ifdef ESP8266
+  #define CS_PIN 16
+#elif ARDUINO_SAM_DUE
+  #define CS_PIN 4
+#elif ESP32
+  #define CS_PIN 5
+#endif
+
+// The analog pins (ADC inputs) which microphone outputs are connected to.
+#define MIC_PIN_1 34
+#define MIC_PIN_2 35
+
+#define NUM_CHANNELS 1
+channel_t channels[] = {{MIC_PIN_1}};
+
+char file_name[] = "/sample.wav";
+File dataFile;
+
+#if defined(ESP32) || defined(ESP8266)
+  AudioSystem* as;
+#endif
+WAVRecorder* wr;
+//SoundActivityDetector* sadet;
+
+void recordAndPlayBack();
+
+void setup() {
+  for (int i = 0; i < sizeof(channels)/sizeof(channel_t); i++)
+    pinMode(channels[i].ADCPin, INPUT);
+  //analogReadResolution(12); for ESP32
+
+  pinMode(LED_BUILTIN, OUTPUT);
+  Serial.begin(115200);
+  Serial.println();
+
+  // put your setup code here, to run once:
+  if (!SD.begin(CS_PIN)) {
+    Serial.println("Failes to initialize SD!");
+  }
+  else {
+    Serial.println("SD opened successfuly");
+  }
+  SPI.setClockDivider(SPI_CLOCK_DIV2); // This is becuase feeding SD Card with more than 40 Mhz, leads to unstable operation.
+                                       // (Also depends on SD class) ESP8266 & ESP32 SPI clock with no division is 80 Mhz.
+
+  #if defined(ESP32) || defined(ESP8266)
+     as = new AudioSystem(CS_PIN);
+  #endif
+  //sadet = new SoundActivityDetector(channels[0].ADCPin, 2000, 10 * 512, 6 * 512, &Serial);
+  wr = new WAVRecorder(12, channels, NUM_CHANNELS, SAMPLE_RATE, SAMPLE_LEN, &Serial);
+
+}
+
+void loop() {
+  // put your main code here, to run repeatedly:
+  recordAndPlayBack();
+}
+
+void recordAndPlayBack() {
+    if (SD.exists(file_name)) {
+      SD.remove(file_name);
+      Serial.println("File removed!");
+    }
+
+    dataFile = SD.open(file_name, FILE_WRITE);
+    if (!dataFile) {
+      Serial.println("Failed to open the file!");
+      return;
+    }
+
+    // Setting file to store recodring
+    wr->setFile(&dataFile);
+
+    Serial.println("Started");
+    // With checks Sound power level and it exceeds a threshold recording starts and stops recording when power fall behind another threshold.
+    //wr->startBlocking(sadet);
+
+    // Recording for 3000 ms
+    wr->startBlocking(3000);
+    Serial.println("File Created");
+
+    Serial.println("Playing file");
+
+    #if defined(ESP32) || defined(ESP8266)
+        as->playAudioBlocking(file_name);
+    #endif
+}

另一个例子:Broadcasting Your Voice with ESP32-S3 & INMP441

The ESP32-S3’s I2S interface is set up to handle the audio data using Direct Memory Access (DMA) buffers. DMA allows for efficient data transfer without involving the main processor, offloading the task to a dedicated DMA controller. By configuring the DMA buffer in I2S, the captured audio samples can be stored and transmitted seamlessly.

ESP32-S3_INMP441_WebSocket_Client.ino

void i2s_install() {
+  // Set up I2S Processor configuration
+  const i2s_config_t i2s_config = {
+    .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
+    .sample_rate = 44100,
+    //.sample_rate = 16000,
+    .bits_per_sample = i2s_bits_per_sample_t(16),
+    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
+    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
+    .intr_alloc_flags = 0,
+    .dma_buf_count = bufferCnt,
+    .dma_buf_len = bufferLen,
+    .use_apll = false
+  };
+
+  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
+}
+
+
+void micTask(void* parameter) {
+
+  i2s_install();
+  i2s_setpin();
+  i2s_start(I2S_PORT);
+
+  size_t bytesIn = 0;
+  while (1) {
+    esp_err_t result = i2s_read(I2S_PORT, &sBuffer, bufferLen, &bytesIn, portMAX_DELAY);
+    if (result == ESP_OK && isWebSocketConnected) {
+      client.sendBinary((const char*)sBuffer, bytesIn);
+    }
+  }
+}

参考:https://diyi0t.com/i2s-sound-tutorial-for-esp32/

+ + + + \ No newline at end of file diff --git a/Rust2.html b/Rust2.html new file mode 100644 index 0000000..bc8c30d --- /dev/null +++ b/Rust2.html @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + 使用 Rust 开发 ESP32 应用 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 使用 Rust 开发 ESP32 应用 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+ +
+

ESP32 的 Rust 应用分为两种类型:

  1. 使用 std 库:可以使用 Rust 标准库的各种类型和特性,如 Vec/HashMap/Box,heap、thread/Mutex 等;
  2. 使用 core 库(non_std ): bare metal 开发;

    Github 的 esp-rs 组织中的各库命名管理:

  3. esp- 开头:是 non_std 类型,如 esp-hal;
  4. esp-idf- 开头:是 std 类型,如 esp-idf-hal;

对于 std 类型应用,cargo build 时会下载和编译依赖的 esp-idf 库。

推荐使用 cargo generate template 来创建 std/non_std 应用:

  1. std 应用:
    • cargo generate esp-rs/esp-idf-template cargo: 纯 Rust 应用(cargo-frst);
    • cargo generate esp-rs/esp-idf-template cmake: 混合 Rust and C/C++ in a traditional ESP-IDF idf.py;

  2. non_std 应用:
    • cargo generate esp-rs/esp-template

注意:

  1. 除了构建 cmake 应用外,构建纯 Rust 的 std 或 non_std 应用都只需 source ~/esp/export-esp.sh 脚本即可, 不能同时 source ~/esp/esp-idf/v5.2.1/export.sh, 否则会构建失败。
  2. 开发 Rust ESP32 应用时,需要先确定是使用 std 还是 non_std 类型,然后选择对应的 crate 库,而不是混合使用两种类型的库。
  3. std 应用可以使用 non_std 的库,但是反过来 non_std 应用只应该使用 non_std 的库

参考:The Embedded Rust ESP Development Ecosystem

+ + + + \ No newline at end of file diff --git a/Rust3.html b/Rust3.html new file mode 100644 index 0000000..9faf489 --- /dev/null +++ b/Rust3.html @@ -0,0 +1,741 @@ + + + + + + + + + + + + + + + + + + + + + + + + 开发 Rust std 应用 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 开发 Rust std 应用 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

对于 std 应用, 核心是使用 esp-idf-sys 和它绑定链接到的 C/C++ 库 esp-idf。

esp-idf 是 C/C++ 开发的,运行 FreeRTOS 操作系统,为 Rust std 提供了 newlib enviroment 实现(~/.rustup/toolchains/esp 目录),

这样 std 应用可以使用 Rust 标准库的各种类型和特性,如 Vec/HashMap/Box,net,heap 内存分配、thread/Mutex 等;

Rust std 应用与 esp-idf 之间的 3 种互操作方式(这些 std 库惯例是 esp-idf- 开头):

  1. esp-idf-sys crate:esp-idf 的 unsafe binding,Gives raw (unsafe) access to drivers, Wi-Fi and more.
  2. esp-idf-svc crate:esp-idf 的 safe binding,抽象层次更高,实现了 embedded-svc trait。
  3. esp-idf-hal crate:实现了 embedded-hal trait,支持 async,底层也是基于 esp-idf;

它们之间的层次关系: esp-idf-svc -> esp-idf-hal -> esp-idf-sys(esp-idf 的 Rust binding).

embedded-hal 和 embedded-svc 是 Rust embedded workgroup 定义的厂商中立的嵌入式规范.

编译 esp-idf-sys 时, build.rs 会会自动下载/安装/配置/编译和链接 esp-idf 库,

安装到 $ESP_IDF_TOOLS_INSTALL_DIR 位置,默认为 by 项目的 .embuild/espressif 目录。

esp-idf-sys 默认启用 esp-idf 所有的 Component(静态库的形式) ,这样可以后续直接链接他们(后续也可以通过 esp_idf_components, $ESP_IDF_COMPONENTS 来配置)。

esp-idf-sys 也支持添加本地和远程的 C/C++ Component,在构建 esp-idf-sys 时自动使用 bindgen 将它封装为Rust 接口,后续自动链接到可执行程序中。(参考后文)。

如果 esp-idf-hal 不满足需求(如缺少一些 esp32 的寄存器的操作),可以使用 esp-rs/esp-pacs 下的esp32s3 create, 它是使用 svd2rust 工具来基于芯片的 svd 自动生成的库。

https://apollolabsblog.hashnode.dev/the-embedded-rust-esp-development-ecosystem

使用 esp-rs/esp-idf-template 模板来创建 std 应用:

zj@a:~/codes/esp32/$ cargo generate esp-rs/esp-idf-template cargo
+⚠️   Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
+🔧   project-name: myesp ...
+🔧   Generating template ...
+✔ 🤷   Which MCU to target? · esp32s3
+✔ 🤷   Configure advanced template options? · true
+✔ 🤷   Enable STD support? · true
+✔ 🤷   Configure project to use Dev Containers (VS Code and GitHub Codespaces)? · false
+✔ 🤷   Configure project to support Wokwi simulation with Wokwi VS Code extension? · false
+✔ 🤷   Add CI files for GitHub Action? · false
+✔ 🤷   ESP-IDF version (master = UNSTABLE) · v5.1
+🔧   Moving generated files into: `/Users/zhangjun/codes/esp32/esp-demo2/myesp`...
+🔧   Initializing a fresh Git repository
+✨   Done! New project created /Users/zhangjun/codes/esp32/esp-demo2/myesp
+
+zj@a:~/codes/esp32/$ cd myesp
+
+zj@a:~/code/esp32/std/myespv4$ cat src/main.rs
+fn main() {
+    // It is necessary to call this function once. Otherwise some patches to the runtime
+    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
+    esp_idf_svc::sys::link_patches();
+
+    // Bind the log crate to the ESP Logging facilities
+    esp_idf_svc::log::EspLogger::initialize_default();
+
+    log::info!("Hello, world!");
+}

按需修改 Cargo.toml

zj@a:~/code/esp32/std/myesp$ cat Cargo.toml
+[package]
+name = "myesp"
+version = "0.1.0"
+authors = ["alizj"]
+edition = "2021"
+resolver = "2"
+rust-version = "1.71"
+
+[profile.release]
+opt-level = "s"
+
+[profile.dev]
+debug = true    # Symbols are nice and they don't increase the size on Flash
+opt-level = "z"
+
+[features]
+# 缺省 features:重点是包含 std 和 esp-idf-svc/native
+# esp-idf-svc/native 指的是 native 平台类型,除此之外还有 pio 平台类型。
+default = ["std", "embassy", "esp-idf-svc/native"]
+# std 包含 alloc 和 esp-idf-svc/std
+std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
+# alloc 依赖 esp-idf-svc/alloc
+alloc = ["esp-idf-svc/alloc"]
+# embassy 也仅依赖 esp-idf-svc
+embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
+# 总结:cargo gen 通过模板创建的 std 应用仅依赖 std 和 esp-idf-svc
+
+pio = ["esp-idf-svc/pio"]
+nightly = ["esp-idf-svc/nightly"]
+experimental = ["esp-idf-svc/experimental"]
+
+[dependencies]
+# 通过 log 打印日志,esp-idf-svc 提供了 log 的具体实现
+log = { version = "0.4", default-features = false }
+# std 类型的 crate
+esp-idf-svc = { version = "0.48", default-features = false }
+
+[build-dependencies] # build.rs 编译脚本的依赖
+embuild = "0.31.3"   # build.rs 依赖 embuild

修改 .cargo/config.toml 文件中的 ESP_IDF_VERSION 为最新版本,内容如下:

zj@a:~/codes/esp32/myesp$ cat .cargo/config.toml
+[build]
+target = "xtensa-esp32s3-espidf"  # 要构建的 target,这里使用链接 esp-idf 的 target
+
+[target.xtensa-esp32s3-espidf]  # target 对应的配置
+linker = "ldproxy" # 位于 ~/.cargo/bin/
+# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
+runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
+rustflags = [ "--cfg",  "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
+
+[unstable]
+build-std = ["std", "panic_abort"]  # 构建和使用 std(对于 no_std 是 core)
+
+[env]  # 被 embuild 使用的环境变量
+MCU="esp32s3"
+# Note: this variable is not used by the pio builder (`cargo build --features pio`)
+ESP_IDF_VERSION = "v5.2.1"
+# 使用全局 ~/.espressif/ 工具链,默认是 by 项目 workspace 的。
+ESP_IDF_TOOLS_INSTALL_DIR = "global"

按需修改 esp-idf 的配置参数文件:sdkconfig.defaults

zj@a:~/docs$ cat ~/code/esp32/std/myespv2/sdkconfig.defaults
+# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
+
+# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
+# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
+#CONFIG_FREERTOS_HZ=1000
+
+# Workaround for https://github.com/espressif/esp-idf/issues/7631
+#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
+#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n

按需修改 rust-toolchain.toml

# esp32 rust 项目通过 rust-toolchain.toml 来选择 channel 和 target
+zj@a:~/code/esp32/myespv2$ cat rust-toolchain.toml
+[toolchain]
+channel = "esp"  # ~/.rustup/toolchains/ 下的目录名称,这里使用 esp toolchain
+
+# 使用 embuild crate 来安装和构建 esp-idf framework
+# 对于 non_std 应用,不依赖 esp-idf, 故不需要 build.rs .
+zj@a:~/code/esp32/myespv2$ cat build.rs
+fn main() {
+    embuild::espidf::sysenv::output();
+}

构建项目:

  1. 每次构建前都需要先 source ~/esp/export-esp.sh 脚本。
  2. 不能启用 python env,不能使用 socks 代理,需要设置环境变量;
  3. 构建过程中默认下载和安装 esp-idf workspace .embuild/espressif/ 目录;

# 构建 std 应用时,不能 source source ~/esp/esp-idf/v5.2.1/export.sh 文件,否则会构建失败。
+zj@a:~/code/esp32/myesp$ source ~/esp/export-esp.sh
+zj@a:~/code/esp32/myesp$ cargo build
+
+# by workspace 安装的 esp-idf 到 .embuild/espressif/ 目录
+zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/
+total 4.0K
+drwxr-xr-x 6 alizj  192  5  5 14:54 dist/
+drwxr-xr-x 3 alizj   96  5  5 14:49 esp-idf/
+-rw-r--r-- 1 alizj 2.8K  5  5 14:51 espidf.constraints.v5.2.txt
+drwxr-xr-x 3 alizj   96  5  5 14:51 python_env/
+drwxr-xr-x 6 alizj  192  5  5 14:54 tools/
+
+# dist 下载的内容被解压到 .embuild/espressif/tools/ 目录
+zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/dist/
+total 193M
+-rw-r--r-- 1 alizj  70M  5  5 14:52 cmake-3.24.0-macos-universal.tar.gz
+-rw-r--r-- 1 alizj  15M  5  5 14:54 esp32ulp-elf-2.35_20220830-macos-arm64.tar.gz
+-rw-r--r-- 1 alizj 271K  5  5 14:52 ninja-mac-v1.11.1.zip
+-rw-r--r-- 1 alizj  96M  5  5 14:51 xtensa-esp-elf-13.2.0_20230928-aarch64-apple-darwin.tar.xz # 交叉编译工具链
+
+zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/tools/
+total 0
+drwxr-xr-x 3 alizj 96  5  5 14:52 cmake/
+drwxr-xr-x 3 alizj 96  5  5 14:54 esp32ulp-elf/ # ULP (Ultra-Low-Powered)
+drwxr-xr-x 3 alizj 96  5  5 14:52 ninja/
+drwxr-xr-x 3 alizj 96  5  5 14:51 xtensa-esp-elf/
+
+# esp-idf framework,被安装到 python_env/ 目录
+zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/esp-idf/v5.2.1/
+total 172K
+-rw-r--r--  1 alizj  12K  5  5 14:49 CMakeLists.txt
+-rw-r--r--  1 alizj 4.2K  5  5 14:49 COMPATIBILITY.md
+-rw-r--r--  1 alizj 4.3K  5  5 14:49 COMPATIBILITY_CN.md
+-rw-r--r--  1 alizj  314  5  5 14:49 CONTRIBUTING.md
+-rw-r--r--  1 alizj  25K  5  5 14:49 Kconfig
+-rw-r--r--  1 alizj  12K  5  5 14:49 LICENSE
+-rw-r--r--  1 alizj 8.9K  5  5 14:49 README.md
+-rw-r--r--  1 alizj 8.8K  5  5 14:49 README_CN.md
+-rw-r--r--  1 alizj  532  5  5 14:49 SECURITY.md
+-rw-r--r--  1 alizj 3.7K  5  5 14:49 SUPPORT_POLICY.md
+-rw-r--r--  1 alizj 3.4K  5  5 14:49 SUPPORT_POLICY_CN.md
+-rw-r--r--  1 alizj  721  5  5 14:49 add_path.sh
+drwxr-xr-x 81 alizj 2.6K  5  5 14:49 components/
+-rw-r--r--  1 alizj  12K  5  5 14:49 conftest.py
+drwxr-xr-x 15 alizj  480  5  5 14:49 docs/
+drwxr-xr-x 22 alizj  704  5  5 14:49 examples/
+-rw-r--r--  1 alizj 3.9K  5  5 14:49 export.bat
+-rw-r--r--  1 alizj 3.7K  5  5 14:49 export.fish
+-rw-r--r--  1 alizj 3.5K  5  5 14:49 export.ps1
+-rw-r--r--  1 alizj 8.0K  5  5 14:49 export.sh
+-rw-r--r--  1 alizj 1.8K  5  5 14:49 install.bat
+-rwxr-xr-x  1 alizj  971  5  5 14:49 install.fish*
+-rw-r--r--  1 alizj  982  5  5 14:49 install.ps1
+-rwxr-xr-x  1 alizj 1004  5  5 14:49 install.sh*
+-rw-r--r--  1 alizj  889  5  5 14:49 pytest.ini
+-rw-r--r--  1 alizj 2.0K  5  5 14:49 sdkconfig.rename
+-rw-r--r--  1 alizj  530  5  5 14:49 sonar-project.properties
+drwxr-xr-x 47 alizj 1.5K  5  5 14:51 tools/
+
+zj@a:~/code/esp32/myesp$ ls target/
+CACHEDIR.TAG  debug/  xtensa-esp32s3-espidf/

构建结果位于 target/xtensa-esp32s3-espidf 目录下:

zj@a:~/code/esp32/myesp$ ls -l target/xtensa-esp32s3-espidf/debug/
+total 11M
+-rw-r--r--   1 alizj  21K  5  5 14:45 bootloader.bin # bootloader
+drwxr-xr-x  18 alizj  576  5  5 14:39 build/
+drwxr-xr-x 178 alizj 5.6K  5  5 14:45 deps/
+drwxr-xr-x   2 alizj   64  5  5 14:39 examples/
+drwxr-xr-x   3 alizj   96  5  5 14:45 incremental/
+-rwxr-xr-x   1 alizj  11M  5  5 14:45 myesp*  # 二进制程序
+-rw-r--r--   1 alizj  153  5  5 14:45 myesp.d
+-rw-r--r--   1 alizj 3.0K  5  5 14:45 partition-table.bin # 分区表
+
+zj@a:~/code/esp32/myesp$ file target/xtensa-esp32s3-espidf/debug/myesp
+target/xtensa-esp32s3-espidf/debug/myesp: ELF 32-bit LSB executable, Tensilica Xtensa, version 1 (SYSV), statically linked, with debug_info, not stripped

构建失败的解决办法:

  1. cargo clen 清理 target 目录;
  2. 手动清理 .embuild 目录;
  3. 不能启用 socks 代理;
  4. 不能开启 python venv;

zj@a:~/code/esp32/myesp$ cargo clean
+zj@a:~/code/esp32/myesp$ rm -rf .embuild/
+zj@a:~/code/esp32/myesp$ ls
+Cargo.lock  Cargo.toml  build.rs  rust-toolchain.toml  sdkconfig.defaults  src/
+zj@a:~/code/esp32/myesp$ cargo build
+
+# cargo build 会安装 esp-idf,期间会安装 python venv 和按照 python 包。所以,不能使用 python 不支
+# 持的 socks 代理,也不能启用 python env。
+zj@a:~/code/esp32/$ enable_http_proxy
+zj@a:~/code/esp32/$ export DIR_TO_REMOVE=/Users/alizj/.venv/bin
+zj@a:~/code/esp32/$ export PATH=$(echo $PATH | sed -e "s;:$DIR_TO_REMOVE;;" -e "s;$DIR_TO_REMOVE:;;" -e "s;$DIR_TO_REMOVE;;")
+
+# 如果是 Mac M1 笔记本,需要给 cargo build 添加环境变量 CRATE_CC_NO_DEFAULTS=1,否则会构建失败,报错:
+# xtensa-esp-elf-gcc: error: unrecognized command-line option '--target=xtensa-esp32s3-espidf'
+# 参考:https://github.com/rust-lang/cc-rs/issues/1005
+zj@a:~/code/esp32/$ export CRATE_CC_NO_DEFAULTS=1

参考:

  1. https://docs.esp-rs.org/std-training/01_intro.html
  2. https://github.com/esp-rs/std-training
  3. https://github.com/danclive/esp-examples/tree/main

1 为 Rust std 应用添加组件 component #

使用 cargo build 构建基于 eps-idf-sysstd 应用时, build.rs 构建脚本执行的 embuild crate 会下载 esp-idf 库,使用 bindgen 来生成 Rust 接口,将编译后的 component 静态库链接到 Rust 可执行程序。

内置 componentbindgenesp-idf-sysbindings.h 头文件定义。

esp-idf-sys 默认启用了 所有内置 component ,并链接到 Rust 可执行程序。可以通过配置 esp_idf_components, $ESP_IDF_COMPONENTS 来指定要

接的 esp-idf 内置 component 列表。

[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_common/libesp_common.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_timer/libesp_timer.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/app_trace/libapp_trace.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_event/libesp_event.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/nvs_flash/libnvs_flash.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_phy/libesp_phy.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/vfs/libvfs.a
+// ...
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libcore.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libespnow.a
+// ...
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_coex/libesp_coex.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_wifi/libesp_wifi.a
+// ...
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/mbedtls/mbedtls/3rdparty/p256-m/libp256m.a
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libcore.a
+[esp32-camera-binding 0.1.0]
+// ...
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=-Wl,--wrap=_Unwind_Backtrace
+[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=-Wl,--wrap=__cxa_call_unexpected

为 esp-idf-sys 添加 外部额外的 component ,让 esp-idf-sys bindgen 它的头文件,并进行编译和链接。

esp-idf-sys crate 提供了一些影响 esp-idf bindgen、编译构建的配置参数和 .cargo/config.tom 环境变量:

添加 extral component 示例:

# 在 workspace 的 members 列表里添加待创建的 crate package 目录名称
+zj@a:~/code/esp32/std$ cat Cargo.toml
+[workspace]
+resolver = "2"
+members = [
+  "myesp",
+  "myespv2",
+  "myespv3",
+  "myespv4",
+  "esp32-camera-binding" # 待创建的 create package 目录名称
+]
+#...
+
+# 由于 workspace 的 Cargo.toml 中没有配置 [package], 即该 workspace 没有默认的 root crate, 它是一
+# 个virtual workspace.
+#
+# 对于 virtual workspace, 必须在 workspace 的 .cargo/config.toml 文件中, 通过环境变量
+# ESP_IDF_SYS_ROOT_CRATE="esp32-camera-binding" 来指定 root crate, 如 esp32-camera-binding.
+zj@a:~/code/esp32/std$ cat .cargo/config.toml
+[env]
+ESP_IDF_SYS_ROOT_CRATE="esp32-camera-binding" # 关键! 否则编译 esp32-camera-binding 时不会进行 bindgen 转换.
+
+# 创建上面定义的 esp32-camera-binding std 项目
+zj@a:~/code/esp32/std$ cargo generate esp-rs/esp-idf-template cargo
+zj@a:~/code/esp32/std$ cd esp32-camera-binding/
+
+# clone 要 bindgen 的 esp-idf component 项目到本地
+zj@a:~/code/esp32/std/esp32-camera-binding$ git clone git@github.com:espressif/esp32-camera.git
+zj@a:~/code/esp32/std/esp32-camera-binding$ ls
+Cargo.toml  bindings.h  build.rs  esp32-camera/  src/
+
+# 创建一个 bindings.h 文件,内容为 bindgen 要转换的 component 头文件列表,
+# 可以查看项目目录中头文件名称。
+zj@a:~/code/esp32/std/esp32-camera-binding$ cat bindings.h
+#include "esp_camera.h"
+
+# 配置刚创建的 package 的 Cargo.toml 文件,添加 [[package.metadata.esp-idf-sys.extra_components]]
+# 这样在编译 esp-idf-sys 时,会自动使用 bindgen 将 bindings.h 中的头文件生成到 esp-idf-sys 的 module 中,
+# 同时也会编译该 component 为静态库,后续可以链接到 Rust 可执行程序中。
+zj@a:~/code/esp32/std/esp32-camera-binding$ cat Cargo.toml
+[package]
+name = "esp32-camera-binding"
+version = "0.1.0"
+authors = ["alizj"]
+edition = "2021"
+resolver = "2"
+rust-version = "1.71"
+
+[profile.release]
+opt-level = "s"
+
+[profile.dev]
+debug = true
+opt-level = "z"
+
+[features]
+default = ["std", "embassy", "esp-idf-svc/native", "esp-idf-sys/native"]
+
+pio = ["esp-idf-svc/pio"]
+std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
+alloc = ["esp-idf-svc/alloc"]
+nightly = ["esp-idf-svc/nightly"]
+experimental = ["esp-idf-svc/experimental"]
+embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
+
+[dependencies]
+log = { version = "0.4", default-features = false }
+esp-idf-svc = { version = "0.48", default-features = false }
+esp-idf-sys = { version = "0.34.1"} # 添加 esp-idf-sys 依赖
+esp-idf-hal = { version = "0.43.1"}
+
+[build-dependencies]
+embuild = "0.31.3"
+
+# esp-idf-sys 的构建脚本 embuild 在编译 esp-idf C 时使用的配置参数.
+#
+# 通过在 crate package 的 Cargo.toml 文件, 而非 workspace 级别的 .cargo/config.toml 的环境变量来配置,
+# 可以更灵活和个性化.
+#
+# 下面的 package.metadata.esp-idf-sys 只能在 root crate 中配置才能生效, 而 root crate 的配置方式有两种:
+# 1. 在 workspace 的 Cargo.toml 中配置的 [package] 称为 root crate;
+# 2. 否则, 需要在 workspace 的 .cargo/config.toml 中用环境变量配置 ESP_IDF_SYS_ROOT_CRATE 配置 root crate 如 "esp32-camera-binding".
+[package.metadata.esp-idf-sys]
+esp_idf_tools_install_dir = "workspace"
+esp_idf_sdkconfig = "sdkconfig" # 相对于 workspace 目录
+esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3"] # 相对于 workspace 目录
+#esp_idf_components = ["pthread"]
+# native builder only
+esp_idf_version = "v5.2.1"
+
+[[package.metadata.esp-idf-sys.extra_components]]  # 为 esp-idf-sys 添加额外的 C component
+component_dirs = [ "esp32-camera" ]  # 本地 component 目录列表
+bindings_header = "bindings.h"  # bindgen 头文件,内容为 component 的头文件
+bindings_module = "camera"  # bindgen 转换后的 Rust 代码所属的 esp-idf-sys module
+zj@a:~/code/esp32/std/esp32-camera-binding$

上面是手动将 component 下载到本地,还可以添加 ESP-IDF component registry 中的 remote component ,这时 esp-idf 会自动下载到本地。

[package.metadata.esp-idf-sys.extra_components.0.remote_component]
+# The name of the remote component. Corresponds to a key in the dependencies of
+# `idf_component.yml`.
+name = "component_name"
+# The version of the remote component. Corresponds to the `version` field of the
+# `idf_component.yml`.
+version = "1.2"
+# A git url that contains this remote component. Corresponds to the `git`
+# field of the `idf_component.yml`.
+#
+# This field is optional.
+git = "https://github.com/espressif/esp32-camera.git"
+
+# A path to the component.
+# Corresponds to the `path` field of the `idf_component.yml`.
+#
+# Note: This should not be used for local components, use
+# `component_dirs` of extra components instead.
+#
+# This field is optional.
+path = "path/to/component"
+
+# A url to a custom component registry. Corresponds to the `service_url`
+# field of the `idf_component.yml`.
+#
+# This field is optional.
+service_url = "https://componentregistry.company.com"

remote component 示例:

zj@a:~/code/esp32/std/esp32-camera-binding$ cat Cargo.toml
+[package]
+name = "esp32-camera-binding"
+version = "0.1.0"
+authors = ["alizj"]
+edition = "2021"
+resolver = "2"
+rust-version = "1.71"
+
+[profile.release]
+opt-level = "s"
+
+[profile.dev]
+debug = true    # Symbols are nice and they don't increase the size on Flash
+opt-level = "z"
+
+[features]
+default = ["std", "embassy", "esp-idf-svc/native", "esp-idf-sys/native"]
+pio = ["esp-idf-svc/pio"]
+std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
+alloc = ["esp-idf-svc/alloc"]
+nightly = ["esp-idf-svc/nightly"]
+experimental = ["esp-idf-svc/experimental"]
+embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
+
+[dependencies]
+log = { version = "0.4", default-features = false }
+esp-idf-svc = { version = "0.48" }
+esp-idf-sys = { version = "0.34.1"}
+esp-idf-hal = { version = "0.43.1"}
+embedded-svc = "0.21"
+anyhow = "1"
+base64 = "0.13.0"
+
+[build-dependencies]
+embuild = "0.31.3"
+
+# esp-idf-sys 的构建脚本 embuild 在编译 esp-idf C 时使用的配置参数.
+#
+# 通过在 crate package 的 Cargo.toml 文件, 而非 workspace 级别的 .cargo/config.toml 的环境变量来配
+# 置,可以更灵活和个性化.
+#
+# 下面的 package.metadata.esp-idf-sys 只能在 root crate 中配置才能生效, 而 root crate 的配置方式有两种:
+# 1. 在 workspace 的 Cargo.toml 中配置的 [package] 称为 root crate;
+# 2. 否则, 需要在 workspace 的 .cargo/config.toml 中用环境变量配置 ESP_IDF_SYS_ROOT_CRATE 配置 root crate 如 "esp32-camera-binding".
+[package.metadata.esp-idf-sys]
+esp_idf_tools_install_dir = "workspace"
+esp_idf_sdkconfig = "sdkconfig" # 相对于 workspace 目录
+esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3"] # 相对于 workspace 目录
+#esp_idf_components = ["pthread"]
+# native builder only
+esp_idf_version = "v5.2.1"
+
+# 对于 virtual workspace 类型, 必须在 .cargo/config.toml 中配置 ESP_IDF_SYS_ROOT_CRATE 来指定 root crate package.
+#esp_idf_sys_root_crate="esp32-camera-binding"
+
+[[package.metadata.esp-idf-sys.extra_components]]
+component_dirs = "esp32-camera"  # 本地 component
+bindings_header = "bindings.h"
+bindings_module = "camera"
+
+[[package.metadata.esp-idf-sys.extra_components]]
+remote_component = { name = "espressif/button", version = "3.2.0"} # 远程 ESP-IDF component registry
+bindings_header = "bindings.h"
+bindings_module = "button"
+zj@a:~/code/esp32/std/esp32-camera-binding$
+
+zj@a:~/code/esp32/std/esp32-camera-binding$ cat bindings.h  # 两个 component 的头文件
+#include "esp_camera.h"
+#include "iot_button.h"

cargo build 会自动将 component 下载到本地,然后进行 bindgen 和链接:

zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out
+total 5.3M
+-rw-r--r--  1 alizj 1.4K  7 24  2006 CMakeLists.txt
+-rw-r--r--  1 alizj 5.2M  5 24 12:36 bindings.rs  # eps-idf-sys 所有 component Rust 接口绑定
+drwxr-xr-x 36 alizj 1.2K  5 24 12:36 build/
+-rw-r--r--  1 alizj 2.6K  5 24 12:36 esp-idf-build.json
+-rw-r--r--  1 alizj  147  5 24 12:36 gen-sdkconfig.defaults
+drwxr-xr-x  5 alizj  160  5 24 12:35 main/
+drwxr-xr-x  4 alizj  128  5 24 12:35 managed_components/ # 自动下载到本地的 remote component 目录
+-rw-r--r--  1 alizj  62K  5 24 12:36 sdkconfig
+
+zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/managed_components/
+total 0
+drwxr-xr-x 16 alizj 512  5 24 12:35 espressif__button/  # button
+drwxr-xr-x 19 alizj 608  5 24 12:35 espressif__cmake_utilities/
+zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/managed_components/espressif__button/
+total 84K
+-rw-r--r-- 1 alizj 2.4K  1  9 16:34 CHANGELOG.md
+-rw-r--r-- 1 alizj  487  1  9 16:34 CMakeLists.txt
+-rw-r--r-- 1 alizj 1.8K  1  9 16:34 Kconfig
+-rw-r--r-- 1 alizj 1.7K  1  9 16:34 README.md
+-rw-r--r-- 1 alizj  12K  1  9 16:34 button_adc.c
+-rw-r--r-- 1 alizj 2.6K  1  9 16:34 button_gpio.c
+-rw-r--r-- 1 alizj 2.0K  1  9 16:34 button_matrix.c
+drwxr-xr-x 3 alizj   96  1  9 16:34 examples/
+-rw-r--r-- 1 alizj  440  1  9 16:34 idf_component.yml
+drwxr-xr-x 6 alizj  192  1  9 16:34 include/
+-rw-r--r-- 1 alizj  30K  1  9 16:34 iot_button.c
+-rw-r--r-- 1 alizj  12K  1  9 16:34 license.txt
+drwxr-xr-x 8 alizj  256  1  9 16:34 test_apps/
+
+zj@a:~/code/esp32/std/esp32-camera-binding$ grep -A 3 'pub mod camera' ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/bindings.rs
+pub mod camera {
+    /* automatically generated by rust-bindgen 0.63.0 */
+
+    #[repr(C)]
+
+zj@a:~/code/esp32/std/esp32-camera-binding$ grep -A 3 'pub mod button' ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/bindings.rs
+pub mod button {
+    /* automatically generated by rust-bindgen 0.63.0 */
+
+    #[repr(C)]

2 配置 esp-rs 项目的 esp-idf-sys 参数 #

esp-idf-sys 配置参数有环境变量和 metadata 两种方式(环境变量的优先级更高):

  1. .cargo/config.toml 中配置 env 环境变量、rusts flags
  2. Cargo.toml 中为 [package.metadata.esp-idf-sys] section 添加配置参数;

如果项目 crate package 位于 workspace 中,则:

  1. 上面 .cargo/config.toml workspace 根目录下的目录和配置;
  2. [package.metadata.esp-idf-sys] 位于项目 crate package Cargo.toml 文件中;
  3. 如果 workspace 没有 root package,则称为 virtual workspace,这时需要在 workspace.cargo/config.toml 中通过环境变量 ESP_IDF_SYS_ROOT_CRATE 来指定项目 crate package 名称,否则编译 esp-idf-sys 时不会编译和 bindgen 额外的 component

通过项目 crate packageCargo.toml [package.metadata.esp-idf-sys] 配置:

[package.metadata.esp-idf-sys]
+esp_idf_tools_install_dir = "global"
+esp_idf_sdkconfig = "sdkconfig"
+esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.ble"]
+# native builder only
+esp_idf_version = "branch:release/v4.4"
+esp_idf_components = ["pthread"]

通过 cargo/config.toml 中的 env 配置: 环境变量列表

  1. esp_idf_sdkconfig_defaults, $ESP_IDF_SDKCONFIG_DEFAULTS: 默认为 sdkconfig.defaults;
  2. esp_idf_sdkconfig, $ESP_IDF_SDKCONFIG: 默认为 sdkconfig
  3. esp_idf_tools_install_dir, $ESP_IDF_TOOLS_INSTALL_DIR, 可选值为:
    • workspace(缺省),默认为 /.embuild/espressif;
    • out - the tooling will be installed or used inside esp-idf-sys’s build output directory, and will be deleted when cargo clean is invoked;
    • global(建议) - the tooling will be installed or used in its standard directory (~/.platformio for PlatformIO, and ~/.espressif for the native ESP-IDF toolset);
    • custom: - the tooling will be installed or used in the directory specified by . If this directory is a relative location, it is assumed to be relative to the workspace directory;
  4. idf_path, $IDF_PATH (native builder only): A path to a user-provided local clone of the esp-idf, that will be used instead of the one downloaded by the build script.
  5. esp_idf_version, $ESP_IDF_VERSION (native builder only) : The version used for the esp-idf, can be one of the following
  6. mcu, $MCU: The MCU name (i.e. esp32, esp32s2, esp32s3 esp32c3, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4).
  7. esp_idf_components, $ESP_IDF_COMPONENTS (native builder only) : Defaults to all components being built.

为了加快 cargo build 速率,避免每次都重新编译构建 esp-idf,建议使用的 .cargo/config.toml 示例配置(在项目执行 cargo build 命令来进行验证):

[build]
+target = "xtensa-esp32s3-espidf"
+# 使用 sccache 来调用 rustc 编译器, 有利用缓存加快构建速度.
+# 先安装 sccache: cargo install sccache --locked
+rustc-wrapper = "/opt/homebrew/bin/sccache"
+
+[target.xtensa-esp32s3-espidf]
+linker = "ldproxy"
+# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
+runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
+
+# 以下是使用 rustc 构建 esp-rs/esp-idf-sys 时传递的参数
+# https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md This is a flag for the libc
+# crate that uses 64-bits (instead of 32-bits) for time_t. This must be set for ESP-IDF 5.0 and
+# above and must be unset for lesser versions.
+rustflags = [ "--cfg",  "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
+# https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md
+# 等效于: -Zbuild-std=std,panic_abort
+# Required for std support. Rust does not provide std libraries for ESP32 targets since they are tier-2/-3.
+[unstable]
+build-std = ["std", "panic_abort"]
+
+# cargo 调用命令时使用的环境变量
+# 参考:https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md#esp-idf-configuration
+[env]
+MCU="esp32s3"
+# Note: this variable is not used by the pio builder (`cargo build --features pio`)
+# 最新 esp-idf 版本:https://github.com/espressif/esp-idf/releases
+ESP_IDF_VERSION = "v5.2.q"
+
+# 使用全局 ~/.espressif/ 工具链,默认是 by 项目 workspace 的。
+ESP_IDF_TOOLS_INSTALL_DIR = "global"

sccache 统计信息:

zj@a:~/code/esp32/myespv3$ sccache --show-stats
+Compile requests                      0
+Compile requests executed             0
+Cache hits                            0
+Cache misses                          0
+Cache timeouts                        0
+Cache read errors                     0
+Forced recaches                       0
+Cache write errors                    0
+Compilation failures                  0
+Cache errors                          0
+Non-cacheable compilations            0
+Non-cacheable calls                   0
+Non-compilation calls                 0
+Unsupported compiler calls            0
+Average cache write               0.000 s
+Average compiler                  0.000 s
+Average cache read hit            0.000 s
+Failed distributed compilations       0
+Cache location                  Local disk: "/Users/alizj/Library/Caches/Mozilla.sccache"
+Use direct/preprocessor mode?   yes
+Version (client)                0.7.7
+Max cache size                       10 GiB

参考:

  1. esp-rs/std-trainning: Embedded Rust Trainings for Espressif
  2. https://github.com/ivmarkov/rust-esp32-std-demo%EF%BC%9A Rust on ESP32 STD demo app
  3. https://apollolabsblog.hashnode.dev/series/esp32-std-embedded-rust%EF%BC%9A 强烈推荐。
  4. https://github.com/apollolabsdev/ESP32C3
+ + + + \ No newline at end of file diff --git a/Rust4.html b/Rust4.html new file mode 100644 index 0000000..bd568b9 --- /dev/null +++ b/Rust4.html @@ -0,0 +1,344 @@ + + + + + + + + + + + + + + + + + + + + + + + + 开发 Rust no_std 应用 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 开发 Rust no_std 应用 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

no_std 应用是 bare-metal 实现,不依赖 esp-idf 及其提供的 FreeRTOS 操作系统和 Rust std 标注库,而是使用 std 的一个子集 core 库,core 库不支持 heap 内存分配和线程。当前支持 HAL/WIFI/BLE/ESP-NOW/Backtrace/Storage 等。

no_std 不依赖于 C/C++ 开发的 esp-idf 及其提供的 FreeRTOS 操作系统环境,而是基于 esp-pacs/esp-hal 开发的 xtensa-esp32s3-none-elf 应用。

esp-alloc crate 为 no_std 提供了 heap 内存分配的支持;例子:esp-examples/alloc

no_std 相关的库:

说明:esp-pacs 和 esp-hal 是 no_std 应用开发的基础。

对比:

  1. std 的 esp-idf-hal 实现了 embeded-hal 和 async trait,底层基于 C/C++ esp-idf;
  2. no_std 的 esp-hal 实现了 embeded-hal 和 async trait,底层基于 esp-pacs;

其它开源的 no_std 库(embedded-* 是 Rust 嵌入式工作组或社区提供的 no_std 应用项目):

  1. embedded-graphics: Embedded-graphics is a 2D graphics library that is focused on memory constrained embedded devices.
  2. embedded-layout: Simple layout/alignment functions
  3. embedded-text: TextBox with text alignment options

embassy 是支持 async 的 no_std 库。

使用 esp-rs/esp-template 模板来快速创建 no_std 类型项目:

创建一个 no_std Bare-Metal 项目, 自定义是否使用 WiFi/Bluetooth/ESP-NOW via the esp-wifi crate;

zj@a:~/code/esp32$ cargo generate esp-rs/esp-template
+
+# no_std 应用需要声明 no_std 和 no_main 宏,这样编译器才不会导入 std 库。
+# #![no_std] 告诉编译器不导入和链接 libstd 库。
+# #![no_main] 告诉编译器不使用标准的 main 接口,而是使用 esp 提供的 main 入口。
+zj@a:~/code/esp32/non_std$ cat myesp-nonstd/src/main.rs
+#![no_std]
+#![no_main]
+
+# in a bare-metal environment, we need a panic handler that runs if a panic occurs in code There are
+# a few different crates you can use (e.g panic-halt) but esp-backtrace provides an implementation
+# that prints _the address of a backtrace_ - together with espflash these addresses can get decoded
+# into source code locations
+use esp_backtrace as _;
+
+use esp_hal::{clock::ClockControl, peripherals::Peripherals, prelude::*, delay::Delay};
+
+#[entry]
+fn main() -> ! {
+    let peripherals = Peripherals::take();
+    let system = peripherals.SYSTEM.split();
+
+    let clocks = ClockControl::max(system.clock_control).freeze();
+    let delay = Delay::new(&clocks);
+
+    esp_println::logger::init_logger_from_env();
+
+    loop {
+        log::info!("Hello world!");
+        delay.delay(500.millis());
+    }
+}

按需配置 Cargo.toml:

zj@a:~/code/esp32/non_std$ cat myesp-nonstd/Cargo.toml
+[package]
+name = "myesp-nonstd"
+version = "0.1.0"
+authors = ["alizj"]
+edition = "2021"
+license = "MIT OR Apache-2.0"
+
+[dependencies]
+# esp-has 是 no_std 类型 crate, features 中指定了 CPU 类型
+esp-hal = { version = "0.17.0", features = [ "esp32s3" ] }
+
+# bare-metal 环境下,当程序 panic 时打印调用栈
+esp-backtrace = { version = "0.11.0", features = [
+    "esp32s3",
+    "exception-handler",
+    "panic-handler",
+    "println",
+] }
+
+# esp-println 启用 log feature 后,为 log 提供具体的实现
+esp-println = { version = "0.9.0", features = ["esp32s3", "log"] }
+
+# 向终端打印日志。esp_println 提供了 log 的具体实现
+log = { version = "0.4.20" }
+
+[profile.dev]
+# Rust debug is too slow.  For debug builds always builds with some optimization
+opt-level = "s" # optimize for binary size
+
+# dev profile 的 debug 参数默认为 2,表示 full debug info,
+# release profile 的 debug 参数默认为 0,表示关闭 debug info;
+
+[profile.release]
+codegen-units = 1 # LLVM can perform better optimizations using a single thread
+debug = 2 # 2/full/true:full debug info, 虽然二进制包含 debuginfo,但是烧写时会被去掉,所以不会增加 flash app 体积
+debug-assertions = false
+incremental = false
+lto = 'fat'
+opt-level = 's'
+overflow-checks = false

按需配置 rust-toolchain.toml :

zj@a:~/code/esp32/myesp-nonstd$ cat rust-toolchain.toml
+[toolchain]
+channel = "esp" # 使用 esp channel 工具链

按需配置 .cargo/config.toml :

zj@a:~/code/esp32/myesp-nonstd$ cat .cargo/config.toml
+[target.xtensa-esp32s3-none-elf]
+runner = "espflash flash --monitor"  # cargo run 会烧录 flash 和读终端日志
+
+[env]
+ESP_LOGLEVEL="INFO"
+
+[build]
+rustflags = [
+  "-C", "link-arg=-nostartfiles",
+]
+
+target = "xtensa-esp32s3-none-elf" # 使用不链接 esp-idf 的 none-elf 工具链
+
+[unstable]
+build-std = ["core"]  # 使用 core 库而非 std 库!

构建和烧录:

zj@a:~/code/esp32$ cd myesp-nonstd/
+
+zj@a:~/code/esp32/myesp-nonstd$ source ~/esp/export-esp.sh
+
+# 构建,只使用 --release profile
+zj@a:~/code/esp32/myesp-nonstd$ cargo build --release
+
+# cargo 会运行 espflash 来烧写 binary
+zj@a:~/code/esp32/myesp-nonstd$ cargo run

no_std 构建结果 只有二进制 myesp-nonstd, 不包含构建 std 应用时生成的 bootloader.bin 和 partition-table.bin:

zj@a:~/code/esp32/non_std$ ls -l target/xtensa-esp32s3-none-elf/debug/
+total 2.0M
+drwxr-xr-x  27 alizj  864  5  9 12:20 build/
+drwxr-xr-x 338 alizj  11K  5 10 15:27 deps/
+drwxr-xr-x   2 alizj   64  5  8 15:06 examples/
+drwxr-xr-x   5 alizj  160  5  9 12:21 incremental/
+-rwxr-xr-x   1 alizj 2.0M  5 10 15:27 myesp-nonstd*
+-rw-r--r--   1 alizj  194  5  8 15:06 myesp-nonstd.d

参考:

  1. 官方文档:Embedded Rust (no_std) on Espressif
  2. 官方 non_std 示例:https://github.com/esp-rs/no_std-training
  3. https://apollolabsblog.hashnode.dev/series/esp32c3-embedded-rust-hal 强烈推荐。
  4. https://github.com/apollolabsdev/ESP32C3
  5. Bare-Metal Rust on ESP32: A Brief Overview
  6. https://apollolabsblog.hashnode.dev/the-embedded-rust-esp-development-ecosystem

1 在 Rust no_std 应用中使用 defmt 日志框架 #

defmt 是一种 no_std 应用的 logging framework,它将 ESP32 芯片中应用打印的日志延迟到 host server 上格式化,从而降低 ESP32 芯片应用的内存开销。

ESP32 no_std book 的 defmt 例子:https://docs.esp-rs.org/no_std-training/03_7_defmt.html

对于 ESP32 no_std 应用来说, esp-println, esp-backtrace and espflash/cargo-espflash provide mechanisms to use defmt :

  1. espflash has support for different logging formats, one of them being defmt.
    • espflash requires framming bytes as when using defmt it also needs to print non-defmt messages, like the bootloader prints. It’s important to note that other defmt-enabled tools like probe-rs won’t be able to parse these messages due to the extra framing bytes. Uses rzcobs encoding
  2. esp-println has a defmt-espflash feature, which adds framming bytes so espflash knows that is a defmt message.
  3. esp-backtrace has a defmt feature that uses defmt logging to print panic and exception handler messages.

在代码里使用 defmt::println!() 等宏来打印日志。

If you want to use any of the logging macros like info, debug

defmt-rtt:Transmit defmt log messages over the RTT (Real-Time Transfer) protocol https://github.com/knurling-rs/defmt/tree/main/firmware/defmt-rtt

embassy 依赖于 defmt 和 defmt-rtt:

  1. https://embassy.dev/book/dev/project_structure.html
  2. https://embassy.dev/book/dev/basic_application.html
+ + + + \ No newline at end of file diff --git a/Rust5.html b/Rust5.html new file mode 100644 index 0000000..e4143ba --- /dev/null +++ b/Rust5.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + + + + + + + + 开发 Rust/C/C++ cmake 混合应用 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 开发 Rust/C/C++ cmake 混合应用 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

esp-idf 支持链接 C/C++ 或 Rust 编写的 component,而使用 cargo generate esp-rs/esp-idf-template cmake来创建 Rust + C/C++ 混合风格的 std 或 non_std 应用,该应用使用安装的 ~/esp/esp-idf/v5.2.1/ 中的idf.py 和 cmake 来构建。

默认创建启用 HAL( esp-idf-svc)的 std 应用。

Rust cmake 读取和使用的参数:

  1. RUST_DEPS
  2. CONFIG_IDF_TARGET_ARCH_RISCV
  3. SDKCONFIG
  4. IDF_PATH

使用 cargo generate esp-rs/esp-idf-template cmake 创建应用:

构建前需要 同时 source esp-idf 的 export.h 和 export-esp.sh。

构建命令是 idf.py build 而非 cargo build。

创建项目,默认是 std 应用

Copy

zj@a:~/code/esp32$ cargo generate esp-rs/esp-idf-template cmake
+⚠️   Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
+🤷   Project Name: mycmake
+🔧   Destination: /Users/alizj/code/esp32/mycmake ...
+🔧   project-name: mycmake ...
+🔧   Generating template ...
+✔ 🤷   Configure advanced template options? · true
+✔ 🤷   Rust toolchain (beware: nightly works only for riscv MCUs!) · esp
+✔ 🤷   Enable HAL support? · true
+✔ 🤷   Enable STD support? · true
+🔧   Moving generated files into: `/Users/alizj/code/esp32/mycmake`...
+🔧   Initializing a fresh Git repository
+✨   Done! New project created /Users/alizj/code/esp32/mycmake
+
+zj@a:~/code/esp32/mycmake$ ls
+CMakeLists.txt  components/  diagram.json  main/  sdkconfig.defaults  wokwi.toml
+
+zj@a:~/code/esp32/mycmake$ ls main/
+CMakeLists.txt  main.c

Rust 代码作为一个 component 被集成:

Copy

zj@a:~/code/esp32/mycmake$ ls components/rust-mycmake/
+CMakeLists.txt  Cargo.toml  build.rs  placeholder.c  rust-toolchain.toml  src/
+
+zj@a:~/code/esp32/mycmake$ ls components/rust-mycmake/src/
+lib.rs

Rust componet 的 CMakeLists.txt 文件封装了构建该 Rust 代码的 cargo 命令和配置参数。

Copy

zj@a:~/code/esp32/mycmake/components/rust-mycmake$ cat CMakeLists.txt
+# If this component depends on other components - be it ESP-IDF or project-specific ones - enumerate those in the double-quotes below, separated by spaces
+# Note that pthread should always be there, or else STD will not work
+set(RUST_DEPS "pthread" "driver" "vfs")
+# Here's a non-minimal, reasonable set of ESP-IDF components that one might want enabled for Rust:
+#set(RUST_DEPS "pthread" "esp_http_client" "esp_http_server" "espcoredump" "app_update" "esp_serial_slave_link" "nvs_flash" "spi_flash" "esp_adc_cal" "mqtt")
+
+idf_component_register(
+    SRCS "placeholder.c"
+    INCLUDE_DIRS ""
+    PRIV_REQUIRES "${RUST_DEPS}"
+)
+
+if(CONFIG_IDF_TARGET_ARCH_RISCV)
+    if (CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H2)
+        set(RUST_TARGET "riscv32imac-esp-espidf")
+    else ()
+        set(RUST_TARGET "riscv32imc-esp-espidf")
+    endif()
+elseif(CONFIG_IDF_TARGET_ESP32)
+    set(RUST_TARGET "xtensa-esp32-espidf")
+elseif(CONFIG_IDF_TARGET_ESP32S2)
+    set(RUST_TARGET "xtensa-esp32s2-espidf")
+elseif(CONFIG_IDF_TARGET_ESP32S3)
+    set(RUST_TARGET "xtensa-esp32s3-espidf")
+else()
+    message(FATAL_ERROR "Unsupported target ${CONFIG_IDF_TARGET}")
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+    set(CARGO_BUILD_TYPE "debug")
+    set(CARGO_BUILD_ARG "")
+else()
+    set(CARGO_BUILD_TYPE "release")
+    set(CARGO_BUILD_ARG "--release")
+endif()
+
+
+set(CARGO_BUILD_STD_ARG -Zbuild-std=std,panic_abort)
+
+
+if(IDF_VERSION_MAJOR GREATER "4")
+set(ESP_RUSTFLAGS "--cfg espidf_time64")
+endif()
+
+set(CARGO_PROJECT_DIR "${CMAKE_CURRENT_LIST_DIR}")
+set(CARGO_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+set(CARGO_TARGET_DIR "${CARGO_BUILD_DIR}/target")
+
+set(RUST_INCLUDE_DIR "${CARGO_TARGET_DIR}")
+set(RUST_STATIC_LIBRARY "${CARGO_TARGET_DIR}/${RUST_TARGET}/${CARGO_BUILD_TYPE}/librust_mycmake.a")
+
+# if this component uses CBindGen to generate a C header, uncomment the lines below and adjust the header name accordingly
+#set(RUST_INCLUDE_HEADER "${RUST_INCLUDE_DIR}/RustApi.h")
+#set_source_files_properties("${RUST_INCLUDE_HEADER}" PROPERTIES GENERATED true)
+
+idf_build_get_property(sdkconfig SDKCONFIG)
+idf_build_get_property(idf_path IDF_PATH)
+
+ExternalProject_Add(
+    project_rust_mycmake
+    PREFIX "${CARGO_PROJECT_DIR}"
+    DOWNLOAD_COMMAND ""
+    CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env
+        cargo clean --target ${RUST_TARGET} --target-dir ${CARGO_TARGET_DIR}
+    USES_TERMINAL_BUILD true
+    BUILD_COMMAND ${CMAKE_COMMAND} -E env
+        CARGO_CMAKE_BUILD_INCLUDES=$<TARGET_PROPERTY:${COMPONENT_LIB},INCLUDE_DIRECTORIES>
+        CARGO_CMAKE_BUILD_LINK_LIBRARIES=$<TARGET_PROPERTY:${COMPONENT_LIB},LINK_LIBRARIES>
+        CARGO_CMAKE_BUILD_SDKCONFIG=${sdkconfig}
+        CARGO_CMAKE_BUILD_ESP_IDF=${idf_path}
+        CARGO_CMAKE_BUILD_COMPILER=${CMAKE_C_COMPILER}
+        RUSTFLAGS=${ESP_RUSTFLAGS}
+        MCU=${CONFIG_IDF_TARGET}
+        cargo build --target ${RUST_TARGET} --target-dir ${CARGO_TARGET_DIR} ${CARGO_BUILD_ARG} ${CARGO_BUILD_STD_ARG}
+    INSTALL_COMMAND ""
+    BUILD_ALWAYS TRUE
+    TMP_DIR "${CARGO_BUILD_DIR}/tmp"
+    STAMP_DIR "${CARGO_BUILD_DIR}/stamp"
+    DOWNLOAD_DIR "${CARGO_BUILD_DIR}"
+    SOURCE_DIR "${CARGO_PROJECT_DIR}"
+    BINARY_DIR "${CARGO_PROJECT_DIR}"
+    INSTALL_DIR "${CARGO_BUILD_DIR}"
+    BUILD_BYPRODUCTS
+        "${RUST_INCLUDE_HEADER}"
+        "${RUST_STATIC_LIBRARY}"
+)
+
+add_prebuilt_library(library_rust_mycmake "${RUST_STATIC_LIBRARY}" PRIV_REQUIRES "${RUST_DEPS}")
+add_dependencies(library_rust_mycmake project_rust_mycmake)
+
+target_include_directories(${COMPONENT_LIB} PUBLIC "${RUST_INCLUDE_DIR}")
+target_link_libraries(${COMPONENT_LIB} PRIVATE library_rust_mycmake)

配置了 crate-type 为 staticlib,故会被编译为 esp-idf 可以链接的 C 库

Copy

zj@a:~/code/esp32/mycmake/components/rust-mycmake$ cat Cargo.toml
+[package]
+name = "rust-mycmake"
+version = "0.1.0"
+authors = ["alizj"]
+edition = "2021"
+resolver = "2"
+rust-version = "1.71"
+
+[lib]
+crate-type = ["staticlib"]
+
+[profile.release]
+opt-level = "s"
+
+[profile.dev]
+debug = true # Symbols are nice and they don't increase the size on Flash
+opt-level = "z"
+[features]
+default = ["std", "embassy", "esp-idf-svc/native"]
+
+pio = ["esp-idf-svc/pio"]
+std = ["alloc", "esp-idf-svc/std"]
+alloc = ["esp-idf-svc/alloc"]
+nightly = ["esp-idf-svc/nightly"]
+experimental = ["esp-idf-svc/experimental"]
+embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
+
+[dependencies]
+log = { version = "0.4", default-features = false }
+esp-idf-svc = { version = "0.48", default-features = false }
+
+[build-dependencies]
+embuild = "0.31.3"
+
+# 编译为 staticlib,可以被 C 库调用的 Rust 代码
+zj@a:~/code/esp32/mycmake/components/rust-mycmake$ cat src/lib.rs
+#[no_mangle]
+extern "C" fn rust_main() -> i32 {
+    // It is necessary to call this function once. Otherwise some patches to the runtime
+    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
+    esp_idf_svc::sys::link_patches();
+
+    // Bind the log crate to the ESP Logging facilities
+    esp_idf_svc::log::EspLogger::initialize_default();
+
+    log::info!("Hello, world!");
+    42
+}
+
+zj@a:~/code/esp32/mycmake/components/rust-mycmake$ cat placeholder.c
+/* Hello World Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+/* This is an empty source file to force the build of a CMake library that
+   can participate in the CMake dependency graph. This placeholder library
+   will depend on the actual library generated by external Rust build.
+*/
+
+# main.c 中调用 Rust 的 rust_main() 函数代码
+zj@a:~/code/esp32/mycmake$ cat main/main.c
+/* Hello World Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <stdio.h>
+
+extern int rust_main(void);
+
+void app_main(void) {
+    printf("Hello world from C!\n");
+
+    int result = rust_main();
+
+    printf("Rust returned code: %d\n", result);
+}

构建前需要 source esp-idf 的 export.h 和 export-esp.sh:

Copy

zj@a:~/code/esp32/mycmake$ source ~/esp/esp-idf/v5.2.1/export.sh   # esp idf
+zj@a:~/code/esp32/mycmake$ source ~/export-esp.sh   # esp rs,因为后续会 build Rust component

构建:

Copy

#idf.py set-target [esp32|esp32s2|esp32s3|esp32c2|esp32c3|esp32c6|esp32h2]
+zj@a:~/code/esp32/mycmake$ idf.py build
+Executing action: all (aliases: build)
+Running ninja in directory /Users/alizj/code/esp32/mycmake/build
+Executing "ninja all"...
+[0/9] Performing build step for 'project_rust_mycmake'    Finished release [optimized] target(s) in 0.25s
+[1/1] cd /Users/alizj/code/esp32/mycmake/build/bootloader/esp-idf/esptool_py && /Users/alizj/.espressif/p...izes.py --offset 0x8000 bootloader 0x1000 /Users/alizj/code/esp32/mycmake/build/bootloader/bootloader.bi
+Bootloader binary size 0x6860 bytes. 0x7a0 bytes (7%) free.
+[3/3] cd /Users/alizj/code/esp32/mycmake/build/esp-idf/esptool_py && /Users/alizj/.espressif/python_env/i...esp32/mycmake/build/partition_table/partition-table.bin /Users/alizj/code/esp32/mycmake/build/mycmake.bi
+mycmake.bin binary size 0x5b770 bytes. Smallest app partition is 0x100000 bytes. 0xa4890 bytes (64%) free.
+
+Project build complete. To flash, run:
+ idf.py flash
+or
+ idf.py -p PORT flash
+or
+ python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/mycmake.bin
+or from the "/Users/alizj/code/esp32/mycmake/build" directory
+ python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"
+
+# 构建的产物位于 build 目录下 。

烧录到设备 flash

Copy

idf.py -p /dev/cu.usbmodem2101 flash monitor

参考:

  1. esp-idf-template 的 cmake 文档;
  2. Integrating a Rust Component into an ESP-IDF Project
+ + + + \ No newline at end of file diff --git a/Rust6.html b/Rust6.html new file mode 100644 index 0000000..1ab1f73 --- /dev/null +++ b/Rust6.html @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + esp-rs 常见问题 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ esp-rs 常见问题 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

rust-esp32 - 这篇文章属于一个选集。

§ 1: 安装 ESP32 C 开发框架 esp-idf

§ 2: 使用 C 和 esp-idf 开发 ESP32 项目

§ 3: 安装 ESP32 Rust 开发工具链 esp-rs

§ 4: 使用 Rust 开发 ESP32 应用

§ 5: 开发 Rust std 应用

§ 6: 开发 Rust no_std 应用

§ 7: 开发 Rust/C/C++ cmake 混合应用

§ 8: 本文

§ 9: 配置 cargo workspace

§ 10: 使用 cargo run 和 espflash 烧写固件

§ 11: 分析 ESP32 固件和分区表

§ 12: 使用 USB-JTAG/probe-rs 调试应用

§ 13: Rust 驱动 LCD - 显示中英文

§ 14: Rust 驱动 LCD - 显示图片

§ 15: Rust 驱动 Touch - 触摸板

§ 16: Rust 驱动 Camera - 采集和播放

§ 17: Rust 驱动 Audio - 播放和录音

  1. 在 esp-idf-template 生成的模板项目中执行 cargo build 报错:
  2. https://github.com/esp-rs/esp-idf-template/issues/165

Copy

  Using managed esp-idf repository: RemoteSdk { repo_url: None, git_ref: Tag("v5.1.2") }
+  Using esp-idf v5.1.2 at '/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2'
+  ERROR: /Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/espidf.constraints.v5.1.txt doesn't exist. Perhaps you've forgotten to run the install scripts. Please check the installation guide for more information.
+  CMake Error at /Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/cmake/build.cmake:363 (message):
+    Some Python dependencies must be installed.  Check above message for
+    details.
+  Call Stack (most recent call first):
+    /Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/cmake/build.cmake:498 (__build_check_python)
+    /Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/cmake/project.cmake:547 (idf_build_process)
+    CMakeLists.txt:28 (project)
+
+
+  thread 'main' panicked at /Users/zhangjun/.cargo/registry/src/index.crates.io-6f17d22bba15001f/cmake-0.1.50/src/lib.rs:1098:5:
+
+  command did not execute successfully, got: exit status: 1
+
+  build script failed, must exit now
+  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

解决办法:

Copy

cargo clean && rm -rf .embuild && cargo build
  1. 报错: Missing dependencies for SOCKS support.

Copy

  来自 https://github.com/ThrowTheSwitch/Unity
+   * branch            7d2bf62b7e6afaf38153041a9d53c21aeeca9a25 -> FETCH_HEAD
+  ERROR: Could not install packages due to an OSError: Missing dependencies for SOCKS support.
+
+  WARNING: There was an error checking the latest version of pip.
+  Error: Command '['/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/python_env/idf5.1_py3.12_env/bin/python3', '-m', 'pip', 'install', '--upgrade', 'pip']' returned non-zero exit status 1.
+  Traceback (most recent call last):
+    File "/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/idf_tools.py", line 2687, in <module>
+      main(sys.argv[1:])
+    File "/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/idf_tools.py", line 2679, in main
+      action_func(args)
+    File "/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/idf_tools.py", line 2098, in action_install_python_env
+      subprocess.check_call([sys.executable, '-m', 'venv',
+    File "/Users/zhangjun/.pyenv/versions/3.12.1/lib/python3.12/subprocess.py", line 413, in check_call
+      raise CalledProcessError(retcode, cmd)
+  subprocess.CalledProcessError: Command '['/Users/zhangjun/.pyenv/versions/3.12.1/bin/python3', '-m', 'venv', '--clear', '--upgrade-deps', '/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/python_env/idf5.1_py3.12_env']' returned non-zero exit status 1.
+  Error: Could not install esp-idf
+
+  Caused by:
+      command 'env -u IDF_PYTHON_ENV_PATH -u MSYSTEM IDF_TOOLS_PATH="/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif" "python3" "/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2/tools/idf_tools.py" "--idf-path" "/Users/zhangjun/codes/esp32/esp-demo/.embuild/espressif/esp-idf/v5.1.2" "--non-interactive" "install-python-env"' exited with non-zero status code 1
+zj@a:~/codes/esp32/esp-demo$

解决办法: 不使用 socks5 代理, 而是使用 https/http 代理:

Copy

# 将下列内容添加到 ~/esp/export.sh 和 ~/esp/export-esp.sh 中
+export all_proxy="http://192.168.3.2:1080" ALL_PROXY="http://192.168.3.2:1080"
+
+# 在 ~/.bashrc 中添加如下内容, 用于手动切换:
+alias enable_http_proxy='export all_proxy="http://192.168.3.2:1080" ALL_PROXY="http://192.168.3.2:1080"'
+alias enable_socks_proxy='export all_proxy="socks5h://192.168.3.2:1080" ALL_PROXY="socks5h://192.168.3.2:1080"'
+alias disable_proxy='unset all_proxy ALL_PROXY'
  1. rust-analyzer 报错,不能正常解析和补全。

解决办法:

Copy

zj@a:~/codes/esp32/esp-demo2/myesp$ cd
+
+zj@a:~$ rustup component add rust-analyzer
+info: downloading component 'rust-analyzer'
+info: installing component 'rust-analyzer'
+
+zj@a:~$ ls -l ~/.rustup/toolchains/
+esp/                         nightly-x86_64-apple-darwin/
+
+zj@a:~$ ls -l ~/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/
+total 95M
+-rwxr-xr-x 1 zhangjun  29M  2  8 14:48 cargo*
+-rwxr-xr-x 1 zhangjun 1.1M  2  8 14:48 cargo-clippy*
+-rwxr-xr-x 1 zhangjun 1.5M  2  8 14:49 cargo-fmt*
+-rwxr-xr-x 1 zhangjun  11M  2  8 14:48 clippy-driver*
+-rwxr-xr-x 1 zhangjun  36M  2  8 16:57 rust-analyzer*
+-rwxr-xr-x 1 zhangjun  980  2  8 14:48 rust-gdb*
+-rwxr-xr-x 1 zhangjun 2.2K  2  8 14:49 rust-gdbgui*
+-rwxr-xr-x 1 zhangjun 1.1K  2  8 14:48 rust-lldb*
+-rwxr-xr-x 1 zhangjun 598K  2  8 14:49 rustc*
+-rwxr-xr-x 1 zhangjun  11M  2  8 14:49 rustdoc*
+-rwxr-xr-x 1 zhangjun 6.6M  2  8 14:49 rustfmt*
+
+zj@a:~$ ln -sf ~/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/rust-analyzer ~/.rustup/toolchains/esp/bin/rust-analyzer
+z
  1. 报错:warning: esp-idf-sys@0.34.1: could not identify the root crate and `ESP_IDF_SYS_ROOT_CRATE` not specified。

    这是因为同时 source 了 export.sh 和 export-esp.sh,当构建纯 Rust std/non_std 应用时,只需要 source export-esp.sh 即可。

    Copy

       zj@a:~/code/esp32/std$ source ~/esp/esp-idf/v5.2.1/export.sh
    +   zj@a:~/code/esp32/std$ source ~/esp/export-esp.sh
    +
    +   zj@a:~/code/esp32/std$ cargo build
    +   warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
    +   package:   /Users/alizj/code/esp32/std/myesp/Cargo.toml
    +   workspace: /Users/alizj/code/esp32/std/Cargo.toml
    +   warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
    +   package:   /Users/alizj/code/esp32/std/myespv2/Cargo.toml
    +   workspace: /Users/alizj/code/esp32/std/Cargo.toml
    +   warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
    +   package:   /Users/alizj/code/esp32/std/myespv3/Cargo.toml
    +   workspace: /Users/alizj/code/esp32/std/Cargo.toml
    +   warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
    +   package:   /Users/alizj/code/esp32/std/myespv4/Cargo.toml
    +   workspace: /Users/alizj/code/esp32/std/Cargo.toml
    +   Compiling esp-idf-sys v0.34.1
    +   The following warnings were emitted during compilation:
    +
    +   warning: esp-idf-sys@0.34.1: could not identify the root crate and `ESP_IDF_SYS_ROOT_CRATE` not specified
    +
    +   error: failed to run custom build command for `esp-idf-sys v0.34.1`
    +
    +   Caused by:
    +   process didn't exit successfully: `/Users/alizj/code/esp32/std/target/debug/build/esp-idf-sys-eac13132720836e4/build-script-build` (exit status: 101)
    +     --- stdout
    +     cargo:rerun-if-env-changed=ESP_IDF_TOOLS_INSTALL_DIR
    +     cargo:rerun-if-env-changed=ESP_IDF_SDKCONFIG
    +     cargo:rerun-if-env-changed=ESP_IDF_SDKCONFIG_DEFAULTS
    +     cargo:rerun-if-env-changed=MCU

rust-esp32 - 这篇文章属于一个选集。

§ 1: 安装 ESP32 C 开发框架 esp-idf

§ 2: 使用 C 和 esp-idf 开发 ESP32 项目

§ 3: 安装 ESP32 Rust 开发工具链 esp-rs

§ 4: 使用 Rust 开发 ESP32 应用

§ 5: 开发 Rust std 应用

§ 6: 开发 Rust no_std 应用

§ 7: 开发 Rust/C/C++ cmake 混合应用

§ 8: 本文

§ 9: 配置 cargo workspace

§ 10: 使用 cargo run 和 espflash 烧写固件

§ 11: 分析 ESP32 固件和分区表

§ 12: 使用 USB-JTAG/probe-rs 调试应用

§ 13: Rust 驱动 LCD - 显示中英文

§ 14: Rust 驱动 LCD - 显示图片

§ 15: Rust 驱动 Touch - 触摸板

§ 16: Rust 驱动 Camera - 采集和播放

§ 17: Rust 驱动 Audio - 播放和录音

Rust 驱动 Audio - 播放和录音

2024-08-28·6323 字

Rust Esp32 Rust Esp32

Rust 驱动 Camera - 采集和播放

2024-08-28·7304 字

Rust Esp32 Rust Esp32

Rust 驱动 LCD - 显示中英文

2024-08-28·818 字

Rust Esp32 Rust Esp32

Rust 驱动 LCD - 显示图片

2024-08-28·4232 字

Rust Esp32 Rust Esp32

+ + + + \ No newline at end of file diff --git a/Rust7.html b/Rust7.html new file mode 100644 index 0000000..fecb950 --- /dev/null +++ b/Rust7.html @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + 配置 cargo workspace | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 配置 cargo workspace +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+ +
+

在 ~/code/esp32 下创建 std/non_std 两个目录,分别作为 std 和 non_std 应用的 workspace 目录, 之所以区分他们是因为 workspace 目录下的 .cargo/config.toml 文件配置不同:

  1. std workspace:创建 Cargo.toml,从一个 std 应用目录拷贝.cargo/config.toml/rust-toolchain.toml/sdkconfig.defaults 文件;
  2. non_std workspace: 创建 Cargo.toml,从一个 non_std 应用目录拷贝.cargo/config.toml/rust-toolchain.toml/sdkconfig.defaults 文件;

std workspace:

  1. .cargo/config.toml 中设置最新的 ESP_IDF_VERSION = “v5.2.1”,没有指定 ESP_IDF_TOOLS_INSTALL_DIR = “global”,这样会在 workspace 目录下的 .embuild 中保存一份。

Copy

zj@a:~/code/esp32/std$ cat Cargo.toml
+[workspace]
+resolver = "2" # 没有指定 workspace 的 root package 时必须要指定该参数
+members = [  # std 应用目录列表
+  "myesp",
+  "myespv2",
+  "myespv3",
+]
+
+exclude = [ # 排除的目录列表
+  "mycmake",
+  "old",
+]
+
+[profile.release]
+opt-level = "s"
+
+[profile.dev]
+debug = true    # Symbols are nice and they don't increase the size on Flash
+opt-level = "z"
+zj@a:~/code/esp32/std$
+zj@a:~/code/esp32/std$ cat .cargo/config.toml
+[build]
+target = "xtensa-esp32s3-espidf"
+
+[target.xtensa-esp32s3-espidf]
+linker = "ldproxy"
+# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
+runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
+rustflags = [ "--cfg",  "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
+
+[unstable]
+build-std = ["std", "panic_abort"]
+
+[env]
+MCU="esp32s3"
+# Note: this variable is not used by the pio builder (`cargo build --features pio`)
+ESP_IDF_VERSION = "v5.2.1"
+ESP_IDF_SDKCONFIG_DEFAULTS = { value = "sdkconfig.defaults", relative = true }
+
+zj@a:~/code/esp32/std$ cat rust-toolchain.toml
+[toolchain]
+channel = "esp"

non_std workspace:

Copy

zj@a:~/code/esp32/non_std$ cat Cargo.toml
+[workspace]
+resolver = "2"
+members = [
+  "myesp-nonstd"
+]
+
+[profile.dev]
+# Rust debug is too slow.
+# For debug builds always builds with some optimization
+opt-level = "s"
+
+[profile.release]
+codegen-units = 1 # LLVM can perform better optimizations using a single thread
+debug = 2
+debug-assertions = false
+incremental = false
+lto = 'fat'
+opt-level = 's'
+overflow-checks = false
+
+zj@a:~/code/esp32/non_std$ cat .cargo/config.toml
+[target.xtensa-esp32s3-none-elf]
+runner = "espflash flash --monitor"
+
+
+[env]
+ESP_LOGLEVEL="INFO"
+
+[build]
+rustflags = [
+  "-C", "link-arg=-nostartfiles",
+]
+
+target = "xtensa-esp32s3-none-elf"
+
+[unstable]
+build-std = ["core"]
+
+zj@a:~/code/esp32/non_std$ cat rust-toolchain.toml
+[toolchain]
+channel = "esp"
+zj@a:~/code/esp32/non_std$

新增应用:

  1. 在对应的 std/non_std 目录下使用 cargo generate 模板来创建应用。
  2. 然后将应用名称添加到 members 列表中;

构建应用:

  1. 在 std/non_std workspace 根目录下: cargo build -p app1
  2. 在特定应用目录,如 std/myespv2 目录下: cargo build # 只构建当前应用

当应用位于 workspace 中(members 列表中) 时,cargo build 不再读取应用目录中的 下列文件,而是都使用 workspaces 目录下的对应文件。

  1. rust-toolchain.toml
  2. .cargo/config.toml
  3. sdkconfig.defaults 和 sdkconfig.defaults.esp32s3 # 相对与 workspace 目录。

但是应用目录下的 Cargo.toml 和 build.rs 还是必须的,分别指定各应用自身的依赖和构建脚本。

+ + + + \ No newline at end of file diff --git a/Rust8.html b/Rust8.html new file mode 100644 index 0000000..e5e9227 --- /dev/null +++ b/Rust8.html @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + 使用 cargo run 和 espflash 烧写固件 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 使用 cargo run 和 espflash 烧写固件 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+ +
+

espflash 是固件烧录和终端监视工具,基于 esptool.py.

参考:https://github.com/esp-rs/espflash/tree/main/espflash#installation

cargo-espflash vs espflash: cargo-espflash 是作为 cargo 的一个子命令来运行的,如 cargo espflash, 它支持 cargo 的 –bin/–expample 参数来快速指定 bin 和 example binary 名称。而 espflash 是一个单独工具,需要指定 bin 文件的具体路径:

Copy

MCU=esp32c3 cargo espflash flash --target riscv32imc-esp-espidf --example ledc_simple --monitor
+cargo espflash flash --example=blinky --monitor
+
+espflash flash target/debug/myapp --monitor

espflash 使用 USB 串口(linux: /dev/ttyUSB0, macOS: /dev/cu.*) 来烧录芯片的 Flash,它会检查项目的依赖是否包含 esp-idf-sys 来判断是那种类型的应用,然后 自动烧写不同的文件

Copy

zj@a:~/codes/esp32$ espflash --help
+A command-line tool for flashing Espressif devices
+
+Usage: espflash <COMMAND>
+
+Commands:
+  board-info       Print information about a connected target device
+  completions      Generate completions for the given shell
+  erase-flash      Erase Flash entirely
+  erase-parts      Erase specified partitions
+  erase-region     Erase specified region
+  flash            Flash an application in ELF format to a connected target device
+  monitor          Open the serial monitor without flashing the connected target device
+  partition-table  Convert partition tables between CSV and binary format
+  save-image       Generate a binary application image and save it to a local disk
+  write-bin        Write a binary file to a specific address in a target device's flash
+  help             Print this message or the help of the given subcommand(s)
+
+Options:
+  -h, --help     Print help
+  -V, --version  Print version
+zj@a:~/codes/esp32$

cargo run: 在项目的 .cargo/config.toml 中添加如下内容, 然后就可以执行 cargo run 来 flash 和 monitor 应用:

Copy

[target.'cfg(any(target_arch = "riscv32", target_arch = "xtensa"))']
+runner = "espflash flash --baud=921600 --monitor"

espflash 配置文件 espflash.toml:

Copy

[connection]
+serial = "/dev/ttyUSB0"
+baudrate = 460800
+bootloader = "path/to/custom/bootloader.bin"
+partition_table = "path/to/custom/partition-table.bin"
+
+[flash]
+mode = "qio"
+size = "8MB"
+frequency = "80MHz"

espflash.toml 文件位置:

  1. 项目目录(Cargo.toml 所在目录);
  2. workspace 根目录;
  3. $HOME/Library/Application Support/rs.esp.espflash/espflash.toml

monitor 日志格式:espflash 的 flash 和 monitor 子命令支持 -L/–log-format 参数指定日志格式:

  1. serial: Default logging format
  2. defmt : Uses [defmt] logging framework. With logging format, logging strings have framing bytes to indicate that they are defmt messages.
    • 一般是在 no_std 应用中使用,需要和 esp-println crate 联合使用。

Establishing a serial connection with the ESP32-S3 target device could be done using USB-to-UART bridge or USB peripheral supported in ESP32-S3. For the ESP32-S3, the USB peripheral is available, allowing you to flash the binaries without the need for an external USB-to-UART bridge.

If you are flashing for the first time, you need to get the ESP32-S3 into the download mode manually. To do so, press and hold the BOOT button and then press the RESET button once. After that release the BOOT button.

在线下载固件和烧写:esp-launchpad,它使用 USB-Serial-JTAG 接口, 例如在线下载和烧写 esp-box 固件

probe-rs:embassy 使用 probe-rs 来烧写和调试:

  1. https://embassy.dev/book/dev/getting_started.html
  2. https://embassy.dev/book/dev/project_structure.html
+ + + + \ No newline at end of file diff --git a/Rust9.html b/Rust9.html new file mode 100644 index 0000000..cb56f99 --- /dev/null +++ b/Rust9.html @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + 分析 ESP32 固件和分区表 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 分析 ESP32 固件和分区表 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+ +
+

1 使用 esptool.py image_info 查看 app 固件内容 #

参考 app_image_format.html

Copy

esptool.py --chip esp32s3 image_info build/app.bin
+esptool.py v2.3.1
+Image version: 1
+Entry point: 40080ea4
+13 segments
+
+Segment 1: len 0x13ce0 load 0x3f400020 file_offs 0x00000018 SOC_DROM
+Segment 2: len 0x00000 load 0x3ff80000 file_offs 0x00013d00 SOC_RTC_DRAM
+Segment 3: len 0x00000 load 0x3ff80000 file_offs 0x00013d08 SOC_RTC_DRAM
+Segment 4: len 0x028e0 load 0x3ffb0000 file_offs 0x00013d10 DRAM
+Segment 5: len 0x00000 load 0x3ffb28e0 file_offs 0x000165f8 DRAM
+Segment 6: len 0x00400 load 0x40080000 file_offs 0x00016600 SOC_IRAM
+Segment 7: len 0x09600 load 0x40080400 file_offs 0x00016a08 SOC_IRAM
+Segment 8: len 0x62e4c load 0x400d0018 file_offs 0x00020010 SOC_IROM
+Segment 9: len 0x06cec load 0x40089a00 file_offs 0x00082e64 SOC_IROM
+Segment 10: len 0x00000 load 0x400c0000 file_offs 0x00089b58 SOC_RTC_IRAM
+Segment 11: len 0x00004 load 0x50000000 file_offs 0x00089b60 SOC_RTC_DATA
+Segment 12: len 0x00000 load 0x50000004 file_offs 0x00089b6c SOC_RTC_DATA
+Segment 13: len 0x00000 load 0x50000004 file_offs 0x00089b74 SOC_RTC_DATA
+Checksum: e8 (valid)
+Validation Hash: 407089ca0eae2bbf83b4120979d3354b1c938a49cb7a0c997f240474ef2ec76b (valid)

查看 Bootloader Image Format:

Copy

esptool.py --chip esp32s3 image_info build/bootloader/bootloader.bin --version 2
+
+File size: 26576 (bytes)
+
+ESP32 image header
+==================
+Image version: 1
+Entry point: 0x40080658
+Segments: 4
+Flash size: 2MB
+Flash freq: 40m
+Flash mode: DIO
+
+ESP32 extended image header
+===========================
+WP pin: 0xee
+Flash pins drive settings: clk_drv: 0x0, q_drv: 0x0, d_drv: 0x0, cs0_drv: 0x0, hd_drv: 0x0, wp_drv: 0x0
+Chip ID: 0
+Minimal chip revision: v0.0, (legacy min_rev = 0)
+Maximal chip revision: v3.99
+
+Segments information
+====================
+Segment   Length   Load addr   File offs  Memory types
+-------  -------  ----------  ----------  ------------
+    1  0x01bb0  0x3fff0030  0x00000018  BYTE_ACCESSIBLE, DRAM, DIRAM_DRAM
+    2  0x03c90  0x40078000  0x00001bd0  CACHE_APP
+    3  0x00004  0x40080400  0x00005868  IRAM
+    4  0x00f2c  0x40080404  0x00005874  IRAM
+
+ESP32 image footer
+==================
+Checksum: 0x65 (valid)
+Validation hash: 6f31a7f8512f26f6bce7c3b270f93bf6cf1ee4602c322998ca8ce27433527e92 (valid)
+
+Bootloader information
+======================
+Bootloader version: 1
+ESP-IDF: v5.1-dev-4304-gcb51a3b-dirty
+Compile time: Mar 30 2023 19:14:17

2 分区表 #

使用 idf.py partition-table 来显示分区表内容。

使用 gen_esp32part.py 工具来将 CSV 分区表转换为烧写到 FLASH 的 bin 格式。

Copy

# Erase partition with name 'storage'
+parttool.py --port "/dev/ttyUSB1" erase_partition --partition-name=storage
+
+# Read partition with type 'data' and subtype 'spiffs' and save to file 'spiffs.bin'
+parttool.py --port "/dev/ttyUSB1" read_partition --partition-type=data --partition-subtype=spiffs --output "spiffs.bin"
+
+# Write to partition 'factory' the contents of a file named 'factory.bin'
+parttool.py --port "/dev/ttyUSB1" write_partition --partition-name=factory --input "factory.bin"
+
+# Print the size of default boot partition
+parttool.py --port "/dev/ttyUSB1" get_partition_info --partition-boot-default --info size
+ + + + \ No newline at end of file diff --git a/SVoc.html b/SVoc.html new file mode 100644 index 0000000..cab7508 --- /dev/null +++ b/SVoc.html @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + 语音合成 | 413’s Website + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+ +

+ 语音合成 +

+ +
+ + Posted on Wed, Dec 7, 2022 + + + + + Web + + + +
+ +
+

关于人工合成语音&人工合成视频的思考

最近被盗了QQ号,于是有了这篇文章,也是用来去科普一些关于语音合成图像合成的一些知识。

先说一下关于语音合成

💡

语音合成是一种技术,可以将文本转换为语音。它被广泛应用在各种场景中,例如语音助手、自动售货机、电话客服等等。现在最先进的语音合成技术使用神经网络来生成语音,从而使其更接近自然人类语言。

历史上,人类一直在探索如何将文字转化为语音,以便更加方便地传播信息。而随着科技的发展,语音合成技术也得到了极大的提升。从最初的机械式语音合成,到后来的数字化语音合成,再到现在的基于深度学习的语音合成技术,其发展速度可谓飞快。

现在的语音合成技术已经非常成熟,不仅可以模仿人类自然的语音表达方式,还可以通过模拟不同的说话风格、情感和语气来增强语音的表现力。此外,它还具备多语言支持和智能性,可以根据特定场景和用户需求进行定制和优化。

由于这种技术的发展和应用,现在甚至可以使用语音合成技术代替文本阅读的全部工作,包括电子书籍、新闻报道、商业文件、通知公告等内容的朗读。这无疑会给人们的生活带来很大的便利和舒适。

关于语音合成的一些素材:

关于虚拟歌姬:

【DiffSinger】《一半一半》开源引擎多说话人模型与声线融合技术展示_哔哩哔哩_bilibili

DiffSinger多说话人模型与声线融合技术现已开放!即日起与亲友伙伴相约,跟随开源教程与代码制作多人声库,即可享受由多人模型为每一位演唱者带来的共同增益,同时开启音色与声线自由融合的全新玩法;对于唱腔多变的专业演唱者,多人模型可实现任意种声线和唱法的自由融合与过渡,极大提高声库上限。视频中使用的模型在总量约12小时的opencpop、绮萱、夏叶子三人女声数据集上使用单张V100显卡训练320k, 视频播放量 100405、弹幕量 100、点赞数 6160、投硬币枚数 3368、收藏人数 6908、转发人数 1956, 视频作者 YQ之神, 作者简介 技术至上,自由至上,相关视频:【DiffSinger】《我多想说再见啊》开源引擎高音质歌声合成效果展示,ACE Studio 1.7.0 更新丨新模型、声线混合、10+新歌手、所有歌手中日英三语开放,《三分钟上手DiffSinger》系列 |基础篇,DiffSinger精标和mfa对比,效果太明显了!,【DiffSingerV2】 基础教程演示(一)前言,Diffsinger+openUTAU+RipX+AI歌姬,跟着教程一步步本地部署合成音频,新手不熟练,多多指教,【真·无参 高表现力 DiffSinger自动音高,让AI歌手真正实现自己唱歌!】不谓侠【挚彬说这是他欠粉丝的歌】,【枫聆月Lyria✧DiffSinger新声库试听】寻遍星空【内测开启+新形象『星熠』公开】【莳光旅客Project】,往阿梓音色里掺一点七海是什么效果?AI变声器音色融合效果展示,AI海梓翻唱《冬之花》,【DiffSinger声线动态融合】《左手指月》-开源歌手· [Opencpop+绮萱]

当然,除了使用现有唱片和音频资源进行训练外,您还可以采集自己的声音,并将其用于模型训练。此外,您甚至可以保存已故亲人的声音,让他们的声音永存于世,并将其用于模型训练。

训练数据不仅限于歌唱说话,您也可以使用它来学习其他语言。这使得该技术不仅在音乐产业中有用,也可以被应用于各种领域,如语音识别机器翻译等等。无论用途如何,这项技术都为我们提供了一个强大而多功能的工具,可以更好地理解并处理声音数据。

确实,如果这项技术被不道德或恶意的人滥用,后果将是不堪设想的。例如,有些人可能利用它来伪造声音文件和视频,并制造虚假的证据。这种滥用可能会导致无辜者受到指控或损失信誉。

QQ被盗如何解决

  1. 冻结账号:如果您的QQ号被盗,第一时间要冻结账号以防止进一步的损失。可以通过微信登录QQ并在设置中进行账号冻结。
  2. 广而告之:及时告知您的朋友和联系人,让他们知道您的QQ号已经被盗。这样可以避免黑客利用您的身份进行诈骗等行为。
  3. 总结经验:在处理了QQ号被盗事件后,应该总结经验,思考如何加强账号保护措施,例如启用双重验证、不使用弱密码、定期更改密码等。

关于图像合成

图像合成技术在过去几年得到了长足的发展。这种技术通过将多个图像或视频片段组合在一起,以创建新的视觉效果。它可以用于各种应用程序,例如特效制作、虚拟现实和增强现实。

图像合成技术的发展离不开人工智能和机器学习的进步。利用深度学习和计算机视觉技术,能够让计算机自动地学习如何识别和处理图像数据,并生成高质量的合成图像。

目前,图像合成技术已经被广泛应用于影视制作、游戏开发、医疗成像等领域。

资源链接

ACE Studio

关于ACE_Studio-从多维度AI演唱参数到高质量的合成效果,我们使用毫不妥协的全流程AI技术,不断追求提升虚拟歌手的歌声自然度与表现力

SV

逼真无暇的歌声 弹指可得 顷刻呈现

DiffSinger

自制声库教程

Github开源地址

+ + + + \ No newline at end of file diff --git a/azure-openai.html b/azure-openai.html new file mode 100644 index 0000000..002f575 --- /dev/null +++ b/azure-openai.html @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + Azure openai 遇到注意事项 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ Azure openai 遇到注意事项 +

+ +
+ + Posted on Wed, Jul 3, 2024 + + + + + openai + + + +
+ +
+

使用Azure 的Openai 调用的时候需要注意的的一些地方

比如

model="gpt-4o"

api_version="2024-02-15-preview"

model 一般为你部署的名字通常是azure_endpoint https://xxxx.openai.azure.com/xxx 的名字

api_version 并不是 gpt的版本

改动后前

from openai import AzureOpenAI
+
+
+client = AzureOpenAI(
+  azure_endpoint = "https://XXXX.openai.azure.com/", 
+  api_key="XXXXX",  
+  api_version="2024-05-13"
+)
+
+
+message_text = [{"role":"system","content":"You are an AI assistant that helps people find information."},{"role":"user","content":"Was ist 4x6?"}]
+
+completion = client.chat.completions.create(
+  model="GPT-4o", # model = "deployment_name"
+  messages = message_text,
+  temperature=0.7,
+  max_tokens=800,
+  top_p=0.95,
+  frequency_penalty=0,
+  presence_penalty=0,
+  stop=None
+)
+
+print(completion.choices[0].message.content)

改动后

from openai import AzureOpenAI
+
+client = AzureOpenAI(
+  azure_endpoint = "https://swcgpt4.openai.azure.com/",
+  api_key="XXXX",
+  api_version="2024-02-15-preview"
+)
+
+
+message_text = [{"role":"system","content":"You are an AI assistant that helps people find information."},{"role":"user","content":"Was ist 4x6?"}]
+
+completion = client.chat.completions.create(
+  model="SWgpt40", # model = "deployment_name"
+  messages = message_text,
+  temperature=0.7,
+  max_tokens=800,
+  top_p=0.95,
+  frequency_penalty=0,
+  presence_penalty=0,
+  stop=None
+)
+
+print(completion.choices[0].message.content)

参考

stackoverflow同样的问题

微软文档说明

+ + + + \ No newline at end of file diff --git a/css/SourceSansPro.css b/css/SourceSansPro.css new file mode 100644 index 0000000..058437e --- /dev/null +++ b/css/SourceSansPro.css @@ -0,0 +1,54 @@ +/* source-sans-pro-regular - latin 源代码-无衬线-常规-拉丁 */ +@font-face { /* 字体 */ + font-family: 'Source Sans Pro'; /* 字体系列 */ + font-style: normal; /* 字体样式 */ + font-weight: 400; /* 字体粗细 */ + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), /* 字体源 */ + url('../fonts/source-sans-pro-v13-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} +/* source-sans-pro-600 - latin 源代码-无衬线-600-拉丁 */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'), + url('../fonts/source-sans-pro-v13-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} +/* source-sans-pro-600italic - latin 源代码-无衬线-600-斜体-拉丁 */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 600; + src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'), + url('../fonts/source-sans-pro-v13-latin-600italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-600italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} +/* source-sans-pro-italic - latin 源代码-无衬线-斜体-拉丁 */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 400; + src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'), + url('../fonts/source-sans-pro-v13-latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} +/* source-sans-pro-700 - latin 源代码-无衬线-700-拉丁 */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), + url('../fonts/source-sans-pro-v13-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} +/* source-sans-pro-700italic - latin 源代码-无衬线-700-斜体-拉丁 */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: italic; + font-weight: 700; + src: local('Source Sans Pro Bold Italic'), local('SourceSansPro-BoldItalic'), + url('../fonts/source-sans-pro-v13-latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ + url('../fonts/source-sans-pro-v13-latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} \ No newline at end of file diff --git a/css/notablog.css b/css/notablog.css new file mode 100644 index 0000000..5eef1a5 --- /dev/null +++ b/css/notablog.css @@ -0,0 +1,329 @@ +:root { + --bg:rgb(255, 255, 255); + --bg-blur: rgba(250, 250, 246, 0.4); +} +/* +这段CSS代码定义了根元素的背景颜色和带模糊效果的导航栏背景颜色。具体来说,--bg变量定义了网站主要背景颜色为RGB值(250, 250, 246), +而--bg-blur变量定义了带模糊效果的导航栏背景颜色为RGBA值(250, 250, 246, 0.4)。 +最后,body选择器使用var()函数将--bg变量应用于整个网站的主要背景。如果您想要更改网站的背景颜色,您可以在此文件中编辑--bg变量的值为所需的颜色即可*/ + +body { /* Body */ + background: var(--bg); /* 使用var()函数将--bg变量应用于整个网站的主要背景 */ +} + + /* Navbar */ /* 导航栏 */ + + .Navbar { /* 导航栏 */ + display: flex; /* 将导航栏的子元素排成一行 */ + align-items: center; /* 将导航栏的子元素垂直居中 */ + font-size: 16px; /* 导航栏的字体大小 */ + position: fixed; /* 将导航栏固定在屏幕上方 */ + top: 0; /* 将导航栏固定在屏幕上方 */ + left: 0; /* 将导航栏固定在屏幕上方 */ + z-index: 999; /* 将导航栏固定在屏幕上方 */ + width: 100%; /* 将导航栏的宽度设置为100% */ + height: 50px; /* 将导航栏的高度设置为50px */ + padding: 10px 20px; /* 将导航栏的内边距设置为10px 20px */ + /* Background blur stuff. */ /* 背景模糊效果 */ + background-color: var(--bg-blur); /* 将导航栏的背景颜色设置为--bg-blur变量的值 */ + -webkit-backdrop-filter: blur(20px); /* 使用-webkit-backdrop-filter属性为导航栏添加背景模糊效果 */ + backdrop-filter: blur(20px); /* 使用backdrop-filter属性为导航栏添加背景模糊效果 */ + overflow-x: auto; /* 将导航栏的子元素溢出部分设置为自动 */ +} + +.Navbar a { /* 导航栏的链接 */ + color: inherit; /* 将导航栏的链接的颜色设置为继承 */ + text-decoration: none; /* 将导航栏的链接的文本装饰设置为无 */ +} + +.Navbar a:last-child > .Navbar__Btn { /* 导航栏的最后一个链接的子元素 */ + /* .nav padding 20px */ /* 将导航栏的内边距设置为20px */ + margin-right: 20px; /* 将导航栏的外边距设置为20px */ +} + +.Navbar__Btn { /* 导航栏的按钮 */ + border-radius: 0.25rem; /* 将导航栏的按钮的边框半径设置为0.25rem */ + padding: 0 6px; /* 将导航栏的按钮的内边距设置为0 6px */ + line-height: 1.8125; /* 将导航栏的按钮的行高设置为1.8125 */ + transition: background 30ms ease-in-out 0s; /* 将导航栏的按钮的背景颜色的过渡效果设置为30ms ease-in-out 0s */ + /* For centering inline image */ /* 用于居中内联图像 */ + display: flex; /* 将导航栏的按钮的子元素排成一行 */ + align-items: center; /* 将导航栏的按钮的子元素垂直居中 */ +} + +.Navbar__Btn:hover { /* 导航栏的按钮的鼠标悬停效果 */ + background: rgba(55, 53, 47, 0.08); +} + +.Navbar__Btn:active { /* 导航栏的按钮的鼠标按下效果 */ + background: rgba(55, 53, 47, 0.16); +} + +.Navbar__Btn > span { /* 导航栏的按钮的子元素 */ + white-space: nowrap; /* 将导航栏的按钮的子元素的空白设置为不换行 */ +} + +.Navbar__Btn > span:not(:first-child) { /* 导航栏的按钮的子元素的非第一个子元素 */ + margin-left: 6px; +} + +.Navbar__Delim { /* 导航栏的分隔符 */ + margin: 0 3px; + color: rgba(55, 53, 47, 0.4); +} + +/* Common */ + +.Header, +.PageRoot, +.ArticleList { /* 头部,页面根,文章列表 */ + /* width: 900px; */ + width: 945px; + max-width: 100%; + margin: 0px auto; + padding: 0px 96px; +} + +/* For mobile devices */ /* 用于移动设备 */ +@media only screen and (max-width: 900px) { + .Header, + .PageRoot, + .ArticleList { + max-width: 87.5%; + padding-left: 0px; + padding-right: 0px; + } +} + +/* Header */ + +.Header a { /* 头部的链接 */ + color: inherit; /* 将头部的链接的颜色设置为继承 */ + text-decoration: none; /* 将头部的链接的文本装饰设置为无 */ +} + +.Header__Cover { /* 头部的封面 */ + position: absolute; /* 将头部的封面的位置设置为绝对定位 */ + top: 0; + right: 0; + /* Firefox need this while Chrome doesn't. */ /* Firefox需要这个,而Chrome不需要。 */ + left: 0; + z-index: -1; +} + +.Header__Cover > img { /* 头部的封面的图片 */ + height: calc(30vh + 50px); /* 将头部的封面的图片的高度设置为calc(30vh + 50px) */ + width: 100%; + object-fit: cover; /* 将头部的封面的图片的对象适配设置为覆盖 */ +} + +.Header__Spacer { /* 头部的间距 */ + margin-top: 30vh; +} + +.Header__Spacer--NoCover { /* 头部的间距--无封面 */ + margin-top: calc(50px + 2.5rem); +} + +.Header__Icon { /* 头部的图标 */ + line-height: 1.1; /* 将头部的图标的行高设置为1.1 */ + /* On Android Firefox, emoji can't be displayed if font-size > 77px. */ /* 在Android Firefox上,如果字体大小大于77px,则无法显示表情符号。 */ + font-size: 75px; +} + +.Header__Title { /* 头部的标题 */ + /* margin-top: 2.5rem; */ + margin-top: 1.5rem; + margin-bottom: 0.25em; + line-height: 1.2; + font-size: 2.625rem; + font-weight: 700; + letter-spacing: -0.005em; +} + +.Header > *:last-child { /* 头部的最后一个子元素 */ + margin-bottom: 2.5rem; +} + +.Header__DescBigQuoteMark { /* 头部的大引号 */ + font-size: 1.5em; + line-height: 0; +} + +/* DateTagBar */ + +.DateTagBar { /* 日期标签栏 */ + font-size: 0.9rem; + line-height: 1.2; + display: flex; + flex-wrap: wrap; +} + +.DateTagBar__Item { /* 日期标签栏的项目 */ + margin-right: 8px; + margin-bottom: 8px; + padding: 2px 6px; + border-radius: 0.25rem; +} + +.DateTagBar__Date { /* 日期标签栏的日期 */ + color: hsla(45, 2%, 40%, 1); + padding-left: 0px; + padding-right: 8px; +} + +.DateTagBar__Tag { /* 日期标签栏的标签 */ + font-size: 0.85rem; + white-space: nowrap; +} + +.DateTagBar__Tag--default { /* 日期标签栏的标签--默认 */ + color: rgb(50, 48, 44); + background: rgba(227, 226, 224, 0.5); +} + +.DateTagBar__Tag--gray { /* 日期标签栏的标签--灰色 */ + color: rgb(50, 48, 44); + background: rgb(227, 226, 224); +} + +.DateTagBar__Tag--brown { /* 日期标签栏的标签--棕色 */ + color: rgb(68, 42, 30); + background: rgb(238, 224, 218); +} + +.DateTagBar__Tag--orange { + color: rgb(73, 41, 14); + background: rgb(250, 222, 201); +} + +.DateTagBar__Tag--yellow { + color: rgb(64, 44, 27); + background: rgb(253, 236, 200); +} + +.DateTagBar__Tag--green { + color: rgb(28, 56, 41); + background: rgb(219, 237, 219); +} + +.DateTagBar__Tag--blue { + color: rgb(24, 51, 71); + background: rgb(211, 229, 239); +} + +.DateTagBar__Tag--purple { + color: rgb(65, 36, 84); + background: rgb(232, 222, 238); +} + +.DateTagBar__Tag--pink { + color: rgb(76, 35, 55); + background: rgb(245, 224, 233); +} + +.DateTagBar__Tag--red { + color: rgb(93, 23, 21); + background: rgb(255, 226, 221); +} + +.DateTagBar__Tag > a { /* 日期标签栏的标签的链接 */ + border: none; /* 将日期标签栏的标签的链接的边框设置为无 */ +} + +/* Article */ + +.Article { /* 文章 */ + margin: 2rem 0; /* 将文章的外边距设置为2rem 0 */ +} + +.Article a { /* 文章的链接 */ + color: inherit; + text-decoration: none; +} + +.Article__Title { /* 文章的标题 */ + margin: 0; + padding-bottom: 0.5rem; + line-height: 1.5; /* 将文章的标题的行高设置为1.5 */ + font-size: 1.4rem; /* 将文章的标题的字体大小设置为1.4rem */ + font-weight: 600; /* 将文章的标题的字体粗细设置为600 */ + letter-spacing: -0.01em; /* 将文章的标题的字母间距设置为-0.01em */ +} + +.Article__Title > a { /* 文章的标题的链接 */ + border-bottom: 2px solid hsl(45, 8%, 85%); /* 将文章的标题的链接的下边框设置为2px solid hsl(45, 8%, 85%) */ +} + +.Article__Title > a:not(:first-child) { /* 文章的标题的链接的非第一个子元素 */ + margin-left: 5px; +} + +.Article__Title > a:hover { /* 文章的标题的链接的鼠标悬停效果 */ + border-bottom: 2px solid hsl(45, 8%, 55%); /* 将文章的标题的链接的下边框设置为2px solid hsl(45, 8%, 55%) */ +} + +.Article__Desc { /* 文章的描述 */ + padding-bottom: 0.5rem; +} + +/* PageRoot (content body) */ + +.PageRoot { /* 页面根 */ + padding-bottom: 0; + padding-top: 0; +} + +/* Footer */ + +.Footer { /* 页脚 */ + display: flex; /* 将页脚的子元素排成一行 */ + flex-wrap: wrap; /* 将页脚的子元素换行 */ + align-items: center; /* 将页脚的子元素垂直居中 */ + justify-content: center; /* 将页脚的子元素水平居中 */ + font-size: 14px; + padding: 5rem 3rem; + color: rgba(55, 53, 47, 0.6); +} + +.Footer > div:nth-child(2) { /* 页脚的第二个子元素 */ + margin: 0 3px; +} + +.Footer a { /* 页脚的链接 */ + color: rgba(55, 53, 47); +} + + +@media only screen and (max-width: 680px) { /* 用于移动设备 */ + * { + -webkit-tap-highlight-color: transparent; + } + .Navbar { + box-shadow: rgba(15, 15, 15, 0.1) 0px 1px 0px, transparent 0px 0px 0px; + } + .Navbar__Btn { + font-size: 14px; + } + .Header__Icon { + font-size: 55px; + } + .Header__Cover > img { + height: calc(30vh + 30px); + } +} + +/* 用于不支持背景模糊效果的浏览器 */ +@supports (not (backdrop-filter: blur(20px))) and + (not (-webkit-backdrop-filter: blur(20px))) { + .Navbar { + background-color: var(--bg); + } +} + +.inline-img-icon { /* 内联图像图标 */ + height: 1.2em; + /* Setting width prevents content shifting after image loaded. */ + width: 1.2em; + vertical-align: sub; +} diff --git a/css/theme.css b/css/theme.css new file mode 100644 index 0000000..61de098 --- /dev/null +++ b/css/theme.css @@ -0,0 +1,1164 @@ +/* Primitives */ + +* { + box-sizing: border-box; /* 使得元素的宽度和高度包括内边距和边框,但不包括外边距 */ +} + +*::selection { /* 选中文本的样式 */ + background: rgba(45, 170, 219, 0.3); /* 选中文本的背景色 */ +} + +:root { /* 根元素 */ + --color-text-default: rgb(55, 53, 47); + --color-text-default-light: rgba(55, 53, 47, 0.6); + --color-text-gray: rgb(155, 154, 151); + --color-text-brown: rgb(100, 71, 58); + --color-text-orange: rgb(217, 115, 13); + --color-text-yellow: rgb(223, 171, 1); + --color-text-green: rgb(15, 123, 108); + --color-text-blue: rgb(11, 110, 153); + --color-text-purple: rgb(105, 64, 165); + --color-text-pink: rgb(173, 26, 114); + --color-text-red: rgb(224, 62, 62); + --color-bg-default: rgb(255, 255, 255); + --color-bg-gray: rgb(235, 236, 237); + --color-bg-gray-light: rgba(235, 236, 237, 0.3); + --color-bg-brown: rgb(233, 229, 227); + --color-bg-brown-light: rgba(233, 229, 227, 0.3); + --color-bg-orange: rgb(250, 235, 221); + --color-bg-orange-light: rgba(250, 235, 221, 0.3); + --color-bg-yellow: rgb(251, 243, 219); + --color-bg-yellow-light: rgba(251, 243, 219, 0.3); + --color-bg-green: rgb(221, 237, 234); + --color-bg-green-light: rgba(221, 237, 234, 0.3); + --color-bg-blue: rgb(221, 235, 241); + --color-bg-blue-light: rgba(221, 235, 241, 0.3); + --color-bg-purple: rgb(234, 228, 242); + --color-bg-purple-light: rgba(234, 228, 242, 0.3); + --color-bg-pink: rgb(244, 223, 235); + --color-bg-pink-light: rgba(244, 223, 235, 0.3); + --color-bg-red: rgb(251, 228, 228); + --color-bg-red-light: rgba(251, 228, 228, 0.3); + --color-pill-default: rgba(206, 205, 202, 0.5); + --color-pill-gray: rgba(155, 154, 151, 0.4); + --color-pill-brown: rgba(140, 46, 0, 0.2); + --color-pill-orange: rgba(245, 93, 0, 0.2); + --color-pill-yellow: rgba(233, 168, 0, 0.2); + --color-pill-green: rgba(0, 135, 107, 0.2); + --color-pill-blue: rgba(0, 120, 223, 0.2); + --color-pill-purple: rgba(103, 36, 222, 0.2); + --color-pill-pink: rgba(221, 0, 129, 0.2); + --color-pill-red: rgba(255, 0, 26, 0.2); + --color-ui-hover-bg: rgba(55, 53, 47, 0.08); + --column-spacing: 46px; +} + +body { /* body元素 */ + color: var(--color-text-default); /* 文本颜色 */ + fill: currentColor; /* 填充颜色 */ + font-family: 'Source Sans Pro', -apple-system, 'BlinkMacSystemFont', + 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + margin: 0; +} + +code { /* code元素 */ + /* Use monospace before Courier so font looks better on Android Chrome. */ + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace, + Courier; +} + +p { /* p元素 */ + margin: 0; +} + +/* Anchor */ + +.Anchor { /* 锚点 */ + color: inherit; /* 颜色 */ + text-decoration: none; /* 文本装饰 */ + padding-right: 4px; /* 右边距 */ + margin-left: -20px; + visibility: hidden; +} + +/* Audio */ + +.Audio { /* 音频 */ + width: 100%; +} + +/* Bookmark */ + +.Bookmark { /* 书签 */ + margin: 4px 0; + border: 1px solid rgba(55, 53, 47, 0.16); + border-radius: 5px; + padding: 12px 14px 14px; + transition: background 120ms ease-in 0s; +} + +.Bookmark:hover { /* 鼠标悬停效果 */ + background: var(--color-ui-hover-bg); +} + +.Bookmark > a { /* 书签的链接 */ + color: inherit; + text-decoration: none; +} + +.Bookmark__Title { /* 书签的标题 */ + margin: 0; + margin-bottom: 2px; + font-size: 0.875rem; + font-weight: normal; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.Bookmark__Desc { /* 书签的描述 */ + margin: 0; + font-size: 0.75rem; + line-height: 1rem; + opacity: 0.6; + height: 2rem; + overflow: hidden; +} + +.Bookmark__Link { /* 书签的链接 */ + margin: 0; + margin-top: 6px; + font-size: 0.75rem; + line-height: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* BulletedList */ + +.BulletedListWrapper { /* 无序列表 */ + margin: 2px 0; + padding-left: calc(1.5em + 4px); + line-height: 1.5; + list-style-type: disc; +} + +.BulletedList { /* 无序列表 */ + margin: 2px 0; + padding-top: 3px; + padding-bottom: 3px; +} + +/* Callout */ + +.Callout { /* Callout */ + display: flex; + border-radius: 5px; + padding: 16px 16px 16px 12px; + margin: 4px 0; + background: var(--color-bg-gray-light); +} + +.Callout__Icon { /* Callout的图标 */ + width: 1.5em; + line-height: 1.5em; +} + +.Callout__Content { /* Callout的内容 */ + margin-left: 8px; +} + +/* Code */ + +pre.Code { /* 代码 */ + border-radius: 5px; + background-color: rgb(247, 246, 243); + margin: 10px 0; + padding: 26px 16px; + overflow: auto; +} + +pre.Code code { /* 代码 */ + background-color: rgb(247, 246, 243); + color: rgb(55, 53, 47); + /* font-size: 0.9em; */ + font-size: 0.85em; + line-height: 1.4; + padding: 0; + word-wrap: break-word; /* 使得单词能够换行 */ + tab-size: 2; +} + +.Code.Code--NoWrap .SemanticString { /* 代码--不换行 */ + white-space: pre; +} + +/* Collection */ + +.CollectionInline > h3 { /* 内联集合的标题 */ + line-height: 1.75; + font-size: 1.25rem; + font-weight: 700; + margin-top: 0.5rem; + margin-bottom: 1px; + padding: 3px 2px; +} + +.CollectionInline > h3:hover > .Anchor { /* 内联集合的标题 */ + visibility: visible; +} + +.Table { /* 表格 */ + overflow-x: auto; +} + +.Table > table { /* 表格 */ + width: 100%; + font-size: 0.875rem; + /* No double border */ + border-collapse: collapse; + /* Force the table respects widths set on */ + table-layout: fixed; + /* Make space between table and scrollbar */ + margin-bottom: 10px; +} + +.Table > table td, /* 表格的单元格 */ +.Table > table th { + /* For and unknown */ + padding: 0 8px; + height: 2rem; + border: 1px solid rgba(55, 53, 47, 0.09); + overflow: hidden; + word-break: break-word; +} + +.Table > table td:first-child, /* 表格的单元格 */ +.Table > table th:first-child { + border-left: none; +} + +.Table > table td:last-child, /* 表格的单元格 */ +.Table > table th:last-child { + border-right: none; +} + +.Table > table th { /* 表格的表头 */ + text-align: left; + font-weight: normal; + color: var(--color-text-default-light); + /* Default width, which may be overridden by inline style attr. */ + width: 200px; +} + +.Table__CellTitle > a { /* 表格的单元格的标题的链接 */ + color: inherit; + text-decoration: none; + border-bottom: 2px solid hsl(45, 8%, 85%); +} + +.Table__CellTitle > a:hover { /* 表格的单元格的标题的链接的鼠标悬停效果 */ + border-bottom: 2px solid hsl(45, 8%, 55%); +} + +.Table__CellText { /* 表格的单元格的文本 */ + padding: 6px 8px; + line-height: 1.5; +} + +.Table__CellSelect { /* 表格的单元格的选择 */ + /* 3px + 3px (margin of span) = 6px (desired) */ + padding: 3px 8px; +} + +.Table__CellCheckbox { /* 表格的单元格的复选框 */ + padding: 6px 8px; +} + +.Table__CellCheckbox > div { /* 表格的单元格的复选框的div */ + display: flex; + align-items: center; + justify-content: center; + width: 1rem; + height: 1rem; +} + +.Table__CellCheckbox--No svg { /* 表格的单元格的复选框--否 */ + width: 100%; + height: 100%; + display: block; + flex-shrink: 0; + backface-visibility: hidden; +} + +.Table__CellCheckbox--Yes svg { /* 表格的单元格的复选框--是 */ + width: 12px; + height: 12px; + display: block; + fill: white; + flex-shrink: 0; + backface-visibility: hidden; +} + +.Gallery { /* 图库 */ + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + grid-auto-rows: 1fr; + gap: 16px; + border-top: 1px solid rgba(55, 53, 47, 0.16); + padding-top: 16px; + padding-bottom: 4px; +} + +.Gallery__Item { /* 图库的项目 */ + box-shadow: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, + rgba(15, 15, 15, 0.1) 0px 2px 4px; + border-radius: 5px; + transition: background 120ms ease-in 0s; + position: relative; + /* 10:7 */ + /* padding-top: 70%; */ +} + +.Gallery__Item:hover { /* 图库的项目的鼠标悬停效果 */ + background: rgba(55, 53, 47, 0.03); +} + +.Gallery__Item > a { /* 图库的项目的链接 */ + color: inherit; + text-decoration: none; +} + +/* .Gallery__Item>a>div { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} */ + +/* This is the key to make the layout robust. */ + +/* .Gallery__Item>a>div>div { + grid-item-title has padding-top 8px, padding-bottom 10px, and its line-height. + Since we can not get line-height with CSS, so just assign a large enough value. + height: calc(100% - 18px - 2rem); +} */ + +.Gallery__Item__Cover { /* 图库的项目的封面 */ + box-shadow: rgba(55, 53, 47, 0.09) 0px -1px 0px 0px inset; + height: 200px; +} + +.Gallery__Item__Cover > img { /* 图库的项目的封面的图片 */ + width: 100%; + height: 100%; + max-height: 200px; + border-radius: 5px 5px 0 0; + object-fit: cover; + object-position: center 50%; + padding-bottom: 1px; +} + +.Gallery__Item__Cover--Contain > img { /* 图库的项目的封面--包含的图片 */ + object-fit: contain; +} + +.Gallery__Item__Title { /* 图库的项目的标题 */ + padding: 8px 10px 6px; + overflow: hidden; + text-overflow: ellipsis; +} + +.Gallery__Item__Title .SemanticString { /* 图库的项目的标题的语义字符串 */ + white-space: nowrap; +} + +.Gallery__Item__Property { /* 图库的项目的属性 */ + display: flex; + align-items: center; + font-size: 0.75rem; + height: 1.5rem; + white-space: nowrap; + overflow: hidden; + padding: 0px 10px; +} + +.Gallery__Item__Property:last-child { /* 图库的项目的属性的最后一个子元素 */ + margin-bottom: 10px; +} + +.Gallery__Item__PropertyText .SemanticString { /* 图库的项目的属性的文本的语义字符串 */ + white-space: nowrap; +} + +@supports not (display: grid) { /* 不支持网格布局 */ + .Gallery__Item { + margin-top: 16px; + } +} + +/* ColorfulBlock */ + +.ColorfulBlock--ColorDefault { /* 彩色块--默认颜色 */ + color: var(--color-text-default); +} + +.ColorfulBlock--ColorGray { /* 彩色块--灰色 */ + color: var(--color-text-gray); +} + +.ColorfulBlock--ColorBrown { + color: var(--color-text-brown); +} + +.ColorfulBlock--ColorOrange { + color: var(--color-text-orange); +} + +.ColorfulBlock--ColorYellow { + color: var(--color-text-yellow); +} + +.ColorfulBlock--ColorGreen { + color: var(--color-text-green); +} + +.ColorfulBlock--ColorBlue { + color: var(--color-text-blue); +} + +.ColorfulBlock--ColorPurple { + color: var(--color-text-purple); +} + +.ColorfulBlock--ColorPink { + color: var(--color-text-pink); +} + +.ColorfulBlock--ColorRed { + color: var(--color-text-red); +} + +.ColorfulBlock--BgDefault { /* 彩色块--默认背景色 */ + background: var(--color-bg-default); +} + +.ColorfulBlock--BgGray { /* 彩色块--灰色背景色 */ + background: var(--color-bg-gray); +} + +.ColorfulBlock--BgBrown { + background: var(--color-bg-brown); +} + +.ColorfulBlock--BgOrange { + background: var(--color-bg-orange); +} + +.ColorfulBlock--BgYellow { + background: var(--color-bg-yellow); +} + +.ColorfulBlock--BgGreen { + background: var(--color-bg-green); +} + +.ColorfulBlock--BgBlue { + background: var(--color-bg-blue); +} + +.ColorfulBlock--BgPurple { + background: var(--color-bg-purple); +} + +.ColorfulBlock--BgPink { + background: var(--color-bg-pink); +} + +.ColorfulBlock--BgRed { + background: var(--color-bg-red); +} + +/* ColumnList */ + +.ColumnList { /* 列表 */ + display: flex; + flex-wrap: wrap; +} + +.Column { /* 列 */ + padding: 12px 0; + word-break: break-word; +} + +.Column:not(:first-child) { /* 列 */ + margin-left: var(--column-spacing); +} + +@media only screen and (max-width: 680px) { + .Column { + width: 100% !important; + margin-left: 0 !important; + } +} + +/* Divider */ + +.Divider { /* 分割线 */ + width: 100%; + border: 1px solid rgba(55, 53, 47, 0.09); +} + +.Divider2 { /* 分割线2 */ + width: 100%; + height: calc(1.5rem + 10px); + color: #1f2225; + background-image: linear-gradient( + to right, + rgb(31, 34, 37) 25%, + rgb(255, 255, 255) 0% + ); + background-position: left center; + background-size: 6px 1px; + background-repeat: repeat-x; +} + +/* Embed */ + +.Embed__Content { /* 嵌入的内容 */ + display: flex; +} + +.Embed__Caption { /* 嵌入的标题 */ + padding: 6px 2px; + color: var(--color-text-default-light); + font-size: 0.875em; +} + +.Embed__ResponsiveContainer { /* 嵌入的响应式容器 */ + position: relative; + min-height: 100px; + height: 0; + margin: 0 auto; +} + +.Embed__ResponsiveContainer > iframe { /* 嵌入的响应式容器的iframe */ + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + border: none; + border-radius: 1px; + pointer-events: auto; + background-color: rgb(247, 246, 245); +} + +/* Equation */ + +.Equation { /* 公式 */ + margin: 4px 0; + padding: 4px 8px; +} + +/* File */ + +.File { /* 文件 */ + color: inherit; + text-decoration: none; +} + +.File > div { /* 文件的div */ + display: flex; + padding: 5px 0; + margin: 2px 0; + border-radius: 5px; + transition: background 120ms ease-in 0s; +} + +.File > div:hover { /* 文件的div的鼠标悬停效果 */ + background: var(--color-ui-hover-bg); +} + +.File__Icon { /* 文件的图标 */ + margin-left: 2px; + margin-right: 4px; + width: 1.5em; + text-align: center; +} + +.File__Title { /* 文件的标题 */ + line-height: 1.5; +} + +.File__Size { /* 文件的大小 */ + margin-left: 6px; + color: var(--color-text-default-light); + font-size: 0.75em; +} + +/* Heading */ + +.Heading { /* 标题 */ + margin-bottom: 1px; + padding: 3px 2px; +} + +/* Font-related CSS should be applied on ".SemanticString". */ +.Heading .SemanticString { /* 标题的语义字符串 */ + font-weight: 600; + letter-spacing: -0.01em; +} + +.Heading:hover > .Anchor { /* 标题的鼠标悬停效果 */ + visibility: visible; +} + +.Heading--1 .SemanticString { /* 标题1的语义字符串 */ + font-size: 2.0625rem; + line-height: 1.515; +} + +.Heading--2 { /* 标题2 */ + margin-top: 1rem; +} + +.Heading--2 .SemanticString { /* 标题2的语义字符串 */ + font-size: 1.625rem; + line-height: 1.538; +} + +.Heading--3 { + margin-top: 0.5rem; +} + +.Heading--3 .SemanticString { + font-size: 1.25rem; + line-height: 1.55; +} + +.Heading--4 .SemanticString { + font-size: 1rem; + line-height: 1.563; +} + +.Heading--5 .SemanticString { + font-size: 0.8125rem; + line-height: 1.615; + color: #888; +} + +.Column > .Heading:first-child { /* 列的第一个子元素的标题 */ + margin-top: 2px; +} + +/* Icon */ + +.Icon { /* 图标 */ + /* For emoji */ + text-align: center; + /* For image */ + border-radius: 3px; +} + +/* Image */ + +.Image { /* 图片 */ + margin-top: 0.5em; + margin-bottom: 0.5em; + align-self: center; +} + +.Image--FullWidth { /* 图片--全宽 */ + width: calc(100vw - 15px); +} + +.Image--Normal, /* 图片--普通 */ +.Image--PageWidth { + max-width: 100%; +} + +.Image--Normal { /* 图片--普通 */ + text-align: center; +} + +.Image--PageWidth { /* 图片--页面宽度 */ + width: 100%; +} + +.Image > figure { /* 图片的figure */ + margin: 0; +} + +.Image > figure > figcaption { /* 图片的figure的figcaption */ + color: var(--color-text-default-light); + font-size: 0.875rem; + text-align: left; +} + +.Image--FullWidth > figure img { + /* 15px is scrollbar */ + width: calc(100vw - 15px); + object-fit: cover; +} + +.Image--FullWidth > figure > figcaption { + padding: 6px 26px; +} + +.Image--Normal > figure img, +.Image--PageWidth > figure img { + max-width: 100%; + object-fit: contain; +} + +.Image--Normal > figure > figcaption, +.Image--PageWidth > figure > figcaption { + padding: 6px 2px; +} + +/* NumberedList */ + +.NumberedListWrapper { /* 有序列表 */ + margin: 2px 0; + padding-left: calc(1.5em + 4px); + line-height: 1.5; +} + +.NumberedList { /* 有序列表 */ + margin: 2px 0; + padding-top: 3px; + padding-bottom: 3px; +} + +/* Page */ + +.PageRoot { /* 页面根元素 */ + display: flex; + flex-direction: column; +} + +.PageRoot--FullWidth { + width: 100%; +} + +.Page { /* 页面 */ + color: inherit; + text-decoration: none; +} + +.Page > div { /* 页面的div */ + display: flex; + padding: 3px 0; + margin: 2px 0; + border-radius: 5px; + transition: background 120ms ease-in 0s; +} + +.Page > div:hover { /* 页面的div的鼠标悬停效果 */ + background: var(--color-ui-hover-bg); +} + +.Page__Icon { /* 页面的图标 */ + display: flex; + align-items: center; + justify-content: center; + width: 1.5em; + margin-left: 2px; + margin-right: 4px; +} + +.Page__Title .SemanticStringArray { + border-bottom: 1px solid rgba(55, 53, 47, 0.16); +} + +/* PDF */ + +.PDF__Content { /* PDF的内容 */ + text-align: center; +} + +.PDF__Content > embed { /* PDF的内容的嵌入 */ + max-width: 100%; +} + +.PDF__Caption { /* PDF的标题 */ + padding: 6px 2px; + color: var(--color-text-default-light); + font-size: 0.875em; +} + +/* Pill */ + +.Pill { /* 药丸 */ + padding: 0 6px; + border-radius: 3px; + white-space: nowrap; + display: inline-block; + /* margin of inline-block does not collapse */ + margin: 3px 0; + margin-right: 6px; +} + +.Pill--ColorDefault { /* 药丸--默认颜色 */ + background: var(--color-pill-default); +} + +.Pill--ColorGray { + background: var(--color-pill-gray); +} + +.Pill--ColorBrown { + background: var(--color-pill-brown); +} + +.Pill--ColorOrange { + background: var(--color-pill-orange); +} + +.Pill--ColorYellow { + background: var(--color-pill-yellow); +} + +.Pill--ColorGreen { + background: var(--color-pill-green); +} + +.Pill--ColorBlue { + background: var(--color-pill-blue); +} + +.Pill--ColorPurple { + background: var(--color-pill-purple); +} + +.Pill--ColorPink { + background: var(--color-pill-pink); +} + +.Pill--ColorRed { + background: var(--color-pill-red); +} + +/* Quote */ + +.Quote { /* 引用 */ + background: var(--color-bg-gray-light); + border-left: 5px solid currentcolor; + border-radius: 5px; + margin: 0.5rem 0; + padding: 0.5em 0.9em; + font-size: 1.2em; +} + +/* SemanticStringArray */ + +.SemanticString { /* 语义字符串 */ + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; +} + +.SemanticString__Fragment--Link, /* 语义字符串的片段--链接 */ +.SemanticString__Fragment--Resource > a { /* 语义字符串的片段--资源的链接 */ + color: inherit; + text-decoration: none; + border-bottom: 2px solid hsl(45, 8%, 85%); +} + +.SemanticString__Fragment--Link:hover, +.SemanticString__Fragment--Resource > a:hover { + border-bottom: 2px solid hsl(45, 8%, 55%); +} + +.SemanticString__Fragment--Code { + border-radius: 5px; + background-color: rgba(135, 131, 120, 0.15); + font-size: 0.9em; + padding: 0.2em 0.4em; + word-break: break-word; +} + +.SemanticString__Fragment--HighlightedBg, /* 语义字符串的片段--高亮背景 */ +.SemanticString__Fragment--HighlightedColor { + background-color: inherit; +} + +/* Code in highlighted should use the highlight background or color. */ + +.SemanticString__Fragment--HighlightedBg .SemanticString__Fragment--Code { + background-color: inherit; +} + +.SemanticString__Fragment--HighlightedColor .SemanticString__Fragment--Code { + color: inherit; +} + +.SemanticString__Fragment--ColorDefault { + color: var(--color-text-default); +} + +.SemanticString__Fragment--ColorGray { + color: var(--color-text-gray); +} + +.SemanticString__Fragment--ColorBrown { + color: var(--color-text-brown); +} + +.SemanticString__Fragment--ColorOrange { + color: var(--color-text-orange); +} + +.SemanticString__Fragment--ColorYellow { + color: var(--color-text-yellow); +} + +.SemanticString__Fragment--ColorGreen { + color: var(--color-text-green); +} + +.SemanticString__Fragment--ColorBlue { + color: var(--color-text-blue); +} + +.SemanticString__Fragment--ColorPurple { + color: var(--color-text-purple); +} + +.SemanticString__Fragment--ColorPink { + color: var(--color-text-pink); +} + +.SemanticString__Fragment--ColorRed { + color: var(--color-text-red); +} + +.SemanticString__Fragment--BgDefault { + background: var(--color-bg-default); +} + +.SemanticString__Fragment--BgGray { + background: var(--color-bg-gray); +} + +.SemanticString__Fragment--BgBrown { + background: var(--color-bg-brown); +} + +.SemanticString__Fragment--BgOrange { + background: var(--color-bg-orange); +} + +.SemanticString__Fragment--BgYellow { + background: var(--color-bg-yellow); +} + +.SemanticString__Fragment--BgGreen { + background: var(--color-bg-green); +} + +.SemanticString__Fragment--BgBlue { + background: var(--color-bg-blue); +} + +.SemanticString__Fragment--BgPurple { + background: var(--color-bg-purple); +} + +.SemanticString__Fragment--BgPink { + background: var(--color-bg-pink); +} + +.SemanticString__Fragment--BgRed { + background: var(--color-bg-red); +} + +.SemanticString__Fragment--Commented { + background: rgba(255, 212, 0, 0.14); + border-bottom: 2px solid rgb(255, 212, 0); +} + +.SemanticString__Fragment--Individual, +.SemanticString__Fragment--Resource, +.SemanticString__Fragment--Date { + color: var(--color-text-default-light); +} + +/* TableOfContents */ + +.TableOfContents { /* 目录 */ + margin: 4px 0; + padding: 5px; + border-radius: 5px; + font-size: 0.875rem; +} + +.TableOfContents__Item { /* 目录的项目 */ + list-style-type: none; + transition: background 120ms ease-in 0s; + border-radius: 5px; +} + +.TableOfContents__Item:hover { /* 目录的项目的鼠标悬停效果 */ + background: var(--color-ui-hover-bg); +} + +.TableOfContents__Item > a { /* 目录的项目的链接 */ + color: inherit; + text-decoration: none; +} + +.TableOfContents__Item > a > div { /* 目录的项目的链接的div */ + padding: 4.5px 2px; +} + +.TableOfContents__Item .SemanticStringArray { /* 目录的项目的语义字符串数组 */ + border-bottom: 1px solid rgba(55, 53, 47, 0.16); +} + +/* Text */ + +.Text { /* 文本 */ + min-height: calc(1.5rem + 10px); +} + +.Text__Content { /* 文本的内容 */ + padding: 3px 2px; + margin: 2px 0; +} + +.Text__Children { /* 文本的子元素 */ + margin-left: 1.5em; +} + +/* ToDo */ + +.ToDo__Content { /* 待办事项的内容 */ + display: flex; + padding: 3px 0; + margin: 2px 0; + line-height: 1.5; +} + +.ToDo__Icon { /* 待办事项的图标 */ + display: flex; + align-items: center; + justify-content: center; + height: 1.5em; + width: 1.5em; + margin-left: 2px; + margin-right: 4px; + flex-shrink: 0; +} + +.ToDo__Title--done { /* 待办事项的标题--完成 */ + opacity: 0.375; +} + +.ToDo__Children { /* 待办事项的子元素 */ + margin-left: calc(1.5em + 6px); +} + +.IconCheckboxChecked { /* 图标复选框--已选中 */ + display: flex; + align-items: center; + justify-content: center; + width: 1em; + height: 1em; + background: rgb(46, 170, 220); +} + +.IconCheckboxChecked > svg { /* 图标复选框--已选中 */ + width: 0.75em; + height: 0.75em; + fill: white; +} + +.IconCheckboxUnchecked { /* 图标复选框--未选中 */ + display: flex; +} + +.IconCheckboxUnchecked > svg { /* 图标复选框--未选中 */ + width: 1em; + height: 1em; + fill: inherit; +} + +/** + * Toggle + * Ref. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details + */ + +.Toggle { /* 切换 */ + margin: 2px 0; +} + +.Toggle__Summary { /* 切换的摘要 */ + padding: 3px 0; + cursor: pointer; + list-style: none; + display: flex; +} + +.Toggle__Summary::-webkit-details-marker { /* 切换的摘要的webkit细节标记 */ + display: none; +} + +.Toggle__Summary:focus { /* 切换的摘要的焦点 */ + outline: none; +} + +.Toggle__Summary::before { /* 切换的摘要的before伪元素 */ + /* If we don't specify "content", this element doesn't show. */ + content: ''; + /* Prevent this element shrink when content is long. */ + flex: 0 0 1.25rem; + border-radius: 0.25rem; + margin: 0.125rem 0.25rem; + width: 1.25rem; + height: 1.25rem; + background-image: url("data:image/svg+xml,%3Csvg width='100%' height='100%' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.8 9.30718C14.1938 9.53454 14.3907 9.64822 14.4568 9.79663C14.5144 9.92609 14.5144 10.0739 11.4568 10.2034C14.3907 10.3518 14.1938 10.4655 13.8 10.6928L8.7 13.6373C8.3062 13.8647 8.10931 13.9783 7.94774 13.9614C7.80681 13.9466 7.67878 13.8726 7.59549 13.758C7.5 13.6266 7.5 13.3992 7.5 12.9445L7.5 7.05551C7.5 6.6008 7.5 6.37344 7.59549 6.24201C7.67878 6.12736 7.80681 6.05345 7.94774 6.03864C8.10931 6.02166 8.3062 6.13533 8.7 6.36269L13.8 9.30718Z' fill='black'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + transition: background-image 0.1s ease-in-out, transform 0.2s ease-in-out; +} + +.Toggle--Empty > .Toggle__Summary::before { + opacity: 0.5; +} + +.Toggle[open] > .Toggle__Summary::before { + transform: rotate(90deg); +} + +.Toggle__Summary:hover::before { + background-image: url("data:image/svg+xml,%3Csvg width='100%' height='100%' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='20' height='20' rx='5' fill='%2337352F' fill-opacity='0.08'/%3E%3Cpath d='M13.8 9.30718C14.1938 9.53454 14.3907 9.64822 14.4568 9.79663C14.5144 9.92608 14.5144 10.0739 14.4568 10.2034C14.3907 10.3518 14.1938 10.4655 13.8 10.6928L8.7 13.6373C8.3062 13.8647 8.10931 13.9783 7.94774 13.9614C7.80681 13.9466 7.67878 13.8726 7.59549 13.758C7.5 13.6266 7.5 13.3992 7.5 12.9445L7.5 7.05551C7.5 6.6008 7.5 6.37344 7.59549 6.24201C7.67878 6.12736 7.80681 6.05345 7.94774 6.03864C8.10931 6.02166 8.3062 6.13533 8.7 6.36269L13.8 9.30718Z' fill='black'/%3E%3C/svg%3E"); +} + +.Toggle__Content { + padding-left: calc(1.5em + 4px); +} + +/* Video */ + +.Video { /* 视频 */ + margin-top: 0.5em; + margin-bottom: 0.5em; + align-self: center; +} + +.Video__Content > video { /* 视频的内容的视频 */ + max-width: 100%; +} + +.Video__Caption { /* 视频的标题 */ + padding: 6px 2px; + color: var(--color-text-default-light); + font-size: 0.875em; +} + + +/* ------------------------------ 新功能*/ \ No newline at end of file diff --git a/e5-exchage.html b/e5-exchage.html new file mode 100644 index 0000000..3812cee --- /dev/null +++ b/e5-exchage.html @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + 使用E5订阅中的exchange邮局 - E5食用指南 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 使用E5订阅中的exchange邮局 - E5食用指南 +

+ +
+ + Posted on Fri, Jun 7, 2024 + + + + + Ondrive + + + + + + Refer + + + +
+ +
+

说明

这一部分是使用E5订阅中的exchange服务,搭建一个企业邮箱(域名邮箱)。

简单来说,你需要首先准备一个域名,例如adc.com,遵循此文,你可以得到一个XXX@abc.com的邮箱用于收发邮件,并可以设置转发规则,即发到以@abc.com结尾的任何邮件都可以被转发到你的邮箱中。

用途:比较有个性,用于注册验证等。

设置自定义域

转到管理后台,登录E5管理员账号,如图,添加新域名

如果是使用cloudflare等DNS提供商,可以一键认证:

按照指示继续添加TXT记录等。注意一个域名只能链接一个企业邮箱(当然你可以开二级域名)。

这样我们就有两个域名了:

添加自定义域用户

转到用户管理界面,选择添加用户即可:

注意在 域 处,选择你刚才添加的域名:

配置好后,在office.com可以直接登录。

设置Catch-all功能,实现无限别名收邮件

打开Exchange管理中心

如图,对于你的域名,设置Internal relay,这样exchange就会将所有邮件都纳入收件中。

对于个人而言,我们只需要将所有发送到此企业邮箱的邮件都转到自己邮箱就行。但是对于团队,我们并不希望将发给团队中其他人的邮件也转发一次。这里我们建立一个用户组,便于管理。

转到用户组管理

如图,添加动态用户组:

转到规则进行配置

如图,对所有外部发件人应用,转发到我们的常用账户。

开启高级选项,即可看到更多设置,在例外中,选择之前建立的小组:

一个配置示例:

这样我们就完成了配置:

测试一下,注意我使用的是kerm@kermsite.com,但这里的收件人是test@kermsite.com。说明转发成功了。

+ + + + \ No newline at end of file diff --git a/fonts/source-sans-pro-v13-latin-600.woff b/fonts/source-sans-pro-v13-latin-600.woff new file mode 100644 index 0000000..0aba561 Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-600.woff differ diff --git a/fonts/source-sans-pro-v13-latin-600.woff2 b/fonts/source-sans-pro-v13-latin-600.woff2 new file mode 100644 index 0000000..cb0ea77 Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-600.woff2 differ diff --git a/fonts/source-sans-pro-v13-latin-600italic.woff b/fonts/source-sans-pro-v13-latin-600italic.woff new file mode 100644 index 0000000..cfdf5cc Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-600italic.woff differ diff --git a/fonts/source-sans-pro-v13-latin-600italic.woff2 b/fonts/source-sans-pro-v13-latin-600italic.woff2 new file mode 100644 index 0000000..5be4f95 Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-600italic.woff2 differ diff --git a/fonts/source-sans-pro-v13-latin-700.woff b/fonts/source-sans-pro-v13-latin-700.woff new file mode 100644 index 0000000..f2a7dd3 Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-700.woff differ diff --git a/fonts/source-sans-pro-v13-latin-700.woff2 b/fonts/source-sans-pro-v13-latin-700.woff2 new file mode 100644 index 0000000..ce34a9f Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-700.woff2 differ diff --git a/fonts/source-sans-pro-v13-latin-700italic.woff b/fonts/source-sans-pro-v13-latin-700italic.woff new file mode 100644 index 0000000..38faafb Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-700italic.woff differ diff --git a/fonts/source-sans-pro-v13-latin-700italic.woff2 b/fonts/source-sans-pro-v13-latin-700italic.woff2 new file mode 100644 index 0000000..a63c727 Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-700italic.woff2 differ diff --git a/fonts/source-sans-pro-v13-latin-italic.woff b/fonts/source-sans-pro-v13-latin-italic.woff new file mode 100644 index 0000000..4e767cf Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-italic.woff differ diff --git a/fonts/source-sans-pro-v13-latin-italic.woff2 b/fonts/source-sans-pro-v13-latin-italic.woff2 new file mode 100644 index 0000000..d3e979b Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-italic.woff2 differ diff --git a/fonts/source-sans-pro-v13-latin-regular.woff b/fonts/source-sans-pro-v13-latin-regular.woff new file mode 100644 index 0000000..5b6e97b Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-regular.woff differ diff --git a/fonts/source-sans-pro-v13-latin-regular.woff2 b/fonts/source-sans-pro-v13-latin-regular.woff2 new file mode 100644 index 0000000..36bdc0e Binary files /dev/null and b/fonts/source-sans-pro-v13-latin-regular.woff2 differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..4744e18 --- /dev/null +++ b/index.html @@ -0,0 +1,514 @@ + + + + + + + + + + + + + + + + + + + + + + + + 413’s Website + + + + + + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+ +

+ 413’s Website +

+ +
+ + 这是413的静态网站 欢迎来玩🥳 + +
+ +
+
+ +
+

+ + + 安装 ESP32 Rust 开发工具链 esp-rs + +

+ +

+ 安装 ESP32 Rust 开发工具链 esp-rs 适用很多Rust嵌入 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Azure openai 遇到注意事项 + +

+ +
+ + Posted on Wed, Jul 3, 2024 + + + + + openai + + + +
+
+ +
+

+ + + 使用E5订阅中的exchange邮局 - E5食用指南 + +

+ +
+ + Posted on Fri, Jun 7, 2024 + + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+

+ + + + + 超详细AI提示词论文润色指南 + +

+ +
+ + Posted on Sun, May 19, 2024 + + + + + Refer + + + +
+
+ +
+

+ + + ESP32中的输出信息 + +

+ +
+ + Posted on Sun, May 5, 2024 + + + + + 单片机 + + + +
+
+ +
+

+ + + Clash Tun 共享网络 + +

+ +

+ Clash Tun 共享网络代理信息 +

+ +
+ + Posted on Sat, Apr 13, 2024 + + + + + 环境 + + + +
+
+ +
+

+ + + 时钟时序线 + +

+ +
+ + Posted on Sat, Feb 17, 2024 + + + + + 单片机 + + + +
+
+ +
+

+ + + + + 语音合成 + +

+ +

+ Listen +

+ +
+ + Posted on Wed, Dec 7, 2022 + + + + + Web + + + +
+
+ +
+

+ + + + + Rclone maigsik 本地音乐 + +

+ +
+ + Posted on Wed, Nov 30, 2022 + + + + + Web + + + +
+
+ +
+

+ + + OneDrive跨域转存 + +

+ +

+ https://www.chirmyram.top/archives/onedrivewebdav +

+ +
+ + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+

+ + + Notion文章免费&自动发布到GitHub Pages - 知乎 + +

+ +

+ https://zhuanlan.zhihu.com/p/469320294 +

+ +
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/latex-learning-7aaa9a.html b/latex-learning-7aaa9a.html new file mode 100644 index 0000000..828167d --- /dev/null +++ b/latex-learning-7aaa9a.html @@ -0,0 +1 @@ +The template name has zero length, please check the "template" field in your Notion table. \ No newline at end of file diff --git a/notiongithub-pages----f5d9bc.html b/notiongithub-pages----f5d9bc.html new file mode 100644 index 0000000..fac865b --- /dev/null +++ b/notiongithub-pages----f5d9bc.html @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + Notion文章免费&自动发布到GitHub Pages - 知乎 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ Notion文章免费&自动发布到GitHub Pages - 知乎 +

+ +
+

摘要

本方案需要依赖:

1. Notion - One workspace. Every team

2. github.com/dragonman225

3. docs.github.com/cn/acti

最终可以实现:

1. 有一个自己的免费网页 Github Pages

2. 在Notion中写完任何文章后的一天内免费&自动发布到 Github Pages 中,

3. 成品请参考我的网页:

FizzerYu​fizzeryu.github.io/

步骤

1. 首先你要有个Github账号和Notion账号,如果你是学生的话可以免费领取 GitHub Student Developer PackNotion for Education

2. 创建一个新仓库,需要给这个仓库一个比较特殊的名字,比如说 userName.github.io,注意这里的 userName 可以是任意你喜欢的名字,但是强烈推荐使用自己的github用户名

3. 把这个 Notion表格模板 复制到自己的Notion账号中【右上角的 Duplicate 按钮】,把这个表格设置为公开【 点击右上角的 Share 按钮→ Share to web 打开 】,然后点击 Copy 按钮复制网址

3.1 比如说我复制出来的网址是 https://www.notion.so/2c4dc4affe68463da4f61f8fa93406e0,打开这个网址检查一下是不是对应的表格模板

4. fork仓库 github.com/dragonman225 ,修改 config.json 文件中的 url

1. 原始的url为:

https://s413.notion.site/557024fb04f24f2ea19e487557abbaae?v=b2d8ec9e232144e596f40037707d674e&pvs=4
"url": "https://www.notion.so/b6fcf809ca5047b89f423948dce013a0?v=03ddc4d6130a47f8b68e74c9d0061de2",

2. 将 b6fc...de2 这段字符修改为 步骤3.1 中的网址后缀(也就是 2c4dc...b594)

3. 对应到本文中也就是修改为:

"url": "https://www.notion.so/2c4dc4affe68463da4f61f8fa93406e0?v=9ad1da13a555443598a33b3bfc3db594"

5. 创建 Personal access token

5.1. 打开 Github.com,依次点击 右上角头像 → Settings → Developer settings → Personal Access Tokens → 右上角Generate new token → 按照示例修改下面

5.2. 最后点击最下方的 Generate token ,就会生成一个字符串,复制这个字符串备用【注意千万别泄露这个字符串,有安全风险

6. 回到你在 步骤4 中fork的名为 notablog-starter 仓库,依次点击 settingssecretsactionsNew repository secret, Name 填为 ACCESS_TOKEN ,Value 就是上一步( 步骤5.2 )生成的字符串,具体如图所示:

7. 回到你在 步骤4 中fork的名为 notablog-starter 仓库,依次点击 ActionsNew workflowset up a workflow yourself

7.1. 使用下方代码覆盖原始文件,注意代码需要修改两处位置:

    # This is a basic workflow to help you get started with Actions
+
+    name: Github Pages
+
+    # Controls when the workflow will run
+    on:
+      schedule:
+    		# 下一行是说在每天的国际标准时间23点(北京时间早上7点)触发该任务
+    		# 你可以随意修改
+        - cron: '0 23 * * *'
+      # Allows you to run this workflow manually from the Actions tab
+      workflow_dispatch:
+
+    # A workflow run is made up of one or more jobs that can run sequentially or in parallel
+    jobs:
+      # This workflow contains a single job called "build"
+      build:
+        # The type of runner that the job will run on
+        runs-on: ubuntu-latest
+
+        # Steps represent a sequence of tasks that will be executed as part of the job
+        steps:
+          # 拉取代码
+          - name: Checkout
+            uses: actions/checkout@v2
+          # 1、生成静态文件
+          - name: Build
+            run: npm i -g notablog && notablog generate .
+          # 2、部署到 GitHub Pages
+          - name: Deploy
+            uses: JamesIves/github-pages-deploy-action@v4.2.5
+            with:
+              token: ${{ secrets.ACCESS_TOKEN }}
+              repository-name: FizzerYu/FizzerYu.github.io # 修改这里
+              BRANCH: main  # 如果你的仓库默认分支是 master 记得修改这里
+              FOLDER: public

7.2. 上方代码需要修改 repository-name ,具体就是步骤2中创建的仓库的 `git clone` 地址,比如说:

# git clone 地址为:
+git@github.com:FizzerYu/FizzerYu.github.io.git
+# 因此 repository-name 为:
+FizzerYu/FizzerYu.github.io

7.3. 注意 步骤2 中创建的仓库默认分支名是否与 BRANCH 参数一致

7.4. 按照要求修改完成后保存:

8. 按照下图要求依次点击:

9. 稍等片刻(大概10min内),就可以打开你的github pages查看啦!比如说我的网址 `FizzerYu.github.io`

测试

1. 在notion中找到复制出来的模板,复制一行数据

2. 然后随便写点东西,再重复步骤8、9,或者等待一天,就可以看到新的文章啦!

+ + + + \ No newline at end of file diff --git a/onedrive-1b24d4.html b/onedrive-1b24d4.html new file mode 100644 index 0000000..f781bf5 --- /dev/null +++ b/onedrive-1b24d4.html @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + OneDrive跨域转存 | 413’s Website + + + + + + + + + + + + + +
+ +
+
+ +

+ OneDrive跨域转存 +

+ +
+ + + + + Ondrive + + + + + + Refer + + + +
+ +
+

九月的阿里社区接近癫狂,点燃这堆干柴烈火的,除了两年10T的分享活动,还因阿里云盘便捷快速的分享方式。然而,这显然是一个资本收割的阳谋,收割的是白嫖控的点击量与用户惯性。可以预见两年之后或者更快,各种收费策略,或大量资源变3秒。那么有没有一种备选方案,既能自由分享各种资源(敏感,音频,压缩包),又能确保屯的粮食不被3秒呢?

没错,唯有onedrive。

大家知道,Microsoft免费订阅中的A1,E5和Team exploratory 都有5T的onedrive,其中1ove社区有两个免费的A1域,用户可以自行注册,并能扩容至25T(近期更新:其中“薅羊毛大学”已翻车,后增“白白大学”与“探索大学”,注册需要邀请码)。而E5订阅可以开25个子号,总容量即5×25=125T,可去开发者官网注册全局账号,并设置程序调用API自动续期。Team exploratory有 100个子号,是低配版的A1,虽然有效期只有一年,但它是相对稳定的订阅,不会像A1莫名炸链和翻车。因此相对于阿里盘的什么福利码200GB,两年10T之流,微软简直太大方了。然并卵,onedrive只支持域内转存,跨域分享需要先下载本地再上传,看着其它域分享的几百GB甚至几个T的资源,想到下载就脑壳疼。

幸好微软为离线转存留了一个后门,即mover ,所以才有了我们TG试验区群组频道

该方案是共享一个或多个账号的SharePoint(25T空间)或 onedrive(5T空间),把它作为转存资源的中转站。分享者需进入账号的SharePoint主页新建网站文件夹,然后登录mover账号挂载该SharePoint,并将资源转存到网站文件夹,至此完成分享操作,关闭网页等着转存完成即可。而被分享者转存文件时也是把共享账号挂载到mover,然后搬运至自己的OneDrive。

若采用共享OneDrive账号的方式,挂载mover将更容易。起初担心这种方式账号会被滥用、恶意修改密码或者删除文件之类。然而,我们不应拿最坏的恶意度人。并且资源分享出去之后,定会被其他人转存,这就像把一个篮子的鸡蛋复制了几份分发到不同的篮子中,因此资源的种子仍在,希望就未熄灭!而采用OneDrive共享方式,则建议每人都注册一个Team exploratory的全局账号,并另开一个子号共享出去。若资源平时存放在全局账号的OD盘(基本不会翻车),分享时采用域内分享链接方式,一键转存到共享账号就可以了。因此分享的繁琐步骤被简化成了域内分享!这样比用mover转存要方便。

这是一种跨域转存的尝试,如果可行则可推广到整个1ove社区,每人注册一个Team free(或Team exploratory) 全局账号,并拿出一个子号用于分享。希望有一天,今天或明天,大家都来参与这个试验并反馈意见,探索出一种高效的跨域转存方式,以此为A1域分流。

希望有一天,群里不会再出现炸链,或翻车的字眼

希望有一天,分享者可以尽情分享,白嫖族可以自由囤货

+ + + + \ No newline at end of file diff --git a/reference.html b/reference.html new file mode 100644 index 0000000..6ee5467 --- /dev/null +++ b/reference.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + 杂项 | 413’s Website + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+ +

+ 杂项 +

+ +
+
💡

Ch549参考资料密码是vm51

答辩

+ + + + \ No newline at end of file diff --git a/tag/LaTex.html b/tag/LaTex.html new file mode 100644 index 0000000..348140c --- /dev/null +++ b/tag/LaTex.html @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + #LaTex | 413’s Website + + + + + + + + + +
+
+

#LaTex +

+
+
+ +
+

+ + + + + LaTeX的简易入门 + +

+ +

+ This is LaTeX tutor +

+ +
+ + Posted on Tue, Apr 18, 2023 + + + + + LaTex + + + +
+
+ +
+

+ + + + + LaTeX & commad 参考 + +

+ +
+ + + + + LaTex + + + +
+
+ +
+

+ + + + + LaTex learning + +

+ +
+ + + + + LaTex + + + +
+
+ +
+

+ + + + + Latex_环境配置 + +

+ +
+ + + + + LaTex + + + + + + Web + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Manim.html b/tag/Manim.html new file mode 100644 index 0000000..44d1c6e --- /dev/null +++ b/tag/Manim.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Manim | 413’s Website + + + + + + + + + +
+
+

#Manim +

+
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Ondrive.html b/tag/Ondrive.html new file mode 100644 index 0000000..dfb3e90 --- /dev/null +++ b/tag/Ondrive.html @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Ondrive | 413’s Website + + + + + + + + + +
+
+

#Ondrive +

+
+
+ +
+

+ + + 使用E5订阅中的exchange邮局 - E5食用指南 + +

+ +
+ + Posted on Fri, Jun 7, 2024 + + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+

+ + + OneDrive跨域转存 + +

+ +

+ https://www.chirmyram.top/archives/onedrivewebdav +

+ +
+ + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Refer.html b/tag/Refer.html new file mode 100644 index 0000000..9177507 --- /dev/null +++ b/tag/Refer.html @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Refer | 413’s Website + + + + + + + + + +
+
+

#Refer +

+
+
+ +
+

+ + + 使用E5订阅中的exchange邮局 - E5食用指南 + +

+ +
+ + Posted on Fri, Jun 7, 2024 + + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+

+ + + + + 超详细AI提示词论文润色指南 + +

+ +
+ + Posted on Sun, May 19, 2024 + + + + + Refer + + + +
+
+ +
+

+ + + OneDrive跨域转存 + +

+ +

+ https://www.chirmyram.top/archives/onedrivewebdav +

+ +
+ + + + + Ondrive + + + + + + Refer + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Rust.html b/tag/Rust.html new file mode 100644 index 0000000..7ac4c21 --- /dev/null +++ b/tag/Rust.html @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Rust | 413’s Website + + + + + + + + + +
+
+

#Rust +

+
+
+ +
+

+ + + Rust 驱动 Audio - 播放和录音 + +

+ +

+ PCM DAC ADC +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 Camera - 采集和播放 + +

+ +

+ 流媒体 控制协议 视频捕获 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 Touch - 触摸板 + +

+ +

+ 触摸屏 校准 驱动 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 LCD - 显示图片 + +

+ +

+ JPEG解码 硬件编码 解码 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 LCD - 显示中英文 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 使用 USB-JTAG/probe-rs 调试应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 分析 ESP32 固件和分区表 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 使用 cargo run 和 espflash 烧写固件 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 配置 cargo workspace + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + esp-rs 常见问题 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust/C/C++ cmake 混合应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust no_std 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust std 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 使用 Rust 开发 ESP32 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 安装 ESP32 Rust 开发工具链 esp-rs + +

+ +

+ 安装 ESP32 Rust 开发工具链 esp-rs 适用很多Rust嵌入 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Test.html b/tag/Test.html new file mode 100644 index 0000000..1aec296 --- /dev/null +++ b/tag/Test.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Test | 413’s Website + + + + + + + + + +
+
+

#Test +

+
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/Web.html b/tag/Web.html new file mode 100644 index 0000000..6663de4 --- /dev/null +++ b/tag/Web.html @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + #Web | 413’s Website + + + + + + + + + +
+
+

#Web +

+
+
+ +
+

+ + + + + 语音合成 + +

+ +

+ Listen +

+ +
+ + Posted on Wed, Dec 7, 2022 + + + + + Web + + + +
+
+ +
+

+ + + + + Rclone maigsik 本地音乐 + +

+ +
+ + Posted on Wed, Nov 30, 2022 + + + + + Web + + + +
+
+ +
+

+ + + + + Latex_环境配置 + +

+ +
+ + + + + LaTex + + + + + + Web + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/note.html b/tag/note.html new file mode 100644 index 0000000..0edfdab --- /dev/null +++ b/tag/note.html @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + #note | 413’s Website + + + + + + + + + +
+
+

#note +

+
+
+ +
+ + + + \ No newline at end of file diff --git a/tag/openai.html b/tag/openai.html new file mode 100644 index 0000000..435837d --- /dev/null +++ b/tag/openai.html @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + #openai | 413’s Website + + + + + + + + + +
+
+

#openai +

+
+
+ +
+

+ + + Azure openai 遇到注意事项 + +

+ +
+ + Posted on Wed, Jul 3, 2024 + + + + + openai + + + +
+
+ +
+ + + + \ No newline at end of file diff --git "a/tag/\345\215\225\347\211\207\346\234\272.html" "b/tag/\345\215\225\347\211\207\346\234\272.html" new file mode 100644 index 0000000..fb1fc30 --- /dev/null +++ "b/tag/\345\215\225\347\211\207\346\234\272.html" @@ -0,0 +1,752 @@ + + + + + + + + + + + + + + + + + + + + + + + + #单片机 | 413’s Website + + + + + + + + + +
+
+

#单片机 +

+
+
+ +
+

+ + + Rust 驱动 Audio - 播放和录音 + +

+ +

+ PCM DAC ADC +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 Camera - 采集和播放 + +

+ +

+ 流媒体 控制协议 视频捕获 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 Touch - 触摸板 + +

+ +

+ 触摸屏 校准 驱动 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 LCD - 显示图片 + +

+ +

+ JPEG解码 硬件编码 解码 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + Rust 驱动 LCD - 显示中英文 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 使用 USB-JTAG/probe-rs 调试应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 分析 ESP32 固件和分区表 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 使用 cargo run 和 espflash 烧写固件 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 配置 cargo workspace + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + esp-rs 常见问题 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust/C/C++ cmake 混合应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust no_std 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 开发 Rust std 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + 使用 Rust 开发 ESP32 应用 + +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + 单片机 + + + + + + Rust + + + +
+
+ +
+

+ + + 安装 ESP32 Rust 开发工具链 esp-rs + +

+ +

+ 安装 ESP32 Rust 开发工具链 esp-rs 适用很多Rust嵌入 +

+ +
+ + Posted on Mon, Sep 2, 2024 + + + + + Rust + + + + + + 单片机 + + + +
+
+ +
+

+ + + ESP32中的输出信息 + +

+ +
+ + Posted on Sun, May 5, 2024 + + + + + 单片机 + + + +
+
+ +
+

+ + + 时钟时序线 + +

+ +
+ + Posted on Sat, Feb 17, 2024 + + + + + 单片机 + + + +
+
+ +
+

+ + + + + CH549的笔记 + +

+ +
+ + Posted on Thu, Dec 28, 2023 + + + + + 单片机 + + + +
+
+ +
+

+ + + 沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置 - Milton - 博客园 + +

+ +

+ 硬件部分 软件部分 下载 +

+ +
+ + + + + 单片机 + + + + + + 环境 + + + +
+
+ +
+ + + + \ No newline at end of file diff --git "a/tag/\347\216\257\345\242\203.html" "b/tag/\347\216\257\345\242\203.html" new file mode 100644 index 0000000..0c32043 --- /dev/null +++ "b/tag/\347\216\257\345\242\203.html" @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + #环境 | 413’s Website + + + + + + + + + +
+
+

#环境 +

+
+
+ +
+

+ + + Clash Tun 共享网络 + +

+ +

+ Clash Tun 共享网络代理信息 +

+ +
+ + Posted on Sat, Apr 13, 2024 + + + + + 环境 + + + +
+
+ +
+

+ + + 沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置 - Milton - 博客园 + +

+ +

+ 硬件部分 软件部分 下载 +

+ +
+ + + + + 单片机 + + + + + + 环境 + + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/timeline.html b/timeline.html new file mode 100644 index 0000000..45340b4 --- /dev/null +++ b/timeline.html @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + 时钟时序线 | 413’s Website + + + + + + + + + + +
+ +
+
+ +

+ 时钟时序线 +

+ +
+ + Posted on Sat, Feb 17, 2024 + + + + + 单片机 + + + +
+ +
+

缘由

突发有一天想要一个高质量的时序图来表达自己的想法,就找到了一个开源的方案.

WAVEDROM

效果很好,语法简单,易于上手

信号的不同种类

wave 后面的每一个都是一个波形甚至. 都是一种表示方式

{name:'Alfa',wave:'01.zx=ud.23.45678'},

时钟线的不同形式

{ signal: [
+  { name: "pclk", wave: 'p.......' },
+  { name: "Pclk", wave: 'P.......' },
+  { name: "nclk", wave: 'n.......' },
+  { name: "Nclk", wave: 'N.......' },
+  {},
+  { name: 'clk0', wave: 'phnlPHNL' },
+  { name: 'clk1', wave: 'xhlhLHl.' },
+  { name: 'clk2', wave: 'hpHplnLn' },
+  { name: 'clk3', wave: 'nhNhplPl' },
+  { name: 'clk4', wave: 'xlh.L.Hx' },
+]}

文字镶嵌

{ signal: [
+  { name: "clk",  wave: "P......" },
+  { name: "bus",  wave: "x.==.=x", data: ["head", "body", "tail", "data"] },
+  { name: "wire", wave: "0.1..0." }
+]}

截断中间不必要的部分

使用 |

{ signal: [
+  { name: "clk",         wave: "p.....|..." },
+  { name: "Data",        wave: "x.345x|=.x", data: ["head", "body", "tail", "data"] },
+  { name: "Request",     wave: "0.1..0|1.0" },
+  {},
+  { name: "Acknowledge", wave: "1.....|01." }
+]}

对于文字的标注

['group name', {...}, {...}, ...] 这样的方式

{ signal: [
+   {name: 'clk',   wave: 'p..Pp..P'},
+  ['Master',
+    ['ctrl',
+      {name: 'write', wave: '01.0....'},
+      {name: 'read',  wave: '0...1..0'}
+    ],
+    {  name: 'addr',  wave: 'x3.x4..x', data: 'A1 A2'},
+    {  name: 'wdata', wave: 'x3.x....', data: 'D1'   },
+  ],
+  {},
+  ['Slave',
+    ['ctrl',
+      {name: 'ack',   wave: 'x01x0.1x'},
+    ],
+    {  name: 'rdata', wave: 'x.....4x', data: 'Q2'},
+  ]
+]}

周期和相位的调整

Period &Phase 关键字来调整

{ signal: [
+  { name: "CK",   wave: "P.......",                                              period: 2  },
+  { name: "CMD",  wave: "x.3x=x4x=x=x=x=x", data: "RAS NOP CAS NOP NOP NOP NOP", phase: 0.5 },
+  { name: "ADDR", wave: "x.=x..=x........", data: "ROW COL",                     phase: 0.5 },
+  { name: "DQS",  wave: "z.......0.1010z." },
+  { name: "DQ",   wave: "z.........5555z.", data: "D0 D1 D2 D3" }
+]}

{signal: [  {name:'clk',         wave: 'p....' },  {name:'Data',        wave: 'x345x', data: 'a b c' },  {name:'Request',     wave: '01..0' }], head:{   text:'WaveDrom example',   tick:0,   every:2 }, foot:{   text:'Figure 100',   tock:9 },}

个性化的特别参数

config:{...}

hscale —>config: { hscale: number }

skin —> config:{skin:'...'} 'default' &'narrow'

head/foot —>head:{...}&foot:{...}

tick

tock

text

every

{signal: [
+  {name:'clk',         wave: 'p....' },
+  {name:'Data',        wave: 'x345x', data: 'a b c' },
+  {name:'Request',     wave: '01..0' }
+],
+ head:{
+   text:'WaveDrom example',
+   tick:0,
+	   every:2
+ },
+ foot:{
+   text:'Figure 100',
+   tock:9
+ },
+}

tspan

{signal: [
+  {name:'clk', wave: 'p.....PPPPp....' },
+  {name:'dat', wave: 'x....2345x.....', data: 'a b c d' },
+  {name:'req', wave: '0....1...0.....' }
+],
+head: {text:
+  ['tspan',
+    ['tspan', {class:'error h1'}, 'error '],
+    ['tspan', {class:'warning h2'}, 'warning '],
+    ['tspan', {class:'info h3'}, 'info '],
+    ['tspan', {class:'success h4'}, 'success '],
+    ['tspan', {class:'muted h5'}, 'muted '],
+    ['tspan', {class:'h6'}, 'h6 '],
+    'default ',
+    ['tspan', {fill:'pink', 'font-weight':'bold', 'font-style':'italic'}, 'pink-bold-italic']
+  ]
+},
+foot: {text:
+  ['tspan', 'E=mc',
+    ['tspan', {dy:'-5'}, '2'],
+    ['tspan', {dy: '5'}, '. '],
+    ['tspan', {'font-size':'25'}, 'B '],
+    ['tspan', {'text-decoration':'overline'},'over '],
+    ['tspan', {'text-decoration':'underline'},'under '],
+    ['tspan', {'baseline-shift':'sub'}, 'sub '],
+    ['tspan', {'baseline-shift':'super'}, 'super ']
+  ],tock:-5
+}
+}

箭头指向

~    -~
+<~>  <-~>
+ ~>   -~>  ~->
{ signal: [
+  { name: 'A', wave: '01........0....',  node: '.a........j' },
+  { name: 'B', wave: '0.1.......0.1..',  node: '..b.......i' },
+  { name: 'C', wave: '0..1....0...1..',  node: '...c....h..' },
+  { name: 'D', wave: '0...1..0.....1.',  node: '....d..g...' },
+  { name: 'E', wave: '0....10.......1',  node: '.....ef....' }
+  ],
+  edge: [
+    'a~b t1', 'c-~a t2', 'c-~>d time 3', 'd~-e',
+    'e~>f', 'f->g', 'g-~>h', 'h~>i some text', 'h~->j'
+  ]
+}

Sharp lines

-   -|   -|-
+<-> <-|> <-|->
+ ->  -|>  -|->  |->
+ +
{ signal: [
+  { name: 'A', wave: '01..0..',  node: '.a..e..' },
+  { name: 'B', wave: '0.1..0.',  node: '..b..d.', phase:0.5 },
+  { name: 'C', wave: '0..1..0',  node: '...c..f' },
+  {                              node: '...g..h' },
+  {                              node: '...I..J',  phase:0.5 },
+  { name: 'D', wave: '0..1..0',  phase:0.5 }
+  ],
+  edge: [
+    'b-|a t1', 'a-|c t2', 'b-|-c t3', 'c-|->e t4', 'e-|>f more text',
+    'e|->d t6', 'c-g', 'f-h', 'g<->h 3 ms', 'I+J 5 ms'
+  ]
+}

可以镶嵌函数代替

(function (bits, ticks) {
+  var i, t, gray, state, data = [], arr = [];
+  for (i = 0; i < bits; i++) {
+    arr.push({name: i + '', wave: ''});
+    state = 1;
+    for (t = 0; t < ticks; t++) {
+      data.push(t + '');
+      gray = (((t >> 1) ^ t) >> i) & 1;
+      arr[i].wave += (gray === state) ? '.' : gray + '';
+      state = gray;
+    }
+  }
+  arr.unshift('gray');
+  return {signal: [
+    {name: 'bin', wave: '='.repeat(ticks), data: data}, arr
+  ]};
+})(5, 16)

Hitchhiker's Guide to the WaveDrom

head/ +foot text has all properties of SVG text. Standard SVG +tspan attributes can be used to modify default properties of text. JsonML markup language used to represent SVG text content. Several predefined styles can be used and intermixed:

+ + + + \ No newline at end of file