<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>凛星的随笔</title><description>摸爬滚打着前进的工科生</description><link>https://rinx.nz/</link><language>zh_CN</language><item><title>Gitleaks 基础使用 - 不要再广播密钥啦</title><link>https://rinx.nz/posts/gitleaks/</link><guid isPermaLink="true">https://rinx.nz/posts/gitleaks/</guid><description>从 Gitleaks 检查，到抹除某一行密钥内容，逐行教你如何在项目开源前确保脱敏</description><pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;这是个什么？&lt;/h1&gt;
&lt;p&gt;Gitleaks 是一个开源 SAST（静态应用安全测试）命令行工具，用于检测 Git 仓库以防止把密码、API 密钥和访问令牌等机密信息硬编码到代码中。&lt;/p&gt;
&lt;p&gt;简单来说，就是&lt;strong&gt;防止你把密钥公之于众，然后被刷爆&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;如何安装&lt;/h1&gt;
&lt;h2&gt;Ubuntu/Debian&lt;/h2&gt;
&lt;p&gt;APT 似乎没有 Gitleaks 的包，可以选择 &lt;a href=&quot;https://github.com/gitleaks/gitleaks/releases&quot;&gt;下载&lt;/a&gt; 对应架构的二进制包，解压后直接运行即可（也可移至 &lt;code&gt;/usr/local/bin&lt;/code&gt; 以便全局使用&lt;/p&gt;
&lt;h2&gt;NixOS&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I use NixOS, btw.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nixpkgs 实在是一个伟大的发明，你只需要运行这行命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nix-shell -p gitleaks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就可以获得一个临时的，拥有 Gitleaks 软件包的 shell。当然也可以将其加入 &lt;code&gt;systemPackages&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Github Action&lt;/h2&gt;
&lt;p&gt;顺带一提，官方提供了 &lt;a href=&quot;https://github.com/marketplace/actions/gitleaks&quot;&gt;Action&lt;/a&gt; ，可以直接关联仓库并检查（虽然我没用过）&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;如何使用&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;gitleaks git . -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该行命令会扫描当前目录下的 git 仓库，并提示可能的泄露
这里我建了一个临时的仓库来实验，并提交了一些commit，然后使用 gitleaks 检查：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/gitleaks-2.webp&quot; alt=&quot;测试结果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然 gitleaks 成功检测到了可能的泄露，但此时我们已经提交了很多commit, 一个个 revert 是有些不太现实的。当然，你可以选择直接吊销/轮换密钥。
这里假设密钥和账户强绑定，无法吊销；或者项目曾经是闭源的，没有在意这方面，那就需要强制修改 git 历史了。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;强制修改 git 历史的方法&lt;/h1&gt;
&lt;p&gt;:::caution
请小心谨慎地修改 git 历史，并在 &lt;code&gt;git push&lt;/code&gt; 后确保在所有设备上拉取一遍以保证同步
:::&lt;/p&gt;
&lt;h2&gt;git 原生方法&lt;/h2&gt;
&lt;h3&gt;常规修改&lt;/h3&gt;
&lt;p&gt;如何泄露的影响还算不大，可以使用 Git 内置的交互式变基 (interactive rebase) 来逐个更改提交内容。
例如上图，需要变基到 &lt;code&gt;2e8121e&lt;/code&gt; 才能修改之后的 &lt;code&gt;d9df113&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rebase -i 2e8121e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/gitleaks-3.webp&quot; alt=&quot;变基操作界面&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我们需要修改 &lt;code&gt;d9df113&lt;/code&gt;，因此将第一行的&quot;pick&quot;改成&quot;edit&quot;并保存，开始交互式变基；&lt;/li&gt;
&lt;li&gt;通过编辑器删除泄露的密钥；&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;git add .&lt;/code&gt; 将更改提交到暂存区，执行 &lt;code&gt;git status&lt;/code&gt; 手动二次确认；&lt;/li&gt;
&lt;li&gt;确定修补完成后，执行 &lt;code&gt;git commit --amend&lt;/code&gt;，在这里可以修改 commit 内容，也可以不修改，但必须手动保存一次文件；&lt;/li&gt;
&lt;li&gt;这一轮修复就算成功了，执行 &lt;code&gt;git rebase --continue&lt;/code&gt;，git将开始自动合并，直到出现冲突。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;处理合并冲突&lt;/h3&gt;
&lt;p&gt;遇到合并冲突时，使用 &lt;code&gt;git status&lt;/code&gt; 查看，并打开冲突的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ cat .env
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
=======
API_KEY=yAxfgL1GnYhk0UYXZdt2eADqnrNXezeK
TIME_ZONE=Asia/Shanghai
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; 72fd120 (feat: 又实现了一个功能)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些格式的意思是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从 &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD&lt;/code&gt; 到 &lt;code&gt;=======&lt;/code&gt; 的部分：当前分支的内容（也就是修改后的）&lt;/li&gt;
&lt;li&gt;从 &lt;code&gt;=======&lt;/code&gt; 到 &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; 72fd120&lt;/code&gt; 的部分：旧分支的内容（修改前）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此处需要删除API_KEY，因此将文件修改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TIME_ZONE=Asia/Shanghai
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，&lt;code&gt;git add .&lt;/code&gt;，&lt;code&gt;git commit --amend&lt;/code&gt;，&lt;code&gt;git rebase --continue&lt;/code&gt;。&lt;strong&gt;如此循环，直到变基全部完成&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;最终效果&lt;/h3&gt;
&lt;p&gt;再次运行 &lt;code&gt;gitleaks git . -v&lt;/code&gt;，就可以看到一片绿啦~~
&lt;img src=&quot;../../assets/images/gitleaks-4.webp&quot; alt=&quot;修复完成&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后 force push 就可以强制修改远端内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push --force --all
git push --force --tags
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;git filter-branch 方法&lt;/h2&gt;
&lt;h3&gt;将某个文件从所有历史中移除&lt;/h3&gt;
&lt;p&gt;先筛选并执行移除：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch --force --index-filter \
&apos;git rm --cached --ignore-unmatch &amp;lt;文件路径&amp;gt;&apos; \
--prune-empty --tag-name-filter cat -- --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再清理备份引用，执行垃圾回收：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now --aggressive
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;将文件中的某一部分从所有历史中移除&lt;/h3&gt;
&lt;p&gt;在这里使用&lt;code&gt;sed&lt;/code&gt;作为示例，可以让AI编写适合你的替换命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch --force --tree-filter &apos;
grep -rl &quot;LEAKED_KEY_PART&quot; . | while read -r f; do
  sed -i &quot;s/LEAKED_KEY_PART/REDACTED/g&quot; &quot;$f&quot;
