<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>hanjin的个人博客</title>
  
  
  <link href="https://syhanjin.moe/atom.xml" rel="self"/>
  
  <link href="https://syhanjin.moe/"/>
  <updated>2025-12-31T09:18:23.000Z</updated>
  <id>https://syhanjin.moe/</id>
  
  <author>
    <name>syhanjin</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>2025年终总结</title>
    <link href="https://syhanjin.moe/20251231/3bbbb225565c/"/>
    <id>https://syhanjin.moe/20251231/3bbbb225565c/</id>
    <published>2025-12-31T09:11:06.000Z</published>
    <updated>2025-12-31T09:18:23.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>又是一年年末，今年干了许多事情呢，感觉比高三还长（奇怪，按理说时间应该过得越来越快才对，今年怎么感觉好长）</p><blockquote><p>2025 这一年我的技术水平突飞猛进了，学到的东西感觉比之前 18 年加起来还多（或许是因为之前学的东西都被默认为常识了所以感觉没那么多（？），这就是技术爆炸吗？</p></blockquote><p>先来一张 GitHub contributions 图吧，今年怎么这么活跃。</p><p><img src="/assets/b2e44db69ce6/contributions.png" alt="contributions" /></p><p>本年度是我参与多人协作开发项目的第一年，以往都只是自己一个人开发，没有什么协作经验。多人协作项目确实让我学到了很多东西，我认为这方面是很重要的，一个人开发项目是不可能长久的，但是一群人开发一个项目就动力十足啊。</p><h2 id="年度项目"><a class="markdownIt-Anchor" href="#年度项目"></a> 年度项目</h2><h3 id="桌面宠物寒假~3月初"><a class="markdownIt-Anchor" href="#桌面宠物寒假~3月初"></a> 桌面宠物（寒假~3月初）</h3><p>我的第一个正式硬件项目，自己设计 PCB 板 + 自己焊接 + 自己写代码</p><p><s>半成品</s>成品见我的 B 站</p><div class="post-link-card-wrap"><div class="post-link-card"><a href="https://www.bilibili.com/video/BV1GxRwYKEMX" title="桌面宠物初稿" rel="noopener nofollow noreferrer" target="_blank"></a><div class="post-link-card-cover-wrap"><img src="/assets/b2e44db69ce6/c960e3d1b92a8b7e45e4a28836ccfe8204a9c42f.jpg" class="no-lightbox" title="桌面宠物初稿" alt="桌面宠物初稿"/></div><div class="post-link-card-item-wrap"><div class="post-link-card-title">桌面宠物初稿</div><div class="post-link-card-excerpt"><span class="icon-link"></span>https://www.bilibili.com/video/BV1GxRwYKEMX</div></div></div></div><p>一笔带过好了，也没做得很满意。</p><h3 id="神秘的微信小程序3月初~5月底"><a class="markdownIt-Anchor" href="#神秘的微信小程序3月初~5月底"></a> 神秘的微信小程序（3月初~5月底）</h3><p>虽然总说小程序很吃屎（确实非常吃屎啊，遇到一个不给钱还老改需求的 <strong>甲方</strong>），学到了许多东西确实是真的，认识了许多大佬，积累了许多开发经验（怎么感觉在说废话）</p><p>这个项目我不想多提（提多了都是泪），仓库还是 private 的（我想给它 public 了）</p><h3 id="机创物流赛六月初~8月初"><a class="markdownIt-Anchor" href="#机创物流赛六月初~8月初"></a> 机创物流赛（六月初~8月初）</h3><blockquote><p style="display: flex; justify-content: space-between"><span>不要给反馈加低通滤波</span><span>——一位不愿意透露姓名的网友</span></p></blockquote><p>物流赛的两个月是能力提升最大的两个月，虽然也是作息彻底乱掉的开端（</p><p>虽然最后并没有好的结果（气死我了真是，就差一点点，但凡再多一天）但是也收获了许多调试经验，测试了之前的很多想法（算是奠定了现在使用的项目结构的基础吧）</p><p>现在想来还是觉得我们物流赛打的很牛逼，五个基本没有经验的大一学生，21 天从画图到调试搓了一台车（至于为什么只有 21 天，答案是经验不足之前的一二代车都有巨大问题），时间实在太紧，我又犯了个错误浪费两天（妈的要不是那个鸟人说 120 Hz 频率太低我 TM 怎么会被误导那么久），最后差一点点进国，只要能进国必然是国一。</p><p>哎，非常可惜。</p><p>物流赛的代码由于时间太紧写的不如 shi，所以没有传。</p><h3 id="robocon-20269-月初-~-今"><a class="markdownIt-Anchor" href="#robocon-20269-月初-~-今"></a> Robocon 2026（9 月初 ~ 今）</h3><p>今年的备赛过程中做了很多创新，包括</p><div class="post-link-card-wrap"><div class="post-link-card"><a href="https://hitsz-robocon-2026-x9ah6ibp.notion.site/27ce6e3332a3805e95c3c12ea324c583" title="电控项目开发规范" rel="noopener nofollow noreferrer" target="_blank"></a><div class="post-link-card-cover-wrap auto"><div class="icon-globe"></div></div><div class="post-link-card-item-wrap"><div class="post-link-card-title">电控项目开发规范</div><div class="post-link-card-excerpt"><span class="icon-link"></span>https://hitsz-robocon-2026-x9ah6ibp.notion.site/27ce6e3332a3805e95c3c12ea324c583</div></div></div></div><div class="post-link-card-wrap"><div class="post-link-card"><a href="/20250908/698d9cb67753/" title="STM32+Git 协作方案" ></a><div class="post-link-card-cover-wrap"><img src="/images/banner.webp" class="no-lightbox" title="STM32+Git 协作方案" alt="STM32+Git 协作方案"/></div><div class="post-link-card-item-wrap"><div class="post-link-card-title">STM32+Git 协作方案</div><div class="post-link-card-excerpt">## 前言在多人协作开发时往往需要用到 [Git](https://zh.wikipedia.org/wiki/Git) 来进行版本管理，但是嵌入式开发中有大量的外设初始化配置内容，STM</div></div></div></div><div class="post-link-card-wrap"><div class="post-link-card"><a href="/20251218/b60a458f11ee/" title="新STM32驱动模块化项目概述" ></a><div class="post-link-card-cover-wrap"><img src="/images/banner.webp" class="no-lightbox" title="新STM32驱动模块化项目概述" alt="新STM32驱动模块化项目概述"/></div><div class="post-link-card-item-wrap"><div class="post-link-card-title">新STM32驱动模块化项目概述</div><div class="post-link-card-excerpt">## 背景{% postLinkCard stm32-git协作方案 %}上面的文章中提出了一种 STM32 + Git 多人协作开发方案，该方案虽然能对单个 STM32 项目进行较好的版</div></div></div></div><p>搭了一堆的基建（今年能给小灯留下很多资料），最主要的是建立了战队官方 org: <a href="https://github.com/HITSZ-WTRobot">https://github.com/HITSZ-WTRobot</a></p><p><strong>开发的基建包括：</strong></p><ul><li><p>统一电机驱动仓库：<a href="https://github.com/HITSZ-WTRobot/motor_drivers">https://github.com/HITSZ-WTRobot/motor_drivers</a></p><p>暂时使用 C 语言开发（我究竟是怎么开始手搓 vtable 的）</p></li><li><p>统一底盘控制仓库：<a href="https://github.com/HITSZ-WTRobot/chassis_controller">https://github.com/HITSZ-WTRobot/chassis_controller</a></p><p>实现了许多神奇的功能，可惜现在边走边转不太完善</p></li><li><p>S 形速度曲线规划库：<a href="https://github.com/HITSZ-WTRobot/s-curve-planner">https://github.com/HITSZ-WTRobot/s-curve-planner</a></p><p>一维规划，可以设置：初位置 $x_s$，初速度 $v_s$，初加速度 $a_s$，末位置 $x_e$，最大速度 $v_m$，最大加速度 $a_m$，最大加加速度 $j_m$。末速度和末加速度为零。</p><p>可以在运动过程中通过当前参数修改目标位置并平滑衔接。</p></li></ul><p><strong>一些工具：</strong></p><ul><li><p>STM32 妙妙小工具（基于 STM32CubeMX）：<a href="https://github.com/HITSZ-WTRobot/stm32tool">https://github.com/HITSZ-WTRobot/stm32tool</a></p><p>可以快速创建 STM32 项目并进行一定初始化，同时可以将一部分操作移动到 shell 执行</p></li><li><p>队内自研电调上位机：<a href="https://github.com/HITSZ-WTRobot/ipmesctool">https://github.com/HITSZ-WTRobot/ipmesctool</a></p></li><li><p>带有 STM32CubeMX 和 stm32tool 的 docker 容器，用于自动化构建：<a href="https://github.com/syhanjin/stm32cubemx-docker">https://github.com/syhanjin/stm32cubemx-docker</a></p></li><li><p>一个基于 cmake 的模块化架构尝试：<a href="https://github.com/HITSZ-WTRobot/template-with-build-check">https://github.com/HITSZ-WTRobot/template-with-build-check</a></p></li></ul><h3 id="别的项目"><a class="markdownIt-Anchor" href="#别的项目"></a> 别的项目</h3><h4 id="blossom-朝华重构尝试"><a class="markdownIt-Anchor" href="#blossom-朝华重构尝试"></a> blossom 朝华重构尝试</h4><p>这个项目又㕛叒叕被搁置了，使用 Rust(Rocket) + PostgreSQL + Minio 搭了个基建又没消息了，但是也算是提供了一些 Rocket 开发的经验</p><h4 id="充电宝项目"><a class="markdownIt-Anchor" href="#充电宝项目"></a> 充电宝项目</h4><p>🤔 没成功，两块板子四个芯片焊出了 4 个不同的问题，没辙</p><h4 id="hoa-astro-重构"><a class="markdownIt-Anchor" href="#hoa-astro-重构"></a> hoa &amp; Astro 重构</h4><p>因为精力不足 + 私人原因（前半段是精力不足现在是私人原因）只开发了前半段就跑路了。简单搭建了基本框架，基于之前 Origami404 写的元数据，进一步讨论构建了现有的元数据内容。</p><p>后面就没写了。</p><p>现在开发结果可以在 <a href="https://next.hoa.moe">https://next.hoa.moe</a> 看到</p><h2 id="年度总结"><a class="markdownIt-Anchor" href="#年度总结"></a> 年度总结</h2><p>怎么感觉今年就没停下来过，各个项目无缝衔接，<s>挺爽的</s>。不过持续工作太久现在精神不是很正常啊，感觉我要精神飞升了。过年的时候还是要好好休息才行。</p><p>好像没什么可写的了，怎么感觉过去的记忆都模糊了（？估计是最近事情太多了吧，也许我过年的时候还会再写一个。</p><h2 id="新年展望"><a class="markdownIt-Anchor" href="#新年展望"></a> 新年展望</h2><blockquote><p>先看看去年写的实现了多少</p><ul><li>❓看看能不能混一个保研资格</li><li>❎大一立项拿个一等奖</li><li>✅入门三电</li><li>✅熟悉 <code>FreeRTOS</code> <code>ROS</code></li><li>❎瞅瞅能不能参与 25 年 RC 正赛 + ✅好好打 26 年 RC 正赛</li><li>✅小狗桌宠完工 + ❎量产版完工</li><li>✅下学期少混一点，多学点东西</li><li>❎<s>结束单身</s>开玩笑的✅活着就行</li><li>❎见到某人结婚（有难度）</li></ul></blockquote><ul><li>[ ] Robocon 2026 取得好成绩（最好是国一喵）</li><li>[ ] 再弄一个好玩的项目（有想法欢迎找我合作喵）</li><li>[ ] 学分绩不要 --（严格来说是老师捞捞我不要让我马原挂了啊）</li><li>[ ] 治安管理处罚法删去有关吸毒记录封存相关内容或者吸毒入刑</li><li>[ ] 活着</li></ul>]]></content>
    
    
    <summary type="html">对于 2025 所做项目的总结，以及对 2026 的一些想法</summary>
    
    
    
    <category term="年终总结" scheme="https://syhanjin.moe/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
    
    <category term="年终总结" scheme="https://syhanjin.moe/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
    <category term="2025" scheme="https://syhanjin.moe/tags/2025/"/>
    
  </entry>
  
  <entry>
    <title>新STM32驱动模块化项目概述</title>
    <link href="https://syhanjin.moe/20251218/b60a458f11ee/"/>
    <id>https://syhanjin.moe/20251218/b60a458f11ee/</id>
    <published>2025-12-18T15:08:51.000Z</published>
    <updated>2025-12-18T15:09:07.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="背景"><a class="markdownIt-Anchor" href="#背景"></a> 背景</h2><div class="post-link-card-wrap">    <div class="post-link-card">      <a href="/20250908/698d9cb67753/" title="STM32+Git 协作方案"></a>            <div class="post-link-card-item-wrap">        <div class="post-link-card-title">STM32+Git 协作方案</div>        <div class="post-link-card-excerpt">## 前言在多人协作开发时往往需要用到 [Git](https://zh.wikipedia.org/wiki/Git) 来进行版本管理，但是嵌入式开发中有大量的外设初始化配置内容，STM</div>      </div>    </div>  </div><p>上面的文章中提出了一种 STM32 + Git 多人协作开发方案，该方案虽然能对单个 STM32 项目进行较好的版本管理，却无法较好的进行驱动复用。如果希望维护一套可以在项目中复用的驱动，能采用的方案只有单独维护驱动仓库，在使用的时候复制到项目仓库内。这种形式会导致下游难以收到上游的更新，下游发现了 bug 自行修复也难以同步到上游，造成各模块不同步。</p><p>本文将提出基于 submodule 的模块分离方案。</p><div class="important custom-block"><p class="custom-block-title">IMPORTANT</p><p>该方案依赖 CMake，需要使用 STM32CubeMX 的 CMake 工具链</p></div><h2 id="使用模块仓库"><a class="markdownIt-Anchor" href="#使用模块仓库"></a> 使用模块仓库</h2><h3 id="在新项目中使用"><a class="markdownIt-Anchor" href="#在新项目中使用"></a> 在新项目中使用</h3><p>假设我们有一个模块仓库地址 <a href="https://github.com/HITSZ-WTRobot/motor_drivers">https://github.com/HITSZ-WTRobot/motor_drivers</a></p><p>要使用这个仓库需要三步</p><ol><li><p>将这个仓库添加到 submodule</p><p>例如，模块目录在 <code>./Modules</code>，我们进入这个目录 <code>cd Modules</code>（或者直接在 <code>./Modules</code> 打开终端），并运行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git submodule add https://github.com/HITSZ-WTRobot/motor_drivers</span><br></pre></td></tr></table></figure></li><li><p>将这个 submodule 目录添加到 CMake</p><p>在根目录的 <code>CMakeLists.txt</code> 里新增</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">add_subdirectory</span>(Modules/motor_drivers/UserCode)</span><br></pre></td></tr></table></figure><div class="info custom-block"><p class="custom-block-title">INFO</p><p>大部分情况下 add_subdirectory 传入的参数都是 submodule 下的 UserCode 路径。</p><p>少数情况如 s-curve-planner 仓库是直接导入根路径</p></div></li><li><p>将 模块添加到 target</p><p>在根目录的 CMakeLists.txt 里找到</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">target_link_libraries</span>(<span class="variable">$&#123;CMAKE_PROJECT_NAME&#125;</span> ...)</span><br></pre></td></tr></table></figure><p>在里面添加模块名，一般情况下模块名是仓库名</p><div class="info custom-block"><p class="custom-block-title">INFO</p><p>模块名可以在 模块目录/UserCode/CMakeLists.txt 里找到，格式如下</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">add_library</span>(motor_drivers STATIC <span class="variable">$&#123;ALL_SOURCES&#125;</span>)</span><br></pre></td></tr></table></figure><p>这里 <code>motor_drivers</code> 就是模块名</p></div></li></ol><h3 id="拉取一个使用了模块的项目"><a class="markdownIt-Anchor" href="#拉取一个使用了模块的项目"></a> 拉取一个使用了模块的项目</h3><p>在拉取有 submodule 的项目后，需要在项目根目录再运行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git submodule update --init --remote</span><br></pre></td></tr></table></figure><h3 id="更新项目里的模块"><a class="markdownIt-Anchor" href="#更新项目里的模块"></a> 更新项目里的模块</h3><p>运行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git submodule update --remote</span><br></pre></td></tr></table></figure><h2 id="模块仓库编写"><a class="markdownIt-Anchor" href="#模块仓库编写"></a> 模块仓库编写</h2><p>为了使模块仓库能够独立编译，模块仓库也应当是一个完整的项目。在之前的协作方案中我们提出</p><blockquote><p>所有的项目代码都应当放在 <code>UserCode</code> 下，并分层编写</p></blockquote><p>所以模块只需在 <code>UserCode</code> 下创建一个 <code>CMakeLists.txt</code>，并从中导出模块。</p><div class="info custom-block"><p class="custom-block-title">INFO</p><p>由于模块编写需要 CMake 基础，如果只是使用仓库不需要知道，本文不具体介绍</p></div><p>以下是一份示例</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># UserCode/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">cmake_minimum_required</span>(VERSION <span class="number">3.21</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(LIB_NAME motor_drivers)</span><br><span class="line"></span><br><span class="line"><span class="keyword">project</span>(<span class="variable">$&#123;LIB_NAME&#125;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导库方式</span></span><br><span class="line"><span class="comment"># add_subdirectory(&#123;project_path&#125;/UserCode)</span></span><br><span class="line"><span class="comment"># target_link_libraries($&#123;PROJECT_NAME&#125;.elf PRIVATE motor_drivers)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># guard: UserCode should only be included by parent project</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">if</span> (PROJECT_IS_TOP_LEVEL)</span><br><span class="line">    <span class="keyword">message</span>(FATAL_ERROR <span class="string">&quot;UserCode must be included via add_subdirectory() from parent project&quot;</span>)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># layer options</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseBSP <span class="string">&quot;use bsp layer&quot;</span> <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseLibs <span class="string">&quot;use library (pid_motor, pid_pd)&quot;</span> <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseControllers <span class="string">&quot;use s-curve-traj (depend on `s_curve`)&quot;</span> <span class="keyword">ON</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># collect layer sources</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">set</span>(ALL_SOURCES interfaces/motor_if.c)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(ALL_HEADERS <span class="string">&quot;interfaces/motor_if.h&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseBSP)</span><br><span class="line">    <span class="keyword">file</span>(GLOB_RECURSE BSP_SOURCES bsp/*.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES <span class="variable">$&#123;BSP_SOURCES&#125;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS</span><br><span class="line">            <span class="string">&quot;bsp/can_driver.h&quot;</span></span><br><span class="line">            <span class="string">&quot;bsp/gpio_driver.h&quot;</span></span><br><span class="line">            <span class="string">&quot;bsp/pwm.h&quot;</span></span><br><span class="line">    )</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseLibs)</span><br><span class="line">    <span class="keyword">file</span>(GLOB_RECURSE LIB_SOURCES libs/*.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES <span class="variable">$&#123;LIB_SOURCES&#125;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS</span><br><span class="line">            <span class="string">&quot;libs/pid_motor.h&quot;</span></span><br><span class="line">            <span class="string">&quot;libs/pid_pd.h&quot;</span></span><br><span class="line">    )</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseControllers)</span><br><span class="line">    <span class="keyword">file</span>(GLOB_RECURSE CONTROLLER_SOURCES controllers/*.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES <span class="variable">$&#123;CONTROLLER_SOURCES&#125;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS <span class="string">&quot;controllers/s_curve_traj_follower.h&quot;</span>)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># feature options: option + source + macro</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">set</span>(DRIVER_MACROS <span class="string">&quot;&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseDJI <span class="string">&quot;enable DJI driver&quot;</span> <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseDJI)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES drivers/DJI.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS <span class="string">&quot;drivers/DJI.h&quot;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND DRIVER_MACROS USE_DJI)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseTB6612 <span class="string">&quot;enable TB6612 driver&quot;</span> <span class="keyword">OFF</span>)</span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseTB6612)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES drivers/tb6612.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS <span class="string">&quot;drivers/tb6612.h&quot;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND DRIVER_MACROS USE_TB6612)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseVESC <span class="string">&quot;enable VESC driver&quot;</span> <span class="keyword">OFF</span>)</span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseVESC)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES drivers/vesc.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS <span class="string">&quot;drivers/vesc.h&quot;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND DRIVER_MACROS USE_VESC)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span>(MotorIF_UseDM <span class="string">&quot;enable DM driver&quot;</span> <span class="keyword">OFF</span>)</span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseDM)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_SOURCES drivers/DM.c)</span><br><span class="line">    <span class="keyword">list</span>(APPEND ALL_HEADERS <span class="string">&quot;drivers/DM.h&quot;</span>)</span><br><span class="line">    <span class="keyword">list</span>(APPEND DRIVER_MACROS USE_DM)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># create static library</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">add_library</span>(motor_drivers STATIC <span class="variable">$&#123;ALL_SOURCES&#125;</span>)</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">NOT</span> <span class="keyword">TARGET</span> <span class="variable">$&#123;LIB_NAME&#125;</span>)</span><br><span class="line">    <span class="keyword">message</span>(FATAL_ERROR <span class="string">&quot;target $&#123;LIB_NAME&#125; not created&quot;</span>)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># compile definitions</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">if</span> (DRIVER_MACROS)</span><br><span class="line">    <span class="keyword">target_compile_definitions</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> PUBLIC <span class="variable">$&#123;DRIVER_MACROS&#125;</span>)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># build/include 目录</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">set</span>(EXPORT_INCLUDE_DIR <span class="variable">$&#123;CMAKE_CURRENT_BINARY_DIR&#125;</span>/<span class="keyword">include</span>)</span><br><span class="line"><span class="keyword">file</span>(<span class="keyword">MAKE_DIRECTORY</span> <span class="variable">$&#123;EXPORT_INCLUDE_DIR&#125;</span>)</span><br><span class="line"><span class="keyword">target_include_directories</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> PUBLIC <span class="variable">$&#123;EXPORT_INCLUDE_DIR&#125;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># copy selected headers</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">foreach</span> (header IN LISTS ALL_HEADERS)</span><br><span class="line">    <span class="keyword">configure_file</span>(<span class="variable">$&#123;CMAKE_CURRENT_LIST_DIR&#125;</span>/<span class="variable">$&#123;header&#125;</span></span><br><span class="line">            <span class="variable">$&#123;EXPORT_INCLUDE_DIR&#125;</span>/<span class="variable">$&#123;header&#125;</span> COPYONLY)</span><br><span class="line"><span class="keyword">endforeach</span> ()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="comment"># declare dependencies</span></span><br><span class="line"><span class="comment"># ---------------------------------------------------------------------------</span></span><br><span class="line"><span class="keyword">add_dependencies</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> stm32cubemx)</span><br><span class="line"><span class="keyword">target_link_libraries</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> PRIVATE stm32cubemx)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (MotorIF_UseControllers)</span><br><span class="line">    <span class="comment"># Repo: https://github.com/HITSZ-WTRobot/s-curve-planner</span></span><br><span class="line">    <span class="keyword">target_link_libraries</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> PRIVATE s_curve)</span><br><span class="line">    <span class="keyword">add_dependencies</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> s_curve)</span><br><span class="line"><span class="keyword">endif</span> ()</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_link_libraries</span>(<span class="variable">$&#123;LIB_NAME&#125;</span> PRIVATE m)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;背景&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#背景&quot;&gt;&lt;/a&gt; 背景&lt;/h2&gt;
