<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/atom-style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://molqzone.vercel.app/</id>
    <title>molqzone</title>
    <updated>2026-02-18T16:23:15.613Z</updated>
    <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
    <author>
        <name>molqzone</name>
        <uri>https://molqzone.vercel.app/</uri>
    </author>
    <link rel="alternate" href="https://molqzone.vercel.app/"/>
    <link rel="self" href="https://molqzone.vercel.app/atom.xml"/>
    <subtitle>Retypeset is a static blog theme based on the Astro framework. Inspired by Typography, Retypeset establishes a new visual standard and reimagines the layout of all pages, creating a reading experience reminiscent of paper books, reviving the beauty of typography. Details in every sight, elegance in every space.</subtitle>
    <rights>Copyright © 2026 molqzone</rights>
    <entry>
        <title type="html"><![CDATA[Eigen 与 CMSIS-DSP 在 Cortex-M4F 上的矩阵性能实测]]></title>
        <id>https://molqzone.vercel.app/posts/eigen-vs-cmsis-dsp/</id>
        <link href="https://molqzone.vercel.app/posts/eigen-vs-cmsis-dsp/"/>
        <updated>2026-02-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[基于 STM32F407ZGT6 的实机测试，对 Eigen 与 CMSIS-DSP 在矩阵乘法/求逆上的性能进行高信息量编译条件对比，并提供可复现数据与图表。]]></summary>
        <content type="html"><![CDATA[<h2>前言</h2>
<p>在嵌入式 MCU 开发中，矩阵运算是处理信号处理、控制算法及姿态解算等任务的核心。如何在高资源受约束的硬件上实现更高效的矩阵计算，始终是开发者关注的焦点。</p>
<p>在 C 语言生态中，CMSIS-DSP 库作为 ARM 官方提供的标准，针对 Cortex-M 系列内核进行了深度指令级优化（如 SIMD、FPU 加速）；而在 C++ 领域，Eigen 则以其高度抽象的模板元编程技术和优雅的语法享誉业界。那么，当“极致的硬件优化”遇上“高度的代码抽象”，两者在性能表现上孰优孰劣？本文旨在通过在 Cortex-M4 平台上的实测数据，深度对比这两个设计哲学迥异的数学库，探讨它们在实际嵌入式场景下的效率差异。</p>
<h2>实验环境</h2>
<ul>
<li><strong>硬件平台</strong>：STM32F407ZGT6（Cortex-M4F）</li>
<li><strong>编译器</strong>：ST ARM Clang 19.1.6+st.10</li>
<li><strong>数学库版本</strong>：Eigen 3.4.90 / CMSIS-DSP v1.17.0-4-g334ed5891</li>
</ul>
<h3>内核视角</h3>
<p>Cortex-M4F 具备单精度 FPU，并支持以 MAC 为代表的 DSP 指令路径。放到矩阵运算语境中，两种路线的差异会非常直观：</p>
<ul>
<li>CMSIS-DSP 的 <code>arm_mat_mult_f32</code> 更接近“人工精调算子”，依赖手写内核、循环展开与寄存器级调度。</li>
<li>Eigen 更接近“模板表达 + 编译器兑现”，依赖模板展开后由编译器完成内联、常量传播与指令选择。</li>
</ul>
<p>因此，这组基准测试本质上是在比较<strong>手工优化上限</strong>与<strong>编译器自动优化上限</strong>在 Cortex-M4F 上的分界点。</p>
<h3>公平性控制</h3>
<ul>
<li>两侧矩阵在内存里的存放顺序统一行主序</li>
<li>输入/输出矩阵全静态分配，避免动态分配抖动</li>
<li>DWT 周期计数，扣除计时本底</li>
<li>测量关键区使用临界区，降低中断干扰</li>
</ul>
<h3>核心计算</h3>
<h4>矩阵生成</h4>
<p>矩阵样本使用线性同余发生器（LCG）<code>state = state * 1664525 + 1013904223</code> 生成伪随机序列，再取高 24 bit 映射到 <code>[-1, 1)</code> 浮点区间。为保证求逆测试稳定可执行，<code>FillInvertibleMatrix</code> 会在随机矩阵基础上对角线加 <code>n</code>，提升对角占优特性，降低奇异矩阵出现概率。</p>
<pre><code>std::uint32_t LcgRng::NextU32()
{
  state_ = state_ * 1664525U + 1013904223U;
  return state_;
}

float LcgRng::NextFloatSigned()
{
  const std::uint32_t u24 = (NextU32() &gt;&gt; 8U) &amp; 0x00FFFFFFU;
  const float unit = static_cast&lt;float&gt;(u24) * (1.0F / 16777216.0F);
  return (unit * 2.0F) - 1.0F;  // [-1, 1)
}

void LcgRng::FillMatrix(float* dst, std::size_t n)
{
  const std::size_t elements = n * n;
  for (std::size_t i = 0; i &lt; elements; ++i)
  {
    dst[i] = NextFloatSigned();
  }
}

void LcgRng::FillInvertibleMatrix(float* dst, std::size_t n)
{
  FillMatrix(dst, n);
  for (std::size_t row = 0; row &lt; n; ++row)
  {
    dst[row * n + row] += static_cast&lt;float&gt;(n);  // 对角增强，提升可逆性
  }
}

</code></pre>
<h4>Eigen 实现</h4>
<p>本文构建统一使用 <code>EIGEN_NO_DEBUG</code> 与 <code>EIGEN_MPL2_ONLY</code>，避免调试断言与无关特性对结果造成干扰。</p>
<pre><code>// 针对不同的矩阵大小，3/4/6/8/10/16/32 走 fixed, 64 走 dynamic
template &lt;int N&gt;
void EigenMultiplyFixed(const float* a, const float* b, float* out)
{
  using MatrixType = Eigen::Matrix&lt;float, N, N, Eigen::RowMajor&gt;;
  const Eigen::Map&lt;const MatrixType&gt; matrix_a(a);
  const Eigen::Map&lt;const MatrixType&gt; matrix_b(b);
  Eigen::Map&lt;MatrixType&gt; matrix_out(out);
  matrix_out.noalias() = matrix_a * matrix_b;
}

void EigenMultiplyDynamic(std::size_t n, const float* a, const float* b, float* out)
{
  using DynamicMatrix = Eigen::Matrix&lt;float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor&gt;;
  const Eigen::Map&lt;const DynamicMatrix&gt; matrix_a(a, static_cast&lt;Eigen::Index&gt;(n),
                                                  static_cast&lt;Eigen::Index&gt;(n));
  const Eigen::Map&lt;const DynamicMatrix&gt; matrix_b(b, static_cast&lt;Eigen::Index&gt;(n),
                                                  static_cast&lt;Eigen::Index&gt;(n));
  Eigen::Map&lt;DynamicMatrix&gt; matrix_out(out, static_cast&lt;Eigen::Index&gt;(n),
                                       static_cast&lt;Eigen::Index&gt;(n));
  matrix_out = matrix_a.lazyProduct(matrix_b);
}

template &lt;int N&gt;
void EigenInverseFixed(const float* src, float* out)
{
  using MatrixType = Eigen::Matrix&lt;float, N, N, Eigen::RowMajor&gt;;
  const Eigen::Map&lt;const MatrixType&gt; matrix_src(src);
  Eigen::Map&lt;MatrixType&gt; matrix_out(out);
  matrix_out = matrix_src.inverse();
}
</code></pre>
<h4>CMSIS-DSP 实现</h4>
<p>CMSIS-DSP 使用了 <code>ARM_MATH_LOOPUNROLL</code> 宏，以便在 M4F 平台上更激进地展开关键循环。</p>
<pre><code>arm_status RunCmsisMultiply(std::size_t n, const float* a, const float* b, float* out)
{
  arm_matrix_instance_f32 matrix_a{};
  arm_matrix_instance_f32 matrix_b{};
  arm_matrix_instance_f32 matrix_out{};

  arm_mat_init_f32(&amp;matrix_a, static_cast&lt;std::uint16_t&gt;(n), static_cast&lt;std::uint16_t&gt;(n),
                   const_cast&lt;float*&gt;(a));
  arm_mat_init_f32(&amp;matrix_b, static_cast&lt;std::uint16_t&gt;(n), static_cast&lt;std::uint16_t&gt;(n),
                   const_cast&lt;float*&gt;(b));
  arm_mat_init_f32(&amp;matrix_out, static_cast&lt;std::uint16_t&gt;(n),
                   static_cast&lt;std::uint16_t&gt;(n), out);

  return arm_mat_mult_f32(&amp;matrix_a, &amp;matrix_b, &amp;matrix_out);
}

arm_status RunCmsisInverse(std::size_t n, float* src_mutable, float* out)
{
  arm_matrix_instance_f32 matrix_src{};
  arm_matrix_instance_f32 matrix_out{};

  arm_mat_init_f32(&amp;matrix_src, static_cast&lt;std::uint16_t&gt;(n),
                   static_cast&lt;std::uint16_t&gt;(n), src_mutable);
  arm_mat_init_f32(&amp;matrix_out, static_cast&lt;std::uint16_t&gt;(n),
                   static_cast&lt;std::uint16_t&gt;(n), out);

  return arm_mat_inverse_f32(&amp;matrix_src, &amp;matrix_out);
}

</code></pre>
<h3>反汇编抽样验证</h3>
<p>为了避免只看跑分结论，这里补一段与基线一致构建（<code>C1</code>）的反汇编抽样。</p>
<blockquote>
<p><strong>编译参数（摘录）</strong></p>
<p><code>-O3 -flto -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -DNDEBUG</code></p>
</blockquote>
<h4>抽样结果</h4>
<ul>
<li>实验代码中 <code>BenchmarkMath::RunEigenMultiply</code> 和动态乘法路径的 <code>gebp_kernel</code> 里，主序列是 <code>vmul.f32 + vadd.f32</code></li>
<li>在 Eigen 主热点路径中，没有看到直接的 <code>vmla.f32</code> 指令</li>
<li><code>fmaf</code> 函数本身是 <code>vfma.f32</code>（地址 <code>0x080007fe</code>），但在这个构建里属于辅助调用路径，而不是主展开内核形态</li>
<li>指令计数（同一抽样范围）：
<ul>
<li><code>RunEigenMultiply</code>：<code>vmul.f32=191</code>、<code>vadd.f32=159</code>、<code>vfma.f32=0</code>、<code>vmla.f32=0</code></li>
<li><code>gebp_kernel</code>：<code>vmul.f32=45</code>、<code>vadd.f32=49</code>、<code>vfma.f32=0</code>、<code>vmla.f32=0</code></li>
</ul>
</li>
</ul>
<p>这意味着本文结论应理解为：<strong>在当前编译器版本和参数下</strong>，Eigen 的矩阵乘法主要依赖编译器生成的 <code>vmul+vadd</code> 序列，而不是稳定的 <code>vmla/vfma</code> 直出。这也正是 ARM Clang 版本变化会带来波动的原因之一。</p>
<h2>编译条件</h2>
<table>
<thead>
<tr>
<th>用途</th>
<th>编译参数</th>
</tr>
</thead>
<tbody>
<tr>
<td>基线发布</td>
<td><code>-O3 -flto -DNDEBUG</code></td>
</tr>
<tr>
<td>去跨单元优化</td>
<td><code>-O3 -DNDEBUG</code>（no LTO）</td>
</tr>
<tr>
<td>激进浮点优化</td>
<td><code>-O3 -flto -ffast-math -DNDEBUG</code></td>
</tr>
<tr>
<td>禁循环展开</td>
<td><code>-O3 -flto -fno-unroll-loops -DNDEBUG</code></td>
</tr>
<tr>
<td>调试友好</td>
<td><code>-Og -g -flto -DNDEBUG</code></td>
</tr>
<tr>
<td>尺寸极限</td>
<td><code>-Oz -flto -DNDEBUG</code></td>
</tr>
</tbody>
</table>
<blockquote>
<p><code>-Ofast -flto -DNDEBUG</code> 与 <code>-O3 -flto -ffast-math -DNDEBUG</code> 现象几乎重合，因此没有纳入统计</p>
</blockquote>
<h2>可视化</h2>
<h3>测试流程</h3>
<pre><code>flowchart LR
  A[编译条件组合] --&gt; B[矩阵尺寸集合]
  B --&gt; C[矩阵数据生成]
  C --&gt; D[Warm-up 预热]
  D --&gt; E[Eigen DWT 计时]
  D --&gt; F[CMSIS-DSP DWT 计时]
  E --&gt; G[误差校验与异常剔除]
  F --&gt; G
  G --&gt; H[3轮汇总与几何均值]
  H --&gt; I[可视化与拐点分析]
</code></pre>
<h3>基线绝对周期图（<code>-O3 -flto -DNDEBUG</code>）</h3>
<p><img src="./assets/eigen-vs-cmsis-dsp-images/baseline_inv_cycles.png" alt="baseline inv cycles" /></p>
<p><img src="./assets/eigen-vs-cmsis-dsp-images/baseline_mul_cycles.png" alt="baseline mul cycles" /></p>
<h3>单图概览</h3>
<p><img src="./assets/eigen-vs-cmsis-dsp-images/overview_high_info_no_c.png" alt="overview high info no c" /></p>
<h3>不同编译条件下的矩阵求逆曲线（Eigen/CMSIS）</h3>
<p><img src="./assets/eigen-vs-cmsis-dsp-images/selected_inv_eigen_over_cmsis.png" alt="selected inv eigen over cmsis" /></p>
<h3>不同编译条件下的矩阵乘法曲线（Eigen/CMSIS）</h3>
<p><img src="./assets/eigen-vs-cmsis-dsp-images/selected_mul_eigen_over_cmsis.png" alt="selected mul eigen over cmsis" /></p>
<h2>总结</h2>
<p>3 轮汇总后可以得到一个稳定结论：<strong>主拐点出现在 <code>N = 8</code> 附近</strong>（<code>-Oz</code> 例外更早，在 <code>N = 3</code> 即接近持平）。</p>
<table>
<thead>
<tr>
<th>编译参数</th>
<th>几何均值 (E/C)</th>
<th>乘法首次达到 <code>E/C &gt;= 1</code> 的尺寸</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-O3 -flto -DNDEBUG</code></td>
<td>0.754</td>
<td>8</td>
</tr>
<tr>
<td><code>-O3 -DNDEBUG</code>（no LTO）</td>
<td>0.769</td>
<td>8</td>
</tr>
<tr>
<td><code>-O3 -flto -ffast-math -DNDEBUG</code></td>
<td>0.731</td>
<td>8</td>
</tr>
<tr>
<td><code>-O3 -flto -fno-unroll-loops -DNDEBUG</code></td>
<td>0.740</td>
<td>8</td>
</tr>
<tr>
<td><code>-Og -g -flto -DNDEBUG</code></td>
<td>0.810</td>
<td>8</td>
</tr>
<tr>
<td><code>-Oz -flto -DNDEBUG</code></td>
<td>0.957</td>
<td>3</td>
</tr>
</tbody>
</table>
<p>可见：在 Cortex-M4F 上，Eigen 与 CMSIS-DSP 的优劣主要由矩阵规模与优化目标共同决定。对于 <code>N &lt; 8</code> 的微小矩阵，Eigen 更容易依靠模板展开、全内联和常量传播摊薄调用与索引开销；当 <code>N &gt;= 8</code> 进入常规矩阵区间后，CMSIS-DSP 的内核级调度与手写算子优势开始稳定体现，且在求逆场景中更明显；而在 <code>-Oz</code> 这类尺寸优先策略下，两者差距会显著收敛，本质上是通过牺牲峰值性能换取代码体积收益。落到算法选型上，可<strong>优先将姿态解算、标定这类 <code>3x3~6x6</code> 小矩阵场景交给 Eigen，将中等状态量 EKF、RLS 等 <code>8</code> 阶以上且求逆频繁的场景交给 CMSIS-DSP</strong>；若同一工程同时覆盖两类热点，按路径混用并在目标编译参数下复测通常更稳妥。</p>
<h2>One More Thing: 产物大小</h2>
<h3>对比口径</h3>
<ul>
<li>固定编译参数：<code>-O3 -flto -DNDEBUG</code>（同一工程、同一链接脚本）</li>
<li>仅切换两个开关：<code>BENCHMARK_ENABLE_EIGEN</code>、<code>BENCHMARK_ENABLE_CMSIS</code></li>
<li><code>.text</code> 用于观察代码体积变化</li>
<li><code>Flash 估算 = .text + .rodata + .data</code></li>
<li><code>RAM 静态占用 = .data + .bss</code></li>
<li>统计工具：<code>starm-size -A</code></li>
</ul>
<h3>实验结果</h3>
<table>
<thead>
<tr>
<th>组合</th>
<th>Eigen</th>
<th>CMSIS-DSP</th>
<th><code>.text</code> (B)</th>
<th>Flash 估算 (B)</th>
<th>RAM 静态占用 (B)</th>
<th>相对 baseline 增量（Flash / RAM）</th>
</tr>
</thead>
<tbody>
<tr>
<td>baseline（两库都关闭）</td>
<td>OFF</td>
<td>OFF</td>
<td>59264</td>
<td>60116</td>
<td>63628</td>
<td>0 / 0</td>
</tr>
<tr>
<td>仅 Eigen</td>
<td>ON</td>
<td>OFF</td>
<td>90528</td>
<td>91380</td>
<td>80032</td>
<td>+31264 / +16404</td>
</tr>
<tr>
<td>仅 CMSIS-DSP</td>
<td>OFF</td>
<td>ON</td>
<td>66128</td>
<td>66980</td>
<td>96396</td>
<td>+6864 / +32768</td>
</tr>
<tr>
<td>Eigen + CMSIS-DSP</td>
<td>ON</td>
<td>ON</td>
<td>99808</td>
<td>100660</td>
<td>112800</td>
<td>+40544 / +49172</td>
</tr>
</tbody>
</table>
<p>这个口径下可见，Eigen 在本项目里的主要开销更偏向 Flash（模板实例化导致 <code>.text</code> 增量较大），CMSIS-DSP 的主要开销更偏向静态 RAM（本工程配置下 <code>.bss</code> 增量更明显）。两库同时启用时，占用接近两者增量叠加。</p>
<blockquote>
<p>这里的 RAM 增量是“当前 benchmark 固件实现”的总增量，包含库函数可达后被保留的静态工作缓冲区，不等同于“库源码本体独立打包大小”。</p>
</blockquote>
]]></content>
        <author>
            <name>molqzone</name>
            <uri>https://molqzone.vercel.app/</uri>
        </author>
        <published>2026-02-17T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[手把手教你配置 STM32CubeIDE for VSCode 下基于 OpenOCD 的调试配置]]></title>
        <id>https://molqzone.vercel.app/posts/stm32-vscode-openocd-debug/</id>
        <link href="https://molqzone.vercel.app/posts/stm32-vscode-openocd-debug/"/>
        <updated>2025-11-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[如果你在使用 STM32CubeIDE for VSCode 插件，可能会发现它默认的烧录工具只完美支持正版 STLink。市面上几十块钱的仿制 STLink 经常出现连接不稳定、烧录失败、甚至无法识别的"玄学"问题。]]></summary>
        <content type="html"><![CDATA[<p>如果你在使用 STM32CubeIDE for VSCode 插件，可能会发现它默认的烧录工具只完美支持正版 STLink。市面上几十块钱的仿制 STLink 经常出现连接不稳定、烧录失败、甚至无法识别的"玄学"问题。</p>
<p>为了彻底解决这个问题，我们推荐使用 DAPLink 调试器配合 OpenOCD。这不仅能解决烧录问题，还能让你拥有一套适用于几乎所有 ARM 芯片（不仅限于 STM32）的通用开发环境。本教程将以 STM32CubeIDE for VSCode 生成的项目为基础，教你如何"外挂" OpenOCD 来接管烧录和调试工作。</p>
<blockquote>
<p>本教程虽然以 STM32CubeIDE for VSCode 为例，但同样适用于任何基于 OpenOCD 的工具链配置，只需要稍作修改。</p>
</blockquote>
<blockquote>
<p>[!warning]</p>
<p>本教程涉及 Scoop 的安装。安装 Scoop 前需要检查自己的用户文件夹是否含有中文，如果有中文会<strong>导致安装后无法正常使用，且卸载复杂</strong>的后果。可以修改默认用户文件夹或重装系统，建议重装系统。</p>
</blockquote>
<h2>硬件准备</h2>
<h3>选购 DAPLink</h3>
<p>市场上 5-20 元的 DAPLink 基本就可以满足日常使用需求。可以买一个淘宝九块九的 <strong>PowerWriter 2 Lite</strong>，性价比高。</p>
<blockquote>
<p>[!tip]</p>
<p><strong>Why DAPLink？</strong></p>
<p><strong>DAPLink</strong> 是 ARM 官方推出的开源调试协议。与 STLink（仅支持 STM32）不同，DAPLink 几乎可以覆盖所有 ARM 内核的设备，通用性更强。</p>
</blockquote>
<h3>检查驱动 (WinUSB)</h3>
<p>大多数 DAPLink 插上就能用。但为了确保 OpenOCD 能通过 <code>libusb</code> 访问它，建议检查一下：</p>
<ol>
<li>插上调试器。</li>
<li>右键此电脑，在右键菜单选择 <code>管理</code>。打开设备管理器，看是否有 <code>CMSIS-DAP</code> 或 <code>WinUSB</code> 设备。</li>
</ol>
<h2>安装 OpenOCD</h2>
<p>OpenOCD 可以通过 Scoop 安装或者在 GitHub Release 直装（直装后建议添加 OpenOCD 到环境变量）。</p>
<p>推荐使用 <a href="https://scoop.sh/">Scoop</a> 安装 OpenOCD，方便后续的维护</p>
<ol>
<li>在开始菜单搜索 <code>PowerShell</code>，启动 PoweShell</li>
<li>安装 Scoop</li>
</ol>
<pre><code>Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
</code></pre>
<ol>
<li>安装 OpenOCD</li>
</ol>
<pre><code>scoop install openocd
</code></pre>
<ol>
<li>安装完成后，检查 OpenOCD 是否正确安装</li>
</ol>
<pre><code>openocd --version
</code></pre>
<p>若显示版本号说明安装成功。如：</p>
<pre><code>Open On-Chip Debugger 0.12.0 (2023-01-14-23:37)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
</code></pre>
<blockquote>
<p>[!tip]</p>
<p>你的电脑无法直接对芯片说："把这个程序写进你的 Flash 里"，所以我们需要引入一个程序用来把用户的调试操作转换成芯片能听懂的指令，这个程序在这儿教程中就是 <strong>OpenOCD</strong>。它一边听懂电脑发出的命令（GDB协议），一边控制 USB 调试器发出高低电平（JTAG/SWD协议），像"牵线木偶"一样控制芯片。 它的核心价值就是用一套软件，桥接不同调试器（DAPLink/STLink）与不同芯片，让烧录和调试变得通用。</p>
</blockquote>
<h2>配置烧录功能</h2>
<p>VSCode 本身只是一个编辑器，并不懂烧录，我们需要写一个"任务说明书"告诉它该怎么做。</p>
<h3>创建烧录任务</h3>
<ol>
<li>在VSCode中打开你的项目</li>
<li>按 <code>Ctrl + Shift + P</code>，输入 <code>Tasks: Configure Task</code></li>
<li>选择 <code>Create tasks.json file from template</code> → <code>Others</code></li>
<li><strong>完全替换</strong>文件内容如下（注意看注释修改芯片型号）:</li>
</ol>
<pre><code>{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Flash Target",
            "type": "shell",
            "command": "openocd",
            "args": [
                "-f", "interface/cmsis-dap.cfg", // 使用 DAPLink
                "-f", "target/stm32f1x.cfg", // 根据你的芯片修改！例如 stm32f4x.cfg, stm32h7x.cfg
                // 下面这行代码会自动获取 STM32CubeIDE 插件编译出的 elf 文件路径,如果环境不是基于 STM32CubeIDE for VSCode 则需要将环境变量替换为具体可执行文件位置，具体可以问 AI
                "-c", "program ${command:st-stm32-ide-debug-launch.get-projects-binary-from-context1} verify reset exit"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always"
            }
        }
    ]
}
</code></pre>
<h3>开始烧录</h3>
<p>配置完成后，按 <code>Ctrl + Shift + P</code>，输入 "Run Task"，选择 "Flash Target" 即可开始烧录。</p>
<h3>添加一键烧录按钮</h3>
<p>不想每次按快捷键？装个插件做个按钮。</p>
<p><code>Ctrl + Shift + X</code>，打开扩展页，搜索 <code>Task Buttons</code> 并安装</p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/task-buttons-ext.png" alt="VSCode Task Buttons 扩展安装页面" /></p>
<p><code>Ctrl + ,</code> 打开设置页，在 <code>Extensions</code> -&gt; <code>VsCodeTaskButtons</code> 找到 <code>Tasks</code>，选择 <code>Edit in settings.json</code></p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/task-button-settings.png" alt="VSCode Task Buttons 设置页面配置" /></p>
<p>在 <code>settings.json</code> 追加：</p>
<pre><code>    "VsCodeTaskButtons.tasks": [
        {
            "label": "$(play) Flash Target",
            "task": "Flash Target",
            "tooltip": "Flash the ordered MCU"
        }
    ],