done
&apos; --tag-name-filter cat -- --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
这会在每个提交 checkout 一次，仓库大时将非常慢。
替换模式尽量写具体，避免误伤正常内容。
:::&lt;/p&gt;
&lt;p&gt;最后同样是 force push：
然后 force push 就可以强制修改远端内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push --force --all
git push --force --tags
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[STM32] EXTI 外部中断</title><link>https://rinx.nz/posts/stm32-exti/</link><guid isPermaLink="true">https://rinx.nz/posts/stm32-exti/</guid><description>关于外部中断与状态机设计</description><pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note[声明]
该文档没有使用任何AIGC内容，主要参考了STM32F10xxx的参考手册。
:::&lt;/p&gt;
&lt;h1&gt;硬件结构&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/stm32-exti_block.webp&quot; alt=&quot;外部中断框图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;外设配置&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GPIO：使用IPU模式即可&lt;/li&gt;
&lt;li&gt;AFIO：选择连接到中断线的具体引脚&lt;/li&gt;
&lt;li&gt;EXTI：配置触发方式&lt;/li&gt;
&lt;li&gt;NVIC：配置中断优先级&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;中断函数触发流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;由输入线经过边沿检测电路，依据EXTI的配置输出对应的触发信号&lt;/li&gt;
&lt;li&gt;进入请求挂起寄存器, 将对应位置1 (处理完中断后需要手动清除)&lt;/li&gt;
&lt;li&gt;若配置了NVIC对应的中断函数，则中断屏蔽寄存器对应位将为1, 信号将送至NVIC中断控制器&lt;/li&gt;
&lt;li&gt;NVIC中断控制器将根据优先级来自动处理函数调用&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;软件实现&lt;/h1&gt;
&lt;h2&gt;优先级分组&lt;/h2&gt;
&lt;p&gt;NVIC 将中断的优先级分为“抢占优先级”与“响应优先级”两种，中断的优先级通过 4bit 的寄存器来存储。
通过设置优先级分组，可以划分这两种优先级使用的位数。分组编号就对应了抢占位。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;下面的表格中，“抢占位”指抢占式优先级使用的位数，“抢占级数”指最多有几个不同的抢占优先级&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分组&lt;/th&gt;
&lt;th&gt;抢占位&lt;/th&gt;
&lt;th&gt;响应位&lt;/th&gt;
&lt;th&gt;抢占级数&lt;/th&gt;
&lt;th&gt;响应级数&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;组0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;没有中断嵌套&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;组1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;组2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;通用&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;组3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;组4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;复杂的中断嵌套&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;基于标准库的代码模板&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;void EXTI_Config(){
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 配置 PA1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &amp;amp;GPIO_InitStructure);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    // Line1 中断线使用来自 GPIOA 的信号
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);

    EXTI_InitStructure.EXTI_Line    = EXTI_Line1;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;  // EXTI 中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 按下时触发
    EXTI_Init(&amp;amp;EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级
    NVIC_Init(&amp;amp;NVIC_InitStructure);
}

// EXTI_Line1 的中断处理函数，可从 startup_stm32xxx.s 中查到
void EXTI1_IRQHandler(){
    // 避免错误进入中断
    if(EXTI_GetITStatus(EXTI_Line1) != RESET){
        // 处理中断, 不要在这儿写复杂的运算或是Delay

        // 清除请求挂起寄存器对应位
        EXTI_ClearITPendingBit(EXTI_Line1);
    }
}