&lt;div class=&quot;post-link-card-wrap&quot;&gt;
    &lt;div class=&quot;post-link-card&quot;&gt;
      &lt;a</summary>
      
    
    
    
    <category term="嵌入式开发" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Git" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/git/"/>
    
    
  </entry>
  
  <entry>
    <title>STM32+Git 协作方案</title>
    <link href="https://syhanjin.moe/20250908/698d9cb67753/"/>
    <id>https://syhanjin.moe/20250908/698d9cb67753/</id>
    <published>2025-09-08T12:54:06.000Z</published>
    <updated>2025-09-08T15:31:16.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>在多人协作开发时往往需要用到 <a href="https://zh.wikipedia.org/wiki/Git">Git</a> 来进行版本管理，但是嵌入式开发中有大量的外设初始化配置内容，STM32 开发又往往会搭配 STM32CubeMX 进行，STM32CubeMX 提供了一个图形化界面来管理 STM32 的板上外设，故我们无须关心具体的初始化代码。由此造成了一个问题：这些初始化代码由 STM32CubeMX 生成，常规的项目结构却会导致它们被 Git 追踪（因为 Git 是基于文件来追踪代码的变更的），从而干扰对于项目实际代码的追踪，这对项目管理是非常不友好的。此外，如果有多种 IDE（集成开发环境）可能需要选用不同的工具链，生成的代码结构也是不同的，导致使用不同 IDE 的开发者之间很难协作。</p><p>本文旨在寻求一种方案能够将 STM32CubeMX 生成的代码全部排除，开发者可以只关注项目代码的变动。</p><h2 id="理论基础"><a class="markdownIt-Anchor" href="#理论基础"></a> 理论基础</h2><p>在项目的 <code>.ioc</code> 文件中存有关于板上外设的全部配置内容（实际上 <code>.ioc</code> 文件是 ini 格式的，可以很轻易地被 Git 追踪）。大型项目中往往会启用 FreeRTOS，这意味着我们可以通过一个最高优先级的 Task 来进行初始化，从而不需要对 <code>main.c</code> 做任何修改。各种外设的回调可以通过重写回调函数或注册回调函数来实现，从而也不需要修改任何 Core 中的代码。至于如何让项目构建的时候包含自己的代码，可以通过编译器参数实现。</p><h2 id="配置流程"><a class="markdownIt-Anchor" href="#配置流程"></a> 配置流程</h2><h3 id="创建项目并完成基本配置"><a class="markdownIt-Anchor" href="#创建项目并完成基本配置"></a> 创建项目并完成基本配置</h3><p>首先使用 STM32CubeMX 创建项目（本文使用 STM32F407VET6 芯片举例，创建项目的过程略），完成基本配置，并在 <code>Middleware and Software Packs</code> 里启用 FreeRTOS，选择 CMSIS_V2。</p><p>在 <code>Task and Queues</code> 里新建一个 Task，优先级配置为 <code>osPriorityRealtime7</code></p><p><img src="/assets/698d9cb67753/image-20250908200356048.png" alt="image-20250908200356048" /></p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>CMSIS 是对 FreeRTOS API 的一层封装，使用起来更顺手，CMSIS V2 的 API 可以参考 <a href="https://arm-software.github.io/CMSIS_5/RTOS2/html/rtos_api2.html">https://arm-software.github.io/CMSIS_5/RTOS2/html/rtos_api2.html</a></p></div><p>接下来生成代码，本文将介绍两种环境的配置方法，分别是 CLion + CMake 和 VSCode + EIDE + Makefile。</p><p>选择适合的工具链（CLion 用 STM32CubeIDE，EIDE 用 Makefile）并生成代码（GENERATE CODE）</p><h3 id="初始化-git-相关配置"><a class="markdownIt-Anchor" href="#初始化-git-相关配置"></a> 初始化 Git 相关配置</h3><p>进入项目文件夹，在当前目录开启终端（Windows 11 用户可以在文件夹右键并点击 <code>在此处打开终端</code>，Linux 用户你肯定知道）</p><p>初始化 Git</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git init</span><br></pre></td></tr></table></figure><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>没有 Git 的请上网找一个安装教程（不要使用 baidu 搜索），记得将 Git 加到 PATH</p></div><p>接下来创建一个文件 <code>.gitignore</code> 并复制以下内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">### STM32CubeMX ###</span><br><span class="line"># CubeMX auto-generated sources</span><br><span class="line">Core/</span><br><span class="line">Drivers/</span><br><span class="line">Middlewares/</span><br><span class="line">USB_Device/</span><br><span class="line">FATFS/</span><br><span class="line">LWIP/</span><br><span class="line">MX/</span><br><span class="line">TouchGFX/</span><br><span class="line">Utilities/</span><br><span class="line">.startup/</span><br><span class="line">startup_stm32*.s</span><br><span class="line">*.ld</span><br><span class="line"></span><br><span class="line"># For CLion</span><br><span class="line">CMakeLists.txt</span><br><span class="line"># this is a template file, do not ignore it</span><br><span class="line">!CMakeLists_template.txt</span><br><span class="line"></span><br><span class="line"># CubeMX metadata</span><br><span class="line">.settings/</span><br><span class="line">.ioc.xml</span><br><span class="line">*.ioc.bak</span><br><span class="line">.mxproject</span><br><span class="line">.mxresources</span><br><span class="line"></span><br><span class="line">### Build artifacts ###</span><br><span class="line">cmake-*</span><br><span class="line">build/</span><br><span class="line">Debug/</span><br><span class="line">Release/</span><br><span class="line">*.o</span><br><span class="line">*.d</span><br><span class="line">*.elf</span><br><span class="line">*.bin</span><br><span class="line">*.hex</span><br><span class="line">*.map</span><br><span class="line">*.lst</span><br><span class="line">*.srec</span><br><span class="line"></span><br><span class="line">### IDE files ###</span><br><span class="line"># STM32CubeIDE / Eclipse</span><br><span class="line">.project</span><br><span class="line">.cproject</span><br><span class="line">.launch</span><br><span class="line">.externalToolBuilders/</span><br><span class="line"></span><br><span class="line"># Keil uVision</span><br><span class="line">*.uvprojx</span><br><span class="line">*.uvoptx</span><br><span class="line"></span><br><span class="line"># IAR Embedded Workbench</span><br><span class="line">*.eww</span><br><span class="line">*.ewd</span><br><span class="line">*.ewp</span><br><span class="line"></span><br><span class="line"># VSCode / CLion</span><br><span class="line"># .vscode/</span><br><span class="line"># .idea/</span><br><span class="line"># .clangd</span><br><span class="line">.eide*</span><br><span class="line">!.eide/</span><br><span class="line"></span><br><span class="line">### System files ###</span><br><span class="line">.DS_Store</span><br><span class="line">Thumbs.db</span><br></pre></td></tr></table></figure><h3 id="用户代码基本结构"><a class="markdownIt-Anchor" href="#用户代码基本结构"></a> 用户代码基本结构</h3><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">UserCode/</span><br><span class="line">├── bsp/                      # 板级驱动，控制板上外设 (I2C, SPI, UART, CAN, ...)</span><br><span class="line">├── drivers/                  # 驱动程序，驱动硬件</span><br><span class="line">├── third_party/              # 第三方（比如厂家）提供的驱动库</span><br><span class="line">├── libs/                     # 库文件，提供算法、功能逻辑、通用模块，不依赖具体硬件</span><br><span class="line">├── interface/                # 接口层，用于对不同的外设提供统一的向上接口</span><br><span class="line">├── controllers/              # 控制层，用于实现 外设+硬件</span><br><span class="line">├── app/                      # 应用层</span><br></pre></td></tr></table></figure><p>可以参照以上目录结构来规划项目内容，我们需要创建的是 <code>UserCode/app/app.h</code> 和 <code>UserCode/app/app.c</code></p><p><strong>app.h</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file    app.h</span></span><br><span class="line"><span class="comment"> * @author  your-name</span></span><br><span class="line"><span class="comment"> * @date    date</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> APP_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> APP_H</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Includes */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">//APP_H</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>app.c</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file    app.h</span></span><br><span class="line"><span class="comment"> * @author  your-name</span></span><br><span class="line"><span class="comment"> * @date    date</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;app.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief Function implementing the initTask thread.</span></span><br><span class="line"><span class="comment"> * @param argument: Not used</span></span><br><span class="line"><span class="comment"> * @retval None</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Init</span><span class="params">(<span class="type">void</span>* argument)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">/* 初始化代码 */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 初始化完成后退出线程 */</span></span><br><span class="line">    osThreadExit();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Init</code> 函数的名称是创建 Task 时填写的 <code>Entry Funnction</code></p><h3 id="编译器配置"><a class="markdownIt-Anchor" href="#编译器配置"></a> 编译器配置</h3><h4 id="clion"><a class="markdownIt-Anchor" href="#clion"></a> CLion</h4><p>用 CLion 打开项目后，项目将具有以下目录结构</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── cmake-build-debug/</span><br><span class="line">├── Core/</span><br><span class="line">│   ├── Inc/</span><br><span class="line">│   ├── Src/</span><br><span class="line">│   └── Startup</span><br><span class="line">│       └── startup_stm32f407vetx.s</span><br><span class="line">├── Drivers/</span><br><span class="line">├── Middlewares/</span><br><span class="line">├── UserCode/</span><br><span class="line">├── CMakeLists_template.txt</span><br><span class="line">├── CMakeLists.txt</span><br><span class="line">├── STM32F407VETX_FLASH.ld</span><br><span class="line">├── STM32F407VETX_RAM.ld</span><br><span class="line">└── template.ioc</span><br></pre></td></tr></table></figure><p>在 <code>CMakeLists_template.txt</code> 中</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">add_executable</span>($<span class="variable">$&#123;PROJECT_NAME&#125;</span>.elf $<span class="variable">$&#123;SOURCES&#125;</span> $<span class="variable">$&#123;LINKER_SCRIPT&#125;</span>)</span><br></pre></td></tr></table></figure><p>后添加一行</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">target_compile_options</span>(<span class="variable">$&#123;PROJECT_NAME&#125;</span>.elf PRIVATE -<span class="keyword">include</span> <span class="variable">$&#123;CMAKE_SOURCE_DIR&#125;</span>/UserCode/app/app.h)</span><br></pre></td></tr></table></figure><p>并重新生成代码即可</p><h4 id="vscode-eide"><a class="markdownIt-Anchor" href="#vscode-eide"></a> VSCode + EIDE</h4><p>首先需要完成 EIDE 的基本配置，具体可以参考</p><div class="post-link-card-wrap">    <div class="post-link-card">      <a href="/20241130/1505fc7d06d8/" title="VSCode+CubeMX+EIDE 环境配置"></a>            <div class="post-link-card-item-wrap">        <div class="post-link-card-title">VSCode+CubeMX+EIDE 环境配置</div>        <div class="post-link-card-excerpt"># `VSCode`+`CubeMX`+`EIDE` 环境配置## 前置环境配置1. [`OpenOCD`](https://github.com/openocd-org/openocd/</div>      </div>    </div>  </div><p>记得将 <code>UserCode</code> 加到 <code>IncludeFolders</code> 和 项目资源 里。</p><p>配置完成后，目录结构如下</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── build/</span><br><span class="line">├── Core</span><br><span class="line">│   ├── Inc/</span><br><span class="line">│   └── Src/</span><br><span class="line">├── Drivers/</span><br><span class="line">├── Middlewares/</span><br><span class="line">├── UserCode/</span><br><span class="line">├── Makefile</span><br><span class="line">├── startup_stm32f407xx.s</span><br><span class="line">├── STM32F407XX_FLASH.ld</span><br><span class="line">├── template.code-workspace</span><br><span class="line">└── template.ioc</span><br></pre></td></tr></table></figure><p>在 Makefile 的</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># compile gcc flags</span></span><br><span class="line">ASFLAGS = <span class="variable">$(MCU)</span> <span class="variable">$(AS_DEFS)</span> <span class="variable">$(AS_INCLUDES)</span> <span class="variable">$(OPT)</span> -Wall -fdata-sections -ffunction-sections</span><br><span class="line"></span><br><span class="line">CFLAGS += <span class="variable">$(MCU)</span> <span class="variable">$(C_DEFS)</span> <span class="variable">$(C_INCLUDES)</span> <span class="variable">$(OPT)</span> -Wall -fdata-sections -ffunction-sections</span><br></pre></td></tr></table></figure><p>后面新增</p><figure class="highlight make"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 非侵入式引入头文件</span></span><br><span class="line">CFLAGS += <span class="keyword">-include</span> UserCode/app/app.h</span><br></pre></td></tr></table></figure><p>即可</p><h2 id="开发流程"><a class="markdownIt-Anchor" href="#开发流程"></a> 开发流程</h2><p>初始配置完环境后可以提交为 Init commit</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>关于 Git 工作流相关内容可以参考</p><div class="post-link-card-wrap">    <div class="post-link-card">      <a href="https://www.conventionalcommits.org/zh-hans/v1.0.0/" title="约定式提交" rel="noopener nofollow noreferrer" target="_blank"></a>            <div class="post-link-card-item-wrap">        <div class="post-link-card-title">约定式提交</div>        <div class="post-link-card-excerpt"><span class="icon-link"></span>https://www.conventionalcommits.org/zh-hans/v1.0.0/</div>      </div>    </div>  </div><div class="post-link-card-wrap">    <div class="post-link-card">      <a href="/20241129/0f5d39a3a888/" title="如何为一个 GitHub 仓库做贡献"></a>            <div class="post-link-card-item-wrap">        <div class="post-link-card-title">如何为一个 GitHub 仓库做贡献</div>        <div class="post-link-card-excerpt"># 如何为一个 GitHub 仓库做贡献## 前置知识在为 GitHub 仓库做贡献前，你需要先了解- （可选 推荐）Markdown 的基本语法- Git 的使用方法（不熟悉可以</div>      </div>    </div>  </div></div><div class="info custom-block"><p class="custom-block-title">INFO</p><p>本配置流程并没有将 IDE 配置目录 <code>.idea</code> <code>.vscode</code> <code>.eide</code> 加到排除目录，旨在统一各成员间的开发环境。如果不需要，也可以将这些目录加到 <code>.gitignore</code></p></div><p>可以在项目内添加 <code>.clang-format</code> 并启用 <code>Clang</code> （CLion 需要设置格式化程序为 clang-format，VSCode 需要安装 Clang-Format 插件）进行格式化，统一项目代码格式，以下是我偏好的格式化配置</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">Language:</span> <span class="string">Cpp</span></span><br><span class="line"><span class="attr">BasedOnStyle:</span> <span class="string">LLVM</span></span><br><span class="line"><span class="attr">AccessModifierOffset:</span> <span class="number">-4</span></span><br><span class="line"><span class="attr">AlignConsecutiveAssignments:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">AlignConsecutiveDeclarations:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">AlignConsecutiveMacros:</span> <span class="string">AcrossEmptyLines</span></span><br><span class="line"><span class="attr">AlignOperands:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">AlignTrailingComments:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">AlwaysBreakTemplateDeclarations:</span> <span class="literal">Yes</span></span><br><span class="line"><span class="attr">BraceWrapping:</span></span><br><span class="line">  <span class="attr">AfterCaseLabel:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterClass:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterControlStatement:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterEnum:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterFunction:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterNamespace:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterStruct:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterUnion:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">AfterExternBlock:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">BeforeCatch:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">BeforeElse:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">BeforeLambdaBody:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">BeforeWhile:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">SplitEmptyFunction:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">SplitEmptyRecord:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">SplitEmptyNamespace:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">BreakBeforeBraces:</span> <span class="string">Custom</span></span><br><span class="line"><span class="attr">BreakConstructorInitializers:</span> <span class="string">AfterColon</span></span><br><span class="line"><span class="attr">BreakConstructorInitializersBeforeComma:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">ColumnLimit:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">ConstructorInitializerAllOnOneLineOrOnePerLine:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">IncludeCategories:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">Regex:</span> <span class="string">&#x27;^&lt;.*&#x27;</span></span><br><span class="line">    <span class="attr">Priority:</span> <span class="number">1</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">Regex:</span> <span class="string">&#x27;^&quot;.*&#x27;</span></span><br><span class="line">    <span class="attr">Priority:</span> <span class="number">2</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">Regex:</span> <span class="string">&#x27;.*&#x27;</span></span><br><span class="line">    <span class="attr">Priority:</span> <span class="number">3</span></span><br><span class="line"><span class="attr">IncludeIsMainRegex:</span> <span class="string">&#x27;([-_](test|unittest))?$&#x27;</span></span><br><span class="line"><span class="attr">IndentCaseBlocks:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">IndentWidth:</span> <span class="number">4</span></span><br><span class="line"><span class="attr">InsertNewlineAtEOF:</span> <span class="literal">true</span></span><br><span class="line"><span class="attr">MacroBlockBegin:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="attr">MacroBlockEnd:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line"><span class="attr">MaxEmptyLinesToKeep:</span> <span class="number">2</span></span><br><span class="line"><span class="attr">NamespaceIndentation:</span> <span class="string">All</span></span><br><span class="line"><span class="attr">PointerAlignment:</span> <span class="string">Left</span></span><br><span class="line"><span class="attr">SpaceInEmptyParentheses:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">SpacesInAngles:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">SpacesInConditionalStatement:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">SpacesInCStyleCastParentheses:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">SpacesInParentheses:</span> <span class="literal">false</span></span><br><span class="line"><span class="attr">TabWidth:</span> <span class="number">4</span></span><br><span class="line"><span class="string">...</span></span><br></pre></td></tr></table></figure><h2 id="回调函数被占用的问题"><a class="markdownIt-Anchor" href="#回调函数被占用的问题"></a> 回调函数被占用的问题</h2><p>由于我们使用 FreeRTOS 时，往往会将 SysTick 设置为其他的定时器，这种情况下 STM32CubeMX 会在 <code>main.c</code> 中生成 <code>HAL_TIM_PeriodElapsedCallback</code> 回调函数来处理 SysTick，导致回调函数被占用。</p><p>此时我们可以在 STM32CubeMX -&gt; <code>Project Manager</code> -&gt; <code>Advanced Settings</code> 右侧的 <code>Register Callback</code> 中将我们使用的外设打开</p><p><img src="/assets/698d9cb67753/image-20250908232510493.png" alt="image-20250908232510493" /></p><p>自己写一个回调函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">TIM6_Callback</span><span class="params">(TIM_HandleTypeDef* htim)</span></span><br><span class="line">&#123;</span><br><span class="line">   <span class="comment">/* 回调执行内容 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后使用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HAL_TIM_RegisterCallback(&amp;htim6, HAL_TIM_PERIOD_ELAPSED_CB_ID, TIM6_Callback);</span><br></pre></td></tr></table></figure><p>函数来注册回调函数，其余外设也有对应的回调注册函数（格式为 <code>HAL_xxx_RegisterCallback</code>）</p><div class="important custom-block"><p class="custom-block-title">IMPORTANT</p><p>回调注册一定要在<strong>初始化之后，中断开启前</strong>进行，否则回调不会被触发</p></div>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#前言&quot;&gt;&lt;/a&gt; 前言&lt;/h2&gt;