</code></pre>
<blockquote>
<p>[!tip]</p>
<p><strong>task.json 本质上是一张"自动化任务说明书"</strong></p>
<p>VSCode 只懂得编辑文字。你想让它帮你做点"额外的事"，比如烧录程序，但它自己完全不知道"烧录"是什么。</p>
<p>但是 VSCode 会自己去读 .vscode 文件夹下的 <strong>task.json</strong>。比如我们配置的 task.json 上面详细写着：</p>
<ol>
<li><strong>任务名称</strong>："烧录程序"</li>
<li><strong>具体步骤</strong>："请调用电脑里的 openocd 这个工具，并把这些参数（比如调试器类型、芯片型号、程序文件位置）一字不差地传给它。"</li>
</ol>
<p>所以，task.json 解决的核心问题是──<strong>在 VSCode 这个图形界面里架起一座桥梁，连接友好的图形界面和底层强大的命令行工具</strong>。</p>
<p>如果感兴趣，你可以借助 AI 去了解我们配置的 task.json 上的每一个参数究竟告诉 VSCode 怎么正确执行这个任务。</p>
</blockquote>
<h2>配置调试功能</h2>
<h3>安装 Everything</h3>
<p>可以从微软官方商店搜索，或者使用 WinGet 安装。</p>
<blockquote>
<p><strong>使用 WinGet 安装 Everything</strong></p>
<p>按刚刚的步骤打开 PowerShell，安装 Everything</p>
<pre><code>winget install voidtools.Everything
</code></pre>
</blockquote>
<h3>确认 arm-none-eabi-gdb 位置</h3>
<p>我们需要找到调试器（GDB）的位置：</p>
<ol>
<li>使用 Everything 搜索 <code>arm-none-eabi-gdb.exe</code>，找到路径包含 <code>gnu-tools-for-stm32</code> 的那个结果</li>
<li>通常路径类似：<code>AppData/Local/stm32cube/bundles/gnu-tools-for-stm32/13.3.1+st.9/bin/arm-none-eabi-gdb.exe</code></li>
<li>在目标文件右键，在右键菜单中选择 <code>复制为路径</code>，将路径保存好</li>
</ol>
<p><img src="./assets/stm32-vscode-openocd-debug-images/everything.png" alt="Everything 搜索 arm-none-eabi-gdb.exe 结果" /></p>
<blockquote>
<p>[!tip]</p>
<p><strong>什么是 GDB？</strong></p>
<p>GDB 是连接你的源代码和芯片实际运行的"桥梁调试器"，它负责和程序对话。它解决了在嵌入式开发中"代码如何真正在芯片上执行"的黑盒问题。</p>
<p>当程序在芯片上运行时，GDB 允许你"暂停时间"——查看变量当前值、分析函数调用关系、跟踪程序执行流程。没有 GDB，调试就像蒙着眼睛找错误；有了 GDB，你可以精确观察程序每一步的行为。</p>
<p>简单说：<strong>OpenOCD 让电脑能"接触"到芯片，而 GDB 让开发者能"理解"芯片上正在发生什么。</strong></p>
</blockquote>
<h3>安装 Cortex Debug 扩展</h3>
<p><code>Ctrl + Shift + X</code>，打开扩展页，搜索 <code>Cortex Debug</code> 并安装</p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/cortex-debug-ext.png" alt="VSCode Cortex Debug 扩展安装" /></p>
<p><code>Ctrl + Shift + D</code>，打开运行或调试页，点击 <code>create a launch.json file</code>，这会在你的项目根目录下的 <code>.vscode</code> 文件夹新建一个 <code>launch.json</code>。你也可以自己手动创建。</p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/run-and-debug-create.png" alt="VSCode 创建 launch.json 文件界面" /></p>
<p>点击 <code>create a launch.json file</code> 后，在上方选项选择第一项，然后下拉框选择 <code>Cortex Debug: OpenOCD</code>。或者你也可以打开 <code>launch.json</code> 文件，点击右下角的 <code>Add Configuration...</code>，同样选择 <code>Cortex Debug: OpenOCD</code>。</p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/add-configuration.png" alt="VSCode 添加 Cortex Debug 配置选项" /></p>
<p>补全配置，添加 GDB 路径：</p>
<pre><code>{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "cwd": "${workspaceRoot}",
            "executable": "${command:st-stm32-ide-debug-launch.get-projects-binary-from-context1}",
            "name": "Debug with OpenOCD",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "configFiles": [
            	"interface/cmsis-dap.cfg",
            	"target/stm32f1x.cfg"
            ],
            "searchDir": [],
            "runToEntryPoint": "main",
            "showDevDebugOutput": "none",
            "gdbPath": ".../AppData/Local/stm32cube/bundles/gnu-tools-for-stm32/13.3.1+st.9/bin/arm-none-eabi-gdb" // 填入刚刚找到的路径
        }

    ]
}
</code></pre>
<p>保存配置后，运行与调试页应该会出现 <code>Debug with OpenOCD</code> 选项，点击运行三角形即可开始调试。</p>
<p><img src="./assets/stm32-vscode-openocd-debug-images/run-debug.png" alt="VSCode 调试界面显示 Debug with OpenOCD 选项" /></p>
<blockquote>
<p>[!tip]</p>
<p><strong>什么是 launch.json？</strong></p>
<p>如果说 <code>task.json</code>是"烧录说明书"，那么 <code>launch.json</code>就是"调试说明书"。它告诉 VSCode 如何启动调试会话：设置断点、查看变量、单步执行等。</p>
</blockquote>
<h2>常见问题</h2>
<h3>报错 <code>target not found</code></h3>
<ul>
<li>检查连线（SWDIO, SWCLK, GND, 3.3V）是否接好。</li>
<li>检查 target/stm32f1x.cfg 是否选对了系列（F1, F4, H7 配置文件不同）。</li>
</ul>
<h3>GDB 报错 <code>No such file or directory</code></h3>
<ul>
<li>检查 <code>launch.json</code> 里的 <code>gdbPath</code> 路径是否写错，尤其是斜杠的方向。</li>
</ul>
<h2>参考教程</h2>
<h3>视频教程</h3>
<ul>
<li><a href="https://www.bilibili.com/video/BV1bnnZz2ESg">[STM32 + VS Code] 用 DAPLink + OpenOCD 调试 - XRobot 官方教程 0.2 节</a></li>
<li><a href="https://www.bilibili.com/video/BV1X4XAYiEkV">【Windows】VSCode开发STM32，但是使用cmake+clangd+ninja+arm-gcc，全套开源工具链，编译烧录调试无压力。</a></li>
</ul>
]]></content>
        <author>
            <name>molqzone</name>
            <uri>https://molqzone.vercel.app/</uri>
        </author>
        <published>2025-11-22T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Linux 环境下的 CH32 + LibXR 开发环境搭建]]></title>
        <id>https://molqzone.vercel.app/posts/linux-env-ch32-libxr/</id>
        <link href="https://molqzone.vercel.app/posts/linux-env-ch32-libxr/"/>
        <updated>2025-11-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[笔者最近在研究国产单片机。大家对国产单片机的固有印象可能还停留在一比一复刻 STM32，但是随着国产单片机产业的蓬勃发展，各个国产单片机也在自己的产品中做出了自己的特色，其中沁恒家的 RISC-V 系列单片机我最近比较感兴趣（因为沁恒真的敢送）。]]></summary>
        <content type="html"><![CDATA[<p>笔者最近在研究国产单片机。大家对国产单片机的固有印象可能还停留在一比一复刻 STM32，但是随着国产单片机产业的蓬勃发展，各个国产单片机也在自己的产品中做出了自己的特色，其中沁恒家的 RISC-V 系列单片机我最近比较感兴趣（因为沁恒真的敢送）。</p>
<p>说到沁恒，相信大家对他们家 CH340 这款经典的 USB 转串口芯片并不陌生。而在单片机领域，沁恒同样展现出强大的技术实力，尤其在USB功能方面独树一帜：既有经济实用、集成 USB2.0 接口的 V203 系列，也有搭载高速 480MHz USB PHY 的 V307 系列，为嵌入式开发者提供了丰富的选择。</p>
<p>LibXR 是一个功能强大的跨平台 C++ 开发框架，集成了丰富的外设驱动、数据结构、通信中间件、操作系统封装和数学工具库。它为 CH32 系列单片机提供了一个兼容层，不仅对 CH32 标准库进行了高层次的抽象封装，还修复了原库中的一些已知问题，大大提升了开发效率和代码质量。</p>
<p>本文将详细介绍如何在Linux环境下搭建基于 CH32 单片机和 LibXR 库的完整开发环境，帮助开发者快速上手这一优秀的开发组合。</p>
<h2>环境准备</h2>
<h3>1. 获取项目模板</h3>
<p>LibXR 官方提供了现成的项目模板，支持 CH32V307 和 CH32V203 两种型号，已预配置好构建脚本和调试配置，无需手动设置。</p>
<pre><code># 克隆对应芯片的模板项目
git clone https://github.com/xrobot-org/CH32V307_LibXR_Template.git
# 或者选择 CH32V203 模板
# git clone https://github.com/xrobot-org/CH32V203_LibXR_Template.git

# 初始化 LibXR 子模块
git submodule add https://github.com/Jiu-xiao/libxr
</code></pre>
<h3>2. 安装调试工具链</h3>
<p>CH32 芯片需要专用的调试工具。从<a href="https://www.mounriver.com/download">MounRiver 官网</a>下载 Linux 版工具链：</p>
<ol>
<li>访问下载页面，选择 Linux 平台</li>
<li>在"工具链和调试器"栏目下载 <strong>MRS_Toolchain_Linux_x64_Vxxx.tar.xz</strong></li>
<li>解压到合适的目录（建议 <code>~/Development/</code> 目录下）</li>
</ol>
<p><img src="./assets/linux-env-ch32-libxr-images/mrs-install.png" alt="MRS 工具链下载" /></p>
<pre><code># 创建开发目录并解压工具链
mkdir -p ~/Development/
cd ~/Development/
tar -xf MRS_Toolchain_Linux_x64_Vxxx.tar.xz  # 根据实际文件名调整
</code></pre>
<p><strong>工具链说明：</strong></p>
<ul>
<li><strong>专用 OpenOCD</strong>：用于 CH32 芯片烧录和调试（开源版本无法支持 CH32）</li>
<li><strong>RISC-V Embedded GCC</strong>：沁恒提供的编译器，支持 WCH 扩展指令集，但对 C++ 标准支持不完整（<em>LibXR 官方不推荐此编译器</em>）</li>
</ul>
<h3>3. 编译推荐编译器</h3>
<p>LibXR 推荐使用上游 RISC-V 工具链，具有完整的 C++ 标准支持，可以正常使用 Eigen 等现代 C++ 库。</p>
<h4>3.1 获取源码</h4>
<pre><code>git clone https://github.com/riscv-collab/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
</code></pre>
<h4>3.2 配置编译选项</h4>
<p>针对 CH32 芯片特性进行配置：</p>
<pre><code>./configure --prefix=~/Development/riscv-ch32 \
            --with-arch=rv32imacf_zicsr_zifencei \
            --with-abi=ilp32f
</code></pre>
<p><strong>配置说明：</strong></p>
<ul>
<li><code>--prefix</code>：指定安装目录</li>
<li><code>--with-arch</code>：目标架构，兼容 CH32 指令集（不包含 WCH 私有扩展）</li>
<li><code>--with-abi</code>：应用程序二进制接口，支持单精度浮点</li>
</ul>
<h4>3.3 开始编译</h4>
<pre><code>make -j$(nproc)  # 使用多核编译加速
</code></pre>
<p>编译完成后，工具链将安装到 <code>~/Development/riscv-ch32/</code> 目录。</p>
<h2>VSCode 开发环境配置</h2>
<h3>1. 安装必要插件</h3>
<p>在 VSCode 中安装以下插件：</p>
<ul>
<li><strong>C/C++</strong>：代码智能提示和语法高亮（注意：clangd 对 RISC-V 支持有限）</li>
<li><strong>CMake Tools</strong>：CMake 项目管理和构建</li>
<li><strong>Cortex Debug</strong>：OpenOCD 调试支持</li>
</ul>
<h3>2. 配置编译器</h3>
<ol>
<li>使用快捷键 <code>Ctrl + Shift + P</code></li>
<li>搜索并选择 <code>CMake: Configure</code></li>
<li>在编译器列表中选择刚刚编译的 RISC-V 工具链：<code>/home/keruth/Development/riscv-ch32/bin/riscv32-unknown-elf-gcc</code></li>
</ol>
<h3>3. 配置调试器</h3>
<ol>
<li>打开设置界面（<code>Ctrl + ,</code>）</li>
<li>切换到 <code>Workspace</code> 选项卡</li>
<li>导航至 <code>Extensions</code> → <code>Cortex Debug</code> → <code>External GDB Servers</code></li>
<li>找到 <code>Cortex-debug: Openocd Path</code> 配置项</li>
<li>点击 <code>Edit in settings.json</code>，添加以下配置：</li>
</ol>
<pre><code>{
    "cortex-debug.openocdPath": "/home/keruth/Development/mrs-toolchain/OpenOCD/OpenOCD/bin/openocd"
}
</code></pre>
<h2>验证开发环境</h2>
<h3>1. 编译测试</h3>
<p>在 VSCode 中：</p>
<ol>
<li>打开命令面板（<code>Ctrl + Shift + P</code>）</li>
<li>选择 <code>CMake: Build</code> 或点击状态栏的构建按钮</li>
<li>确认编译过程无错误完成</li>
</ol>
<h3>2. 调试测试</h3>
<ol>
<li>使用 WCHLinkE 连接 CH32 开发板到电脑</li>
<li>打开调试界面（<code>Ctrl + Shift + D</code>）</li>
<li>选择模板项目预设的调试配置</li>
<li>点击运行按钮，确认能够正常烧录和调试</li>
</ol>
<p>如果编译和调试都能正常工作，说明开发环境已经搭建完成，可以开始你的 CH32 + LibXR 开发之旅了！</p>
]]></content>
        <author>
            <name>molqzone</name>
            <uri>https://molqzone.vercel.app/</uri>
        </author>
        <published>2025-11-16T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[STM32H7 HAL 库开发中的 DMA 传输失败问题的解决方案]]></title>
        <id>https://molqzone.vercel.app/posts/stm32h7-dma-transfer-fix/</id>
        <link href="https://molqzone.vercel.app/posts/stm32h7-dma-transfer-fix/"/>
        <updated>2025-07-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[最近我尝试在 STM32H723ZGT6 上使用 STM32CubeMX + CMake + GCC 方案配置 ADC1 的 DMA Circular Mode，发现在我的 STM32CubeMX 的参数配置正确的情况下仍然配置不成功。]]></summary>
        <content type="html"><![CDATA[<h2>引言</h2>
<p>最近我尝试在 STM32H723ZGT6 上使用 STM32CubeMX + CMake + GCC 方案配置 ADC1 的 DMA Circular Mode，发现在我的 STM32CubeMX 的参数配置正确的情况下仍然配置不成功。现象是可以进入 ADC1 对应的 DMA1_Stream0_IRQHandler 中断函数，但是无法进入 HAL_ADC_ConvCpltCallback。经过排查发现系统稳定地在 DMA 中断中报告传输错误（TEIF - Transfer Error Interrupt Flag）。下面是我的错误验证代码：</p>
<pre><code>void DMA1_Stream0_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream0_IRQn 0 */
    if (__HAL_DMA_GET_FLAG(&amp;hdma_adc1, DMA_FLAG_TEIF0_4)) {
        log_e("DMA1 Stream0 transfer error occurred"); // 此处报错
    }
  /* USER CODE END DMA1_Stream0_IRQn 0 */
  HAL_DMA_IRQHandler(&amp;hdma_adc1);
  /* USER CODE BEGIN DMA1_Stream0_IRQn 1 */

  /* USER CODE END DMA1_Stream0_IRQn 1 */
}
</code></pre>
<p>常规的调试手段，如排查 MPU 配置、管理 D-Cache 一致性，乃至验证外设时钟与模拟电源，均无法解决问题。从这个现象可以看出，这个坑并非来自 ADC 或 DMA 外设本身的功能缺陷，而是两者在特定工作模式下，触发了系统级深层次的硬件冲突。幸运的是，最终我在社区的帮助下解决了这一问题。</p>
<h2>问题根源</h2>
<p>这个问题的关键在于 STM32H7 系列并非一个简单的单总线 MCU，而是一个拥有复杂多总线主控和多内存域的 SoC。</p>
<p><img src="./assets/stm32h7-dma-transfer-fix-images/stm32h7-bus-architecture.jpeg" alt="STM32H7 总线架构" /></p>
<p>由上图可见，STM32H7 被分成三国鼎立的局面，分别是 D1 域、D2 域和 D3 域：</p>
<ul>
<li><strong>D1域</strong>：性能核心。包含 CPU 内核和高速 AXI SRAM（始于0x24000000），是 CPU 存取数据的主战场。</li>
<li><strong>D2域</strong>：外设集群。包含 ADC、通用定时器以及 DMA1/DMA2 控制器。</li>
<li><strong>D3域</strong>：辅助系统。包含部分低速外设和一块独立的 SRAM（RAM_D3，始于0x38000000）。</li>
</ul>
<p>一个复杂的多层总线矩阵（Bus Matrix）负责连接所有主控和内存/外设。当一个位于 D2 域的主控（如 DMA1），需要访问 D1 域的从属单元（如 AXI SRAM）时，其访问请求必须经由总线矩阵进行路由和仲裁。</p>
<p>问题的核心即在于此：D1 域的 AXI SRAM 是为 CPU 内核性能最大化而设计的，CPU 对其拥有极高的访问优先权和带宽。当 DMA 控制器作为一个"外部"主控，试图高频地向这块 SRAM 写入数据时，极易与 CPU 的指令操作在总线矩阵层面发生总线竞争，导致无法访问到这部分内存。在这种高负载的跨域访问场景下，硬件仲裁逻辑的复杂性可能导致 DMA 的访问周期被过度延长甚至失败，从而产生总线错误，最终被 DMA 控制器报告为 TEIF。</p>
<h2>解决方案</h2>
<p>常规的 MPU 配置，例如将 AXI SRAM 区域的属性设置为可共享、非缓存，是在策略层面告知系统如何处理该内存。这只能解决访问权限和缓存一致性问题，但无法解决硬件物理拓扑层面固有的总线竞争问题。因此，根治此问题的方案必须从系统架构层面为 DMA 缓冲区重定向内存。</p>
<p>链接器脚本是定义最终可执行文件内存布局的蓝图。它允许开发者精确控制每一个代码段和数据段在物理内存中的位置。默认情况下，所有变量（包括 DMA 缓冲区）都被链接器放置在名为RAM的内存区域，即 D1 域的 AXI SRAM。我们的策略是，将 DMA 缓冲区从高冲突的 D1 域 AXI SRAM，迁移到访问路径更简单、总线竞争更小的 D3 域 RAM_D3。</p>
<h3>第一步：在链接器脚本中声明专用内存段</h3>
<p>STM32CubeMX 会在项目根目录下自动生成一个STM32H723XG_FLASH.ld 文件，且此后生成项目不会再覆盖。我们在文件的 SECTIONS 块中，新增一个输出段定义，将其明确指向RAM_D3物理内存区域。</p>
<pre><code>/* In STM32H723XG_FLASH.ld, inside SECTIONS { ... } */
.ram_d3 (NOLOAD) :
{
  . = ALIGN(4);
  *(.ram_d3)
  *(.ram_d3*)
  . = ALIGN(4);
} &gt;RAM_D3
</code></pre>
<p>此定义创建了一个名为 .ram_d3 的输出段，并规定它应被放置在 MEMORY 块中定义的 RAM_D3 区域。(NOLOAD) 属性指明该段为非初始化数据，无需在启动时从 Flash 加载（适配 DMA 缓冲区）。</p>
<h3>第二步：在 C 代码中应用该内存段</h3>
<p>随后，在 C 代码中定义 DMA 缓冲区时，我们使用 GCC 的 <strong>attribute</strong> 指令，告诉编译器将此特定变量放入我们刚刚创建的输入段中。</p>
<pre><code>#define ADC_BUFFER_SIZE 256

// 使用属性指令将此缓冲区变量精确链接到 .ram_d3 段
ALIGN_32BYTES(static uint16_t adc_dma_buffer[ADC_BUFFER_SIZE]) __attribute__((section(".ram_d3")));
</code></pre>
<p>通过上面两步，我们就成功实现了 DMA 缓冲区的物理内存重定向，规避了总线竞争带来的 TEIF 问题。从前我从来没有修改过甚至没有接触过 .ld 文件，想要用上更强大的主控的代价，也许就是要关注更复杂的细节。</p>
<h3>补充说明</h3>
<p><strong>2025-11-16 补充：</strong></p>
<p>事实上，这个问题只有在 STM32CubeMX 生成的 CMake 环境下才会被触发。根本原因在于 STM32CubeMX 生成的默认链接脚本将所有变量分配到了 D1 域的 AXI SRAM 中，这种默认的内存布局配置直接导致了跨域总线访问冲突，从而引发 DMA 传输错误。在其他开发环境或使用不同链接脚本配置的情况下，此问题可能不会出现。</p>
<h3>验证效果</h3>
<p>使用 OpenOCD 烧录程序，可以看到 RAM_D3 区域的内存占用增加了：</p>
<p><img src="./assets/stm32h7-dma-transfer-fix-images/ram_d3_memory_usage.jpeg" alt="RAM_D3 内存占用" /></p>
<h2>参考</h2>
<ul>
<li><a href="https://docs.xrobot.top/cortex-m/cache.html">高速缓存 | XRobot Docs</a></li>
<li><a href="https://shequ.stmicroelectronics.cn/thread-633344-1-1.html">【经验分享】STM32H7 DMA 传输异常案例分析 - STM32团队 ST意法半导体中文论坛</a></li>
</ul>
]]></content>
        <author>
            <name>molqzone</name>
            <uri>https://molqzone.vercel.app/</uri>
        </author>
        <published>2025-07-29T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[手把手教你移植 CMSIS-DSP 到 STM32CubeMX 生成的 CMake 项目]]></title>
        <id>https://molqzone.vercel.app/posts/cmake-cmsis-dsp-stm32/</id>
        <link href="https://molqzone.vercel.app/posts/cmake-cmsis-dsp-stm32/"/>
        <updated>2025-07-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在使用 STM32 系列单片机进行信号处理的过程中，我们往往会选择 ARM 提供的 CMSIS-DSP 库。本文将带你将 GitHub 上的 CMSIS-DSP 源码加入到 STM32CubeMX 生成的 CMake 项目中。]]></summary>
        <content type="html"><![CDATA[<p>在使用 STM32 系列单片机进行信号处理的过程中，我们往往会选择 ARM 提供的 CMSIS-DSP 库。CMSIS-DSP库涵盖了嵌入式信号处理的大部分常用算法函数，同时针对 Cortex-M 核心做了手工汇编优化，还提供了统一的接口。目前使用 CMSIS-DSP 库有以下几种方案：</p>
<ol>
<li>Keil 项目 + 封装好的 CMSIS-DSP 库 CMSIS-Pack</li>
<li>STM32CubeMX CMake 项目 + Software Components</li>
<li>STM32CubeMX CMake 项目 + CMSIS-DSP 源码</li>
</ol>
<p>其中后两种方案更现代化，可以适配 STM32 for Visual Studio Code、CLion 等现代开发环境。而现在（2025 年 7 月）通过 Software Components 安装的 DSP Library 版本还停留在 v1.4.0，这是 2013 年 1 月发布的 CMSIS-DSP 版本，距今已超过十年。这十年里 ARM 为 CMSIS-DSP 追加了更多Cortex-M 架构的支持，新增了<strong>窗函数</strong>等功能模块。而第三种方案直接从 GitHub 上抓取源码，版本最新，功能最全面。但是需要一些 CMake 配置，没有第二种方案直接快捷。而这篇文章的使命就是带你将 GitHub 上的 CMSIS-DSP 源码加入到 STM32CubeMX 生成的 CMake 项目中。（下图是第二种方法通过 Software Components 配置 CMSIS-DSP）</p>
<p><img src="./assets/cmake-cmsis-dsp-stm32-images/cmsis-dap-in-stm32cubemx.png" alt="cmsis-dap-in-stm32cubemx" /></p>
<h2>第一步：从 GitHub 下载源码并解压</h2>
<p>我们有许多种下载源码的办法，其中每一种都需要科学上网：</p>
<ul>
<li>安装 Git 后在命令行输入（无需解压）：</li>
</ul>
<pre><code>git clone https://github.com/ARM-software/CMSIS-DSP.git
</code></pre>
<ul>
<li>在 Release 页的 Assets 里找到 Source code (zip) 下载</li>
<li>在 Code 窗口选择 Download ZIP</li>
</ul>
<h2>第二步：将源码加入到自己的项目中</h2>
<p>直接将解压下来的文件复制到项目文件夹中。我删除目录的版本号后复制到了项目文件夹下的Drivers文件夹下，下面是我现在的项目结构：</p>
<pre><code>Project/
├── .gitattributes          # Git属性文件，用于定义特定路径的属性
├── .gitignore              # Git忽略文件，指定哪些文件或目录不被版本控制
├── .mxproject              # STM32CubeMX项目文件，存储IDE特定的配置
├── *.ioc       # STM32CubeMX配置文件，核心文件，定义了MCU的引脚、时钟和中间件配置
├── build/                  # 编译输出目录，存放编译生成的目标文件和可执行文件（如.elf, .bin）
├── cmake/                  # 可能包含自定义的CMake脚本或模块
├── CMakeLists.txt          # 主CMake构建脚本，定义了整个项目的构建规则
├── CMakePresets.json       # CMake预设文件，用于配置常用的构建选项
├── Core/                   # 核心代码目录
│   ├── Inc/                # 存放核心代码的头文件 (.h)，例如 main.h, stm32h7xx_it.h
│   └── Src/                # 存放核心代码的源文件 (.c)，例如 main.c, stm32h7xx_it.c
├── Drivers/                # 驱动程序目录
│   ├── CMSIS/              # ARM Cortex微控制器软件接口标准，包含MCU核心访问文件
│   ├── CMSIS-DSP/          # CMSIS提供的数字信号处理库
│   └── STM32*xx_HAL_Driver/ # ST官方提供的硬件抽象层(HAL)驱动库，用于简化对MCU外设的操作
├── LICENSE                 # 项目的开源许可证文件
├── Middlewares/            # 中间件目录，例如FreeRTOS, FatFS, USB等
├── README.md               # 项目说明文件，使用Markdown格式
├── startup_*xx.s   # 启动文件(汇编)，MCU上电后最先执行的代码，用于初始化堆栈和中断向量表
├── *_FLASH.ld    # 链接器脚本(Linker Script)，告诉链接器如何组织代码和数据在内存中的布局
└── USB_DEVICE/             # USB设备库相关文件
</code></pre>
<h2>第三步：将 CMSIS-DSP 目录加入到项目 CMakeLists 中</h2>
<blockquote>
<p>[!WARNING]
STM32CubeMX v6.15.0 前，项目 CMakeLists 中非 User defined 注释下的内容会在生成时被覆盖。v6.15.0 版本更新后，用户可以自由更改 CMakeLists.txt，STM32CubeMX 只会在项目第一次生成时构建 CMakeLists.txt 模板。本教程的操作依赖自由更改 CMakeLists.txt，故请确认自己的 STM32CubeMX 是否为 v6.15.0 及更高版本！</p>
</blockquote>
<p>CMSIS-DSP 根目录下有一个引用了整个库的 CMakeLists.txt，我们需要将其加入到我们的项目构建中：</p>
<pre><code># Add CMSIS-DSP sources
add_subdirectory(Drivers/CMSIS-DSP)
</code></pre>
<p>同时我们需要在 target_link_libraries 中加入 CMSISDSP 库，保证项目链接到 CMSIS-DSP 库</p>
<pre><code># Add linked libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
    stm32cubemx

    # Add user defined libraries
        CMSISDSP
)
</code></pre>
<p>理论上到这里就已经结束了，但是我们此时编译会发现报错：找不到cmsis_compiler.h。这是因为 CMSIS-DSP 依赖 CMSIS-Core，而 CMSIS-Core 是 STM32CubeMX 生成时携带的，就在Drivers/CMSIS里。（CMSIS-Core 的路径可以通过在项目中找哪里有cmsis_compiler.h来找到）如果我们在项目 CMakeLists.txt 加入：</p>
<pre><code># Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
        /Drivers/CMSIS/Include
)
</code></pre>
<p>会发现依然报错，因为 CMSIS-DSP 库中的内容并不受项目的 CMakeLists.txt 管理，而是由库中的 CMakeLists.txt 管理。山重水复疑无路，回头看 CMSIS-DSP 的 README，才发现答案人家已经给出了——</p>
<h2>第四步：在 CMakePresets.json 加入 CMSISCORE 定义</h2>
<p>在 README 中写到：</p>
<blockquote>
<p>CMSIS-DSP is dependent on the CMSIS Core includes. So, you should define CMSISCORE on the cmake command line. The path used by CMSIS-DSP will be ${CMSISCORE}/Include.</p>
</blockquote>
<p>因此我们需要给 CMake 传入 CMSISCORE 的定义。传入的方法就是在CMakePresets.json中找到"default"项，在其中的"cacheVariables"加入 CMSIS-Core 的路径：</p>
<pre><code>"configurePresets": [
    {
        "name": "default",
        "hidden": true,
        "generator": "Ninja",
        "binaryDir": "${sourceDir}/build/${presetName}",
        "toolchainFile": "${sourceDir}/cmake/gcc-arm-none-eabi.cmake",
     "cacheVariables": {
         "CMSISCORE": "${sourceDir}/Drivers/CMSIS"
     }
 }
]
</code></pre>
<p>此时清除缓存重新编译，会看到 CMSIS-DSP 库已经正常引入到了我们的项目中。大功告成！</p>
]]></content>
        <author>
            <name>molqzone</name>
            <uri>https://molqzone.vercel.app/</uri>
        </author>
        <published>2025-07-20T00:00:00.000Z</published>
    </entry>
</feed>