int main(void){
  // 务必配置中断优先级分组
  NVIC_SetPriorityGrouping(2);
  EXTI_Config();

  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;关于状态机&lt;/h2&gt;
&lt;p&gt;可以声明一个全局 state 变量，仅在中断函数处理状态转移的逻辑，在main函数中编写具体的动作&lt;/p&gt;
</content:encoded></item><item><title>一些可能有用的资源</title><link>https://rinx.nz/posts/resource/</link><guid isPermaLink="true">https://rinx.nz/posts/resource/</guid><description>啥都有</description><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;工具网站类&lt;/h1&gt;
&lt;h2&gt;下载解析&lt;/h2&gt;
&lt;h3&gt;闪链&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;功能：解析&lt;strong&gt;百度网盘&lt;/strong&gt;和&lt;strong&gt;夸克&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;官网链接：https://mf.dp.wpurl.cc/user/parse&lt;/li&gt;
&lt;li&gt;记录于 2025/12/29&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结合 &lt;a href=&quot;https://motrix.app/&quot;&gt;Motrix&lt;/a&gt; 等多线程下载器可以获得更好的体验。&lt;/p&gt;
&lt;h2&gt;镜像站&lt;/h2&gt;
&lt;h3&gt;GitHub Proxy&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;发布站链接：https://ghproxy.link/&lt;/li&gt;
&lt;li&gt;目前可用链接：&lt;a href=&quot;https://ghfast.top/&quot;&gt;https://ghfast.top&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;记录于 2025/12/21&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;工具软件类&lt;/h1&gt;
&lt;h2&gt;Android&lt;/h2&gt;
&lt;h3&gt;MacroDroid&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一款功能强大的&lt;strong&gt;任务自动化&lt;/strong&gt;和配置应用程序&lt;/li&gt;
&lt;li&gt;破解版链接 (果核)：https://www.ghxi.com/macrodroid.html&lt;/li&gt;
&lt;li&gt;记录于 2025/12/30&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[STM32] TIM 定时器</title><link>https://rinx.nz/posts/stm32-tim/</link><guid isPermaLink="true">https://rinx.nz/posts/stm32-tim/</guid><description>较为系统的记录了通用定时器的硬件组成与运行逻辑</description><pubDate>Wed, 24 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note[声明]
该文档没有使用任何AIGC内容，主要参考了STM32F10xxx的参考手册。
:::&lt;/p&gt;
&lt;h1&gt;硬件结构&lt;/h1&gt;
&lt;h2&gt;核心：时基单元&lt;/h2&gt;
&lt;p&gt;该部分是实现&quot;定时&quot;功能的基础，虽然名字是&quot;时基&quot;，但应该被看作是一个&lt;strong&gt;计数器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;其输入是来自触发控制器的时钟信号，可暂时先视作系统时钟信号。&lt;/p&gt;
&lt;h3&gt;硬件组成&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;运行逻辑&lt;/th&gt;
&lt;th&gt;相关组件&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16位预分频器 &lt;strong&gt;PSC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;遇到上升沿时输出一个脉冲给CNT，然后等待 N 个脉冲 (不输出)&lt;/td&gt;
&lt;td&gt;对应的影子寄存器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16位计数器 &lt;strong&gt;CNT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;每遇到一个上升沿，寄存器就执行一次计数 (有多种模式)&lt;/td&gt;
&lt;td&gt;&quot;触发控制器&quot;的CNT控制器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16位自动重装载寄存器 &lt;strong&gt;ARR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;根据向上/向下/中央对齐模式操作CNT，并产生更新事件与更新中断&lt;/td&gt;
&lt;td&gt;对应的影子寄存器&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;例如，有一个 72MHz 的时钟信号作为输入。若设置为向上计数，PSC = (7200-1)，ARR = (10000-1)。则预分频器会输出$\frac{72\text{MHz}}{7200} = 10000\text{Hz}$ 的信号，又由于ARR会在 CNT==(10000-1) 的时候产生更新事件，所以最终的效果就是每秒产生一次更新。&lt;/p&gt;
&lt;p&gt;:::note[关于中断和事件]
在此处，更新事件只会影响 CNT 与硬件电路（如 TRGO），而各个中断可以配置 NVIC 或 DMA 来处理
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;影子寄存器&lt;/strong&gt;(预加载寄存器)：在定时器运行的过程中，可能需要对PSC或ARR做修改，而计数器仍在运行，这可能会诱发不可预料的错误。而影子寄存器可以先记录要修改的目标值，在&lt;strong&gt;下一次触发更新事件&lt;/strong&gt;时，再将修改同步到实际寄存器&lt;/p&gt;
&lt;p&gt;:::tip[影子寄存器需要手动激活]
例如ARR的影子寄存器，需要使用&lt;code&gt;TIM_ARRPreloadConfig()&lt;/code&gt;函数使能
:::&lt;/p&gt;
&lt;h3&gt;向上计数模式&lt;/h3&gt;
&lt;p&gt;计数器从 0 开始向上计数到 ARR，然后产生一个更新事件并将 CNT 置 0&lt;/p&gt;
&lt;h3&gt;向下计数模式&lt;/h3&gt;
&lt;p&gt;计数器从 ARR 开始向下计数到 0，然后产生一个更新事件并将 CNT 置 ARR&lt;/p&gt;
&lt;h3&gt;中央对齐模式&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;ToDo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;通用定时器 的结构与运行逻辑&lt;/h2&gt;
&lt;p&gt;对于通用定时器，应该将其视为一个“&lt;strong&gt;可编程的通用计数器&lt;/strong&gt;”。其主要结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/stm32-tim-general-purpose_timer_block.webp&quot; alt=&quot;通用定时器框图 (英文版)&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;触发器信号输入部分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;内部时钟 &lt;strong&gt;CK_INT&lt;/strong&gt;：来自RCC的 TIMxCLK&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ETRF&lt;/strong&gt; (处理后的外部时钟信号)：由 ETR (外部触发引脚) 的信号经过 极性选择、边沿检测、预分频 (Prescaler) 得到 &lt;strong&gt;ETRP&lt;/strong&gt;，再经过滤波得到 ETRF，发送到&lt;strong&gt;触发控制器&lt;/strong&gt;用于外部时钟模式2（&lt;strong&gt;直接输入时基单元&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ITR&lt;/strong&gt; (Internal Trigger 内部触发信号)：由四路 &lt;strong&gt;ITRx&lt;/strong&gt; 信号经过 MUX (多路复用器)得到&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TRC&lt;/strong&gt; (Timer Remote Control 定时器遥控信号)：由 ITR 与 TI1F_ED (TI1信号经过滤波和双边沿检测，常用于 三相无刷直流电机) 经过 MUX 得到&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TRGI&lt;/strong&gt; (Trigger Input 触发器输入信号)：由 ETRF、TRC、TI1FP1、TI2FP2 经过 触发选择器(&lt;strong&gt;TS&lt;/strong&gt;) 得到，作为从模式的输入信号&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;触发控制器&lt;/h3&gt;
&lt;p&gt;作为整个定时器的&lt;strong&gt;信号路由&lt;/strong&gt;，负责处理输入与输出，并控制CNT的工作。可以选择以下时钟源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内部时钟模式&lt;/strong&gt;：（&lt;strong&gt;默认状态&lt;/strong&gt;）使用系统时钟，&lt;strong&gt;CK_INT&lt;/strong&gt; 信号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部时钟模式2&lt;/strong&gt;：直接使用 &lt;strong&gt;ETRF&lt;/strong&gt; 信号，注意&lt;strong&gt;不是从 TRGI 得到的&lt;/strong&gt;信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所选的时钟源的信号将被&lt;strong&gt;直接发送到CK_PSC&lt;/strong&gt;，而不经过从模式控制器。这意味着它可以&lt;strong&gt;与从模式中的复位、门控、触发模式一起使用&lt;/strong&gt;，这也是两种外部时钟模式的区别。&lt;/p&gt;
&lt;p&gt;:::note[时钟源的配置]
初始化定时器时，默认会使用内部时钟作为时钟源。
:::&lt;/p&gt;
&lt;p&gt;触发控制器&lt;strong&gt;可以同时开启&lt;/strong&gt;两种模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;从模式&lt;/strong&gt; (Slave controller mode)：&lt;strong&gt;接收&lt;/strong&gt;外部信号 &lt;strong&gt;TRGI&lt;/strong&gt;，控制CNT&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外部时钟模式1&lt;/strong&gt;：来自&lt;strong&gt;TRGI&lt;/strong&gt;的信号将被发送到 &lt;strong&gt;CK_PSC&lt;/strong&gt;，作为时基单元的预分频器的输入。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;复位模式&lt;/strong&gt;：&lt;strong&gt;每次&lt;/strong&gt;收到信号，CNT将被&lt;strong&gt;清零&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;门控模式&lt;/strong&gt;：只有在&lt;strong&gt;收到信号时&lt;/strong&gt;，CNT才会&lt;strong&gt;计数&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;触发模式&lt;/strong&gt;：收到信号，CNT将被&lt;strong&gt;使能&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编码器模式&lt;/strong&gt;：这是一种特殊的从模式，开启后从模式控制器将自动控制 CNT 进行自增或自减。相当于使用了一个带有方向选择的外部时钟。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编码器模式1&lt;/strong&gt;：2倍频。只在 A 相跳变时计数，看 B 相电平判方向&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码器模式2&lt;/strong&gt;：2倍频。只在 B 相跳变时计数，看 A 相电平判方向&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码器模式3&lt;/strong&gt;：4倍频 (&lt;strong&gt;最常用&lt;/strong&gt;)。AB 两相跳变时都计数，看另一相电平判方向，精度最高&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note[关于编码器模式下的ARR]
通常将ARR设置为65535，这样就可以利用补码的特性来很好的处理正反转
例如，编码器接口收到了两个反转信号，CNT 变为65534。又收到两个正转信号，CNT 先加到65535，再加一次就是0
:::&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;主模式&lt;/strong&gt; (Master controller mode)：将内部的事件&lt;strong&gt;输出&lt;/strong&gt;为外部信号 &lt;strong&gt;TRGO&lt;/strong&gt;（发往其它定时器实现级联，或发往 DAC/ADC）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;复位&lt;/strong&gt;：复位事件发生时，发送一个正脉冲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使能&lt;/strong&gt;：将 &lt;strong&gt;CNT_EN&lt;/strong&gt; 作为 TRGO，只要计数器在运行就是高电平&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新&lt;/strong&gt;：当更新事件发生时，发送一个正脉冲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;比较脉冲&lt;/strong&gt;：在输入通道1 (&lt;strong&gt;IC1&lt;/strong&gt;) 发生一次捕获或一次比较成功时，将要设置 &lt;strong&gt;CC1IF&lt;/strong&gt; 标志时，发送一个正脉冲&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;四种比较模式&lt;/strong&gt;：对应四个通道 &lt;strong&gt;ICx&lt;/strong&gt;，将 &lt;strong&gt;OCxREF&lt;/strong&gt; (&lt;strong&gt;Output Compare x Reference Signal&lt;/strong&gt; 输出比较 x 参考信号) 作为 TRGO&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;输入/输出部分&lt;/h3&gt;
&lt;p&gt;该部分有四路，每路有一个输入捕获单元和一个输出比较单元（只能选择一个开启）。该部分的核心是 &lt;strong&gt;CCRx&lt;/strong&gt;（捕获/比较寄存器），输入与输出都要操作该寄存器。
&lt;strong&gt;CCRx&lt;/strong&gt; 有对应的影子寄存器（预装载寄存器），可配置开启。&lt;/p&gt;
&lt;h3&gt;输入捕获单元&lt;/h3&gt;
&lt;p&gt;以第一路通道举例。信号从 TIMx_CH1 外部引脚输入，经过滤波和边沿检测得到 &lt;strong&gt;TI1&lt;/strong&gt;，之后分为两路。&lt;strong&gt;TI1FP1&lt;/strong&gt; 通往 &lt;strong&gt;IC1&lt;/strong&gt; 的 MUX，&lt;strong&gt;TI1FP2&lt;/strong&gt; 通往 &lt;strong&gt;IC2&lt;/strong&gt; 的 MUX；经过 &lt;strong&gt;预分频&lt;/strong&gt; 后得到触发信号，触发时 &lt;strong&gt;CNT 将被复制到 CCR1&lt;/strong&gt; (捕获/比较寄存器)。&lt;/p&gt;
&lt;p&gt;:::note[关于编码器模式]
而在编码器模式中，信号&lt;strong&gt;不经过边沿检测器&lt;/strong&gt;就直接进入了编码器接口，因此 TIM_ICPolarity 在这里指的是 &lt;strong&gt;是否要对输入信号做反相处理&lt;/strong&gt;。如果发现编码器接口中得到的数值与实际正好相反，就可以改变某一个 IC 的 TIM_ICPolarity，省去了换线的麻烦。
:::&lt;/p&gt;
&lt;h3&gt;输出比较单元&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;对于 OCx&lt;/strong&gt; (OC1 ~ OC4)，它会 &lt;strong&gt;根据 CNT、CCRx 与设定的输出比较模式&lt;/strong&gt;，将结果输出到 &lt;strong&gt;OCxREF&lt;/strong&gt;，并触发 &lt;strong&gt;CCxI&lt;/strong&gt; 中断，若触发该中断时，中断标志位非0 (即之前的中断未被处理完)，会将 &lt;strong&gt;CC1OF&lt;/strong&gt; 置1（表示系统未能及时处理捕获信号）&lt;/p&gt;
&lt;p&gt;有下面几种&lt;strong&gt;输出比较模式&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;冻结&lt;/strong&gt;：REF 保持原状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;匹配时置有效电平&lt;/strong&gt;：CNT==CCRx 时，REF 置有效电平（不适合输出连续信号）
:::note[有效电平]
在通用计时器中，有效电平即为高电平。
:::&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;匹配时置无效电平&lt;/strong&gt;：CNT==CCRx 时，REF 置无效电平（不适合输出连续信号）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;匹配时电平翻转&lt;/strong&gt;：CNT==CCRx 时，REF 电平翻转
:::note[输出一个占空比始终为50%的波形]
设置CNT=0，定时器就会在每次更新时翻转电平，因此周期为ARR的两倍，
:::&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;强制为无效电平&lt;/strong&gt;：REF 强制为无效电平&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;强制为有效电平&lt;/strong&gt;：REF 强制为有效电平&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PWM模式1&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;向上计数&lt;/strong&gt;：$\text{CNT} &amp;lt; \text{CCRx}$ 时，REF 置有效电平；$\text{CNT} \geq \text{CCRx}$ 时， REF 置无效电平&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向下计数&lt;/strong&gt;：$\text{CNT} \leq \text{CCRx}$ 时，REF 置有效电平；$\text{CNT} &amp;gt; \text{CCRx}$ 时， REF 置无效电平&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PWM模式2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;向上计数&lt;/strong&gt;：$\text{CNT} &amp;lt; \text{CCRx}$ 时，REF 置有效电平；$\text{CNT} \geq \text{CCRx}$ 时， REF 置无效电平&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向下计数&lt;/strong&gt;：$\text{CNT} \leq \text{CCRx}$ 时，REF 置有效电平；$\text{CNT} &amp;gt; \text{CCRx}$ 时， REF 置无效电平&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note[关于PWM模式]
模式2 与 模式1 的输出完全反相；一般较少使用向下计数。对于模式1的向上计数，有如下参数：
PWM频率：　　Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比：　Duty = CCR / (ARR + 1)
PWM分辨率：　Reso = 1 / (ARR + 1)，也就是占空比最小变化步距，越小越好。
:::
:::note[快速使能模式]
&lt;strong&gt;仅当开启从模式并由外部信号触发，且使用PWM输出时&lt;/strong&gt;，该模式才有用。
开启该模式后，会跳过部分同步逻辑，缩短信号的输出延迟（牺牲微小的逻辑同步稳定性）
使用&lt;code&gt;TIM_OCxFastConfig&lt;/code&gt;开启
:::&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::important[注意]
输入捕获 和 输出比较 &lt;strong&gt;不能同时开启&lt;/strong&gt;，因为它们共用 CCRx
:::&lt;/p&gt;
&lt;h3&gt;输出控制&lt;/h3&gt;
&lt;p&gt;负责将 REF 转化为 GPIO 的输出&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;极性选择&lt;/strong&gt;：设置&lt;strong&gt;有效电平&lt;/strong&gt;极性（为高电平或低电平），参数为  &lt;code&gt;TIM_OCPolarity&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出使能&lt;/strong&gt;：参数为 &lt;code&gt;TIM_OutputState&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;基本定时器 的结构与运行逻辑&lt;/h2&gt;
&lt;p&gt;基本定时器由一个最简单的触发控制器和一个时基单元组成，大概只能处理简单的定时任务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/stm32-tim-basic_timer_block.webp&quot; alt=&quot;基本定时器框图 (英文版)&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;高级定时器 的结构与运行逻辑&lt;/h2&gt;
&lt;p&gt;相比通用定时器，其在时基单元后连接了 &lt;strong&gt;重复次数计数器&lt;/strong&gt;；在输出部分增加了 &lt;strong&gt;DTG 死区生成电路&lt;/strong&gt;；输出引脚变为了两个&lt;strong&gt;互补的输出&lt;/strong&gt;，可用来控制三相无刷电机；接入了&lt;strong&gt;刹车输入&lt;/strong&gt;与&lt;strong&gt;时钟安全系统&lt;/strong&gt;，用于在意外情况切断输出。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ToDo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;基于标准库的代码&lt;/h1&gt;
&lt;h2&gt;初始化一个最简单的通用定时器&lt;/h2&gt;
&lt;p&gt;以下代码用于初始化使用&lt;strong&gt;内部时钟&lt;/strong&gt;的，每秒触发一次中断的TIM2。&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化 TIM2 时钟&lt;/li&gt;
&lt;li&gt;初始化 TIM2&lt;/li&gt;
&lt;li&gt;打开 TIM2 的更新中断&lt;/li&gt;
&lt;li&gt;配置 NVIC 处理更新中断&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void TIM_Config(){
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    TIM_InternalClockConfig(TIM2);                                    // 配置TIM2使用内部时钟
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;       // 对定时器时钟的分频操作
    TIM_TimeBaseInitStructure.TIM_CounterMode   = TIM_CounterMode_Up; // 向上计数
    TIM_TimeBaseInitStructure.TIM_Prescaler     = 0;                  // 预分频器
    TIM_TimeBaseInitStructure.TIM_Period        = 10 - 1;             // 自动重装载寄存器的值
    TIM_TimeBaseInit(TIM2, &amp;amp;TIM_TimeBaseInitStructure);

    TIM_ClearFlag(TIM2, TIM_IT_Update);  // 避免启用后立刻触发的中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_Init(&amp;amp;NVIC_InitStructure);

    TIM_Cmd(TIM2, ENABLE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip[注意设置NVIC优先级分组]&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);&lt;/code&gt;
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;初始化一个通用定时器，用于输出PWM&lt;/h2&gt;
&lt;p&gt;该示例可用于驱动SG90舵机（让PA1输出周期为20ms的PWM）&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启 GPIO 与 TIM 的时钟&lt;/li&gt;
&lt;li&gt;配置时基单元&lt;/li&gt;
&lt;li&gt;配置对应通道的输出比较单元&lt;/li&gt;
&lt;li&gt;初始化 GPIO&lt;/li&gt;
&lt;li&gt;开启 TIM&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void PWM_Config(){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 72MHz * 0.020 = 72*20000
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;       // 配置时钟分频，此处设置为不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode   = TIM_CounterMode_Up; // 向上计数
    TIM_TimeBaseInitStructure.TIM_Prescaler     = 72-1;               // 预分频 (PSC)
    TIM_TimeBaseInitStructure.TIM_Period        = 20000-1;            // 周期   (ARR)
    TIM_TimeBaseInit(TIM2, &amp;amp;TIM_TimeBaseInitStructure);

    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;        // PWM1模式
    TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_High;    // 有效电平为高电平
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能
    TIM_OCInitStructure.TIM_Pulse       = 1500;                   // CCR初始值为1500 (高电平宽度 1.5ms)
    TIM_OC2Init(TIM2, &amp;amp;TIM_OCInitStructure);                      // 配置通道2 (输出比较单元2)

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &amp;amp;GPIO_InitStructure);

    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); // 开启CCR的影子寄存器
    TIM_ARRPreloadConfig(TIM2, ENABLE);               // 开启ARR的影子寄存器

    TIM_Cmd(TIM2, ENABLE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;初始化一个通用定时器，用测周法测量 PWM&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;测周法：在两个上升沿内，以标准频率 $f_c$ 记次，得到 N，则频率 $f_x = \frac{f_c}{N}$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;该示例中，$f_c = \frac{72\text{MHz}}{72} = 1\text{MHz}$。
每个上升沿，IC1 都会将 CNT 复制到 CCR1。随即从模式控制器将 CNT 复位。因此只需要读取CCR1，就可以得到最新的N。
周期值（注意单位换算）：&lt;code&gt;TIM_GetCapture1(TIM3)+1&lt;/code&gt;
频率值：&lt;code&gt;(1000000)/(TIM_GetCapture1(TIM3)+1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::warning[注意核算可测量的最小频率值]
此处的最小频率为 $\frac{1000000}{65535} \approx 15.26\text{Hz} $
:::&lt;/p&gt;
&lt;p&gt;同理，将 TI1FP2 信号输入 IC2，并在每个下降沿将 CNT 复制到 CCR2，就可以得到高电平在一个周期中的占比，通过计算可以得到占空比（此处计算结果为千分值） &lt;code&gt;(TIM_GetCapture2(TIM3)+1)*1000/(TIM_GetCapture1(TIM3)+1)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启 GPIO 与 TIM 的时钟&lt;/li&gt;
&lt;li&gt;配置时基单元&lt;/li&gt;
&lt;li&gt;配置输入捕获单元&lt;/li&gt;
&lt;li&gt;选择从模式-复位模式&lt;/li&gt;
&lt;li&gt;选择输入触发信号的来源 (TRGI)&lt;/li&gt;
&lt;li&gt;初始化 GPIO&lt;/li&gt;
&lt;li&gt;开启 TIM&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void PWMInput_Config(){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPD; // 注意此处使用了下拉输入
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &amp;amp;GPIO_InitStructure);

    // 72MHz = 72*1000000 (1s)
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;       // 配置时钟分频，此处设置为不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode   = TIM_CounterMode_Up; // 向上计数
    TIM_TimeBaseInitStructure.TIM_Prescaler     = 72-1;             // 预分频 (PSC)
    TIM_TimeBaseInitStructure.TIM_Period        = 65536-1;            // 周期   (ARR)
    TIM_TimeBaseInit(TIM3, &amp;amp;TIM_TimeBaseInitStructure);

    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;                // 配置IC1
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                         // 不启用滤波
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;     // 上升沿触发
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;           // 不分频
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // TI1 连接到 IC1（直连）
    TIM_ICInit(TIM3, &amp;amp;TIM_ICInitStructure);

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
    TIM_ICInit(TIM3, &amp;amp;TIM_ICInitStructure);

    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); // 选择从模式-复位模式
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);    // 选择 TI1FP1 作为 TRGI

    TIM_ARRPreloadConfig(TIM3, ENABLE);

    TIM_Cmd(TIM3, ENABLE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;初始化一个通用定时器，用标准库的PWMI模式测PWM&lt;/h2&gt;
&lt;p&gt;PWMI模式&lt;strong&gt;在硬件层面是不存在的&lt;/strong&gt;，是标准库为了简化程序而引入的函数。如果使用它，则上节代码中配置输入捕获的部分可以改为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_PWMIConfig(TIM3, &amp;amp;TIM_ICInitStructure);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[关于 &lt;code&gt;TIM_PWMIConfig()&lt;/code&gt;]
只需要传入一个通道的初始化结构体，函数会自动将另一个通道设置成对应的配置。&lt;/p&gt;
&lt;p&gt;例如：传入 &lt;strong&gt;通道1，直连，上升沿&lt;/strong&gt;。函数就会自动配置 &lt;strong&gt;通道2，交叉，下降沿&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但注意&lt;strong&gt;不能传入通道3或通道4&lt;/strong&gt;
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;初始化一个通用定时器，读取编码器&lt;/h2&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启 GPIO 与 TIM 的时钟&lt;/li&gt;
&lt;li&gt;初始化 GPIO 为输入模式&lt;/li&gt;
&lt;li&gt;配置时基单元（不分频）&lt;/li&gt;
&lt;li&gt;配置输入捕获单元&lt;/li&gt;
&lt;li&gt;配置编码器接口模式&lt;/li&gt;
&lt;li&gt;开启 TIM&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void EncoderInput_Config(){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU; // 注意此处使用了上拉输入
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &amp;amp;GPIO_InitStructure);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseStructInit(&amp;amp;TIM_TimeBaseInitStructure);
    TIM_TimeBaseInitStructure.TIM_Prescaler     = 0;       // 预分频 (PSC)
    TIM_TimeBaseInitStructure.TIM_Period        = 0xFFFF;  // 周期   (ARR)
    TIM_TimeBaseInit(TIM3, &amp;amp;TIM_TimeBaseInitStructure);

    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICStructInit(&amp;amp;TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;   // 滤波拉满
    TIM_ICInit(TIM3, &amp;amp;TIM_ICInitStructure);
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;   // 滤波拉满
    TIM_ICInit(TIM3, &amp;amp;TIM_ICInitStructure);

    // 开启TIM3的编码器接口，模式3，两个通道均不反相
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);

    TIM_Cmd(TIM3, ENABLE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note[如何测速]
由于硬件限制，是无法做到硬件层面的测速的。最佳的方法就是再配置一个定时器，定时去读取编码器测量的结果，并计算速度。
:::&lt;/p&gt;
</content:encoded></item><item><title>Gemini-CLI 登录失败的解决方案以及使用技巧</title><link>https://rinx.nz/posts/gemini-cli/</link><guid isPermaLink="true">https://rinx.nz/posts/gemini-cli/</guid><description>Powershell的设置给我坑惨了</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;代理软件的设置&lt;/h2&gt;
&lt;p&gt;开启系统代理，开启TUN模式，确保所使用的节点能正常访问 &lt;a href=&quot;https://gemini.google.com/app&quot;&gt;Gemini&lt;/a&gt; （注意部分送中节点可能无法解锁Gemini）&lt;/p&gt;
&lt;p&gt;开启PowerShell，推荐使用&lt;a href=&quot;https://apps.microsoft.com/detail/9n8g5rfz9xk3?hl=zh-CN&amp;amp;gl=CN&quot;&gt;终端预览&lt;/a&gt;，cd到项目目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$env:https_proxy=&quot;http://127.0.0.1:7897&quot;
gemini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
请根据你代理软件的设置，修改上述代码中的端口
:::&lt;/p&gt;
&lt;h2&gt;使用技巧&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;注意 Gemini-CLI 不会主动读取文件的更新，因此在手动修改代码后，需要在下一次交互时提醒Gemini文件已经更新&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Mindows 在低电量时自动切换到 Android 系统</title><link>https://rinx.nz/posts/mindows-low_battery-auto_switch/</link><guid isPermaLink="true">https://rinx.nz/posts/mindows-low_battery-auto_switch/</guid><description>解决在windows系统下意外关机·并且充电功率极低的 尴尬场景</description><pubDate>Thu, 18 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前景提要&lt;/h2&gt;
&lt;p&gt;阴差阳错地购买了 小米平板5，又阴差阳错的发现了 WOA 和 Mindows，于是有了接下来的故事。&lt;/p&gt;
&lt;p&gt;经过一番折腾，编写了一个bat脚本，成功解决了 在windows系统下意外关机·并且充电功率极低的 尴尬场景。虽然是个小东西，但苦于找不到资料，也并不算轻松&lt;/p&gt;
&lt;h2&gt;功能&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;检测到电量较低时，发出警告通知&lt;/li&gt;
&lt;li&gt;检测到电量过低时，发出警告并在一分钟后自动重启到 Android 系统&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@echo off
:loop
for /f &quot;skip=1 tokens=2 delims==&quot; %%i in (&apos;wmic Path Win32_Battery Get EstimatedChargeRemaining /value&apos;) do set batteryLevel=%%i

if %batteryLevel% leq 10 (
    start /min powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command &quot;[Reflection.Assembly]::LoadWithPartialName(&apos;System.Windows.Forms&apos;);$obj=New-Object Windows.Forms.NotifyIcon;$obj.Icon = [drawing.icon]::ExtractAssociatedIcon($PSHOME + &apos;\powershell.exe&apos;);$obj.Visible = $True;$obj.ShowBalloonTip(60000, &apos;Mindows 低电量警告&apos;,&apos;电量过低！将在 1 分钟后 强制 切换到Android！&apos;,3)&quot;
    timeout /t 75
    :reboot
    call &quot;c:\\Mindows工具包\\Mindows一键切换\\Mindows一键切换.bat&quot;
    
    timeout /t 20
    start /min powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command &quot;[Reflection.Assembly]::LoadWithPartialName(&apos;System.Windows.Forms&apos;);$obj=New-Object Windows.Forms.NotifyIcon;$obj.Icon = [drawing.icon]::ExtractAssociatedIcon($PSHOME + &apos;\powershell.exe&apos;);$obj.Visible = $True;$obj.ShowBalloonTip(60000, &apos;Mindows 系统切换 失败！&apos;,&apos;脚本运行失败，将在三十秒内重试&apos;,3)&quot;
    timeout /t 45
    goto reboot
) else if %batteryLevel% leq 20 (
    start /min powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command &quot;[Reflection.Assembly]::LoadWithPartialName(&apos;System.Windows.Forms&apos;);$obj=New-Object Windows.Forms.NotifyIcon;$obj.Icon = [drawing.icon]::ExtractAssociatedIcon($PSHOME + &apos;\powershell.exe&apos;);$obj.Visible = $True;$obj.ShowBalloonTip(200000, &apos;Mindows 低电量警告&apos;,&apos;电量低！请保存数据并准备切换到 Android！&apos;,2)&quot;
    timeout /t 300
    goto loop
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
请根据实际情况替换程序中的路径 (指这行命令 &lt;code&gt;call &apos;C:\Mindows工具包\Mindows一键切换\Mindows一键切换.bat&apos;&lt;/code&gt;)
:::&lt;/p&gt;
&lt;p&gt;:::important
&lt;strong&gt;请保存为 ANSI 编码&lt;/strong&gt;，如果使用记事本创建该脚本，则可以在 &lt;strong&gt;另存为&lt;/strong&gt; 处找到该功能
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;接下来，通过一个vbs来实现静默启动。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set ws=WScript.CreateObject(&quot;WScript.Shell&quot;)
ws.Run &quot;c:\Mindows工具包\RinStel-低电量警告.bat&quot;,0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后，将其移动到 &lt;code&gt;C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp&lt;/code&gt; (系统的自启动文件夹)，顺便双击运行测试。&lt;/p&gt;
&lt;p&gt;:::tip
请根据实际情况替换程序中的路径
:::&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/junyidcf/article/details/122445851&quot;&gt;windows cmd balloon notification&lt;/a&gt;
&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.notifyicon.showballoontip?view=windowsdesktop-8.0&quot;&gt;NotifyIcon.ShowBalloonTip Method - microsoft&lt;/a&gt;
&lt;a href=&quot;https://blog.csdn.net/plasma007/article/details/128259287&quot;&gt;powershell实现发送win10系统通知&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>解决 MTP传输后中文文件名乱码 的问题</title><link>https://rinx.nz/posts/chinese-filename-garbled-after-mtp-transfer/</link><guid isPermaLink="true">https://rinx.nz/posts/chinese-filename-garbled-after-mtp-transfer/</guid><description>需要从 UTF-8 编码转换至 GBK</description><pubDate>Fri, 03 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;病症&lt;/h2&gt;
&lt;p&gt;文件自手机传输至电脑后，原本正常的文件名中的汉字会变成乱码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls

101_澹板満澧炵泭.xml         103_閫氱敤褰遍煶浼樺寲.xml   105_浜哄０浼樺寲+澹板満澧炵泭_闊充箰.xml
102_浣庨煶+澹板満澧炵泭.xml  104_浜哄０浼樺寲_鐢靛奖.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;p&gt;需要将文件名从 UTF-8 编码转换至 GBK&lt;/p&gt;
&lt;h3&gt;Linux&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt-get install convmv
$ cd &amp;lt;TargetPath&amp;gt;
$ convmv -f UTF-8 -t GBK --notest *
mv &quot;./101_澹板満澧炵泭.xml&quot;     &quot;./101_声场增益.xml&quot;
mv &quot;./102_浣庨煶+澹板満澧炵泭.xml&quot;      &quot;./102_低音+声场增益.xml&quot;
mv &quot;./103_閫氱敤褰遍煶浼樺寲.xml&quot;       &quot;./103_通用影音优化.xml&quot;
mv &quot;./104_浜哄０浼樺寲_鐢靛奖.xml&quot;      &quot;./104_人声优化_电影.xml&quot;
mv &quot;./105_浜哄０浼樺寲+澹板満澧炵泭_闊充箰.xml&quot; &quot;./105_人声优化+声场增益_音乐.xml&quot;
Ready! I converted 5 files in 0 seconds.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 切换到目标目录 (例如：cd C:\Your\Files)
$targetPath = Get-Location

Get-ChildItem -Path $targetPath | ForEach-Object {
    $originalName = $_.Name
    try {
        # 假设文件名是 UTF-8 编码，但被系统错误地显示为 ANSI/GBK
        # 1. 将 Unicode 字符串（Windows显示的文件名）编码成 UTF-8 字节数组
        $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($originalName)

        # 2. 将 UTF-8 字节数组解码成 GBK 字符串
        #    注意：这里是为了生成一个GBK兼容的字符串，用于重命名。
        #    如果原始文件名是“正常的”Unicode，并且你只是想创建一个GBK版本的文件名，
        #    那么可能会有更好的方法。
        $gbkString = [System.Text.Encoding]::GetEncoding(&quot;GBK&quot;).GetString($utf8Bytes)

        # 3. 如果需要，可以进一步处理 $gbkString，例如替换一些特殊字符

        # 检查是否需要重命名
        if ($originalName -ne $gbkString) {
            $oldPath = $_.FullName
            $newPath = Join-Path $_.Directory.FullName $gbkString
            Write-Host &quot;尝试重命名: &apos;$originalName&apos; -&amp;gt; &apos;$gbkString&apos;&quot;
            Rename-Item -Path $oldPath -NewName $gbkString -ErrorAction Stop
            Write-Host &quot;成功重命名: &apos;$originalName&apos; -&amp;gt; &apos;$gbkString&apos;&quot;
        } else {
            Write-Host &quot;文件名 &apos;$originalName&apos; 无需转换或转换后相同。&quot;
        }
    }
    catch {
        Write-Warning &quot;处理文件名 &apos;$originalName&apos; 时出错: $_&quot;
    }
}

Write-Host &quot;文件名转换完成。&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>