&lt;p&gt;在多人协作开发时往往需要用到 &lt;a href=&quot;https://zh.wikipedia.org/wiki/Git&quot;&gt;Git&lt;/a&gt; 来进行版本</summary>
      
    
    
    
    <category term="嵌入式开发" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
    <category term="Git" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/git/"/>
    
    
  </entry>
  
  <entry>
    <title>三极管饱和管压降 UCES 含义</title>
    <link href="https://syhanjin.moe/20250513/e5abc48f8291/"/>
    <id>https://syhanjin.moe/20250513/e5abc48f8291/</id>
    <published>2025-05-13T12:56:39.000Z</published>
    <updated>2025-05-13T13:02:26.000Z</updated>
    
    <content type="html"><![CDATA[<p>之前关于模电教材上浅浅掠过的一个量：饱和管压降 $U_{CES}$（或者 $U_{CE}(sat)$）有诸多疑问，查了许多资料在这里给出自己的理解。</p><h2 id="到底什么叫饱和区"><a class="markdownIt-Anchor" href="#到底什么叫饱和区"></a> 到底什么叫饱和区</h2><img src="/assets/e5abc48f8291/image-20250513202908914.png" alt="image-20250513202908914" style="zoom:50%;" /><p>上图是一个基本共射放大电路，工作在放大区时（发射结正偏、集电结反偏） $I_C$ 可以受到 $I_B$ 控制，且近似满足 $I_C&#x3D;\overline{\beta}I_B$，即具有一个倍数放大关系，那么当 $I_B$ 增大引起 $I_C$ 增大时，电阻 $R_c$ 的分压也会增大，导致集电极电位降低，所以 <strong>$I_C$ 增大的代价是集电极电位降低</strong>。故而必然 $I_B$ 增大到某个值时会导致 $U_c \le U_b$，即书上说的 <code>饱和区</code>，如果电流继续增大，则 $i_C &lt; \overline{\beta}i_B$，不再满足原本的放大关系。</p><h2 id="那-tm-为啥还有一个饱和管压降"><a class="markdownIt-Anchor" href="#那-tm-为啥还有一个饱和管压降"></a> 那 TM 为啥还有一个饱和管压降？？</h2><p>因为书上的是近似理论分析，根据查找到的资料<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>，我们可以拿一张实际的特性表来分析</p><p><img src="/assets/e5abc48f8291/fa5c085b-33b2-43de-8e91-7355469d0c95.png" alt="fa5c085b-33b2-43de-8e91-7355469d0c95" /></p><p>这是 9013 的特性表，我们可以看到共射直流放大系数 $\overline\beta$ （$H_{FE}$）其实会受到 $I_C$ 大小的影响，并且实际饱和是一个过程，之前根据 $U_b,U_c$ 大小比较所获得的临界态其实是一个初始饱和状态，之后增大 $I_B$，$I_C$ 仍会继续增大，并且仍然满足一定的比例关系。</p><blockquote><p>注意：饱和时Vb&gt;Vc，但Vb&gt;Vc不一定饱和。一般判断饱和的直接依据还是放大倍数，有的管子Vb&gt;Vc时还能保持相当高的放大倍数。例如：有的管子将Ic/Ib&lt;10定义为饱和，Ic/Ib&lt;1应该属于深饱和了。</p></blockquote><p>所以，在另一种近似分析下，并不将 $U_b&#x3D;U_c$ 当做临界饱和，而是将另一个位置（$U_{CE}&#x3D;U_{CES}$）当做临界饱和态。</p><p>从做题的角度，如果没给 $U_{CES}$，则将 $U_{CE}&#x3D;U_{BE}$ 当做临界饱和，如果给了 $U_{CES}$ 则将 $U_{CS}&#x3D;U_{CES}$ 当做临界饱和。</p><h2 id="补兑集电极正偏那电流为啥还是反着的"><a class="markdownIt-Anchor" href="#补兑集电极正偏那电流为啥还是反着的"></a> 补兑，集电极正偏那电流为啥还是反着的</h2><p>谁说电流一定从高电位流向低电位，我能量又不一定从这里的电场来</p><hr class="footnotes-sep" /><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p><a href="http://www.360doc.com/content/19/0902/20/42387867_858738067.shtml">http://www.360doc.com/content/19/0902/20/42387867_858738067.shtml</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;之前关于模电教材上浅浅掠过的一个量：饱和管压降 $U_{CES}$（或者 $U_{CE}(sat)$）有诸多疑问，查了许多资料在这里给出自己的理解。&lt;/p&gt;
&lt;h2 id=&quot;到底什么叫饱和区&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#到底什么</summary>
      
    
    
    
    <category term="三大天书学习笔记" scheme="https://syhanjin.moe/categories/%E4%B8%89%E5%A4%A7%E5%A4%A9%E4%B9%A6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="学习笔记" scheme="https://syhanjin.moe/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    <category term="模拟电子技术基础" scheme="https://syhanjin.moe/tags/%E6%A8%A1%E6%8B%9F%E7%94%B5%E5%AD%90%E6%8A%80%E6%9C%AF%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>高阶常系数线性微分方程通解公式推导</title>
    <link href="https://syhanjin.moe/20250310/6e387a2a6736/"/>
    <id>https://syhanjin.moe/20250310/6e387a2a6736/</id>
    <published>2025-03-10T09:34:58.000Z</published>
    <updated>2025-05-10T12:09:45.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引言低阶常系数齐次线性微分方程通解公式推导"><a class="markdownIt-Anchor" href="#引言低阶常系数齐次线性微分方程通解公式推导"></a> 引言·低阶常系数齐次线性微分方程通解公式推导</h2><p>我们知道一阶常系数齐次线性微分方程的形式为</p><p>$$y&#39;+ay&#x3D;0$$</p><p>利用关系式</p><p>$$\displaystyle y&#39;+f(x)y&#x3D;\frac{(ye^{\int f(x)dx})&#39;}{e^{\int f(x)dx}}$$</p><p>我们很容易得出 $y&#x3D;Ce^{-ax}$</p><p>二阶常系数齐次线性微分方程的形式为</p><p>$$y&#39;&#39;+ay&#39;+by&#x3D;0$$</p><p>此时一个很自然的想法是<strong>降阶</strong>，由此利用待定系数法可以得出</p><p>$$(y&#39;-r_1y)&#39;-r_2(y&#39;-r_1y)&#x3D;y&#39;&#39;-(r_1+r_2)y&#39;+r_1r_2y&#x3D;0$$</p><p>注意到 $r_1, r_2$ 为方程 $r^2+ar+b&#x3D;0$ 的两个根，我们将这个方程称为这个微分方程的<strong>特征方程</strong>，$r_1,r_2$ 为微分方程的<strong>特征根</strong></p><p>于是运用换元 $p&#x3D;y&#39;-r_1y$ 我们有</p><p>$$p&#39;-r_2p&#x3D;0$$</p><p>所以</p><p>$$\begin{aligned}p&amp;&#x3D;Ce^{r_2x}\\&amp;&#x3D;y&#39;-r_1y&#x3D;\frac{(ye^{-r_1x})&#39;}{e^{-r_1x}}\end{aligned}$$</p><p>进而当 $r_1 \neq r_2$ 时，有</p><p>$$y &#x3D; \frac{C}{r_2-r_1}e^{r_2x}+C_1e^{r_1x}&#x3D;C_1e^{r_1x}+C_2e^{r_2x}$$</p><p>而当 $r_1 &#x3D; r_2$ 时，有</p><p>$$y &#x3D; (C_1+xC_2)e^{rx}$$</p><p>其中 $r&#x3D;r_1&#x3D;r_2$.</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>$r_1,r_2$ 前面使用负号是为了让结果好看（使得构造式中的 $r_1,r_2$ 就是特征根），实际上正负都无所谓</p></div><h2 id="高阶常系数齐次线性微分方程的通解"><a class="markdownIt-Anchor" href="#高阶常系数齐次线性微分方程的通解"></a> 高阶常系数齐次线性微分方程的通解</h2><p>从之前二阶时候的推导方式我们可以猜测：高阶常系数齐次线性微分方程的也可以通过特征根构造降阶。</p><p>$n$ 阶常系数齐次线性微分方程</p><p>$$y^{(n)}+P_{n-1}y^{(n-1)}+\cdots+P_1y&#39;+P_0y&#x3D;0$$</p><p>的特征方程为</p><p>$$\begin{equation}r^n+P_{n-1}r^{n-1}+\cdots+P_1r+P_0&#x3D;0\label{Eq.1}\end{equation}$$</p><p>假设方程的 $n$ 个根分别为 $r_1,r_2,\cdots,r_n$，则有</p><p>$$(r-r_1)(r-r_2)\cdots(r-r_n)&#x3D;0$$</p><p>将其展开有</p><p>$$\begin{aligned}r^n+P_{n-1}r^{n-1}+\cdots+P_1r+P_0 &amp; &#x3D; r^n &amp;\\&amp; + r^{n-1}[-r_1-r_2-\cdots-r_n]&amp;\\&amp; + r^{n-2}[(-r_1)(-r_2)+(-r_1)(-r_3)+\cdots+(-r_{n-1})(-r_n)]&amp;\\&amp;\;\;\vdots &amp;\\&amp; + r\sum_{i&#x3D;2}^{n}\left(\prod_{j&#x3D;1,2,\cdots,i-1,i+1,\cdots,n}r_j\right)&amp;\\&amp; + \prod_{i&#x3D;1}^nr_i&amp; &#x3D;0\end{aligned}$$</p><p>此时仿照二阶情况，令</p><p>$$f_1&#x3D;y&#39;-r_1y$$</p><p>尝试换元，得</p><p>$$f_1(r_2r_3\cdots r_n)+f_1&#39;(\prod_{从 r_2, r_3, \cdots, r_n 中任选 n-2 个}r_i) + \cdots +f_1^{(n-1)}&#x3D; 0$$</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>本步最关键的就是在每一项前面乘一个系数使得换元后的式子与原方程等价</p></div><p>最关键的一步来了，注意到换元后的 $n-1$ 阶常系数齐次线性微分方程的特征根为 $r_2,r_3,\cdots,r_n$</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>这里我真是注意到的，将 $\eqref{Eq.1}$ 式的第一个因子去掉之后再执行展开，系数与这个 $n-1$ 阶方程完全一致，进而马上得出特征根</p></div><p>于是我们就可以通过一系列换元</p><p>$$\begin{array}{left}f_1&amp;&#x3D;y&#39;-r_1y&amp;&#x3D;\displaystyle\frac{(ye^{-r_1x})&#39;}{e^{-r_1x}}\\f_2&amp;&#x3D;f_1&#39;-r_2f_1&amp;&#x3D;\displaystyle\frac{(f_1e^{-r_2x})&#39;}{e^{-r_2x}}\\&amp;\;\vdots\\f_n&amp;&#x3D;f_{n-1}&#39;-r_nf_{n-1}&amp;&#x3D;\displaystyle\frac{(f_{n-1}e^{-r_nx})&#39;}{e^{-r_nx}}\\\end{array}$$</p><p>将微分方程化为 $f_n&#x3D;0$ 的形式，再来依次积分求解</p><div class="danger custom-block"><p class="custom-block-title">DANGER</p><p>之前未考虑 $r_i&#x3D;r_j$ 的情况，现已修正</p></div><h3 id="特征根两两不相等"><a class="markdownIt-Anchor" href="#特征根两两不相等"></a> 特征根两两不相等</h3><p>当 $r_i\neq r_j(i \neq j;i,j&#x3D;1,2,\cdots,n)$ 时，有</p><p>$$\begin{equation}\begin{array}{left}f_{n-1}&amp;&#x3D;\displaystyle\frac{(f_{n-2}e^{-r_{n-1}x})&#39;}{e^{-r_{n-1}x}}&amp;&#x3D;C_1e^{r_nx}\\f_{n-2}&amp;&#x3D;\displaystyle\frac{(f_{n-3}e^{-r_{n-2}x})&#39;}{e^{-r_{n-2}x}}&amp;&#x3D;\frac{C_1}{r_n-r_{n-1}}e^{r_nx}+C_2e^{r_{n-1}x}\\&amp;\;\vdots\\y&amp; &amp;&#x3D;\widetilde{C_1}e^{r_nx}+\widetilde{C_2}e^{r_{n-1}x}+\cdots+\widetilde{C_n}e^{r_{1}x}\\\end{array}\label{Eq.2}\end{equation}$$</p><p>即 $y$ 为 $e^{r_ix}$ 的线性组合</p><h3 id="特征根出现重根"><a class="markdownIt-Anchor" href="#特征根出现重根"></a> 特征根出现重根</h3><p>注意到</p><p>$$\begin{equation}\displaystyle\int(C_1+C_2x+\cdots+C_kx^{k-1})e^{rx}dx&#x3D;(\widetilde{C_1}+\widetilde{C_2}x+\cdots+\widetilde{C_k}x^{k-1})e^{rx}\label{Eq.3}\end{equation}$$</p><p>即上式左侧积分形式不变。</p><p>不妨假设递推式组 $\eqref{Eq.2}$ 中 $r_{n}&#x3D;r_{n-1}&#x3D;\cdots&#x3D;r_{n-k+1}&#x3D;r$ 是特征方程的一个 $k$ 重根，则</p><p>$$\begin{array}{left}f_{n-1}&amp;&#x3D;\displaystyle\frac{(f_{n-2}e^{-rx})&#39;}{e^{-rx}}&amp;&#x3D;C_1e^{rx}\\f_{n-2}&amp;&#x3D;\displaystyle\frac{(f_{n-3}e^{-rx})&#39;}{e^{-rx}}&amp;&#x3D;(C_1x+C_2)e^{rx}\\&amp;\;\vdots\\f_{n-k}&amp;&#x3D;\displaystyle\frac{(f_{n-k-1}e^{-r_{n-k}x})&#39;}{e^{-r_{n-k}x}}&amp;&#x3D;(C_1x^{k-1}+C_2x^{k-2}+\cdots+C_k)e^{rx}\end{array}$$</p><p>化简得</p><p>$$(f_{n-k-1}e^{-r_{n-k}x})&#39;&#x3D;(C_1x^{k-1}+C_2x^{k-2}+\cdots+C_k)e^{(r-r_{n-k})x}$$</p><p>对两边积分，由 $\eqref{Eq.3}$ 得</p><p>$$f_{n-k-1}&#x3D;(C&#39;_1x^{k-1}+C&#39;_2x^{k-2}+\cdots+C&#39;_k)e^{rx}+C_{k+1}e^{r_{n-k}x}$$</p><p>如果还有重根，则积分后和式左边形式不变，重根对应项次数 $+1$.</p><p>假设特征方程有 $m$ 个不同的特征根 $r_1, r_2, \cdots,r_m$，重数分别为 $k_1, k_2, \cdots. k_m$，则</p><p>$$\begin{array}{left}y&#x3D;&amp; &amp;(C_{11}+C_{12}x+\cdots+C_{1k_1}x^{k_1-1})e^{r_1x}\\&amp;+&amp;(C_{21}+C_{22}x+\cdots+C_{2k_2}x^{k_2-1})e^{r_2x}\\&amp;\;\vdots&amp;\\&amp;+&amp;(C_{m1}+C_{m2}x+\cdots+C_{mk_m}x^{k_m-1})e^{r_mx}\\\end{array}$$</p><p>其中 $k_1+k_2+\cdots+k_m&#x3D;n$</p>]]></content>
    
    
    <summary type="html">从低阶常系数线性微分方程类比推导高阶常系数线性微分方程</summary>
    
    
    
    <category term="高等数学" scheme="https://syhanjin.moe/categories/%E9%AB%98%E7%AD%89%E6%95%B0%E5%AD%A6/"/>
    
    
    <category term="微分方程" scheme="https://syhanjin.moe/tags/%E5%BE%AE%E5%88%86%E6%96%B9%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>用 AlphaZero 算法实现一个五子棋 AI</title>
    <link href="https://syhanjin.moe/20250218/6be18bad2e07/"/>
    <id>https://syhanjin.moe/20250218/6be18bad2e07/</id>
    <published>2025-02-18T08:00:27.000Z</published>
    <updated>2025-02-18T08:08:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>突然想整一下 RL 于是就看上了 AlphaZero，围棋我也不会，训练也难，那就上一个五子棋版本</p><blockquote><p>注意，这里展示的基本上是我跟着这些资料学习的流程，想要最终最完善的版本可以直接点目录跳转，有些代码已经优化改进，但是我还是把初稿放上来了</p></blockquote><h2 id="游戏环境实现"><a class="markdownIt-Anchor" href="#游戏环境实现"></a> 游戏环境实现</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> pygame</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Board</span>:</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    board: 0: available, 1: player1, 2: player2</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    board: np.ndarray</span><br><span class="line"></span><br><span class="line">    current_player = <span class="number">1</span></span><br><span class="line">    last_move = -<span class="number">1</span></span><br><span class="line">    move_count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, width=<span class="number">8</span>, height=<span class="number">8</span>, n_in_rows=<span class="number">4</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.width = width</span><br><span class="line">        <span class="variable language_">self</span>.height = height</span><br><span class="line">        <span class="variable language_">self</span>.n_in_rows = n_in_rows</span><br><span class="line">        <span class="keyword">if</span> width &lt; <span class="variable language_">self</span>.n_in_rows <span class="keyword">or</span> height &lt; <span class="variable language_">self</span>.n_in_rows:</span><br><span class="line">            <span class="keyword">raise</span> ValueError(<span class="string">&quot;Board size must be greater than or equal to n_in_rows&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">init_board</span>(<span class="params">self, start_player=<span class="number">1</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.board = np.zeros((<span class="variable language_">self</span>.height, <span class="variable language_">self</span>.width))</span><br><span class="line">        <span class="variable language_">self</span>.current_player = start_player</span><br><span class="line">        <span class="variable language_">self</span>.move_count = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">position2move</span>(<span class="params">self, pos</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        :param pos: (y, x)</span></span><br><span class="line"><span class="string">        :return: move x + w * y</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="number">0</span> &lt;= pos[<span class="number">1</span>] &lt; <span class="variable language_">self</span>.width <span class="keyword">and</span> <span class="number">0</span> &lt;= pos[<span class="number">0</span>] &lt; <span class="variable language_">self</span>.height, <span class="string">f&#x27;Position <span class="subst">&#123;pos&#125;</span> out of bounds&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> pos[<span class="number">1</span>] + <span class="variable language_">self</span>.width * pos[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">move2position</span>(<span class="params">self, move: <span class="built_in">int</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        :param move:</span></span><br><span class="line"><span class="string">        :return: pos: (y, x)</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="number">0</span> &lt;= move &lt; <span class="variable language_">self</span>.height * <span class="variable language_">self</span>.width, <span class="string">f&#x27;Move <span class="subst">&#123;move&#125;</span> out of bounds&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> move // <span class="variable language_">self</span>.width, move % <span class="variable language_">self</span>.width</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">game_over</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.last_move == -<span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span>, <span class="literal">None</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.move_count &lt; <span class="variable language_">self</span>.n_in_rows * <span class="number">2</span> - <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span>, <span class="literal">None</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.move_count == <span class="variable language_">self</span>.width * <span class="variable language_">self</span>.height:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">True</span>, <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        directions = [(<span class="number">0</span>, <span class="number">1</span>), (<span class="number">1</span>, <span class="number">0</span>), (<span class="number">1</span>, <span class="number">1</span>), (<span class="number">1</span>, -<span class="number">1</span>)]</span><br><span class="line">        last_pos = <span class="variable language_">self</span>.move2position(<span class="variable language_">self</span>.last_move)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> d <span class="keyword">in</span> directions:</span><br><span class="line">            count = <span class="number">1</span>  <span class="comment"># 当前刚落子的棋子</span></span><br><span class="line">            <span class="comment"># 正向检查</span></span><br><span class="line">            y, x = last_pos[<span class="number">0</span>] + d[<span class="number">0</span>], last_pos[<span class="number">1</span>] + d[<span class="number">1</span>]</span><br><span class="line">            <span class="keyword">while</span> <span class="number">0</span> &lt;= x &lt; <span class="variable language_">self</span>.width <span class="keyword">and</span> <span class="number">0</span> &lt;= y &lt; <span class="variable language_">self</span>.height <span class="keyword">and</span> <span class="variable language_">self</span>.board[y, x] == <span class="variable language_">self</span>.opponent_player:</span><br><span class="line">                count += <span class="number">1</span></span><br><span class="line">                x += d[<span class="number">1</span>]</span><br><span class="line">                y += d[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># 反向检查</span></span><br><span class="line">            y, x = last_pos[<span class="number">0</span>] - d[<span class="number">0</span>], last_pos[<span class="number">1</span>] - d[<span class="number">1</span>]</span><br><span class="line">            <span class="keyword">while</span> <span class="number">0</span> &lt;= x &lt; <span class="variable language_">self</span>.width <span class="keyword">and</span> <span class="number">0</span> &lt;= y &lt; <span class="variable language_">self</span>.height <span class="keyword">and</span> <span class="variable language_">self</span>.board[y, x] == <span class="variable language_">self</span>.opponent_player:</span><br><span class="line">                count += <span class="number">1</span></span><br><span class="line">                x -= d[<span class="number">1</span>]</span><br><span class="line">                y -= d[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> count &gt;= <span class="variable language_">self</span>.n_in_rows:</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">True</span>, <span class="variable language_">self</span>.opponent_player</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">False</span>, <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">state</span>(<span class="params">self</span>):</span><br><span class="line">        state = np.zeros((<span class="number">4</span>, <span class="variable language_">self</span>.height, <span class="variable language_">self</span>.width))</span><br><span class="line">        state[<span class="number">0</span>] = <span class="variable language_">self</span>.board == <span class="variable language_">self</span>.current_player</span><br><span class="line">        state[<span class="number">1</span>] = <span class="variable language_">self</span>.board == <span class="variable language_">self</span>.opponent_player</span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.last_move != -<span class="number">1</span>:</span><br><span class="line">            last_pos = <span class="variable language_">self</span>.move2position(<span class="variable language_">self</span>.last_move)</span><br><span class="line">            state[<span class="number">2</span>, last_pos[<span class="number">0</span>], last_pos[<span class="number">1</span>]] = <span class="number">1.0</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.move_count % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">            state[<span class="number">3</span>][:, :] = <span class="number">1.0</span></span><br><span class="line">        <span class="comment"># https://github.com/junxiaosong/AlphaZero_Gomoku/issues/33</span></span><br><span class="line">        <span class="comment"># original state[:, ::-1, :]</span></span><br><span class="line">        <span class="keyword">return</span> state</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">opponent_player</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span> <span class="keyword">if</span> (<span class="variable language_">self</span>.current_player == <span class="number">2</span>) <span class="keyword">else</span> <span class="number">2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_available_moves</span>(<span class="params">self</span>):</span><br><span class="line">        rows, columns = np.where(<span class="variable language_">self</span>.board == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> rows * <span class="variable language_">self</span>.width + columns</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="算法基本思路"><a class="markdownIt-Anchor" href="#算法基本思路"></a> 算法基本思路</h2><p>传统的 MCTS 过程是这样的，给定一个棋面，MCTS 共进行 N 次模拟。主要的搜索阶段有 4 个：选择，扩展，仿真和回溯</p><p><img src="https://ai-studio-static-online.cdn.bcebos.com/73384055df364b44a49e7e206a9015790be7b3c0aa1942d0a4e57aa617fad087" alt="蒙特卡洛树搜索过程" /></p><ul><li>第一步是选择 (Selection)，这一步会从根节点开始，每次都选一个“最值得搜索的子节点”，一般使用上限置信区间算法 (Upper Confidence Bound Apply to Tree, UCT) 选择分数最高的节点，直到来到一个“存在未扩展的子节点”的节点</li><li>第二步是扩展 (Expansion)，在这个搜索到的“存在未扩展的子节点”之上，加上一个没有历史记录的子节点并初始化该子节点</li><li>第三步是仿真 (simulation)，从上面这个没有试过的着法开始，用一个简单策略比如快速走子策略 (Rollout policy) 走到底，得到一个胜负结果。快速走子策略虽然不是很精确，但是速度较快，在这里具有优势。因为如果这个策略走得慢，结果虽然会更准确，但由于耗时多了，在单位时间内的模拟次数就少了，所以不一定会棋力更强，有可能会更弱。这也是为什么我们一般只模拟一次，因为如果模拟多次，虽然更准确，但更慢。</li><li>第四步是回溯  (backpropagation), 将我们最后得到的胜负结果回溯加到 MCTS 树结构上。注意除了之前的 MCTS 树要回溯外，新加入的节点也要加上一次胜负历史记录。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></li></ul><p>而 AlphaZero 算法用一个神经网络代替了第三步的仿真过程，我个人的理解是神经网络拟合后的准确度比一次仿真高，而用时又比多次仿真少，所以能在不增加太多时间的情况下大幅提升准确度</p><h2 id="基于-mcts-的-player-实现"><a class="markdownIt-Anchor" href="#基于-mcts-的-player-实现"></a> 基于 MCTS 的 player 实现</h2><h3 id="用-python-实现传统-mcts-搜索"><a class="markdownIt-Anchor" href="#用-python-实现传统-mcts-搜索"></a> 用 Python 实现传统 MCTS 搜索</h3><p>MCTS 搜索代码逻辑都是差不多的（原理参照<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup>，这边仿照 <a href="https://github.com/junxiaosong">@junxiaosong</a> 的代码写了一份<sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>，要注意的关键点都在注释里了</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TreeNode</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, parent, prior_p</span>):</span><br><span class="line">        <span class="variable language_">self</span>.parent = parent</span><br><span class="line">        <span class="variable language_">self</span>.children = &#123;&#125;</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.n_visits = <span class="number">0</span></span><br><span class="line">        <span class="variable language_">self</span>.Q = <span class="number">0</span></span><br><span class="line">        <span class="variable language_">self</span>.u = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.P = prior_p</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">expand</span>(<span class="params">self, action_priors</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Expand tree by creating new children.</span></span><br><span class="line"><span class="string">        :param action_priors: a list of tuples of actions and their prior probability</span></span><br><span class="line"><span class="string">            according to the policy function.</span></span><br><span class="line"><span class="string">        :return:</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> action, prior_prob <span class="keyword">in</span> action_priors:</span><br><span class="line">            <span class="keyword">if</span> action <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.children:</span><br><span class="line">                <span class="variable language_">self</span>.children[action] = TreeNode(<span class="variable language_">self</span>, prior_prob)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">select</span>(<span class="params">self, c_puct</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Select action among children that gives maximum action value Q plus bonus u(P).</span></span><br><span class="line"><span class="string">        :return: A tuple of (action, next_node)</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(<span class="variable language_">self</span>.children.items(), key=<span class="keyword">lambda</span> act_node: act_node[<span class="number">1</span>].get_value(c_puct))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">update</span>(<span class="params">self, leaf_value</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Update node values from leaf evaluation.</span></span><br><span class="line"><span class="string">        :param leaf_value: the value of subtree evaluation from the current player&#x27;s</span></span><br><span class="line"><span class="string">            perspective.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># Count visit.</span></span><br><span class="line">        <span class="variable language_">self</span>.n_visits += <span class="number">1</span></span><br><span class="line">        <span class="comment"># Update Q, a running average of values for all visits.</span></span><br><span class="line">        <span class="comment"># (n * Q + v) / (n + 1) - Q = (v - Q) / (n + 1)</span></span><br><span class="line">        <span class="variable language_">self</span>.Q += <span class="number">1.0</span> * (leaf_value - <span class="variable language_">self</span>.Q) / <span class="variable language_">self</span>.n_visits</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">update_recursive</span>(<span class="params">self, leaf_value</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Like a call to update(), but applied recursively for all ancestors.&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># If it is not root, this node&#x27;s parent should be updated first.</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable language_">self</span>.parent:</span><br><span class="line">            <span class="variable language_">self</span>.parent.update_recursive(-leaf_value)</span><br><span class="line">        <span class="variable language_">self</span>.update(leaf_value)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_value</span>(<span class="params">self, c_puct</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Calculate and return the value for this node.</span></span><br><span class="line"><span class="string">        It is a combination of leaf evaluations Q, and this node&#x27;s prior</span></span><br><span class="line"><span class="string">        adjusted for its visit count, u.</span></span><br><span class="line"><span class="string">        :param c_puct: a number in (0, inf) controlling the relative impact of</span></span><br><span class="line"><span class="string">            value Q, and prior probability P, on this node&#x27;s score.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.u = (c_puct * <span class="variable language_">self</span>.P * np.sqrt(<span class="variable language_">self</span>.parent.n_visits) / (<span class="number">1</span> + <span class="variable language_">self</span>.n_visits))</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.Q + <span class="variable language_">self</span>.u</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">is_leaf</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Check if leaf node (i.e. no nodes below this have been expanded).&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.children == &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">is_root</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.parent <span class="keyword">is</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MCTSPure</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, policy_value_fn, c_puct=<span class="number">5</span>, n_playout=<span class="number">10000</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        :param policy_value_fn: a function that takes in a board state and outputs</span></span><br><span class="line"><span class="string">            a list of (action, probability) tuples and also a score in [-1, 1]</span></span><br><span class="line"><span class="string">            (i.e. the expected value of the end game score from the current</span></span><br><span class="line"><span class="string">            player&#x27;s perspective) for the current player.</span></span><br><span class="line"><span class="string">        :param c_puct: a number in (0, inf) that controls how quickly exploration</span></span><br><span class="line"><span class="string">            converges to the maximum-value policy. A higher value means</span></span><br><span class="line"><span class="string">            relying on the prior more.</span></span><br><span class="line"><span class="string">        :param n_playout:</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="variable language_">self</span>.root = TreeNode(<span class="literal">None</span>, <span class="number">1.0</span>)</span><br><span class="line">        <span class="variable language_">self</span>.policy_value_fn = policy_value_fn</span><br><span class="line">        <span class="variable language_">self</span>.c_puct = c_puct</span><br><span class="line">        <span class="variable language_">self</span>.n_playout = n_playout</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">playout</span>(<span class="params">self, state: Board</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Run a single playout from the root to the leaf, getting a value at</span></span><br><span class="line"><span class="string">        the leaf and propagating it back through its parents.</span></span><br><span class="line"><span class="string">        State is modified in-place, so a copy must be provided.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        node = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="keyword">while</span> <span class="keyword">not</span> node.is_leaf():</span><br><span class="line">            action, node = node.select(<span class="variable language_">self</span>.c_puct)</span><br><span class="line">            state.perform_move(action)</span><br><span class="line">        action_probs, _ = <span class="variable language_">self</span>.policy_value_fn(state)</span><br><span class="line"></span><br><span class="line">        end, winner = state.game_over()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> end:</span><br><span class="line">            node.expand(action_probs)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Evaluate the leaf node by random rollout</span></span><br><span class="line">        leaf_value = <span class="variable language_">self</span>.evaluate_rollout(state)</span><br><span class="line">        <span class="comment"># Update value and visit count of nodes in this traversal.</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Q:  Why is the negative leaf_value for update_recursive function?</span></span><br><span class="line"><span class="string">        A:  We use the negative value of the state, this is because alternate levels </span></span><br><span class="line"><span class="string">            in the search tree are from the perspective of different players and </span></span><br><span class="line"><span class="string">            the Q-values are in fact used by the parent node in select stage.</span></span><br><span class="line"><span class="string">        Reference https://github.com/junxiaosong/AlphaZero_Gomoku/issues/25</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        node.update_recursive(-leaf_value)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">evaluate_rollout</span>(<span class="params">self, state: Board, limit=<span class="number">1000</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Use the rollout policy to play until the end of the game,</span></span><br><span class="line"><span class="string">        returning +1 if the current player wins, -1 if the opponent wins,</span></span><br><span class="line"><span class="string">        and 0 if it is a tie.&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">assert</span> limit &gt;= <span class="number">1</span>, <span class="string">&#x27;Limit must be &gt; 0&#x27;</span></span><br><span class="line">        player = state.current_player</span><br><span class="line">        winner = <span class="literal">None</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(limit):</span><br><span class="line">            end, winner = state.game_over()</span><br><span class="line">            <span class="keyword">if</span> end:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            action_probs = random_rollout_policy_fn(state)</span><br><span class="line">            max_action = <span class="built_in">max</span>(action_probs, key=itemgetter(<span class="number">1</span>))[<span class="number">0</span>]</span><br><span class="line">            state.perform_move(max_action)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># If no break from the loop, issue a warning.</span></span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;WARNING: rollout reached move limit&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> winner <span class="keyword">is</span> <span class="literal">None</span>:  <span class="comment"># tie</span></span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span> <span class="keyword">if</span> winner == player <span class="keyword">else</span> -<span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_move</span>(<span class="params">self, state</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Runs all playouts sequentially and returns the most visited action.</span></span><br><span class="line"><span class="string">        state: the current game state</span></span><br><span class="line"><span class="string">        :return: the selected action</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> n <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.n_playout):</span><br><span class="line">            state_copy = deepcopy(state)</span><br><span class="line">            <span class="variable language_">self</span>.playout(state_copy)</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">max</span>(<span class="variable language_">self</span>.root.children.items(), key=<span class="keyword">lambda</span> act_node: act_node[<span class="number">1</span>].n_visits)[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">update_with_move</span>(<span class="params">self, last_move</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Step forward in the tree, keeping everything we already know</span></span><br><span class="line"><span class="string">        about the subtree.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> last_move <span class="keyword">in</span> <span class="variable language_">self</span>.root.children:</span><br><span class="line">            <span class="variable language_">self</span>.root = <span class="variable language_">self</span>.root.children[last_move]</span><br><span class="line">            <span class="variable language_">self</span>.root.parent = <span class="literal">None</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="variable language_">self</span>.root = TreeNode(<span class="literal">None</span>, <span class="number">1.0</span>)</span><br></pre></td></tr></table></figure><h3 id="alphazero-算法的-mcts"><a class="markdownIt-Anchor" href="#alphazero-算法的-mcts"></a> AlphaZero 算法的 MCTS</h3><p>直接继承上面的纯蒙特卡洛，重写 playout（一次完整的 MCTS 模拟，选择、扩展、仿真、回溯），其中用神经网络（<code>policy_value_fn</code>）代替仿真步骤。由于训练过程需要完整的决策概率，所以实现了 <code>get_move_probs</code>。</p><p>关于概率计算<sup class="footnote-ref"><a href="#fn1" id="fnref1:1">[1:1]</a></sup></p><blockquote><p>MCTS 搜索完毕后，模型就可以在 MCTS 的根节点 s 基于以下公式选择行棋的 MCTS 分支了:</p><p><img src="https://ai-studio-static-online.cdn.bcebos.com/3a14cd6be857468b9bcbcdee61d6ecdb325a864649284df4a8aa5b1d2b7605a0" alt="https://ai-studio-static-online.cdn.bcebos.com/3a14cd6be857468b9bcbcdee61d6ecdb325a864649284df4a8aa5b1d2b7605a0" /></p><p>τ 是用来控制探索的程度，τ 的取值介于 (0,1] 之间，当 τ越接近于 1 时，神经网络的采样越接近于 MCTS 的原始采样，当 τ 越接近于 0 时，神经网络的采样越接近于贪婪策略，即选择最大访问次数 N 所对应的动作。 因为在 τ 很小的情况下，直接计算访问次数 N 的 τ 次方根可能会导致数值异常，为了避免这种情况，在计算行动概率时，先将访问次数 N 加上一个非常小的数值（本项目是 1e-10），取自然对数后乘上 1/τ，再用一个简化的 softmax 函数将输出还原为概率，这和原始公式在数学上基本上是等效的。</p></blockquote><p>计算 softmax 时减去一个最大值是为了防止指数运算得到的结果过大，最终的结果仍是相同的。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">softmax</span>(<span class="params">x</span>):</span><br><span class="line">    probs = np.exp(x - np.<span class="built_in">max</span>(x))</span><br><span class="line">    probs /= np.<span class="built_in">sum</span>(probs)</span><br><span class="line">    <span class="keyword">return</span> probs</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MCTSAlphaZero</span>(<span class="title class_ inherited__">MCTSPure</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">playout</span>(<span class="params">self, state: Board</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Run a single playout from the root to the leaf, getting a value at</span></span><br><span class="line"><span class="string">        the leaf and propagating it back through its parents.</span></span><br><span class="line"><span class="string">        State is modified in-place, so a copy must be provided.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        node = <span class="variable language_">self</span>.root</span><br><span class="line">        <span class="keyword">while</span> <span class="keyword">not</span> node.is_leaf():</span><br><span class="line">            action, node = node.select(<span class="variable language_">self</span>.c_puct)</span><br><span class="line">            state.perform_move(action)</span><br><span class="line">        <span class="comment"># Evaluate the leaf using a network which outputs a list of</span></span><br><span class="line">        <span class="comment"># (action, probability) tuples player and also a score v in [-1, 1]</span></span><br><span class="line">        <span class="comment"># for the current player.</span></span><br><span class="line">        action_probs, leaf_value = <span class="variable language_">self</span>.policy_value_fn(state)</span><br><span class="line">        end, winner = state.game_over()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> end:</span><br><span class="line">            node.expand(action_probs)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># for end state，return the &quot;true&quot; leaf_value</span></span><br><span class="line">            <span class="keyword">if</span> winner <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">                leaf_value = <span class="number">0</span></span><br><span class="line">            <span class="keyword">elif</span> winner == state.current_player:</span><br><span class="line">                leaf_value = <span class="number">1</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                leaf_value = -<span class="number">1</span></span><br><span class="line">        <span class="comment"># Update value and visit count of nodes in this traversal.</span></span><br><span class="line">        node.update_recursive(-leaf_value)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_move_probs</span>(<span class="params">self, state, temperature=<span class="number">1e-3</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;Run all playouts sequentially and return the available actions and</span></span><br><span class="line"><span class="string">        their corresponding probabilities.</span></span><br><span class="line"><span class="string">        state: the current game state</span></span><br><span class="line"><span class="string">        temperature: temperature parameter in (0, 1] controls the level of exploration</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> n <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.n_playout):</span><br><span class="line">            state_copy = deepcopy(state)</span><br><span class="line">            <span class="variable language_">self</span>.playout(state_copy)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># calc the move probabilities based on visit counts at the root node</span></span><br><span class="line">        act_visits = [(act, node.n_visits) <span class="keyword">for</span> act, node <span class="keyword">in</span> <span class="variable language_">self</span>.root.children.items()]</span><br><span class="line">        acts, visits = <span class="built_in">zip</span>(*act_visits)</span><br><span class="line">        act_probs = softmax(<span class="number">1.0</span> / temperature * np.log(np.array(visits) + <span class="number">1e-10</span>))</span><br><span class="line">        <span class="keyword">return</span> acts, act_probs</span><br></pre></td></tr></table></figure><h3 id="基于两种不同的-mcts-实现-player"><a class="markdownIt-Anchor" href="#基于两种不同的-mcts-实现-player"></a> 基于两种不同的 MCTS 实现 player</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">random_rollout_policy_fn</span>(<span class="params">board: Board</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;a coarse, fast version of policy_fn used in the rollout phase.&quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># rollout randomly</span></span><br><span class="line">    available_moves = board.get_available_moves()</span><br><span class="line">    action_probs = np.random.rand(<span class="built_in">len</span>(available_moves))</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">zip</span>(available_moves, action_probs)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">random_policy_value_fn</span>(<span class="params">board: Board</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;a function that takes in a state and outputs a list of (action, probability)</span></span><br><span class="line"><span class="string">    tuples and a score for the state&quot;&quot;&quot;</span></span><br><span class="line">    <span class="comment"># return uniform probabilities and 0 score for pure MCTSPure</span></span><br><span class="line">    available_moves = board.get_available_moves()</span><br><span class="line">    action_probs = np.ones(<span class="built_in">len</span>(available_moves)) / <span class="built_in">len</span>(available_moves)</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">zip</span>(available_moves, action_probs), <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BaceMCTSPlayer</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;AI player based on MCTS&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, mcts</span>):</span><br><span class="line">        <span class="variable language_">self</span>.player = <span class="literal">None</span></span><br><span class="line">        <span class="variable language_">self</span>.mcts = mcts</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set_player</span>(<span class="params">self, player: <span class="built_in">int</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.player = player</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">reset_player</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="variable language_">self</span>.mcts.update_with_move(-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">act</span>(<span class="params">self, board: Board</span>):</span><br><span class="line">        <span class="keyword">raise</span> NotImplementedError()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MCTSPlayer</span>(<span class="title class_ inherited__">BaceMCTSPlayer</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;AI player based on MCTSPure&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, c_puct=<span class="number">5</span>, n_playout=<span class="number">2000</span></span>):</span><br><span class="line">        <span class="built_in">super</span>().__init__(MCTSPure(random_policy_value_fn, c_puct, n_playout))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">act</span>(<span class="params">self, board: Board</span>):</span><br><span class="line">        sensible_moves = board.get_available_moves()</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(sensible_moves) &gt; <span class="number">0</span>:</span><br><span class="line">            move = <span class="variable language_">self</span>.mcts.get_move(board)</span><br><span class="line">            <span class="comment"># https://github.com/junxiaosong/AlphaZero_Gomoku/issues/108</span></span><br><span class="line">            <span class="variable language_">self</span>.mcts.update_with_move(-<span class="number">1</span>)</span><br><span class="line">            <span class="comment"># self.mcts.update_with_move(move)</span></span><br><span class="line">            <span class="keyword">return</span> move</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;WARNING: the board is full&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MCTSAlphaZeroPlayer</span>(<span class="title class_ inherited__">BaceMCTSPlayer</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;AI player based on MCTSAlphaZero&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, policy_value_function, c_puct=<span class="number">5</span>, n_playout=<span class="number">2000</span>, dirichlet=<span class="number">0.3</span>, selfplay=<span class="literal">False</span></span>):</span><br><span class="line">        <span class="built_in">super</span>().__init__(MCTSAlphaZero(policy_value_function, c_puct, n_playout))</span><br><span class="line">        <span class="variable language_">self</span>.dirichlet = dirichlet</span><br><span class="line">        <span class="variable language_">self</span>.selfplay = selfplay</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">act</span>(<span class="params">self, board, temperature=<span class="number">1e-3</span>, return_prob=<span class="literal">False</span></span>):</span><br><span class="line">        sensible_moves = board.get_available_moves()</span><br><span class="line">        <span class="comment"># the pi vector returned by MCTS as in the alphaGo Zero paper</span></span><br><span class="line">        move_probs = np.zeros(board.width * board.height)</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">len</span>(sensible_moves) &gt; <span class="number">0</span>:</span><br><span class="line">            acts, probs = <span class="variable language_">self</span>.mcts.get_move_probs(board, temperature)</span><br><span class="line">            move_probs[<span class="built_in">list</span>(acts)] = probs</span><br><span class="line">            <span class="keyword">if</span> <span class="variable language_">self</span>.selfplay:</span><br><span class="line">                <span class="comment"># add Dirichlet Noise for exploration (needed for</span></span><br><span class="line">                <span class="comment"># self-play training)</span></span><br><span class="line">                move = np.random.choice(</span><br><span class="line">                    acts,</span><br><span class="line">                    p=<span class="number">0.75</span> * probs + <span class="number">0.25</span> * np.random.dirichlet(<span class="variable language_">self</span>.dirichlet * np.ones(<span class="built_in">len</span>(probs)))</span><br><span class="line">                )</span><br><span class="line">                <span class="comment"># update the root node and reuse the search tree</span></span><br><span class="line">                <span class="variable language_">self</span>.mcts.update_with_move(move)</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="comment"># with the default temperature=1e-3, it is almost equivalent</span></span><br><span class="line">                <span class="comment"># to choosing the move with the highest prob</span></span><br><span class="line">                move = np.random.choice(acts, p=probs)</span><br><span class="line">                <span class="comment"># reset the root node</span></span><br><span class="line">                <span class="variable language_">self</span>.mcts.update_with_move(-<span class="number">1</span>)</span><br><span class="line">                <span class="comment"># location = board.move_to_location(move)</span></span><br><span class="line">                <span class="comment"># print(&quot;AI move: %d,%d\n&quot; % (location[0], location[1]))</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> return_prob:</span><br><span class="line">                <span class="keyword">return</span> move, move_probs</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">return</span> move</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;WARNING: the board is full&quot;</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="神经网络实现"><a class="markdownIt-Anchor" href="#神经网络实现"></a> 神经网络实现</h2><p>这里我使用的网络与  <a href="https://github.com/junxiaosong">@junxiaosong</a> 并不相同，他使用的网络在 6x6 4 子连珠的情况下效果非常好，但是将棋盘扩大之后网络的拟合能力有些不足，效果没有那么好，所以我使用了残差层（<code>ResidualBlock</code>）。</p><p>具体的理论实现请参考<sup class="footnote-ref"><a href="#fn1" id="fnref1:2">[1:2]</a></sup></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> warnings</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"><span class="keyword">import</span> paddle</span><br><span class="line"><span class="keyword">from</span> paddle <span class="keyword">import</span> nn</span><br><span class="line"><span class="keyword">from</span> paddle.static <span class="keyword">import</span> InputSpec</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> game <span class="keyword">import</span> Board</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ResidualBlock</span>(nn.Layer):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, channels</span>):</span><br><span class="line">        <span class="built_in">super</span>(ResidualBlock, <span class="variable language_">self</span>).__init__()</span><br><span class="line">        <span class="variable language_">self</span>.conv_block = nn.Sequential(</span><br><span class="line">            nn.Conv2D(channels, channels, kernel_size=<span class="number">3</span>, padding=<span class="number">1</span>, bias_attr=<span class="literal">False</span>),</span><br><span class="line">            nn.BatchNorm2D(channels),</span><br><span class="line">            nn.ReLU(),</span><br><span class="line">            nn.Conv2D(channels, channels, kernel_size=<span class="number">3</span>, padding=<span class="number">1</span>, bias_attr=<span class="literal">False</span>),</span><br><span class="line">            nn.BatchNorm2D(channels)</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        residual = x  <span class="comment"># 旁路连接</span></span><br><span class="line">        out = <span class="variable language_">self</span>.conv_block(x)</span><br><span class="line">        out += residual  <span class="comment"># 跳跃连接</span></span><br><span class="line">        <span class="keyword">return</span> nn.functional.relu(out)  <span class="comment"># 经过 ReLU 激活</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Net</span>(nn.Layer):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    https://github.com/junxiaosong/AlphaZero_Gomoku</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, board_width, board_height, num_blocks=<span class="number">5</span></span>):</span><br><span class="line">        <span class="built_in">super</span>().__init__()</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.board_width = board_width</span><br><span class="line">        <span class="variable language_">self</span>.board_height = board_height</span><br><span class="line"></span><br><span class="line">        <span class="comment"># common layers</span></span><br><span class="line">        <span class="variable language_">self</span>.common_layers = nn.Sequential(</span><br><span class="line">            nn.Conv2D(<span class="number">4</span>, <span class="number">128</span>, kernel_size=<span class="number">3</span>, stride=<span class="number">1</span>, padding=<span class="number">2</span>),</span><br><span class="line">            nn.BatchNorm2D(<span class="number">128</span>),</span><br><span class="line">            *[ResidualBlock(<span class="number">128</span>) <span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(num_blocks)]</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="comment"># action policy layers</span></span><br><span class="line">        <span class="variable language_">self</span>.act_layers = nn.Sequential(</span><br><span class="line">            nn.Conv2D(<span class="number">128</span>, <span class="number">4</span>, kernel_size=<span class="number">1</span>, stride=<span class="number">1</span>, padding=<span class="number">0</span>),</span><br><span class="line">            nn.ReLU(),</span><br><span class="line">            nn.Flatten(),</span><br><span class="line">            nn.Linear(<span class="number">4</span> * (board_height + <span class="number">2</span>) * (board_width + <span class="number">2</span>), board_width * board_height),</span><br><span class="line">            nn.LogSoftmax()</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="comment"># action value layers</span></span><br><span class="line">        <span class="variable language_">self</span>.val_layers = nn.Sequential(</span><br><span class="line">            nn.Conv2D(<span class="number">128</span>, <span class="number">2</span>, kernel_size=<span class="number">1</span>, stride=<span class="number">1</span>, padding=<span class="number">0</span>),</span><br><span class="line">            nn.ReLU(),</span><br><span class="line">            nn.Flatten(),</span><br><span class="line">            nn.Linear(<span class="number">2</span> * (board_height + <span class="number">2</span>) * (board_width + <span class="number">2</span>), <span class="number">64</span>),</span><br><span class="line">            nn.ReLU(),</span><br><span class="line">            nn.Linear(<span class="number">64</span>, <span class="number">1</span>),</span><br><span class="line">            nn.Tanh()</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        x = <span class="variable language_">self</span>.common_layers(x)</span><br><span class="line">        x_act = <span class="variable language_">self</span>.act_layers(x)</span><br><span class="line">        x_val = <span class="variable language_">self</span>.val_layers(x)</span><br><span class="line">        <span class="keyword">return</span> x_act, x_val</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PolicyValueNet</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, board_width, board_height, model_file=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="variable language_">self</span>.board_width = board_width</span><br><span class="line">        <span class="variable language_">self</span>.board_height = board_height</span><br><span class="line">        <span class="variable language_">self</span>.model_file = model_file</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.l2_const = <span class="number">1e-4</span>  <span class="comment"># coef of l2 penalty</span></span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.net = Net(<span class="variable language_">self</span>.board_width, <span class="variable language_">self</span>.board_height)</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.optimizer = paddle.optimizer.Adam(parameters=<span class="variable language_">self</span>.net.parameters(), weight_decay=<span class="variable language_">self</span>.l2_const,</span><br><span class="line">                                               learning_rate=<span class="number">2e-3</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> model_file <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="comment"># if not os.path.isfile(model_file):</span></span><br><span class="line">            <span class="comment">#     raise FileNotFoundError(f&#x27;&#123;model_file&#125; is not exist&#x27;)</span></span><br><span class="line">            net_params = paddle.load(model_file + <span class="string">&#x27;.pdparams&#x27;</span>)</span><br><span class="line">            <span class="variable language_">self</span>.net.set_state_dict(net_params)</span><br><span class="line">            opt_params = paddle.load(model_file + <span class="string">&#x27;.pdopt&#x27;</span>)</span><br><span class="line">            <span class="variable language_">self</span>.optimizer.set_state_dict(opt_params)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">policy_value</span>(<span class="params">self, state: np.ndarray | <span class="built_in">list</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        input: a batch of states</span></span><br><span class="line"><span class="string">        output: a batch of action probabilities and state values</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        log_act_probs, value = <span class="variable language_">self</span>.net(paddle.to_tensor(state, <span class="string">&#x27;float32&#x27;</span>))</span><br><span class="line">        act_probs = log_act_probs.exp()</span><br><span class="line">        <span class="keyword">return</span> act_probs.numpy(), value.numpy()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">policy_value_fn</span>(<span class="params">self, board: Board</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        input: board</span></span><br><span class="line"><span class="string">        output: a list of (action, probability) tuples for each available</span></span><br><span class="line"><span class="string">        action and the score of the board state</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        legal_positions = board.get_available_moves()</span><br><span class="line">        current_state = np.ascontiguousarray(board.state.reshape(-<span class="number">1</span>, <span class="number">4</span>, <span class="variable language_">self</span>.board_height, <span class="variable language_">self</span>.board_width))</span><br><span class="line">        act_probs, value = <span class="variable language_">self</span>.policy_value(current_state)</span><br><span class="line">        value = value[<span class="number">0</span>][<span class="number">0</span>]</span><br><span class="line">        act_probs = <span class="built_in">zip</span>(legal_positions, act_probs[<span class="number">0</span>][legal_positions])</span><br><span class="line">        <span class="keyword">return</span> act_probs, value</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">train_step</span>(<span class="params">self, state_batch, mcts_probs, winner_batch, lr</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;perform a training step&quot;&quot;&quot;</span></span><br><span class="line">        state_batch = paddle.to_tensor(state_batch, dtype=<span class="string">&#x27;float32&#x27;</span>)</span><br><span class="line">        mcts_probs = paddle.to_tensor(mcts_probs, dtype=<span class="string">&#x27;float32&#x27;</span>)</span><br><span class="line">        winner_batch = paddle.to_tensor(winner_batch, dtype=<span class="string">&#x27;float32&#x27;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># zero the parameter gradients</span></span><br><span class="line">        <span class="variable language_">self</span>.optimizer.clear_grad()</span><br><span class="line">        <span class="comment"># set learning rate</span></span><br><span class="line">        <span class="variable language_">self</span>.optimizer.set_lr(lr)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># forward</span></span><br><span class="line">        log_act_probs, value = <span class="variable language_">self</span>.net(state_batch)</span><br><span class="line">        <span class="comment"># define the loss = (z - v)^2 - pi^T * log(p) + c||theta||^2</span></span><br><span class="line">        <span class="comment"># Note: the L2 penalty is incorporated in optimizer</span></span><br><span class="line">        value = paddle.reshape(x=value, shape=[-<span class="number">1</span>])</span><br><span class="line">        value_loss = nn.functional.mse_loss(value, winner_batch)</span><br><span class="line">        policy_loss = -paddle.mean(paddle.<span class="built_in">sum</span>(mcts_probs * log_act_probs, <span class="number">1</span>))</span><br><span class="line">        loss = value_loss + policy_loss</span><br><span class="line">        <span class="comment"># backward and optimize</span></span><br><span class="line">        loss.backward()</span><br><span class="line">        <span class="variable language_">self</span>.optimizer.step()</span><br><span class="line">        <span class="comment"># calc policy entropy, for monitoring only</span></span><br><span class="line">        entropy = -paddle.mean(</span><br><span class="line">            paddle.<span class="built_in">sum</span>(paddle.exp(log_act_probs) * log_act_probs, <span class="number">1</span>)</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">return</span> loss.item(), entropy.item()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_policy_param</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">self</span>.net.state_dict()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set_policy_param</span>(<span class="params">self, param</span>):</span><br><span class="line">        <span class="variable language_">self</span>.net.set_state_dict(param)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">save_model</span>(<span class="params">self, path: Path | <span class="built_in">str</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot; save model params to file &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">isinstance</span>(path, Path):</span><br><span class="line">            path = <span class="built_in">str</span>(path)</span><br><span class="line">        net_params = <span class="variable language_">self</span>.get_policy_param()  <span class="comment"># get model params</span></span><br><span class="line">        paddle.save(net_params, path + <span class="string">&#x27;.pdparams&#x27;</span>)</span><br><span class="line">        opt_params = <span class="variable language_">self</span>.optimizer.state_dict()</span><br><span class="line">        paddle.save(opt_params, path + <span class="string">&#x27;.pdopt&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">save_for_predict</span>(<span class="params">self, path: Path | <span class="built_in">str</span></span>):</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">isinstance</span>(path, Path):</span><br><span class="line">            path = <span class="built_in">str</span>(path)</span><br><span class="line">        <span class="comment"># save inferencing format model</span></span><br><span class="line">        paddle.jit.save(</span><br><span class="line">            <span class="variable language_">self</span>.net, path, input_spec=[</span><br><span class="line">                InputSpec(shape=[<span class="literal">None</span>, <span class="number">4</span>, <span class="variable language_">self</span>.board_height, <span class="variable language_">self</span>.board_width], dtype=<span class="string">&#x27;float32&#x27;</span>)</span><br><span class="line">            ]</span><br><span class="line">        )</span><br></pre></td></tr></table></figure><h2 id="训练流程"><a class="markdownIt-Anchor" href="#训练流程"></a> 训练流程</h2><p>整体的思路是，先自我对弈收集数据，然后进行数据扩充（旋转，镜像），再通过梯度下降优化参数，循环进行</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> deque</span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> Path</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> game <span class="keyword">import</span> Board, GameGomuku</span><br><span class="line"><span class="keyword">from</span> mcts <span class="keyword">import</span> MCTSPlayer, MCTSAlphaZeroPlayer</span><br><span class="line"><span class="keyword">from</span> policy_value_net <span class="keyword">import</span> PolicyValueNet</span><br><span class="line"></span><br><span class="line">MODEL_DIR = Path(<span class="string">&#x27;./models&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TrainPipeline</span>:</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, init_model=<span class="literal">None</span>, **kwargs</span>):</span><br><span class="line">        <span class="variable language_">self</span>.name = name</span><br><span class="line"></span><br><span class="line">        <span class="comment"># params of the board and the game</span></span><br><span class="line">        <span class="variable language_">self</span>.board_width = kwargs.get(<span class="string">&#x27;board_width&#x27;</span>, <span class="number">8</span>)</span><br><span class="line">        <span class="variable language_">self</span>.board_height = kwargs.get(<span class="string">&#x27;board_height&#x27;</span>, <span class="number">8</span>)</span><br><span class="line">        <span class="variable language_">self</span>.n_in_rows = kwargs.get(<span class="string">&#x27;n_in_rows&#x27;</span>, <span class="number">4</span>)</span><br><span class="line">        <span class="variable language_">self</span>.board = Board(width=<span class="variable language_">self</span>.board_width,</span><br><span class="line">                           height=<span class="variable language_">self</span>.board_height,</span><br><span class="line">                           n_in_rows=<span class="variable language_">self</span>.n_in_rows)</span><br><span class="line">        <span class="variable language_">self</span>.game = GameGomuku(<span class="variable language_">self</span>.board)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># training params</span></span><br><span class="line">        <span class="variable language_">self</span>.learning_rate = kwargs.get(<span class="string">&#x27;learning_rate&#x27;</span>, <span class="number">2e-3</span>)</span><br><span class="line">        <span class="variable language_">self</span>.lr_multiplier = kwargs.get(<span class="string">&#x27;lr_multiplier&#x27;</span>, <span class="number">1.0</span>)  <span class="comment"># adaptively adjust the learning rate based on KL</span></span><br><span class="line">        <span class="variable language_">self</span>.temperature = kwargs.get(<span class="string">&#x27;temperature&#x27;</span>, <span class="number">1.0</span>)</span><br><span class="line">        <span class="variable language_">self</span>.n_playout = kwargs.get(<span class="string">&#x27;n_playout&#x27;</span>, <span class="number">400</span>)</span><br><span class="line">        <span class="variable language_">self</span>.c_puct = kwargs.get(<span class="string">&#x27;c_puct&#x27;</span>, <span class="number">5</span>)</span><br><span class="line">        <span class="variable language_">self</span>.buffer_size = kwargs.get(<span class="string">&#x27;buffer_size&#x27;</span>, <span class="number">10000</span>)</span><br><span class="line">        <span class="variable language_">self</span>.batch_size = kwargs.get(<span class="string">&#x27;batch_size&#x27;</span>, <span class="number">512</span>)</span><br><span class="line">        <span class="variable language_">self</span>.data_buffer = deque(maxlen=<span class="variable language_">self</span>.buffer_size)</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.play_batch_size = <span class="number">1</span></span><br><span class="line">        <span class="variable language_">self</span>.epochs = kwargs.get(<span class="string">&#x27;epochs&#x27;</span>, <span class="number">5</span>)  <span class="comment"># num of train_steps for each update</span></span><br><span class="line">        <span class="variable language_">self</span>.kl_targ = kwargs.get(<span class="string">&#x27;kl_targ&#x27;</span>, <span class="number">0.02</span>)</span><br><span class="line">        <span class="variable language_">self</span>.check_freq = kwargs.get(<span class="string">&#x27;check_freq&#x27;</span>, <span class="number">50</span>)</span><br><span class="line">        <span class="variable language_">self</span>.game_batch_num = kwargs.get(<span class="string">&#x27;game_batch_num&#x27;</span>, <span class="number">1500</span>)</span><br><span class="line">        <span class="variable language_">self</span>.dirichlet = kwargs.get(<span class="string">&#x27;dirichlet&#x27;</span>, <span class="number">0.3</span>)</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.best_win_ratio = <span class="number">0.0</span></span><br><span class="line">        <span class="comment"># num of simulations used for the pure mcts, which is used as</span></span><br><span class="line">        <span class="comment"># the opponent to evaluate the trained policy</span></span><br><span class="line">        <span class="variable language_">self</span>.pure_mcts_playout_num = kwargs.get(<span class="string">&#x27;pure_mcts_playout_num&#x27;</span>, <span class="number">1000</span>)</span><br><span class="line">        <span class="keyword">if</span> init_model <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            logger.info(<span class="string">f&#x27;Loading model from [underline]<span class="subst">&#123;init_model&#125;</span>[/]&#x27;</span>)</span><br><span class="line">            <span class="comment"># start training from an initial policy-value net</span></span><br><span class="line">            <span class="variable language_">self</span>.policy_value_net = PolicyValueNet(<span class="variable language_">self</span>.board_width,</span><br><span class="line">                                                   <span class="variable language_">self</span>.board_height,</span><br><span class="line">                                                   model_file=init_model)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># start training from a new policy-value net</span></span><br><span class="line">            <span class="variable language_">self</span>.policy_value_net = PolicyValueNet(<span class="variable language_">self</span>.board_width,</span><br><span class="line">                                                   <span class="variable language_">self</span>.board_height)</span><br><span class="line">        <span class="variable language_">self</span>.mcts_player = MCTSAlphaZeroPlayer(<span class="variable language_">self</span>.policy_value_net.policy_value_fn,</span><br><span class="line">                                               c_puct=<span class="variable language_">self</span>.c_puct,</span><br><span class="line">                                               n_playout=<span class="variable language_">self</span>.n_playout,</span><br><span class="line">                                               dirichlet=<span class="variable language_">self</span>.dirichlet,</span><br><span class="line">                                               selfplay=<span class="literal">True</span>)</span><br><span class="line"></span><br><span class="line">        <span class="variable language_">self</span>.episode_len = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_equi_data</span>(<span class="params">self, play_data</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;augment the data set by rotation and flipping</span></span><br><span class="line"><span class="string">        :param play_data: [(state, mcts_prob, winner_z), ..., ...]</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        extend_data = []</span><br><span class="line">        <span class="keyword">for</span> state, mcts_prob, winner <span class="keyword">in</span> play_data:</span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> ([<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>] <span class="keyword">if</span> (<span class="variable language_">self</span>.board_width == <span class="variable language_">self</span>.board_height) <span class="keyword">else</span> [<span class="number">0</span>, <span class="number">2</span>]):</span><br><span class="line">                <span class="comment"># rotate counterclockwise</span></span><br><span class="line">                equi_state = np.rot90(state, i, axes=(<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line">                equi_mcts_prob = np.rot90(mcts_prob.reshape(<span class="variable language_">self</span>.board_height, <span class="variable language_">self</span>.board_width), i)</span><br><span class="line">                extend_data.append((equi_state, equi_mcts_prob.flatten(), winner))</span><br><span class="line">                <span class="comment"># flip horizontally</span></span><br><span class="line">                equi_state = equi_state[:, :, ::-<span class="number">1</span>]</span><br><span class="line">                equi_mcts_prob = np.fliplr(equi_mcts_prob)</span><br><span class="line">                extend_data.append((equi_state, equi_mcts_prob.flatten(), winner))</span><br><span class="line">        <span class="keyword">return</span> extend_data</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">collect_selfplay_data</span>(<span class="params">self, n_games=<span class="number">1</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;collect self-play data for training&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n_games):</span><br><span class="line">            winner, play_data = <span class="variable language_">self</span>.game.start_self_play(<span class="variable language_">self</span>.mcts_player, temperature=<span class="variable language_">self</span>.temperature)</span><br><span class="line">            play_data = <span class="built_in">list</span>(play_data)[:]</span><br><span class="line">            <span class="variable language_">self</span>.episode_len = <span class="built_in">len</span>(play_data)</span><br><span class="line">            <span class="comment"># augment the data</span></span><br><span class="line">            play_data = <span class="variable language_">self</span>.get_equi_data(play_data)</span><br><span class="line">            <span class="variable language_">self</span>.data_buffer.extend(play_data)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">policy_update</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;update the policy-value net&quot;&quot;&quot;</span></span><br><span class="line">        mini_batch = random.sample(<span class="variable language_">self</span>.data_buffer, <span class="variable language_">self</span>.batch_size)</span><br><span class="line">        state_batch, mcts_probs_batch, winner_batch = <span class="built_in">zip</span>(*mini_batch)</span><br><span class="line">        old_probs, old_v = <span class="variable language_">self</span>.policy_value_net.policy_value(state_batch)</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.epochs):</span><br><span class="line">            loss, entropy = <span class="variable language_">self</span>.policy_value_net.train_step(</span><br><span class="line">                state_batch,</span><br><span class="line">                mcts_probs_batch,</span><br><span class="line">                winner_batch,</span><br><span class="line">                <span class="variable language_">self</span>.learning_rate * <span class="variable language_">self</span>.lr_multiplier)</span><br><span class="line">            new_probs, new_v = <span class="variable language_">self</span>.policy_value_net.policy_value(state_batch)</span><br><span class="line">            kl = np.mean(np.<span class="built_in">sum</span>(old_probs * (np.log(old_probs + <span class="number">1e-10</span>) - np.log(new_probs + <span class="number">1e-10</span>)), axis=<span class="number">1</span>))</span><br><span class="line">            <span class="keyword">if</span> kl &gt; <span class="variable language_">self</span>.kl_targ * <span class="number">4</span>:  <span class="comment"># early stopping if D_KL diverges badly</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="comment"># adaptively adjust the learning rate</span></span><br><span class="line">        <span class="keyword">if</span> kl &gt; <span class="variable language_">self</span>.kl_targ * <span class="number">2</span> <span class="keyword">and</span> <span class="variable language_">self</span>.lr_multiplier &gt; <span class="number">0.1</span>:</span><br><span class="line">            <span class="variable language_">self</span>.lr_multiplier /= <span class="number">1.5</span></span><br><span class="line">        <span class="keyword">elif</span> kl &lt; <span class="variable language_">self</span>.kl_targ / <span class="number">2</span> <span class="keyword">and</span> <span class="variable language_">self</span>.lr_multiplier &lt; <span class="number">20</span>:</span><br><span class="line">            <span class="variable language_">self</span>.lr_multiplier *= <span class="number">1.5</span></span><br><span class="line"></span><br><span class="line">        explained_var_old = (<span class="number">1</span> -</span><br><span class="line">                             np.var(np.array(winner_batch) - old_v.flatten()) /</span><br><span class="line">                             np.var(np.array(winner_batch)))</span><br><span class="line">        explained_var_new = (<span class="number">1</span> -</span><br><span class="line">                             np.var(np.array(winner_batch) - new_v.flatten()) /</span><br><span class="line">                             np.var(np.array(winner_batch)))</span><br><span class="line">        logger.info(<span class="string">f&#x27;[bold]Train[/] Step &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;kl: <span class="subst">&#123;kl:<span class="number">.5</span>f&#125;</span>, &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;lr_multiplier: <span class="subst">&#123;self.lr_multiplier:<span class="number">.3</span>f&#125;</span>, &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;loss: <span class="subst">&#123;loss&#125;</span>, &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;entropy: <span class="subst">&#123;entropy&#125;</span>, &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;explained_var_old: <span class="subst">&#123;explained_var_old:<span class="number">.3</span>f&#125;</span>, &#x27;</span></span><br><span class="line">                    <span class="string">f&#x27;explained_var_new: <span class="subst">&#123;explained_var_new:<span class="number">.3</span>f&#125;</span>&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">policy_evaluate</span>(<span class="params">self, n_games=<span class="number">10</span></span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Evaluate the trained policy by playing against the pure MCTS player</span></span><br><span class="line"><span class="string">        Note: this is only for monitoring the progress of training</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        logger.info(<span class="string">f&#x27;[bold]Evaluating[/]&#x27;</span>)</span><br><span class="line">        current_mcts_player = MCTSAlphaZeroPlayer(<span class="variable language_">self</span>.policy_value_net.policy_value_fn,</span><br><span class="line">                                                  c_puct=<span class="variable language_">self</span>.c_puct,</span><br><span class="line">                                                  n_playout=<span class="variable language_">self</span>.n_playout)</span><br><span class="line">        pure_mcts_player = MCTSPlayer(c_puct=<span class="number">5</span>,</span><br><span class="line">                                      n_playout=<span class="variable language_">self</span>.pure_mcts_playout_num)</span><br><span class="line">        win, lose, tie = <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(n_games):</span><br><span class="line">            winner = <span class="variable language_">self</span>.game.start_play(current_mcts_player,</span><br><span class="line">                                          pure_mcts_player,</span><br><span class="line">                                          start_player=i % <span class="number">2</span> + <span class="number">1</span>, )</span><br><span class="line">            <span class="keyword">if</span> winner == <span class="number">1</span>:</span><br><span class="line">                logger.info(<span class="string">f&#x27;    <span class="subst">&#123;i + <span class="number">1</span>&#125;</span>/<span class="subst">&#123;n_games&#125;</span> [bold]Win[/]&#x27;</span>)</span><br><span class="line">                win += <span class="number">1</span></span><br><span class="line">            <span class="keyword">elif</span> winner == <span class="number">2</span>:</span><br><span class="line">                logger.info(<span class="string">f&#x27;    <span class="subst">&#123;i + <span class="number">1</span>&#125;</span>/<span class="subst">&#123;n_games&#125;</span> [bold]Lose[/]&#x27;</span>)</span><br><span class="line">                lose += <span class="number">1</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                logger.info(<span class="string">f&#x27;    <span class="subst">&#123;i + <span class="number">1</span>&#125;</span>/<span class="subst">&#123;n_games&#125;</span> [bold]Tie[/]&#x27;</span>)</span><br><span class="line">                tie += <span class="number">1</span></span><br><span class="line">        win_ratio = <span class="number">1.0</span> * (win + <span class="number">0.5</span> * tie) / n_games</span><br><span class="line">        logger.info(<span class="string">f&quot;num_playouts:<span class="subst">&#123;self.pure_mcts_playout_num&#125;</span>, &quot;</span></span><br><span class="line">                    <span class="string">f&quot;win: <span class="subst">&#123;win&#125;</span>, lose: <span class="subst">&#123;lose&#125;</span>, tie: <span class="subst">&#123;tie&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> win_ratio</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;run the training pipeline&quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            logger.info(<span class="string">&#x27;Start training.&#x27;</span>)</span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="variable language_">self</span>.game_batch_num):</span><br><span class="line">                logger.info(<span class="string">f&#x27;[bold]Selfplay[/] batch <span class="subst">&#123;i + <span class="number">1</span>&#125;</span>&#x27;</span>)</span><br><span class="line">                <span class="variable language_">self</span>.collect_selfplay_data(<span class="variable language_">self</span>.play_batch_size)</span><br><span class="line">                <span class="keyword">if</span> <span class="built_in">len</span>(<span class="variable language_">self</span>.data_buffer) &gt; <span class="variable language_">self</span>.batch_size:</span><br><span class="line">                    <span class="variable language_">self</span>.policy_update()</span><br><span class="line">                <span class="comment"># check the performance of the current model,</span></span><br><span class="line">                <span class="comment"># and save the model params</span></span><br><span class="line">                <span class="keyword">if</span> (i + <span class="number">1</span>) % <span class="variable language_">self</span>.check_freq == <span class="number">0</span>:</span><br><span class="line">                    <span class="variable language_">self</span>.policy_value_net.save_model(</span><br><span class="line">                        MODEL_DIR / <span class="variable language_">self</span>.name / <span class="string">&#x27;train&#x27;</span> / <span class="string">f&#x27;current_step-<span class="subst">&#123;i + <span class="number">1</span>&#125;</span>&#x27;</span></span><br><span class="line">                    )</span><br><span class="line">                    win_ratio = <span class="variable language_">self</span>.policy_evaluate()</span><br><span class="line">                    <span class="keyword">if</span> win_ratio &gt; <span class="variable language_">self</span>.best_win_ratio:</span><br><span class="line">                        logger.info(<span class="string">&quot;[bold]New best policy!!!!!!!!&quot;</span>)</span><br><span class="line">                        <span class="variable language_">self</span>.best_win_ratio = win_ratio</span><br><span class="line">                        <span class="comment"># update the best_policy</span></span><br><span class="line">                        <span class="variable language_">self</span>.policy_value_net.save_model(</span><br><span class="line">                            MODEL_DIR / <span class="variable language_">self</span>.name / <span class="string">&#x27;train&#x27;</span> / <span class="string">&#x27;best&#x27;</span></span><br><span class="line">                        )</span><br><span class="line">                        <span class="variable language_">self</span>.policy_value_net.save_for_predict(</span><br><span class="line">                            MODEL_DIR / <span class="variable language_">self</span>.name / <span class="string">&#x27;production&#x27;</span> / <span class="string">&#x27;best&#x27;</span></span><br><span class="line">                        )</span><br><span class="line">                        <span class="keyword">if</span> (<span class="variable language_">self</span>.best_win_ratio == <span class="number">1.0</span> <span class="keyword">and</span></span><br><span class="line">                                <span class="variable language_">self</span>.pure_mcts_playout_num &lt; <span class="number">5000</span>):</span><br><span class="line">                            <span class="variable language_">self</span>.pure_mcts_playout_num += <span class="number">1000</span></span><br><span class="line">                            <span class="variable language_">self</span>.best_win_ratio = <span class="number">0.0</span></span><br><span class="line">        <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line">            logger.info(<span class="string">&#x27;[bold red]quit.&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">    @property</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">name_suffix</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">f&#x27;<span class="subst">&#123;self.board_width&#125;</span>x<span class="subst">&#123;self.board_height&#125;</span>-<span class="subst">&#123;self.n_in_rows&#125;</span>&#x27;</span></span><br></pre></td></tr></table></figure><h2 id="训练速度优化c多线程"><a class="markdownIt-Anchor" href="#训练速度优化c多线程"></a> 训练速度优化（C++多线程）</h2><p>这部分才是本文的重点。在训练过程中会发现大部分的时间都花费在生成对局信息上了，这部分过程对算力的利用率很低，我一开始是想用 C++ 代替这部分（实际上我也试了，用 C++ 代替 MCTS 搜索之后，2000 次模拟用时从 8 秒多缩减到了 6 秒多，进一步分析后发现，模拟过程中大部分的时间其实在神经网络运算上），效果其实并不算好，想到可以多线程进行（最终实现是多线程同时生成对局 + 并行 MCTS，参考了 <a href="https://github.com/hijkzzz">@hijkzzz</a> <sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>）</p><p><s>其实我还写了一个 Python 多线程版本，但是写完之后想起来 Python 有一个 GIL 锁，会强制限制多线程到一个 CPU 核心，所以这样处理对性能没有帮助</s></p><p>以下是多线程高性能版本</p><blockquote><p>值得一提的是，这些代码还存在一些问题</p><ul><li>存在内存泄露，训练过程中内存越跑越大，具体什么原因没查出来，只看代码我没分析出来</li><li>是否能训练出优秀的模型存疑，因为到现在我并没有训练出一个在 11x11-5 的棋盘上有良好表现的模型</li></ul></blockquote><p>与 <a href="https://github.com/hijkzzz">@hijkzzz</a> 不同的是，我并没有使用 SWIG，而是使用了 pybind11</p><h3 id="pybind11-环境配置"><a class="markdownIt-Anchor" href="#pybind11-环境配置"></a> pybind11 环境配置</h3><p>环境配置之所以单独拿出来说，是因为 Windows 环境下 pybind11 很容易出问题。</p><h4 id="方式1-使用-visual-studio"><a class="markdownIt-Anchor" href="#方式1-使用-visual-studio"></a> 方式1 使用 Visual Studio</h4><p>官方有详细教程 <sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup></p><h4 id="方式2-使用-clion-cmake"><a class="markdownIt-Anchor" href="#方式2-使用-clion-cmake"></a> 方式2 使用 CLion + CMake</h4><h5 id="cmakelisttxt"><a class="markdownIt-Anchor" href="#cmakelisttxt"></a> CMakeList.txt</h5><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">cmake_minimum_required</span>(VERSION <span class="number">3.29</span>)</span><br><span class="line"><span class="keyword">project</span>(Gomuku LANGUAGES CXX)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(CMAKE_CXX_STANDARD <span class="number">20</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#set(PYTHON_EXECUTABLE &quot;E:/Anaconda3/envs/paddle/python.exe&quot;)</span></span><br><span class="line"><span class="keyword">set</span>(pybind11_DIR <span class="string">&quot;E:/Anaconda3/envs/paddle/Lib/site-packages/pybind11/share/cmake/pybind11&quot;</span>)</span><br><span class="line"><span class="keyword">set</span>(PYBIND11_FINDPYTHON <span class="keyword">ON</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#find_package(Python3 COMPONENTS Interpreter Development)</span></span><br><span class="line"><span class="keyword">message</span>(DEBUG <span class="variable">$&#123;PYTHON_INCLUDE_PATH&#125;</span>)</span><br><span class="line"><span class="keyword">include_directories</span>(<span class="variable">$&#123;PYTHON_INCLUDE_PATH&#125;</span>)</span><br><span class="line"><span class="keyword">link_libraries</span>(<span class="variable">$&#123;Python3_LIBRARIES&#125;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">find_package</span>(pybind11 REQUIRED)</span><br><span class="line"><span class="keyword">include_directories</span>(<span class="string">&quot;E:/Anaconda3/envs/paddle/Lib/site-packages/pybind11/include&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">include_directories</span>(src)</span><br><span class="line"><span class="keyword">file</span>(GLOB SOURCES <span class="string">&quot;src/*.*&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># module</span></span><br><span class="line">pybind11_add_module(</span><br><span class="line">        library</span><br><span class="line">        <span class="variable">$&#123;SOURCES&#125;</span></span><br><span class="line">)</span><br><span class="line"><span class="comment">#set_target_properties(library PROPERTIES MSVC_RUNTIME_LIBRARY &quot;MultiThreaded$&lt;$&lt;CONFIG:Debug&gt;:Debug&gt;&quot;)</span></span><br><span class="line"><span class="keyword">target_include_directories</span>(</span><br><span class="line">        library</span><br><span class="line">        PRIVATE <span class="string">&quot;src&quot;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p><code>src</code> 是我的 C++ 源码目录，其中 <code>pybind11_DIR</code> 通过指令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -m pybind11 --cmakedir</span><br></pre></td></tr></table></figure><p>获取，<code>E:/Anaconda3/envs/paddle/Lib/site-packages/pybind11/include</code> 这个路径是通过指令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -m pybind11 --includes</span><br></pre></td></tr></table></figure><p>获得（只要后半部分去掉 <code>-I</code> 就行，前半部分在 <code>include_directories($&#123;PYTHON_INCLUDE_PATH&#125;)</code> 已经导入）</p><p>有一个关键点是不要使用 <code>find_package(Python3 COMPONENTS Interpreter Development)</code>，这样得到的是系统解释器，如果你使用虚拟环境，则会识别错误，应当在 CLion 中选用传递 Python 解释器。</p><p><img src="/assets/6be18bad2e07/image-20250214162604194.png" alt="image-20250214162604194" /></p><h5 id="工具链"><a class="markdownIt-Anchor" href="#工具链"></a> 工具链</h5><p><img src="/assets/6be18bad2e07/image-20250214162649520.png" alt="image-20250214162649520" /></p><h5 id="构建配置"><a class="markdownIt-Anchor" href="#构建配置"></a> 构建配置</h5><p><img src="/assets/6be18bad2e07/image-20250214162708787.png" alt="image-20250214162708787" /></p><p>构建好之后把 <code>.pyd</code> 文件改名成包名直接放到工作目录下就好。</p><h3 id="代码"><a class="markdownIt-Anchor" href="#代码"></a> 代码</h3><p><a href="https://github.com/syhanjin/Gomuku_Cpp_muti">https://github.com/syhanjin/Gomuku_Cpp_muti</a></p><p>未完待续（主要是我模型还没跑出来，这里只有原理</p><h2 id="reference"><a class="markdownIt-Anchor" href="#reference"></a> Reference</h2><hr class="footnotes-sep" /><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p><a href="https://www.paddlepaddle.org.cn/documentation/docs/zh/practices/reinforcement_learning/AlphaZero.html">https://www.paddlepaddle.org.cn/documentation/docs/zh/practices/reinforcement_learning/AlphaZero.html</a> <a href="#fnref1" class="footnote-backref">↩︎</a> <a href="#fnref1:1" class="footnote-backref">↩︎</a> <a href="#fnref1:2" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p><a href="https://blog.csdn.net/qq_41033011/article/details/109034887">https://blog.csdn.net/qq_41033011/article/details/109034887</a> <a href="#fnref2" class="footnote-backref">↩︎</a></p></li><li id="fn3" class="footnote-item"><p><a href="https://github.com/junxiaosong/AlphaZero_Gomoku">https://github.com/junxiaosong/AlphaZero_Gomoku</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p></li><li id="fn4" class="footnote-item"><p><a href="https://github.com/hijkzzz/alpha-zero-gomoku">https://github.com/hijkzzz/alpha-zero-gomoku</a> <a href="#fnref4" class="footnote-backref">↩︎</a></p></li><li id="fn5" class="footnote-item"><p><a href="https://learn.microsoft.com/zh-cn/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2022">https://learn.microsoft.com/zh-cn/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2022</a> <a href="#fnref5" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;突然想整一下 RL 于是就看上了 AlphaZero，围棋我也不会，训练也难，那就上一个五子棋版本&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，这里展示的基本上是我跟着这些资料学习的流程，想要最终最完善的版本可以直接点目录跳转，有些代码已经优化改进，但是我还是把初稿放上来</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>电路学习笔记01</title>
    <link href="https://syhanjin.moe/20250109/d86f50fc6ff4/"/>
    <id>https://syhanjin.moe/20250109/d86f50fc6ff4/</id>
    <published>2025-01-09T15:28:24.000Z</published>
    <updated>2025-03-19T13:06:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概念梳理汇总"><a class="markdownIt-Anchor" href="#概念梳理汇总"></a> 概念梳理汇总</h2><ul><li><p><strong>集中参数电路</strong>   只含集中参数元件的电路（简单来说无需考虑电磁量空间分布）</p></li><li><p><strong>支路</strong>   二端元件（两个节点之间）</p></li><li><p><strong>节点</strong>   若干支路的连接点（3+）</p></li><li><p><strong>网孔（内网孔）</strong>   内部不存在任何支路</p></li><li><p><strong>短路</strong>   无论电流为任何有限值电压始终为零的支路</p></li><li><p><strong>开路/断路</strong>   无论电压为任何有限值电流始终为零的支路</p></li><li><p><strong>直流电路</strong>   由直流电压源或直流电流源激励，且各部分电压和电流都是恒定量的电路</p></li><li><p><strong>线性直流电路</strong>   由线性元件和独立源组成的直流电路</p><blockquote><p>线性受控电源应当算作线性元件</p></blockquote></li></ul><h2 id="基尔霍夫定律"><a class="markdownIt-Anchor" href="#基尔霍夫定律"></a> 基尔霍夫定律</h2><p>又称电路结构约束</p><p>作用范围：<strong>集中参数电路</strong></p><h3 id="基尔霍夫电流定律kirchhoffs-current-law-kcl"><a class="markdownIt-Anchor" href="#基尔霍夫电流定律kirchhoffs-current-law-kcl"></a> 基尔霍夫电流定律（Kirchhoff’s Current Law, KCL)</h3><p>定律形式和推广形式</p><ul><li>在集中参数电路中，任一时刻流出（或流入）任一节点的支路电路代数和等于零</li><li>在集中参数电路中任一时刻流出（或流入）任意闭合边界的支路电流代数和为零</li><li>在集中参数电路中，任一时刻流出任一节点（或闭合边界）的电流的代数和等于流入该节点（或闭合边界）的电流的代数和</li></ul><p>$$\sum i_{流出}&#x3D;\sum i_{流入}$$</p><blockquote><p>在有 $n$ 个节点的电路中，任意 $n-1$ 个节点的 KCL 方程是一组独立方程</p></blockquote><h3 id="基尔霍夫电压定律kirchhoffs-voltage-law-kvl"><a class="markdownIt-Anchor" href="#基尔霍夫电压定律kirchhoffs-voltage-law-kvl"></a> 基尔霍夫电压定律（Kirchhoff’s Voltage Law, KVL）</h3><p>定律形式和推广形式</p><ul><li>在集中参数电路中，任一时刻沿任一回路各支路电压的代数和等于零</li><li>在集中参数电路中，沿任一回路，各支路电压降的代数和等于电压升的的代数和</li><li>在集中参数电路中，任意两点之间的电压具有确定值，与计算路径无关</li></ul><p>$$\sum u_{电压降}&#x3D;\sum u_{电压升}$$</p><p>任一回路的 KVL 方程是组成该回路的各个网孔上 KVL 方程的代数和</p><p>平面电路网孔上的 KVL 方程是一组独立方程</p><blockquote><p>设电路有 $b$ 个支路 $n$ 个节点，可以证明：平面电路的网孔数即独立 KVL 方程的个数为 $b-n+1$</p></blockquote><h2 id="电路分析"><a class="markdownIt-Anchor" href="#电路分析"></a> 电路分析</h2><blockquote><p>这些电路分析方法都是为了确定电路中的参数</p></blockquote><p>给定的线性直流电路具有 $b$ 条支路，$n$ 个节点</p><h3 id="支路电流法branch-current-analysis"><a class="markdownIt-Anchor" href="#支路电流法branch-current-analysis"></a> 支路电流法（branch current analysis）</h3><p>以 $b$ 条未知的支路电流作为待求量，对 $n-1$ 个节点列出独立的 KCL 方程，再对 $b-n+1$ 个回路列出独立的 KVL 方程，这 $b$ 个方程联立即可解得 $b$ 个支路电流</p><blockquote><p>要列出 $b-n+1$ 个独立 KVL 方程，可以每选取一个回路时包括一条新的支路。（注意这是选取的充分而非必要条件）</p></blockquote><p>例题预留位</p><h3 id="回路电流法"><a class="markdownIt-Anchor" href="#回路电流法"></a> 回路电流法</h3><p>选择 $b-n+1$ 个独立回路，以各回路电流为待求量列写 KVL 方程</p><p>一般规则：</p><p>$$\begin{bmatrix}R_{11}&amp; R_{12}&amp; \cdots&amp; R_{1m}\\R_{21}&amp; R_{22}&amp; \cdots&amp; R_{2m}\\\vdots&amp; \vdots&amp;       &amp; \vdots\\R_{1m}&amp; R_{2m}&amp; \cdots&amp; R_{mm}\end{bmatrix}\begin{bmatrix}I_{m1}\\I_{m2}\\\vdots\\I_{mm}\\\end{bmatrix}&#x3D;\begin{bmatrix}\displaystyle \sum_{回路1}U_s\\\displaystyle \sum_{回路2}U_s\\\vdots\\\displaystyle \sum_{回路m}U_s\\\end{bmatrix}$$</p><ol><li>组成回路 $i$ 的各支路上电阻之和 $R_{ii}$ 称为回路的<strong>自阻</strong></li><li>回路 $i$ 和回路 $j$ 之间公共支路上的电阻 $R_{ij}$，称为相邻两回路之间的<strong>互阻</strong>，在公共支路上同向取正、反向取负</li><li>$\displaystyle \sum_{回路i}U_S$ 为沿回路 $i$ 电压源电位升的代数和</li><li>其中 $(I_{m1},_{m2},\cdots,I_{mm})^T$ 称作<strong>回路电流向量</strong>，$\begin{pmatrix}\displaystyle \sum_{回路1}U_S,\displaystyle \sum_{回路2}U_S,\cdots,\displaystyle \sum_{回路m}U_S\end{pmatrix}^T$ 称作<strong>回路源电压向量</strong></li></ol><p>不含受控电源的情况下 $R_{ij}&#x3D;R_{ji}$，</p><blockquote><p>当电路中包括电流源时，需要先设出电流源两端电压，并补充一个电流源所在支路电流与回路电流的关系。可以<mark>适当选取回路使得电流源只包含在一个回路中</mark></p><p>当电路中包括受控电源时，先将受控电源当做独立源处理，再将关系代入</p></blockquote><p>例题预留位</p><h3 id="节点电压法"><a class="markdownIt-Anchor" href="#节点电压法"></a> 节点电压法</h3><blockquote><p>任选一节点作为参考点，其它各节点与参考点之间的电压称为该节点的<strong>节点电压</strong>或<strong>节点电位</strong></p><p>用节点电压表示支路电压时，相当于等价地列写了 KVL 方程（？）</p></blockquote><p>以 $n-1$ 个节点电压为待求量，对 $n-1$ 个节点列写 KCL 方程的方法就是<strong>节点电压法</strong>或<strong>节点分析法</strong></p><p>一般规则：</p><p>$$\begin{bmatrix}G_{11}&amp; G_{12}&amp; \cdots&amp; G_{1(n-1)}\\G_{21}&amp; G_{22}&amp; \cdots&amp; G_{2(n-1)}\\\vdots&amp; \vdots&amp;       &amp; \vdots\\G_{1(n-1)}&amp; G_{2(n-1)}&amp; \cdots&amp; G_{(n-1)(n-1)}\end{bmatrix}\begin{bmatrix}U_{n1}\\U_{n2}\\\vdots\\U_{nn}\\\end{bmatrix}&#x3D;\begin{bmatrix}\displaystyle \sum_{节点1}I_S+\displaystyle \sum_{节点1}G_kU_{Sk}\\\displaystyle \sum_{节点2}I_S+\displaystyle \sum_{节点2}G_kU_{Sk}\\\vdots\\\displaystyle \sum_{节点(n-1)}I_S+\displaystyle \sum_{节点(n-1)}G_kU_{Sk}\\\end{bmatrix}$$</p><ol><li>与节点 $i$ 相连的各支路电导之和 $G_{ii}$ 称为该节点的<strong>自导</strong></li><li>直接连接在节点 $i$ 和节点 $j$ 之间各支路电导之和取相反数 $G_{ij}$ 称为节点 $i,j$ 之间的<strong>互导</strong></li><li>$\displaystyle \sum_{节点i}I_S$ 为与节点 $i$ 相连的电流源电流代数和（流入取正，流出取负）；$\displaystyle \sum_{节点i}G_kU_{Sk}$ 是与节点 $i$ 相连的电压源与串联电导乘积的代数和（电压源正极性指向节点时取正，否则取负）</li><li>其中 $\begin{pmatrix}U_{n1},U_{n2},\cdots,U_{nn}\end{pmatrix}^T$ 称为<strong>节点电压向量</strong>，等式右边称为<strong>节点源电流向量</strong></li></ol><blockquote><p>改进节点电压法</p><p>（我还没研究，之后再填）</p></blockquote><p>例题预留位</p><h2 id="电路等效"><a class="markdownIt-Anchor" href="#电路等效"></a> 电路等效</h2><blockquote><p>注意：<mark>等效只针对外部电路而言，不包括内部电路</mark>，意味着变换前后内部电路的参数（电压、电流、功率等）会发生变化</p></blockquote><h3 id="电阻网络无源二端网络"><a class="markdownIt-Anchor" href="#电阻网络无源二端网络"></a> 电阻网络（无源二端网络）</h3><blockquote><p>这里就是指里面连了一大坨电阻，本部分会包括一些我自己总结的例子</p></blockquote><ul><li>以下两种形式等效（并联 + 串联）</li></ul><p><img src="/assets/d86f50fc6ff4/image-20250109205801519.png" alt="image-20250109205801519" /></p><p><img src="/assets/d86f50fc6ff4/image-20250109205907473.png" alt="image-20250109205907473" /></p><ul><li>以下电路，当 $\displaystyle\frac{R_1}{R_2}&#x3D;\frac{R_3}{R_4}$ 时，中间的电桥 $R$ 可以直接改为断路</li></ul><p><img src="/assets/d86f50fc6ff4/image-20250109210911971.png" alt="image-20250109210911971" /></p><ul><li>$Y$ 形联结（左）和 $\Delta$ 形联结（右）</li></ul><p><img src="/assets/d86f50fc6ff4/image-20250109213836326.png" alt="image-20250109213836326" /></p><p>两种联结之间的等效关系式为</p><p>$$\begin{aligned}&amp;\left\{\begin{aligned}R_1&#x3D;\frac{R_{12}R_{31}}{R_{12}+R_{23}+R_{31}}\\R_2&#x3D;\frac{R_{12}R_{23}}{R_{12}+R_{23}+R_{31}}\\R_3&#x3D;\frac{R_{23}R_{31}}{R_{12}+R_{23}+R_{31}}\end{aligned}\right.&amp;R_{1,2,3}&#x3D;\frac{\Delta 形联结中与该节点相连的两电阻之积}{\Delta 形联结中各电阻之和}\\\\&amp;\left\{\begin{aligned}R_{12}&#x3D;\frac{R_{1}R_{2}+R_{2}R_{3}+R_{3}R_{1}}{R_3}\\R_{23}&#x3D;\frac{R_{1}R_{2}+R_{2}R_{3}+R_{3}R_{1}}{R_1}\\R_{31}&#x3D;\frac{R_{1}R_{2}+R_{2}R_{3}+R_{3}R_{1}}{R_2}\end{aligned}\right.&amp;R_{12,23,31}&#x3D;\frac{Y 形联结中各电阻两两乘积之和}{Y 形联结中不相邻电阻}\end{aligned}$$</p><p>一般用对称情况 $R_1&#x3D;R_2&#x3D;R_3&#x3D;R_Y$，$R_{12}&#x3D;R_{23}&#x3D;R_{31}&#x3D;R_{\Delta}$，其中 $R_{\Delta}&#x3D;3R_Y$</p><h3 id="含源支路的等效变换"><a class="markdownIt-Anchor" href="#含源支路的等效变换"></a> 含源支路的等效变换</h3><p><img src="/assets/d86f50fc6ff4/image-20250109222353530.png" alt="image-20250109222353530" /></p><p>变换条件</p><p>$$U_S&#x3D;\frac{I_S}{G_i},R_i&#x3D;\frac{1}{G_i}$$</p><p>其中 $R_i\neq0,G_i\neq0$（否则为理想电压 / 电流源）</p><h3 id="等效电源戴维南定理-诺顿定理"><a class="markdownIt-Anchor" href="#等效电源戴维南定理-诺顿定理"></a> 等效电源（戴维南定理 &amp; 诺顿定理）</h3><p><strong>戴维南定理</strong>   线性含源端口网络对外作用可以用一个电压源串联一个电阻的电路来等效替代，其中</p><ol><li>电压源的源电压等于此端口网络的<strong>开路电压</strong>（去阻留源）</li><li>电阻等于此一端口网络内部各独立源置零后所得无源一端口网络的等效电阻（去源留阻）</li></ol><p><strong>诺顿定理</strong>   线性含源端口网络对外作用可以用一个电流源并联一个电导的电路来等效替代，其中</p><ol><li>电流源的源电流等于此端口网络的<strong>短路电压</strong></li><li>电导等于此一端口网络内部各独立源置零后所得无源一端口网络的等效电导</li></ol>]]></content>
    
    
    <summary type="html">有关电路理论基础的概念梳理、基尔霍夫定律、电路分析、电路等效等知识总结笔记</summary>
    
    
    
    <category term="三大天书学习笔记" scheme="https://syhanjin.moe/categories/%E4%B8%89%E5%A4%A7%E5%A4%A9%E4%B9%A6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="电路理论基础" scheme="https://syhanjin.moe/tags/%E7%94%B5%E8%B7%AF%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80/"/>
    
    <category term="学习笔记" scheme="https://syhanjin.moe/tags/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>2024年终总结</title>
    <link href="https://syhanjin.moe/20241231/49c8fcdd7193/"/>
    <id>https://syhanjin.moe/20241231/49c8fcdd7193/</id>
    <published>2024-12-31T09:14:10.000Z</published>
    <updated>2024-12-31T09:16:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="简单顺一顺今年的经历"><a class="markdownIt-Anchor" href="#简单顺一顺今年的经历"></a> 简单顺一顺今年的经历</h2><h3 id="高考及高考之前"><a class="markdownIt-Anchor" href="#高考及高考之前"></a> 高考及高考之前</h3><blockquote><p>我今年上半年与下半年的区别真有点大，不是一般的大，无论是从体重上还是状态性格上</p><p>说真的现在反而挺怀念高三的时候了，放寒假想回高三坐坐，跟下一届小登一起自习几天。</p><p>嘿嘿我可是真的会这样干的哦，小登们备战高考，我学高数下（或者三电）</p></blockquote><p>高考的确是人生中非常重要的一环，不过考完之后会发现，其实也就那样罢了。（真也就那样，，，）说到这个，我觉得我今年干过的最 NB 的事情就是<strong>在高考数学烤成 shi 之后剩下四科保持正常水准考完了</strong>，大喊 <code>NB</code></p><p>觉得比较可惜的事情就是，应该把高中每次考试的考后总结留下来的，到了大学莫名想回看，很珍贵的回忆</p><p>高考的分数是一点没浪费，踩线进（虽然我校保研率实在是奇葩，但是综合来看我还是比较喜欢的）</p><h3 id="高考后的暑假"><a class="markdownIt-Anchor" href="#高考后的暑假"></a> 高考后的暑假</h3><p>总的来说就干了两件事：考了个驾照、做了个网站</p><p>整个暑假大量的时间都花在这个网站上了，虽然最终的结果只能算半成品。</p><p>只是一个简单的电子同学录。</p><p>网站地址 <a href="https://blossom.hn.cn/">https://blossom.hn.cn/</a> 想看效果的可以找我要一下测试账号<s>别对我的网站做什么奇奇怪怪的事情，找到安全漏洞了可以 @我</s>。</p><h3 id="大学生活"><a class="markdownIt-Anchor" href="#大学生活"></a> 大学生活</h3><h4 id="军训"><a class="markdownIt-Anchor" href="#军训"></a> 军训</h4><p>我校的军训确实挺人性化的<s>但是建议之后不要出一身汗去吹空调听理论课</s>，虽然军训期间穿插高数先修和英语分级考试非常难绷，以及高数先修考试开幕雷击。还有上午上线代下午继续军训的高级操作</p><h4 id="学期概述"><a class="markdownIt-Anchor" href="#学期概述"></a> 学期概述</h4><p><code>南工问天</code> 预备队员、<a href="https://osa.moe/"><code>OSA</code></a> 社员、<a href="https://academic.blossom.hn.cn/"><code>24学术讨论群</code></a>成员，大学生活还算丰富。虽然实际出勤率比较感人<s>但是没考勤怎么叫没出勤</s>以及这个学期确实有点不务正业（就是感觉堕落了很多啊喂，不然为啥想回高三找找感觉），但是除习概考试外的其他科目考的还行<s>好吧可能略大于还行</s></p><h2 id="详细讲讲大学生活"><a class="markdownIt-Anchor" href="#详细讲讲大学生活"></a> 详细讲讲大学生活</h2><blockquote><p>关于工科生在工科大学基本找不到女朋友这件事 —— 单四年罢了</p></blockquote><p>做了不少项目，这些项目就是我大一上的主要内容了，哎哎主打一个不务正业</p><h3 id="大一立项"><a class="markdownIt-Anchor" href="#大一立项"></a> 大一立项</h3><p>《面向野外动物预警的六足仿生机器人系统》= 一个六足底座 + 二轴防抖云台 + 摄像头 + 视觉识别</p><p>说白了，六足好做…而且我在军训的时候就把六足壳子搓出来了</p><p>具体内容且听下回分解</p><h3 id="队内赛"><a class="markdownIt-Anchor" href="#队内赛"></a> 队内赛</h3><blockquote><p>因为车没出来弃赛了，5 组里唯一一个弃赛的，机械全责（我电控）</p></blockquote><p>这队内赛我一个人调了 3 块板子，两套 PID 参数（关于 PID 参见 <a href="https://syhanjin.moe/20241201/ce368667e188/">基于 PID 算法控制直流电机</a>），搓了整个上位机控制代码（而且不知道发什么神经在 <code>Linux</code> 系统下用了 <code>Ros2</code> 还用的是键盘操控），称为劳模</p><p>出了个底盘，底盘工作较为流畅，手感还行</p><h3 id="小玩具"><a class="markdownIt-Anchor" href="#小玩具"></a> 小玩具</h3><p>灵感来源：<a href="https://www.bilibili.com/video/BV1xD6MYGEzD/">【开源】STM32智能桌面宠物总教程</a></p><p>计划进化形态：<a href="https://b23.tv/oLNEv3i">我也创造出了生命!!!用stm32制作的桌面宠物，电子小狗，赛博狗子，机械小狗-哔哩哔哩</a> 但皮卡丘</p><p>另外三位合伙人：离谱、浮萍、花雨嫣然</p><p>目前状态：在等嘉立创 SMT（28 号晚上焊了一晚上板子眼睛都要瞎了结果有一大堆问题，想学好硬件仍然任重而道远）<s>所以嘉立创你是怎么知道我焊板子要疯了还给我体验免费 SMT</s>~</p><p>目前进度：</p><ul><li>蓝牙、OLED、舵机控制驱动完成</li><li>外壳设计完成，样稿已经打印完毕</li><li>OLED 动画在做（没准会参照一下邦布的眼睛？）</li></ul><p>哈哈哈这算是我学习 <code>FreeRTOS</code> 的一个练手项目</p><h3 id="整理一下我搓过的驱动代码"><a class="markdownIt-Anchor" href="#整理一下我搓过的驱动代码"></a> 整理一下我搓过的驱动代码</h3><ol><li><p>基于 <code>TB6612</code> 控制芯片的直流电机驱动 <code>motor.h/motor.c</code></p></li><li><p>适应直流电机的 <code>PID</code> 控制驱动 <code>pid.h/pid.c</code></p></li><li><p>通过 <code>PCA9685</code> 控制舵机的舵机驱动板驱动 <code>pca9685.h/pca9685.c</code></p></li><li><p>直接驱动 <code>MG92B</code> 舵机的舵机驱动 <code>mg92b.h/mg92b.c</code></p></li><li><p>因为 <code>MPU6050</code> 质量不好导致调了一个下午的 <code>MPU6050</code> 陀螺仪驱动 <code>mpu6050.h/mpu6050.c</code></p></li><li><p>裸机 <code>MX-02</code> 系列蓝牙模块驱动 <code>mx02s.h/mx02s.c</code></p></li><li><p><code>JY901S</code> 陀螺仪驱动 <code>jy901s.h/jy901s.c</code></p></li><li><p>四轮全向轮底盘驱动（基于 <code>motor.h/pid.h</code>）<code>omni4.h/omni4.c</code></p></li><li><p>基于 <code>FreeRTOS</code> 的 <code>MX-02</code> 系列蓝牙模块驱动移植版本 <code>ble.h/ble.c</code></p></li><li><p>基于 <code>FreeRTOS</code> 的 <code>OLED</code> 驱动（支持 <code>SSD1306</code> 和 <code>SH1106</code> 双驱动芯片，<code>SPI</code> 协议通信）<code>oled.h/oled.c</code></p><p>该驱动支持基本图形库：直线，圆，矩形，字模（不带文字库），显示图片，播放视频</p></li><li><p>基于 <code>FreeRTOS</code> 的舵机驱动 <code>servo.h/servo.c</code></p></li></ol><p>我草原来我搓过这么多库，什么手搓习惯真是…</p><h3 id="别的什么"><a class="markdownIt-Anchor" href="#别的什么"></a> 别的什么</h3><p>总感觉跟别的同学比我像是上了个假大学，别人的课那么满，我为何如此清闲…<s>大专是这样的</s></p><p>这学期高数算是吃老本了，都是高中提前看的，但是下个学期多元微分学就得好好学学了，毕竟是高中没成功突破的部分</p><p>数电模电电路开了个小头，本来想学一部分的因为不想干活也没开始多少，希望明年不要被三电搞得晕头转向吧</p><p>机器人学导论也算开头，机械臂解算这种东西还是要好好学</p><p>我记性不是很好，经常记不住发生过什么（或者我内心里也没有想记住这些事吧，时间会冲淡一切），所以对过去的事情反而没什么感想，<s>也如我说，我本质上是一个冷漠的人</s>，可能更多的精力都放在各种好玩的事情上了？</p><p>国庆那会本来是找了一位朋友提醒我背单词的但是因为我自己的原因中断了，这么想来这是我大一上学期堕落的开始了…从那会儿到现在都有点缺少动力。</p><p>等某人的恋爱小故事ing… <s>说起来我校居然有传说级我还认识（工科生哇居然能从初一谈到现在，神了！）</s></p><p>嘿嘿嘿高数最后一题别扣我分啊（虽然我确实忘记说有界了但是别卡我嘛）平时分我都交了应该不会扣吧（也想有高数 A 满分历史啊）</p><p>习概分数炸了现在 <code>CGPA</code> 都到 80% 往后了… 高数线代快出分，把我拉回来一点点</p><p>星见雅三体人！星见雅帅的！</p><h2 id="新年展望"><a class="markdownIt-Anchor" href="#新年展望"></a> 新年展望</h2><ul><li>[ ] 看看能不能混一个保研资格</li><li>[ ] 大一立项拿个一等奖</li><li>[ ] 入门三电</li><li>[ ] 熟悉 <code>FreeRTOS</code> <code>ROS</code></li><li>[ ] 瞅瞅能不能参与 25 年 RC 正赛 + 好好打 26 年 RC 正赛</li><li>[ ] 小狗桌宠完工 + 量产版完工</li><li>[ ] 下学期少混一点，多学点东西</li><li>[ ] <s>结束单身</s>开玩笑的活着就行</li><li>[ ] 见到某人结婚（有难度）</li></ul><p>再见咯 <code>2024</code>，你好 <code>2025</code></p>]]></content>
    
    
    <summary type="html">对于 2024 年的总结和感想，以及对 2025 年的一些想法</summary>
    
    
    
    <category term="年终总结" scheme="https://syhanjin.moe/categories/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
    
    <category term="2024" scheme="https://syhanjin.moe/tags/2024/"/>
    
    <category term="年终总结" scheme="https://syhanjin.moe/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>线性代数知识点总结2</title>
    <link href="https://syhanjin.moe/20241214/4941f8fb48b8/"/>
    <id>https://syhanjin.moe/20241214/4941f8fb48b8/</id>
    <published>2024-12-14T14:55:55.000Z</published>
    <updated>2024-12-27T10:54:03.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>因为线性代数作业写到第六章时发现很多性质都不清楚，故总结一下。</p></blockquote><h2 id="特征值与特征向量"><a class="markdownIt-Anchor" href="#特征值与特征向量"></a> 特征值与特征向量</h2><h3 id="定义"><a class="markdownIt-Anchor" href="#定义"></a> 定义</h3><p><strong>特征方程</strong> $|\lambda\boldsymbol E-\boldsymbol A|&#x3D;0$<br /><mark>最高次项系数为$1$</mark></p><p>$\boldsymbol A$ 的属于特征值 $\lambda_i$ 的特征向量就是齐次线性方程组</p><p>$$\begin{equation}(\lambda_i \boldsymbol E_n-\boldsymbol A)\boldsymbol X&#x3D;\boldsymbol 0\label{1}\end{equation}$$</p><p>的非零解（<mark>故可以通过同解的其他方程简化求解过程</mark>）</p><p><strong>特征子空间</strong> $\eqref{1}$ 的解空间 $N(\lambda_1\boldsymbol E_n-\boldsymbol A)$ 为 $\boldsymbol A$ 关于特征值 $\lambda_i$ 的特征子空间</p><h3 id="性质"><a class="markdownIt-Anchor" href="#性质"></a> 性质</h3><ol><li><p>$\displaystyle\sum_{i&#x3D;1}^n\lambda_i&#x3D;\tr(\boldsymbol A)$</p></li><li><p>$\displaystyle\prod_{i&#x3D;1}^n\lambda_i &#x3D; |\boldsymbol A|$</p></li><li><p>$f(\lambda)$ 是 $f(\boldsymbol A)$ 的特征值</p></li><li><p>$\boldsymbol A$ 的不同特征值对应的线性无关的特征向量的集合构成的向量组依旧线性无关</p><p>$\Rightarrow$ 不同特征值对应的特征向量线性无关</p></li></ol><h2 id="相似矩阵"><a class="markdownIt-Anchor" href="#相似矩阵"></a> 相似矩阵</h2><p><strong>定义</strong> $\boldsymbol B&#x3D;\boldsymbol T^{-1}\boldsymbol {AT}$，$\boldsymbol T$ 可逆，称 $\boldsymbol A$ 与 $\boldsymbol B$ <strong>相似</strong>，记作 $\boldsymbol A\thicksim \boldsymbol B$；称 $\boldsymbol T$ 为<strong>相似变换矩阵</strong></p><p>​相似变换是一种等价关系：满足自反性、对称性、传递性</p><p>若 $\boldsymbol A\thicksim \boldsymbol B$，则 $\boldsymbol A$ 与 $\boldsymbol B$ 的特征多项式相同</p><p>$\Rightarrow$ 若 $\boldsymbol A\thicksim \boldsymbol B$，则 $\boldsymbol A$ 与 $\boldsymbol B$ 的特征值相同（<mark>反之未必成立</mark>）</p><p>$\Rightarrow$  若 $\boldsymbol A\thicksim \boldsymbol B$，则 $\tr(\boldsymbol A)&#x3D;\tr(\boldsymbol B)$，$|\boldsymbol A|&#x3D;|\boldsymbol B|$</p><p>若 $\boldsymbol A \thicksim \mathrm{diag}\{\lambda_1, \lambda_2, \cdots,\lambda_n\}$，则 $\lambda_1,\lambda_2,\cdots,\lambda_n$ 是 $\boldsymbol A$ 的 $n$ 个特征值</p><p><strong>相似变换中的不变量</strong> 秩、迹、行列式</p><h3 id="相似对角化"><a class="markdownIt-Anchor" href="#相似对角化"></a> 相似对角化</h3><p>$\boldsymbol A \thicksim \mathrm{diag}\{\lambda_1, \lambda_2, \cdots,\lambda_n\}\Leftrightarrow\boldsymbol A$ 有 $n$ 个线性无关的特征向量，</p><p>$\boldsymbol T^{-1}\boldsymbol {AT}&#x3D;\boldsymbol D&#x3D;\mathrm{diag}\{\lambda_1, \lambda_2, \cdots,\lambda_n\}\Leftrightarrow \boldsymbol T$ 的列向量组 $\boldsymbol t_1,\boldsymbol t——2,\cdots,\boldsymbol t_n$ 是 $\boldsymbol A$ 的 $n$ 个线性无关的特征向量，且对应的特征值依次为 $\lambda_1,\lambda_2,\cdots,\lambda_n$</p><p>如果 $n$ 个特征值互不相等，则 $\boldsymbol A$ 与对角矩阵相似</p><h3 id="几何重数与代数重数"><a class="markdownIt-Anchor" href="#几何重数与代数重数"></a> 几何重数与代数重数</h3><p><strong>定义</strong> $\lambda_0$ 为特征方程的 $m$ 重根，则称 $m$ 为 $\lambda_0$ 的<strong>代数重数</strong>；$\dim(\lambda_0\boldsymbol E-\boldsymbol A)$ 为<strong>几何重数</strong></p><p>矩阵的特征值的几何重数小于等于它的代数重数</p><p><strong>定理</strong> 复矩阵 $\boldsymbol A$ 可相似对角化的充要条件是 $\boldsymbol A$ 的每一特征值的代数重数与几何重数相等</p><h2 id="二次型"><a class="markdownIt-Anchor" href="#二次型"></a> 二次型</h2><p>$$\begin{split}f(x_1, x_2, \cdots, x_n)&#x3D;a_{11}x_1^2 &amp;+2a_{12}x_1x_2 &amp;+2a_{13}x_1x_3 &amp;+\cdots &amp;+2a_{1n}x_1x_n\\            &amp;+a_{22}x_2^2   &amp;+2a_{23}x_2x_3 &amp;+\cdots &amp;+2a_{2n}x_2x_n\\            &amp;+\cdots        &amp;               &amp;        &amp;              \\            &amp;               &amp;               &amp;        &amp;+a_{nn}x_n^2\end{split}$$</p><p>写成矩阵形式</p><p>$$f(\boldsymbol X)&#x3D;\boldsymbol X&#39;\boldsymbol{AX}$$</p><blockquote><p>注意：定义二次型的矩阵时规定了 <em>$\boldsymbol A$ 为对称矩阵</em>，但实际上要表示这个二次型，$\boldsymbol A$ 不一定要是对称矩阵，这种取法只是一个便于研究的特殊情况</p></blockquote><p><mark>二次型可以由正交替换变为标准型，但是不一定能变为规范型</mark></p><h3 id="矩阵的合同"><a class="markdownIt-Anchor" href="#矩阵的合同"></a> 矩阵的合同</h3><p>设一个可逆矩阵 $\boldsymbol C$，进行变换 $\boldsymbol X&#x3D;\boldsymbol {CY}$（称为<strong>非退化线性变换</strong>或者<strong>可逆线性变换</strong>，实际上是坐标变换），则 $\boldsymbol Y&#x3D;\boldsymbol C^{-1}\boldsymbol X$，则二次型变换为</p><p>$$f(\boldsymbol X)&#x3D;(\boldsymbol {CY})&#39;\boldsymbol{A}(\boldsymbol{CY})&#x3D;\boldsymbol Y&#39;(\boldsymbol C&#39;\boldsymbol {AC})\boldsymbol Y$$</p><p>令 $\boldsymbol B&#x3D;\boldsymbol C&#39;\boldsymbol{AC}$，显然 $\boldsymbol B$ 为实对称矩阵</p><p>称矩阵 $\boldsymbol A,\boldsymbol B$ 合同，记作 $\boldsymbol A\simeq\boldsymbol B$</p><h2 id="实对称矩阵"><a class="markdownIt-Anchor" href="#实对称矩阵"></a> 实对称矩阵</h2><h3 id="性质-2"><a class="markdownIt-Anchor" href="#性质-2"></a> 性质</h3><ol><li>特征值都是实数</li><li>对应于不同特征值的实特征向量必正交（<mark>在特征子空间选取正交基则可得到正交特征向量组</mark>）</li><li>特征值的几何重数与代数重数相等</li><li>互不相同的特征值的几何重数之和为 $n$</li><li><strong>惯性定律</strong> 同一个实二次型的标准型中的正系数、负系数及零系数的个数不因实可逆线性变换的不同而改变</li><li>正惯性系数和负惯性系数相同的实对称矩阵合同</li></ol><h3 id="实对称矩阵的相似对角化"><a class="markdownIt-Anchor" href="#实对称矩阵的相似对角化"></a> 实对称矩阵的相似对角化</h3><p>设 $\boldsymbol A$ 为 $n$ 阶实对称矩阵，则存在 $n$ 阶<strong>正交矩阵</strong> $\boldsymbol P$，使得</p><p>$$\boldsymbol P^{-1}\boldsymbol {AP}&#x3D;\mathrm{diag}\{\lambda_1, \lambda_2, \cdots,\lambda_n\}$$</p><p>其中 $\lambda_1, \lambda_2, \cdots,\lambda_n$ 是 $\boldsymbol A$ 的 $n$ 个特征值</p><blockquote><p>注意证明过程：</p><ul><li>将 $\boldsymbol A$ 的属于 $\lambda_i$ 的 $r_i$ 个线性无关的特征向量<strong>规范正交化</strong></li></ul></blockquote><p>已知 $\lambda_i$ 的线性无关的特征向量，可以利用<strong>正交</strong> + <strong>同解</strong> 求其他特征向量</p><p>由于正交矩阵 $\boldsymbol P^{-1}&#x3D;\boldsymbol P&#39;$，故正交矩阵与对角矩阵合同</p><h3 id="矩阵的正定与负定"><a class="markdownIt-Anchor" href="#矩阵的正定与负定"></a> 矩阵的正定与负定</h3><p><mark>正定（负定）矩阵一定是实对称矩阵</mark></p><p><strong>定义</strong> 设有 $n$ 元二次型 $f&#x3D;\boldsymbol X&#39;\boldsymbol {AX}$ 如果对 $\boldsymbol R^n$ 中任何列向量 $\boldsymbol X\neq \boldsymbol 0$ 都有 $f&gt;0$ 则称 $f$ 为正定二次型，$\boldsymbol A$ 为正定矩阵</p><p>实对称矩阵 $\boldsymbol A$ 正定，$f$ 为正定二次型</p><p>$\Leftrightarrow$ $f$ 标准型中的 $n$ 个系数都为正数</p><p>$\Leftrightarrow$ $\boldsymbol A$ 的特征值都为正数</p><p>$\Leftrightarrow$ 存在实可逆矩阵 $\boldsymbol Q$ 使得 $\boldsymbol A&#x3D;\boldsymbol Q&#39;\boldsymbol Q$</p><p>$\Leftrightarrow$ $\boldsymbol A$ 的各阶顺序主子式都大于零</p>]]></content>
    
    
    <summary type="html">一些特征值与特征向量和实对称矩阵的性质</summary>
    
    
    
    <category term="线性代数" scheme="https://syhanjin.moe/categories/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/"/>
    
    
    <category term="线性代数" scheme="https://syhanjin.moe/tags/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/"/>
    
    <category term="特征值" scheme="https://syhanjin.moe/tags/%E7%89%B9%E5%BE%81%E5%80%BC/"/>
    
  </entry>
  
  <entry>
    <title>基于 PID 算法控制直流电机</title>
    <link href="https://syhanjin.moe/20241201/ce368667e188/"/>
    <id>https://syhanjin.moe/20241201/ce368667e188/</id>
    <published>2024-12-01T19:23:28.000Z</published>
    <updated>2024-12-02T07:30:59.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文基于 <code>STM32F103CBT6</code> 和 <code>TB6612</code> 驱动芯片驱动趣轮科技 <code>MG513P30_12V</code> 直流电机，并使用  PID 算法进行闭环控制</p></blockquote><h2 id="直流电机驱动"><a class="markdownIt-Anchor" href="#直流电机驱动"></a> 直流电机驱动</h2><h3 id="驱动芯片"><a class="markdownIt-Anchor" href="#驱动芯片"></a> 驱动芯片</h3><p>具体芯片使用请看 <a href="https://blog.csdn.net/cyj972628089/article/details/112851786">这篇</a></p><h3 id="驱动代码"><a class="markdownIt-Anchor" href="#驱动代码"></a> 驱动代码</h3><p>驱动代码要讲的部分不多，注释里已经基本全了，<code>Motor_SetSpeed</code> 之所以输入范围是 $[-1, 1]$ 是对齐 PID 控制的输出</p><p>默认将 <code>STBY</code> 引脚直接接入 <code>3.3v</code> 所以不需要使能 / 失能函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file motor.h</span></span><br><span class="line"><span class="comment"> * @brief 直流电机控制驱动</span></span><br><span class="line"><span class="comment"> * @author hanjin</span></span><br><span class="line"><span class="comment"> * @date 2024-10-14</span></span><br><span class="line"><span class="comment"> * @version v1.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> MOTOR_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MOTOR_H</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;main.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;tim.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_COMPARE     (1000U - 1U)    <span class="comment">///&lt; 其实就是自动重载值</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ROTO_RADIO      (4 * 13)        <span class="comment">///&lt; 倍频器*线数</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> REDUCTION_RADIO (30)            <span class="comment">///&lt; 减速比 30:1</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_SPEED       (366)           <span class="comment">///&lt; 最大转速(rpm)</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SAMPLING_PERIOD (1e-2)          <span class="comment">///&lt; 采样间隔(s)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @def __SPEED2COMPARE</span></span><br><span class="line"><span class="comment"> * @brief 将速度转化为CCR</span></span><br><span class="line"><span class="comment"> * @attention 结果四舍五入</span></span><br><span class="line"><span class="comment"> * @param SPEED 速度 [0, 1]</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __SPEED2CCR(SPEED) (uint16_t)((SPEED) * (MAX_COMPARE) + 0.5)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    TIM_HandleTypeDef* <span class="type">const</span> htim;</span><br><span class="line">    <span class="type">const</span> <span class="type">uint32_t</span> channel;</span><br><span class="line">&#125; TIM_Channel_t;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    GPIO_TypeDef* <span class="type">const</span> GPIOx;</span><br><span class="line">    <span class="type">const</span> <span class="type">uint16_t</span> GPIO_Pin;</span><br><span class="line">&#125; GPIO_Out_t;</span><br><span class="line"></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> 直接用speed正负来表示方向 &lt; 但是也许从实用的角度不需要？</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="type">const</span> TIM_Channel_t pwm;</span><br><span class="line">    <span class="comment">// const TIM_Channel_t encoder1, encoder2;</span></span><br><span class="line">    TIM_HandleTypeDef* <span class="type">const</span> encoder;</span><br><span class="line">    <span class="type">const</span> GPIO_Out_t in1, in2;</span><br><span class="line">    <span class="type">uint8_t</span> direction; <span class="comment">///&lt; 方向：0 正向 1 反向</span></span><br><span class="line">    <span class="type">float</span> speed; <span class="comment">///&lt; 速度：0 &lt;= speed &lt;= 1</span></span><br><span class="line"></span><br><span class="line">    <span class="type">float</span> real_speed; <span class="comment">///&lt; 由编码器测得的速度</span></span><br><span class="line">    <span class="type">float</span> real_round; <span class="comment">///&lt; 由编码器测得的圈数</span></span><br><span class="line">&#125; Motor_t;</span><br><span class="line"></span><br><span class="line"><span class="comment">// void Motor_Enable();</span></span><br><span class="line"><span class="comment">// void Motor_Disable();</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_Start</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_Stop</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_SetSpeed</span><span class="params">(Motor_t* hmotor, <span class="type">float</span> speed)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_SetDirection</span><span class="params">(Motor_t* hmotor, <span class="type">uint8_t</span> direction)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_ToggleDirection</span><span class="params">(Motor_t* hmotor)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">Encoder_Start</span><span class="params">(Motor_t* hmotor)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Encoder_Stop</span><span class="params">(Motor_t* hmotor)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">Encoder_Progress</span><span class="params">(Motor_t* hmotor)</span>;</span><br><span class="line"><span class="type">float</span> <span class="title function_">Encoder_GetSpeed</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file motor.c</span></span><br><span class="line"><span class="comment"> * @brief 直流电机控制驱动</span></span><br><span class="line"><span class="comment"> * @author hanjin</span></span><br><span class="line"><span class="comment"> * @date 2024-10-14</span></span><br><span class="line"><span class="comment"> * @version v1.0</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;motor.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 应用方向变更，也可以用于启动</span></span><br><span class="line"><span class="comment"> * @note 为啥是inline呢，因为我只是不想写两遍罢了</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> * @retval None</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> _Perform_SetDirection(<span class="type">const</span> Motor_t* hmotor)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (hmotor-&gt;direction == <span class="number">0</span>) <span class="comment">// 正向旋转</span></span><br><span class="line">    &#123;</span><br><span class="line">        HAL_GPIO_WritePin(hmotor-&gt;in1.GPIOx, hmotor-&gt;in1.GPIO_Pin, GPIO_PIN_RESET);</span><br><span class="line">        HAL_GPIO_WritePin(hmotor-&gt;in2.GPIOx, hmotor-&gt;in2.GPIO_Pin, GPIO_PIN_SET);</span><br><span class="line">    &#125; <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        HAL_GPIO_WritePin(hmotor-&gt;in1.GPIOx, hmotor-&gt;in1.GPIO_Pin, GPIO_PIN_SET);</span><br><span class="line">        HAL_GPIO_WritePin(hmotor-&gt;in2.GPIOx, hmotor-&gt;in2.GPIO_Pin, GPIO_PIN_RESET);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 使能驱动芯片</span></span><br><span class="line"><span class="comment"> * @note 打算不使用STBY引脚，想直接给他接3v3</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">// void Motor_Enable()</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//     HAL_GPIO_WritePin(STBY_GPIO_Port, STBY_Pin, GPIO_PIN_SET);</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 失能驱动芯片</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="comment">// void Motor_Disable()</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">//     HAL_GPIO_WritePin(STBY_GPIO_Port, STBY_Pin, GPIO_PIN_SET);</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 启动电机</span></span><br><span class="line"><span class="comment"> * @attention 本函数不会设置速度，所以会按照原本速度运行</span></span><br><span class="line"><span class="comment"> * @param hmotor 电机</span></span><br><span class="line"><span class="comment"> * @retval None</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_Start</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    _Perform_SetDirection(hmotor);</span><br><span class="line">    __HAL_TIM_SET_COMPARE(hmotor-&gt;pwm.htim, hmotor-&gt;pwm.channel, __SPEED2CCR(hmotor-&gt;speed)); <span class="comment">// 根据速度设置占空比</span></span><br><span class="line">    HAL_TIM_PWM_Start(hmotor-&gt;pwm.htim, hmotor-&gt;pwm.channel);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 停止电机</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_Stop</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    HAL_TIM_PWM_Stop(hmotor-&gt;pwm.htim, hmotor-&gt;pwm.channel);</span><br><span class="line">    HAL_GPIO_WritePin(hmotor-&gt;in1.GPIOx, hmotor-&gt;in1.GPIO_Pin, GPIO_PIN_RESET);</span><br><span class="line">    HAL_GPIO_WritePin(hmotor-&gt;in2.GPIOx, hmotor-&gt;in2.GPIO_Pin, GPIO_PIN_RESET);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 设置速度</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> * @param speed -1&lt;= speed &lt;= 1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">Motor_SetSpeed</span><span class="params">(Motor_t* hmotor, <span class="type">float</span> speed)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (speed &gt; <span class="number">0</span> &amp;&amp; hmotor-&gt;direction == <span class="number">1</span>)</span><br><span class="line">        Motor_SetDirection(hmotor, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (speed &lt; <span class="number">0</span> &amp;&amp; hmotor-&gt;direction == <span class="number">0</span>)</span><br><span class="line">        Motor_SetDirection(hmotor, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (speed &lt; <span class="number">0</span>) speed = -speed;</span><br><span class="line">    hmotor-&gt;speed = speed;</span><br><span class="line">    __HAL_TIM_SET_COMPARE(hmotor-&gt;pwm.htim, hmotor-&gt;pwm.channel, __SPEED2CCR(speed));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 设置方向</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> * @param direction 0 正转  1 反转</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Motor_SetDirection</span><span class="params">(Motor_t* hmotor, <span class="type">const</span> <span class="type">uint8_t</span> direction)</span></span><br><span class="line">&#123;</span><br><span class="line">    hmotor-&gt;direction = direction;</span><br><span class="line">    _Perform_SetDirection(hmotor);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 切换方向</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">Motor_ToggleDirection</span><span class="params">(Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    Motor_SetDirection(hmotor, !hmotor-&gt;direction);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 启动编码器</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">Encoder_Start</span><span class="params">(Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    HAL_TIM_Encoder_Start(hmotor-&gt;encoder, TIM_CHANNEL_ALL);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 关闭编码器</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">Encoder_Stop</span><span class="params">(Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    HAL_TIM_Encoder_Stop(hmotor-&gt;encoder, TIM_CHANNEL_ALL);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 在定时器中处理编码器的数据</span></span><br><span class="line"><span class="comment"> * @note 双边沿计数，TI1 and TI2</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">Encoder_Progress</span><span class="params">(Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 此处需要保证 counter &lt; 32768 正常电机转不了那么快</span></span><br><span class="line">    <span class="type">const</span> <span class="type">int16_t</span> counter = __HAL_TIM_GET_COUNTER(hmotor-&gt;encoder);</span><br><span class="line">    <span class="comment">// printf(&quot;%d&quot;, counter);</span></span><br><span class="line">    hmotor-&gt;real_round += (<span class="type">float</span>)counter / (ROTO_RADIO * REDUCTION_RADIO); <span class="comment">// 转过的圈数</span></span><br><span class="line">    hmotor-&gt;real_speed = (<span class="type">float</span>)counter / (ROTO_RADIO * REDUCTION_RADIO) / SAMPLING_PERIOD * <span class="number">60</span>; <span class="comment">// 实际转速(rpm)</span></span><br><span class="line">    __HAL_TIM_SET_COUNTER(hmotor-&gt;encoder, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 获取电机速度</span></span><br><span class="line"><span class="comment"> * @param hmotor</span></span><br><span class="line"><span class="comment"> * @return 电机速度(rpm)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">float</span> <span class="title function_">Encoder_GetSpeed</span><span class="params">(<span class="type">const</span> Motor_t* hmotor)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> hmotor-&gt;real_speed;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>Motor_t</code> 类型变量定义示例</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Motor_t motor = &#123;</span><br><span class="line">  &#123;&amp;htim4, TIM_CHANNEL_1&#125;, <span class="comment">///&lt; PWM信号 &#123;定时器, 通道&#125;</span></span><br><span class="line">  &amp;htim2, <span class="comment">///&lt; 编码器使用的定时器</span></span><br><span class="line">  &#123;MOTORA_IN1_GPIO_Port, MOTORA_IN1_Pin&#125;, <span class="comment">///&lt; 电机输入IN1 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">  &#123;MOTORA_IN2_GPIO_Port, MOTORA_IN2_Pin&#125;, <span class="comment">///&lt; 电机输入IN2 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">  <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span> <span class="comment">///&lt; 方向，速度（0~1)，编码器测得的速度，编码器测得的圈数</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="pid-控制"><a class="markdownIt-Anchor" href="#pid-控制"></a> PID 控制</h2><h3 id="理论基础-参考资料"><a class="markdownIt-Anchor" href="#理论基础-参考资料"></a> 理论基础 / 参考资料</h3><ul><li>SSC 的 PID 控制资料 - 不可共享</li><li><a href="https://blog.csdn.net/weixin_39258979/article/details/130611823">(三) PID控制中的噪声过滤</a></li><li><a href="http://www.360doc.com/content/23/0412/18/32066980_1076225134.shtml">史上最详细的PID教程——理解PID原理及优化算法</a></li><li><a href="https://blog.csdn.net/foxclever/article/details/83932107">PID控制器开发笔记之十二：模糊PID控制器的实现</a></li></ul><h3 id="实践开始"><a class="markdownIt-Anchor" href="#实践开始"></a> 实践开始</h3><p>要完成 PID 控制，我们需要有反馈输入 <code>feedback</code>，并拥有预设定的目标值 <code>target</code>，然后根据 PID 算法计算输出 <code>output</code></p><p>首先，我们假定</p><ul><li>输入范围不限，因为 PID 的输入可能是 圈数、速度 等很多东西</li><li>输出有效范围在 $[0,1]$，因为我们的 PID 输出应当作为电机控制的输入（在上面的电机控制驱动中指的是 <code>Motor_SetSpeed</code> 函数的输入）</li></ul><p>由一大坨理论（从 SSC 的 PID 控制教程中）得到增量式 PID 计算公式</p><p>$$\Delta u(n) &#x3D; u(n) - u(n-1) &#x3D; K_P(e(n)-e(n-1)) + K_I e(n) + K_D(e(n)-2e(n-1)+e(n-2))$$</p><p>可见增量 $\Delta u(n)$ 只与<strong>最近三次</strong>误差值有关，故我们只需记录最近三个误差值 <code>error</code>, <code>error_last1</code>, <code>error_last2</code></p><blockquote><p>注意：PID 计算函数应当是周期性的，因为单片机中使用的是离散型 PID 系统</p><p>离散 PID 系统存在控制频率要求，实际使用时需要在硬件定时器中使用，周期越小，要求系统抗噪声能力较强，同时参数值较小</p></blockquote><p>根据上面一坨东西，我们可以得到初步的 PID 计算函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> <span class="title function_">PID_Calculate</span><span class="params">(PID_t* hpid, <span class="type">const</span> <span class="type">float</span> feedback)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* 计算误差值 */</span></span><br><span class="line">    hpid-&gt;error = hpid-&gt;target - feedback;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* 计算增量 */</span></span><br><span class="line">    <span class="comment">/* 比例部分 */</span></span><br><span class="line">    <span class="type">float</span> p = hpid-&gt;Kp * (hpid-&gt;error - hpid-&gt;error_last1);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 积分部分 */</span></span><br><span class="line">    <span class="type">float</span> i = hpid-&gt;Ki * hpid-&gt;error;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 微分部分 */</span></span><br><span class="line">    <span class="type">float</span> d = hpid-&gt;Kd * (hpid-&gt;error - <span class="number">2</span> * hpid-&gt;error_last1 + hpid-&gt;error_last2);</span><br><span class="line">    </span><br><span class="line">    hpid-&gt;output += p + i + d;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新误差值</span></span><br><span class="line">    hpid-&gt;error_last2 = hpid-&gt;error_last1;</span><br><span class="line">    hpid-&gt;error_last1 = hpid-&gt;error;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> hpid-&gt;output;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是很快我们会发现一些奇奇怪怪的问题，比如</p><ul><li>我在设置目标值时系统一下子就跑飞了，油门轰轰地响，半天停不下来，都要开到华盛顿去了</li><li>怎么输出老是在目标附近摇摆不定做类简谐运动，让我回忆起我美好的高中生活</li><li>速度环为啥老是波动不能固定？这个位置环到位了能不能就别动来动去了</li><li>它怎么总是先飞过去再飞回来</li><li>臣卜木曹为啥它做简谐运动振幅还越来越大，我草要飞了！</li><li>我趣它为啥非要先反着转然后再正着转过去？</li></ul><p>显然，我们需要一定的优化，比如</p><ol><li><p>限制输出范围，或者叫抗饱和</p><p>将 <code>output</code> 的值限制在有效范围内，避免一直处于超调区，系统能够更快响应</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* 抗饱和：限制范围，防止跑飞，同时限制速度 */</span></span><br><span class="line"><span class="keyword">if</span> (hpid-&gt;output &gt; hpid-&gt;output_abs_max)</span><br><span class="line">    hpid-&gt;output = hpid-&gt;output_abs_max;</span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> (hpid-&gt;output &lt; -hpid-&gt;output_abs_max)</span><br><span class="line">    hpid-&gt;output = -hpid-&gt;output_abs_max;</span><br></pre></td></tr></table></figure></li><li><p>加一些低通滤波器器，避免高频噪声（同时也避免系统输出剧烈变化）</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 对微分部分进行低通滤波</span></span><br><span class="line">hpid-&gt;filtered_d = hpid-&gt;filtered_d * dfilter + d * (<span class="number">1</span> - dfilter);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对输出进行低通滤波</span></span><br><span class="line">hpid-&gt;output += du * (<span class="number">1</span> - ufilter);</span><br></pre></td></tr></table></figure></li><li><p>对积分部分添加死区</p><p>通常在非常接近目标值时输出的波动都是由积分部分引起的，如果所谓的「非常接近」已经达到精度需求，则完全可以在这一精度范围内舍弃微分部分以避免波动</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> i = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (hpid-&gt;error &gt; hpid-&gt;ideadzone || hpid-&gt;error &lt; -hpid-&gt;ideadzone) i = Ki * hpid-&gt;error;</span><br></pre></td></tr></table></figure></li><li><p>使用模糊 PID 控制</p><p>往往一组 PID 参数不能满足动态过程的所有需求，所以就有了动态改变 PID 参数的模糊 PID 算法。</p><p>由于我还不会模糊 PID 控制，故采用下位方案：将控制过程分为两部分，以 $|error|$ 作为分界值 $cutoff$，使用两组不同的 PID 参数。这两组参数应当分别具有以下特性</p><ul><li>初始参数应当让响应速度尽可能快，以缩短调节时间</li><li>末尾参数应当尽可能将系统控制在目标附近，以获得更高精度</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> Kp, Ki, Kd, ufilter, dfilter;</span><br><span class="line"><span class="keyword">if</span> (hpid-&gt;error &lt; hpid-&gt;cutoff &amp;&amp; hpid-&gt;error &gt; -hpid-&gt;cutoff)</span><br><span class="line">    Kp = hpid-&gt;KpE, Ki = hpid-&gt;KiE, Kd = hpid-&gt;KdE, ufilter = hpid-&gt;ufilterE, dfilter = hpid-&gt;dfilterE;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    Kp = hpid-&gt;KpS, Ki = hpid-&gt;KiS, Kd = hpid-&gt;KdS, ufilter = hpid-&gt;ufilterS, dfilter = hpid-&gt;dfilterS;</span><br></pre></td></tr></table></figure></li></ol><p>另外，对于以上几个奇怪的问题的原因，可能要从参数入手</p><blockquote><p>注意：以下参数方面的原因判断不一定准确，建议自己调试总结</p></blockquote><ul><li>类简谐运动大概是由于积分部分和微分部分的超调，加大比例部分参数压住即可（或者减小积分部分）</li><li>先反转可能是微分部分设大了，减小微分部分参数即可</li></ul><p>速度环的波动是正常的，因为即使一直给电机供一样的电，电机的速度也会发生变化，速度环在一定范围内的波动是正常的。</p><h3 id="驱动代码-2"><a class="markdownIt-Anchor" href="#驱动代码-2"></a> 驱动代码</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file pid.h</span></span><br><span class="line"><span class="comment"> * @brief PID 驱动文件</span></span><br><span class="line"><span class="comment"> * @author hanjin</span></span><br><span class="line"><span class="comment"> * @date 2024-10-14</span></span><br><span class="line"><span class="comment"> * @version v0.3.2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> PID_H</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PID_H</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PID_VERSION <span class="string">&quot;0.3.2&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;main.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 增量式PID</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="type">uint8_t</span> enable;</span><br><span class="line">    <span class="type">float</span> target; <span class="comment">///&lt; PID控制要求输入值的目标</span></span><br><span class="line">    <span class="type">float</span> output_abs_max; <span class="comment">///&lt; PID输出绝对值最大值，防止跑飞</span></span><br><span class="line">    <span class="type">float</span> cutoff; <span class="comment">///&lt; 分界值</span></span><br><span class="line">    <span class="comment">/* v3.0 将pid控制分段 */</span></span><br><span class="line">    <span class="comment">// error &lt; 5 时的参数</span></span><br><span class="line">    <span class="type">float</span> KpE, KiE, KdE; <span class="comment">///&lt; 比例系数 积分系数 微分系数</span></span><br><span class="line">    <span class="type">float</span> dfilterE; <span class="comment">///&lt; 微分部分低通滤波系数，越大越平滑</span></span><br><span class="line">    <span class="type">float</span> ufilterE; <span class="comment">///&lt; 输出值滤波</span></span><br><span class="line">    <span class="comment">/* v3.1 增加死区大小，用于位置环稳定 */</span></span><br><span class="line">    <span class="type">float</span> ideadzone; <span class="comment">///&lt; 积分部分死区大小</span></span><br><span class="line">    <span class="comment">// error &gt;=5 时的参数</span></span><br><span class="line">    <span class="type">float</span> KpS, KiS, KdS;</span><br><span class="line">    <span class="type">float</span> ufilterS, dfilterS;</span><br><span class="line">    <span class="comment">// 运行过程中间量</span></span><br><span class="line">    <span class="type">float</span> error_last1, error_last2, error; <span class="comment">///&lt; 前一次误差 前前次误差 本次误差</span></span><br><span class="line">    <span class="type">float</span> output; <span class="comment">///&lt; PID控制输出值</span></span><br><span class="line">    <span class="type">float</span> filtered_d; <span class="comment">///&lt; 微分部分经过滤波后的值</span></span><br><span class="line">&#125; PID_t;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 设置PID控制的目标</span></span><br><span class="line"><span class="comment"> * @param __PID_HANDLE__ PID对象的指针</span></span><br><span class="line"><span class="comment"> * @param __TARGET__ 目标</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __PID_SET_TARGET(__PID_HANDLE__, __TARGET__) ((__PID_HANDLE__)-&gt;target = (__TARGET__))</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 重置PID过程量</span></span><br><span class="line"><span class="comment"> * @note added 0.3.2</span></span><br><span class="line"><span class="comment"> * @param __PID_HANDLE__</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __PID_RESET(__PID_HANDLE__) \</span></span><br><span class="line"><span class="meta">    do &#123; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;<span class="keyword">error</span> = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;error_last1 = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;error_last2 = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;output = 0; \</span></span><br><span class="line"><span class="meta">    &#125; while(0)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">PID_Init</span><span class="params">(PID_t* hpid,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> KpS, <span class="type">float</span> KiS, <span class="type">float</span> KdS, <span class="type">float</span> ufilterS, <span class="type">float</span> dfilterS,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> KpE, <span class="type">float</span> KiE, <span class="type">float</span> KdE, <span class="type">float</span> ufilterE, <span class="type">float</span> dfilterE, <span class="type">float</span> ideadzone,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> output_abs_max, <span class="type">float</span> cutoff, <span class="type">float</span> target</span></span><br><span class="line"><span class="params">)</span>;</span><br><span class="line"><span class="type">float</span> <span class="title function_">PID_Calculate</span><span class="params">(PID_t* hpid, <span class="type">const</span> <span class="type">float</span> feedback)</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @file pid.c</span></span><br><span class="line"><span class="comment"> * @brief PID 驱动文件</span></span><br><span class="line"><span class="comment"> * @author hanjin</span></span><br><span class="line"><span class="comment"> * @date 2024-10-14</span></span><br><span class="line"><span class="comment"> * @version v0.3.2</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;pid.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief PID初始化</span></span><br><span class="line"><span class="comment"> * @attention 由于输出在 -1 ~ 1 之间，所以参数需要 / MAX_INPUT 来对齐阶数 &lt; 只是猜测，具体需要实践</span></span><br><span class="line"><span class="comment"> * @param hpid PID对讲指针</span></span><br><span class="line"><span class="comment"> * @param KpS 初始比例系数</span></span><br><span class="line"><span class="comment"> * @param KiS 初始积分系数</span></span><br><span class="line"><span class="comment"> * @param KdS 初始微分系数</span></span><br><span class="line"><span class="comment"> * @param ufilterS 初始输出滤波系数</span></span><br><span class="line"><span class="comment"> * @param dfilterS 初始微分部分滤波系数</span></span><br><span class="line"><span class="comment"> * @param KpE 末尾比例系数</span></span><br><span class="line"><span class="comment"> * @param KiE 末尾积分系数</span></span><br><span class="line"><span class="comment"> * @param KdE 末尾微分系数</span></span><br><span class="line"><span class="comment"> * @param ufilterE 末尾输出滤波系数</span></span><br><span class="line"><span class="comment"> * @param dfilterE 末尾微分部分滤波系数</span></span><br><span class="line"><span class="comment"> * @param ideadzone 积分部分死区大小</span></span><br><span class="line"><span class="comment"> * @param output_abs_max 输出绝对值最大限制</span></span><br><span class="line"><span class="comment"> * @param cutoff 初始与末尾分隔量 abs(error) &lt; cutoff 视为末尾状态</span></span><br><span class="line"><span class="comment"> * @param target PID控制目标</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">PID_Init</span><span class="params">(PID_t* hpid,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> KpS, <span class="type">float</span> KiS, <span class="type">float</span> KdS, <span class="type">float</span> ufilterS, <span class="type">float</span> dfilterS,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> KpE, <span class="type">float</span> KiE, <span class="type">float</span> KdE, <span class="type">float</span> ufilterE, <span class="type">float</span> dfilterE, <span class="type">float</span> ideadzone,</span></span><br><span class="line"><span class="params">              <span class="type">float</span> output_abs_max, <span class="type">float</span> cutoff, <span class="type">float</span> target</span></span><br><span class="line"><span class="params">)</span></span><br><span class="line">&#123;</span><br><span class="line">    hpid-&gt;target = target;</span><br><span class="line">    hpid-&gt;output_abs_max = output_abs_max;</span><br><span class="line">    hpid-&gt;cutoff = cutoff;</span><br><span class="line"></span><br><span class="line">    hpid-&gt;KpS = KpS, hpid-&gt;KiS = KiS, hpid-&gt;KdS = KdS, hpid-&gt;ufilterS = ufilterS, hpid-&gt;dfilterS = dfilterS;</span><br><span class="line">    hpid-&gt;KpE = KpE, hpid-&gt;KiE = KiE, hpid-&gt;KdE = KdE, hpid-&gt;ufilterE = ufilterE, hpid-&gt;dfilterE = dfilterE, hpid-&gt;ideadzone = ideadzone;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 计算pid输出</span></span><br><span class="line"><span class="comment"> * @note 理论上各种地方都能用不是么</span></span><br><span class="line"><span class="comment"> * @param hpid PID对象</span></span><br><span class="line"><span class="comment"> * @param feedback 输入值</span></span><br><span class="line"><span class="comment"> * @return -1 ~ 1</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">float</span> <span class="title function_">PID_Calculate</span><span class="params">(PID_t* hpid, <span class="type">const</span> <span class="type">float</span> feedback)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 计算误差值</span></span><br><span class="line">    <span class="comment">// hpid-&gt;error = input - hpid-&gt;target;</span></span><br><span class="line">    hpid-&gt;error = hpid-&gt;target - feedback;</span><br><span class="line">    <span class="comment">// 计算增量</span></span><br><span class="line">    <span class="type">float</span> Kp, Ki, Kd, ufilter, dfilter;</span><br><span class="line">    <span class="keyword">if</span> (hpid-&gt;error &lt; hpid-&gt;cutoff &amp;&amp; hpid-&gt;error &gt; -hpid-&gt;cutoff)</span><br><span class="line">        Kp = hpid-&gt;KpE, Ki = hpid-&gt;KiE, Kd = hpid-&gt;KdE, ufilter = hpid-&gt;ufilterE, dfilter = hpid-&gt;dfilterE;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        Kp = hpid-&gt;KpS, Ki = hpid-&gt;KiS, Kd = hpid-&gt;KdS, ufilter = hpid-&gt;ufilterS, dfilter = hpid-&gt;dfilterS;</span><br><span class="line">    <span class="comment">/* 比例部分 */</span></span><br><span class="line">    <span class="type">float</span> p = Kp * (hpid-&gt;error - hpid-&gt;error_last1);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 积分部分 */</span></span><br><span class="line">    <span class="type">float</span> i = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (hpid-&gt;error &gt; hpid-&gt;ideadzone || hpid-&gt;error &lt; -hpid-&gt;ideadzone) i = Ki * hpid-&gt;error;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 微分部分 */</span></span><br><span class="line">    <span class="type">float</span> d = Kd * (hpid-&gt;error - <span class="number">2</span> * hpid-&gt;error_last1 + hpid-&gt;error_last2);</span><br><span class="line">    <span class="comment">// 对微分部分进行低通滤波</span></span><br><span class="line">    hpid-&gt;filtered_d = hpid-&gt;filtered_d * dfilter + d * (<span class="number">1</span> - dfilter);</span><br><span class="line"></span><br><span class="line">    <span class="type">float</span> du = p + i + hpid-&gt;filtered_d;</span><br><span class="line"></span><br><span class="line">    hpid-&gt;output += du * (<span class="number">1</span> - ufilter);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 抗饱和：限制范围，防止跑飞，同时限制速度 */</span></span><br><span class="line">    <span class="keyword">if</span> (hpid-&gt;output &gt; hpid-&gt;output_abs_max)</span><br><span class="line">        hpid-&gt;output = hpid-&gt;output_abs_max;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (hpid-&gt;output &lt; -hpid-&gt;output_abs_max)</span><br><span class="line">        hpid-&gt;output = -hpid-&gt;output_abs_max;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新误差值</span></span><br><span class="line">    hpid-&gt;error_last2 = hpid-&gt;error_last1;</span><br><span class="line">    hpid-&gt;error_last1 = hpid-&gt;error;</span><br><span class="line">    <span class="keyword">return</span> hpid-&gt;output;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="使用电机驱动和-pid-控制驱动"><a class="markdownIt-Anchor" href="#使用电机驱动和-pid-控制驱动"></a> 使用电机驱动和 PID 控制驱动</h2><h3 id="cubemx-配置"><a class="markdownIt-Anchor" href="#cubemx-配置"></a> CubeMX 配置</h3><h4 id="时钟"><a class="markdownIt-Anchor" href="#时钟"></a> 时钟</h4><p><code>HLCK</code> 配置为 <code>72</code> MHz</p><p>TIM1：预分配 <code>72-1</code>，自动重装载 <code>10000-1</code>。提供间隔 <code>10ms</code> 的定时中断</p><p>TIM2/TIM3：<code>Combined Channels</code> 配置为 <code>Encoder Mode</code>，并将 <code>Encoder Mode</code> 改为 <code>Encoder Mode TI1 and TI2</code>，其余默认</p><p>TIM4：预分频 <code>72-1</code>，自动重装载值 <code>1000-1</code>，并选择两个通道设为 <code>PWM Generation</code> 这里选择 <code>CH1</code> 和 <code>CH2</code></p><p>记的打开定时器的中断</p><h4 id="引脚"><a class="markdownIt-Anchor" href="#引脚"></a> 引脚</h4><p>需要四个引脚，分别具有用户标签 <code>MOTORA_IN1</code> <code>MOTORA_IN2</code> <code>MOTORB_IN1</code> <code>MOTORB_IN2</code>，即两个电机的四个输入</p><h3 id="必要代码实现"><a class="markdownIt-Anchor" href="#必要代码实现"></a> 必要代码实现</h3><ol><li><p>定义 <code>Motor_t</code> 类型变量和 <code>PID_t</code> 类型变量</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Motor_t motors[<span class="number">2</span>] = &#123;</span><br><span class="line">  &#123;</span><br><span class="line">    &#123;&amp;htim4, TIM_CHANNEL_1&#125;, <span class="comment">///&lt; PWM信号 &#123;定时器, 通道&#125;</span></span><br><span class="line">    &amp;htim2, <span class="comment">///&lt; 编码器使用的定时器</span></span><br><span class="line">    &#123;MOTORA_IN1_GPIO_Port, MOTORA_IN1_Pin&#125;, <span class="comment">///&lt; 电机输入IN1 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">    &#123;MOTORA_IN2_GPIO_Port, MOTORA_IN2_Pin&#125;, <span class="comment">///&lt; 电机输入IN2 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">    <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span> <span class="comment">///&lt; 方向，速度（0~1)，编码器测得的速度，编码器测得的圈数</span></span><br><span class="line">  &#125;, &#123;</span><br><span class="line">    &#123;&amp;htim4, TIM_CHANNEL_2&#125;, <span class="comment">///&lt; PWM信号 &#123;定时器, 通道&#125;</span></span><br><span class="line">    &amp;htim3, <span class="comment">///&lt; 编码器使用的定时器</span></span><br><span class="line">    &#123;MOTORB_IN1_GPIO_Port, MOTORB_IN1_Pin&#125;, <span class="comment">///&lt; 电机输入IN1 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">    &#123;MOTORB_IN2_GPIO_Port, MOTORB_IN2_Pin&#125;, <span class="comment">///&lt; 电机输入IN2 &#123;GPIOx, Pin&#125;</span></span><br><span class="line">    <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span> <span class="comment">///&lt; 方向，速度（0~1)，编码器测得的速度，编码器测得的圈数</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line">PID_t PIDs[<span class="number">2</span>];</span><br></pre></td></tr></table></figure></li><li><p>在 <code>HAL_TIM_PeriodElapsedCallback</code> 回调中处理编码器数据并执行 PID 计算</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">HAL_TIM_PeriodElapsedCallback</span><span class="params">(TIM_HandleTypeDef* htim)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (htim == &amp;htim1)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">/* 编码器采样 */</span></span><br><span class="line">    Encoder_Progress(motors + <span class="number">0</span>);</span><br><span class="line">    Encoder_Progress(motors + <span class="number">1</span>);</span><br><span class="line">    <span class="comment">/* PID计算 速度环 */</span></span><br><span class="line">    <span class="keyword">if</span> (PIDs[<span class="number">0</span>].enable)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="type">float</span> pid_output = PID_Calculate(PIDs + <span class="number">0</span>, motors[<span class="number">0</span>].real_speed);</span><br><span class="line">      Motor_SetSpeed(motors + <span class="number">0</span>, pid_output);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (PIDs[<span class="number">1</span>].enable)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="type">float</span> pid_output = PID_Calculate(PIDs + <span class="number">1</span>, motors[<span class="number">1</span>].real_speed);</span><br><span class="line">      Motor_SetSpeed(motors + <span class="number">1</span>, pid_output);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此处执行的是速度环控制，如果想要使用位置环，你只需要换一套参数，并将 <code>feedback</code> 的实参改为 <code>motors[x].real_round</code> 即可</p></li><li><p>在 <code>USER CODE BEGIN 2</code> 中进行 PID 参数初始化（具体参数自己调去），并使能 <code>TIM1</code> 的定时器中断</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">PID_Init(&amp;PIDs[<span class="number">0</span>],</span><br><span class="line">  , , , , ,</span><br><span class="line">  , , , , , ,</span><br><span class="line">  , , </span><br><span class="line">);</span><br><span class="line">PID_Init(&amp;PIDs[<span class="number">1</span>],</span><br><span class="line">  , , , , ,</span><br><span class="line">  , , , , , ,</span><br><span class="line">  , , </span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">HAL_TIM_Base_Start_IT(&amp;htim1);</span><br></pre></td></tr></table></figure></li><li><p>在恰当的地方使能编码器和电机</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Motor_Start(motors + <span class="number">0</span>);</span><br><span class="line">Encoder_Start(motors + <span class="number">0</span>);</span><br><span class="line">PIDs[<span class="number">0</span>].enable = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">Motor_Start(motors + <span class="number">1</span>);</span><br><span class="line">Encoder_Start(motors + <span class="number">1</span>);</span><br><span class="line">PIDs[<span class="number">1</span>].enable = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></li></ol><blockquote><p>注意事项</p><ol><li>电机在 Start 之前或 Stop 之后处于断电状态，如果你希望就让那个电机保持在那里，你应当使用速度环控制并将目标设定为 <code>0</code>（或者使用位置环控制，并让电机位于目标值）</li><li>不要试图自己调用 <code>Motor_SetSpeed</code> 来设置速度，这是一个愚蠢的选择，因为这相当于完全舍弃了 PID 控制，正确的做法是修改 PID 控制的目标值，让 PID 来帮你实现</li></ol></blockquote><h3 id="控制模式切换"><a class="markdownIt-Anchor" href="#控制模式切换"></a> 控制模式切换</h3><blockquote><p>「那我要是 TM 的既想要速度环又想要位置环怎么办？」 会有人这样想的</p></blockquote><p>所以我们可以通过一些方法来实现两种控制的切换</p><ol><li><p>定义一个集成类</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">enum</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    SPEED_LOOP = <span class="number">0U</span>,</span><br><span class="line">    POSITION_LOOP = <span class="number">1U</span>,</span><br><span class="line">&#125; WheelState_t;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    Motor_t motor; <span class="comment">///&lt; 电机类</span></span><br><span class="line">    PID_t speed_loop, position_loop; <span class="comment">///&lt; 速度环pid和位置环pid</span></span><br><span class="line">    WheelState_t state; <span class="comment">///&lt; 处于哪种控制模式下</span></span><br><span class="line">    <span class="type">uint8_t</span> enable; <span class="comment">///&lt; 是否开启</span></span><br><span class="line">&#125; Wheel_t;</span><br></pre></td></tr></table></figure></li><li><p>计算的时候稍费些功夫</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* PID计算 速度环 &amp; 位置环 */</span></span><br><span class="line"><span class="type">float</span> pid_output;</span><br><span class="line"><span class="keyword">if</span> (wheels[<span class="number">0</span>].enable)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (wheels[<span class="number">0</span>].state == SPEED_LOOP)</span><br><span class="line">    pid_output = PID_Calculate(&amp;wheels[<span class="number">0</span>].speed_loop, wheels[<span class="number">0</span>].motor.real_speed);</span><br><span class="line">  <span class="keyword">else</span> <span class="comment">// wheels[0].state == POSITION_LOOP</span></span><br><span class="line">    pid_output = PID_Calculate(&amp;wheels[<span class="number">0</span>].position_loop, wheels[<span class="number">0</span>].motor.real_round);</span><br><span class="line">  Motor_SetSpeed(&amp;wheels[<span class="number">0</span>].motor, pid_output);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (wheels[<span class="number">1</span>].enable)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (wheels[<span class="number">1</span>].state == SPEED_LOOP)</span><br><span class="line">    pid_output = PID_Calculate(&amp;wheels[<span class="number">1</span>].speed_loop, wheels[<span class="number">1</span>].motor.real_speed);</span><br><span class="line">  <span class="keyword">else</span> <span class="comment">// wheels[1].state == POSITION_LOOP</span></span><br><span class="line">    pid_output = PID_Calculate(&amp;wheels[<span class="number">1</span>].position_loop, wheels[<span class="number">1</span>].motor.real_round);</span><br><span class="line">  Motor_SetSpeed(&amp;wheels[<span class="number">1</span>].motor, pid_output);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>然后编写一个控制状态切换函数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">void switchWheelState(Wheel_t* wheel, WheelState_t newState)</span><br><span class="line">&#123;</span><br><span class="line">    if (wheel-&gt;state == newState) return;</span><br><span class="line">    wheel-&gt;state = newState;</span><br><span class="line">    switch (newState)</span><br><span class="line">    &#123;</span><br><span class="line">    case SPEED_LOOP:</span><br><span class="line">        wheel-&gt;speed_loop.output = wheel-&gt;position_loop.output;</span><br><span class="line">        break;</span><br><span class="line">    case POSITION_LOOP:</span><br><span class="line">        wheel-&gt;position_loop.output = wheel-&gt;speed_loop.output;</span><br><span class="line">        break;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>如果你在切到位置环的时候不知道速度环到底把电机转到哪了</p><p>你还可以使用之前在 <code>pid.h</code> 中挖下的一个坑来归零</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * @brief 重置PID过程量</span></span><br><span class="line"><span class="comment"> * @note added 0.3.2</span></span><br><span class="line"><span class="comment"> * @param __PID_HANDLE__</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __PID_RESET(__PID_HANDLE__) \</span></span><br><span class="line"><span class="meta">    do &#123; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;<span class="keyword">error</span> = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;error_last1 = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;error_last2 = 0; \</span></span><br><span class="line"><span class="meta">        (__PID_HANDLE__)-&gt;output = 0; \</span></span><br><span class="line"><span class="meta">    &#125; while(0)</span></span><br></pre></td></tr></table></figure></li></ol><p>你可以随时切换两种控制方式，获得更自由的控制体验</p><h2 id="使用-vofa-提供舒适的-pid-调参体验"><a class="markdownIt-Anchor" href="#使用-vofa-提供舒适的-pid-调参体验"></a> 使用 VOFA+ 提供舒适的 PID 调参体验</h2><blockquote><p><a href="vofa.plus">VOFA+：插件驱动的高自由度上位机</a></p></blockquote><p>显然，在代码里修改 PID 参数再重新烧录的调参方式是非常繁琐且低效的，我们需要一个更<strong>妙</strong>的调参方式，所以想到了 VOFA+ 辅助调参，调参过程中我们默认只对一个电机调参。</p><h3 id="代码配置"><a class="markdownIt-Anchor" href="#代码配置"></a> 代码配置</h3><ol><li><p>对 <code>prinft</code> 输出进行重定向</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* printf retarget */</span></span><br><span class="line"><span class="type">int</span> __io_putchar(<span class="type">int</span> ch)</span><br><span class="line">&#123;</span><br><span class="line">  HAL_UART_Transmit(&amp;huart1, (<span class="type">uint8_t</span>*)&amp;ch, <span class="number">1</span>, <span class="number">0xFFFF</span>);</span><br><span class="line">  <span class="keyword">return</span> ch;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>向 VOFA+ 发送当前系统的状态</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Infinite loop */</span></span><br><span class="line"><span class="comment">/* USER CODE BEGIN WHILE */</span></span><br><span class="line"><span class="keyword">while</span> (<span class="number">1</span>)</span><br><span class="line">&#123;</span><br><span class="line">  <span class="comment">// delay_counter 的作用是在 PID 禁用后仍发送一些数据，带来更好的 VOFA+ 前端体验</span></span><br><span class="line">  <span class="keyword">if</span> (PIDs[<span class="number">0</span>].enable || delay_counter)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%f,%f,%f,%f\n&quot;</span>, motors[<span class="number">0</span>].real_round, motors[<span class="number">0</span>].real_speed, PIDs[<span class="number">0</span>].output, PIDs[<span class="number">0</span>].target);</span><br><span class="line">    delay_counter--;</span><br><span class="line">  &#125;</span><br><span class="line">  HAL_Delay(<span class="number">20</span>);</span><br><span class="line">  <span class="comment">/* USER CODE END WHILE */</span></span><br><span class="line">  <span class="comment">/* USER CODE BEGIN 3 */</span></span><br><span class="line">&#125;</span><br><span class="line">  <span class="comment">/* USER CODE END 3 */</span></span><br></pre></td></tr></table></figure></li><li><p>能够接受 VOFA+ 发送的指令</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">HAL_UART_RxCpltCallback</span><span class="params">(UART_HandleTypeDef* huart)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (huart == &amp;huart1)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="comment">/* 校验协议头和协议尾 */</span></span><br><span class="line">    <span class="keyword">if</span> (rb[<span class="number">0</span>] == <span class="number">0xAA</span> &amp;&amp; rb[<span class="number">6</span>] == <span class="number">0xBB</span>) </span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">switch</span> (rb[<span class="number">1</span>])</span><br><span class="line">      &#123;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x11</span>: <span class="comment">// 设置目标值</span></span><br><span class="line">        PIDs[<span class="number">0</span>].target = *(<span class="type">float</span>*)(rb + <span class="number">2</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x22</span>: <span class="comment">// 启动电机</span></span><br><span class="line">        Motor_Start(motors);</span><br><span class="line">        Encoder_Start(motors);</span><br><span class="line">        PIDs[<span class="number">0</span>].enable = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x33</span>: <span class="comment">// 关闭电机，注意此时我们保证编码器开启，以持续记录电机位置</span></span><br><span class="line">        Motor_Stop(motors);</span><br><span class="line">        PIDs[<span class="number">0</span>].enable = <span class="number">0</span>;</span><br><span class="line">        PIDs[<span class="number">0</span>].output = <span class="number">0</span>;</span><br><span class="line">        delay_counter = <span class="number">10</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x44</span>: <span class="comment">// 修改比例系数</span></span><br><span class="line">        PIDs[<span class="number">0</span>].KpS = *(<span class="type">float</span>*)(rb + <span class="number">2</span>)</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> SPEED_LOOP</span></span><br><span class="line">          / MAX_SPEED</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">          ;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x55</span>: <span class="comment">// 修改积分系数</span></span><br><span class="line">        PIDs[<span class="number">0</span>].KiS = *(<span class="type">float</span>*)(rb + <span class="number">2</span>)</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> SPEED_LOOP</span></span><br><span class="line">          / MAX_SPEED</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">          ;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x66</span>: <span class="comment">// 修改微分系数</span></span><br><span class="line">        PIDs[<span class="number">0</span>].KdS = *(<span class="type">float</span>*)(rb + <span class="number">2</span>)</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> SPEED_LOOP</span></span><br><span class="line">          / MAX_SPEED</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">          ;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x77</span>: <span class="comment">// 修改微分低通滤波系数</span></span><br><span class="line">        PIDs[<span class="number">0</span>].dfilterS = *(<span class="type">float</span>*)(rb + <span class="number">2</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">case</span> <span class="number">0x99</span>: <span class="comment">// 修改输出值低通滤波系数</span></span><br><span class="line">        PIDs[<span class="number">0</span>].ufilterS = *(<span class="type">float</span>*)(rb + <span class="number">2</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">      <span class="keyword">default</span>: ;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="built_in">printf</span>(<span class="string">&quot;ACK: %f,%f,%f,%f,%f,%f\n&quot;</span>,</span><br><span class="line">             PIDs[<span class="number">0</span>].target, PIDs[<span class="number">0</span>].KpS, PIDs[<span class="number">0</span>].KiS, PIDs[<span class="number">0</span>].KdS, PIDs[<span class="number">0</span>].dfilterS, PIDs[<span class="number">0</span>].ufilterS);</span><br><span class="line">    &#125;</span><br><span class="line">    UART_Start_Receive_IT(&amp;huart1, rb, <span class="number">7</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注意</p><ol><li><p>这里调试的都是<strong>开始</strong>的 PID 参数，如果想要调试<strong>末尾</strong>的 PID 参数，请将后缀 <code>S</code> 改为 <code>E</code></p></li><li><p>在位置环调试模式下之所以 <code>/ MAX_SPEED</code> 是因为电机的速度范围为 $[-366, 366]$，而 PID 输出范围为 $[-1, 1]$，需要对齐阶数</p></li></ol></blockquote></li><li><p>记得在 <code>main</code> 函数里开启第一次接收</p></li></ol><h3 id="vofa-配置"><a class="markdownIt-Anchor" href="#vofa-配置"></a> VOFA+ 配置</h3><ol><li><p>在 VOFA+ 里新建一个命令组，添加一些命令</p><ul><li><code>Start</code>：<code>Hex</code> <code>AA 22 FF FF FF FF BB</code></li><li><code>Stop</code>：<code>Hex</code> <code>AA 33 FF FF FF FF BB</code></li><li><code>SetTarget</code>：<code>Hex</code> <code>AA 11 %% BB</code></li><li><code>...</code></li></ul><p><img src="/assets/ce368667e188/image-20241202151624845.png" alt="image-20241202151624845" /></p></li><li><p>先进行一次烧录，让 VOFA+ 收到第一组数据（FireWater），并配置四个通道</p><p><img src="/assets/ce368667e188/image-20241202152117108.png" alt="image-20241202152117108" /></p><p>在左侧拖出波形图</p><p><img src="/assets/ce368667e188/image-20241202152157385.png" alt="image-20241202152157385" /></p><p>和一堆滑动条</p><p><img src="/assets/ce368667e188/image-20241202152246823.png" alt="image-20241202152246823" /></p><p>右键滑动条并绑定命令</p><p><img src="/assets/ce368667e188/image-20241202152325780.png" alt="image-20241202152325780" /></p><p>将事件触发改为</p><p><img src="/assets/ce368667e188/image-20241202152356340.png" alt="image-20241202152356340" /></p></li><li><p>其实就差不多了，剩下的还是得自己摸索</p></li></ol><h3 id="项目地址"><a class="markdownIt-Anchor" href="#项目地址"></a> 项目地址</h3><p>调参项目发到了 <a href="https://github.com/syhanjin/PIDTest">GitHub</a>，不完善，需要者自取</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;STM32F103CBT6&lt;/code&gt; 和 &lt;code&gt;TB6612&lt;/code&gt; 驱动芯片驱动趣轮科技 &lt;code&gt;MG513P30_12V&lt;/code&gt; 直流电机，并使用  PID 算法进行闭环控制&lt;/p&gt;
&lt;/bloc</summary>
      
    
    
    
    <category term="嵌入式开发" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="直流电机" scheme="https://syhanjin.moe/tags/%E7%9B%B4%E6%B5%81%E7%94%B5%E6%9C%BA/"/>
    
    <category term="PID控制" scheme="https://syhanjin.moe/tags/pid%E6%8E%A7%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>VSCode+CubeMX+EIDE 环境配置</title>
    <link href="https://syhanjin.moe/20241130/1505fc7d06d8/"/>
    <id>https://syhanjin.moe/20241130/1505fc7d06d8/</id>
    <published>2024-11-30T13:29:38.000Z</published>
    <updated>2025-01-12T10:26:17.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="vscodecubemxeide-环境配置"><a class="markdownIt-Anchor" href="#vscodecubemxeide-环境配置"></a> <code>VSCode</code>+<code>CubeMX</code>+<code>EIDE</code> 环境配置</h1><h2 id="前置环境配置"><a class="markdownIt-Anchor" href="#前置环境配置"></a> 前置环境配置</h2><ol><li><a href="https://github.com/openocd-org/openocd/releases"><code>OpenOCD</code></a> 用于程序烧录，推荐使用 <code>0.11.0</code> 版本（因为之前似乎因为版本过高出现问题）</li><li>（可选）<a href="https://sourceforge.net/projects/mingw-w64/"><code>MinGW</code></a> 提供编译环境</li><li><a href="https://developer.arm.com/downloads/-/gnu-rm"><code>GNU Arm Embedded Toolchain</code></a> 这个页面是 <code>10.3-2021.10</code> 版本的，虽然已经被弃用，但是我用起来良好，主要是要这个：<code>arm-none-eabi-gcc</code> 交叉编译链</li></ol><p>添加环境变量</p><ul><li><p><code>xxx\OpenOCD\bin</code></p></li><li><p><code>xxx\GNU Arm Embedded Toolchain\10 2021.10\bin</code></p></li><li><p><code>xxx\mingw64\bin</code></p></li></ul><p>具体取决于你的安装路径</p><h2 id="配置-vscode"><a class="markdownIt-Anchor" href="#配置-vscode"></a> 配置 <code>VSCode</code></h2><p>首先下载以下插件</p><ul><li><code>Embedded IDE</code></li><li><code>Cortex-Debug</code></li></ul><blockquote><p>如果安装了 <code>Keil</code> 可以配置 <code>ARMCC5</code>，但是似乎不需要</p><p>将 <code>EIDE.ARM.ARMCC5</code> 和 <code>EIDE.ARM.ARMCC6</code> 配置为  <code>Keil</code> 安装目录下的 <code>ARM\ARMCC</code> 目录路径</p><p>似乎某些情况下安装了 <code>Keil</code> 也没有 <code>ARMCC</code> 目录，这时候要自己安装 <code>Compiler Version 5</code></p><p>具体参考这篇 <a href="https://blog.csdn.net/2301_78033819/article/details/143795005">https://blog.csdn.net/2301_78033819/article/details/143795005</a></p></blockquote><p>配置 <code>arm-none-eabi-gcc</code> 交叉编译链：</p><blockquote><p>在 <code>EIDE.ARM.GCC</code> 下配置 <code>arm-none-eabi-gcc.exe</code> 路径，要具体到 <code>.exe</code></p></blockquote><p>将 <code>EIDE.ARM.Options: Axf To Elf</code> 勾上</p><p>点击左侧边框生成的 <code>EIDE</code> 插件<img src="/assets/1505fc7d06d8/picture_22.jpg" alt="NULL" /></p><p>在<code>设置编译链</code>里面如果看到相关编译链旁边显示√，则配置完成。</p><h3 id="工程配置"><a class="markdownIt-Anchor" href="#工程配置"></a> 工程配置</h3><ol><li><p>使用 <code>STM32CubeMX</code> 中的 <code>Makefile</code> 选项生成工程。</p></li><li><p>用 <code>VSCode</code> 打开刚才生成的工程目录，随后 <code>EIDE</code> - 新建项目 - 空项目 - <code>Cortex-M</code> - 输入项目名称（需要和 <code>CubeMX</code> 生成的文件夹名称相同）</p></li><li><p>弹出文件夹选择窗口，选择工程目录的上一级文件夹（<code>../</code>）。</p></li><li><p>出现“项目文件夹已存在”的警告，点击 <code>Yes</code></p></li><li><p>将 <code>Core</code> <code>Drivers</code> 文件夹（选择<code>普通文件夹</code>）和 <code>startup_stm32xxxx.s</code> 文件添加到项目资源</p><p><img src="/assets/1505fc7d06d8/image-20241130205528730.png" alt="image-20241130205528730" /></p></li><li><p>将构建配置下的链接脚本路径修改为工程文件夹下的 <code>.ld</code> 文件</p><p><img src="/assets/1505fc7d06d8/image-20241130205710063.png" alt="image-20241130205710063" /></p></li><li><p>烧录配置选择 <code>STLink</code></p></li><li><p>点击项目属性右侧的编辑按钮，添加头文件目录和宏定义</p><p><img src="/assets/1505fc7d06d8/image-20241130205921294.png" alt="image-20241130205921294" /></p><p><img src="/assets/1505fc7d06d8/image-20241130210137970.png" alt="image-20241130210137970" /></p><p>注意这些东西可以在 <code>Makefile</code> 里面找到</p><p><img src="/assets/1505fc7d06d8/image-20241130210236851.png" alt="image-20241130210236851" /></p><p>去除前面的 <code>D</code> <code>I</code> 和末尾的 <code>\</code> 即可</p></li></ol><blockquote><p>注意可能会出现构建能正常进行，但是编辑器提示找不到宏的问题，这是因为构建使用的是交叉编译链，宏定义在项目配置中，而 <code>C/C++</code> 扩展的宏定义配置在插件配置里，所以缺少了两个宏定义</p><p>解决方案</p><ol><li><p>编辑 <code>C/C++</code> 扩展的配置文件 <code>c_cpp_properties.json</code> 在 <code>defines</code> 里面增加  <code>USE_HAL_DRIVER</code> 和 <code>STM32F103xB</code></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;defines&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;USE_HAL_DRIVER&quot;</span><span class="punctuation">,</span> <span class="string">&quot;STM32F103xB&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>就是上面 <code>Defines</code> 中的两项</p></li><li><p>在设置中找到  <code>Makefile: Configure On Open</code> 勾选，可以在打开时自动根据 <code>Makefile</code> 配置宏定义</p></li><li><p>在 <code>c_cpp_properties.json</code> 里配置 <code>configurationProvider</code> 为 <code>ms-vscode.makefile-tools</code></p><p>参考配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;configurations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;windows-gcc-x64&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;includePath&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;$&#123;workspaceFolder&#125;/**&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;compilerPath&quot;</span><span class="punctuation">:</span> <span class="string">&quot;E:/mingw64/bin/gcc.exe&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;cStandard&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;default&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;cppStandard&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;default&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;intelliSenseMode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;windows-gcc-x64&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;compilerArgs&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;configurationProvider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ms-vscode.makefile-tools&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="number">4</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li></ol></blockquote><blockquote><p>还有可能出现大概长下面那样的错误</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">./startup_stm32f103xb.s: Assembler messages:</span><br><span class="line">./startup_stm32f103xb.s:1: Error: junk at end of line, first unrecognized character is `-<span class="string">&#x27;</span></span><br><span class="line"><span class="string">./startup_stm32f103xb.s:2: Error: bad size 0 in type specifier</span></span><br><span class="line"><span class="string">./startup_stm32f103xb.s:2: Error: bad instruction `startup_stm32f103xb.s&#x27;</span></span><br></pre></td></tr></table></figure><p>此时删除项目根目录下的 <code>.s</code> 文件并重新生成即可</p></blockquote><h3 id="调试配置"><a class="markdownIt-Anchor" href="#调试配置"></a> 调试配置</h3><p>在运行和调试中点击设置按钮，在 <code>launch.json</code> 中关注</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;configFiles&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;interface/stlink-v2.cfg&quot;</span><span class="punctuation">,</span> <span class="string">&quot;target/stm32f1x.cfg&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>注意将后面的 <code>stm32fxxx</code> 修改为自己对应的芯片型号</p><p>在 <code>settings.json</code> 中加入以下几行</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;cortex-debug.armToolchainPath&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxx\\GNU Arm Embedded Toolchain\\10 2021.10\\bin&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;cortex-debug.variableUseNaturalFormat&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;cortex-debug.openocdPath&quot;</span><span class="punctuation">:</span> <span class="string">&quot;xxx\\OpenOCD\\bin\\openocd.exe&quot;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><p>注意将路径替换为自己的安装路径</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;vscodecubemxeide-环境配置&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#vscodecubemxeide-环境配置&quot;&gt;&lt;/a&gt; &lt;code&gt;VSCode&lt;/code&gt;+&lt;code&gt;CubeMX&lt;/code&gt;+&lt;code</summary>
      
    
    
    
    <category term="嵌入式开发" scheme="https://syhanjin.moe/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%BC%80%E5%8F%91/"/>
    
    
  </entry>
  
  <entry>
    <title>如何为一个 GitHub 仓库做贡献</title>
    <link href="https://syhanjin.moe/20241129/0f5d39a3a888/"/>
    <id>https://syhanjin.moe/20241129/0f5d39a3a888/</id>
    <published>2024-11-29T14:24:54.000Z</published>
    <updated>2025-05-10T12:09:30.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何为一个-github-仓库做贡献"><a class="markdownIt-Anchor" href="#如何为一个-github-仓库做贡献"></a> 如何为一个 GitHub 仓库做贡献</h1><h2 id="前置知识"><a class="markdownIt-Anchor" href="#前置知识"></a> 前置知识</h2><p>在为 GitHub 仓库做贡献前，你需要先了解</p><ul><li><p>（可选 推荐）Markdown 的基本语法</p></li><li><p>Git 的使用方法（不熟悉可以用 <a href="https://github.com/apps/desktop">GitHub Desktop</a> 代替，能满足<s>一点点</s>大部分需求）</p><p><a href="https://wiki.osa.moe/guide-for-beginner/git-tutorial">HITSZ OSA 的 Git 使用指南</a></p></li><li><p><a href="https://github.com/sparanoid/chinese-copywriting-guidelines/blob/master/README.zh-Hans.md">《中文文案排版指北》</a></p></li><li><p><a href="https://www.conventionalcommits.org/zh-hans/v1.0.0/">约定式提交规范</a></p></li></ul><p>你需要拥有一个 GitHub 账号，<s>并且可能需要学会一些科学上网的手段</s></p><h2 id="在你的你合作的仓库"><a class="markdownIt-Anchor" href="#在你的你合作的仓库"></a> 在你的/你合作的仓库</h2><p>你当然可以直接向 <code>master/main</code> 分支提交代码，但是通常不建议这么做，这样会让使用你的仓库或者想要对你的仓库做出贡献的人非常难受。</p><p>正确的做法是在需要修改的分支 <code>origin-branch</code> 上新建一个分支 <code>your-branch</code>，这个分支的名字可以代表你需要做的事情（至少有点含义，起码不要是 <code>001</code> <code>abcd</code> 这种），然后签出到新建的分支上进行更改。</p><p>你可以向你新建的分支 Commit，每个 Commit 通常只完成一个工作（比如配置了 xxx 东西，修复了 xxx 问题）。如果你一次性完成了多项工作，你应当为每一项工作创建一个 Commit，而不是把所有的东西一股脑用一个 Commit 提交（不要学我之前干的事情）</p><p>一个 Commit message 的书写最好遵循统一的规范（比如 <a href="https://www.conventionalcommits.org/zh-hans/v1.0.0/">约定式提交规范</a>）</p><p>当你完成了所有需要干的事情之后，希望合并到主分支 <code>master/main</code> 你需要新建一个 Pull request 选择将当前分支 <code>your-branch</code> 合并到  <code>origin-branch</code>，并在该 Pull request 中说明你的一系列 Commit 完成的工作（或者是这一个 Pull request 完成了什么事情）</p><p>在 Pull request 创建之后，GitHub 可能会基于仓库配置自动运行一些 Check 来保证你的代码不会影响整个程序的正常工作，仓库的所有者或者其他合作者可以查看你的 Pull request 并发表评论。当别人发现你的代码存在一些问题，他 / 她可以在存在问题的地方发起一个 Review，在你处理了这个问题后可以关闭这个 Review</p><blockquote><p>这一块我现在也不是特别熟悉，之后会继续更新</p></blockquote><p>当 Pull request 中的代码检查完毕后，仓库的管理员可以同过 Merge, Rebase 或者 Squash 的方式将其合并到 <code>origin-branch</code>。</p><p>至此，你对本仓库的一次贡献就完成了</p><h2 id="在一些开源仓库"><a class="markdownIt-Anchor" href="#在一些开源仓库"></a> 在一些开源仓库</h2><p>首先你应当将这个开源仓库 <code>fork</code> 到你自己的账户下，并新建一个分支。</p><p>接下来与在你的/你合作的仓库工作时的步骤一致</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;如何为一个-github-仓库做贡献&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#如何为一个-github-仓库做贡献&quot;&gt;&lt;/a&gt; 如何为一个 GitHub 仓库做贡献&lt;/h1&gt;
&lt;h2 id=&quot;前置知识&quot;&gt;&lt;a class=&quot;mar</summary>
      
    
    
    
    
    <category term="GitHub" scheme="https://syhanjin.moe/tags/github/"/>
    
    <category term="Git" scheme="https://syhanjin.moe/tags/git/"/>
    
  </entry>
  
  <entry>
    <title>线性方程组解的结构</title>
    <link href="https://syhanjin.moe/20241128/5e114e805aee/"/>
    <id>https://syhanjin.moe/20241128/5e114e805aee/</id>
    <published>2024-11-28T17:37:27.000Z</published>
    <updated>2024-12-02T05:13:42.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="线性方程组解的结构"><a class="markdownIt-Anchor" href="#线性方程组解的结构"></a> 线性方程组解的结构</h1><h2 id="齐次线性方程组解的结构"><a class="markdownIt-Anchor" href="#齐次线性方程组解的结构"></a> 齐次线性方程组解的结构</h2><blockquote><p>为何要从齐次线性方程组开始研究？</p><p><mark>（个人见解）齐次线性方程组具有一个已知解$\boldsymbol 0$，并且写成向量形式后能利用线性相关性的知识求解</mark></p></blockquote><p>设齐次线性方程组</p><p>$$\begin{equation}\left\{\begin{gathered}a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n&#x3D;0\\a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n&#x3D;0\\\cdots\cdots\cdots\cdots\\a_{m1}x_1+a_{m2}x_2+\cdots+a_{mn}x_n&#x3D;0\\\end{gathered}\right.\label{Eq.1}\end{equation}$$</p><p>写成矩阵形式</p><p>$$\begin{equation}\boldsymbol{AX}&#x3D;\boldsymbol{0}\label{Eq.2}\end{equation}$$</p><p>写成向量形式</p><p>$$\begin{equation}x_1\boldsymbol{\alpha}_1+x_2\boldsymbol{\alpha}_2+\cdots+x_n\boldsymbol{\alpha}_n&#x3D;\boldsymbol0\label{Eq.3}\end{equation}$$</p><p>记$N(\boldsymbol A)$为齐次线性方程组$\eqref{Eq.1}$的全体解向量所构成的解集合，称$N(\boldsymbol A)$的基$\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r}$为方程组$\eqref{Eq.1}$的<strong>基础解系</strong></p><p><strong>定理5.2</strong> 齐次线性方程组$\eqref{Eq.1}$的解集合$N(\boldsymbol A)$是向量空间，并且$N(\boldsymbol A)$的维数是$n-R(\boldsymbol A)$.</p><blockquote><p>该定理包含两个部分：<mark>$N(\boldsymbol A)$是向量空间</mark>；<mark>$N(\boldsymbol A)$的维数是$n-R(\boldsymbol A)$</mark>。</p><p><mark>先证明$N(\boldsymbol A)$是向量空间（通过定义），再去找它的一个基（由此可以求出它的维数）</mark></p><p>从书上证明过程中引发的几个问题来整理：</p><p>$Q1$. 为什么能设左上角$r$阶子式不为$0$？</p><p>$A1$. 任何矩阵都能由初等行变化变为行阶梯矩阵，对应到线性方程组上即为<strong>方程加减和扩大缩小</strong>，不改变其解，此时可以假定第$i$行的主元就在第$i$列（如果主元不在，则可交换两列，此时只改变的解的顺序，不改变解集的维数）<s>写的都是些什么呀（唔，这个定理记住应该就行了吧）</s></p><p>$Q2$. 为什么要/能给 $x_{r+1},x_{r+2},\cdots,x_{n}$ 一组值？</p><p>$A2$. 因为此时只将 $x_1,x_2,\cdots,x_r$ 视为变量，而证明这个定理的最终方式是<strong>找到一个基</strong>，其实就是找到<strong>一些线性无关的向量</strong>，而我们要证明维数是$n-r$，所以前$r$个变量可以不重要（由$P119$推论$4.1$知，只要保证后面$n-r$个分量构成的向量组线性无关，则加上前$r$个分量也无所谓）。方程$(7)$的作用就是保证在这种 $x_{r+1},x_{r+2},\cdots,x_ {n}$ 取值之下$x_1,x_2,\cdots,x_r$存在。由此使得构造的 $\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r}$ 线性无关</p></blockquote><blockquote><p>$Q3$. 为什么$\boldsymbol \eta$要那样构造？</p><p>$A3$. 其实在证明<strong>如果两个解的后$n-r$个分量相同，则前$r$个分量也相同</strong>，最终的结论应当是 $L(\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r})&#x3D;N(\boldsymbol A) $ ，一个解最后的$n-r$个分量已经确定了 $\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r}$ 表示这个解的形式，所以最后$n-r$个分量包括了向量组 $\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_ {n-r}$ 所有的表示，此时我们必须要让前$r$个分量能被后$n-r$个分量表示，否则结论将不成立。联想到之前的步骤：当 $x_{r+1},x_{r+2},\cdots,x_n$ 选定时，由$\rm Cramer$法则知 $x_1,x_2,\cdots,x_r$ 唯一，与之前的想法一致。</p><p>相对来说，书上提到的另一种由秩证明线性相关的方式更好想到</p></blockquote><p><strong>推论</strong> 设$\boldsymbol A$是$m\times n$矩阵，$\boldsymbol X&#x3D;(x_1, x_2, \cdots, x_n)&#39;$，则</p><p>​    (1) $\boldsymbol{AX}&#x3D;\boldsymbol0$有唯一解$\Leftrightarrow$$R(\boldsymbol A)$等于未知数个数$n\Leftrightarrow\boldsymbolA$为列满秩矩阵</p><p>​    (2) $\boldsymbol{AX}&#x3D;\boldsymbol0$有无穷多解（有非零解）$\Leftrightarrow R(\boldsymbol A)$小于未知数的个数</p><p>​    (3) 当$r&#x3D;R(\boldsymbol A)&lt;n$时，设$\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r}$是$N(\boldsymbol A)$的基，则$\boldsymbol{AX}&#x3D;\boldsymbol{0}$的解空间可以表示成</p><p>$$N(\boldsymbol A)&#x3D;\{\boldsymbol X | \boldsymbol X&#x3D;k_1\boldsymbol\xi_1+k_2\boldsymbol\xi_2+\cdots+k_{n-r}\boldsymbol\xi_{n-r};k_1,k_2,\cdots,k_{n-r}\in \boldsymbol F\}$$</p><p>方程组的通解可以表示成</p><p>$$\boldsymbol X&#x3D;k_1\boldsymbol\xi_1+k_2\boldsymbol\xi_2+\cdots+k_{n-r}\boldsymbol\xi_{n-r}$$</p><p>当$R(\boldsymbol A)&#x3D;n$时，$N(\boldsymbol A)&#x3D;{\boldsymbol 0}$，此时方程组没有基础解系；当$R(\boldsymbol A)&lt;n$时…（书上有，不想敲了…)</p><h2 id="非齐次线性方程组解的结构"><a class="markdownIt-Anchor" href="#非齐次线性方程组解的结构"></a> 非齐次线性方程组解的结构</h2><p>设有非齐次线性方程组</p><p>$$\begin{equation}\left\{\begin{gathered}a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n&#x3D;b_1\\a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n&#x3D;b_2\\\cdots\cdots\cdots\cdots\\a_{m1}x_1+a_{m2}x_2+\cdots+a_{mn}x_n&#x3D;b_m\\\end{gathered}\right.\label{Eq.4}\end{equation}$$</p><p>称齐次线性方程组</p><p>$$\begin{equation}\left\{\begin{gathered}a_{11}x_1+a_{12}x_2+\cdots+a_{1n}x_n&#x3D;0\\a_{21}x_1+a_{22}x_2+\cdots+a_{2n}x_n&#x3D;0\\\cdots\cdots\cdots\cdots\\a_{m1}x_1+a_{m2}x_2+\cdots+a_{mn}x_n&#x3D;0\\\end{gathered}\right.\label{Eq.5}\end{equation}$$</p><p>为非齐次线性方程组$\eqref{Eq.4}$的<strong>导出组</strong></p><p><strong>定理5.3</strong> 方程组$\eqref{Eq.4}$与方程组$\eqref{Eq.5}$的解向量满足</p><p>​(1). 若$\boldsymbol \eta_1,\boldsymbol \eta_2$都是$\eqref{Eq.4}$的解，则$\boldsymbol\eta_1-\boldsymbol\eta_2$是$\eqref{Eq.5}$</p><p>​(2). 若$\boldsymbol\eta$是$\eqref{Eq.4}$的解，$\boldsymbol\xi$是$\eqref{Eq.5}$的解，则$\boldsymbol X&#x3D;\boldsymbol\xi+\boldsymbol\eta$还是$\eqref{Eq.4}$的解</p><blockquote><p>​这个定理表明，非齐次线性方程组两个解的差一定是其导出组的解，而其中一个解加上其导出组的解一定也是这个非齐次线性方程组的解，所以才会有后面推论</p></blockquote><p><strong>推论5.3</strong> 设$\boldsymbol A$是方程组$\eqref{Eq.4}$的系数矩阵，$\boldsymbol B$是$\eqref{Eq.4}$的增广矩阵，$n$是$\eqref{Eq.4}$的未知数个数，则</p><p>​(1). 方程组$\eqref{Eq.4}$有唯一解$\Leftrightarrow R(\boldsymbol A)&#x3D;R(\boldsymbol B)&#x3D;n$</p><p>​(2). 方程组$\eqref{Eq.4}$有无穷多解$\Leftrightarrow R(\boldsymbol A)&#x3D;R(\boldsymbol B)&lt;n$</p><blockquote><p>​由于方程组$\eqref{Eq.5}$有非零解，所以可以通过定理5.3(2)构建出无数个解</p></blockquote><p>​(3). 当$r&#x3D;R(\boldsymbol A)&#x3D;R(\boldsymbol B)&lt;n$时，设$\boldsymbol\xi_1,\boldsymbol\xi_2,\cdots,\boldsymbol\xi_{n-r}$是方程组$\eqref{Eq.5}$的一个基础解系，$\boldsymbol\eta^*$是方程组$\eqref{Eq.4}$的一个特解，则$\eqref{Eq.4}$的通解可以表示成</p><p>$$\boldsymbol X&#x3D;\boldsymbol\eta^*+k_1\boldsymbol\xi_1+k_2\boldsymbol\xi_2+\cdots+k_{n-r}\boldsymbol\xi_{n-r}$$</p><blockquote><p>实际上就是<strong>非齐次线性方程组的特解+齐次线性方程组的解</strong>的形式</p><p>解非齐次线性方程组的一般解法即为</p><ol><li>求出其一个特解</li><li>求出其导出组的一个基础解系，构造方法可以是定理5.2证明过程中的构造方法</li><li>用特解和基础解系表示通解</li></ol></blockquote><h3 id="课本例题里还有些东西"><a class="markdownIt-Anchor" href="#课本例题里还有些东西"></a> 课本例题里还有些东西</h3><p><strong>例$3$</strong> 如果$\boldsymbol\eta_1,\boldsymbol\eta_2,\cdots,\boldsymbol\eta_m$都是非齐次线性方程组$\boldsymbol{AX}&#x3D;\boldsymbol\beta$的解，那么$\displaystyle\frac{\displaystyle\sum^m_{i&#x3D;1}k_i\boldsymbol\eta_i}{\displaystyle\sum^{m}_{i&#x3D;1}k_i}$也是$\boldsymbol{AX}&#x3D;\boldsymbol\beta$的解</p><blockquote><p>有啥用？做作业的时候你就发现了，好吧可以方便求基础解系</p><p>可以去对应一下作业里$P57-4$（注意不同人作业可能版本不同）</p></blockquote><p>~ <s>哎好像这种东西没人看，下次想摆烂了（直接拍书行不行.?</s> ~</p>]]></content>
    
    
    <summary type="html">一些对于线性代数教材上，线性方程组解的结构章节的看法</summary>
    
    
    
    <category term="线性代数" scheme="https://syhanjin.moe/categories/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/"/>
    
    
    <category term="线性代数" scheme="https://syhanjin.moe/tags/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0/"/>
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="https://syhanjin.moe/20241128/f535cb882fd7/"/>
    <id>https://syhanjin.moe/20241128/f535cb882fd7/</id>
    <published>2024-11-28T17:07:22.000Z</published>
    <updated>2024-11-28T19:19:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>如你所见，这是一篇<code>Hello World</code></p><p>这篇文章标志着我完成了该博客系统的配置，本博客</p><ul><li>由<a href="https://hexo.io/">Hexo</a>驱动</li><li>使用<a href="https://github.com/D-Sketon/hexo-theme-reimu">Reimu</a>主题</li></ul><p>之后会对博客头图，封面图等进行进一步的配置，并将不定时更新，更新内容可能包括但不限于：</p><ul><li>高数、线代等数理基础</li><li>三大天书——数电模电电路的学习笔记</li><li><code>STM32</code>电控相关知识</li><li>硬件相关知识</li></ul>]]></content>
    
    
    <summary type="html">如你所见，这是一篇Hello World，这篇文章标志着我完成了该博客系统的配置</summary>
    
    
    
    
  </entry>
  
</feed>
