<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>LsWorld</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://lsworl.github.io/</id>
  <link href="https://lsworl.github.io/" rel="alternate"/>
  <link href="https://lsworl.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, LsWorld</rights>
  <subtitle>机器学习与代码笔记</subtitle>
  <title>LsWorld</title>
  <updated>2026-05-23T02:18:10.000Z</updated>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="机器学习" scheme="https://lsworl.github.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <category term="机器学习" scheme="https://lsworl.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <category term="监督学习" scheme="https://lsworl.github.io/tags/%E7%9B%91%E7%9D%A3%E5%AD%A6%E4%B9%A0/"/>
    <category term="回归算法" scheme="https://lsworl.github.io/tags/%E5%9B%9E%E5%BD%92%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>线性回归（Linear Regression）是机器学习中最基础、也最常用的监督学习算法之一。它主要用于解决回归问题，也就是预测一个连续数值。</p><p>例如：</p><ul><li>根据房屋面积预测房价。</li><li>根据学习时长预测考试分数。</li><li>根据广告投入预测销售额。</li></ul><p>线性回归的核心思想很朴素：如果两个变量之间存在近似线性的关系，就可以用一条直线或者一个线性函数去描述这种关系，并用它来预测未知样本。</p><img src="/images/ml-blog/linear-regression-fit.png" alt="线性回归拟合直线示意图"><p>图中的蓝色点是训练数据，红色直线是拟合出的线性模型，灰色虚线表示预测值和真实值之间的误差。线性回归训练时，本质上就是在寻找一条让整体误差尽可能小的直线。</p><span id="more"></span><h2 id="1-线性回归的直觉"><a href="#1-线性回归的直觉" class="headerlink" title="1. 线性回归的直觉"></a>1. 线性回归的直觉</h2><p>假设我们想根据学习时长预测考试分数，已有数据如下：</p><table><thead><tr><th align="center">学习时长&#x2F;小时</th><th align="center">考试分数</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">50</td></tr><tr><td align="center">2</td><td align="center">55</td></tr><tr><td align="center">3</td><td align="center">65</td></tr><tr><td align="center">4</td><td align="center">70</td></tr><tr><td align="center">5</td><td align="center">80</td></tr></tbody></table><p>把这些点画到坐标系中，可以发现：学习时间越长，考试分数通常越高。虽然这些点不一定完全落在同一条直线上，但整体趋势接近一条向上倾斜的直线。</p><p>线性回归要做的事情，就是找到一条尽可能贴近这些数据点的直线：</p><p>$$<br>\hat{y} &#x3D; wx + b<br>$$</p><p>其中：</p><ul><li>$x$ 表示输入特征，比如学习时长。</li><li>$\hat{y}$ 表示模型预测值，比如预测分数。</li><li>$w$ 表示权重，也可以理解为直线的斜率。</li><li>$b$ 表示偏置，也可以理解为直线在 $y$ 轴上的截距。</li></ul><p>如果最终学到的模型是：</p><p>$$<br>\hat{y} &#x3D; 7x + 43<br>$$</p><p>那么当一个学生学习 6 小时时，模型会预测：</p><p>$$<br>\hat{y} &#x3D; 7 \times 6 + 43 &#x3D; 85<br>$$</p><p>也就是说，预测考试分数为 85 分。</p><h2 id="2-一元线性回归"><a href="#2-一元线性回归" class="headerlink" title="2. 一元线性回归"></a>2. 一元线性回归</h2><p>只有一个输入特征时，称为一元线性回归。模型形式为：</p><p>$$<br>\hat{y}^{(i)} &#x3D; wx^{(i)} + b<br>$$</p><p>其中 $i$ 表示第 $i$ 个样本。</p><p>对于训练集：</p><p>$$<br>(x^{(1)}, y^{(1)}), (x^{(2)}, y^{(2)}), \dots, (x^{(m)}, y^{(m)})<br>$$</p><p>线性回归希望找到合适的 $w$ 和 $b$，让所有样本的预测值 $\hat{y}$ 尽可能接近真实值 $y$。</p><p>这里的“接近”需要一个明确的衡量标准，于是就引出了损失函数。</p><h2 id="3-损失函数"><a href="#3-损失函数" class="headerlink" title="3. 损失函数"></a>3. 损失函数</h2><p>线性回归中最常用的损失函数是均方误差（Mean Squared Error，MSE）：</p><p>$$<br>J(w, b) &#x3D; \frac{1}{m}\sum_{i&#x3D;1}^{m}(\hat{y}^{(i)} - y^{(i)})^2<br>$$</p><p>因为：</p><p>$$<br>\hat{y}^{(i)} &#x3D; wx^{(i)} + b<br>$$</p><p>所以也可以写成：</p><p>$$<br>J(w, b) &#x3D; \frac{1}{m}\sum_{i&#x3D;1}^{m}(wx^{(i)} + b - y^{(i)})^2<br>$$</p><p>这个公式的含义是：</p><ol><li>对每个样本计算预测值和真实值之间的误差。</li><li>将误差平方，避免正负误差相互抵消。</li><li>对所有平方误差求平均。</li></ol><p>线性回归的训练目标就是让这个损失函数尽可能小：</p><p>$$<br>\min_{w,b} J(w,b)<br>$$</p><img src="/images/ml-blog/linear-regression-loss-curve.png" alt="固定截距时斜率和均方误差的关系"><p>上图固定截距 $b&#x3D;41.5$，只观察斜率 $w$ 变化时均方误差的变化。曲线最低的位置对应更合适的参数，这也解释了为什么训练模型可以理解为“寻找损失函数的最低点”。</p><h2 id="4-最小二乘法"><a href="#4-最小二乘法" class="headerlink" title="4. 最小二乘法"></a>4. 最小二乘法</h2><p>对于一元线性回归，可以直接使用最小二乘法求出最优的 $w$ 和 $b$。</p><p>最优斜率 $w$ 的计算公式为：</p><p>$$<br>w &#x3D; \frac{\sum_{i&#x3D;1}^{m}(x^{(i)} - \bar{x})(y^{(i)} - \bar{y})}{\sum_{i&#x3D;1}^{m}(x^{(i)} - \bar{x})^2}<br>$$</p><p>最优截距 $b$ 的计算公式为：</p><p>$$<br>b &#x3D; \bar{y} - w\bar{x}<br>$$</p><p>其中：</p><ul><li>$\bar{x}$ 表示所有 $x$ 的平均值。</li><li>$\bar{y}$ 表示所有 $y$ 的平均值。</li></ul><p>从直觉上看，$w$ 描述的是 $x$ 增加时 $y$ 大约会增加多少；$b$ 则负责调整整条直线的上下位置。</p><h2 id="5-手工计算例子"><a href="#5-手工计算例子" class="headerlink" title="5. 手工计算例子"></a>5. 手工计算例子</h2><p>仍然使用学习时长和考试分数的数据：</p><table><thead><tr><th align="center">编号</th><th align="right">$x$</th><th align="right">$y$</th></tr></thead><tbody><tr><td align="center">1</td><td align="right">1</td><td align="right">50</td></tr><tr><td align="center">2</td><td align="right">2</td><td align="right">55</td></tr><tr><td align="center">3</td><td align="right">3</td><td align="right">65</td></tr><tr><td align="center">4</td><td align="right">4</td><td align="right">70</td></tr><tr><td align="center">5</td><td align="right">5</td><td align="right">80</td></tr></tbody></table><p>先计算平均值：</p><p>$$<br>\bar{x} &#x3D; \frac{1+2+3+4+5}{5} &#x3D; 3<br>$$</p><p>$$<br>\bar{y} &#x3D; \frac{50+55+65+70+80}{5} &#x3D; 64<br>$$</p><p>接着计算 $w$：</p><table><thead><tr><th align="right">$x$</th><th align="right">$y$</th><th align="right">$x-\bar{x}$</th><th align="right">$y-\bar{y}$</th><th align="right">$(x-\bar{x})(y-\bar{y})$</th><th align="right">$(x-\bar{x})^2$</th></tr></thead><tbody><tr><td align="right">1</td><td align="right">50</td><td align="right">-2</td><td align="right">-14</td><td align="right">28</td><td align="right">4</td></tr><tr><td align="right">2</td><td align="right">55</td><td align="right">-1</td><td align="right">-9</td><td align="right">9</td><td align="right">1</td></tr><tr><td align="right">3</td><td align="right">65</td><td align="right">0</td><td align="right">1</td><td align="right">0</td><td align="right">0</td></tr><tr><td align="right">4</td><td align="right">70</td><td align="right">1</td><td align="right">6</td><td align="right">6</td><td align="right">1</td></tr><tr><td align="right">5</td><td align="right">80</td><td align="right">2</td><td align="right">16</td><td align="right">32</td><td align="right">4</td></tr></tbody></table><p>因此：</p><p>$$<br>w &#x3D; \frac{28+9+0+6+32}{4+1+0+1+4} &#x3D; \frac{75}{10} &#x3D; 7.5<br>$$</p><p>再计算 $b$：</p><p>$$<br>b &#x3D; 64 - 7.5 \times 3 &#x3D; 41.5<br>$$</p><p>所以拟合出来的直线为：</p><p>$$<br>\hat{y} &#x3D; 7.5x + 41.5<br>$$</p><p>当学习时间为 6 小时时：</p><p>$$<br>\hat{y} &#x3D; 7.5 \times 6 + 41.5 &#x3D; 86.5<br>$$</p><p>模型预测考试分数约为 86.5 分。</p><h2 id="6-多元线性回归"><a href="#6-多元线性回归" class="headerlink" title="6. 多元线性回归"></a>6. 多元线性回归</h2><p>实际问题中，预测目标往往不只受一个因素影响。</p><p>例如房价可能同时受到这些因素影响：</p><ul><li>房屋面积</li><li>房间数量</li><li>地理位置</li><li>房龄</li><li>楼层</li></ul><p>如果有多个输入特征，就称为多元线性回归。模型形式为：</p><p>$$<br>\hat{y} &#x3D; w_1x_1 + w_2x_2 + \dots + w_nx_n + b<br>$$</p><p>也可以写成向量形式：</p><p>$$<br>\hat{y} &#x3D; \mathbf{w}^T\mathbf{x} + b<br>$$</p><p>其中：</p><ul><li>$\mathbf{x}$ 是特征向量。</li><li>$\mathbf{w}$ 是权重向量。</li><li>$b$ 是偏置。</li></ul><p>多元线性回归的本质没有变化，仍然是在寻找一组参数，让预测值和真实值之间的误差尽可能小。</p><h2 id="7-梯度下降求解"><a href="#7-梯度下降求解" class="headerlink" title="7. 梯度下降求解"></a>7. 梯度下降求解</h2><p>当数据量较大、特征较多时，可以使用梯度下降来优化参数。</p><p>梯度下降的核心思想是：先随机给 $w$ 和 $b$ 一个初始值，然后不断沿着损失函数下降最快的方向更新参数，直到损失足够小。</p><p>参数更新公式为：</p><p>$$<br>w :&#x3D; w - \alpha \frac{\partial J(w,b)}{\partial w}<br>$$</p><p>$$<br>b :&#x3D; b - \alpha \frac{\partial J(w,b)}{\partial b}<br>$$</p><p>其中 $\alpha$ 是学习率，用来控制每次更新参数的步长。</p><p>对于一元线性回归：</p><p>$$<br>\frac{\partial J(w,b)}{\partial w}<br>&#x3D; \frac{2}{m}\sum_{i&#x3D;1}^{m}(\hat{y}^{(i)} - y^{(i)})x^{(i)}<br>$$</p><p>$$<br>\frac{\partial J(w,b)}{\partial b}<br>&#x3D; \frac{2}{m}\sum_{i&#x3D;1}^{m}(\hat{y}^{(i)} - y^{(i)})<br>$$</p><p>学习率不能太大，也不能太小：</p><ul><li>如果学习率太大，可能会越过最优点，导致损失震荡甚至发散。</li><li>如果学习率太小，收敛速度会很慢，需要训练很多轮。</li></ul><h2 id="8-Python-实现"><a href="#8-Python-实现" class="headerlink" title="8. Python 实现"></a>8. Python 实现</h2><h3 id="8-1-使用最小二乘法手写实现"><a href="#8-1-使用最小二乘法手写实现" class="headerlink" title="8.1 使用最小二乘法手写实现"></a>8.1 使用最小二乘法手写实现</h3><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> npx <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token builtin">float</span><span class="token punctuation">)</span>y <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">55</span><span class="token punctuation">,</span> <span class="token number">65</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">]</span><span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token builtin">float</span><span class="token punctuation">)</span>x_mean <span class="token operator">=</span> np<span class="token punctuation">.</span>mean<span class="token punctuation">(</span>x<span class="token punctuation">)</span>y_mean <span class="token operator">=</span> np<span class="token punctuation">.</span>mean<span class="token punctuation">(</span>y<span class="token punctuation">)</span>w <span class="token operator">=</span> np<span class="token punctuation">.</span><span class="token builtin">sum</span><span class="token punctuation">(</span><span class="token punctuation">(</span>x <span class="token operator">-</span> x_mean<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>y <span class="token operator">-</span> y_mean<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> np<span class="token punctuation">.</span><span class="token builtin">sum</span><span class="token punctuation">(</span><span class="token punctuation">(</span>x <span class="token operator">-</span> x_mean<span class="token punctuation">)</span> <span class="token operator">**</span> <span class="token number">2</span><span class="token punctuation">)</span>b <span class="token operator">=</span> y_mean <span class="token operator">-</span> w <span class="token operator">*</span> x_mean<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"w ="</span><span class="token punctuation">,</span> w<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"b ="</span><span class="token punctuation">,</span> b<span class="token punctuation">)</span>new_x <span class="token operator">=</span> <span class="token number">6</span>prediction <span class="token operator">=</span> w <span class="token operator">*</span> new_x <span class="token operator">+</span> b<span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"预测分数 ="</span><span class="token punctuation">,</span> prediction<span class="token punctuation">)</span></code></pre><p>输出结果大致为：</p><pre class="language-text" data-language="text"><code class="language-text">w = 7.5b = 41.5预测分数 = 86.5</code></pre><h3 id="8-2-使用-scikit-learn-实现"><a href="#8-2-使用-scikit-learn-实现" class="headerlink" title="8.2 使用 scikit-learn 实现"></a>8.2 使用 scikit-learn 实现</h3><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>linear_model <span class="token keyword">import</span> LinearRegressionX <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token builtin">float</span><span class="token punctuation">)</span>y <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token number">55</span><span class="token punctuation">,</span> <span class="token number">65</span><span class="token punctuation">,</span> <span class="token number">70</span><span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">]</span><span class="token punctuation">,</span> dtype<span class="token operator">=</span><span class="token builtin">float</span><span class="token punctuation">)</span>model <span class="token operator">=</span> LinearRegression<span class="token punctuation">(</span><span class="token punctuation">)</span>model<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"w ="</span><span class="token punctuation">,</span> model<span class="token punctuation">.</span>coef_<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"b ="</span><span class="token punctuation">,</span> model<span class="token punctuation">.</span>intercept_<span class="token punctuation">)</span>prediction <span class="token operator">=</span> model<span class="token punctuation">.</span>predict<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"预测分数 ="</span><span class="token punctuation">,</span> prediction<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre><p>在 scikit-learn 中，输入特征 $X$ 通常需要是二维数组，即使只有一个特征，也要写成 <code>[[1], [2], [3]]</code> 这样的形式。</p><h2 id="9-模型评估指标"><a href="#9-模型评估指标" class="headerlink" title="9. 模型评估指标"></a>9. 模型评估指标</h2><p>线性回归属于回归模型，常用评估指标包括 MAE、MSE、RMSE 和 $R^2$。</p><h3 id="9-1-MAE"><a href="#9-1-MAE" class="headerlink" title="9.1 MAE"></a>9.1 MAE</h3><p>MAE（Mean Absolute Error，平均绝对误差）：</p><p>$$<br>MAE &#x3D; \frac{1}{m}\sum_{i&#x3D;1}^{m}|\hat{y}^{(i)} - y^{(i)}|<br>$$</p><p>MAE 表示预测值和真实值平均相差多少，单位和原始标签一致，因此比较容易理解。</p><h3 id="9-2-MSE"><a href="#9-2-MSE" class="headerlink" title="9.2 MSE"></a>9.2 MSE</h3><p>MSE（Mean Squared Error，均方误差）：</p><p>$$<br>MSE &#x3D; \frac{1}{m}\sum_{i&#x3D;1}^{m}(\hat{y}^{(i)} - y^{(i)})^2<br>$$</p><p>MSE 会放大较大的误差，因此对异常值更敏感。</p><h3 id="9-3-RMSE"><a href="#9-3-RMSE" class="headerlink" title="9.3 RMSE"></a>9.3 RMSE</h3><p>RMSE（Root Mean Squared Error，均方根误差）：</p><p>$$<br>RMSE &#x3D; \sqrt{\frac{1}{m}\sum_{i&#x3D;1}^{m}(\hat{y}^{(i)} - y^{(i)})^2}<br>$$</p><p>RMSE 对 MSE 开平方后，单位重新回到原始标签的单位。</p><h3 id="9-4-R-2"><a href="#9-4-R-2" class="headerlink" title="9.4 $R^2$"></a>9.4 $R^2$</h3><p>$R^2$ 又叫决定系数，用来衡量模型对数据变化的解释能力：</p><p>$$<br>R^2 &#x3D; 1 - \frac{\sum_{i&#x3D;1}^{m}(y^{(i)} - \hat{y}^{(i)})^2}{\sum_{i&#x3D;1}^{m}(y^{(i)} - \bar{y})^2}<br>$$</p><p>$R^2$ 越接近 1，说明模型拟合效果越好；如果 $R^2$ 接近 0，说明模型效果可能和直接预测平均值差不多。</p><h2 id="10-线性回归的优缺点"><a href="#10-线性回归的优缺点" class="headerlink" title="10. 线性回归的优缺点"></a>10. 线性回归的优缺点</h2><h3 id="10-1-优点"><a href="#10-1-优点" class="headerlink" title="10.1 优点"></a>10.1 优点</h3><ul><li>模型简单，容易理解和实现。</li><li>训练速度快，适合作为回归任务的 baseline。</li><li>参数具有一定可解释性，可以观察每个特征对预测结果的影响。</li><li>对线性关系明显的数据效果很好。</li></ul><h3 id="10-2-缺点"><a href="#10-2-缺点" class="headerlink" title="10.2 缺点"></a>10.2 缺点</h3><ul><li>表达能力有限，难以拟合复杂的非线性关系。</li><li>对异常值比较敏感，极端样本可能明显影响拟合结果。</li><li>特征之间存在强相关性时，参数解释可能变得不稳定。</li><li>假设特征和目标值之间近似线性，如果这个假设不成立，效果会比较差。</li></ul><h2 id="11-实际使用时的注意点"><a href="#11-实际使用时的注意点" class="headerlink" title="11. 实际使用时的注意点"></a>11. 实际使用时的注意点</h2><h3 id="11-1-观察数据是否接近线性关系"><a href="#11-1-观察数据是否接近线性关系" class="headerlink" title="11.1 观察数据是否接近线性关系"></a>11.1 观察数据是否接近线性关系</h3><p>线性回归适合处理接近线性关系的数据。如果特征和目标之间明显是曲线关系，可以考虑加入多项式特征，或者使用决策树、随机森林、梯度提升树等非线性模型。</p><h3 id="11-2-注意异常值"><a href="#11-2-注意异常值" class="headerlink" title="11.2 注意异常值"></a>11.2 注意异常值</h3><p>由于线性回归通常使用平方误差作为损失函数，异常值会被放大。训练前可以先画散点图、箱线图，或者使用统计方法检查异常样本。</p><h3 id="11-3-做好特征处理"><a href="#11-3-做好特征处理" class="headerlink" title="11.3 做好特征处理"></a>11.3 做好特征处理</h3><p>对于多元线性回归，特征尺度差异过大时，虽然普通最小二乘法仍然能求解，但在使用梯度下降时会影响收敛速度。因此常见做法是对特征进行标准化：</p><p>$$<br>z &#x3D; \frac{x - \mu}{\sigma}<br>$$</p><p>其中 $\mu$ 是均值，$\sigma$ 是标准差。</p><h3 id="11-4-避免只看训练集误差"><a href="#11-4-避免只看训练集误差" class="headerlink" title="11.4 避免只看训练集误差"></a>11.4 避免只看训练集误差</h3><p>如果模型只在训练集上表现好，但在测试集上表现差，说明模型泛化能力不足。更合理的做法是将数据划分为训练集和测试集，再使用测试集评估模型效果。</p><h2 id="12-总结"><a href="#12-总结" class="headerlink" title="12. 总结"></a>12. 总结</h2><p>线性回归的核心可以概括为一句话：用一个线性函数去拟合输入特征和连续目标值之间的关系。</p><p>它的关键步骤是：</p><ol><li>假设模型形式，例如 $\hat{y} &#x3D; wx + b$。</li><li>定义损失函数，例如均方误差。</li><li>通过最小二乘法或梯度下降求出最优参数。</li><li>使用 MAE、MSE、RMSE、$R^2$ 等指标评估模型。</li></ol><p>虽然线性回归看起来简单，但它包含了机器学习中很多重要思想：模型假设、损失函数、参数优化、泛化能力和特征处理。理解线性回归之后，再学习逻辑回归、神经网络、支持向量机等模型时，会更容易抓住其中的共同脉络。</p>]]>
    </content>
    <id>https://lsworl.github.io/2026/05/23/machine-learning-linear-regression/</id>
    <link href="https://lsworl.github.io/2026/05/23/machine-learning-linear-regression/"/>
    <published>2026-05-22T16:00:00.000Z</published>
    <summary>从直觉、数学公式、手工计算、Python 实现和优缺点几个角度理解线性回归。</summary>
    <title>线性回归：从直觉到最小二乘法</title>
    <updated>2026-05-23T02:18:10.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="机器学习" scheme="https://lsworl.github.io/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <category term="机器学习" scheme="https://lsworl.github.io/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <category term="监督学习" scheme="https://lsworl.github.io/tags/%E7%9B%91%E7%9D%A3%E5%AD%A6%E4%B9%A0/"/>
    <category term="分类算法" scheme="https://lsworl.github.io/tags/%E5%88%86%E7%B1%BB%E7%AE%97%E6%B3%95/"/>
    <content>
      <![CDATA[<p>KNN（K-Nearest Neighbors，K 近邻）是一种非常直观的监督学习算法：一个样本属于什么类别，可以参考它在特征空间中最近的 $K$ 个邻居。</p><p>在分类任务中，KNN 通常采用“多数投票”：最近的 $K$ 个样本里哪个类别最多，就预测为哪个类别；在回归任务中，KNN 通常取最近 $K$ 个样本标签值的平均值或加权平均值。</p><span id="more"></span><h2 id="1-KNN-的直觉"><a href="#1-KNN-的直觉" class="headerlink" title="1. KNN 的直觉"></a>1. KNN 的直觉</h2><p>可以把 KNN 想象成“向邻居打听答案”。</p><p>假设小区里新搬来一户人家，你想判断他们更像“高收入家庭”还是“普通收入家庭”。最直接的方式，是观察离他们最近的几户邻居：如果最近的 5 户里有 3 户都是高收入家庭，那么你可能会猜测这户新邻居也更接近高收入家庭。</p><p>机器学习里的 KNN 做的是类似的事情：</p><ul><li>输入：一个尚未标记类别的新样本。</li><li>过程：在训练集中找到距离它最近的 $K$ 个已知样本。</li><li>输出：分类时进行多数投票，回归时进行平均或加权平均。</li></ul><p>KNN 本质上没有显式的训练过程，它把训练数据保存下来，预测时再计算新样本与训练样本之间的距离。因此它也被称为惰性学习（Lazy Learning）。</p><h2 id="2-数学原理"><a href="#2-数学原理" class="headerlink" title="2. 数学原理"></a>2. 数学原理</h2><p>KNN 要解决两个核心问题：</p><ol><li>如何定义“近”？也就是距离度量。</li><li>找到邻居后如何做决策？也就是分类或回归规则。</li></ol><h3 id="2-1-距离度量"><a href="#2-1-距离度量" class="headerlink" title="2.1 距离度量"></a>2.1 距离度量</h3><p>假设两个 $n$ 维样本分别为：</p><p>$$<br>\mathbf{x} &#x3D; (x_1, x_2, \dots, x_n)<br>$$</p><p>$$<br>\mathbf{y} &#x3D; (y_1, y_2, \dots, y_n)<br>$$</p><p>不同的距离函数会影响 KNN 对“相似”的判断。</p><h4 id="欧氏距离"><a href="#欧氏距离" class="headerlink" title="欧氏距离"></a>欧氏距离</h4><p>欧氏距离是最常见的距离度量，可以理解为多维空间中的直线距离：</p><p>$$<br>d(\mathbf{x}, \mathbf{y}) &#x3D; \sqrt{\sum_{i&#x3D;1}^{n}(x_i - y_i)^2}<br>$$</p><p>二维空间中，它就是两点之间的几何距离。</p><h4 id="曼哈顿距离"><a href="#曼哈顿距离" class="headerlink" title="曼哈顿距离"></a>曼哈顿距离</h4><p>曼哈顿距离可以理解为在网格道路中行走时的距离，只能横向或纵向移动：</p><p>$$<br>d(\mathbf{x}, \mathbf{y}) &#x3D; \sum_{i&#x3D;1}^{n}|x_i - y_i|<br>$$</p><h4 id="闵可夫斯基距离"><a href="#闵可夫斯基距离" class="headerlink" title="闵可夫斯基距离"></a>闵可夫斯基距离</h4><p>闵可夫斯基距离是欧氏距离和曼哈顿距离的推广：</p><p>$$<br>d(\mathbf{x}, \mathbf{y}) &#x3D;<br>\left(\sum_{i&#x3D;1}^{n}|x_i - y_i|^p\right)^{\frac{1}{p}}<br>$$</p><p>当 $p&#x3D;1$ 时，它等价于曼哈顿距离；当 $p&#x3D;2$ 时，它等价于欧氏距离。</p><h3 id="2-2-为什么要做特征缩放"><a href="#2-2-为什么要做特征缩放" class="headerlink" title="2.2 为什么要做特征缩放"></a>2.2 为什么要做特征缩放</h3><p>KNN 对特征尺度非常敏感。</p><p>假设一个样本有两个特征：年收入和年龄。年收入的范围可能是 $0 \sim 100000$，年龄的范围可能是 $0 \sim 100$。如果直接计算欧氏距离，年收入会主导距离结果，年龄的影响几乎被淹没。</p><p>因此，在使用 KNN 前通常要先进行标准化或归一化：</p><p>$$<br>z &#x3D; \frac{x - \mu}{\sigma}<br>$$</p><p>其中 $\mu$ 是均值，$\sigma$ 是标准差。标准化后，不同特征会处于更可比较的尺度上。</p><h2 id="3-手工计算例子：水果分类"><a href="#3-手工计算例子：水果分类" class="headerlink" title="3. 手工计算例子：水果分类"></a>3. 手工计算例子：水果分类</h2><p>假设我们用两个特征判断水果类别：</p><ul><li>甜度 $x_1$</li><li>脆度 $x_2$</li></ul><p>已有训练数据如下：</p><table><thead><tr><th align="left">编号</th><th align="left">类别</th><th align="right">甜度 $x_1$</th><th align="right">脆度 $x_2$</th><th align="left">坐标</th></tr></thead><tbody><tr><td align="left">A</td><td align="left">苹果</td><td align="right">8</td><td align="right">7</td><td align="left">$(8, 7)$</td></tr><tr><td align="left">B</td><td align="left">苹果</td><td align="right">7</td><td align="right">8</td><td align="left">$(7, 8)$</td></tr><tr><td align="left">C</td><td align="left">梨</td><td align="right">2</td><td align="right">4</td><td align="left">$(2, 4)$</td></tr><tr><td align="left">D</td><td align="left">梨</td><td align="right">3</td><td align="right">3</td><td align="left">$(3, 3)$</td></tr></tbody></table><p>现在有一个未知水果 $X&#x3D;(4,5)$，设 $K&#x3D;3$，判断它是苹果还是梨。</p><img src="/images/ml-blog/knn-fruit-python.png" alt="KNN 水果分类示意图"><p>图中的星形点表示未知水果，虚线连接的是它最近的 3 个邻居。通过这张图可以更直观地看到：KNN 并不是提前学出一个公式，而是在预测时临时寻找附近样本，再根据邻居类别做判断。</p><h3 id="3-1-计算距离"><a href="#3-1-计算距离" class="headerlink" title="3.1 计算距离"></a>3.1 计算距离</h3><p>使用欧氏距离：</p><p>$$<br>d(X,A)&#x3D;\sqrt{(4-8)^2+(5-7)^2}&#x3D;\sqrt{20}\approx4.47<br>$$</p><p>$$<br>d(X,B)&#x3D;\sqrt{(4-7)^2+(5-8)^2}&#x3D;\sqrt{18}\approx4.24<br>$$</p><p>$$<br>d(X,C)&#x3D;\sqrt{(4-2)^2+(5-4)^2}&#x3D;\sqrt{5}\approx2.24<br>$$</p><p>$$<br>d(X,D)&#x3D;\sqrt{(4-3)^2+(5-3)^2}&#x3D;\sqrt{5}\approx2.24<br>$$</p><p>按距离从小到大排序：</p><table><thead><tr><th align="right">排名</th><th align="left">邻居</th><th align="left">类别</th><th align="right">距离</th></tr></thead><tbody><tr><td align="right">1</td><td align="left">C</td><td align="left">梨</td><td align="right">$2.24$</td></tr><tr><td align="right">2</td><td align="left">D</td><td align="left">梨</td><td align="right">$2.24$</td></tr><tr><td align="right">3</td><td align="left">B</td><td align="left">苹果</td><td align="right">$4.24$</td></tr><tr><td align="right">4</td><td align="left">A</td><td align="left">苹果</td><td align="right">$4.47$</td></tr></tbody></table><p>因为 $K&#x3D;3$，最近的 3 个邻居是 C、D、B。其中梨有 2 票，苹果有 1 票，所以 KNN 会预测：</p><p>$$<br>\hat{y}&#x3D;\text{梨}<br>$$</p><h2 id="4-决策规则"><a href="#4-决策规则" class="headerlink" title="4. 决策规则"></a>4. 决策规则</h2><h3 id="4-1-分类任务"><a href="#4-1-分类任务" class="headerlink" title="4.1 分类任务"></a>4.1 分类任务</h3><p>分类任务中最常见的是多数投票：</p><p>$$<br>\hat{y}&#x3D;\arg\max_{c}\sum_{i \in N_K(x)} I(y_i&#x3D;c)<br>$$</p><p>其中 $N_K(x)$ 表示样本 $x$ 的 $K$ 个最近邻，$I(\cdot)$ 是指示函数。</p><p>如果希望离得更近的邻居拥有更高权重，可以使用距离加权投票：</p><p>$$<br>w_i&#x3D;\frac{1}{d(x,x_i)+\epsilon}<br>$$</p><p>这里 $\epsilon$ 是一个很小的正数，用来避免距离为 0 时分母出错。</p><h3 id="4-2-回归任务"><a href="#4-2-回归任务" class="headerlink" title="4.2 回归任务"></a>4.2 回归任务</h3><p>回归任务中，KNN 可以取最近邻标签的平均值：</p><p>$$<br>\hat{y}&#x3D;\frac{1}{K}\sum_{i \in N_K(x)}y_i<br>$$</p><p>也可以使用距离加权平均：</p><p>$$<br>\hat{y}&#x3D;\frac{\sum_{i \in N_K(x)} w_i y_i}{\sum_{i \in N_K(x)}w_i}<br>$$</p><h2 id="5-如何选择-K-值"><a href="#5-如何选择-K-值" class="headerlink" title="5. 如何选择 K 值"></a>5. 如何选择 K 值</h2><p>$K$ 是 KNN 中最重要的超参数之一。</p><ul><li>$K$ 太小：模型会过于敏感，容易受到噪声点影响，训练集表现很好但泛化能力较差，这通常对应过拟合。</li><li>$K$ 太大：模型会参考太多远处样本，决策边界过于平滑，难以捕捉局部结构，这通常对应欠拟合。</li></ul><p>在二分类任务中，通常会优先尝试奇数 $K$，例如 $K&#x3D;3,5,7$，这样可以减少平票情况。但在多分类任务中，即使 $K$ 是奇数也可能平票，此时可以使用距离加权、降低 $K$ 或制定固定的平票处理规则。</p><p>实践中常用交叉验证选择合适的 $K$：</p><p>$$<br>K^* &#x3D; \arg\min_K \text{CVError}(K)<br>$$</p><h2 id="6-Python-实战：观察不同-K-值的决策边界"><a href="#6-Python-实战：观察不同-K-值的决策边界" class="headerlink" title="6. Python 实战：观察不同 K 值的决策边界"></a>6. Python 实战：观察不同 K 值的决策边界</h2><p>下面代码使用 <code>scikit-learn</code> 构造一个二维数据集，并对比 $K&#x3D;1$、$K&#x3D;5$、$K&#x3D;15$ 时的决策边界。</p><p>安装依赖：</p><pre class="language-bash" data-language="bash"><code class="language-bash">pip <span class="token function">install</span> scikit-learn numpy matplotlib</code></pre><p>示例代码：</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">import</span> matplotlib<span class="token punctuation">.</span>pyplot <span class="token keyword">as</span> plt<span class="token keyword">from</span> matplotlib<span class="token punctuation">.</span>colors <span class="token keyword">import</span> ListedColormap<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>datasets <span class="token keyword">import</span> make_moons<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>neighbors <span class="token keyword">import</span> KNeighborsClassifier<span class="token keyword">from</span> sklearn<span class="token punctuation">.</span>preprocessing <span class="token keyword">import</span> StandardScalerX<span class="token punctuation">,</span> y <span class="token operator">=</span> make_moons<span class="token punctuation">(</span>n_samples<span class="token operator">=</span><span class="token number">200</span><span class="token punctuation">,</span> noise<span class="token operator">=</span><span class="token number">0.3</span><span class="token punctuation">,</span> random_state<span class="token operator">=</span><span class="token number">42</span><span class="token punctuation">)</span>scaler <span class="token operator">=</span> StandardScaler<span class="token punctuation">(</span><span class="token punctuation">)</span>X_scaled <span class="token operator">=</span> scaler<span class="token punctuation">.</span>fit_transform<span class="token punctuation">(</span>X<span class="token punctuation">)</span>h <span class="token operator">=</span> <span class="token number">0.02</span>x_min<span class="token punctuation">,</span> x_max <span class="token operator">=</span> X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token builtin">min</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">0.5</span><span class="token punctuation">,</span> X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token builtin">max</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">0.5</span>y_min<span class="token punctuation">,</span> y_max <span class="token operator">=</span> X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token builtin">min</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">0.5</span><span class="token punctuation">,</span> X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token builtin">max</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">0.5</span>xx<span class="token punctuation">,</span> yy <span class="token operator">=</span> np<span class="token punctuation">.</span>meshgrid<span class="token punctuation">(</span>    np<span class="token punctuation">.</span>arange<span class="token punctuation">(</span>x_min<span class="token punctuation">,</span> x_max<span class="token punctuation">,</span> h<span class="token punctuation">)</span><span class="token punctuation">,</span>    np<span class="token punctuation">.</span>arange<span class="token punctuation">(</span>y_min<span class="token punctuation">,</span> y_max<span class="token punctuation">,</span> h<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">)</span>k_values <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">]</span>cmap_light <span class="token operator">=</span> ListedColormap<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"#FEE2E2"</span><span class="token punctuation">,</span> <span class="token string">"#DBEAFE"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>cmap_bold <span class="token operator">=</span> ListedColormap<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"#DC2626"</span><span class="token punctuation">,</span> <span class="token string">"#2563EB"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>figure<span class="token punctuation">(</span>figsize<span class="token operator">=</span><span class="token punctuation">(</span><span class="token number">15</span><span class="token punctuation">,</span> <span class="token number">4.5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">for</span> i<span class="token punctuation">,</span> k <span class="token keyword">in</span> <span class="token builtin">enumerate</span><span class="token punctuation">(</span>k_values<span class="token punctuation">,</span> start<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    clf <span class="token operator">=</span> KNeighborsClassifier<span class="token punctuation">(</span>n_neighbors<span class="token operator">=</span>k<span class="token punctuation">)</span>    clf<span class="token punctuation">.</span>fit<span class="token punctuation">(</span>X_scaled<span class="token punctuation">,</span> y<span class="token punctuation">)</span>    Z <span class="token operator">=</span> clf<span class="token punctuation">.</span>predict<span class="token punctuation">(</span>np<span class="token punctuation">.</span>c_<span class="token punctuation">[</span>xx<span class="token punctuation">.</span>ravel<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> yy<span class="token punctuation">.</span>ravel<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    Z <span class="token operator">=</span> Z<span class="token punctuation">.</span>reshape<span class="token punctuation">(</span>xx<span class="token punctuation">.</span>shape<span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>subplot<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>pcolormesh<span class="token punctuation">(</span>xx<span class="token punctuation">,</span> yy<span class="token punctuation">,</span> Z<span class="token punctuation">,</span> cmap<span class="token operator">=</span>cmap_light<span class="token punctuation">,</span> shading<span class="token operator">=</span><span class="token string">"auto"</span><span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>scatter<span class="token punctuation">(</span>        X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span>        X_scaled<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>        c<span class="token operator">=</span>y<span class="token punctuation">,</span>        cmap<span class="token operator">=</span>cmap_bold<span class="token punctuation">,</span>        edgecolors<span class="token operator">=</span><span class="token string">"k"</span><span class="token punctuation">,</span>        s<span class="token operator">=</span><span class="token number">36</span><span class="token punctuation">,</span>    <span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"KNN Decision Boundary (K=</span><span class="token interpolation"><span class="token punctuation">&#123;</span>k<span class="token punctuation">&#125;</span></span><span class="token string">)"</span></span><span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>xlabel<span class="token punctuation">(</span><span class="token string">"Feature 1"</span><span class="token punctuation">)</span>    plt<span class="token punctuation">.</span>ylabel<span class="token punctuation">(</span><span class="token string">"Feature 2"</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>tight_layout<span class="token punctuation">(</span><span class="token punctuation">)</span>plt<span class="token punctuation">.</span>show<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><img src="/images/ml-blog/knn-decision-boundary-python.png" alt="不同 K 值下的决策边界示意"><p>从图中可以看到：</p><ul><li>$K&#x3D;1$ 时，决策边界非常曲折，模型对局部噪声非常敏感。</li><li>$K&#x3D;5$ 时，边界相对平滑，同时还能保留数据的局部结构。</li><li>$K&#x3D;15$ 时，边界进一步变平滑，如果继续增大 $K$，模型可能出现欠拟合。</li></ul><h2 id="7-KNN-的优缺点"><a href="#7-KNN-的优缺点" class="headerlink" title="7. KNN 的优缺点"></a>7. KNN 的优缺点</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li>简单直观，容易理解和实现。</li><li>不需要显式训练过程，适合做快速基线模型。</li><li>天然支持多分类任务。</li><li>对非线性边界有一定表达能力，因为决策边界由局部邻居决定。</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li>预测阶段计算成本高，每次预测都需要计算新样本与大量训练样本的距离。</li><li>内存消耗较大，需要保存训练集。</li><li>对特征尺度敏感，通常必须先做特征缩放。</li><li>对高维数据不友好，容易受到维度灾难影响。</li><li>对类别不平衡较敏感，多数类样本可能在邻域中占优势。</li></ul><h2 id="8-实践建议"><a href="#8-实践建议" class="headerlink" title="8. 实践建议"></a>8. 实践建议</h2><p>使用 KNN 时，可以按下面的顺序思考：</p><ol><li>先进行数据清洗，处理缺失值和异常值。</li><li>对连续特征做标准化或归一化。</li><li>用交叉验证选择 $K$。</li><li>视情况选择距离度量，例如欧氏距离、曼哈顿距离或余弦距离。</li><li>如果数据规模较大，可以考虑 KD-Tree、Ball Tree 或近似最近邻搜索。</li><li>如果类别不平衡，可以尝试距离加权或重采样方法。</li></ol><h2 id="9-总结"><a href="#9-总结" class="headerlink" title="9. 总结"></a>9. 总结</h2><p>KNN 的思想非常朴素：相似的样本往往拥有相似的标签。它没有复杂的训练过程，却能在许多小规模、低维、结构清晰的数据集上取得不错效果。</p><p>要用好 KNN，最重要的是记住三点：</p><ul><li>特征缩放几乎是必需的。</li><li>$K$ 值需要通过验证集或交叉验证选择。</li><li>数据维度和数据规模会直接影响 KNN 的效果与速度。</li></ul><p>因此，KNN 很适合作为入门算法和基线模型；但在大规模、高维度或实时预测场景中，需要谨慎使用。</p>]]>
    </content>
    <id>https://lsworl.github.io/2026/05/19/machine-learning-knn/</id>
    <link href="https://lsworl.github.io/2026/05/19/machine-learning-knn/"/>
    <published>2026-05-19T11:43:23.000Z</published>
    <summary>从直觉、数学公式、手工例子、代码实现和优缺点几个角度理解 K 近邻算法。</summary>
    <title>K 近邻算法：KNN（K-Nearest Neighbors）</title>
    <updated>2026-05-23T02:17:58.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="嵌入式" scheme="https://lsworl.github.io/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><p><strong>1. 任务管理 (Task Management)</strong> </p><ul><li><p><strong><code>xTaskCreate()</code></strong>:  <strong>创建任务</strong>。</p><ul><li><p><strong>功能</strong>:  动态创建一个新的任务，并将其加入到 FreeRTOS 的任务调度器中。</p></li><li><p><strong>常用场景</strong>:  在系统初始化阶段或运行时动态创建任务，用于执行不同的功能模块。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>pvTaskCode</code>:  指向任务函数的指针，任务代码的入口。</li><li><code>pcName</code>:  任务名称，字符串形式，方便调试和追踪。</li><li><code>usStackDepth</code>:  任务堆栈大小，以字 (word) 为单位，需要根据任务需求合理配置。</li><li><code>pvParameters</code>:  传递给任务函数的参数，void 指针类型，可以传递任意类型数据。</li><li><code>uxPriority</code>:  任务优先级，数值越大优先级越高，FreeRTOS 会根据优先级进行任务调度。</li><li><code>pxCreatedTask</code>:  任务句柄指针，用于存储创建的任务句柄，后续操作任务时需要使用该句柄。</li></ul></li></ul></li><li><p><strong><code>vTaskDelete()</code></strong>:  <strong>删除任务</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的任务。可以删除自身任务或其它任务。</p></li><li><p><strong>常用场景</strong>:  任务执行完成后不再需要，或者需要动态管理任务生命周期时。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTaskToDelete</code>:  要删除的任务句柄。如果为 <code>NULL</code>，则删除自身任务。</li></ul></li></ul></li><li><p><strong><code>vTaskDelay()</code></strong>:  <strong>任务延时</strong>。</p><ul><li><p><strong>功能</strong>:  使当前任务进入阻塞态，延时指定的时间。</p></li><li><p><strong>常用场景</strong>:  周期性任务的延时，或在不需要立即执行的任务中加入延时以降低 CPU 占用率。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTicksToDelay</code>:  延时的时间，以 tick 为单位。tick 的时间长度由 <code>configTICK_RATE_HZ</code> 配置宏定义，例如 <code>configTICK_RATE_HZ</code> 为 1000，则 1 tick 为 1ms。</li></ul></li></ul></li><li><p><strong><code>vTaskSuspend()</code></strong>:  <strong>挂起任务</strong>。</p><ul><li><p><strong>功能</strong>:  将指定的任务挂起，使其进入挂起态，不再参与任务调度。</p></li><li><p><strong>常用场景</strong>:  暂时停止某个任务的执行，例如调试时暂停某个功能模块。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTaskToSuspend</code>:  要挂起的任务句柄。如果为 <code>NULL</code>，则挂起自身任务。</li></ul></li></ul></li><li><p><strong><code>vTaskResume()</code></strong>:  <strong>恢复任务</strong>。</p><ul><li><p><strong>功能</strong>:  恢复被挂起的任务，使其进入就绪态，重新参与任务调度。</p></li><li><p><strong>常用场景</strong>:  与 <code>vTaskSuspend()</code> 配对使用，在需要时恢复被挂起的任务。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTaskToResume</code>:  要恢复的任务句柄。</li></ul></li></ul></li><li><p><strong><code>uxTaskGetStackHighWaterMark()</code></strong>:  <strong>获取任务堆栈剩余空间</strong>。</p><ul><li><p><strong>功能</strong>:  获取任务堆栈当前剩余空间的大小，用于评估堆栈使用情况，避免堆栈溢出。</p></li><li><p><strong>常用场景</strong>:  调试阶段或性能优化时，检查任务堆栈是否足够，预防潜在的堆栈溢出问题。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTask</code>:  要查询的任务句柄。如果为 <code>NULL</code>，则查询自身任务。</li></ul></li></ul></li><li><p><strong><code>pcTaskGetName()</code></strong>: <strong>获取任务名称</strong>。</p><ul><li><p><strong>功能</strong>: 获取指定任务的任务名称字符串。</p></li><li><p><strong>常用场景</strong>:  调试信息输出，日志记录等，方便识别任务。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTaskToQuery</code>: 要查询的任务句柄。如果为 <code>NULL</code>，则查询自身任务。</li></ul></li></ul></li></ul><p><strong>2. 队列管理 (Queue Management)</strong></p><ul><li><p><strong><code>xQueueCreate()</code></strong>:  <strong>创建队列</strong>。</p><ul><li><p><strong>功能</strong>:  创建一个新的队列，用于任务间或 ISR 与任务间的数据传递。</p></li><li><p><strong>常用场景</strong>:  任务间通信，数据缓冲，消息传递。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>uxQueueLength</code>:  队列的长度，即队列可以存储的最大消息数量。</li><li><code>uxItemSize</code>:  队列中每个消息的长度，以字节为单位。如果消息是结构体，则为结构体的大小。</li></ul></li></ul></li><li><p><strong><code>xQueueSend()</code> &#x2F; <code>xQueueSendToBack()</code></strong>:  <strong>发送消息到队列尾部</strong> (前者是宏定义，后者是函数)。</p><ul><li><p><strong>功能</strong>:  向队列的尾部发送一条消息。</p></li><li><p><strong>常用场景</strong>:  任务向队列发送数据。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xQueue</code>:  要发送消息的队列句柄。</li><li><code>pvItemToQueue</code>:  指向要发送的消息数据的指针。</li><li><code>xTicksToWait</code>:  阻塞等待时间，如果队列已满，任务将阻塞等待，直到队列有空间或超时。设置为 0 表示非阻塞发送，队列满则立即返回错误。<code>portMAX_DELAY</code> 表示永久阻塞等待。</li></ul></li></ul></li><li><p><strong><code>xQueueSendToFront()</code></strong>:  <strong>发送消息到队列头部</strong>。</p><ul><li><strong>功能</strong>:  向队列的头部发送一条消息。</li><li><strong>常用场景</strong>:  发送高优先级消息，需要优先处理的消息。</li><li><strong>参数</strong>:  与 <code>xQueueSend()</code> 类似。</li></ul></li><li><p><strong><code>xQueueReceive()</code></strong>:  <strong>接收队列消息</strong>。</p><ul><li><p><strong>功能</strong>:  从队列中接收一条消息。</p></li><li><p><strong>常用场景</strong>:  任务从队列接收数据。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xQueue</code>:  要接收消息的队列句柄。</li><li><code>pvBuffer</code>:  指向接收消息数据缓冲区的指针。</li><li><code>xTicksToWait</code>:  阻塞等待时间，如果队列为空，任务将阻塞等待，直到队列有消息或超时。设置为 0 表示非阻塞接收，队列空则立即返回错误。<code>portMAX_DELAY</code> 表示永久阻塞等待。</li></ul></li></ul></li><li><p><strong><code>uxQueueMessagesWaiting()</code></strong>:  <strong>获取队列中消息数量</strong>。</p><ul><li><p><strong>功能</strong>:  获取当前队列中已有的消息数量。</p></li><li><p><strong>常用场景</strong>:  查询队列状态，判断队列是否为空或已满。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xQueue</code>:  要查询的队列句柄。</li></ul></li></ul></li><li><p><strong><code>vQueueDelete()</code></strong>:  <strong>删除队列</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的队列，释放队列占用的内存。</p></li><li><p><strong>常用场景</strong>:  队列不再需要时，释放资源。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xQueueToDelete</code>:  要删除的队列句柄。</li></ul></li></ul></li></ul><p><strong>3. 信号量管理 (Semaphore Management)</strong></p><ul><li><p><strong><code>xSemaphoreCreateBinary()</code></strong>:  <strong>创建二值信号量</strong>。</p><ul><li><strong>功能</strong>:  创建一个二值信号量，初始状态可以为可用或不可用。二值信号量只能取 0 和 1 两个值。</li><li><strong>常用场景</strong>:  互斥访问共享资源，任务同步 (例如事件触发)。</li><li><strong>返回</strong>:  信号量句柄，创建失败返回 <code>NULL</code>。</li></ul></li><li><p><strong><code>xSemaphoreCreateCounting()</code></strong>:  <strong>创建计数信号量</strong>。</p><ul><li><p><strong>功能</strong>:  创建一个计数信号量，可以设定最大计数值和初始计数值。计数信号量可以允许多个任务访问资源 (但访问数量有限制)。</p></li><li><p><strong>常用场景</strong>:  资源计数，例如控制访问具有多个槽位的资源池 (例如缓冲区管理)。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>uxMaxCount</code>:  信号量的最大计数值。</li><li><code>uxInitialCount</code>:  信号量的初始计数值。</li></ul></li><li><p><strong>返回</strong>:  信号量句柄，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>xSemaphoreCreateMutex()</code></strong>:  **创建互斥信号量 (互斥锁)**。</p><ul><li><strong>功能</strong>:  创建一个互斥信号量，用于保护共享资源，防止多个任务同时访问造成数据竞争。互斥信号量具有优先级继承机制，可以避免优先级反转问题。</li><li><strong>常用场景</strong>:  保护临界区，例如访问共享外设、全局变量等。</li><li><strong>返回</strong>:  互斥信号量句柄，创建失败返回 <code>NULL</code>。</li></ul></li><li><p><strong><code>xSemaphoreTake()</code></strong>:  <strong>获取信号量</strong>。</p><ul><li><p><strong>功能</strong>:  尝试获取信号量，如果信号量不可用，则任务进入阻塞态等待信号量可用。</p></li><li><p><strong>常用场景</strong>:  任务访问共享资源前，先获取信号量。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xSemaphore</code>:  要获取的信号量句柄。</li><li><code>xTicksToWait</code>:  阻塞等待时间，如果信号量不可用，任务将阻塞等待，直到信号量可用或超时。<code>portMAX_DELAY</code> 表示永久阻塞等待。</li></ul></li></ul></li><li><p><strong><code>xSemaphoreGive()</code></strong>:  <strong>释放信号量</strong>。</p><ul><li><p><strong>功能</strong>:  释放信号量，使信号量可用。</p></li><li><p><strong>常用场景</strong>:  任务访问完共享资源后，释放信号量，允许其他任务访问。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xSemaphore</code>:  要释放的信号量句柄。</li></ul></li></ul></li><li><p><strong><code>vSemaphoreDelete()</code></strong>:  <strong>删除信号量</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的信号量，释放信号量占用的内存。</p></li><li><p><strong>常用场景</strong>:  信号量不再需要时，释放资源。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xSemaphoreToDelete</code>:  要删除的信号量句柄。</li></ul></li></ul></li></ul><p><strong>4. 互斥量 (Mutex) 管理</strong></p><ul><li><strong>互斥信号量 <code>xSemaphoreCreateMutex()</code>  实际上就是互斥量，因此互斥量管理 API 与信号量管理 API 在互斥量部分是重合的。</strong>  常用的互斥量操作 API 实际上就是 <code>xSemaphoreTake()</code> 和 <code>xSemaphoreGive()</code>。</li></ul><p><strong>5. 事件组管理 (Event Group Management)</strong></p><ul><li><p><strong><code>xEventGroupCreate()</code></strong>:  <strong>创建事件组</strong>。</p><ul><li><strong>功能</strong>:  创建一个事件组，用于任务间的事件同步。一个事件组可以包含多个事件标志位。</li><li><strong>常用场景</strong>:  多事件同步，一个任务需要等待多个事件发生后才能执行。</li><li><strong>返回</strong>:  事件组句柄，创建失败返回 <code>NULL</code>。</li></ul></li><li><p><strong><code>xEventGroupSetBits()</code></strong>:  <strong>设置事件位</strong>。</p><ul><li><p><strong>功能</strong>:  设置事件组中指定的事件位。</p></li><li><p><strong>常用场景</strong>:  事件发生时，设置对应的事件位。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xEventGroup</code>:  要设置事件位的事件组句柄。</li><li><code>uxBitsToSet</code>:  要设置的事件位掩码。可以使用位操作来设置多个事件位。</li></ul></li></ul></li><li><p><strong><code>xEventGroupWaitBits()</code></strong>:  <strong>等待事件位</strong>。</p><ul><li><p><strong>功能</strong>:  等待事件组中指定的事件位被设置。</p></li><li><p><strong>常用场景</strong>:  任务等待指定的事件发生。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xEventGroup</code>:  要等待事件位的事件组句柄。</li><li><code>uxBitsToWaitFor</code>:  要等待的事件位掩码。</li><li><code>xClearOnExit</code>:  退出时是否清除等待的事件位。</li><li><code>xWaitForAllBits</code>:  是否等待所有指定的事件位都被设置。</li><li><code>xTicksToWait</code>:  阻塞等待时间。</li></ul></li><li><p><strong>返回</strong>:  实际触发的事件位掩码。</p></li></ul></li><li><p><strong><code>xEventGroupClearBits()</code></strong>:  <strong>清除事件位</strong>。</p><ul><li><p><strong>功能</strong>:  清除事件组中指定的事件位。</p></li><li><p><strong>常用场景</strong>:  事件处理完成后，清除事件位，准备下一次事件触发。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xEventGroup</code>:  要清除事件位的事件组句柄。</li><li><code>uxBitsToClear</code>:  要清除的事件位掩码。</li></ul></li></ul></li><li><p><strong><code>vEventGroupDelete()</code></strong>:  <strong>删除事件组</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的事件组，释放事件组占用的内存。</p></li><li><p><strong>常用场景</strong>:  事件组不再需要时，释放资源。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xEventGroupToDelete</code>:  要删除的事件组句柄。</li></ul></li></ul></li></ul><p><strong>6. 软件定时器管理 (Software Timer Management)</strong></p><ul><li><p><strong><code>xTimerCreate()</code></strong>:  <strong>创建软件定时器</strong>。</p><ul><li><p><strong>功能</strong>:  创建一个软件定时器，可以设置定时器周期和回调函数。</p></li><li><p><strong>常用场景</strong>:  周期性任务触发，延时操作，事件触发等。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>pcTimerName</code>:  定时器名称，字符串形式。</li><li><code>xTimerPeriodInTicks</code>:  定时器周期，以 tick 为单位。</li><li><code>uxAutoReload</code>:  自动重载标志，<code>pdTRUE</code> 为自动重载 (周期定时器)，<code>pdFALSE</code> 为单次触发定时器。</li><li><code>pvTimerID</code>:  定时器 ID，用户自定义数据，可以传递给定时器回调函数。</li><li><code>pxCallbackFunction</code>:  定时器回调函数指针，定时器超时后执行的函数。</li></ul></li><li><p><strong>返回</strong>:  定时器句柄，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>xTimerStart()</code></strong>:  <strong>启动定时器</strong>。</p><ul><li><p><strong>功能</strong>:  启动指定的定时器。</p></li><li><p><strong>常用场景</strong>:  在需要开始定时时启动定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTimer</code>:  要启动的定时器句柄。</li><li><code>xTicksToWait</code>:  阻塞等待时间 (通常设置为 0 或 <code>portMAX_DELAY</code>，在定时器服务任务上下文中可以阻塞等待，在其他任务上下文中不应该阻塞等待)。</li></ul></li></ul></li><li><p><strong><code>xTimerStop()</code></strong>:  <strong>停止定时器</strong>。</p><ul><li><p><strong>功能</strong>:  停止指定的定时器。</p></li><li><p><strong>常用场景</strong>:  在不需要定时器触发时停止定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTimer</code>:  要停止的定时器句柄。</li><li><code>xTicksToWait</code>:  阻塞等待时间 (同 <code>xTimerStart()</code> )。</li></ul></li></ul></li><li><p><strong><code>xTimerChangePeriod()</code></strong>:  <strong>修改定时器周期</strong>。</p><ul><li><p><strong>功能</strong>:  动态修改定时器的周期。</p></li><li><p><strong>常用场景</strong>:  在运行时根据需要调整定时器周期。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTimer</code>:  要修改周期的定时器句柄。</li><li><code>xNewPeriodInTicks</code>:  新的定时器周期，以 tick 为单位。</li><li><code>xTicksToWait</code>:  阻塞等待时间 (同 <code>xTimerStart()</code> )。</li></ul></li></ul></li><li><p><strong><code>xTimerIsTimerActive()</code></strong>:  <strong>判断定时器是否激活</strong>。</p><ul><li><p><strong>功能</strong>:  判断指定的定时器是否处于激活状态。</p></li><li><p><strong>常用场景</strong>:  查询定时器状态。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTimer</code>:  要查询的定时器句柄。</li></ul></li><li><p><strong>返回</strong>:  <code>pdTRUE</code> 表示激活，<code>pdFALSE</code> 表示未激活。</p></li></ul></li><li><p><strong><code>xTimerDelete()</code></strong>:  <strong>删除定时器</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的定时器，释放定时器占用的内存。</p></li><li><p><strong>常用场景</strong>:  定时器不再需要时，释放资源。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xTimerToDelete</code>:  要删除的定时器句柄。</li><li><code>xTicksToWait</code>:  阻塞等待时间 (同 <code>xTimerStart()</code> )。</li></ul></li></ul></li></ul><p><strong>7. 内存管理 (Memory Management)</strong></p><ul><li><p><strong><code>pvPortMalloc()</code></strong>:  <strong>动态内存分配</strong> (FreeRTOS 的内存分配函数)。</p><ul><li><p><strong>功能</strong>:  从 FreeRTOS 的堆内存中分配指定大小的内存块。</p></li><li><p><strong>常用场景</strong>:  动态创建 FreeRTOS 对象 (例如队列、信号量、任务等) 或应用程序需要动态内存分配时。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xWantedSize</code>:  要分配的内存块大小，以字节为单位。</li></ul></li><li><p><strong>返回</strong>:  指向分配的内存块的指针，分配失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>vPortFree()</code></strong>:  <strong>动态内存释放</strong> (FreeRTOS 的内存释放函数)。</p><ul><li><p><strong>功能</strong>:  释放由 <code>pvPortMalloc()</code> 分配的内存块。</p></li><li><p><strong>常用场景</strong>:  释放不再使用的动态分配内存，避免内存泄漏。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>pvBlockToFree</code>:  指向要释放的内存块的指针，必须是由 <code>pvPortMalloc()</code> 分配的内存块。</li></ul></li></ul></li></ul><p><strong>8. 中断管理 (Interrupt Management)</strong></p><ul><li><p><strong><code>xSemaphoreGiveFromISR()</code></strong>:  <strong>在中断服务例程 (ISR) 中释放信号量</strong>。</p><ul><li><p><strong>功能</strong>:  在 ISR 中释放信号量，用于通知任务发生了某个事件。</p></li><li><p><strong>常用场景</strong>:  ISR 中检测到硬件事件，需要通知任务进行处理时。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xSemaphore</code>:  要释放的信号量句柄。</li><li><code>pxHigherPriorityTaskWoken</code>:  输出参数，用于指示是否有更高优先级的任务因为释放信号量而进入就绪态，需要进行上下文切换。通常需要根据该参数判断是否需要手动触发上下文切换 (<code>portYIELD_FROM_ISR()</code> 或 <code>taskYIELD_FROM_ISR()</code> )。</li></ul></li></ul></li><li><p><strong><code>xQueueSendFromISR()</code> &#x2F; <code>xQueueSendToBackFromISR()</code></strong>:  <strong>在 ISR 中发送消息到队列尾部</strong>。</p><ul><li><p><strong>功能</strong>:  在 ISR 中向队列发送消息，用于 ISR 与任务间的数据传递。</p></li><li><p><strong>常用场景</strong>:  ISR 中接收到数据，需要传递给任务进行处理时。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>xQueue</code>:  要发送消息的队列句柄。</li><li><code>pvItemToQueue</code>:  指向要发送的消息数据的指针。</li><li><code>pxHigherPriorityTaskWoken</code>:  输出参数，同 <code>xSemaphoreGiveFromISR()</code>。</li></ul></li></ul></li><li><p><strong><code>xQueueSendToFrontFromISR()</code></strong>:  <strong>在 ISR 中发送消息到队列头部</strong>。</p><ul><li><strong>功能</strong>:  在 ISR 中向队列头部发送消息。</li><li><strong>参数</strong>:  与 <code>xQueueSendFromISR()</code> 类似。</li></ul></li><li><p><strong><code>portYIELD_FROM_ISR()</code> &#x2F; <code>taskYIELD_FROM_ISR()</code></strong>:  <strong>在 ISR 中触发上下文切换</strong> (与具体的 FreeRTOS 移植有关)。</p><ul><li><p><strong>功能</strong>:  在 ISR 中手动触发上下文切换，将 CPU 使用权交给更高优先级的就绪态任务。</p></li><li><p><strong>常用场景</strong>:  当 ISR 中释放信号量或发送消息到队列，导致更高优先级的任务进入就绪态时，需要触发上下文切换，立即执行高优先级任务，提高系统实时性。</p></li><li><p>参数</p><p>:</p><ul><li>无参数，根据 <code>xSemaphoreGiveFromISR()</code> 或 <code>xQueueSendFromISR()</code> 的 <code>pxHigherPriorityTaskWoken</code> 参数判断是否需要调用。</li></ul></li></ul></li></ul><p><strong>二、CMSIS-V2 RTOS API (在 STM32CubeMX 中常用)</strong></p><p>CMSIS-V2 RTOS API 提供了一套标准化的 RTOS 接口，在 STM32CubeMX 中，如果您选择了 CMSIS_RTOS_V2，则可以使用 CMSIS-V2 标准接口来操作 FreeRTOS 内核对象。CMSIS-V2 RTOS API 实际上是对原生 FreeRTOS API 的一层封装，其底层实现仍然是调用原生 FreeRTOS API。</p><p>使用 CMSIS-V2 RTOS API 的好处是可以提高代码的可移植性，如果将来需要更换 RTOS，只需要修改 RTOS 适配层即可，应用程序代码可以基本保持不变。</p><p>以下是一些常用的 CMSIS-V2 RTOS API 接口，对应于原生 FreeRTOS API 的功能模块：</p><p><strong>1. 任务管理 (Thread Management)</strong>  (CMSIS-V2 中任务称为线程)</p><ul><li><p><strong><code>osThreadNew()</code></strong>:  <strong>创建线程</strong> (对应 <code>xTaskCreate()</code> )。</p><ul><li><p><strong>功能</strong>:  创建并启动一个新的线程。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_def</code>:  线程定义结构体指针，包含线程入口函数、名称、优先级、堆栈大小等信息。</li><li><code>thread_attr</code>:  线程属性结构体指针，可以配置线程属性，例如堆栈、优先级等，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  线程 ID (<code>osThreadId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osThreadTerminate()</code></strong>:  <strong>终止线程</strong> (对应 <code>vTaskDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  终止指定的线程。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_id</code>:  要终止的线程 ID。</li></ul></li></ul></li><li><p><strong><code>osDelay()</code></strong>:  <strong>线程延时</strong> (对应 <code>vTaskDelay()</code> )。</p><ul><li><p><strong>功能</strong>:  使当前线程进入延时状态。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ticks</code>:  延时的时间，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osThreadSuspend()</code></strong>:  <strong>挂起线程</strong> (对应 <code>vTaskSuspend()</code> )。</p><ul><li><p><strong>功能</strong>:  挂起指定的线程。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_id</code>:  要挂起的线程 ID。</li></ul></li></ul></li><li><p><strong><code>osThreadResume()</code></strong>:  <strong>恢复线程</strong> (对应 <code>vTaskResume()</code> )。</p><ul><li><p><strong>功能</strong>:  恢复被挂起的线程。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_id</code>:  要恢复的线程 ID。</li></ul></li></ul></li><li><p><strong><code>osThreadGetStackSpace()</code></strong>:  <strong>获取线程堆栈剩余空间</strong> (对应 <code>uxTaskGetStackHighWaterMark()</code> )。</p><ul><li><p><strong>功能</strong>:  获取线程堆栈当前剩余空间的大小。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_id</code>:  要查询的线程 ID。</li></ul></li></ul></li><li><p><strong><code>osThreadGetName()</code></strong>: <strong>获取线程名称</strong> (对应 <code>pcTaskGetName()</code> )。</p><ul><li><p><strong>功能</strong>: 获取指定线程的线程名称字符串。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>thread_id</code>: 要查询的线程 ID。</li></ul></li></ul></li></ul><p><strong>2. 队列管理 (Message Queue Management)</strong> (CMSIS-V2 中队列称为消息队列)</p><ul><li><p><strong><code>osMessageQueueNew()</code></strong>:  <strong>创建消息队列</strong> (对应 <code>xQueueCreate()</code> )。</p><ul><li><p><strong>功能</strong>:  创建一个新的消息队列。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>msg_count</code>:  消息队列的长度，即队列可以存储的最大消息数量。</li><li><code>msg_size</code>:  队列中每个消息的长度，以字节为单位。</li><li><code>queue_attr</code>:  队列属性结构体指针，可以配置队列属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  消息队列 ID (<code>osMessageQueueId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osMessageQueuePut()</code></strong>:  <strong>发送消息到消息队列尾部</strong> (对应 <code>xQueueSend()</code> &#x2F; <code>xQueueSendToBack()</code> )。</p><ul><li><p><strong>功能</strong>:  向消息队列的尾部发送一条消息。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mq_id</code>:  要发送消息的消息队列 ID。</li><li><code>msg_ptr</code>:  指向要发送的消息数据的指针。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osMessageQueueGet()</code></strong>:  <strong>接收消息队列消息</strong> (对应 <code>xQueueReceive()</code> )。</p><ul><li><p><strong>功能</strong>:  从消息队列中接收一条消息。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mq_id</code>:  要接收消息的消息队列 ID。</li><li><code>msg_ptr</code>:  指向接收消息数据缓冲区的指针。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osMessageQueueGetCount()</code></strong>:  <strong>获取消息队列中消息数量</strong> (对应 <code>uxQueueMessagesWaiting()</code> )。</p><ul><li><p><strong>功能</strong>:  获取当前消息队列中已有的消息数量。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mq_id</code>:  要查询的消息队列 ID。</li></ul></li></ul></li><li><p><strong><code>osMessageQueueDelete()</code></strong>:  <strong>删除消息队列</strong> (对应 <code>vQueueDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  删除指定的消息队列。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mq_id</code>:  要删除的消息队列 ID。</li></ul></li></ul></li></ul><p><strong>3. 信号量管理 (Semaphore Management)</strong></p><ul><li><p><strong><code>osSemaphoreNew()</code></strong>:  <strong>创建信号量</strong> (对应 <code>xSemaphoreCreateBinary()</code> 和 <code>xSemaphoreCreateCounting()</code> )。</p><ul><li><p>功能</p><p>:  创建一个信号量，可以创建二值信号量或计数信号量，根据 </p><pre class="language-none"><code class="language-none">max_signals</code></pre><p> 参数决定。</p><ul><li>如果 <code>max_signals</code> 为 1，则创建二值信号量。</li><li>如果 <code>max_signals</code> 大于 1，则创建计数信号量。</li></ul></li><li><p>关键参数</p><p>:</p><ul><li><code>max_signals</code>:  信号量的最大计数数量 (二值信号量为 1，计数信号量大于 1)。</li><li><code>initial_signals</code>:  信号量的初始计数数量。</li><li><code>semaphore_attr</code>:  信号量属性结构体指针，可以配置信号量属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  信号量 ID (<code>osSemaphoreId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osSemaphoreAcquire()</code></strong>:  <strong>获取信号量</strong> (对应 <code>xSemaphoreTake()</code> )。</p><ul><li><p><strong>功能</strong>:  尝试获取信号量。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>semaphore_id</code>:  要获取的信号量 ID。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osSemaphoreRelease()</code></strong>:  <strong>释放信号量</strong> (对应 <code>xSemaphoreGive()</code> )。</p><ul><li><p><strong>功能</strong>:  释放信号量。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>semaphore_id</code>:  要释放的信号量 ID。</li></ul></li></ul></li><li><p><strong><code>osSemaphoreDelete()</code></strong>:  <strong>删除信号量</strong> (对应 <code>vSemaphoreDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  删除指定的信号量。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>semaphore_id</code>:  要删除的信号量 ID。</li></ul></li></ul></li></ul><p><strong>4. 互斥锁 (Mutex) 管理</strong></p><ul><li><p><strong><code>osMutexNew()</code></strong>:  <strong>创建互斥锁</strong> (对应 <code>xSemaphoreCreateMutex()</code> )。</p><ul><li><p><strong>功能</strong>:  创建一个互斥锁。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mutex_attr</code>:  互斥锁属性结构体指针，可以配置互斥锁属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  互斥锁 ID (<code>osMutexId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osMutexAcquire()</code></strong>:  <strong>获取互斥锁</strong> (对应 <code>xSemaphoreTake()</code> )。</p><ul><li><p><strong>功能</strong>:  尝试获取互斥锁。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mutex_id</code>:  要获取的互斥锁 ID。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osMutexRelease()</code></strong>:  <strong>释放互斥锁</strong> (对应 <code>xSemaphoreGive()</code> )。</p><ul><li><p><strong>功能</strong>:  释放互斥锁。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mutex_id</code>:  要释放的互斥锁 ID。</li></ul></li></ul></li><li><p><strong><code>osMutexDelete()</code></strong>:  <strong>删除互斥锁</strong> (对应 <code>vSemaphoreDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  删除指定的互斥锁。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mutex_id</code>:  要删除的互斥锁 ID。</li></ul></li></ul></li></ul><p><strong>5. 事件标志 (Event Flags) 管理</strong> (CMSIS-V2 中事件组称为事件标志)</p><ul><li><p><strong><code>osEventFlagsNew()</code></strong>:  <strong>创建事件标志</strong> (对应 <code>xEventGroupCreate()</code> )。</p><ul><li><p><strong>功能</strong>:  创建一个事件标志组。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ef_attr</code>:  事件标志属性结构体指针，可以配置事件标志属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  事件标志 ID (<code>osEventFlagsId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osEventFlagsSet()</code></strong>:  <strong>设置事件标志位</strong> (对应 <code>xEventGroupSetBits()</code> )。</p><ul><li><p><strong>功能</strong>:  设置事件标志组中指定的事件位。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ef_id</code>:  要设置事件位的事件标志 ID。</li><li><code>flags</code>:  要设置的事件位掩码。</li></ul></li></ul></li><li><p><strong><code>osEventFlagsWait()</code></strong>:  <strong>等待事件标志位</strong> (对应 <code>xEventGroupWaitBits()</code> )。</p><ul><li><p><strong>功能</strong>:  等待事件标志组中指定的事件位被设置。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ef_id</code>:  要等待事件位的事件标志 ID。</li><li><code>flags</code>:  要等待的事件位掩码。</li><li><code>options</code>:  等待选项，例如 <code>osFlagsWaitAny</code> (等待任意一个事件位被设置) 或 <code>osFlagsWaitAll</code> (等待所有指定的事件位都被设置)。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li><li><p><strong>返回</strong>:  实际触发的事件位掩码。</p></li></ul></li><li><p><strong><code>osEventFlagsClear()</code></strong>:  <strong>清除事件标志位</strong> (对应 <code>xEventGroupClearBits()</code> )。</p><ul><li><p><strong>功能</strong>:  清除事件标志组中指定的事件位。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ef_id</code>:  要清除事件位的事件标志 ID。</li><li><code>flags</code>:  要清除的事件位掩码。</li></ul></li></ul></li><li><p><strong><code>osEventFlagsDelete()</code></strong>:  <strong>删除事件标志</strong> (对应 <code>vEventGroupDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  删除指定的事件标志组。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>ef_id</code>:  要删除的事件标志 ID。</li></ul></li></ul></li></ul><p><strong>6. 定时器 (Timer) 管理</strong> (CMSIS-V2 中软件定时器称为定时器)</p><ul><li><p><strong><code>osTimerNew()</code></strong>:  <strong>创建定时器</strong> (对应 <code>xTimerCreate()</code> )。</p><ul><li><p><strong>功能</strong>:  创建一个定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>callback</code>:  定时器回调函数指针。</li><li><code>type</code>:  定时器类型，<code>osTimerOnce</code> (单次触发) 或 <code>osTimerPeriodic</code> (周期触发)。</li><li><code>timer_attr</code>:  定时器属性结构体指针，可以配置定时器属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  定时器 ID (<code>osTimerId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osTimerStart()</code></strong>:  <strong>启动定时器</strong> (对应 <code>xTimerStart()</code> )。</p><ul><li><p><strong>功能</strong>:  启动指定的定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>timer_id</code>:  要启动的定时器 ID。</li><li><code>period</code>:  定时器周期，以 tick 为单位。</li></ul></li></ul></li><li><p><strong><code>osTimerStop()</code></strong>:  <strong>停止定时器</strong> (对应 <code>xTimerStop()</code> )。</p><ul><li><p><strong>功能</strong>:  停止指定的定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>timer_id</code>:  要停止的定时器 ID。</li></ul></li></ul></li><li><p><strong><code>osTimerIsRunning()</code></strong>:  <strong>判断定时器是否运行</strong> (对应 <code>xTimerIsTimerActive()</code> )。</p><ul><li><p><strong>功能</strong>:  判断指定的定时器是否处于运行状态。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>timer_id</code>:  要查询的定时器 ID。</li></ul></li><li><p><strong>返回</strong>:  <code>true</code> 表示运行，<code>false</code> 表示停止。</p></li></ul></li><li><p><strong><code>osTimerDelete()</code></strong>:  <strong>删除定时器</strong> (对应 <code>xTimerDelete()</code> )。</p><ul><li><p><strong>功能</strong>:  删除指定的定时器。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>timer_id</code>:  要删除的定时器 ID。</li></ul></li></ul></li></ul><p><strong>7. 内存管理 (Memory Pool Management)</strong> (CMSIS-V2 中没有直接对应 <code>pvPortMalloc()</code> 和 <code>vPortFree()</code> 的标准 API，CMSIS-V2 提供了内存池管理，用于管理固定大小的内存块，如果需要动态内存分配，仍然需要使用原生 FreeRTOS 的 <code>pvPortMalloc()</code> 和 <code>vPortFree()</code>  或者 C 标准库的 <code>malloc()</code> 和 <code>free()</code> )</p><ul><li><p><strong><code>osMemoryPoolNew()</code></strong>:  <strong>创建内存池</strong>。</p><ul><li><p><strong>功能</strong>:  创建一个内存池，用于管理固定大小的内存块。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>block_count</code>:  内存池中内存块的数量。</li><li><code>block_size</code>:  每个内存块的大小，以字节为单位。</li><li><code>mp_attr</code>:  内存池属性结构体指针，可以配置内存池属性，可以设置为 <code>NULL</code> 使用默认属性。</li></ul></li><li><p><strong>返回</strong>:  内存池 ID (<code>osMemoryPoolId_t</code>)，创建失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osMemoryPoolAlloc()</code></strong>:  <strong>从内存池分配内存块</strong>。</p><ul><li><p><strong>功能</strong>:  从内存池中分配一个内存块。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mp_id</code>:  要分配内存的内存池 ID。</li><li><code>timeout</code>:  阻塞等待时间，以 tick 为单位。</li></ul></li><li><p><strong>返回</strong>:  指向分配的内存块的指针，分配失败返回 <code>NULL</code>。</p></li></ul></li><li><p><strong><code>osMemoryPoolFree()</code></strong>:  <strong>释放内存块到内存池</strong>。</p><ul><li><p><strong>功能</strong>:  将从内存池分配的内存块释放回内存池。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mp_id</code>:  要释放内存的内存池 ID。</li><li><code>block</code>:  指向要释放的内存块的指针。</li></ul></li></ul></li><li><p><strong><code>osMemoryPoolDelete()</code></strong>:  <strong>删除内存池</strong>。</p><ul><li><p><strong>功能</strong>:  删除指定的内存池。</p></li><li><p>关键参数</p><p>:</p><ul><li><code>mp_id</code>:  要删除的内存池 ID。</li></ul></li></ul></li></ul>]]>
    </content>
    <id>https://lsworl.github.io/2025/02/12/freertos-zhong-chang-yong-api-zong-jie/</id>
    <link href="https://lsworl.github.io/2025/02/12/freertos-zhong-chang-yong-api-zong-jie/"/>
    <published>2025-02-12T06:43:35.000Z</published>
    <summary>汇总 FreeRTOS 任务管理、队列、信号量、软件定时器等常用 API 的作用、参数和典型使用场景。</summary>
    <title>freertos中常用api总结</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>所使用的模块与工具：</p><ul><li>STM32F103C8T6</li><li>DHT11温湿度传感器</li><li>ESP32WROOM(采用ESP-IDF)</li><li>EMQX Serverless（MQTT Broker）</li><li>TiDB Cloud（云数据库）</li><li>腾讯位置服务API</li><li>和风天气API</li></ul></blockquote><h1 id="硬件层"><a href="#硬件层" class="headerlink" title="硬件层"></a>硬件层</h1><h2 id="STM32"><a href="#STM32" class="headerlink" title="STM32"></a>STM32</h2><h3 id="Stm32Cubemx配置"><a href="#Stm32Cubemx配置" class="headerlink" title="Stm32Cubemx配置"></a>Stm32Cubemx配置</h3><blockquote><p>使用引脚:</p><ul><li>PA10-&gt;D17(USART1_RX连到ESP32的D17)</li><li>P9-&gt;D16(USART1_TX连到ESP32的D16)</li><li>PB10-&gt;LED（用于测试系统是否正常运作，设置为GPIO_Output，推挽输出）</li><li>PA0-&gt;DHT11 Do口（芯片左边标S的口，最右边有-的标志连GND，中间连VCC）</li></ul></blockquote><h4 id="Pinout-amp-Configuration"><a href="#Pinout-amp-Configuration" class="headerlink" title="Pinout&amp;Configuration"></a>Pinout&amp;Configuration</h4><blockquote><p>由于硬件层面比较简单，就不采用DMA来传输了</p></blockquote><h5 id="SystemCore配置"><a href="#SystemCore配置" class="headerlink" title="SystemCore配置"></a>SystemCore配置</h5><ul><li><p>GPIO</p><ul><li><p>PA0</p><ul><li>GPIO output level: low（默认输出低电平）</li><li>GPIO mode : Output Push Pull（推挽输出）</li><li>GPIO Pull-up&#x2F;Pull-down：No pull-up and pull-down（无上下拉）</li><li>Maximum output speed:Low（这里任意）</li></ul></li><li><p>PA10</p><ul><li>GPIO output level: low（默认输出低电平）</li><li>GPIO mode : Output Push Pull（推挽输出）</li><li>GPIO Pull-up&#x2F;Pull-down：No pull-up and pull-down（无上下拉）</li><li>Maximum output speed:Low（这里任意）</li></ul><p>USART配置采用默认。</p></li></ul></li><li><p>NVIC（USART1 global interrupt 使能，开启USART中断）</p></li><li><p>RCC</p><ul><li>高速，低速都配置外部晶振时钟</li></ul></li><li><p>SYS</p><ul><li>Debug：Serial Wire</li><li>Timebase Source：SysTick，时基源选系统滴答。</li></ul></li></ul><h5 id="Connectivity配置"><a href="#Connectivity配置" class="headerlink" title="Connectivity配置"></a>Connectivity配置</h5><p><strong>USART1</strong></p><ul><li>Mode：Asynchronous（异步）</li><li>Hardware Flow Control：Disable（不采用硬件流控制）</li><li>Parameter Settings：<ul><li>Baud Rate：115200 Bits&#x2F;s</li><li>Word Length: 8Bits</li><li>Parity: None</li><li>Stop Bits:1</li></ul></li></ul><h4 id="Clock-Configuration"><a href="#Clock-Configuration" class="headerlink" title="Clock Configuration"></a>Clock Configuration</h4><p><img src="https://i-blog.csdnimg.cn/direct/65f2857860d54aa2ad23f2ec290f7a11.png"></p><p>采用系统时钟为72MHz。</p><h4 id="Project-Manager"><a href="#Project-Manager" class="headerlink" title="Project Manager"></a>Project Manager</h4><p><img src="https://i-blog.csdnimg.cn/direct/7d574f01e7a34700999b3611af47e223.png"></p><p>输入项目名已经存放路径，还有IDE配置。</p><p><img src="https://i-blog.csdnimg.cn/direct/9bfdeb22779c438a8f7788be2b1e8a23.png"></p><p>选择只复制必要的库以及分开生成对应的c和h文件。</p><p>然后点GENERATE CODE生成代码。</p><h3 id="代码编写获取DHT11数据"><a href="#代码编写获取DHT11数据" class="headerlink" title="代码编写获取DHT11数据"></a>代码编写获取DHT11数据</h3><p>由于DHT11A通信中需要用到微妙延迟，而HAL库没有自带的微妙延迟函数，需要自己编写，这里就借助DWT来实现微妙延迟。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Enable_DWT</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span><span class="token comment">//Enable_DWT在main.c初始化中调用</span>    CoreDebug<span class="token operator">-></span>DEMCR <span class="token operator">|=</span> CoreDebug_DEMCR_TRCENA_Msk<span class="token punctuation">;</span> <span class="token comment">//enable DWT</span>    DWT<span class="token operator">-></span>CYCCNT <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// reset period counter</span>    DWT<span class="token operator">-></span>CTRL <span class="token operator">|=</span> DWT_CTRL_CYCCNTENA_Msk<span class="token punctuation">;</span> <span class="token comment">// enable period counter</span><span class="token punctuation">&#125;</span><span class="token keyword">void</span> <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token class-name">uint32_t</span> us<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token class-name">uint32_t</span> ticks <span class="token operator">=</span> us <span class="token operator">*</span> <span class="token punctuation">(</span>SystemCoreClock <span class="token operator">/</span> <span class="token number">1000000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// compute ticks</span>  <span class="token class-name">uint32_t</span> start <span class="token operator">=</span> DWT<span class="token operator">-></span>CYCCNT<span class="token punctuation">;</span>                      <span class="token comment">// get current period counter value</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>DWT<span class="token operator">-></span>CYCCNT <span class="token operator">-</span> start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> ticks<span class="token punctuation">)</span>    <span class="token punctuation">;</span> <span class="token comment">// wait for specified period</span><span class="token punctuation">&#125;</span></code></pre><p><img src="https://i-blog.csdnimg.cn/direct/ba4dc61504eb426590e14b77781c0094.png"></p><h4 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h4><p>上方是DHT11传输时序图，由于采用单总线通信，需要先将PA0默认置高电平，PA0口为推挽输出，代码如下。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// DHT11 数据结构体</span><span class="token keyword">typedef</span> <span class="token keyword">struct</span><span class="token punctuation">&#123;</span>    <span class="token class-name">uint8_t</span> humidity_int<span class="token punctuation">;</span>      <span class="token comment">// 湿度整数部分</span>    <span class="token class-name">uint8_t</span> humidity_dec<span class="token punctuation">;</span>      <span class="token comment">// 湿度小数部分</span>    <span class="token class-name">uint8_t</span> temperature_int<span class="token punctuation">;</span>   <span class="token comment">// 温度整数部分</span>    <span class="token class-name">uint8_t</span> temperature_dec<span class="token punctuation">;</span>   <span class="token comment">// 温度小数部分</span>    <span class="token class-name">uint8_t</span> check_sum<span class="token punctuation">;</span>        <span class="token comment">// 校验和</span><span class="token punctuation">&#125;</span> DHT11_Data_TypeDef<span class="token punctuation">;</span><span class="token comment">/** * @brief  初始化DHT11 * @param  无 * @retval 无 */</span><span class="token keyword">void</span> <span class="token function">DHT11_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">DHT11_GPIO_OUT</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">HAL_GPIO_WritePin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">,</span> GPIO_PIN_SET<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">HAL_Delay</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 上电等待1s</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @brief  配置DHT11的GPIO为输出模式 * @param  无 * @retval 无 */</span><span class="token keyword">void</span> <span class="token function">DHT11_GPIO_OUT</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStruct <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        GPIO_InitStruct<span class="token punctuation">.</span>Pin <span class="token operator">=</span> DHT11_Pin<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>Mode <span class="token operator">=</span> GPIO_MODE_OUTPUT_PP<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>Pull <span class="token operator">=</span> GPIO_NOPULL<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>Speed <span class="token operator">=</span> GPIO_SPEED_FREQ_LOW<span class="token punctuation">;</span>    <span class="token function">HAL_GPIO_Init</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @brief  配置DHT11的GPIO为输入模式 * @param  无 * @retval 无 */</span><span class="token keyword">void</span> <span class="token function">DHT11_GPIO_IN</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStruct <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token number">0</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        GPIO_InitStruct<span class="token punctuation">.</span>Pin <span class="token operator">=</span> DHT11_Pin<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>Mode <span class="token operator">=</span> GPIO_MODE_INPUT<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>Pull <span class="token operator">=</span> GPIO_NOPULL<span class="token punctuation">;</span>    <span class="token function">HAL_GPIO_Init</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="通信"><a href="#通信" class="headerlink" title="通信"></a>通信</h4><p>开始通信先将PA0口置低电平，等待2s，后再拉高，保持30us，同时将PA0口置为浮空输入模式，等待DHT11发送响应输出信号到PA0口（即将PA0口电平拉低），等待时间为100us。采用轮询的方式不断判断PA0口的电平，若为低，则将其拉高准备通信，若超时则认为通信失败。接下来就可以接受40bit的数据，前2字节为湿度，3-4字节为温度，最后一字节为校验，具体代码如下。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">/** * @brief  读取DHT11的数据 * @param  dht11_data: DHT11数据结构体指针 * @retval 0: 读取成功, 1: 读取失败 */</span><span class="token class-name">uint8_t</span> <span class="token function">DHT11_Read_Data</span><span class="token punctuation">(</span>DHT11_Data_TypeDef <span class="token operator">*</span>dht11_data<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token class-name">uint8_t</span> i<span class="token punctuation">,</span> j<span class="token punctuation">,</span> temp<span class="token punctuation">;</span>    <span class="token class-name">uint8_t</span> buf<span class="token punctuation">[</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token class-name">uint8_t</span> retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token comment">// 主机发送开始信号</span>    <span class="token function">DHT11_GPIO_OUT</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">HAL_GPIO_WritePin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">,</span> GPIO_PIN_RESET<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">20000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">HAL_GPIO_WritePin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">,</span> GPIO_PIN_SET<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//延迟30us</span>        <span class="token comment">// 切换为输入模式,等待DHT11响应</span>    <span class="token function">DHT11_GPIO_IN</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 等待DHT11拉低(响应信号) 等待100us</span>    retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> retry <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        retry<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>retry <span class="token operator">>=</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token comment">// 等待DHT11拉高</span>    retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> retry <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        retry<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>retry <span class="token operator">>=</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token comment">// 等待DHT11拉低(准备发送数据)</span>    retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> retry <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        retry<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>retry <span class="token operator">>=</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token comment">// 开始接收40bit数据</span>    <span class="token keyword">for</span><span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        temp <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span>j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">8</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>        <span class="token punctuation">&#123;</span>            <span class="token comment">// 等待50us低电平结束</span>            retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> retry <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">)</span>            <span class="token punctuation">&#123;</span>                retry<span class="token operator">++</span><span class="token punctuation">;</span>                <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>retry <span class="token operator">>=</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>                        <span class="token comment">// 延时40us</span>            <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment">// 判断数据位是1还是0</span>            temp <span class="token operator">&lt;&lt;=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">&#123;</span>                temp <span class="token operator">|=</span> <span class="token number">1</span><span class="token punctuation">;</span>                <span class="token comment">// 等待高电平结束</span>                retry <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>                <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token function">HAL_GPIO_ReadPin</span><span class="token punctuation">(</span>DHT11_GPIO_Port<span class="token punctuation">,</span> DHT11_Pin<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> retry <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">)</span>                <span class="token punctuation">&#123;</span>                    retry<span class="token operator">++</span><span class="token punctuation">;</span>                    <span class="token function">delay_us</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">&#125;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>retry <span class="token operator">>=</span> <span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>        buf<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> temp<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>        <span class="token comment">// 验证校验和</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token punctuation">(</span>buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> buf<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> buf<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">+</span> buf<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        dht11_data<span class="token operator">-></span>humidity_int <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dht11_data<span class="token operator">-></span>humidity_dec <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dht11_data<span class="token operator">-></span>temperature_int <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dht11_data<span class="token operator">-></span>temperature_dec <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dht11_data<span class="token operator">-></span>check_sum <span class="token operator">=</span> buf<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>        <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>下个模块实现将DHT11获取的温湿度信息通过USART传给ESP32。</p><h3 id="温湿度传给ESP32"><a href="#温湿度传给ESP32" class="headerlink" title="温湿度传给ESP32"></a>温湿度传给ESP32</h3><p>首先先重写<code>printf</code>函数，映射到usart1，在<code>usart.c</code>中添加该函数即可实现映射。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">int</span> <span class="token function">fputc</span><span class="token punctuation">(</span><span class="token keyword">int</span> ch<span class="token punctuation">,</span> FILE <span class="token operator">*</span>f<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">HAL_UART_Transmit</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>huart1<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">uint8_t</span> <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&amp;</span>ch<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> ch<span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>接下来在<code>main.c</code>中编写。</p><pre class="language-c" data-language="c"><code class="language-c">DHT11_Data_TypeDef dht11_data<span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">DHT11_Read_Data</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>dht11_data<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"Temperature: %d.%dC, Humidity: %d.%d%%"</span><span class="token punctuation">,</span>               dht11_data<span class="token punctuation">.</span>temperature_int<span class="token punctuation">,</span> dht11_data<span class="token punctuation">.</span>temperature_dec<span class="token punctuation">,</span>               dht11_data<span class="token punctuation">.</span>humidity_int<span class="token punctuation">,</span> dht11_data<span class="token punctuation">.</span>humidity_dec<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">else</span>    <span class="token punctuation">&#123;</span>        <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"DHT11 Read Failed!\r\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span></code></pre><p>这样要是获取到的湿度是60.1，温度是12.2的话，ESP32通过串口接受到的就是<code>Temperature: 12.2C, Humidity: 60.1%</code></p><h2 id="ESP32"><a href="#ESP32" class="headerlink" title="ESP32"></a>ESP32</h2><blockquote><p>ESP32采用在ubuntu 20.04系统上用esp-idf编程，在此前在ubuntu上安装对应依赖</p><p>使用命令<code>sudo apt-get install git wget flex bison gperf python3-pip python3-venv cmake ninja-build ccache libffi-dev dfu-util libusb-1.0-0 net-tools</code></p><p>先拉取esp工具</p><p><code>git clone https://gitee.com/EspressifSystems/esp-gitee-tools.git</code></p><p>到esp-gitee-tools目录下执行<code>jihu-mirror.sh set</code>，将镜像改为极狐</p><p>然后通过git拉取esp-idf</p><p><code>git clone --recursive https://github.com/espressif/esp-idf.git</code></p><p>再在esp-gitee-tools目录执行<code>install.sh</code>，安装编译工具。</p><p>还需要设置esp-idf的环境变量，在esp-idf目录下执行<code>source export.sh</code>，就会自动设置环境变量。</p><p>若烧录<code>idf.py flash</code>权限不够时，直接将权限设置为最大,<code>sudo chmod 777 /dev/ttyUSB0</code>。</p></blockquote><h3 id="WIFI模块实现"><a href="#WIFI模块实现" class="headerlink" title="WIFI模块实现"></a>WIFI模块实现</h3><pre class="language-c" data-language="c"><code class="language-c"><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">WIFI_SSID</span> <span class="token string">"wifi账号"</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">WIFI_PASS</span> <span class="token string">"wifi密码"</span></span><span class="token keyword">static</span> <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>TAG <span class="token operator">=</span> <span class="token string">"WIFI_LINK"</span><span class="token punctuation">;</span><span class="token keyword">static</span> bool s_wifi_connected <span class="token operator">=</span> false<span class="token punctuation">;</span><span class="token keyword">static</span> <span class="token class-name">esp_netif_t</span> <span class="token operator">*</span>s_sta_netif <span class="token operator">=</span> <span class="token constant">NULL</span><span class="token punctuation">;</span></code></pre><p>使用函数<code>wifi_init_sta()</code>来初始化wifi，<code>ESP_ERROR_CHECK()</code>来核对错误代码。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">wifi_init_sta</span><span class="token punctuation">(</span>WIFI_SSID<span class="token punctuation">,</span> WIFI_PASS<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><code>wifi_init_sta()</code>具体实现：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">esp_err_t</span> <span class="token function">wifi_init_sta</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>ssid<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>password<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_netif_init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_loop_create_default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    s_sta_netif <span class="token operator">=</span> <span class="token function">esp_netif_create_default_wifi_sta</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">wifi_init_config_t</span> cfg <span class="token operator">=</span> <span class="token function">WIFI_INIT_CONFIG_DEFAULT</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>cfg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_handler_register</span><span class="token punctuation">(</span>WIFI_EVENT<span class="token punctuation">,</span> ESP_EVENT_ANY_ID<span class="token punctuation">,</span> <span class="token operator">&amp;</span>event_handler<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_handler_register</span><span class="token punctuation">(</span>IP_EVENT<span class="token punctuation">,</span> IP_EVENT_STA_GOT_IP<span class="token punctuation">,</span> <span class="token operator">&amp;</span>event_handler<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token class-name">wifi_config_t</span> wifi_config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token punctuation">.</span>sta <span class="token operator">=</span> <span class="token punctuation">&#123;</span>            <span class="token punctuation">.</span>threshold<span class="token punctuation">.</span>authmode <span class="token operator">=</span> WIFI_AUTH_WPA2_PSK<span class="token punctuation">,</span>            <span class="token punctuation">.</span>pmf_cfg <span class="token operator">=</span> <span class="token punctuation">&#123;</span>                <span class="token punctuation">.</span>capable <span class="token operator">=</span> true<span class="token punctuation">,</span>                <span class="token punctuation">.</span>required <span class="token operator">=</span> false            <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        <span class="token function">strncpy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>ssid<span class="token punctuation">,</span> ssid<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>ssid<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">strncpy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>password<span class="token punctuation">,</span> password<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>password<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_set_mode</span><span class="token punctuation">(</span>WIFI_MODE_STA<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_set_config</span><span class="token punctuation">(</span>ESP_IF_WIFI_STA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>wifi_config<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"WiFi initialization completed"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ESP_OK<span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="1-函数定义"><a href="#1-函数定义" class="headerlink" title="1. 函数定义"></a>1. <strong>函数定义</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">esp_err_t</span> <span class="token function">wifi_init_sta</span><span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>ssid<span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>password<span class="token punctuation">)</span></code></pre><ul><li>这是一个函数定义，函数名为 <code>wifi_init_sta</code>，返回类型为 <code>esp_err_t</code>（表示 ESP-IDF 中的错误码类型）。</li><li>函数接受两个参数：<ul><li><code>const char *ssid</code>：要连接的 Wi-Fi 网络的 SSID（账号）。</li><li><code>const char *password</code>：要连接的 Wi-Fi 网络的密码。</li></ul></li></ul><hr><h4 id="2-初始化网络接口"><a href="#2-初始化网络接口" class="headerlink" title="2. 初始化网络接口"></a>2. <strong>初始化网络接口</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_netif_init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_netif_init()</code> 初始化 ESP32 的网络接口（TCP&#x2F;IP 协议栈）。</li><li><code>ESP_ERROR_CHECK</code> 是一个宏，用于检查函数返回值是否为 <code>ESP_OK</code>，如果不是，则触发错误处理。</li></ul><hr><h4 id="3-创建默认事件循环"><a href="#3-创建默认事件循环" class="headerlink" title="3. 创建默认事件循环"></a>3. <strong>创建默认事件循环</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_loop_create_default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_event_loop_create_default()</code> 创建默认的事件循环。</li><li>事件循环用于处理系统中发生的各种事件（如 Wi-Fi 连接成功、断开连接等）。</li></ul><hr><h4 id="4-创建默认的-STA-模式网络接口"><a href="#4-创建默认的-STA-模式网络接口" class="headerlink" title="4. 创建默认的 STA 模式网络接口"></a>4. <strong>创建默认的 STA 模式网络接口</strong></h4><pre class="language-c" data-language="c"><code class="language-c">s_sta_netif <span class="token operator">=</span> <span class="token function">esp_netif_create_default_wifi_sta</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_netif_create_default_wifi_sta()</code> 创建默认的 Wi-Fi STA（Station Mode）网络接口。</li><li><code>s_sta_netif</code> 是一个全局变量，用于保存创建的 STA 网络接口对象。</li></ul><hr><h4 id="5-初始化-Wi-Fi-配置"><a href="#5-初始化-Wi-Fi-配置" class="headerlink" title="5. 初始化 Wi-Fi 配置"></a>5. <strong>初始化 Wi-Fi 配置</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">wifi_init_config_t</span> cfg <span class="token operator">=</span> <span class="token function">WIFI_INIT_CONFIG_DEFAULT</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>cfg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li><code>wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();</code>：使用默认配置初始化 Wi-Fi 配置结构体。</li><li><code>esp_wifi_init(&amp;cfg)</code>：根据配置初始化 Wi-Fi 驱动。</li></ul><hr><h4 id="6-注册事件处理函数"><a href="#6-注册事件处理函数" class="headerlink" title="6. 注册事件处理函数"></a>6. <strong>注册事件处理函数</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_handler_register</span><span class="token punctuation">(</span>WIFI_EVENT<span class="token punctuation">,</span> ESP_EVENT_ANY_ID<span class="token punctuation">,</span> <span class="token operator">&amp;</span>event_handler<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_event_handler_register</span><span class="token punctuation">(</span>IP_EVENT<span class="token punctuation">,</span> IP_EVENT_STA_GOT_IP<span class="token punctuation">,</span> <span class="token operator">&amp;</span>event_handler<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>注册事件处理函数 <code>event_handler</code>，用于处理 Wi-Fi 和 IP 事件。<ul><li>第一行：注册处理所有 Wi-Fi 事件（<code>WIFI_EVENT</code>）。</li><li>第二行：注册处理获取 IP 地址事件（<code>IP_EVENT_STA_GOT_IP</code>）。</li></ul></li></ul><hr><h4 id="7-配置-Wi-Fi-连接参数"><a href="#7-配置-Wi-Fi-连接参数" class="headerlink" title="7. 配置 Wi-Fi 连接参数"></a>7. <strong>配置 Wi-Fi 连接参数</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">wifi_config_t</span> wifi_config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token punctuation">.</span>sta <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token punctuation">.</span>threshold<span class="token punctuation">.</span>authmode <span class="token operator">=</span> WIFI_AUTH_WPA2_PSK<span class="token punctuation">,</span>        <span class="token punctuation">.</span>pmf_cfg <span class="token operator">=</span> <span class="token punctuation">&#123;</span>            <span class="token punctuation">.</span>capable <span class="token operator">=</span> true<span class="token punctuation">,</span>            <span class="token punctuation">.</span>required <span class="token operator">=</span> false        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span></code></pre><ul><li>定义 <code>wifi_config_t</code> 结构体变量 <code>wifi_config</code>，用于配置 Wi-Fi 连接参数。</li><li><code>sta</code> 是 STA 模式的配置部分：<ul><li><code>threshold.authmode = WIFI_AUTH_WPA2_PSK</code>：设置认证模式为 WPA2-PSK（常用的 Wi-Fi 加密方式）。</li><li><code>pmf_cfg</code>：配置 Protected Management Frames（PMF，保护管理帧）功能。<ul><li><code>capable = true</code>：设备支持 PMF。</li><li><code>required = false</code>：不强制要求 PMF。</li></ul></li></ul></li></ul><hr><h4 id="8-设置-SSID-和密码"><a href="#8-设置-SSID-和密码" class="headerlink" title="8. 设置 SSID 和密码"></a>8. <strong>设置 SSID 和密码</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">strncpy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>ssid<span class="token punctuation">,</span> ssid<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>ssid<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">strncpy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">char</span> <span class="token operator">*</span><span class="token punctuation">)</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>password<span class="token punctuation">,</span> password<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>wifi_config<span class="token punctuation">.</span>sta<span class="token punctuation">.</span>password<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>将传入的 <code>ssid</code> 和 <code>password</code> 复制到 <code>wifi_config</code> 结构体中。</li><li><code>strncpy</code> 用于安全地复制字符串，避免缓冲区溢出。</li><li><code>sizeof(wifi_config.sta.ssid) - 1</code> 确保字符串以 <code>\0</code> 结尾。</li></ul><hr><h4 id="9-设置-Wi-Fi-模式为-STA"><a href="#9-设置-Wi-Fi-模式为-STA" class="headerlink" title="9. 设置 Wi-Fi 模式为 STA"></a>9. <strong>设置 Wi-Fi 模式为 STA</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_set_mode</span><span class="token punctuation">(</span>WIFI_MODE_STA<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_wifi_set_mode(WIFI_MODE_STA)</code> 设置 Wi-Fi 模式为 STA 模式（客户端模式）。</li></ul><hr><h4 id="10-设置-Wi-Fi-配置"><a href="#10-设置-Wi-Fi-配置" class="headerlink" title="10. 设置 Wi-Fi 配置"></a>10. <strong>设置 Wi-Fi 配置</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_set_config</span><span class="token punctuation">(</span>ESP_IF_WIFI_STA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>wifi_config<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_wifi_set_config()</code> 设置 STA 模式的 Wi-Fi 配置。</li></ul><hr><h4 id="11-启动-Wi-Fi"><a href="#11-启动-Wi-Fi" class="headerlink" title="11. 启动 Wi-Fi"></a>11. <strong>启动 Wi-Fi</strong></h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_wifi_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><ul><li>调用 <code>esp_wifi_start()</code> 启动 Wi-Fi 模块。</li></ul><p>若成功启动WIFI后会调用<code>event_handler()</code>函数。</p><h4 id="event-handler-函数说明"><a href="#event-handler-函数说明" class="headerlink" title="**event_handler()**函数说明"></a>**event_handler()**函数说明</h4><p>在上方WIFI模块共注册两个事件基，<code>WIFI_EVENT</code>和<code>IP_EVENT</code>事件，当对应事件发生时会调用该函数。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">event_handler</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token operator">*</span> arg<span class="token punctuation">,</span> <span class="token class-name">esp_event_base_t</span> event_base<span class="token punctuation">,</span>                         <span class="token class-name">int32_t</span> event_id<span class="token punctuation">,</span> <span class="token keyword">void</span><span class="token operator">*</span> event_data<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>event_base <span class="token operator">==</span> WIFI_EVENT<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">switch</span> <span class="token punctuation">(</span>event_id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">case</span> WIFI_EVENT_STA_START<span class="token operator">:</span>                <span class="token function">esp_wifi_connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">case</span> WIFI_EVENT_STA_DISCONNECTED<span class="token operator">:</span>                s_wifi_connected <span class="token operator">=</span> false<span class="token punctuation">;</span>                <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Disconnected from WiFi, trying to reconnect..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token function">esp_wifi_connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">default</span><span class="token operator">:</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>event_base <span class="token operator">==</span> IP_EVENT <span class="token operator">&amp;&amp;</span> event_id <span class="token operator">==</span> IP_EVENT_STA_GOT_IP<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token class-name">ip_event_got_ip_t</span><span class="token operator">*</span> event <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">ip_event_got_ip_t</span><span class="token operator">*</span><span class="token punctuation">)</span> event_data<span class="token punctuation">;</span>        <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Got IP: "</span> IPSTR<span class="token punctuation">,</span> <span class="token function">IP2STR</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>event<span class="token operator">-></span>ip_info<span class="token punctuation">.</span>ip<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        s_wifi_connected <span class="token operator">=</span> true<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><p>在该函数中判断若事件基是wifi事件则监听是连接还是断开，ip事件只有判断获取ip事件。</p><p>若成功获取到ip则认为成功连接上wifi。</p><h3 id="MQTT模块实现"><a href="#MQTT模块实现" class="headerlink" title="MQTT模块实现"></a>MQTT模块实现</h3><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// MQTT连接配置</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MQTT_BROKER_URL</span> <span class="token string">"mqtts://mqtt地址:端口"</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MQTT_CLIENT_ID</span> <span class="token string">"esp32_client"</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MQTT_USERNAME</span> <span class="token string">"用户名"</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MQTT_PASSWORD</span> <span class="token string">"密码"</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MQTT_TOPIC</span> <span class="token string">"esp32/data"</span><span class="token comment">//发布的主题</span></span><span class="token comment">// EMQX的根证书</span><span class="token keyword">static</span> <span class="token keyword">const</span> <span class="token keyword">char</span> <span class="token operator">*</span>MQTT_SERVER_ROOT_CERT <span class="token operator">=</span> <span class="token string">"填入证书"</span><span class="token punctuation">;</span></code></pre><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">esp_err_t</span> <span class="token function">mqtt_ali_init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// MQTT客户端配置</span>    <span class="token class-name">esp_mqtt_client_config_t</span> mqtt_cfg <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token punctuation">.</span>broker<span class="token punctuation">.</span>address<span class="token punctuation">.</span>uri <span class="token operator">=</span> MQTT_BROKER_URL<span class="token punctuation">,</span>        <span class="token punctuation">.</span>credentials<span class="token punctuation">.</span>client_id <span class="token operator">=</span> MQTT_CLIENT_ID<span class="token punctuation">,</span>        <span class="token punctuation">.</span>credentials<span class="token punctuation">.</span>username <span class="token operator">=</span> MQTT_USERNAME<span class="token punctuation">,</span>        <span class="token punctuation">.</span>credentials<span class="token punctuation">.</span>authentication<span class="token punctuation">.</span>password <span class="token operator">=</span> MQTT_PASSWORD<span class="token punctuation">,</span>        <span class="token punctuation">.</span>broker<span class="token punctuation">.</span>verification<span class="token punctuation">.</span>certificate <span class="token operator">=</span> MQTT_SERVER_ROOT_CERT<span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token comment">// 创建MQTT客户端</span>    mqtt_client <span class="token operator">=</span> <span class="token function">esp_mqtt_client_init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>mqtt_cfg<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>mqtt_client <span class="token operator">==</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Failed to initialize MQTT client"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> ESP_FAIL<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 注册MQTT事件处理函数</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_mqtt_client_register_event</span><span class="token punctuation">(</span>mqtt_client<span class="token punctuation">,</span> ESP_EVENT_ANY_ID<span class="token punctuation">,</span> mqtt_event_handler<span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 启动MQTT客户端</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">esp_mqtt_client_start</span><span class="token punctuation">(</span>mqtt_client<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT client initialized"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ESP_OK<span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>初始化部分和wifi大部分相同，不再赘述。</p><p>创建完成后若有响应事件会调用回调函数<code>mqtt_event_handler()</code></p><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// MQTT事件处理函数</span><span class="token keyword">void</span> <span class="token function">mqtt_event_handler</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token operator">*</span>handler_args<span class="token punctuation">,</span> <span class="token class-name">esp_event_base_t</span> base<span class="token punctuation">,</span> <span class="token class-name">int32_t</span> event_id<span class="token punctuation">,</span> <span class="token keyword">void</span> <span class="token operator">*</span>event_data<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token class-name">esp_mqtt_event_handle_t</span> event <span class="token operator">=</span> event_data<span class="token punctuation">;</span>        <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">esp_mqtt_event_id_t</span><span class="token punctuation">)</span>event_id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">case</span> MQTT_EVENT_CONNECTED<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT Connected to broker"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            mqtt_connected <span class="token operator">=</span> true<span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_DISCONNECTED<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT Disconnected from broker"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            mqtt_connected <span class="token operator">=</span> false<span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_SUBSCRIBED<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT_EVENT_SUBSCRIBED, msg_id=%d"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>msg_id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_UNSUBSCRIBED<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT_EVENT_UNSUBSCRIBED, msg_id=%d"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>msg_id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_PUBLISHED<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT_EVENT_PUBLISHED, msg_id=%d"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>msg_id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_DATA<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT_EVENT_DATA"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"TOPIC=%.*s\r\n"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>topic_len<span class="token punctuation">,</span> event<span class="token operator">-></span>topic<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"DATA=%.*s\r\n"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>data_len<span class="token punctuation">,</span> event<span class="token operator">-></span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">case</span> MQTT_EVENT_ERROR<span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"MQTT_EVENT_ERROR"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token operator">-></span>error_handle<span class="token operator">-></span>error_type <span class="token operator">==</span> MQTT_ERROR_TYPE_TCP_TRANSPORT<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Last error code reported from esp-tls: 0x%x"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>error_handle<span class="token operator">-></span>esp_tls_last_esp_err<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Last tls stack error number: 0x%x"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>error_handle<span class="token operator">-></span>esp_tls_stack_err<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Last captured errno : %d (%s)"</span><span class="token punctuation">,</span>  event<span class="token operator">-></span>error_handle<span class="token operator">-></span>esp_transport_sock_errno<span class="token punctuation">,</span>                        <span class="token function">strerror</span><span class="token punctuation">(</span>event<span class="token operator">-></span>error_handle<span class="token operator">-></span>esp_transport_sock_errno<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token keyword">default</span><span class="token operator">:</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Other event id:%d"</span><span class="token punctuation">,</span> event<span class="token operator">-></span>event_id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><h3 id="UART模块"><a href="#UART模块" class="headerlink" title="UART模块"></a>UART模块</h3><p>需要将波特率和数据位调成和STM32一致。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">uart_init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token class-name">uart_config_t</span> uart_config <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token punctuation">.</span>baud_rate <span class="token operator">=</span> <span class="token number">115200</span><span class="token punctuation">,</span>                   <span class="token comment">// 波特率</span>        <span class="token punctuation">.</span>data_bits <span class="token operator">=</span> UART_DATA_8_BITS<span class="token punctuation">,</span>         <span class="token comment">// 数据位8</span>        <span class="token punctuation">.</span>parity <span class="token operator">=</span> UART_PARITY_DISABLE<span class="token punctuation">,</span>         <span class="token comment">// 无校验</span>        <span class="token punctuation">.</span>stop_bits <span class="token operator">=</span> UART_STOP_BITS_1<span class="token punctuation">,</span>         <span class="token comment">// 停止位1</span>        <span class="token punctuation">.</span>flow_ctrl <span class="token operator">=</span> UART_HW_FLOWCTRL_DISABLE<span class="token punctuation">,</span> <span class="token comment">// 硬件流控制关闭</span>        <span class="token punctuation">.</span>source_clk <span class="token operator">=</span> UART_SCLK_APB<span class="token punctuation">,</span>           <span class="token comment">// 时钟源</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">uart_param_config</span><span class="token punctuation">(</span>UART_NUM<span class="token punctuation">,</span> <span class="token operator">&amp;</span>uart_config<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">uart_set_pin</span><span class="token punctuation">(</span>UART_NUM<span class="token punctuation">,</span> UART_TX_PIN<span class="token punctuation">,</span> UART_RX_PIN<span class="token punctuation">,</span> UART_PIN_NO_CHANGE<span class="token punctuation">,</span> UART_PIN_NO_CHANGE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ESP_ERROR_CHECK</span><span class="token punctuation">(</span><span class="token function">uart_driver_install</span><span class="token punctuation">(</span>UART_NUM<span class="token punctuation">,</span> BUF_SIZE <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token constant">NULL</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"UART initialized successfully"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h3 id="TCP模块"><a href="#TCP模块" class="headerlink" title="TCP模块"></a>TCP模块</h3><p>在使用WIfi模块和mqtt时需要启动tcp这一运输层协议。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">tcp_server_task</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token operator">*</span>pvParameters<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">char</span> rx_buffer<span class="token punctuation">[</span>BUFFER_SIZE<span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment">// 定义一个缓冲区，用于存储从客户端接收的数据</span>    <span class="token keyword">int</span> listen_sock<span class="token punctuation">,</span> client_sock<span class="token punctuation">;</span>  <span class="token comment">// 定义两个套接字：listen_sock用于监听连接，client_sock用于与客户端通信</span>    <span class="token keyword">struct</span> <span class="token class-name">sockaddr_in</span> server_addr<span class="token punctuation">,</span> client_addr<span class="token punctuation">;</span>  <span class="token comment">// 定义两个结构体，分别用于存储服务器和客户端的地址信息</span>    <span class="token class-name">socklen_t</span> addr_len <span class="token operator">=</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>client_addr<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 定义客户端地址结构体的大小</span>    <span class="token comment">// 创建TCP套接字</span>    listen_sock <span class="token operator">=</span> <span class="token function">socket</span><span class="token punctuation">(</span>AF_INET<span class="token punctuation">,</span> SOCK_STREAM<span class="token punctuation">,</span> IPPROTO_IP<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 创建一个IPv4的TCP套接字</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>listen_sock <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果套接字创建失败</span>        <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Failed to create socket"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录错误日志</span>        <span class="token function">vTaskDelete</span><span class="token punctuation">(</span><span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 删除当前任务</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 配置服务器地址</span>    server_addr<span class="token punctuation">.</span>sin_family <span class="token operator">=</span> AF_INET<span class="token punctuation">;</span>  <span class="token comment">// 设置地址族为IPv4</span>    server_addr<span class="token punctuation">.</span>sin_port <span class="token operator">=</span> <span class="token function">htons</span><span class="token punctuation">(</span>TCP_PORT<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 设置服务器端口号，htons函数将端口号从主机字节序转换为网络字节序</span>    server_addr<span class="token punctuation">.</span>sin_addr<span class="token punctuation">.</span>s_addr <span class="token operator">=</span> <span class="token function">htonl</span><span class="token punctuation">(</span>INADDR_ANY<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 设置服务器IP地址为任意地址，htonl函数将IP地址从主机字节序转换为网络字节序</span>    <span class="token comment">// 绑定套接字</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">bind</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">sockaddr</span> <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&amp;</span>server_addr<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>server_addr<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 将套接字绑定到服务器地址</span>        <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Failed to bind socket"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 如果绑定失败，记录错误日志</span>        <span class="token function">close</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 关闭套接字</span>        <span class="token function">vTaskDelete</span><span class="token punctuation">(</span><span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 删除当前任务</span>    <span class="token punctuation">&#125;</span>    <span class="token comment">// 监听连接</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">listen</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">,</span> MAX_CLIENTS<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 开始监听连接请求，MAX_CLIENTS为最大连接数</span>        <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Failed to listen"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 如果监听失败，记录错误日志</span>        <span class="token function">close</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 关闭套接字</span>        <span class="token function">vTaskDelete</span><span class="token punctuation">(</span><span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 删除当前任务</span>    <span class="token punctuation">&#125;</span>    <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"TCP server started on port %d"</span><span class="token punctuation">,</span> TCP_PORT<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录日志，表示TCP服务器已启动</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 进入无限循环，持续处理客户端连接</span>        <span class="token comment">// 接受客户端连接</span>        client_sock <span class="token operator">=</span> <span class="token function">accept</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">struct</span> <span class="token class-name">sockaddr</span> <span class="token operator">*</span><span class="token punctuation">)</span><span class="token operator">&amp;</span>client_addr<span class="token punctuation">,</span> <span class="token operator">&amp;</span>addr_len<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 接受客户端连接请求</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>client_sock <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果接受连接失败</span>            <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Failed to accept connection"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录错误日志</span>            <span class="token keyword">continue</span><span class="token punctuation">;</span>  <span class="token comment">// 继续下一次循环</span>        <span class="token punctuation">&#125;</span>        <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"New client connected"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录日志，表示有新客户端连接</span>        <span class="token comment">// 处理客户端数据</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 进入无限循环，持续处理客户端发送的数据</span>            <span class="token keyword">int</span> len <span class="token operator">=</span> <span class="token function">recv</span><span class="token punctuation">(</span>client_sock<span class="token punctuation">,</span> rx_buffer<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>rx_buffer<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 从客户端接收数据</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果接收数据失败</span>                <span class="token function">ESP_LOGE</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Error receiving data"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录错误日志</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token comment">// 退出内层循环</span>            <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果客户端断开连接</span>                <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Client disconnected"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录日志，表示客户端已断开连接</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token comment">// 退出内层循环</span>            <span class="token punctuation">&#125;</span>            rx_buffer<span class="token punctuation">[</span>len<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token char">'\0'</span><span class="token punctuation">;</span>  <span class="token comment">// 在接收到的数据末尾添加字符串结束符</span>            <span class="token function">ESP_LOGI</span><span class="token punctuation">(</span>TAG<span class="token punctuation">,</span> <span class="token string">"Received: %s"</span><span class="token punctuation">,</span> rx_buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 记录日志，显示接收到的数据</span>            <span class="token comment">// 处理命令</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strstr</span><span class="token punctuation">(</span>rx_buffer<span class="token punctuation">,</span> <span class="token string">"LED_ON"</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果接收到的数据包含"LED_ON"命令</span>                <span class="token function">uart_send_data</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 通过UART发送"1"，控制LED灯打开</span>                <span class="token function">send</span><span class="token punctuation">(</span>client_sock<span class="token punctuation">,</span> <span class="token string">"LED IS ON\n"</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 向客户端发送响应，表示LED灯已打开</span>            <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">strstr</span><span class="token punctuation">(</span>rx_buffer<span class="token punctuation">,</span> <span class="token string">"LED_OFF"</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果接收到的数据包含"LED_OFF"命令</span>                <span class="token function">uart_send_data</span><span class="token punctuation">(</span><span class="token string">"0"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 通过UART发送"0"，控制LED灯关闭</span>                <span class="token function">send</span><span class="token punctuation">(</span>client_sock<span class="token punctuation">,</span> <span class="token string">"LED IS OFF\n"</span><span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 向客户端发送响应，表示LED灯已关闭</span>            <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// 如果接收到的命令无效</span>                <span class="token function">send</span><span class="token punctuation">(</span>client_sock<span class="token punctuation">,</span> <span class="token string">"Invalid command\n"</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 向客户端发送响应，表示命令无效</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>        <span class="token function">close</span><span class="token punctuation">(</span>client_sock<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 关闭客户端套接字</span>    <span class="token punctuation">&#125;</span>    <span class="token function">close</span><span class="token punctuation">(</span>listen_sock<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 关闭监听套接字</span>    <span class="token function">vTaskDelete</span><span class="token punctuation">(</span><span class="token constant">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 删除当前任务</span><span class="token punctuation">&#125;</span></code></pre><h1 id="软件层"><a href="#软件层" class="headerlink" title="软件层"></a>软件层</h1><ol><li><strong>数据收集</strong><ul><li>温度和湿度数据<ul><li>可以使用API或网站（如OpenWeatherMap）获取当前天气状况的温度和湿度数据。</li><li>可以使用历史数据（如气象站记录）来预测天气模式。</li></ul></li><li>地理信息数据<ul><li>使用GIS库（如Folium或Geopandas）获取地理坐标、城市信息和气象站数据。</li><li>使用地图数据（如OpenStreetMap或国土测绘数据）获取城市和区域信息。</li></ul></li><li>其它相关数据<ul><li>可以使用API或网站获取城市人口、经济情况和其它环境因素（如空气质量、噪音指数等）。</li><li>使用社交媒体或用户评论数据，了解用户对所在城市的感受和需求。</li></ul></li></ul></li><li><strong>数据预处理</strong><ul><li>数据清理和转换<ul><li>转换所有数据为标准格式和单位，以便进行比较和分析。</li><li>删除异常值或错误数据。</li></ul></li><li>数据集成和标准化<ul><li>集成来自各个数据源的数据，确保所有数据与所在城市的坐标和信息相关联。</li><li>进行标准化（如归一化或标准化），使不同特征的数据具有相同的范围和分布。</li></ul></li></ul></li><li><strong>特征工程</strong><ul><li>提取城市环境特征<ul><li>使用GIS功能提取城市环境特征，如面积、人口密度、交通网络、绿地面积等。</li><li>使用机器学习算法提取城市环境模式，如天气模式、空气指数等。</li></ul></li><li>构建特征矩阵<ul><li>将提取的特征构建为矩阵格式，以便进行分析和比较。</li></ul></li></ul></li><li><strong>环境模拟</strong><ul><li>机器学习算法<ul><li>选择适合的机器学习算法，如决策树、随机森林、支持向量机等。</li><li>使用这些算法训练一个模型，根据用户所在地的环境数据预测相应城市的环境数据。</li></ul></li><li>城市环境推断<ul><li>使用训练好的模型，对用户所在地的环境数据进行预测，推断出与所在地环境相近的城市。</li></ul></li></ul></li><li><strong>结果评估和优化</strong><ul><li>结果评估<ul><li>使用评估指标（如准确性、召回率、F1分数等）评估模型的性能。</li><li>进行错误分析，找出错误数据或模式的原因。</li></ul></li><li>模型优化<ul><li>根据错误分析结果和评估指标对模型进行优化和改进。</li><li>试验不同算法和参数，以找到最佳的模型性能。</li></ul></li></ul></li></ol><p>以下是一些常用的算法和库：</p><ul><li><strong>决策树和随机森林</strong>：scikit-learn（Python）</li><li><strong>支持向量机</strong>：libsvm（C++）和scikit-learn（Python）</li><li><strong>K近邻算法</strong>：scikit-learn（Python）</li><li><strong>GIS库</strong>：Folium（Python）、Geopandas（Python）和GeoServer（Java）</li><li><strong>地图数据</strong>：OpenStreetMap（Osmosis）和国土测绘数据</li></ul>]]>
    </content>
    <id>https://lsworl.github.io/2025/02/04/stm32-gen-ju-dht11-wen-shi-du-fen-xi-cheng-shi-xiang-si-du-xiang-mu-zong-jie/</id>
    <link href="https://lsworl.github.io/2025/02/04/stm32-gen-ju-dht11-wen-shi-du-fen-xi-cheng-shi-xiang-si-du-xiang-mu-zong-jie/"/>
    <published>2025-02-04T03:03:33.000Z</published>
    <summary>总结 STM32、DHT11、ESP32、MQTT、数据库和天气 API 组成的温湿度采集与城市相似度分析项目。</summary>
    <title>STM32根据DHT11温湿度分析城市相似度项目总结</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>使用的型号为stm32f103c8t6与w25q64。</p></blockquote><h2 id="STM32CubeMX配置与引脚衔接"><a href="#STM32CubeMX配置与引脚衔接" class="headerlink" title="STM32CubeMX配置与引脚衔接"></a>STM32CubeMX配置与引脚衔接</h2><p>根据stm32f103c8t6引脚手册，采用B12-B15四个引脚与W25Q64连接，实现SPI通信。</p><p><img src="https://i-blog.csdnimg.cn/direct/98c29417491e44da93ee9f3b7dc81831.png"></p><table><thead><tr><th align="left">W25Q64</th><th align="left">SCK（CLK）</th><th align="left">PB13</th></tr></thead><tbody><tr><td align="left"></td><td align="left">MOSI（DI）</td><td align="left">PB15</td></tr><tr><td align="left"></td><td align="left">MISO(DO)</td><td align="left">PB14</td></tr><tr><td align="left"></td><td align="left">CS（这里不采用硬件CS，所以接任意GPIO口都可以）</td><td align="left">PB12</td></tr></tbody></table><h3 id="STM32CubeMX配置"><a href="#STM32CubeMX配置" class="headerlink" title="STM32CubeMX配置"></a>STM32CubeMX配置</h3><p>这里对于时钟相关的配置就不做赘述了，由于是练习所以将系统时钟配置成了72MHz，主要是配置引脚。</p><p><img src="https://i-blog.csdnimg.cn/direct/1bbb27352c42473b96cd271a77fa55b2.png"></p><p>在引脚配置将PB12配置为推挽输出，默认电平为高。</p><p><img src="https://i-blog.csdnimg.cn/direct/d70d8629a65c4c0b8af9141d3428d748.png"></p><p>PB13，PB14，PB15均使用硬件的SPI。</p><p><img src="https://i-blog.csdnimg.cn/direct/13d32f2c76f44a89b9c50f37b3a232ef.png"></p><p>可以在系统核心的GPIO中的SPI中看到这三个配置。</p><p><img src="https://i-blog.csdnimg.cn/direct/42f748bc2b1a4ce8af5ab005472a7911.png"></p><p>然后到SPI2中将参数配置，采用高位优先，波特率为18MB&#x2F;s，若不是则需要调整Prescaler分频到18MB&#x2F;s，传输以字节为单位。</p><p><img src="https://i-blog.csdnimg.cn/direct/b75904857b604314b77c73d13b110b2f.png"></p><p>这里先不开启中断，所以NVIC setting里面的中断没有选上，做好基础配置后就可以生成代码，点击GENERATE CODE。</p><hr><p>在Src中就会出现spi.c文件</p><p><img src="https://i-blog.csdnimg.cn/direct/746d46bd463f4dba9ceda7456a74600d.png"></p><p>查看PB13和PB15应该是默认配置为复用推挽，PB14为浮空输入。</p><p><img src="https://i-blog.csdnimg.cn/direct/0f7aa54b20a64e08ae9845a5dd244f8c.png"></p><p>spi2的stm32cubemx生成的默认配置如下。</p><p><img src="https://i-blog.csdnimg.cn/direct/3bef3439bf844c8a8c41897a044da1c0.png"></p><h2 id="配置完成后代码实现与W25Q64通信"><a href="#配置完成后代码实现与W25Q64通信" class="headerlink" title="配置完成后代码实现与W25Q64通信"></a>配置完成后代码实现与W25Q64通信</h2><p>接下来就是实现与W25Q64的通信，先测试能不能获取到W25Q64的厂商ID和设备ID。</p><p>对SPI的收发函数进行了封装，通过<code>HAL_SPI_TransmitReceive()</code>函数，将<code>byte</code>发送给w25q64，并将收到的数据放入<code>rByte</code>。该函数第一个参数为句柄的指针，由于才用spi2，句柄为<code>hspi2</code>，第二个参数为要发送的字节，第三个为接受的字节，第四个为大小（以字节为单位），第五个为超时时长，以ms为单位。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">uint8_t</span> <span class="token function">SPI_SwapByte</span><span class="token punctuation">(</span><span class="token class-name">uint8_t</span> byte<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token class-name">uint8_t</span> rByte <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token function">HAL_SPI_TransmitReceive</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>hspi2<span class="token punctuation">,</span> <span class="token operator">&amp;</span>byte<span class="token punctuation">,</span> <span class="token operator">&amp;</span>rByte<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> rByte<span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">W25Q64_ReadID</span><span class="token punctuation">(</span><span class="token class-name">uint8_t</span> <span class="token operator">*</span>mid<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> <span class="token operator">*</span>did<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token comment">// 读取ID</span>  <span class="token comment">// 开启片选信号</span>  <span class="token function">HAL_GPIO_WritePin</span><span class="token punctuation">(</span>GPIOB<span class="token punctuation">,</span> GPIO_PIN_12<span class="token punctuation">,</span> GPIO_PIN_RESET<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 发送读取ID命令</span>  <span class="token function">SPI_SwapByte</span><span class="token punctuation">(</span><span class="token number">0x9f</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 读取制造商id</span>  <span class="token operator">*</span>mid <span class="token operator">=</span> <span class="token function">SPI_SwapByte</span><span class="token punctuation">(</span><span class="token number">0xff</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 读取设备id</span>  <span class="token operator">*</span>did <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token operator">*</span>did <span class="token operator">|=</span> <span class="token function">SPI_SwapByte</span><span class="token punctuation">(</span><span class="token number">0xff</span><span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token number">8</span><span class="token punctuation">;</span>   <span class="token comment">// 高8位</span>  <span class="token operator">*</span>did <span class="token operator">|=</span> <span class="token function">SPI_SwapByte</span><span class="token punctuation">(</span><span class="token number">0xff</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> <span class="token number">0xff</span><span class="token punctuation">;</span> <span class="token comment">// 低8位</span>  <span class="token comment">// 关闭片选信号</span>  <span class="token function">HAL_GPIO_WritePin</span><span class="token punctuation">(</span>GPIOB<span class="token punctuation">,</span> GPIO_PIN_12<span class="token punctuation">,</span> GPIO_PIN_SET<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 发送调试信息到串口</span>  <span class="token keyword">char</span> debugMsg<span class="token punctuation">[</span><span class="token number">50</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token keyword">int</span> msgLength <span class="token operator">=</span> <span class="token function">snprintf</span><span class="token punctuation">(</span>debugMsg<span class="token punctuation">,</span> <span class="token keyword">sizeof</span><span class="token punctuation">(</span>debugMsg<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"Manufacturer ID: 0x%02X, Device ID: 0x%04X\r\n"</span><span class="token punctuation">,</span> <span class="token operator">*</span>mid<span class="token punctuation">,</span> <span class="token operator">*</span>did<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">HAL_UART_Transmit</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>huart1<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">uint8_t</span> <span class="token operator">*</span><span class="token punctuation">)</span>debugMsg<span class="token punctuation">,</span> msgLength<span class="token punctuation">,</span> HAL_MAX_DELAY<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/31/stm32-ying-jian-shi-xian-yu-w25qxx-tong-xin/</id>
    <link href="https://lsworl.github.io/2025/01/31/stm32-ying-jian-shi-xian-yu-w25qxx-tong-xin/"/>
    <published>2025-01-31T01:05:54.000Z</published>
    <summary>记录 STM32F103C8T6 通过 SPI 与 W25Q64 Flash 通信的硬件连接、CubeMX 配置和代码实现。</summary>
    <title>stm32硬件实现与w25qxx通信</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>代码项目地址为<a href="https://github.com/lsWorl/stm32_practice">stm32综合学习项目</a>。</p></blockquote><h3 id="项目概述（详细可在项目的Project-Structure中查看）"><a href="#项目概述（详细可在项目的Project-Structure中查看）" class="headerlink" title="项目概述（详细可在项目的Project_Structure中查看）"></a>项目概述（详细可在项目的Project_Structure中查看）</h3><p>该项目基于STM32F103C8T6芯片，学习I2C,GPIO,ADC,EXTI,PWM等基础模块的使用，并通过模块化的方式使代码具有良好的可读性与健壮性。</p><h4 id="准备的模块"><a href="#准备的模块" class="headerlink" title="准备的模块"></a>准备的模块</h4><ol><li>OLED显示屏（I2C）</li><li>蜂鸣器模块（GPIO）</li><li>光敏电阻传感器（ADC）</li><li>热敏电阻传感器（ADC）</li><li>MPU6050陀螺仪（I2C）</li><li>旋转编码器（EXTI+TIM）</li><li>舵机（PWM）</li><li>LED指示灯（GPIO）</li><li>按钮（GPIO+EXTI）</li><li>STM23F103C8T6芯片</li></ol><h4 id="文件结构设计"><a href="#文件结构设计" class="headerlink" title="文件结构设计"></a>文件结构设计</h4><h5 id="硬件驱动层"><a href="#硬件驱动层" class="headerlink" title="硬件驱动层"></a>硬件驱动层</h5><pre class="language-none"><code class="language-none">Hardware&#x2F;├── OLED.c├── OLED.h├── OLED_Font.h├── beeper.c├── beeper.h├── light_sensor.c├── light_sensor.h├── temp_sensor.c├── temp_sensor.h├── mpu6050.c├── mpu6050.h├── encoder.c├── encoder.h├── servo.c├── servo.h├── led.c├── led.h├── button.c└── button.h</code></pre><h5 id="中间件层"><a href="#中间件层" class="headerlink" title="中间件层"></a>中间件层</h5><pre class="language-none"><code class="language-none">Middlewares&#x2F;├── i2c.c├── i2c.h├── adc.c├── adc.h├── pwm.c├── pwm.h├── exti.c├── exti.h├── gpio.c└── gpio.h</code></pre><h5 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h5><pre class="language-none"><code class="language-none">Application&#x2F;├── system.c├── system.h├── menu.c├── menu.h├── alarm.c├── alarm.h├── control.c├── control.h├── indicator.c└── indicator.h</code></pre><h5 id="主程序"><a href="#主程序" class="headerlink" title="主程序"></a>主程序</h5><pre class="language-none"><code class="language-none">User&#x2F;├── main.c└── stm32f10x_it.c</code></pre><h5 id="各模块所接入的GPIO口（在Hardware-pin-config-h中）"><a href="#各模块所接入的GPIO口（在Hardware-pin-config-h中）" class="headerlink" title="各模块所接入的GPIO口（在Hardware/pin_config.h中）"></a>各模块所接入的GPIO口（在<code>Hardware/pin_config.h</code>中）</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// OLED 引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">OLED_SCL_PIN</span>        <span class="token expression">GPIO_Pin_6  </span><span class="token comment">// PB6</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">OLED_SDA_PIN</span>        <span class="token expression">GPIO_Pin_7  </span><span class="token comment">// PB7</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">OLED_PORT</span>           <span class="token expression">GPIOB  </span><span class="token comment">// OLED引脚所在的端口</span></span><span class="token comment">//MPU6050引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MPU6050_SDA_PIN</span>     <span class="token expression">GPIO_Pin_10  </span><span class="token comment">// PB10</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MPU6050_SCL_PIN</span>     <span class="token expression">GPIO_Pin_11  </span><span class="token comment">// PB11</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">MPU6050_PORT</span>        <span class="token expression">GPIOB  </span><span class="token comment">// MPU6050引脚所在的端口</span></span><span class="token comment">// 光敏传感器引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LIGHT_SENSOR_PIN</span>    <span class="token expression">GPIO_Pin_0  </span><span class="token comment">// PA0</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LIGHT_SENSOR_PORT</span>   <span class="token expression">GPIOA  </span><span class="token comment">// 光敏传感器引脚所在的端口</span></span><span class="token comment">// 温度传感器引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">TEMP_SENSOR_PIN</span>     <span class="token expression">GPIO_Pin_1  </span><span class="token comment">// PA1</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">TEMP_SENSOR_PORT</span>    <span class="token expression">GPIOA   </span><span class="token comment">// 温度传感器引脚所在的端口</span></span><span class="token comment">// 蜂鸣器引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BEEPER_PIN</span>          <span class="token expression">GPIO_Pin_2  </span><span class="token comment">// PA2</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BEEPER_PORT</span>         <span class="token expression">GPIOA</span></span><span class="token comment">// 编码器引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">ENCODER_A_PIN</span>       <span class="token expression">GPIO_Pin_3  </span><span class="token comment">// PA3</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">ENCODER_B_PIN</span>       <span class="token expression">GPIO_Pin_4  </span><span class="token comment">// PA4</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">ENCODER_PORT</span>        <span class="token expression">GPIOA  </span><span class="token comment">// 编码器引脚所在的端口</span></span><span class="token comment">// 舵机引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">SERVO_PWM_PIN</span>       <span class="token expression">GPIO_Pin_8  </span><span class="token comment">// PA8</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">SERVO_PORT</span>          <span class="token expression">GPIOA  </span><span class="token comment">// 舵机引脚所在的端口</span></span><span class="token comment">// LED引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LED1_PIN</span>            <span class="token expression">GPIO_Pin_9  </span><span class="token comment">// PA9</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LED2_PIN</span>            <span class="token expression">GPIO_Pin_10  </span><span class="token comment">// PA10</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LED3_PIN</span>            <span class="token expression">GPIO_Pin_11  </span><span class="token comment">// PA11</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LED4_PIN</span>            <span class="token expression">GPIO_Pin_12  </span><span class="token comment">// PA12</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LED_PORT</span>            <span class="token expression">GPIOA  </span><span class="token comment">// LED引脚所在的端口</span></span><span class="token comment">// 按钮引脚</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BTN1_PIN</span>            <span class="token expression">GPIO_Pin_5  </span><span class="token comment">// PA5</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BTN2_PIN</span>            <span class="token expression">GPIO_Pin_7  </span><span class="token comment">// PA7</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BTN3_PIN</span>            <span class="token expression">GPIO_Pin_1  </span><span class="token comment">// PB1</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BTN4_PIN</span>            <span class="token expression">GPIO_Pin_12  </span><span class="token comment">// PB12</span></span></code></pre><p>以下的模块设计均以上方定义的名字为主，若要换成自己的具体引脚只需修改<code>pin_config.h</code>即可。</p><h3 id="硬件驱动层模块设计"><a href="#硬件驱动层模块设计" class="headerlink" title="硬件驱动层模块设计"></a>硬件驱动层模块设计</h3><h4 id="LED模块"><a href="#LED模块" class="headerlink" title="LED模块"></a>LED模块</h4><p>该模块主要就是使能GPIO口（将LED的引脚正极接电源，负极接芯片引脚），通过芯片输出低电平导通，使LED点亮。</p><h5 id="LED-Init"><a href="#LED-Init" class="headerlink" title="LED_Init()"></a>LED_Init()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">LED_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>        <span class="token comment">// 使能LED所在的GPIO端口时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置LED引脚为推挽输出</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> LED1_PIN <span class="token operator">|</span> LED2_PIN <span class="token operator">|</span> LED3_PIN <span class="token operator">|</span> LED4_PIN<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_Out_PP<span class="token punctuation">;</span> <span class="token comment">// 推挽输出</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>LED_PORT<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 初始状态，所有LED关闭 该LED_Alloff();是自己写的一个函数，就是将所有引脚输出初始置为高电平，即LED全灭</span>    <span class="token function">LED_AllOff</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="LED-On"><a href="#LED-On" class="headerlink" title="LED_On()"></a>LED_On()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">LED_On</span><span class="token punctuation">(</span><span class="token class-name">uint16_t</span> LED_PIN<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">GPIO_ResetBits</span><span class="token punctuation">(</span>LED_PORT<span class="token punctuation">,</span> LED_PIN<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="LED-Off"><a href="#LED-Off" class="headerlink" title="LED_Off()"></a>LED_Off()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">LED_Off</span><span class="token punctuation">(</span><span class="token class-name">uint16_t</span> LED_PIN<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">GPIO_SetBits</span><span class="token punctuation">(</span>LED_PORT<span class="token punctuation">,</span> LED_PIN<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="LED-Toggle"><a href="#LED-Toggle" class="headerlink" title="LED_Toggle()"></a>LED_Toggle()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">LED_Toggle</span><span class="token punctuation">(</span><span class="token class-name">uint16_t</span> LED_PIN<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    LED_PORT<span class="token operator">-></span>ODR <span class="token operator">^=</span> LED_PIN<span class="token punctuation">;</span>    GPIOA<span class="token operator">-></span>ODR<span class="token punctuation">&#125;</span></code></pre><p><strong>模块所使用到的主要标准库函数：</strong></p><ul><li>GPIO_InitTypeDef：配置 GPIO（通用输入输出）引脚的结构体。</li><li>GPIO_SetBits(GPIOA, GPIO_Pin_11)：设置对应GPIO口的引脚为高电平。</li><li>GPIO_ResetBits(GPIOA, GPIO_Pin_11);：设置对应GPIO口的引脚为低电平。</li><li>GPIOA-&gt;ODR ^&#x3D; GPIO_Pin_11;：将GPIO口的引脚电平取反。（ODR（<strong>Output Data Register</strong>），用于控制GPIO端口的输出状态）</li></ul><h4 id="蜂鸣器模块"><a href="#蜂鸣器模块" class="headerlink" title="蜂鸣器模块"></a>蜂鸣器模块</h4><p>使用的是有源蜂鸣器，具体如下图。</p><p><img src="https://i-blog.csdnimg.cn/direct/053ae53420ef456983f828c8829d3f7d.png"></p><p>有源蜂鸣器可以直接通过引脚输出低电平直接引发鸣叫。</p><h5 id="Beeper-Init"><a href="#Beeper-Init" class="headerlink" title="Beeper_Init()"></a>Beeper_Init()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Beeper_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>        <span class="token comment">// 使能GPIOA时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置蜂鸣器引脚为推挽输出</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> BEEPER_PIN<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_Out_PP<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>BEEPER_PORT<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 初始状态为关闭 关闭的逻辑与LED的一致，高电平关闭。</span>    <span class="token function">Beeper_Off</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">&#125;</span></code></pre><h5 id="Beeper-Beep-（控制蜂鸣器鸣叫时长）"><a href="#Beeper-Beep-（控制蜂鸣器鸣叫时长）" class="headerlink" title="Beeper_Beep()（控制蜂鸣器鸣叫时长）"></a>Beeper_Beep()（控制蜂鸣器鸣叫时长）</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Beeper_Beep</span><span class="token punctuation">(</span><span class="token class-name">uint16_t</span> ms<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">Beeper_On</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//关闭就是用GPIO_ResetBits(BEEPER_PORT, BEEPER_PIN);</span>    <span class="token function">delay_ms</span><span class="token punctuation">(</span>ms<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//标准库中延迟函数需要自己写，可以找网上现成的</span>    <span class="token function">Beeper_Off</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//开启就是用GPIO_SetBits(BEEPER_PORT, BEEPER_PIN);</span><span class="token punctuation">&#125;</span></code></pre><h4 id="按钮模块"><a href="#按钮模块" class="headerlink" title="按钮模块"></a>按钮模块</h4><p>按钮采用中断的方式触发，需要设置中断向量，判断长按还是短按需要通过SysTick来协同工作，这里我总共接入了4个按钮，对应的引脚接口为A5,A7,B0,B12，所以我在初始化中需要初始两个GPIO口。</p><h5 id="button模块的常量定义"><a href="#button模块的常量定义" class="headerlink" title="button模块的常量定义"></a>button模块的常量定义</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// 按键参数定义</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">BUTTON_SCAN_INTERVAL</span>    <span class="token expression"><span class="token number">20</span>      </span><span class="token comment">// 扫描间隔（ms）</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LONG_PRESS_TIME</span>         <span class="token expression"><span class="token number">2000</span>    </span><span class="token comment">// 长按时间（ms）</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">DEBOUNCE_TIME</span>          <span class="token expression"><span class="token number">20</span>       </span><span class="token comment">// 消抖时间（ms）</span></span><span class="token comment">// 按键引脚定义</span><span class="token keyword">static</span> <span class="token keyword">const</span> <span class="token class-name">uint16_t</span> KEY_PINS<span class="token punctuation">[</span>KEY_COUNT<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    BTN1_PIN<span class="token punctuation">,</span>   <span class="token comment">// KEY_MODE  (BTN1)</span>    BTN2_PIN<span class="token punctuation">,</span>   <span class="token comment">// KEY_CONFIRM (BTN2)</span>    BTN3_PIN<span class="token punctuation">,</span>   <span class="token comment">// KEY_ALARM (BTN3)</span>    BTN4_PIN    <span class="token comment">// KEY_RESET (BTN4)</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token keyword">static</span> GPIO_TypeDef<span class="token operator">*</span> <span class="token keyword">const</span> KEY_PORTS<span class="token punctuation">[</span>KEY_COUNT<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    GPIOA<span class="token punctuation">,</span>      <span class="token comment">// KEY_MODE  (PA5)</span>    GPIOA<span class="token punctuation">,</span>      <span class="token comment">// KEY_CONFIRM (PA7)</span>    GPIOB<span class="token punctuation">,</span>      <span class="token comment">// KEY_ALARM (PB1)</span>    GPIOB       <span class="token comment">// KEY_RESET (PB12)</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span class="token comment">// 按键逻辑状态定义</span><span class="token keyword">typedef</span> <span class="token keyword">enum</span> <span class="token punctuation">&#123;</span>    BTN_IDLE <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span>       <span class="token comment">// 空闲状态</span>    BTN_DEBOUNCE<span class="token punctuation">,</span>       <span class="token comment">// 消抖状态</span>    BTN_SHORT_PRESS<span class="token punctuation">,</span>    <span class="token comment">// 短按状态</span>    BTN_LONG_PRESS      <span class="token comment">// 长按状态</span><span class="token punctuation">&#125;</span> ButtonState<span class="token punctuation">;</span><span class="token comment">// 按键编号定义</span><span class="token keyword">typedef</span> <span class="token keyword">enum</span> <span class="token punctuation">&#123;</span>    KEY_MODE <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span>   <span class="token comment">// 模式切换按键 (BTN1)</span>    KEY_CONFIRM<span class="token punctuation">,</span>    <span class="token comment">// 确认按键 (BTN2)</span>    KEY_ALARM<span class="token punctuation">,</span>      <span class="token comment">// 报警确认按键 (BTN3)</span>    KEY_RESET<span class="token punctuation">,</span>      <span class="token comment">// 复位按键 (BTN4)</span>    KEY_COUNT       <span class="token comment">// 按键总数</span><span class="token punctuation">&#125;</span> KeyID<span class="token punctuation">;</span></code></pre><h5 id="Button-Init"><a href="#Button-Init" class="headerlink" title="Button_Init()"></a>Button_Init()</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Button_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>    EXTI_InitTypeDef EXTI_InitStructure<span class="token punctuation">;</span>    NVIC_InitTypeDef NVIC_InitStructure<span class="token punctuation">;</span>    <span class="token class-name">uint8_t</span> i<span class="token punctuation">;</span>s        <span class="token comment">// 使能GPIO时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA <span class="token operator">|</span> RCC_APB2Periph_GPIOB <span class="token operator">|</span> RCC_APB2Periph_AFIO<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置按键引脚为输入上拉</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_IPU<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>        <span class="token comment">//逐一配置按键引脚</span>    <span class="token keyword">for</span><span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> KEY_COUNT<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> KEY_PINS<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>KEY_PORTS<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>        keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">=</span> BTN_IDLE<span class="token punctuation">;</span>        keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>isPressed <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>        <span class="token comment">// 配置EXTI中断</span>    <span class="token function">GPIO_EXTILineConfig</span><span class="token punctuation">(</span>GPIO_PortSourceGPIOA<span class="token punctuation">,</span> GPIO_PinSource5<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// KEY_MODE</span>    <span class="token function">GPIO_EXTILineConfig</span><span class="token punctuation">(</span>GPIO_PortSourceGPIOA<span class="token punctuation">,</span> GPIO_PinSource7<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// KEY_CONFIRM</span>    <span class="token function">GPIO_EXTILineConfig</span><span class="token punctuation">(</span>GPIO_PortSourceGPIOB<span class="token punctuation">,</span> GPIO_PinSource1<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// KEY_ALARM</span>    <span class="token function">GPIO_EXTILineConfig</span><span class="token punctuation">(</span>GPIO_PortSourceGPIOB<span class="token punctuation">,</span> GPIO_PinSource12<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// KEY_RESET</span>        EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Mode <span class="token operator">=</span> EXTI_Mode_Interrupt<span class="token punctuation">;</span>   <span class="token comment">//采用中断触发</span>    EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Trigger <span class="token operator">=</span> EXTI_Trigger_Falling<span class="token punctuation">;</span><span class="token comment">//下降沿触发</span>    EXTI_InitStructure<span class="token punctuation">.</span>EXTI_LineCmd <span class="token operator">=</span> ENABLE<span class="token punctuation">;</span>        EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Line <span class="token operator">=</span> EXTI_Line5<span class="token punctuation">;</span>  <span class="token comment">// KEY_MODE</span>    <span class="token function">EXTI_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>EXTI_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Line <span class="token operator">=</span> EXTI_Line7<span class="token punctuation">;</span>  <span class="token comment">// KEY_CONFIRM</span>    <span class="token function">EXTI_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>EXTI_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Line <span class="token operator">=</span> EXTI_Line1<span class="token punctuation">;</span>  <span class="token comment">// KEY_ALARM</span>    <span class="token function">EXTI_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>EXTI_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    EXTI_InitStructure<span class="token punctuation">.</span>EXTI_Line <span class="token operator">=</span> EXTI_Line12<span class="token punctuation">;</span> <span class="token comment">// KEY_RESET</span>    <span class="token function">EXTI_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>EXTI_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置NVIC</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannelPreemptionPriority <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannelSubPriority <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannelCmd <span class="token operator">=</span> ENABLE<span class="token punctuation">;</span>    <span class="token comment">// 配置中断通道</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannel <span class="token operator">=</span> EXTI9_5_IRQn<span class="token punctuation">;</span>   <span class="token comment">// KEY_MODE, KEY_CONFIRM</span>    <span class="token function">NVIC_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>NVIC_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannel <span class="token operator">=</span> EXTI1_IRQn<span class="token punctuation">;</span>     <span class="token comment">// KEY_ALARM</span>    <span class="token function">NVIC_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>NVIC_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    NVIC_InitStructure<span class="token punctuation">.</span>NVIC_IRQChannel <span class="token operator">=</span> EXTI15_10_IRQn<span class="token punctuation">;</span> <span class="token comment">// KEY_RESET</span>    <span class="token function">NVIC_Init</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>NVIC_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="Button-Scan"><a href="#Button-Scan" class="headerlink" title="Button_Scan()"></a>Button_Scan()</h5><p><code>Button_Scan()</code>每隔20ms扫描按钮状态，通过状态机的方式来进行消抖。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Button_Scan</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">static</span> <span class="token class-name">uint32_t</span> lastScanTime <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token class-name">uint32_t</span> currentTime<span class="token punctuation">;</span>    <span class="token class-name">uint8_t</span> i<span class="token punctuation">;</span>    currentTime <span class="token operator">=</span> <span class="token function">GetTickCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 按扫描间隔进行处理 小于扫描间隔则不处理</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>currentTime <span class="token operator">-</span> lastScanTime <span class="token operator">&lt;</span> BUTTON_SCAN_INTERVAL<span class="token punctuation">)</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token comment">// 更新扫描时间</span>    lastScanTime <span class="token operator">=</span> currentTime<span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> KEY_COUNT<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token comment">// 读取按键状态</span>        <span class="token class-name">uint8_t</span> pinState <span class="token operator">=</span> <span class="token function">GPIO_ReadInputDataBit</span><span class="token punctuation">(</span>KEY_PORTS<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> KEY_PINS<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>pinState <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// 按键按下</span>        <span class="token punctuation">&#123;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>isPressed<span class="token punctuation">)</span> <span class="token comment">//isPressed为0表示按键未按下</span>            <span class="token punctuation">&#123;</span>                keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>isPressed <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>                keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>pressTime <span class="token operator">=</span> currentTime<span class="token punctuation">;</span>                keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">=</span> BTN_DEBOUNCE<span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>            <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">==</span> BTN_DEBOUNCE<span class="token punctuation">)</span> <span class="token comment">// 按键处于消抖状态</span>            <span class="token punctuation">&#123;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>currentTime <span class="token operator">-</span> keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>pressTime <span class="token operator">>=</span> LONG_PRESS_TIME<span class="token punctuation">)</span> <span class="token comment">// 按键按下时间大于长按时间</span>                <span class="token punctuation">&#123;</span>                    keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">=</span> BTN_LONG_PRESS<span class="token punctuation">;</span> <span class="token comment">// 按键状态设置为长按状态</span>                <span class="token punctuation">&#125;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>        <span class="token keyword">else</span> <span class="token comment">// 按键释放</span>        <span class="token punctuation">&#123;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>isPressed<span class="token punctuation">)</span>            <span class="token punctuation">&#123;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>currentTime <span class="token operator">-</span> keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>pressTime <span class="token operator">>=</span> DEBOUNCE_TIME<span class="token punctuation">)</span>                <span class="token punctuation">&#123;</span>                    <span class="token keyword">if</span><span class="token punctuation">(</span>keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">!=</span> BTN_LONG_PRESS<span class="token punctuation">)</span>                    <span class="token punctuation">&#123;</span>                        keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>state <span class="token operator">=</span> BTN_SHORT_PRESS<span class="token punctuation">;</span>                    <span class="token punctuation">&#125;</span>                <span class="token punctuation">&#125;</span>                keyInfo<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>isPressed <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="光敏传感器模块"><a href="#光敏传感器模块" class="headerlink" title="光敏传感器模块"></a>光敏传感器模块</h4><p>以下图是所使用的光敏电阻模块，芯片引脚接AO。</p><p><img src="https://i-blog.csdnimg.cn/direct/09f920576c084557a8f2139821ce3a3d.png"></p><h5 id="light-sensor常量定义"><a href="#light-sensor常量定义" class="headerlink" title="light_sensor常量定义"></a>light_sensor常量定义</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">// 光照强度等级定义</span><span class="token keyword">typedef</span> <span class="token keyword">enum</span> <span class="token punctuation">&#123;</span>    LIGHT_LEVEL_DARK <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span>    <span class="token comment">// 黑暗</span>    LIGHT_LEVEL_DIM<span class="token punctuation">,</span>         <span class="token comment">// 昏暗</span>    LIGHT_LEVEL_NORMAL<span class="token punctuation">,</span>      <span class="token comment">// 正常</span>    LIGHT_LEVEL_BRIGHT       <span class="token comment">// 明亮</span><span class="token punctuation">&#125;</span> LightLevel<span class="token punctuation">;</span><span class="token comment">// 光照等级阈值定义（ADC值）</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LIGHT_THRESHOLD_DARK</span> <span class="token expression"><span class="token number">3000</span>   </span><span class="token comment">// 黑暗阈值</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LIGHT_THRESHOLD_DIM</span> <span class="token expression"><span class="token number">1500</span>    </span><span class="token comment">// 昏暗阈值</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">LIGHT_THRESHOLD_NORMAL</span> <span class="token expression"><span class="token number">1000</span> </span><span class="token comment">// 正常阈值</span></span></code></pre><h5 id="LightSensor-Init"><a href="#LightSensor-Init" class="headerlink" title="LightSensor_Init()"></a><strong>LightSensor_Init()</strong></h5><p>该模块主要配置GPIO口和ADC初始化。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">LightSensor_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>  ADC_InitTypeDef ADC_InitStructure<span class="token punctuation">;</span>  <span class="token comment">// 使能ADC1和GPIOA的时钟</span>  <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_ADC1 <span class="token operator">|</span> RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 配置PA0为模拟输入</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> LIGHT_SENSOR_PIN<span class="token punctuation">;</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_AIN<span class="token punctuation">;</span>  <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>LIGHT_SENSOR_PORT<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// ADC1配置</span>  <span class="token function">ADC_DeInit</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_Mode <span class="token operator">=</span> ADC_Mode_Independent<span class="token punctuation">;</span>    <span class="token comment">// 独立模式</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_ScanConvMode <span class="token operator">=</span> DISABLE<span class="token punctuation">;</span>         <span class="token comment">// 单通道模式</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_ContinuousConvMode <span class="token operator">=</span> DISABLE<span class="token punctuation">;</span>  <span class="token comment">// 单次转换模式</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_ExternalTrigConv <span class="token operator">=</span> ADC_ExternalTrigConv_None<span class="token punctuation">;</span> <span class="token comment">// 外部触发模式</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_DataAlign <span class="token operator">=</span> ADC_DataAlign_Right<span class="token punctuation">;</span> <span class="token comment">// 数据对齐</span>  ADC_InitStructure<span class="token punctuation">.</span>ADC_NbrOfChannel <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  <span class="token function">ADC_Init</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> <span class="token operator">&amp;</span>ADC_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 配置ADC通道和采样时间</span>  <span class="token function">ADC_RegularChannelConfig</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ADC_Channel_0<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> ADC_SampleTime_239Cycles5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 配置ADC通道和采样时间</span>  <span class="token comment">// 使能ADC1</span>  <span class="token function">ADC_Cmd</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// ADC校准</span>  <span class="token function">ADC_ResetCalibration</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 复位校准</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">ADC_GetResetCalibrationStatus</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">;</span>  <span class="token function">ADC_StartCalibration</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 开始校准</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">ADC_GetCalibrationStatus</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="LightSensor-GetValue"><a href="#LightSensor-GetValue" class="headerlink" title="LightSensor_GetValue()"></a><strong>LightSensor_GetValue</strong>()</h5><p>获取ADC值主要通过<code>ADC_GetConversionValue(ADC1);</code></p><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">uint16_t</span> <span class="token function">LightSensor_GetValue</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token comment">// 启动ADC转换</span>  <span class="token function">ADC_SoftwareStartConvCmd</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 等待转换完成</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">ADC_GetFlagStatus</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ADC_FLAG_EOC<span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">;</span>  <span class="token comment">// 返回转换结果</span>  <span class="token keyword">return</span> <span class="token function">ADC_GetConversionValue</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="MPU6050模块"><a href="#MPU6050模块" class="headerlink" title="MPU6050模块"></a>MPU6050模块</h4><p>由MPU6050官方文档可以知道默认寄存器值为<code>0x68</code></p><p><img src="https://i-blog.csdnimg.cn/direct/c3985068c9934607b854b4751bcb5009.png"></p>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/18/stm32f103c8t6-zong-he-xue-xi-xiang-mu-de-ju-ti-gong-neng-shi-xian-xiang-jie/</id>
    <link href="https://lsworl.github.io/2025/01/18/stm32f103c8t6-zong-he-xue-xi-xiang-mu-de-ju-ti-gong-neng-shi-xian-xiang-jie/"/>
    <published>2025-01-18T02:12:33.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>

<blockquote>
<p>代码项目地址为<a]]>
    </summary>
    <title>STM32F103C8T6综合学习项目的具体功能实现详解</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>该项目基于型号STM32F103C8T6，项目的具体内容参考GitHub仓库，根据各个模块来学习对应的功能的实现。</p></blockquote><h3 id="开发GPIO驱动"><a href="#开发GPIO驱动" class="headerlink" title="开发GPIO驱动"></a>开发GPIO驱动</h3><p>在开发GPIO模块前，需要对<code>GPIO_TypeDef</code>中的参数有一定的认识。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">typedef</span> <span class="token keyword">struct</span><span class="token punctuation">&#123;</span>  __IO <span class="token class-name">uint32_t</span> CRL<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> CRH<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> IDR<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> ODR<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> BSRR<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> BRR<span class="token punctuation">;</span>  __IO <span class="token class-name">uint32_t</span> LCKR<span class="token punctuation">;</span><span class="token punctuation">&#125;</span> GPIO_TypeDef<span class="token punctuation">;</span></code></pre><h4 id="1-CRL-Configuration-Register-Low"><a href="#1-CRL-Configuration-Register-Low" class="headerlink" title="1. CRL (Configuration Register Low)"></a>1. <strong>CRL (Configuration Register Low)</strong></h4><ul><li><p><strong>作用</strong>：配置 GPIO 端口的低 8 个引脚（Pin 0 ~ Pin 7）的模式和速度。</p></li><li><p><strong>位域</strong>：</p><ul><li>每 4 位控制一个引脚：<ul><li><code>MODEy[1:0]</code>：配置引脚的模式（输入、输出、复用功能、模拟模式）。</li><li><code>CNFy[1:0]</code>：配置引脚的输出类型（推挽、开漏、复用功能、模拟模式）。</li></ul></li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>设置 Pin 0 为推挽输出模式：</p><pre class="language-none"><code class="language-none">GPIOx-&gt;CRL &amp;&#x3D; ~(0xF &lt;&lt; 0); &#x2F;&#x2F; 清除 Pin 0 的配置GPIOx-&gt;CRL |&#x3D; (0x3 &lt;&lt; 0);  &#x2F;&#x2F; 设置 Pin 0 为推挽输出模式</code></pre></li></ul></li></ul><hr><h4 id="2-CRH-Configuration-Register-High"><a href="#2-CRH-Configuration-Register-High" class="headerlink" title="2. CRH (Configuration Register High)"></a>2. <strong>CRH (Configuration Register High)</strong></h4><ul><li><p><strong>作用</strong>：配置 GPIO 端口的高 8 个引脚（Pin 8 ~ Pin 15）的模式和速度。</p></li><li><p><strong>位域</strong>：</p><ul><li>每 4 位控制一个引脚：<ul><li><code>MODEy[1:0]</code>：配置引脚的模式（输入、输出、复用功能、模拟模式）。</li><li><code>CNFy[1:0]</code>：配置引脚的输出类型（推挽、开漏、复用功能、模拟模式）。</li></ul></li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>设置 Pin 8 为开漏输出模式：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;CRH &amp;&#x3D; ~(0xF &lt;&lt; 0); &#x2F;&#x2F; 清除 Pin 8 的配置GPIOx-&gt;CRH |&#x3D; (0x6 &lt;&lt; 0);  &#x2F;&#x2F; 设置 Pin 8 为开漏输出模式</code></pre></li></ul></li></ul><hr><h4 id="3-IDR-Input-Data-Register"><a href="#3-IDR-Input-Data-Register" class="headerlink" title="3. IDR (Input Data Register)"></a>3. <strong>IDR (Input Data Register)</strong></h4><ul><li><p><strong>作用</strong>：读取 GPIO 端口的输入数据。</p></li><li><p><strong>位域</strong>：</p><ul><li>低 16 位（Bit 0 ~ Bit 15）对应 GPIO 的 16 个引脚。</li><li>每个位的值为 <code>0</code> 或 <code>1</code>，表示对应引脚的输入状态。</li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>读取 Pin 5 的输入状态：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">uint8_t pinState &#x3D; (GPIOx-&gt;IDR &amp; GPIO_PIN_5) ? 1 : 0;</code></pre></li></ul></li></ul><hr><h4 id="4-ODR-Output-Data-Register"><a href="#4-ODR-Output-Data-Register" class="headerlink" title="4. ODR (Output Data Register)"></a>4. <strong>ODR (Output Data Register)</strong></h4><ul><li><p><strong>作用</strong>：设置 GPIO 端口的输出数据。</p></li><li><p><strong>位域</strong>：</p><ul><li>低 16 位（Bit 0 ~ Bit 15）对应 GPIO 的 16 个引脚。</li><li>每个位的值为 <code>0</code> 或 <code>1</code>，表示对应引脚的输出状态。</li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>设置 Pin 6 输出高电平：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;ODR |&#x3D; GPIO_PIN_6;</code></pre></li><li><p>设置 Pin 6 输出低电平：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;ODR &amp;&#x3D; ~GPIO_PIN_6;</code></pre></li></ul></li></ul><hr><h4 id="5-BSRR-Bit-Set-x2F-Reset-Register"><a href="#5-BSRR-Bit-Set-x2F-Reset-Register" class="headerlink" title="5. BSRR (Bit Set&#x2F;Reset Register)"></a>5. <strong>BSRR (Bit Set&#x2F;Reset Register)</strong></h4><ul><li><p><strong>作用</strong>：原子地设置或复位 GPIO 引脚。</p></li><li><p><strong>位域</strong>：</p><ul><li>低 16 位（Bit 0 ~ Bit 15）：设置对应引脚为高电平。</li><li>高 16 位（Bit 16 ~ Bit 31）：复位对应引脚为低电平。</li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>设置 Pin 7 为高电平：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;BSRR &#x3D; GPIO_PIN_7;</code></pre></li><li><p>设置 Pin 7 为低电平：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;BSRR &#x3D; GPIO_PIN_7 &lt;&lt; 16;</code></pre></li></ul></li></ul><hr><h4 id="6-BRR-Bit-Reset-Register"><a href="#6-BRR-Bit-Reset-Register" class="headerlink" title="6. BRR (Bit Reset Register)"></a>6. <strong>BRR (Bit Reset Register)</strong></h4><ul><li><p><strong>作用</strong>：复位 GPIO 引脚（设置为低电平）。</p></li><li><p><strong>位域</strong>：</p><ul><li>低 16 位（Bit 0 ~ Bit 15）：复位对应引脚为低电平。</li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>复位 Pin 9 为低电平：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;BRR &#x3D; GPIO_PIN_9;</code></pre></li></ul></li></ul><hr><h4 id="7-LCKR-Configuration-Lock-Register"><a href="#7-LCKR-Configuration-Lock-Register" class="headerlink" title="7. LCKR (Configuration Lock Register)"></a>7. <strong>LCKR (Configuration Lock Register)</strong></h4><ul><li><p><strong>作用</strong>：锁定 GPIO 端口的配置寄存器（CRL 和 CRH），防止意外修改。</p></li><li><p><strong>位域</strong>：</p><ul><li>低 16 位（Bit 0 ~ Bit 15）：锁定对应引脚的配置。</li><li>Bit 16：锁定键（Lock Key），用于确认锁定操作。</li></ul></li><li><p><strong>示例</strong>：</p><ul><li><p>锁定 Pin 10 的配置：</p><p>c</p><p>Copy</p><pre class="language-none"><code class="language-none">GPIOx-&gt;LCKR &#x3D; GPIO_PIN_10 | (1 &lt;&lt; 16); &#x2F;&#x2F; 锁定 Pin 10GPIOx-&gt;LCKR &#x3D; GPIO_PIN_10;             &#x2F;&#x2F; 确认锁定GPIOx-&gt;LCKR &#x3D; GPIO_PIN_10 | (1 &lt;&lt; 16); &#x2F;&#x2F; 再次锁定uint32_t lockStatus &#x3D; GPIOx-&gt;LCKR;     &#x2F;&#x2F; 读取锁定状态</code></pre></li></ul></li></ul><h4 id="引脚初始化"><a href="#引脚初始化" class="headerlink" title="引脚初始化"></a>引脚初始化</h4><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">GPIO_Init_Pin</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">,</span> GPIOMode_TypeDef GPIO_Mode<span class="token punctuation">,</span> GPIOSpeed_TypeDef GPIO_Speed<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStruct<span class="token punctuation">;</span>        <span class="token comment">/* 使能GPIO时钟 */</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>GPIOx <span class="token operator">==</span> GPIOA<span class="token punctuation">)</span>        <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>GPIOx <span class="token operator">==</span> GPIOB<span class="token punctuation">)</span>        <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOB<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>GPIOx <span class="token operator">==</span> GPIOC<span class="token punctuation">)</span>        <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOC<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>GPIOx <span class="token operator">==</span> GPIOD<span class="token punctuation">)</span>        <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOD<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>GPIOx <span class="token operator">==</span> GPIOE<span class="token punctuation">)</span>        <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOE<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">/* 配置GPIO */</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOx<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="设置引脚状态"><a href="#设置引脚状态" class="headerlink" title="设置引脚状态"></a>设置引脚状态</h4>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/15/ji-yu-stm32-zong-he-xue-xi-xiang-mu-jie-gou-she-ji/</id>
    <link href="https://lsworl.github.io/2025/01/15/ji-yu-stm32-zong-he-xue-xi-xiang-mu-jie-gou-she-ji/"/>
    <published>2025-01-15T13:16:54.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer"]]>
    </summary>
    <title>基于STM32综合学习项目结构设计</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>采用stm32f103c8t6芯片的A0引脚连接热敏电阻模块。采用ADC1的通道0，使用标准库实现。</p></blockquote><p>实现功能所使用的常量为：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">//温度传感器参数</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">VREF</span> <span class="token expression"><span class="token number">3.3f</span>  </span><span class="token comment">// ADC参考电压 3.3V</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">R1</span> <span class="token expression"><span class="token number">10000.0f</span>  </span><span class="token comment">// 电压分压阻值为10KΩ，与热敏电阻串联，用于计算热敏电阻的阻值</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">B</span> <span class="token expression"><span class="token number">3950.0f</span>  </span><span class="token comment">// 热敏电阻的 B 常数，单位为开尔文（K），用于计算热敏电阻的温度</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">T0</span> <span class="token expression"><span class="token number">298.15f</span>  </span><span class="token comment">// 参考温度，单位为开尔文（K），298.15K即 25°C</span></span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">R0</span> <span class="token expression"><span class="token number">10000.0f</span>  </span><span class="token comment">// 热敏电阻在参考温度 T0 下的阻值，用于计算热敏电阻在当前温度下的阻值</span></span><span class="token comment">// DMA内存存入位置</span><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">define</span> <span class="token macro-name">ADC_BUFFER_SIZE</span> <span class="token expression"><span class="token number">10</span></span></span><span class="token class-name">uint16_t</span> ADC_ConvertedValue<span class="token punctuation">[</span>ADC_BUFFER_SIZE<span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre><p>GPIO引脚配置：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">GPIO_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>    <span class="token comment">// 使能GPIOA时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">//配置PA0为模拟输入用于温度传感器</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_0<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_AIN<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>ADC配置：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">ADC_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    ADC_InitTypeDef ADC_InitStructure<span class="token punctuation">;</span>    <span class="token comment">// 使能ADC1时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置 ADC1 的基本参数</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_Mode <span class="token operator">=</span> ADC_Mode_Independent<span class="token punctuation">;</span> <span class="token comment">//配置为独立模式因芯片只有ADC1</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_ScanConvMode <span class="token operator">=</span> ENABLE<span class="token punctuation">;</span><span class="token comment">//启用连续扫描，这里因只开一个通道，是否启用连续扫描都不影响结果</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_ContinuousConvMode <span class="token operator">=</span> ENABLE<span class="token punctuation">;</span><span class="token comment">//启用连续转换，每次读入模拟量就进行转换</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_ExternalTrigConv <span class="token operator">=</span> ADC_ExternalTrigConv_None<span class="token punctuation">;</span><span class="token comment">//不适用外部触发</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_DataAlign <span class="token operator">=</span> ADC_DataAlign_Right<span class="token punctuation">;</span><span class="token comment">//数据选择右对齐，转换结果低12位有效</span>    ADC_InitStructure<span class="token punctuation">.</span>ADC_NbrOfChannel <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//转换通道数设置为1</span>    <span class="token function">ADC_Init</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> <span class="token operator">&amp;</span>ADC_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 配置 ADC1 的通道，设置通道在规则组中的转换顺序为第 1 个，设置采样时间为 239.5 个时钟周期</span>    <span class="token function">ADC_RegularChannelConfig</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ADC_Channel_0<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> ADC_SampleTime_239Cycles5<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 启用 ADC1 的 DMA 功能，可以通过DMA实现高速转换</span>    <span class="token function">ADC_DMACmd</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 启用 ADC1</span>    <span class="token function">ADC_Cmd</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 校准 ADC1，消除 ADC 的偏移误差</span>    <span class="token function">ADC_ResetCalibration</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//复位 ADC1 的校准寄存器</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token function">ADC_GetResetCalibrationStatus</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//等待复位校准完成，完成置0</span>    <span class="token function">ADC_StartCalibration</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//启动 ADC1 的校准</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token function">ADC_GetCalibrationStatus</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//等待完成</span>    <span class="token comment">// 启动 ADC1 转换，使用软件触发转换功能，将结果通过DMA传输刀内存中</span>    <span class="token function">ADC_SoftwareStartConvCmd</span><span class="token punctuation">(</span>ADC1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>DMA模块实现：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">DMA_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    DMA_InitTypeDef DMA_InitStructure<span class="token punctuation">;</span>        <span class="token comment">// 使能 DMA1 时钟</span>    <span class="token function">RCC_AHBPeriphClockCmd</span><span class="token punctuation">(</span>RCC_AHBPeriph_DMA1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 配置 DMA1 通道1</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_PeripheralBaseAddr <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">uint32_t</span><span class="token punctuation">)</span><span class="token operator">&amp;</span>ADC1<span class="token operator">-></span>DR<span class="token punctuation">;</span><span class="token comment">//配置DMA读取数据位置为ADC1的DR寄存器中（每次通过A0引脚读入的值都放在DR中所以DMA通过DR读数据）</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_MemoryBaseAddr <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token class-name">uint32_t</span><span class="token punctuation">)</span>ADC_ConvertedValue<span class="token punctuation">;</span><span class="token comment">//配置DMA内存地址为ADC_ConvertedValue，即上方常量的地址</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_DIR <span class="token operator">=</span> DMA_DIR_PeripheralSRC<span class="token punctuation">;</span><span class="token comment">//传输方向为外设到内存</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_BufferSize <span class="token operator">=</span> ADC_BUFFER_SIZE<span class="token punctuation">;</span><span class="token comment">//缓冲区大小为每次DMA传输的数据量，即上方常量中的值</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_PeripheralInc <span class="token operator">=</span> DMA_PeripheralInc_Disable<span class="token punctuation">;</span><span class="token comment">//禁用外设地址自增，ADC 数据寄存器地址是固定的</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_MemoryInc <span class="token operator">=</span> DMA_MemoryInc_Enable<span class="token punctuation">;</span><span class="token comment">//启用内存地址自增，数组的每个元素地址是连续的</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_PeripheralDataSize <span class="token operator">=</span> DMA_PeripheralDataSize_HalfWord<span class="token punctuation">;</span> <span class="token comment">//设置外设数据大小为半字（16位）</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_MemoryDataSize <span class="token operator">=</span> DMA_MemoryDataSize_HalfWord<span class="token punctuation">;</span><span class="token comment">//设置内存数据大小为半字（16位），与 ADC 数据寄存器一致</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_Mode <span class="token operator">=</span> DMA_Mode_Circular<span class="token punctuation">;</span><span class="token comment">//设置 DMA 模式为循环模式，即 DMA 传输完成后会自动重新开始</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_Priority <span class="token operator">=</span> DMA_Priority_High<span class="token punctuation">;</span><span class="token comment">//设置 DMA 通道的优先级为高（因为就一个通道，不设置也没关系）</span>    DMA_InitStructure<span class="token punctuation">.</span>DMA_M2M <span class="token operator">=</span> DMA_M2M_Disable<span class="token punctuation">;</span><span class="token comment">//禁用内存到内存的传输模式</span>    <span class="token function">DMA_Init</span><span class="token punctuation">(</span>DMA1_Channel1<span class="token punctuation">,</span> <span class="token operator">&amp;</span>DMA_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 使能 DMA1 通道1</span>    <span class="token function">DMA_Cmd</span><span class="token punctuation">(</span>DMA1_Channel1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>将模拟量转换为温度：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">float</span> <span class="token function">GetTemperature</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token class-name">uint32_t</span> sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token class-name">uint8_t</span> i<span class="token punctuation">;</span>    <span class="token keyword">float</span> voltage<span class="token punctuation">,</span> rt<span class="token punctuation">,</span> temperature<span class="token punctuation">;</span>    <span class="token class-name">uint16_t</span> adc_value<span class="token punctuation">;</span>     <span class="token comment">// 计算10个样本的平均值 ADC_BUFFER_SIZE为10</span>    <span class="token keyword">for</span><span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> ADC_BUFFER_SIZE<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        sum <span class="token operator">+=</span> ADC_ConvertedValue<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    adc_value <span class="token operator">=</span> sum <span class="token operator">/</span> ADC_BUFFER_SIZE<span class="token punctuation">;</span>        <span class="token comment">// 将ADC值转换为电压 12位为4096</span>    voltage <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">float</span><span class="token punctuation">)</span>adc_value <span class="token operator">*</span> VREF <span class="token operator">/</span> <span class="token number">4096.0f</span><span class="token punctuation">;</span>    <span class="token comment">// 计算热敏电阻阻值</span>    rt <span class="token operator">=</span> R1 <span class="token operator">*</span> voltage <span class="token operator">/</span> <span class="token punctuation">(</span>VREF <span class="token operator">-</span> voltage<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 使用Steinhart-Hart公式计算温度</span>    temperature <span class="token operator">=</span> <span class="token number">1.0f</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">1.0f</span><span class="token operator">/</span>T0 <span class="token operator">+</span> <span class="token function">log</span><span class="token punctuation">(</span>rt<span class="token operator">/</span>R0<span class="token punctuation">)</span><span class="token operator">/</span>B<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">273.15f</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> temperature<span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>温度T的计算公式：<br>$$<br>T&#x3D;\frac{1}{\frac{1}{T_0}+\frac{\frac{ln(R_t)}{R_0}}{B}}<br>$$</p><h5 id="公式中的参数"><a href="#公式中的参数" class="headerlink" title="公式中的参数"></a>公式中的参数</h5><ul><li><strong>T</strong>：参考温度，通常为 25°C（即 298.15K）。</li><li><strong>R0</strong>：热敏电阻在参考温度 T0<em>T</em>0 下的阻值，通常为 10kΩ。</li><li><strong>Rt</strong>：热敏电阻在当前温度下的阻值，通过分压电路和 ADC 测量得到。</li><li><strong>B</strong>：热敏电阻的 B 常数，通常由热敏电阻的规格书提供，例如 3950。</li></ul><p>通过以上的函数调用即可实现热敏电阻读取温度，最后在main函数中使用OLED显示温度：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 初始化</span>    <span class="token function">GPIO_Config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">DMA_Config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">ADC_Config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// 初始化 OLED（OLED的代码需要自行去搜索获取）</span>    <span class="token function">OLED_Init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">OLED_Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>        <span class="token class-name">uint32_t</span> i<span class="token punctuation">;</span>        <span class="token comment">// 获取温度</span>        <span class="token keyword">float</span> temperature <span class="token operator">=</span> <span class="token function">GetTemperature</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">char</span> temp_str<span class="token punctuation">[</span><span class="token number">16</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token function">sprintf</span><span class="token punctuation">(</span>temp_str<span class="token punctuation">,</span> <span class="token string">"Temp:%.1fC"</span><span class="token punctuation">,</span> temperature<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//格式化字符串</span>        <span class="token function">OLED_ShowString</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> temp_str<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//OLED显示温度</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/15/stm32-zhong-re-min-dian-zu-mo-kuai-zai-oled-shang-shi-shi-xian-shi-wen-du/</id>
    <link href="https://lsworl.github.io/2025/01/15/stm32-zhong-re-min-dian-zu-mo-kuai-zai-oled-shang-shi-shi-xian-shi-wen-du/"/>
    <published>2025-01-15T01:11:56.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer"]]>
    </summary>
    <title>stm32中热敏电阻模块在OLED上实时显示温度</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h3 id="SG90舵机模块介绍"><a href="#SG90舵机模块介绍" class="headerlink" title="SG90舵机模块介绍"></a>SG90舵机模块介绍</h3><p><img src="https://i-blog.csdnimg.cn/direct/d01abb86c0ec4fea8bf57e3feff2552e.png"></p><h3 id="标准库代码实现驱动舵机"><a href="#标准库代码实现驱动舵机" class="headerlink" title="标准库代码实现驱动舵机"></a>标准库代码实现驱动舵机</h3><p>由上图可知可以通过PWM来实现驱动舵机，这里采用STM32F103C8T6的TIM2的通道4来实现，即该芯片的A3口引脚，如下图所示</p><p><img src="https://i-blog.csdnimg.cn/direct/766b50d8ed004cafb65dcafbf3f27713.png"></p><p>配置20ms的时基单元（以PLL时钟频率为72MHz为例），那么就是将TIM2的定时器的周期配置为20ms（50Hz），即设置<code>ARR= 2000 - 1</code>，<code>PSC = 720 -1</code>，则定时器的时钟周期就为20ms，并且我在初始位置设置为90°，即起始值设置为150，代码如下：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">TIM2_PWM_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure<span class="token punctuation">;</span>    TIM_OCInitTypeDef TIM_OCInitStructure<span class="token punctuation">;</span>    <span class="token comment">// 配置定时器周期为20ms（50Hz）</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_Period <span class="token operator">=</span> <span class="token number">2000</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 72MHz / 7200 = 10KHz</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_Prescaler <span class="token operator">=</span> <span class="token number">720</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 10KHz / 720 = 50Hz</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_ClockDivision <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 时钟不分频</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_CounterMode <span class="token operator">=</span> TIM_CounterMode_Up<span class="token punctuation">;</span> <span class="token comment">// 向上计数</span>    <span class="token function">TIM_TimeBaseInit</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> <span class="token operator">&amp;</span>TIM_TimeBaseStructure<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 初始化TIM2</span>    <span class="token comment">// 配置PWM模式</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OCMode <span class="token operator">=</span> TIM_OCMode_PWM1<span class="token punctuation">;</span> <span class="token comment">// PWM模式1</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OutputState <span class="token operator">=</span> TIM_OutputState_Enable<span class="token punctuation">;</span> <span class="token comment">// 输出使能</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_Pulse <span class="token operator">=</span> <span class="token number">150</span><span class="token punctuation">;</span> <span class="token comment">// 初始位置1.5ms</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OCPolarity <span class="token operator">=</span> TIM_OCPolarity_High<span class="token punctuation">;</span> <span class="token comment">// 输出极性高</span>    <span class="token function">TIM_OC4Init</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> <span class="token operator">&amp;</span>TIM_OCInitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用通道4</span>    <span class="token comment">// 使能TIM2</span>    <span class="token function">TIM_Cmd</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">TIM_CtrlPWMOutputs</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 主输出使能</span><span class="token punctuation">&#125;</span></code></pre><p>这里我采用A3口作为舵机的驱动引脚，那么GPIO的配置就应该是以下：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">GPIO_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>    <span class="token comment">// Enable GPIOA clock</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// Configure PA3 as TIM2_CH4 alternate function push-pull output</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_3<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_AF_PP<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>然后可以通过<code>TIM_SetCompare4();</code>该函数设置捕获&#x2F;比较寄存器4(CCR4)的值，来动态调整占空比，从而控制每个周期的高电平时间，实现舵机的转向。</p><p>即</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">TIM_SetCompare4</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> <span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//此时占空比为7.5%，高电平时间为1.5ms</span></code></pre><p>计算公式：<br>$$<br>高电平时间&#x3D; \frac{定时器时钟频率}{TIM_Pulse}&#x3D; \frac{150}{0.1MHz} &#x3D;1.5ms<br>$$</p><p>$$<br>占空比&#x3D;(\frac{TIM_Pulse}{TIM_Period +1})×100%&#x3D;\frac{150}{2000}×100%&#x3D;7.5%<br>$$</p>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/09/stm32f1-xi-lie-qu-dong-sg90-duo-ji-mo-kuai/</id>
    <link href="https://lsworl.github.io/2025/01/09/stm32f1-xi-lie-qu-dong-sg90-duo-ji-mo-kuai/"/>
    <published>2025-01-09T04:06:02.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>



<h3 id="SG90舵机模块介绍"><a href="#SG90舵机模块介绍" class="headerlink"]]>
    </summary>
    <title>STM32F1系列驱动SG90舵机模块</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="python" scheme="https://lsworl.github.io/categories/python/"/>
    <category term="python" scheme="https://lsworl.github.io/tags/python/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><blockquote><p>本文参考<a href="https://www.runoob.com/">Python菜鸟教程</a></p></blockquote><h3 id="Python基础语法"><a href="#Python基础语法" class="headerlink" title="Python基础语法"></a>Python基础语法</h3><pre class="language-python" data-language="python"><code class="language-python"><span class="token comment">#单行注释以`#`，多行注释以`"""`开头和`"""`结尾。</span><span class="token comment">#注释1</span><span class="token triple-quoted-string string">"""多行注释"""</span><span class="token comment">#缩进与if，无需分号</span><span class="token keyword">if</span> <span class="token boolean">True</span><span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'true'</span><span class="token punctuation">)</span>    <span class="token comment">#多行语句，变量通过‘\’来实现多行语句</span>a <span class="token operator">=</span> <span class="token number">1</span>b <span class="token operator">=</span> <span class="token number">2</span>c <span class="token operator">=</span> <span class="token number">3</span>total <span class="token operator">=</span> a <span class="token operator">+</span> \b <span class="token operator">+</span> \c <span class="token operator">+</span> \<span class="token keyword">print</span><span class="token punctuation">(</span>total<span class="token punctuation">)</span>  <span class="token comment">#输出6</span><span class="token comment">#字符串</span><span class="token builtin">str</span> <span class="token operator">=</span> <span class="token string">'012345'</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">)</span> <span class="token comment">#输出012345</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">#输出01234 取0到4左闭右开 -1表示最右边</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">#输出0123</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">#输出 1</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">#输出 2345</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">:</span><span class="token number">4</span><span class="token punctuation">:</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span>   <span class="token comment">#输出 02 范围[0,4),即0 1 2 3 步长为2</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token comment">#输出012345012345</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">str</span> <span class="token operator">+</span> <span class="token string">'你好'</span><span class="token punctuation">)</span>  <span class="token comment">#输出012345你好 字符串拼接</span>data <span class="token operator">=</span>  <span class="token builtin">input</span><span class="token punctuation">(</span><span class="token string">"输入数据"</span><span class="token punctuation">)</span> <span class="token comment">#读取数据</span><span class="token keyword">print</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token comment">#输出</span><span class="token comment">#同一行显示多条语句</span>a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> b<span class="token punctuation">,</span>c<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token string">'hello'</span><span class="token keyword">print</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span>b<span class="token punctuation">,</span>c<span class="token punctuation">)</span><span class="token comment">#输出1 2 hello</span></code></pre><h4 id="Python基本数据类型："><a href="#Python基本数据类型：" class="headerlink" title="Python基本数据类型："></a>Python基本数据类型：</h4><ul><li>不可变数据：Number（数字），String（字符串），Tuple（元组）；</li><li>可变数据：List（列表），Dictionary（字典），Set（集合）；</li></ul><pre class="language-python" data-language="python"><code class="language-python">a<span class="token punctuation">,</span> b<span class="token punctuation">,</span> c<span class="token punctuation">,</span> d<span class="token punctuation">,</span> e<span class="token punctuation">,</span> f <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span><span class="token number">5.1</span><span class="token punctuation">,</span><span class="token boolean">True</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token operator">+</span><span class="token number">2j</span><span class="token punctuation">,</span> <span class="token string">"Hello"</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">isinstance</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> <span class="token builtin">int</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">isinstance</span><span class="token punctuation">(</span>b<span class="token punctuation">,</span> <span class="token builtin">float</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">isinstance</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token builtin">bool</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">isinstance</span><span class="token punctuation">(</span>d<span class="token punctuation">,</span> <span class="token builtin">complex</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">isinstance</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token builtin">str</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">isinstance</span><span class="token punctuation">(</span>f<span class="token punctuation">,</span> <span class="token builtin">list</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token comment">#全部输出True</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token builtin">type</span><span class="token punctuation">(</span>f<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment">#输出&lt;class 'int'> &lt;class 'float'> &lt;class 'bool'> &lt;class 'complex'> &lt;class 'str'> &lt;class 'list'></span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">#输出5 强制转换float为int类型</span></code></pre><p><strong>isinstance 和 type 的区别：</strong></p><p><code>type()</code>可以检测出是否是子类，<code>isinstance()</code>无法检测出是否是子类。</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">Animal</span><span class="token punctuation">:</span>    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">:</span>        self<span class="token punctuation">.</span>name <span class="token operator">=</span> name    <span class="token keyword">def</span> <span class="token function">speak</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">&#123;</span>self<span class="token punctuation">.</span>name<span class="token punctuation">&#125;</span></span><span class="token string"> says something"</span></span><span class="token punctuation">)</span>    a<span class="token operator">=</span>Animal<span class="token punctuation">(</span><span class="token string">"Dog"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token operator">==</span>Animal<span class="token punctuation">)</span><span class="token comment">#True</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">type</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token operator">==</span><span class="token builtin">object</span><span class="token punctuation">)</span><span class="token comment">#False</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">isinstance</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span>Animal<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment">#True</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">isinstance</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span><span class="token builtin">object</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">#True</span></code></pre><h5 id="bool布尔值"><a href="#bool布尔值" class="headerlink" title="bool布尔值"></a>bool布尔值</h5><p>对于右方的值bool值判断均为<code>False</code>：<code>None</code>,<code>False</code>,<code>0</code>,<code>&#39;&#39;</code>,<code>()</code>,<code>[]</code>,<code>&#123;&#125;</code>。</p><p>其他的均为<code>True</code>。</p><pre class="language-python" data-language="python"><code class="language-python">a<span class="token punctuation">,</span>b<span class="token punctuation">,</span>c<span class="token punctuation">,</span>d<span class="token punctuation">,</span>e<span class="token punctuation">,</span>f<span class="token punctuation">,</span>g <span class="token operator">=</span> <span class="token boolean">None</span><span class="token punctuation">,</span><span class="token boolean">False</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token string">''</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">bool</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>f<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token builtin">bool</span><span class="token punctuation">(</span>g<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">#输出False False False False False False False</span><span class="token comment"># 布尔类型的整数</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">(</span><span class="token boolean">True</span><span class="token punctuation">)</span><span class="token punctuation">)</span>   <span class="token comment"># 1</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">(</span><span class="token boolean">False</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token comment"># 0</span></code></pre><h5 id="LIst列表"><a href="#LIst列表" class="headerlink" title="LIst列表"></a>LIst列表</h5><pre class="language-python" data-language="python"><code class="language-python"><span class="token builtin">list</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2.1</span><span class="token punctuation">,</span><span class="token string">'abc'</span><span class="token punctuation">,</span><span class="token boolean">True</span><span class="token punctuation">]</span>list2 <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">list</span><span class="token punctuation">)</span><span class="token comment">#输出[1, 2.1, 'abc', True]</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">list</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">#输出abc</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">list</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">#输出[2.1, 'abc']</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">list</span> <span class="token operator">+</span> list2<span class="token punctuation">)</span><span class="token comment">#输出[1, 2.1, 'abc', True, 2, 4]</span><span class="token builtin">list</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'def'</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">list</span><span class="token punctuation">)</span><span class="token comment">#输出[1, 3, 'def', True]</span></code></pre><h5 id="Tuple元组"><a href="#Tuple元组" class="headerlink" title="Tuple元组"></a>Tuple元组</h5><pre class="language-python" data-language="python"><code class="language-python">tuple1 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2.1</span><span class="token punctuation">,</span><span class="token string">'abc'</span><span class="token punctuation">,</span><span class="token boolean">True</span><span class="token punctuation">)</span>tuple2 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token comment">#一个元素后面要添加逗号</span><span class="token keyword">print</span><span class="token punctuation">(</span>tuple1<span class="token punctuation">)</span><span class="token comment">#输出(1, 2.1, 'abc', True)</span><span class="token keyword">print</span><span class="token punctuation">(</span>tuple1<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">#输出abc</span><span class="token keyword">print</span><span class="token punctuation">(</span>tuple1<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment">#输出(2.1, 'abc')</span><span class="token keyword">print</span><span class="token punctuation">(</span>tuple1 <span class="token operator">+</span> tuple2<span class="token punctuation">)</span><span class="token comment">#输出(1, 2.1, 'abc', True, 2,)</span></code></pre><p><strong>注：元组中元素不可变，列表中元素可变</strong></p><h5 id="Set集合"><a href="#Set集合" class="headerlink" title="Set集合"></a>Set集合</h5><pre class="language-python" data-language="python"><code class="language-python">set1 <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'d'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span><span class="token string">'a'</span><span class="token punctuation">&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1<span class="token punctuation">)</span>   <span class="token comment"># 输出 &#123;'a', 'b', 'c', 'd', 'e'&#125; 自动去重</span><span class="token keyword">if</span> <span class="token string">'a'</span> <span class="token keyword">in</span> set1<span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'a 在集合中'</span><span class="token punctuation">)</span>   <span class="token comment"># 走这条 输出 a 在集合中</span><span class="token keyword">else</span><span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'a 不在集合中'</span><span class="token punctuation">)</span>set2 <span class="token operator">=</span> <span class="token builtin">set</span><span class="token punctuation">(</span><span class="token string">'abcde'</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1 <span class="token operator">==</span> set2<span class="token punctuation">)</span>  <span class="token comment"># 输出 True</span>set2 <span class="token operator">=</span> <span class="token builtin">set</span><span class="token punctuation">(</span><span class="token string">'abcdh'</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1 <span class="token operator">-</span> set2<span class="token punctuation">)</span>  <span class="token comment"># 求set1和set2的差集 输出 &#123;'e'&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1 <span class="token operator">|</span> set2<span class="token punctuation">)</span>  <span class="token comment"># 求set1和set2的并集 输出 &#123;'a', 'b', 'c', 'd', 'e', 'h'&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1 <span class="token operator">&amp;</span> set2<span class="token punctuation">)</span>  <span class="token comment"># 求set1和set2的交集 输出 &#123;'a', 'b', 'c', 'd'&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span>set1 <span class="token operator">^</span> set2<span class="token punctuation">)</span>  <span class="token comment"># 求set1和set2中不重复的元素 输出 &#123;'e', 'h'&#125;</span></code></pre><h5 id="Dictionary-字典"><a href="#Dictionary-字典" class="headerlink" title="Dictionary 字典"></a>Dictionary 字典</h5><pre class="language-python" data-language="python"><code class="language-python"><span class="token builtin">dict</span> <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token string">'key1'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'value1'</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token string">'key2'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'value2'</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token string">'key3'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'value3'</span>dict2 <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">'d_key1'</span><span class="token punctuation">:</span> <span class="token string">'d_value1'</span><span class="token punctuation">,</span>    <span class="token number">0</span><span class="token punctuation">:</span> <span class="token string">'d_value2'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">[</span><span class="token string">'key1'</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment"># 输出 value1</span><span class="token keyword">print</span><span class="token punctuation">(</span>dict2<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment"># 输出 d_value2</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">.</span>keys<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment"># 输出 dict_keys(['key1', 'key2', 'key3']) 输出所有key值</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">.</span>values<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment"># 输出 dict_values(['value1', 'value2', 'value3']) 输出所有value值</span></code></pre><h4 id="Python运算符"><a href="#Python运算符" class="headerlink" title="Python运算符"></a>Python运算符</h4><table><thead><tr><th>算数运算符</th><th>描述</th><th>例子</th></tr></thead><tbody><tr><td>+</td><td>相加</td><td>5+6输出11，’a’+’b’输出‘ab’</td></tr><tr><td>-</td><td>相减只允许数之间集合中表示差集</td><td>5-6输出-1</td></tr><tr><td>*</td><td>相乘</td><td>5*6输出30  ‘a’*2输出’aa’</td></tr><tr><td>&#x2F;</td><td>相除</td><td>1&#x2F;2输出0.5</td></tr><tr><td>&#x2F;&#x2F;</td><td>整除，向下取整，去掉小数部分</td><td>1&#x2F;&#x2F;2输出0</td></tr><tr><td>%</td><td>取模</td><td>1%2输出1</td></tr><tr><td>**</td><td>取幂</td><td>5**2输出25</td></tr></tbody></table><table><thead><tr><th>赋值运算符</th><th>描述</th><th>例子</th></tr></thead><tbody><tr><td>&#x3D;</td><td>直接赋值</td><td>c&#x3D;1</td></tr><tr><td>+&#x3D;</td><td>加法赋值</td><td>c+&#x3D;1等效 c &#x3D; c+1</td></tr><tr><td>-&#x3D;</td><td>减法赋值</td><td>c-&#x3D;1等效 c &#x3D; c-1</td></tr><tr><td>*&#x3D;</td><td>乘法赋值</td><td>c*&#x3D;1等效 c &#x3D; c*1</td></tr><tr><td>&#x2F;&#x3D;</td><td>除法赋值</td><td>c&#x2F;&#x3D;1等效 c &#x3D; c&#x2F;1</td></tr><tr><td>&#x2F;&#x2F;&#x3D;</td><td>整除赋值</td><td>c&#x2F;&#x2F;&#x3D;1等效 c &#x3D; c&#x2F;&#x2F;1</td></tr><tr><td>%&#x3D;</td><td>取模赋值</td><td>c%&#x3D;1等效 c &#x3D; c%1</td></tr><tr><td>**&#x3D;</td><td>取幂赋值</td><td>c**&#x3D;1等效 c &#x3D; c**1</td></tr><tr><td>:&#x3D;</td><td>海象运算符，用于表达式中同时赋值和返回赋值的值（<strong>Pthon3.8新增</strong>）</td><td>下方为例子</td></tr></tbody></table><pre class="language-python" data-language="python"><code class="language-python">n <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token keyword">if</span> n <span class="token operator">></span> <span class="token number">5</span><span class="token punctuation">:</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token comment">#使用海象运算符</span><span class="token keyword">if</span> <span class="token punctuation">(</span>n <span class="token operator">:=</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token operator">></span><span class="token number">5</span><span class="token punctuation">:</span><span class="token comment">#可以直接在表达式中赋值，省去外部赋值</span>    <span class="token keyword">print</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span></code></pre><table><thead><tr><th>位运算符</th><th>例子</th></tr></thead><tbody><tr><td>&amp;</td><td>与，print(0x5 &amp; 0xf) 输出5 ，即0000 0101 &amp; 0000 1111</td></tr><tr><td>|</td><td>或，print(0x5 | 0xf) 输出15 ，即0000 0101 | 0000 1111</td></tr><tr><td>^</td><td>异或，print(0x5 | 0xf) 输出10 ，即0000 0101 ^ 0000 1111，结果0000 1010</td></tr><tr><td>~</td><td>取反（补码的形式），print(0x5)输出-6，即~0000 0101，输出1111 1010，原码为1000 0110</td></tr><tr><td>&lt;&lt;</td><td>左移，print(0x5&lt;&lt;1)输出10，即0000 0101 &lt;&lt; 1，结果0000 1010</td></tr><tr><td>&gt;&gt;</td><td>右移，print(0xff&gt;&gt;1)输出127，即1111 1111&gt;&gt;1，结果0111 1111</td></tr></tbody></table><table><thead><tr><th>逻辑运算符</th><th></th></tr></thead><tbody><tr><td>and</td><td>x and y，如果x为False，返回x，True则返回y，如 print(10 and 20)输出 20</td></tr><tr><td>or</td><td>x or y，如果x为False，返回y，True则返回x，如 print(10 and 20)输出 10</td></tr><tr><td>not</td><td>not x,如果x为True，返回False，为False返回Ture。如print(not 20)输出 False</td></tr></tbody></table><table><thead><tr><th>成员运算符</th><th></th></tr></thead><tbody><tr><td>in</td><td>判断x是否在y的序列中，若在返回True</td></tr><tr><td>not in</td><td>判断x是否在y的序列中，若不在返回True</td></tr></tbody></table><pre class="language-python" data-language="python"><code class="language-python"><span class="token builtin">list</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">0</span> <span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token number">0</span> <span class="token keyword">in</span> <span class="token builtin">list</span><span class="token punctuation">)</span><span class="token comment">#输出True</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token number">5</span> <span class="token keyword">not</span> <span class="token keyword">in</span> <span class="token builtin">list</span><span class="token punctuation">)</span><span class="token comment">#输出True</span></code></pre><table><thead><tr><th>身份运算符</th><th></th></tr></thead><tbody><tr><td>is</td><td>x is y判断是否x和y的id相同，相同返回Ture（id通过id()函数查看）</td></tr><tr><td>is not</td><td>x is y判断是否x和y的id相同，相同返回False</td></tr></tbody></table><h3 id="Python常用函数"><a href="#Python常用函数" class="headerlink" title="Python常用函数"></a>Python常用函数</h3><h4 id="数学函数"><a href="#数学函数" class="headerlink" title="数学函数"></a>数学函数</h4><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> math <span class="token keyword">import</span> <span class="token operator">*</span><span class="token keyword">import</span> random<span class="token comment">#数学函数</span><span class="token builtin">abs</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token comment">#取绝对值 输出10</span>ceil<span class="token punctuation">(</span><span class="token number">4.1</span><span class="token punctuation">)</span> <span class="token comment">#向上取整 输出5</span>exp<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">#e的1次方 输出2.718281828459045</span>fabs<span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">10.2</span><span class="token punctuation">)</span> <span class="token comment">#浮点数取绝对值 输出10.2</span>floor<span class="token punctuation">(</span><span class="token number">4.9</span><span class="token punctuation">)</span> <span class="token comment">#向下取整 输出4</span>log<span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">,</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token comment">#以10为底100的对数 输出2.0</span>log2<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token comment">#以2为底4的对数 输出2.0</span><span class="token builtin">max</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment">#取最大值 输出5</span><span class="token builtin">min</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token comment">#取最小值 输出1</span>modf<span class="token punctuation">(</span><span class="token number">4.5</span><span class="token punctuation">)</span> <span class="token comment">#返回整数部分和小数部分 输出(0.5, 4.0)</span><span class="token builtin">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token comment">#2的3次方 输出8.0</span><span class="token builtin">round</span><span class="token punctuation">(</span><span class="token number">4.6</span><span class="token punctuation">)</span> <span class="token comment">#四舍五入 输出5</span>sqrt<span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token comment">#开平方 输出2.0</span><span class="token comment">#随机数函数</span>random<span class="token punctuation">.</span>choice<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">#从序列中随机选取一个元素 输出3</span>random<span class="token punctuation">.</span>randrange<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token comment">#从指定范围内，按指定基数递增的集合中获取一个随机数 输出1</span>random<span class="token punctuation">.</span>random<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">#随机生成下一个实数，它在[0,1)范围内 输出0.940667311678628</span>random<span class="token punctuation">.</span>seed<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token comment">#改变随机数生成器的种子seed 输出None</span><span class="token keyword">print</span><span class="token punctuation">(</span>random<span class="token punctuation">.</span>random<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">#0.5714025946899135  与采用新种子生成上面的random.random()不同</span>random<span class="token punctuation">.</span>shuffle<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">#将一个列表中的元素打乱 输出[2, 5, 3, 1, 4]</span>random<span class="token punctuation">.</span>uniform<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token comment">#随机生成下一个实数，它在[x,y]范围内 输出7.750021655</span></code></pre>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/07/python3-yu-fa/</id>
    <link href="https://lsworl.github.io/2025/01/07/python3-yu-fa/"/>
    <published>2025-01-07T02:31:15.000Z</published>
    <summary>整理 Python3 基础语法、字符串、列表、字典、函数和常用内置方法，适合作为入门速查笔记。</summary>
    <title>Python3中常用函数</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="环境搭建" scheme="https://lsworl.github.io/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    <category term="hexo" scheme="https://lsworl.github.io/tags/hexo/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><p>在本地可以用hexo来搭建自己的博客后（若还没有搭建可以看我的<a href="https://lsworl.github.io/2022/04/11/hexo-da-jian-yu-ying-yong-zhu-ti/">博客搭建</a>这篇文章），再在<a href="https://vercel.com/">Vercel</a>实现线上访问（若想以其他静态页面而非Hexo来Vercel结合github实现线上访问可以看我的<a href="https://lsworl.github.io/2022/05/27/shi-yong-vercel-lai-bu-shu-jing-tai-ye-mian-bing-jie-jue-kua-yu-wen-ti/">使用Vercel来部署静态页面并解决跨域问题</a>这篇文章）。</p><p>经过以上两个步骤可以实现使用科技手段来访问搭建的网页或博客，但此时若不想使用科技手段对Vercel提供的域名在国内访问搭建的博客大概率是会访问失败。（这里的原理结合我所学的知识应该是因为Vercel所生成的默认的域名在国内dns服务器解析失败导致无法找到ip，从而使我们所搭建的网站在国内无法访问）  </p><p>这个问题可以通过购买国内域名就可以实现，购买一些便宜的域名即可（域名购买的步骤不再演示了，便宜的域名6块1年也不是很贵，这里以阿里云的域名为例）。</p><p>域名购买完成后可在搜索栏中搜索域名，进入域名控制台页面。</p><p><img src="https://i-blog.csdnimg.cn/direct/ac2336782b1849b49b325725d1a85557.png"></p><p>然后点击全部域名查看自己购买到的域名。</p><p><img src="https://i-blog.csdnimg.cn/direct/abaa16c19ff94bec8bac03ff6739606c.png"></p><p>下方应该就会有你所购买到的域名信息（若状态不是处于正常状态需要等待一段时间检查）。</p><p><img src="https://i-blog.csdnimg.cn/direct/59bf3e5f54564d9aa7088916288bf301.png"></p><p>然后打开Vercel，点击右上角的ADD NEW下面的Project。</p><p><img src="https://i-blog.csdnimg.cn/direct/e9350aed7ee9476e9b6a59e26e08ca7d.png"></p><p>在下方选择你的博客或其他静态页面仓库（我的博客是lsWorl.github.io，所以我选择这个进行import）</p><p><img src="https://i-blog.csdnimg.cn/direct/e2be1a8a74814e51b8a11a7abd8dae84.png"></p><p>该页面下无需额外配置直接点部署。（当然也可以修改Framework Preset中改成hexo，或者你的build或output命令自己做了自定义则需修改build and output Setting，更详细的可以留言进行进一步交流，这里我直接点部署）</p><p><img src="https://i-blog.csdnimg.cn/direct/42990545dbca44bdb8ac9a263a33e4d1.png"></p><p>部署完成应该可以通过外网访问自己的博客了。</p><p><img src="https://i-blog.csdnimg.cn/direct/d039bd1d563447f794d1f31b074572c6.png"></p><p>接下来就是添加域名，通过点击下方Add Domain。</p><p><img src="https://i-blog.csdnimg.cn/direct/1ae0b8eb093e4af2bffe901268fa2d45.png"></p><p>点击Edit。</p><p><img src="https://i-blog.csdnimg.cn/direct/432faaa7ed724427841dddad1649d6f1.png"></p><p>在输入框中输入你购买到的域名后点击Add。</p><p><img src="https://i-blog.csdnimg.cn/direct/a4dc6c4d64d143e0a035878f174d1903.png"></p><p>直接点击Add即可，无需修改。</p><p><img src="https://i-blog.csdnimg.cn/direct/bb901405a15a4eb2a914c886be219bba.png"></p><p>然后回到阿里云域名平台这点击解析设置。</p><p><img src="https://i-blog.csdnimg.cn/direct/d78743866a8a4348b6f3a6845e3c2d39.png"></p><p>点击添加记录。（我这里因为先前已经添加过两条记录所以下方有两条已经启用的主机记录）</p><p><img src="https://i-blog.csdnimg.cn/direct/ec913f532e164f1ba82ed19af7f63dc9.png"></p><p>进入到添加记录，如我下面两个这样进行填写。（这里的<strong>记录值</strong>得和Vercel要求填写的对应，每个人的应该是不相同的）</p><p><img src="https://i-blog.csdnimg.cn/direct/b25dfd6cd5b647d0afb3b9b38bc1a8ed.png"></p><p><img src="https://i-blog.csdnimg.cn/direct/14a9042ae0384e94929646226884ab6b.png"></p><p>添加后两条记录应该处于启用状态，然后回到Vercel，下方所有都处于Valid Configuration就说明配置成功。</p><p><img src="https://i-blog.csdnimg.cn/direct/0f8c1043a23e4897a2bda971422a687e.png"></p><p>以上步骤都实现后就可以直接访问自己的博客，无需再使用科技手段！</p>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/07/hexo-bo-ke-bang-ding-yu-ming-shi-xian-guo-nei-fang-wen/</id>
    <link href="https://lsworl.github.io/2025/01/07/hexo-bo-ke-bang-ding-yu-ming-shi-xian-guo-nei-fang-wen/"/>
    <published>2025-01-07T00:30:18.000Z</published>
    <summary>记录 Hexo 博客绑定自定义域名的流程，并说明如何改善 GitHub Pages 或 Vercel 站点的国内访问体验。</summary>
    <title>hexo博客绑定域名实现国内访问</title>
    <updated>2026-05-23T07:38:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h4 id="USART串口通信"><a href="#USART串口通信" class="headerlink" title="USART串口通信"></a>USART串口通信</h4><p>stm32中使用USART串口通信主要通过RX和TX两个接口完成基础操作，发送的数据通过总线到发送数据寄存器TDR，然后TDR中数据会发送到发送移位寄存器，发送移位寄存器会将比特右移的传到USARTX_TX引脚，产生串口协议规定的波形，<strong>当数据从发送移位寄存器发送完成时，TXE标志位会置1，只需判断TXE就可以知道是否可以传下一个数据</strong>。</p><p>同样在接受控制器中通过USARTX_TX引脚接受数据，<strong>接受完成会将RXNE标志位置1</strong>。</p><p>在实际使用中只有DR寄存器可供读写，写入DR时，数据走TX发送，读出DR时，数据走TX接受。</p><p>发送数据的函数形式为</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Serial_SendChar</span><span class="token punctuation">(</span><span class="token class-name">uint8_t</span> ch<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>      <span class="token comment">// 发送字符</span>  <span class="token function">USART_SendData</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> ch<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 等待发送缓冲区为空  发送完成会将USART_FLAG_TXE置1</span>  <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">USART_GetFlagStatus</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> USART_FLAG_TXE<span class="token punctuation">)</span> <span class="token operator">==</span> RESET<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><p>接受数据的形式为</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">USART_GetITStatus</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> USART_IT_RXNE<span class="token punctuation">)</span> <span class="token operator">==</span> SET<span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    <span class="token comment">// 读取接收到的数据</span>    <span class="token class-name">uint8_t</span> RxData <span class="token operator">=</span> <span class="token function">USART_ReceiveData</span><span class="token punctuation">(</span>USART1<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">//do something...</span>        <span class="token comment">//清除接收中断标志位</span>    <span class="token function">USART_ClearITPendingBit</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> USART_IT_RXNE<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></code></pre><h5 id="波特率计算公式"><a href="#波特率计算公式" class="headerlink" title="波特率计算公式"></a>波特率计算公式</h5><p>$$<br>波特率&#x3D;f_{PCLK2或1}&#x2F;(16*DIV)<br>$$</p><p>发送器和接收器的波特率由波特率寄存器BRR里的DIV确定。</p><p>例：</p><p>若频率$f_{PCLK1}&#x3D;72MHz$，波特率为9600，代入上式，解得$DIV&#x3D;72MHz&#x2F;9600&#x2F;16&#x3D;468.75$转为十六进制为<code>0x01D4.CH</code></p><h5 id="USARTX串口初始化-例子中采用USART1端口"><a href="#USARTX串口初始化-例子中采用USART1端口" class="headerlink" title="USARTX串口初始化(例子中采用USART1端口)"></a>USARTX串口初始化(例子中采用USART1端口)</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">Serial_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span> <span class="token comment">// 定义 GPIO 初始化结构体</span>  USART_InitTypeDef USART_InitStructure<span class="token punctuation">;</span> <span class="token comment">// 定义 USART 初始化结构体</span>  <span class="token comment">// 使能 GPIOA 和 USART1 的时钟（改成对应端口）</span>  <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA <span class="token operator">|</span> RCC_APB2Periph_USART1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 配置 GPIOA 的引脚 9 为复用推挽输出（改成对应端口）</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_9<span class="token punctuation">;</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_AF_PP<span class="token punctuation">;</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>  <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 配置 GPIOA 的引脚 10 为浮空输入（改成对应端口）</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_10<span class="token punctuation">;</span>  GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_IN_FLOATING<span class="token punctuation">;</span>  <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 配置 USART 初始化结构体</span>  USART_InitStructure<span class="token punctuation">.</span>USART_BaudRate <span class="token operator">=</span> <span class="token number">9600</span><span class="token punctuation">;</span> <span class="token comment">// 波特率为 9600</span>  USART_InitStructure<span class="token punctuation">.</span>USART_WordLength <span class="token operator">=</span> USART_WordLength_8b<span class="token punctuation">;</span> <span class="token comment">// 数据位为 8 位</span>  USART_InitStructure<span class="token punctuation">.</span>USART_StopBits <span class="token operator">=</span> USART_StopBits_1<span class="token punctuation">;</span> <span class="token comment">// 停止位为 1 位</span>  USART_InitStructure<span class="token punctuation">.</span>USART_Parity <span class="token operator">=</span> USART_Parity_No<span class="token punctuation">;</span> <span class="token comment">// 无奇偶校验</span>  USART_InitStructure<span class="token punctuation">.</span>USART_HardwareFlowControl <span class="token operator">=</span> USART_HardwareFlowControl_None<span class="token punctuation">;</span> <span class="token comment">// 无硬件流控</span>  USART_InitStructure<span class="token punctuation">.</span>USART_Mode <span class="token operator">=</span> USART_Mode_Rx <span class="token operator">|</span> USART_Mode_Tx<span class="token punctuation">;</span> <span class="token comment">// 支持接收和发送</span>  <span class="token comment">// 初始化 USART1</span>  <span class="token function">USART_Init</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> <span class="token operator">&amp;</span>USART_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// 使能 USART1</span>  <span class="token function">USART_Cmd</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h4 id="I2C通信"><a href="#I2C通信" class="headerlink" title="I2C通信"></a>I2C通信</h4><p>I2C有两根通信线：SCL（Serial Clock），SDA（Serial Data），支持同步，半双工通信，支持总线挂载多设备（一主多从，多主多从）</p><p>设备的SCL和SDA均要配置程开漏输出模式，且SCL和SDA需各添加一个上拉电阻，阻值一般为4.7kΩ左右。</p><h5 id="I2C时序基本单元"><a href="#I2C时序基本单元" class="headerlink" title="I2C时序基本单元"></a>I2C时序基本单元</h5><p>I2C总线进行通信的起始条件是SCL高电平期间，SDA从高电平转换到低电平，如下图所示。</p><p><img src="https://i-blog.csdnimg.cn/direct/51b332a892e1417d8a60b991305f1830.png"></p><p>终止条件是SDA从低电平转换到高电平，SCL处于高电平。</p><p><img src="https://i-blog.csdnimg.cn/direct/2e7990de05fb43fe8e7f01ebfc3e9351.png"></p><p>I2C发送数据：在SCL低电平期间，发送端将数据位依次按高位到低位放到SDA线上，然后由接收端在SCL高电平期间读取数据位。（若主机为接受端，需要在主机接受数据之前释放SDA，即相对于切换为输入模式）</p><p>在应答时候SCL处于高电平时，SDA应处于低电平表示收到应答信号。第一个发送字节通常都是主机寻找从机地址。</p><h5 id="软件实现"><a href="#软件实现" class="headerlink" title="软件实现"></a>软件实现</h5><ul><li><p>初始化（以stm32f103c8t6芯片的引脚为例，可知PB10是I2C_SCL的引脚，PB11是I2C_SDA的引脚）</p><p><img src="https://i-blog.csdnimg.cn/direct/9ea2727127a74f1f9eb46083f0b4b65e.png"></p></li></ul><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">MyI2C_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">// 使能 I2C1 时钟</span>    <span class="token function">RCC_APB1PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB1Periph_I2C1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 使能 GPIOB 时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOB<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 配置 GPIOB 的引脚 10 和 11 为开漏输出</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_Out_OD<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_10 <span class="token operator">|</span> GPIO_Pin_11<span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>SCL_PORT<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">GPIO_SetBits</span><span class="token punctuation">(</span>GPIOB<span class="token punctuation">,</span> GPIO_Pin_10 <span class="token operator">|</span> GPIO_Pin_11<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 设置 PB10 和 PB11 为高电平</span>    <span class="token punctuation">&#125;</span></code></pre>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/03/chuan-kou-xie-yi/</id>
    <link href="https://lsworl.github.io/2025/01/03/chuan-kou-xie-yi/"/>
    <published>2025-01-03T13:42:33.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>



<h4 id="USART串口通信"><a href="#USART串口通信" class="headerlink"]]>
    </summary>
    <title>串口协议</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><p>ADC可以将模拟电压转换为数字变量，</p><p>输入电压范围0<del>3.3V，转换结果的范围：0</del>4095，即0V对应0，3.3V对应4095，中间均为一一对应的线性关系。</p><blockquote><p>STM32F10系列中ADC有多达18个通道，可测量16个外部和2个内部信号源。各通道的A&#x2F;D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。</p></blockquote><h5 id="STM32-ADC的总转换时间"><a href="#STM32-ADC的总转换时间" class="headerlink" title="STM32 ADC的总转换时间"></a>STM32 ADC的总转换时间</h5><p>$$<br>T_{CONV} &#x3D; 采样时间+12.5个ADC周期<br>$$</p><p>例：</p><p>当ADCCLK &#x3D;14MHz，采样时间为1.5个ADC周期，  则<br>$$<br>T_{CONV} &#x3D; 1.5 +12.5 &#x3D;14个ADC周期&#x3D;1us<br>$$</p>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/02/adc-mo-shu-zhuan-huan-qi/</id>
    <link href="https://lsworl.github.io/2025/01/02/adc-mo-shu-zhuan-huan-qi/"/>
    <published>2025-01-02T08:32:08.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer"]]>
    </summary>
    <title>ADC模数转换器</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="stm32" scheme="https://lsworl.github.io/tags/stm32/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h4 id="中断优先级"><a href="#中断优先级" class="headerlink" title="中断优先级"></a>中断优先级</h4><p>配置NVIC来进行中断优先级配置（值越小，优先级越高）。</p><h5 id="NVIC优先级分组"><a href="#NVIC优先级分组" class="headerlink" title="NVIC优先级分组"></a>NVIC优先级分组</h5><p>NVIC的优先级组通过<code>NVIC_PriorityGroupConfig(NVIC_PriorityGroup_X)</code>来配置<code>X</code>表示哪个分组。</p><table><thead><tr><th>NVIC组</th><th>抢占优先级</th><th>响应优先级</th></tr></thead><tbody><tr><td><code>NVIC_PriorityGroup_0</code></td><td>0bits</td><td>4bits，可分为0-15</td></tr><tr><td><code>NVIC_PriorityGroup_1</code></td><td>1bits</td><td>3bits</td></tr><tr><td><code>NVIC_PriorityGroup_2</code></td><td>2bits</td><td>2bits</td></tr><tr><td><code>NVIC_PriorityGroup_3</code></td><td>3bits</td><td>1bits</td></tr><tr><td><code>NVIC_PriorityGroup_4</code></td><td>4bits</td><td>0bits</td></tr></tbody></table><h5 id="计数器计数频率"><a href="#计数器计数频率" class="headerlink" title="计数器计数频率"></a>计数器计数频率</h5><p>$$<br>CK_CNT &#x3D; CK_PSC&#x2F;(PSC+1)<br>$$</p><p>CK_CNT：定时器时钟，CK_PSC：定时器时钟源，PSC：预频分值。</p><h5 id="计数器溢出频率"><a href="#计数器溢出频率" class="headerlink" title="计数器溢出频率"></a>计数器溢出频率</h5><p>$$<br>CK_CNT_OV &#x3D; CK_CNT&#x2F;(ARR +1)&#x3D;CK_PSC&#x2F;(PSC+1)&#x2F;(ARR+1)<br>$$</p><p>ARR：自动重装值。</p><h3 id="PWM"><a href="#PWM" class="headerlink" title="PWM"></a>PWM</h3><h5 id="PWM频率"><a href="#PWM频率" class="headerlink" title="PWM频率"></a>PWM频率</h5><p>$$<br>Freq &#x3D;CK_CNT &#x2F;(PSC+1)&#x2F;(ARR+1)<br>$$</p><h5 id="PWM占空比"><a href="#PWM占空比" class="headerlink" title="PWM占空比"></a>PWM占空比</h5><p>$$<br>Duty&#x3D;CCR&#x2F;(ARR+1)<br>$$</p><h5 id="PWM分辨率"><a href="#PWM分辨率" class="headerlink" title="PWM分辨率"></a>PWM分辨率</h5><p>$$<br>Reso &#x3D; 1&#x2F;(ARR+1)<br>$$</p><h4 id="标准库PWM设置"><a href="#标准库PWM设置" class="headerlink" title="标准库PWM设置"></a>标准库PWM设置</h4><p>以TIM2的通道4为例。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">TIM2_PWM_Config</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure<span class="token punctuation">;</span>    TIM_OCInitTypeDef TIM_OCInitStructure<span class="token punctuation">;</span>    <span class="token comment">// 配置定时器周期为20ms（50Hz）</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_Period <span class="token operator">=</span> <span class="token number">2000</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 72MHz / 7200 = 10KHz (ARR)</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_Prescaler <span class="token operator">=</span> <span class="token number">720</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 10KHz / 720 = 50Hz (PSC)</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_ClockDivision <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 时钟不分频</span>    TIM_TimeBaseStructure<span class="token punctuation">.</span>TIM_CounterMode <span class="token operator">=</span> TIM_CounterMode_Up<span class="token punctuation">;</span> <span class="token comment">// 向上计数</span>    <span class="token function">TIM_TimeBaseInit</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> <span class="token operator">&amp;</span>TIM_TimeBaseStructure<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 初始化TIM2</span>    <span class="token comment">// 配置PWM模式</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OCMode <span class="token operator">=</span> TIM_OCMode_PWM1<span class="token punctuation">;</span> <span class="token comment">// PWM模式1</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OutputState <span class="token operator">=</span> TIM_OutputState_Enable<span class="token punctuation">;</span> <span class="token comment">// 输出使能</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_Pulse <span class="token operator">=</span> <span class="token number">150</span><span class="token punctuation">;</span> <span class="token comment">// 初始位置1.5ms (初始值)</span>    TIM_OCInitStructure<span class="token punctuation">.</span>TIM_OCPolarity <span class="token operator">=</span> TIM_OCPolarity_High<span class="token punctuation">;</span> <span class="token comment">// 输出极性高</span>    <span class="token function">TIM_OC4Init</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> <span class="token operator">&amp;</span>TIM_OCInitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用通道4</span>    <span class="token comment">// 使能TIM2</span>    <span class="token function">TIM_Cmd</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">TIM_CtrlPWMOutputs</span><span class="token punctuation">(</span>TIM2<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 主输出使能</span><span class="token punctuation">&#125;</span></code></pre><h4 id="PWM模式1和PWM模式2的区别"><a href="#PWM模式1和PWM模式2的区别" class="headerlink" title="PWM模式1和PWM模式2的区别"></a><strong>PWM模式1和PWM模式2的区别</strong></h4><table><thead><tr><th>特性</th><th>PWM模式1 (<code>TIM_OCMode_PWM1</code>)</th><th>PWM模式2 (<code>TIM_OCMode_PWM2</code>)</th></tr></thead><tbody><tr><td><strong>有效电平</strong></td><td>计数器值 &lt; <code>TIM_Pulse</code> 时有效</td><td>计数器值 ≥ <code>TIM_Pulse</code> 时有效</td></tr><tr><td><strong>无效电平</strong></td><td>计数器值 ≥ <code>TIM_Pulse</code> 时无效</td><td>计数器值 &lt; <code>TIM_Pulse</code> 时无效</td></tr><tr><td><strong>极性反转</strong></td><td>通过 <code>TIM_OCPolarity</code> 配置</td><td>通过 <code>TIM_OCPolarity</code> 配置</td></tr><tr><td><strong>适用场景</strong></td><td>常规PWM控制</td><td>需要反向逻辑的PWM控制</td></tr></tbody></table>]]>
    </content>
    <id>https://lsworl.github.io/2025/01/01/stm32tim-ding-shi-zhong-duan/</id>
    <link href="https://lsworl.github.io/2025/01/01/stm32tim-ding-shi-zhong-duan/"/>
    <published>2025-01-01T07:03:18.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>

<h4 id="中断优先级"><a href="#中断优先级" class="headerlink"]]>
    </summary>
    <title>stm32TIM定时中断</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="嵌入式" scheme="https://lsworl.github.io/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    <category term="单片机" scheme="https://lsworl.github.io/tags/%E5%8D%95%E7%89%87%E6%9C%BA/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h3 id="标准库初始化"><a href="#标准库初始化" class="headerlink" title="标准库初始化"></a>标准库初始化</h3><hr><blockquote><p>[!IMPORTANT]</p><p>采用Keil IDE基于C语言进行编写stm32运行代码（以stm32f10x系列为例），使用标准库首先需要引入相应的库文件，该文件需要自己去网上下载  </p></blockquote><h4 id="新建工程"><a href="#新建工程" class="headerlink" title="新建工程"></a>新建工程</h4><p>编写逻辑代码前需要对keil将文件夹中的内容读入到IDE中 ，点击keil上方tab栏中的<code>Project</code>里面的<code>New μVisioon Project...</code>，找到存放共工程的文件夹，并在该文件夹中在下方的文件名中输入该工程的名称，后点击保存。</p><p><img src="https://i-blog.csdnimg.cn/direct/4ea1f7c781d04b82b40bdb6a10f51acf.png" alt="选择芯片">   </p><p>在芯片选择中选择<code>STM32F1x</code>对应的芯片，后点击OK，后续的窗口直接关闭即可。  </p><p>后面再将对应的固件库文件放入到该工程文件夹中。</p><h5 id="keil中把文件导入工程"><a href="#keil中把文件导入工程" class="headerlink" title="keil中把文件导入工程"></a>keil中把文件导入工程</h5><p>在keil中点击三个方块样式的工程管理按钮</p><p><img src="https://i-blog.csdnimg.cn/direct/39bdbbf0c37f4ec3991068162084ba65.png">  </p><p>在<code>Project Items</code>中的<code>Groups</code>可添加对应名的文件夹，然后在再右侧的<code>files</code>的下方<code>Add Files</code>添加对应的固件库文件，即可成功创建文件。  </p><p>然后点击三个方块样式旁边的魔术棒按钮。</p><p><img src="https://i-blog.csdnimg.cn/direct/746be52f03874323a14db7cf49249dd8.png">  </p><p>在Tab栏中C&#x2F;C++中的<code>Include Paths</code>中添加工程所用到的文件以及<code>Define</code>中改为<code>USE_STDPERIPH_DRIVER</code>。</p><p><img src="https://i-blog.csdnimg.cn/direct/2b966ba0e35a4e9487f5e41258f9129c.png"></p><p><strong>注：程序烧入到单片机中需要在该tab中的<code>Debug</code>中设置</strong>  </p><p>这样就成功完成了工程创建。</p><h4 id="操作STM32的GPIO所需的初始化"><a href="#操作STM32的GPIO所需的初始化" class="headerlink" title="操作STM32的GPIO所需的初始化"></a>操作STM32的GPIO所需的初始化</h4><blockquote><p>先引入包<code>#include &quot;stm32f10x.h&quot;</code>再进行后续操作</p></blockquote><p>首先需要将stm32的时钟使能，对应的函数方法在<code>stm32f10x_rcc.h</code>中，主要函数为以下三个</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">RCC_AHBPeriphClockCmd</span><span class="token punctuation">(</span><span class="token class-name">uint32_t</span> RCC_AHBPeriph<span class="token punctuation">,</span> FunctionalState NewState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//使能AHB的外设时钟</span><span class="token keyword">void</span> <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span><span class="token class-name">uint32_t</span> RCC_APB2Periph<span class="token punctuation">,</span> FunctionalState NewState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//使能PB2的外设时钟</span><span class="token keyword">void</span> <span class="token function">RCC_APB1PeriphClockCmd</span><span class="token punctuation">(</span><span class="token class-name">uint32_t</span> RCC_APB1Periph<span class="token punctuation">,</span> FunctionalState NewState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//使能PB1的外设时钟</span></code></pre><p>还需要使用<code>void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)</code>函数，使用结构体的参数来初始化GPIO口，使用该函数需要定义以该结构体<code>GPIO_InitTypeDef</code>为类型的结构体变量，以下为例：  </p><pre class="language-c" data-language="c"><code class="language-c">GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_Out_PP<span class="token punctuation">;</span><span class="token comment">//</span>GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_13<span class="token punctuation">;</span> <span class="token comment">//</span>GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span><span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span><span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>加上以上操作的完整代码为：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token macro property"><span class="token directive-hash">#</span><span class="token directive keyword">include</span> <span class="token string">"stm32f10x.h"</span>                  <span class="token comment">// Device header</span></span><span class="token keyword">int</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token comment">//ENABLE CLOCK</span>    GPIO_InitTypeDef GPIO_InitStructure<span class="token punctuation">;</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOA<span class="token punctuation">,</span>ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_Out_PP<span class="token punctuation">;</span><span class="token comment">//采用推挽输出模式</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_0 <span class="token operator">|</span> GPIO_Pin_1 <span class="token operator">|</span> GPIO_Pin_2<span class="token punctuation">;</span><span class="token comment">//使用GPIO0，1，2号引脚</span>    GPIO_InitStructure<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span><span class="token comment">//输出速度设置为50MHz</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span><span class="token operator">&amp;</span>GPIO_InitStructure<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//初始化GPIOA外设的0号引脚</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>    <span class="token punctuation">&#123;</span>            <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="GPIO的工作模式"><a href="#GPIO的工作模式" class="headerlink" title="GPIO的工作模式"></a>GPIO的工作模式</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">typedef</span> <span class="token keyword">enum</span><span class="token punctuation">&#123;</span> GPIO_Mode_AIN <span class="token operator">=</span> <span class="token number">0x0</span><span class="token punctuation">,</span><span class="token comment">//模拟输入</span>  GPIO_Mode_IN_FLOATING <span class="token operator">=</span> <span class="token number">0x04</span><span class="token punctuation">,</span> <span class="token comment">//浮空输入</span>  GPIO_Mode_IPD <span class="token operator">=</span> <span class="token number">0x28</span><span class="token punctuation">,</span><span class="token comment">//下拉输入</span>  GPIO_Mode_IPU <span class="token operator">=</span> <span class="token number">0x48</span><span class="token punctuation">,</span><span class="token comment">//上拉输入</span>  GPIO_Mode_Out_OD <span class="token operator">=</span> <span class="token number">0x14</span><span class="token punctuation">,</span><span class="token comment">//开漏输出</span>  GPIO_Mode_Out_PP <span class="token operator">=</span> <span class="token number">0x10</span><span class="token punctuation">,</span><span class="token comment">//推挽输出</span>  GPIO_Mode_AF_OD <span class="token operator">=</span> <span class="token number">0x1C</span><span class="token punctuation">,</span><span class="token comment">//复用开漏</span>  GPIO_Mode_AF_PP <span class="token operator">=</span> <span class="token number">0x18</span><span class="token comment">//复用推挽</span><span class="token punctuation">&#125;</span>GPIOMode_TypeDef<span class="token punctuation">;</span></code></pre><table><thead><tr><th>GPIO八种模式</th><th>特点以及应用</th></tr></thead><tbody><tr><td>模拟输入</td><td>ADC和DAC使用。此模式下引脚电平不被采样或拉动。在此模式GPIO的数字输入功能关闭。</td></tr><tr><td>浮空输入</td><td>不使用内部电阻，完全浮空，默认状态不定，通常用于外部电路已经提供明确的上拉或下拉信号的场景。如 I²C 总线 SDA&#x2F;SCL 引脚，外部已经设计有上拉电阻。</td></tr><tr><td>下拉输入</td><td>使用内部下拉电阻，默认输入低电平。适用于引脚通常为低电平，仅在特定情况下拉高。</td></tr><tr><td>上拉输入</td><td>使用内部上拉电阻，默认输入高电平。适用于检测低电平的输入信号，例如开关按钮的闭合状态。</td></tr><tr><td>开漏输出</td><td>引脚通过 MOS 管连接到地，输出低电平时导通，<strong>输出高电平时需外部上拉电阻</strong>。适用于多设备共享总线，如 I²C、多个设备的中断信号线以及电平兼容，如3.3V和5V设备互联。</td></tr><tr><td>推挽输出</td><td>引脚可以输出高电平或低电平，内部由MOS管驱动。适用于控制 LED、蜂鸣器、继电器等负载。</td></tr><tr><td>复用开漏</td><td>配置为复用功能，并使用开漏输出方式。适用于I²C 总线通信的 SDA 和 SCL 引脚以及多设备共享复用引脚时。</td></tr><tr><td>复用推挽</td><td>配置为复用功能，并使用推挽输出方式驱动信号。适用于外设信号需要主动输出高低电平。如 USART 的 TX、SPI 的 SCK&#x2F; MOSI 等时序信号。</td></tr></tbody></table><h5 id="模拟输入（下方所有×号代表不启用）"><a href="#模拟输入（下方所有×号代表不启用）" class="headerlink" title="模拟输入（下方所有×号代表不启用）"></a>模拟输入（下方所有×号代表不启用）</h5><p><img src="https://i-blog.csdnimg.cn/direct/26862517021c47afb6c517c587b002ea.png"></p><h5 id="浮空输入"><a href="#浮空输入" class="headerlink" title="浮空输入"></a>浮空输入</h5><p><img src="https://i-blog.csdnimg.cn/direct/80d0830b2f19455fb31d9eb28b801bc2.png"></p><h5 id="输入上拉（上开关打开，默认为高电平）"><a href="#输入上拉（上开关打开，默认为高电平）" class="headerlink" title="输入上拉（上开关打开，默认为高电平）"></a>输入上拉（上开关打开，默认为高电平）</h5><p><img src="https://i-blog.csdnimg.cn/direct/2a9329638fd242efa9bce594c4c52ad9.png"></p><h5 id="输入下拉（下开关打开，默认为低电平）"><a href="#输入下拉（下开关打开，默认为低电平）" class="headerlink" title="输入下拉（下开关打开，默认为低电平）"></a>输入下拉（下开关打开，默认为低电平）</h5><p><img src="https://i-blog.csdnimg.cn/direct/ad4c360600064a4784db6ef3a50449ad.png"></p><h5 id="开漏输出（允许输入，且输出低电平导通）"><a href="#开漏输出（允许输入，且输出低电平导通）" class="headerlink" title="开漏输出（允许输入，且输出低电平导通）"></a>开漏输出（允许输入，且输出低电平导通）</h5><p><img src="https://i-blog.csdnimg.cn/direct/b37f357d57df4191836d839d94bc5ba2.png"></p><h5 id="推挽输出（输出数据寄存器输出高电平P-MOS导通，低电平N-MOS导通）"><a href="#推挽输出（输出数据寄存器输出高电平P-MOS导通，低电平N-MOS导通）" class="headerlink" title="推挽输出（输出数据寄存器输出高电平P-MOS导通，低电平N-MOS导通）"></a>推挽输出（输出数据寄存器输出高电平P-MOS导通，低电平N-MOS导通）</h5><p><img src="https://i-blog.csdnimg.cn/direct/2f8b50df185647e69b49da58fca5e37a.png"></p><h5 id="复用开漏（类似开漏输出，不过输出电平来自片上外设）"><a href="#复用开漏（类似开漏输出，不过输出电平来自片上外设）" class="headerlink" title="复用开漏（类似开漏输出，不过输出电平来自片上外设）"></a>复用开漏（类似开漏输出，不过输出电平来自片上外设）</h5><p><img src="https://i-blog.csdnimg.cn/direct/e40510a7ff7d46f2a043ccebcec022ae.png"></p><h5 id="复用推挽（类似推挽输出，不过输出电平来自片上外设）"><a href="#复用推挽（类似推挽输出，不过输出电平来自片上外设）" class="headerlink" title="复用推挽（类似推挽输出，不过输出电平来自片上外设）"></a>复用推挽（类似推挽输出，不过输出电平来自片上外设）</h5><p><img src="https://i-blog.csdnimg.cn/direct/c021049db6a74fe19eb4709fb21638df.png"></p><h3 id="GPIO的函数使用"><a href="#GPIO的函数使用" class="headerlink" title="GPIO的函数使用"></a>GPIO的函数使用</h3><h5 id="设置函数"><a href="#设置函数" class="headerlink" title="设置函数"></a>设置函数</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">GPIO_SetBits</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将GPIO_Pin设置为高电平，支持多引脚</span><span class="token keyword">void</span> <span class="token function">GPIO_ResetBits</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将GPIO_Pin设置为低电平，支持多引脚</span><span class="token keyword">void</span> <span class="token function">GPIO_WriteBit</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">,</span> BitAction BitVal<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//第三个参数为Bit_SET置为高电平，Bit_RESET置为低电平，仅支持单引脚配置</span><span class="token keyword">void</span> <span class="token function">GPIO_Write</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> PortVal<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>使用实例：</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token function">GPIO_SetBits</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span>GPIO_Pin_0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//将GPIOA端口的第0号引脚置为高电平</span><span class="token function">GPIO_ResetBits</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span>GPIO_Pin_0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//将GPIOA端口的第0号引脚置为低电平</span><span class="token function">GPIO_WriteBit</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span>GPIO_Pin_0<span class="token punctuation">,</span>Bit_RESET<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//将GPIOA端口的第0号引脚置为低电平</span><span class="token function">GPIO_Write</span><span class="token punctuation">(</span>GPIOA<span class="token punctuation">,</span><span class="token operator">~</span><span class="token number">0x0001</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">//将GPIOA端口的第0号引脚置为低电平</span></code></pre><h5 id="读取函数"><a href="#读取函数" class="headerlink" title="读取函数"></a>读取函数</h5><pre class="language-c" data-language="c"><code class="language-c"><span class="token class-name">uint8_t</span> <span class="token function">GPIO_ReadInputDataBit</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//读取GPIO_Pin的输入值，返回值代表该端口的电平</span><span class="token class-name">uint16_t</span> <span class="token function">GPIO_ReadInputData</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//读取整个输入寄存器，返回每一位端口的电平</span><span class="token class-name">uint8_t</span> <span class="token function">GPIO_ReadOutputDataBit</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">,</span> <span class="token class-name">uint16_t</span> GPIO_Pin<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//</span><span class="token class-name">uint16_t</span> <span class="token function">GPIO_ReadOutputData</span><span class="token punctuation">(</span>GPIO_TypeDef<span class="token operator">*</span> GPIOx<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//</span></code></pre><h4 id="AHB总线连接的外设"><a href="#AHB总线连接的外设" class="headerlink" title="AHB总线连接的外设"></a>AHB总线连接的外设</h4><p><img src="https://i-blog.csdnimg.cn/direct/69348e69e1df41a08c186ebf5fd4a7d4.png"></p><p><strong>注：在APB1的操作速度最大频率为36MHz，在APB2的操作速度最大频率为72MHz</strong></p><h4 id="AFIO重映射"><a href="#AFIO重映射" class="headerlink" title="AFIO重映射"></a>AFIO重映射</h4><p>当使用的引脚的功能冲突时可以通过AFIO重映射来使用其他引脚来实现对应的功能，如USART1和TIM1的CH2和CH3引脚冲突，此时就可以使用AFIO将USART1_TX和USART1_RX的引脚改为PB6和PB7。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">USART1_Remap_Init</span><span class="token punctuation">(</span><span class="token keyword">void</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 1. 启用 GPIOB 和 AFIO 时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_GPIOB <span class="token operator">|</span> RCC_APB2Periph_AFIO<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 2. 重映射 USART1 引脚到 PB6 (TX) 和 PB7 (RX)</span>    <span class="token function">GPIO_PinRemapConfig</span><span class="token punctuation">(</span>GPIO_Remap_USART1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 3. 配置 GPIOB 的引脚为复用功能</span>    GPIO_InitTypeDef GPIO_InitStruct<span class="token punctuation">;</span>        <span class="token comment">// 配置 PB6 为复用推挽输出（USART1_TX）</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_6<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_AF_PP<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Speed <span class="token operator">=</span> GPIO_Speed_50MHz<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOB<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 配置 PB7 为浮空输入（USART1_RX）</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Pin <span class="token operator">=</span> GPIO_Pin_7<span class="token punctuation">;</span>    GPIO_InitStruct<span class="token punctuation">.</span>GPIO_Mode <span class="token operator">=</span> GPIO_Mode_IN_FLOATING<span class="token punctuation">;</span>    <span class="token function">GPIO_Init</span><span class="token punctuation">(</span>GPIOB<span class="token punctuation">,</span> <span class="token operator">&amp;</span>GPIO_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 4. 启用 USART1 时钟</span>    <span class="token function">RCC_APB2PeriphClockCmd</span><span class="token punctuation">(</span>RCC_APB2Periph_USART1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 5. 配置 USART1 参数</span>    USART_InitTypeDef USART_InitStruct<span class="token punctuation">;</span>    USART_InitStruct<span class="token punctuation">.</span>USART_BaudRate <span class="token operator">=</span> <span class="token number">9600</span><span class="token punctuation">;</span>                      <span class="token comment">// 波特率 9600</span>    USART_InitStruct<span class="token punctuation">.</span>USART_WordLength <span class="token operator">=</span> USART_WordLength_8b<span class="token punctuation">;</span>     <span class="token comment">// 数据位 8 位</span>    USART_InitStruct<span class="token punctuation">.</span>USART_StopBits <span class="token operator">=</span> USART_StopBits_1<span class="token punctuation">;</span>          <span class="token comment">// 停止位 1 位</span>    USART_InitStruct<span class="token punctuation">.</span>USART_Parity <span class="token operator">=</span> USART_Parity_No<span class="token punctuation">;</span>             <span class="token comment">// 无校验</span>    USART_InitStruct<span class="token punctuation">.</span>USART_HardwareFlowControl <span class="token operator">=</span> USART_HardwareFlowControl_None<span class="token punctuation">;</span> <span class="token comment">// 无硬件流控</span>    USART_InitStruct<span class="token punctuation">.</span>USART_Mode <span class="token operator">=</span> USART_Mode_Rx <span class="token operator">|</span> USART_Mode_Tx<span class="token punctuation">;</span> <span class="token comment">// 收发模式</span>    <span class="token function">USART_Init</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> <span class="token operator">&amp;</span>USART_InitStruct<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 6. 启用 USART1</span>    <span class="token function">USART_Cmd</span><span class="token punctuation">(</span>USART1<span class="token punctuation">,</span> ENABLE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h3 id="时钟"><a href="#时钟" class="headerlink" title="时钟"></a>时钟</h3><p>在STM32中由三种不同的时钟源来驱动系统时钟（SYSCLK）:</p><ul><li>HSI振荡器时钟（High Speed Internal）</li><li>HSE振荡器时钟（High Speed External）</li><li>PLL时钟()</li></ul><p>SYSCLK通过时钟树分发到不同的子系统和外设，如</p><ol><li>SYSCLK-&gt;AHB时钟<ul><li>通过AHB Prescaler分频器生产AHB时钟（HCLK）用于：<ul><li>Cortex-M内核</li><li>DMA控制器</li><li>内存接口（SRAM、FLASH等）</li></ul></li></ul></li><li>HCLK-&gt;APB时钟<ul><li>通过APB和APB Prescaler 分频器生成APB时钟（PCLK1和PCLK2),用于：<ul><li>APB1 外设（如 TIM2、USART2 等）</li><li>APB2 外设（如 ADC、USART1 等）</li></ul></li></ul></li></ol><p>时钟配置可以使高速设备接高速时钟，低速设备接低速时钟，起到最大程度的<strong>节能效果</strong>。</p><table><thead><tr><th>名称</th><th>频率</th><th>外部连接</th><th>功能</th><th>用途</th><th>特性</th></tr></thead><tbody><tr><td>HSE（High-Speed External），高速外部时钟</td><td>4~16MHz</td><td>4~16MHz晶体</td><td></td><td>系统时钟&#x2F;RTC</td><td>成本高，温漂小</td></tr><tr><td>LSE(Low-Speed External)，低速外部时钟</td><td>32kHz</td><td>32.768kHz晶体</td><td>带校准功能</td><td>RTC</td><td>成本高，温漂小</td></tr><tr><td>HSI（High-Speed Internal），高速内部时钟</td><td>8MHz</td><td>无</td><td>经出产调校</td><td>系统时钟</td><td>成本低，温漂大</td></tr><tr><td>LSI（Low-Speed Interal）,低速内部时钟</td><td>40kHz</td><td>无</td><td>带校准功能</td><td>RTC</td><td>成本低，温漂大</td></tr></tbody></table><h4 id="时钟树"><a href="#时钟树" class="headerlink" title="时钟树"></a>时钟树</h4><p><img src="https://i-blog.csdnimg.cn/direct/1d595ec21b3c4d5a9b96b4d3a09b5a84.png"></p>]]>
    </content>
    <id>https://lsworl.github.io/2024/12/30/stm32-xue-xi/</id>
    <link href="https://lsworl.github.io/2024/12/30/stm32-xue-xi/"/>
    <published>2024-12-30T05:54:55.000Z</published>
    <summary>以 STM32F10x 为例，整理 Keil 工程创建、标准库导入、GPIO 初始化和基础外设配置流程。</summary>
    <title>stm32标准库初始化</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="计算机网络" scheme="https://lsworl.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="计算机网络" scheme="https://lsworl.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="物理层" scheme="https://lsworl.github.io/tags/%E7%89%A9%E7%90%86%E5%B1%82/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h5 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h5><ol><li>数据：传送信息的实体，通常是有意义的符号序列。</li><li>信号：数据的电气&#x2F;电磁的表现，是数据在传输过程中的存在形式。</li><li>信源：产生和发送数据的源头。</li><li>信宿：接受数据的终点。</li><li>信道：信号的传输媒介。一般用来表示向某一个方向传送信息的介质。</li><li>码元：码元指用一个固定时长的信号波形（数字脉冲），代表不同离散数值的基本波形，是数字通信中数字信号的计量单位，这个时长内的信号称为k进制码元，而该时长称为码元宽度。<strong>一码元可以携带多个比特的信息量</strong>。</li><li>速率：也叫数据率，指数据的传输速率，表示单位时间内传输的数据量。可以用码元传输速率和信息传输速率表示。</li><li><strong>码元传输速率</strong>：表示单位时间内数字通信系统所传输的码元个数，单位是波特，1波特表示数字通信系统每秒传输一个码元。</li><li><strong>信息传输速率</strong>：表示单位时间内数字通信系统传输的二进制码元个数（即比特数），单位是比特&#x2F;秒。</li></ol><p><strong>三种基本通信方式</strong></p><ol><li>单向通信。只有一个方向的通信而没有反方向的交互，仅需要一条信道。如，无线电广播、电视广播就属于这种类型。</li><li>半双工通信。通信的双方都可以发送或接收信息，但任何一方都不能同时发送和接受信息，需要两条信道。</li><li>全双工通信。通信双方可以同时发送和接受信息，也需要两条信道。</li></ol><h5 id="奈氏准则"><a href="#奈氏准则" class="headerlink" title="奈氏准则"></a>奈氏准则</h5><p>由于具体的信道所能铜棍的频率范围是有限的，信号中的许多高频分类往往不能通过信道，奈氏准则就是在理想低通的条件下，为了避免码间串扰，规定<strong>极限码元传输速率为2W Baud（波特），W是信道带宽，单位是Hz</strong>。<br>$$<br>理想低通信道下的极限数据传输速率&#x3D;2W\log_2V (b&#x2F;s)<br>$$<br>对于奈氏准则，可以得出以下结论：</p><ol><li>在任何信道中，码元传输速率氏有上限的。</li><li>信道的频带越宽，就可以用更高的速率进行码元的有效传输。</li><li>奈氏准则给出了码元传输速率的限制，但并未对信息传输速率给出限制，即未对一个码元可以对应多少个二进制给出限制。</li></ol><h5 id="香农定理"><a href="#香农定理" class="headerlink" title="香农定理"></a>香农定理</h5><p>香农定理给出了带宽受限且有高斯白噪声干扰的信道的极限数据传输速率。</p><p><strong>信噪比&#x3D;信号的平均功率&#x2F;噪声的平均功率，记为S&#x2F;N</strong>，并用分贝（dB）作为度量单位，即：<br>$$<br>信噪比(dB)&#x3D;10\log_{10}(S&#x2F;N)<br>$$<br>香农定理：在带宽受限且有噪声的信道中，为了不产生误差，信息的数据传输率有上限值。<br>$$<br>信道的极限数据传输速率&#x3D;W\log_2(1+S&#x2F;N)(b&#x2F;s)<br>$$<br>W是信道的带宽，S为信道所传输信号的平均功率，N为信道内部的高斯噪声功率。</p><p>香农定理可得出以下结论：</p><ol><li>信道的带宽或信道中的信噪比越大，信息的极限传输速率越高。</li><li>对一定的传输带宽和一定的信噪比，信息传输速率的上限是确定的。</li><li>只要信息传输速率低于信道的极限传输速率，就能找到某种方法来实现无差错的传输。</li><li>香农定理得出的是极限信息传输速率，实际信道能达到的传输速率要比它低。</li></ol>]]>
    </content>
    <id>https://lsworl.github.io/2024/02/11/ji-suan-ji-wang-luo-wu-li-ceng/</id>
    <link href="https://lsworl.github.io/2024/02/11/ji-suan-ji-wang-luo-wu-li-ceng/"/>
    <published>2024-02-11T11:16:09.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>



<h5 id="基础概念"><a href="#基础概念" class="headerlink"]]>
    </summary>
    <title>计算机网络-物理层</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="计算机网络" scheme="https://lsworl.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="计算机网络" scheme="https://lsworl.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/"/>
    <category term="计算机网络体系结构" scheme="https://lsworl.github.io/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h5 id="多层次ISP结构"><a href="#多层次ISP结构" class="headerlink" title="多层次ISP结构"></a>多层次ISP结构</h5><blockquote><p>ISP：因特网服务提供者&#x2F;因特网服务提供商，是一个向广大用户综合提供互联网接入业务、信息业务、和增值业务的公司，如中国电信、中国联通、中国移动等。</p><p>分为主干ISP、地区ISP和本地ISP。</p></blockquote><p><img src="https://img-blog.csdnimg.cn/direct/26aa22a91c894de183a00ec077ddfa79.png"></p><p>图中IXP可以提高同地区的网络通信速度。</p><p>计算机网络OSI七层模型中，下三层网络层，数据链路层和物理层就代表着<strong>通信子网</strong>（各种传输介质、通讯设备、相应的网络协议组成），<strong>路由器是网络层的通讯设备，交换机、网桥是数据链路层的通讯设备，集线器，中继器是物理层的通讯设备。</strong></p><p>上三层应用层、表示层和会话层则是<strong>资源子网</strong>（实现资源共享功能的设备和软件的集合）。</p><h4 id="计算机网络的分类"><a href="#计算机网络的分类" class="headerlink" title="计算机网络的分类"></a>计算机网络的分类</h4><h5 id="按分布范围分类"><a href="#按分布范围分类" class="headerlink" title="按分布范围分类"></a>按分布范围分类</h5><p>按分布范围可分为以下几种：</p><ol><li>广域网（WAN）。广域网实现只要通过<strong>交换技术</strong>，需要交换机及路由器作为媒介来进行报文交换，一般各节点交换机都是高速链路，具有较大的通信容量。</li><li>城域网（MAN）。城域网大多采用以太网技术，有时也常并入局域网的范围讨论。</li><li>局域网（LAN）。局域网一般通过<strong>广播技术</strong>实现，覆盖范围较小。</li><li>个人区域网（PAN）。个人区域网指用无线技术连接起来的网络。</li></ol><h5 id="按交换技术分类"><a href="#按交换技术分类" class="headerlink" title="按交换技术分类"></a>按交换技术分类</h5><p>按交换技术分类可分为以下几种：</p><ol><li>电路交换。电路交换类似于双方打电话，会先在源节点和目的节点之间建立一条专用的通路用于传送数据。</li><li>报文交换。用户数据加上源地址、目的地址、校验码等辅助信息，然后封装成报文，通过链路再进行发送，根据目的地址来判断目的源。</li><li>分组交换。分组交换就是将多个报文组成一个分组后发送。</li></ol><h5 id="按拓扑结构分类"><a href="#按拓扑结构分类" class="headerlink" title="按拓扑结构分类"></a>按拓扑结构分类</h5><p>网络拓扑结构是指由网中结点（路由器、主机等）与通信线路（网线）之间的几何关系表示的网络结构，主要指通信子网的拓扑结构。</p><p>可分为以下几种：</p><ol><li>总线型。用单根传输线把计算机连接起来。</li><li>星型。每个终端或计算机都以单独的线路与中央设备相连。现在中央一般是交换机或路由器。</li><li>环形。所有计算机接口设备连接成一个环。如令牌环局域网，环可以是单环，也可以是双环，环中信号是单向传输的。</li><li>网状型。一般情况下，每个结点至少由两条路径与其他结点相连，<strong>多用在广域网</strong>中。</li></ol><p><img src="https://img-blog.csdnimg.cn/direct/307dd60910ef4eaba28eff0074c3361a.png"></p><h4 id="性能指标"><a href="#性能指标" class="headerlink" title="性能指标"></a>性能指标</h4><ol><li><p>速率。即数据率或称数据传输率或比特率。单位是b&#x2F;s,kb&#x2F;s,Mb&#x2F;s,Gb&#x2F;s,Tb&#x2F;s等。（速率中$1k&#x3D;10^3$，存储中$1k&#x3D;2^{10}$）</p></li><li><p>带宽。原本指某个信号具有的频带宽度，在计算机网络中，带宽表示网络的通信线路所能传送数据的能力，是数字信道所能传送的“最高数据传输速率”的同义词，即<strong>网络设备所支持的最高速度</strong>，单位是b&#x2F;s。</p></li><li><p>吞吐量。表示在单位时间内通过某个网络（或信道、接口）的数据量。单位b&#x2F;s，kb&#x2F;s等。</p></li><li><p>时延。指数据从网络（或链路）的一端传送到另一端所需的时间。也叫延迟或迟延单位是s。</p><p>时延由四部分构成：发送时延、传播时延、处理时延和排队时延。</p><ul><li><p>发送时延。结点将分组的所有比特推向链路所需的时间，即整个数据都上链路的所花费的时间。<br>$$<br>发送时延&#x3D;分组长度&#x2F;信道宽度<br>$$</p></li><li><p>传播时延。即一个比特从链路的一端传播到另一端所需的时间。计算公式为<br>$$<br>传播时延&#x3D;信道长度&#x2F;电磁波在信道上的传播速率<br>$$</p></li><li><p>处理时延。数据在交换结点为存储转发而进行的一些必要的处理所花费的时间。例，分析分组的首部、从分组中提取数据部分、进行差错校验或查找适当的路由等。</p></li><li><p>排队时延。分组在进入路由器后要先在输入队列中排队等待处理。路由器确定转发端口后，还要在输出队列中排队等待转发。</p></li></ul></li><li><p>时延宽带积。指发送端发送的第一个比特即将到达终点时，发送端已经发出了多少个比特，因此又称以比特为单位的链路长度。<br>$$<br>时延带宽积&#x3D;传播时延\times 带宽<br>$$</p></li><li><p>往返时延RTT。从发送方发送数据开始，到发送方收到接收方的确认，总共经历的时延。</p><p>RTT总共包括：往返传播时延&#x3D;传播时延*2，末端处理时间。</p></li><li><p>信道利用率。指再某一信道有百分之多少的时间是有数据通过的，即<br>$$<br>信道利用率&#x3D;有数据通过时间&#x2F;(有+无)数据通过时间<br>$$</p></li></ol><h4 id="计算机网络分层结构"><a href="#计算机网络分层结构" class="headerlink" title="计算机网络分层结构"></a>计算机网络分层结构</h4><p>计算机网络体系结构分层的基本原则如下：</p><ol><li>每层都实现一种相对独立的功能，降低大系统的复杂度。</li><li>各层之间界面自然清晰，易于理解，相互交流尽可能少。</li><li>各层功能的精确定义独立于具体的实现方法，可以采用最适合的技术来实现。</li><li>保持下层对上层的独立性，上层单向使用下层提供的服务。</li><li>整个分层结构应能促进标准化工作。</li></ol><h5 id="OSI参考模型"><a href="#OSI参考模型" class="headerlink" title="OSI参考模型"></a>OSI参考模型</h5><p>OSI参考模型中上三层应用层、表示层、会话层称为资源子网，下三层网络层、数据链路层、物理层称为通信子网。</p><p><img src="https://img-blog.csdnimg.cn/direct/56808def3cfa438682181afb1d1c1d4f.png"></p><ol><li>物理层。物理层的传输单位是比特，功能是在物理媒体上为数据端设备透明地传输原始比特流。</li><li>数据链路层。数据链路层的传输单位是帧，任务是将网络层传来的IP数据包组装成帧，在次层也会进行差错控制等功能。</li><li>网络层。网络层的传输单位的是数据包，它关心的是通信子网的运行控制，主要任务是把网络层的协议数据单元从源端传到目的端。</li><li>传输层。传输单位是报文段（TCP）或用户数据报（UDP），传输层负责主机中两个进程之间的通信，功能是为端到端连接提供可靠的传输服务。</li><li>会话层。会话层运行不同主机上的各个进程之间进行会话。会话层利用传输层提供的端到端的服务，向表示层提供它的增值服务。</li><li>表示层。表示层主要处理在两个通信系统中交换信息的表示方式。</li><li>应用层。应用层是用户与网络的界面，应用层为特定类型的网络应用提供访问OSI参考模型环境的手段。</li></ol><p><img src="https://img-blog.csdnimg.cn/direct/b4f39ee4c92e4493a478243715fa72ea.png"></p>]]>
    </content>
    <id>https://lsworl.github.io/2024/02/09/ji-suan-ji-wang-luo-ji-suan-ji-wang-luo-ti-xi-jie-gou/</id>
    <link href="https://lsworl.github.io/2024/02/09/ji-suan-ji-wang-luo-ji-suan-ji-wang-luo-ti-xi-jie-gou/"/>
    <published>2024-02-09T11:02:56.000Z</published>
    <summary>梳理计算机网络体系结构、协议分层、OSI 与 TCP/IP 模型，以及各层的基本功能。</summary>
    <title>计算机网络-计算机网络体系结构</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="操作系统" scheme="https://lsworl.github.io/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="操作系统" scheme="https://lsworl.github.io/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="输入输出管理" scheme="https://lsworl.github.io/tags/%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%AE%A1%E7%90%86/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h4 id="IO设备"><a href="#IO设备" class="headerlink" title="IO设备"></a>IO设备</h4><p>IO设备就是可以将数据输入到计算机，或者可以接受计算机输出数据的外部设备，属于计算机中的硬件设备。</p><h5 id="IO设备的分类"><a href="#IO设备的分类" class="headerlink" title="IO设备的分类"></a>IO设备的分类</h5><ul><li><p>按使用特性</p><p>人机交互类外部设备。例如鼠标、键盘、打印机等——用于人机交互</p><p>存储设备。例移动硬盘、光盘等——用于数据存储</p><p>网络通信设备。调制解调器等——用于网络通信</p></li><li><p>按传输速率</p><p>低速设备。例如鼠标、键盘等——传输速率为每秒几个到几百字节。</p><p>中速设备。如激光打印机等——传输速率为每秒数千至上万个字节。</p><p>高速设备。如硬盘等——传输速率为每秒数千字节至千兆字节。</p></li><li><p>按信息交换的单位分类</p><p>块设备。传输速率较高，可寻址，即对它可随机地读&#x2F;写任一块，如磁盘等。</p><p>字符设备。传输速率较慢，不可寻址，在输入&#x2F;输出时常采用中断驱动方式，如鼠标、键盘等。</p></li></ul><h4 id="IO控制方式"><a href="#IO控制方式" class="headerlink" title="IO控制方式"></a>IO控制方式</h4><h5 id="程序直接控制方式"><a href="#程序直接控制方式" class="headerlink" title="程序直接控制方式"></a>程序直接控制方式</h5><p>从计算机外部设备读取的每个字，CPU需要对外设状态进行循环检查，直到确定该字已经在IO控制器的数据寄存器中。</p><p><img src="https://img-blog.csdnimg.cn/direct/ee2815f2aec941e0ae32245198ef0eb9.png"></p><p>优点：实现简单。在读&#x2F;写指令之后，加上实现循环检查的一系列指令即可。</p><p>缺点：CPU和IO设备只能串行工作，CPU需要一直轮询检查，长期处于“忙等”状态，CPU利用率低。</p><h5 id="中断驱动方式"><a href="#中断驱动方式" class="headerlink" title="中断驱动方式"></a>中断驱动方式</h5><p>引入中断机制。由于IO设备速度要远慢于CPU速度，因此在CPU发出读写命令后，可将等待IO的进程阻塞，先切换到别的进程指向。当IO完成后，控制器会向CPU发出一个中断信号，CPU检测到中断信号后，会保存当前进程运行环境，转去执行中断处理程序处理该中断。</p><p><img src="https://img-blog.csdnimg.cn/direct/5010967d18a546e8b9ae4aad33cb4dbf.png"></p><p>优点：与程序直接控制方式相比，在中断驱动方式中，IO控制器会通过中断信号主动报告IO已完成，CPU不再需要不停地轮询。CPU和IO设备可以并行工作，CPU利用率得到明显提升。</p><p>缺点：每个字在IO设备与内存之间的传输，都需要经过CPU，而<strong>频繁的中断处理会消耗较多的CPU时间</strong>。</p><h5 id="DMA方式"><a href="#DMA方式" class="headerlink" title="DMA方式"></a>DMA方式</h5><p>DMA（Direct Memory Access，直接存储器存取）方式主要用于块设备的IO控制，对于中断驱动方式有以下几点改进：</p><ul><li>数据的传输单位是“块”，一次可以传输多个字。</li><li>数据的流向是从设备直接放入内存，或从内存直接到设备，无需经历CPU中转。</li></ul><p>DMA方式中会从IO中读入的数据线存入到DR（数据寄存器）中，然后再根据MAR（内存地址寄存器）来将IO读入的数据放到内存相应地址中。</p><p><img src="https://img-blog.csdnimg.cn/direct/c2cc432e15ab4ff8ab2e0177eba5a7d1.png"></p><p>优点：传输以块为单位，CPU介入频率进一步降低。数据传输效率进一步增加。CPU和IO设备的并行性得到提升。</p><p>缺点：CPU每发出一条IO指令，只能读写一个或多个连续的数据块。</p><h5 id="SPOOLing技术（假脱机技术）"><a href="#SPOOLing技术（假脱机技术）" class="headerlink" title="SPOOLing技术（假脱机技术）"></a>SPOOLing技术（假脱机技术）</h5><p>假脱机技术是为了缓和CPU的高速性和IO设备低速性之间的矛盾，该技术利用专门的外围控制机，将低速IO设备上的数据传送到高速磁盘上，或者相反。</p><p><img src="https://img-blog.csdnimg.cn/direct/9adb2e4bc48941bd80d0ef0ab379ec7a.png"></p><p>系统会在磁盘开辟出输入井和输出井两个存储区域。</p><p>“输入井”模拟脱机输入时的磁带，用于收容IO设备输入的数据，“输出井”模拟脱机输出时的磁带，用于收容用户进程输出的数据。</p><p>然后通过内存的输入缓冲区和输出缓冲区作为中转，来实现磁盘与输入输出设备的交互。</p><p>SPOOLing技术是必须要有多道程序技术的支持，系统会建立输入进程和输出进程。</p><h4 id="缓冲区管理"><a href="#缓冲区管理" class="headerlink" title="缓冲区管理"></a>缓冲区管理</h4><p>缓冲区是一个存储区域，是用于缓和CPU与IO设备之间速度不匹配的矛盾，减少对CPU的中断频率，放宽对CPU中断相应时间的限制，解决数据粒度不匹配的问题，并提高CPU与IO之间的并行性。</p><p>缓冲区的实现方法：</p><ol><li>采用硬件缓冲区，用一个寄存器或其他部件来作为数据间的缓冲区，但由于成本太高，一般不建议采用硬件缓冲区。</li><li>采用内存缓冲区。将内存一部分空间作为缓冲区使用。</li></ol><p>根据系统设置缓冲器的个数，缓冲技术可以分为以下几种：</p><h5 id="单缓冲"><a href="#单缓冲" class="headerlink" title="单缓冲"></a>单缓冲</h5><p>操作系统会在主存中为其分配一个缓冲区。当设备和处理机交换数据时，会先将数据写入缓冲区，然后需要数据的设备或处理机从缓冲区取走数据，在缓冲区写入或取出时，其他需要使用的设备要等待。</p><p><img src="https://img-blog.csdnimg.cn/direct/060396b277064424b899da72d63023e0.png"></p><p>采用单缓冲策略，<strong>处理一块数据平均耗时为Max(输入时间，处理时间)+传送时间</strong>。</p><h5 id="双缓冲"><a href="#双缓冲" class="headerlink" title="双缓冲"></a>双缓冲</h5><p>采用双缓冲策略，操作系统会在主存中为其分配两个缓冲区。</p><p>若假设初始状态为：工作区空，其中一个缓冲区满，另一个缓冲区空</p><p>假设输入时间&lt;处理时间+传送时间</p><p><img src="https://img-blog.csdnimg.cn/direct/c8e0c7e7ae5e47398bdac77a7c7ed414.png"></p><p>由此可得处理一块数据所消耗的时间，只有当回到初始状态才算数据处理完成，所以首先会经历M传送时间，然后再经历C处理时间，同步地再从工作区读入一块数据，输入时间为T，由于输入时间&lt;处理时间+传送时间，所以处理一块数据所消耗的时间为T。</p><h5 id="循环缓冲区"><a href="#循环缓冲区" class="headerlink" title="循环缓冲区"></a>循环缓冲区</h5><p>循环缓冲区是将内存中多个大小相等的缓冲区链接成一个循环队列。</p><p><img src="https://img-blog.csdnimg.cn/direct/a864053acaa4427cbc4f3c82cb9b4441.png"></p><h3 id="磁盘与固态硬盘"><a href="#磁盘与固态硬盘" class="headerlink" title="磁盘与固态硬盘"></a>磁盘与固态硬盘</h3><p>磁盘就是由表面涂有磁性物质的物理盘片，通过一个称为磁头的导体线圈从磁盘存取数据。</p><p><img src="https://img-blog.csdnimg.cn/direct/f57400c801234571bf045ddf9ca4bf35.png"></p><p>磁盘进行读写操作时，磁头固定，磁盘再下面高速旋转。</p><p><img src="https://img-blog.csdnimg.cn/direct/9f505f8b737045a2aff68b87e6452d70.png"></p><p>由上图可得，可以通过柱面号，盘面号，扇区号来定位任意一个“磁盘块”。在“文件的物理结构”中存放在外存中的几号块，就可以转换成柱面号，盘面号，扇区号的地址形式。</p><p>磁盘按不同的形式可分为若干类型：</p><p>磁头相对于盘片的径向方向固定的称为<strong>固定头磁盘</strong>，每个磁道对应一个磁头。（磁头不可移动）</p><p>磁头可移动的，称为<strong>活动头磁盘</strong>，磁头臂可来回伸缩定位磁道；</p><p>磁盘永久固定在磁盘驱动器内的，称为<strong>固定盘磁盘</strong>；</p><p>可移动和替换的，称为<strong>可换盘磁盘</strong>。</p><h4 id="磁盘调度算法"><a href="#磁盘调度算法" class="headerlink" title="磁盘调度算法"></a>磁盘调度算法</h4><p>磁盘调度算法了解前需要知道相关判断算法好坏的指标：</p><ul><li><p>寻找时间（寻道时间）$T_s$：在读&#x2F;写数据前，将磁头移动到指定磁道所花的时间。</p></li><li><p>延迟时间$T_R$：通过旋转磁盘，使磁头定位到目标扇区所需要的时间，设磁盘转速为r（单位：转&#x2F;秒），则平均所需的延迟时间<br>$$<br>T_R&#x3D;(1&#x2F;2)*(1&#x2F;r)&#x3D;1&#x2F;2r<br>$$</p></li><li><p>传输时间$T_t$：从磁盘读出或向磁盘写入数据所经历的时间，假设磁盘转速为r，此次读写的字节数为b，每个磁道上的字节数为N，则传输时间<br>$$<br>T_t&#x3D;\frac{1}{r}*\frac{b}{N}&#x3D;\frac{b}{rN}<br>$$</p></li></ul><h5 id="先来先服务算法（FCFS）"><a href="#先来先服务算法（FCFS）" class="headerlink" title="先来先服务算法（FCFS）"></a>先来先服务算法（FCFS）</h5><p>根据进程请求访问磁盘的先后顺序进行调度。</p><p>假设磁头初始位置是100号磁道</p><p>55、58、39、18、90、160、150、38、184号磁道陆续来访问，</p><p>根据FCFS的规则，根据请求到达的顺序来响应。</p><p><img src="https://img-blog.csdnimg.cn/direct/57f706fb8e5b4fc9841b9dfabc0db8fe.png"></p><h5 id="最短寻找时间优先（SSTF）"><a href="#最短寻找时间优先（SSTF）" class="headerlink" title="最短寻找时间优先（SSTF）"></a>最短寻找时间优先（SSTF）</h5><p>SSTF会优先处理磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短，但是并不能保证总的寻道时间最短。</p><p>假设磁头初始位置是100号磁道</p><p>55、58、39、18、90、160、150、38、184号磁道陆续来访问，</p><p>根据SSTF的规则，根据离磁头初始位置最近的磁道来响应。</p><p><img src="https://img-blog.csdnimg.cn/direct/4b0aa93c2d97441ba5cab15d9c27ae60.png"></p><h5 id="扫描算法（SCAN）"><a href="#扫描算法（SCAN）" class="headerlink" title="扫描算法（SCAN）"></a>扫描算法（SCAN）</h5><p>扫描算法规定只要磁头移动到最外侧磁道的时候才能往内移动，移动到最内侧磁道的时候才能往外移动。也称电梯算法。</p><p>假设某磁盘的磁道为0~200号，磁头的初始位置是100号磁道，且此时<strong>磁头正在往磁道号增大的方向移动</strong>，有多个进程先后陆续地请求访问</p><p>55、58、39、18、90、160、150、38、184号磁道</p><p><img src="https://img-blog.csdnimg.cn/direct/e6d72db94a494c0db7c6a7f73df21a47.png"></p><h5 id="LOOK调度算法"><a href="#LOOK调度算法" class="headerlink" title="LOOK调度算法"></a>LOOK调度算法</h5><p>LOOK调度算法在如果磁头移动方向上已经没有别的请求，就可以立即改变磁头移动方向。</p><p>假设某磁盘的磁道为0~200号，磁头的初始位置是100号磁道，且此时<strong>磁头正在往磁道号增大的方向移动</strong>，有多个进程先后陆续地请求访问</p><p>55、58、39、18、90、160、150、38、184号磁道</p><p><img src="https://img-blog.csdnimg.cn/direct/707a2de7dde94e16b83132f62cf74a42.png"></p><h5 id="循环扫描算法（C-SCAN）"><a href="#循环扫描算法（C-SCAN）" class="headerlink" title="循环扫描算法（C-SCAN）"></a>循环扫描算法（C-SCAN）</h5><p>C-SCAN算法规定只要磁头朝某个特定方向移动时才处理磁道访问请求，而返回时直接快速移动至起始端而不处理任何请求。</p><p>假设某磁盘的磁道为0~200号，磁头的初始位置是100号磁道，且此时<strong>磁头正在往磁道号增大的方向移动</strong>，有多个进程先后陆续地请求访问</p><p>55、58、39、18、90、160、150、38、184号磁道</p><p><img src="https://img-blog.csdnimg.cn/direct/102f334559d9453d8d3b5e3b1904da0a.png"></p><h5 id="C-LOOK调度算法"><a href="#C-LOOK调度算法" class="headerlink" title="C-LOOK调度算法"></a>C-LOOK调度算法</h5><p>C-LOOK调度算法主要是用于解决C-SCAN算法需要到达最边上的磁道时才能改变磁头移动方向的缺点，用C-LOOK算法如果磁头移动的方向上已经没有磁道访问请求了，就可以立即让磁头返回，并且磁头只需要返回到有磁道访问请求的位置即可。</p><p>假设某磁盘的磁道为0~200号，磁头的初始位置是100号磁道，且此时<strong>磁头正在往磁道号增大的方向移动</strong>，有多个进程先后陆续地请求访问</p><p>55、58、39、18、90、160、150、38、184号磁道</p><p><img src="https://img-blog.csdnimg.cn/direct/676bbdb81f984255982c699c29a505a6.png"></p>]]>
    </content>
    <id>https://lsworl.github.io/2024/02/05/cao-zuo-xi-tong-shu-ru-shu-chu-guan-li/</id>
    <link href="https://lsworl.github.io/2024/02/05/cao-zuo-xi-tong-shu-ru-shu-chu-guan-li/"/>
    <published>2024-02-05T11:07:34.000Z</published>
    <summary>
      <![CDATA[<meta name="referrer" content="no-referrer"/>



<h4 id="IO设备"><a href="#IO设备" class="headerlink"]]>
    </summary>
    <title>操作系统-输入输出管理</title>
    <updated>2026-05-22T11:24:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="内存管理" scheme="https://lsworl.github.io/categories/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
    <category term="操作系统" scheme="https://lsworl.github.io/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="内存管理" scheme="https://lsworl.github.io/tags/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h3 id="内存管理的概念"><a href="#内存管理的概念" class="headerlink" title="内存管理的概念"></a>内存管理的概念</h3><h4 id="内存空间的分配与回收"><a href="#内存空间的分配与回收" class="headerlink" title="内存空间的分配与回收"></a>内存空间的分配与回收</h4><p>为了更好能适应不同大小的程序要求和提高内存的利用率，就需要对内存空间进行分配与回收。</p><h5 id="覆盖与交换"><a href="#覆盖与交换" class="headerlink" title="覆盖与交换"></a>覆盖与交换</h5><p>覆盖于交换技术是在多道程序环境下用来扩充内存的两种方法。</p><ol><li><p>覆盖技术</p><p>覆盖技术的思想就是将程序分为多个段。常用的段常驻内存，不常用的段在需要时调入内存。</p><p>常驻内存的段放在<strong>“固定区”</strong>中，调入后就不再调出（除非运行结束），</p><p>不常用的段放在<strong>“覆盖区”</strong>，需要用到时调入内存，用不到时调出内存。</p><p>缺点：由于必须由程序员声明覆盖结构，操作系统完成自动覆盖。对用户不透明，增加了用户编程负担</p></li><li><p>交换技术</p><p>交换技术的思想技术当内存空间紧张时，系统将内存某些进程暂时换出外存，把外存中某些以具备运行条件的进程换入内存（类似进程在内存与磁盘间动态调度）。</p></li></ol><h4 id="连续分配管理方式"><a href="#连续分配管理方式" class="headerlink" title="连续分配管理方式"></a>连续分配管理方式</h4><p>连续分配是为用户程序分配一个连续的内存空间。</p><h5 id="单一连续分配"><a href="#单一连续分配" class="headerlink" title="单一连续分配"></a>单一连续分配</h5><p>在单一连续分配方式中，内存被分为系统区和用户区。系统区通常位于内存的低地址部分，存放操作系统相关数据；用户区用于存放用户进程相关数据。</p><p>在单一连续分配中，内存<strong>只能有一道用户程序</strong>，用户程序独占整个用户区空间。</p><p><img src="https://img-blog.csdnimg.cn/direct/e4909957df1d439bb4d88027cced5969.png"></p><p>优点：实现简单，无外部虽破；可以采用覆盖技术扩充内存；不一定需要采取内存保护。</p><p>缺点：只能用于单用户、单任务的操作系统中；有内部碎片（即分配给某进程的内存区域中，有些没有用上的部分就称内部碎片）；存储器利用率极低。</p><h5 id="固定分区分配"><a href="#固定分区分配" class="headerlink" title="固定分区分配"></a>固定分区分配</h5><p>固定分区分配就是将整个用户空间划分未若干个固定大小的分区，在每个分区中只装入一道作业。</p><p>固定分区分配分为分区大小相等和分区大小不等两种情况。</p><p>分区大小相等：会缺乏灵活性，但是很适合用于用<strong>一台计算机控制多个相同对象的场合</strong>。</p><p>分区大小不等：增加了灵活性，可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分。</p><p><img src="https://img-blog.csdnimg.cn/direct/8e4706bf3a134c648adecf56c29e6934.png"></p><p>优点：</p><p>实现简单，无外部碎片。</p><p>缺点：</p><p>当用户程序太大时，可能所有分区都不能满足需求，需要采用覆盖技术来解决，这回导致性能降低；</p><p>会产生内部碎片，内存利用率低。</p><h5 id="动态分区分配"><a href="#动态分区分配" class="headerlink" title="动态分区分配"></a>动态分区分配</h5><p>动态分区分配又称为可变分区分配。这种分配方式是在进程装入内存时，<strong>根据进程的大小动态地建立分区</strong>，并使分区的大小正好适合进程的需要。</p><p>动态分区没有内部碎片，但有外部碎片。</p><p><strong>内部碎片</strong>，分配给某进程的内存区域中，如果有些部分没有用上就称没有用上的部分为内部碎片。</p><p><strong>外部碎片</strong>，是指内存中某些空闲分区由于太小而难以利用，而没有用上的部分就称外部碎片。</p><p>可以通过紧凑（拼凑，Compaction）技术来解决外部碎片，紧凑即对内存中进程的位置进行挪位，从而减去外部碎片。</p><h5 id="动态分区分配算法"><a href="#动态分区分配算法" class="headerlink" title="动态分区分配算法"></a>动态分区分配算法</h5><ul><li><p>首次适应算法</p><p>首次适应算法使每次都从低地址开始查找，找到第一个能满足大小的空间分区。</p></li><li><p>最佳适应算法</p><p>最佳适应算法是由于动态分区分配是一种连续方式，为各进程分配的空间必须是连续的一整片区域。因此为了保证留有大片的连续空间，会<strong>优先使用更小的空闲区</strong>。</p><p>缺点：每次都选最小的分区进行分配，会留下越来越多难以利用的外部碎片块。</p><p>实现：会让分区的数据结构根据<strong>容量递增次序排序</strong>，找到满足大小的第一个空闲分区。</p></li><li><p>最坏适应算法</p><p>为了解决最佳适应算法的问题，该算法会优先使用最大的连续空闲区，这样分配后的剩余空间区就不会太小，更方便使用。</p><p>实现：会让分区的数据结构根据<strong>容量递减次序排序</strong>，找到满足大小的第一个空闲分区。</p><p>缺点：会导致之后有“大进程”到达就没有内存分区可以使用。</p></li><li><p>邻近适应算法</p><p>该算法每次查找都会从<strong>上次查找结束的位置开始检索</strong>，找到第一个能满足大小的空间分区。</p></li></ul><p><img src="https://img-blog.csdnimg.cn/direct/b57ce49334444221ba11f58d1dd8c5ee.png"></p><h4 id="基本分页存储管理"><a href="#基本分页存储管理" class="headerlink" title="基本分页存储管理"></a>基本分页存储管理</h4><p>页式存储就是将内存分为一个个<strong>大小相等的分区</strong>，每个分区就是一个“<strong>页框</strong>”（也称内存块、物理块）。每个页框有一个编号，即“<strong>页框号</strong>”（也称内存块号、物理块号），页框号<strong>从0开始</strong>。</p><p>将<strong>进程的逻辑地址空间</strong>页分为与页框大小相等的一个个部分，每个部分称为一“页”或“页面”。每个页面也有一个编号，即<strong>“页号”</strong>，也会也是<strong>从0开始</strong>。</p><p><strong>根据内存块数列计算页表项的字节数</strong>：</p><p>例：假设某系统物理内存大小为4GB，页面大小为4KB，则每个页表项至少应该为多少字节？</p><p>由题可得内存块数：<br>$$<br>内存块数&#x3D;\frac{4GB}{4KB}&#x3D;\frac{2^{32}B}{2^{12}B}&#x3D;2^{20}块<br>$$<br>所以内存块号的地址范围应该是0~$2^{20}-1$</p><p>所以内存块号至少要20bit来表示。</p><p>换成字节为3B来表示块号。</p><p><strong>在页表中页号无需计算，是隐含的（类似于数组）</strong>。</p><h4 id="基本地址变换机构"><a href="#基本地址变换机构" class="headerlink" title="基本地址变换机构"></a>基本地址变换机构</h4><p>基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。</p><p>通常会再系统中设置一个页表寄存器（PTR），存放页表再内存中的起始地址F和页表长度M。</p><p><strong>注：页面大小是2的整数幂</strong></p><p>页面逻辑地址到物理地址的转变过程如下：</p><p><img src="https://img-blog.csdnimg.cn/direct/081cae0316724ccaae917c6719b590ff.png"></p><h4 id="具有快表的地址变换机构"><a href="#具有快表的地址变换机构" class="headerlink" title="具有快表的地址变换机构"></a>具有快表的地址变换机构</h4><p><strong>快表</strong>，又称联想寄存器（TLB, translation lookaside buffer），是一种访问速度比内存快很多的高速缓存，用来存放最近访问的页表项的副本。</p><p>快表的功能和cache差不多，快表会存放最近使用的页表项，然后下次访问若命中就可以直接通过快表获得物理地址，增加了查表的速度。</p><p><img src="https://img-blog.csdnimg.cn/direct/947d959fa4584482a0028b0f2d7788ae.png"></p><p>基于局部性原理，一般快表的命中率可以达到90%以上。</p><h5 id="基本地址变换机构和具有快表的地址变换机构的区别"><a href="#基本地址变换机构和具有快表的地址变换机构的区别" class="headerlink" title="基本地址变换机构和具有快表的地址变换机构的区别"></a>基本地址变换机构和具有快表的地址变换机构的区别</h5><p><img src="https://img-blog.csdnimg.cn/direct/2f3a898e8e7446f3a7bd65d064d95bef.png"></p><h4 id="两级页表"><a href="#两级页表" class="headerlink" title="两级页表"></a>两级页表</h4><p>为了解决单级页表中：</p><ul><li>页表必须连续存放，当页表很大时，需要占用很多个连续页框。</li><li>无需整个页表常驻内存，因为进程在一段时间可能只访问某几个特定的页面。</li></ul><p>的问题，就需要使用两级页表。</p><p>两级页表实现<strong>离散分配</strong>就是为页表在建立一张页表，称为<strong>页目录表</strong>。</p><p>若想让页表不常驻内存，可采用虚拟存储技术，在页表项中增加一个标志位，用于表示该页面是否已经调入内存。</p><p><strong>使用二级页表需要注意的细节:</strong></p><ul><li><p>若采用多级页表机制，则各级页表的大小不能超过一个页面</p></li><li><p>两级页表的访存次数分析（不采用快表）</p><p>第一次访存：访问内存中页目录表。</p><p>第二次访存：访问内存中二级页表。</p><p>第三次访存：访问目标内存单元。</p></li></ul><h3 id="虚拟内存管理"><a href="#虚拟内存管理" class="headerlink" title="虚拟内存管理"></a>虚拟内存管理</h3><h5 id="虚拟内存的的定义和特征"><a href="#虚拟内存的的定义和特征" class="headerlink" title="虚拟内存的的定义和特征"></a>虚拟内存的的定义和特征</h5><p>虚拟内存是基于局部性原理，在程序装入时，可以将程序中<strong>很快会用到的部分装入内存，暂时用不到的部分留在外存</strong>，就可以让程序开始执行。</p><p>在执行过程中，当所访问的信息不在内存时，由操作系统负责将所需信息从外存调入内存，然后执行。</p><p>若内存不够，由操作系统负责将内存暂时用不到的信息换出到外存。</p><p>在操作系统的管理下，在用户看来似乎有比实际大得多的内存，这就是虚拟内存。</p><p><strong>虚拟内存的三个特征：</strong></p><ul><li>多次性：无需在作业运行时一次性全部装入内存，而是允许被分成多次调入内存。</li><li>对换性：在作业运行时无需一直常驻内存，而是允许在作业运行过程中，将作业换入、换出。</li><li>虚拟性：从逻辑上扩充了内存的容量，使用户看到的内存容量，远大于实际的容量。</li></ul><p>由于连续分配方式不方便实现虚拟内存技术，所以会建立在<strong>离散分配</strong>上实现虚拟内存技术。</p><p><strong>与传统的非连续分配存储管理的区别：</strong></p><p>在程序执行过程中，当所访问的信息不在内存时，由操作系统负责将所需信息从外存调入内存，然后继续执行。</p><p>若内存空间不够，由操作系统负责将内存暂时用不到的信息换出外存。</p><h4 id="请求分页管理方式"><a href="#请求分页管理方式" class="headerlink" title="请求分页管理方式"></a>请求分页管理方式</h4><p>在请求分页管理系统中，当访问的页面不在内存时，便产生一个缺页中断，然后由操作系统的缺页中断处理程序处理中断。</p><p>此时<strong>缺页的进程阻塞</strong>，等调页完成后再将其唤醒，放回就绪队列。</p><p>如果内存中<strong>有空闲块</strong>，则为进程分配一个空闲块。</p><p>如果内存中<strong>没有空闲块</strong>，则有页面置换算法选择一个页面淘汰，若该页面再内存期间被修改过，则要写回外存。未经修改无需写回。</p><p><img src="https://img-blog.csdnimg.cn/direct/11a9288265574b61a598dec416a0cb35.png"></p><h4 id="页面置换算法"><a href="#页面置换算法" class="headerlink" title="页面置换算法"></a>页面置换算法</h4><h5 id="最佳置换算法（OPT）"><a href="#最佳置换算法（OPT）" class="headerlink" title="最佳置换算法（OPT）"></a>最佳置换算法（OPT）</h5><p>最佳置换算法时每次选择淘汰的页面将是以后永不使用，或者再最长时间内不再被访问的页面，这样可以保证最低的缺页率。</p><p>例：假设有三个内存块，并考虑到有一下页面号引用串（会依次访问这些页面）：</p><p>7，0，1，2，0，3，0，4，2，3，0，3，2，1，2，0，1，7，0，1</p><p>通过最佳置换算法可得：</p><p><img src="https://img-blog.csdnimg.cn/direct/58f71a3d0fb741c0afd4cd8d4ef9119a.png"></p><p>每当内存块满时换出最长时间不被访问的页面号。</p><p>最佳置换算法可以保证最低的缺页率，但实际上，只有再进程执行的过程中才能知道接下来会访问哪个页面。操作系统无法提前预判页面访问序列。因此，<strong>最佳置换算法是无法实现的</strong>。</p><h5 id="先进先出置换算法（FIFO）"><a href="#先进先出置换算法（FIFO）" class="headerlink" title="先进先出置换算法（FIFO）"></a>先进先出置换算法（FIFO）</h5><p>每次选择淘汰的页面是最早进入内存的页面。</p><p>例：假设系统为某进程分配了三个内存块，并考虑到有以下页面号引用串：</p><p>3，2，1，0，3，2，4，3，2，1，0，4</p><p><img src="https://img-blog.csdnimg.cn/direct/88017d5389a64e8baa06ddb45f16f254.png"></p><p><strong>Belady异常</strong>：当为进程分配的物理块数增大时，缺页次数不减反增的异常现象。</p><p>在所有置换算法中，只有FIFO算法会产生Belady异常。虽然FIFO算法实现简单，但由于算法与进程实际运行时的规律不适应，算法性能较差。</p><h5 id="最近最久未使用置换算法（LRU）"><a href="#最近最久未使用置换算法（LRU）" class="headerlink" title="最近最久未使用置换算法（LRU）"></a>最近最久未使用置换算法（LRU）</h5><p>每次淘汰的页面是最近最久未使用的页面。</p><p>在该算法会为每个页面添加一个访问记录字段，该字段用于记录上次被访问依赖所经历的时间。</p><p>例：假设系统为某进程分配了四个内存块，并考虑到有以下页面号引用串：</p><p>1，8，1，7，8，2，7，2，1，8，3，8，2，1，3，1，7，1，3，7</p><p><img src="https://img-blog.csdnimg.cn/direct/e693c604e277427897bb75768c1e7db1.png"></p><p>在实际做题只需逆向检查此时内存中的页面号<strong>最后一个出现的页号就是要淘汰的页面</strong>。</p><p>该算法的实现需要专门的硬件支持，虽然算法性能好，但是实现困难，开销大。</p><h5 id="时钟置换算法（CLOCK）"><a href="#时钟置换算法（CLOCK）" class="headerlink" title="时钟置换算法（CLOCK）"></a>时钟置换算法（CLOCK）</h5><p>时钟置换算法又称最近未用算法（NRU），时钟置换算法又分为简单的CLOCK算法和改进的CLOCK算法。</p><ul><li><p>简单的CLOCK算法。为每个页面设置一个访问位，再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时，其访问位置为1。若访问位为0，表示最近没被访问过。</p><p>当CLOCK扫描过后会将该页面的访问位置为0，所以第二轮扫描必定会有访问位为0的页面。（简单的CLOCK算法淘汰页面<strong>最多会经历两轮扫描</strong>）</p></li><li><p>改进型的时钟置换算法。该算法除了考虑一个页面最近有没有被访问过之外，还会考虑该页面有没有被修改过。<strong>在其他条件都相同时，应优先淘汰没有修改过的页面</strong>。</p><p>在该算法会为页面增加一位修改位，若修改位为0，表示没有被修改过，为1，表示页面被修改过。假设各页面状态以（访问位，修改位）形式表示。</p><p>​算法规则：将所有可能被置换的页面排成一个循环队列</p><p>​第一轮：从当前位置开始扫描到第一个（0，0）的帧用于替换。本轮扫描不修改任何标志位。</p><p>​第二轮：若第一轮扫描失败，则查找第一个（0，1）的帧用于替换。本轮将所有扫描过的帧访问位设置为0。</p><p>​第三轮：若第二轮扫描失败，则查找第一个（0，0）的帧用于替换。本轮扫描不修改任何标志位。</p><p>​第四轮：若第三轮扫描失败，则查找第一个（0，1）的帧用于替换。</p><p>改进型CLOCK置换算法<strong>最多会进行四轮扫描</strong>。</p></li></ul><p><img src="https://img-blog.csdnimg.cn/direct/54e2ea249acc4f68afa4516282694824.png"></p><h4 id="页面分配策略"><a href="#页面分配策略" class="headerlink" title="页面分配策略"></a>页面分配策略</h4><p>驻留集：指请求分页存储管理中给进程分配的物理块的集合。</p><p>采用虚拟存储技术的系统中，驻留集大小一般小于进程的总大小。</p><h5 id="置换策略"><a href="#置换策略" class="headerlink" title="置换策略"></a>置换策略</h5><p>在请求分页系统中，可采取固定分配和可变分配两种内存分配策略。在进行置换时，可采用全局置换和局部置换两种策略。</p><p><strong>固定分配</strong>：操作系统位每个进程分配一组固定数目的物理块，在进程运行期间不可改变。即<strong>驻留集大小不变</strong>。</p><p><strong>可变分配</strong>：操作系统位每个进程分配一定数目的物理块，在进程运行期间可改变。即<strong>驻留集大小可变</strong>。</p><p><strong>局部置换</strong>：发生缺页时只能选进程自己的物理块进行置换。</p><p><strong>全局置换</strong>：可以将操作系统保留的空闲物理块分配给缺页进程，也可以将别的进程持有的物理块置换到外存，再分配给缺页进程。</p><p><img src="https://img-blog.csdnimg.cn/direct/35a3ad912f3a4a93ac3945755ff3cec6.png"></p><p>可变分配全局置换：只要缺页就给分配新物理块。</p><p>可变分配局部置换：要根据<strong>缺页的频率</strong>来动态地增加或减少进程的物理块。</p><h5 id="抖动现象"><a href="#抖动现象" class="headerlink" title="抖动现象"></a>抖动现象</h5><p>刚刚换出的页面马上又要换入内&#x2F;外存，这种频繁的页面调度行为称为抖动，或颠簸。产生抖动的<strong>主要原因</strong>是进程频繁访问的页面数目高于可用的物理块数。</p><h5 id="工作集"><a href="#工作集" class="headerlink" title="工作集"></a>工作集</h5><p>为了减少抖动现象，就提出了工作集的概念。</p><p>工作集指在某段时间间隔里，进程实际访问页面的集合。</p><p>操作系统会根据“窗口尺寸”来算出工作集。</p><p>例：</p><p>某进程的页面访问序列如下，窗口尺寸为4，各时刻的工作集为：</p><p><img src="https://img-blog.csdnimg.cn/direct/520bcb0051804d889b54e4ce23c15b35.png"></p><p>工作集大小可能小于窗口尺寸，实际应用中，操作系统可以统计进程的工作集大小，根据工作集大小给进程分配若干内存块。</p>]]>
    </content>
    <id>https://lsworl.github.io/2024/01/30/cao-zuo-xi-tong-nei-cun-guan-li/</id>
    <link href="https://lsworl.github.io/2024/01/30/cao-zuo-xi-tong-nei-cun-guan-li/"/>
    <published>2024-01-30T10:48:11.000Z</published>
    <summary>整理操作系统内存管理中的分页、分段、虚拟内存、页面置换算法和地址转换机制。</summary>
    <title>操作系统-内存管理</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LsWorld</name>
    </author>
    <category term="操作系统" scheme="https://lsworl.github.io/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="操作系统" scheme="https://lsworl.github.io/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="进程与线程" scheme="https://lsworl.github.io/tags/%E8%BF%9B%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B/"/>
    <content>
      <![CDATA[<meta name="referrer" content="no-referrer"/><h4 id="进程的概念"><a href="#进程的概念" class="headerlink" title="进程的概念"></a>进程的概念</h4><p>为了让使参与的每个程序都能独立地运行，为之专门配置的一个数据结构就称为进程块（Process Control Block, PCB）。而由程序段、相关数据段和PCB三部分构成了进程实体（又称进程映像）。</p><p><strong>PCB是进程存在的唯一标识，当进程被创建时，操作系统会为其创建PCB，结束后会回收其PCB。</strong></p><p>若执行相同的三个程序，此时会将三个程序载入进程，它们的PCB、数据段各不相同，但程序段的内容都是相同的。</p><h4 id="进程的状态与转换"><a href="#进程的状态与转换" class="headerlink" title="进程的状态与转换"></a>进程的状态与转换</h4><p>进程通常有以下几种状态：</p><ol><li>运行态。进程正在处理机上运行。</li><li>就绪态。进程获得了除处理机外的一切所需资源，一旦得到处理机，便可立即运行。</li><li>阻塞态。又称等待态，进程正在等待<strong>某一事件</strong>（不是在等待CPU处理完成）而暂停运行，如等待某资源为可用（不包括处理机）或等待输入&#x2F;输出完成。即使根据阻塞原因的不同，可设置多个阻塞队列。</li><li>创建态。进程正在被创建，尚未转到就绪态。</li><li>终止态。进程正从系统中小时，可能是进程正常结束或其他原因推出运行。</li></ol><p><img src="https://img-blog.csdnimg.cn/direct/8e52873648664b05b5bd1ff4a21b9829.png"></p><p>由于进程的整个生命周期都在运行态、就绪态、阻塞态三种状态，所以也称这三状态为基本状态。</p><h4 id="进程控制"><a href="#进程控制" class="headerlink" title="进程控制"></a>进程控制</h4><p>进程控制主要是对系统中的所有进程实施有效的管理，可为了保证进程连续，一般进程控制会用原语（即运行时不可被中断，通过关中断指令和开中断指令实现）来实现。</p><h5 id="进程的创建"><a href="#进程的创建" class="headerlink" title="进程的创建"></a>进程的创建</h5><p>进程创建需要使用创建原语，即：</p><ol><li>申请空白PCB。</li><li>为新进程分配所需资源。</li><li>初始化PCB。</li><li>将PCB插入就绪队列</li></ol><p>当用户登录、作业调度（把外存中的应用运行到CPU上）、提供服务、应用请求时会引起进程创建。</p><h5 id="进程的终止"><a href="#进程的终止" class="headerlink" title="进程的终止"></a>进程的终止</h5><p>在进程结束前需要使用撤销原语来撤销事件，即</p><ol><li>从PCB集合中找到终止进程的PCB</li><li>若进程正在运行，立即剥夺CPU，将CPU分配给其他进程。</li><li>终止其所有子进程</li><li>将其进程拥有的所有资源归还给父进程或操作系统。</li><li>删除PCB</li></ol><p>使用撤销原语能使就绪态&#x2F;阻塞态&#x2F;运行态来转为终止态。</p><h5 id="进程的阻塞和唤醒"><a href="#进程的阻塞和唤醒" class="headerlink" title="进程的阻塞和唤醒"></a>进程的阻塞和唤醒</h5><p>进程的阻塞就是<strong>主动</strong>将进程从运行态转变到阻塞态。</p><p>阻塞原语的步骤为：</p><ol><li>找到要阻塞进程对应的PCB。</li><li>保护进程运行现场（CPU通过栈顶和栈底寄存器来执行），将PCB状态信息设置为“阻塞态”，暂时停止进程运行。</li><li>将PCB插入相应事件的等待队列。</li></ol><p>进程的唤醒就是<strong>被动</strong>地被CPU从阻塞态改为就绪态。</p><p>唤醒原语的步骤为：</p><ol><li>在事件等待队列中找到PCB</li><li>将PCB从等待队列移除，设置进程为就绪态。</li><li>将PCB插入就绪队列，等待被调度。</li></ol><h5 id="进程的切换"><a href="#进程的切换" class="headerlink" title="进程的切换"></a>进程的切换</h5><p>进程切换就是将运行态切换为就绪态或把就绪态切换为运行态。</p><p>切换原语的步骤为：</p><ol><li>将运行环境信息存入PCB</li><li>PCB移入相应队列</li><li>选择另一个进程执行，并更新其PCB</li><li>根据PCB恢复新进程所需的运行环境。</li></ol><h4 id="进程的通信"><a href="#进程的通信" class="headerlink" title="进程的通信"></a>进程的通信</h4><p>由于进程与进程不能访问相互的地址空间，所以得通过进程通信来实现交互。</p><h5 id="共享存储"><a href="#共享存储" class="headerlink" title="共享存储"></a>共享存储</h5><p>共享存储时在内存中开辟一个共享存储区，所有进程都可以对其进行访问。</p><p><img src="https://img-blog.csdnimg.cn/direct/0eed2ba68fb94864bd798c9e883f88e8.png"></p><p>为了保证可靠性，要求进程进行访问的操作是<strong>互斥</strong>的，即当一个进程访问该空间时，其他进程在此时无法访问。</p><p>基于存储区的共享方式是一种<strong>高级通信</strong>方式。</p><p>而基于数据结构（通过开辟数组等操作）的共享方式是<strong>低级通信</strong>方式。</p><h5 id="消息传递"><a href="#消息传递" class="headerlink" title="消息传递"></a>消息传递</h5><p>消息传递就是将进程要传递的数据进行格式化为消息头和消息体两部分，通过操作系统的发送和接受原语来实现。</p><p><img src="https://img-blog.csdnimg.cn/direct/5d22744662d24dfbb4e4ead328077376.png"></p><p>消息传递分为两种通信方式。</p><ol><li><p>直接通信方式</p><p>直接将消息通过发送原语来发到接收方的<strong>消息队列中</strong>，然后接收方通过接受原语来从消息队列取数据。</p></li><li><p>间接通信方式</p><p>通过一个中间实体“信箱”来实现消息传递。发送方通过发送原语来将数据发送到操作系统所开辟的“信箱”中，然后接收方通过接受原语来将对应“信箱”中的数据取走。</p><p>多个进程可以往同一个信箱发送消息，也可以多个进程从同一个信箱中接受消息。</p></li></ol><h5 id="管道通信"><a href="#管道通信" class="headerlink" title="管道通信"></a>管道通信</h5><p>管道通信就是在发送方和接受方建立一个“管道”（FIFO，采用队列的方式实现），发送方只能在管道的一端写，而接收方只能在管道的另一方读。</p><p>管道只采用<strong>半双工通信</strong>，某一时间段内只能实现单向的传输。如果要实现双向同时通信，则需要设置两个管道。</p><p><img src="https://img-blog.csdnimg.cn/direct/9e3d442ebe3248fb91ea382fe5fe382c.png"></p><p>并且各进程需要<strong>互斥</strong>地访问管道。</p><h4 id="线程概念和多线程模型"><a href="#线程概念和多线程模型" class="headerlink" title="线程概念和多线程模型"></a>线程概念和多线程模型</h4><h5 id="线程的基本概念"><a href="#线程的基本概念" class="headerlink" title="线程的基本概念"></a>线程的基本概念</h5><p>为了更好的并发执行多道程序，引入了线程概念，线程相对于微量型进程，作为<strong>基本的CPU执行单元</strong>，也是<strong>程序执行流的最小单位</strong>。</p><p>一个进程中会蕴含多个线程。而当CPU进行一个进程时可以并发调用多个线程，从而提高计算机的并发性。</p><h5 id="线程的实现方式"><a href="#线程的实现方式" class="headerlink" title="线程的实现方式"></a>线程的实现方式</h5><p>线程实现可以分为两类：用户级线程（User-Level Thread, ULT），和内核级线程（Kernel-Level  Thread, KLT）。</p><ol><li><p>用户级线程</p><ol><li><p>是程序员自己通过代码来创建一个线程库来实现，从代码角度看，线程就是一段代码逻辑，通过线程库来实现对线程的管理工作。</p><p><img src="https://img-blog.csdnimg.cn/direct/9cc3451d3dbc45af87b2470ec92ef92d.png"></p></li></ol><p>优点：用户级线程的切换在用户空间即可完成，不需要切换到核心态，线程管理的系统开销小，效率高。</p><p>缺点：当一个用户级线程被阻塞后，整个线程都会被阻塞，并发度不高。多个线程不可再多核处理器上并行运行。</p></li><li><p>内核级线程</p><p><img src="https://img-blog.csdnimg.cn/direct/9d9a5d61dfbe4753b6f8ff0ecbfbf34c.png"></p><p>内核级线程的管理工作由操作系统内核完成。</p><p><strong>线程内核级切换</strong>需要在核心态下才能完成。</p><p>并且操作系统内核视角可以“看到”线程。</p><p>优点：当一个线程被阻塞后，别的线程还可以继续执行。</p><p>缺点：一个用户进程会占用多个内核级线程，线程切换由操作系统内核完成，需要切换到核心态，因此线程管理的成本高，开销大。</p></li></ol><h5 id="多线程模型"><a href="#多线程模型" class="headerlink" title="多线程模型"></a>多线程模型</h5><p>在支持内核级线程的系统中，根据用户和内核级线程的映射关系，可以划分为多种多线程模型。</p><ol><li><p>一对一模型。一个用户级线程映射一个内核级线程。</p><p>优点：当一个线程被阻塞后，别的线程还可以继续执行，并发能力强。多线程可在多核处理器上并行执行。</p><p>缺点：线程管理成本高，开销大。</p></li><li><p>多对一模型。多个用户线程映射一个内核级线程。</p><p><img src="https://img-blog.csdnimg.cn/direct/3ebb07c3dc7b4f3aa77d63b6af39b823.png"></p><p>优点：用户级线程切换不需要切换到核心态，线程管理系统开销小，效率高。</p><p>缺点：当一个用户级线程被阻塞后，整个进程都会被阻塞，并发性不高。</p></li><li><p>多对多模型。n个用户级线程映射到m个内核级线程（<strong>用户级线程数量大于等于内核级线程数量，即n&gt;&#x3D;m</strong>）。每个用户进程对应m个内核级线程。</p><p><img src="https://img-blog.csdnimg.cn/direct/800c89bda22b4e958666fe28f6838b7e.png"></p></li></ol><h3 id="处理机调度"><a href="#处理机调度" class="headerlink" title="处理机调度"></a>处理机调度</h3><h4 id="调度的基本概念"><a href="#调度的基本概念" class="headerlink" title="调度的基本概念"></a>调度的基本概念</h4><p>调度就是当有一堆任务要处理，但由于资源有限，这些事情没法同时处理时候就需要根据某种规则来调整这些任务的顺序，这就是调度研究的问题。</p><h4 id="调度的层次"><a href="#调度的层次" class="headerlink" title="调度的层次"></a>调度的层次</h4><p>调度往往需要经历三个层次。</p><h5 id="高级调度（作业调度）"><a href="#高级调度（作业调度）" class="headerlink" title="高级调度（作业调度）"></a>高级调度（作业调度）</h5><p>按一定的原则从外存的作业后备队列中挑选一个作业调入内存，并创建进程。<strong>每个作业只调入一次，调出一次</strong>。作业调入时会建立PCB，调出时才撤销PCB。</p><h5 id="中级调度（内存调度）"><a href="#中级调度（内存调度）" class="headerlink" title="中级调度（内存调度）"></a>中级调度（内存调度）</h5><p>由于内存不够时，CPU可将某些进程的数据调出外存，当内存空闲或进程需要运行时再重新调入内存。</p><p>暂时调到外存等待的进程状态为<strong>挂起状态</strong>。被挂起的进程PCB会被组织成<strong>挂起队列</strong>。</p><p>将挂起队列中的进程重新调入到内存就称内存调度。</p><h5 id="低级调度（进程调度）"><a href="#低级调度（进程调度）" class="headerlink" title="低级调度（进程调度）"></a>低级调度（进程调度）</h5><p>进程调度就是按照某种策略从就绪队列中选取一个进程，将处理机分配给它。进程调度是操作系统中<strong>最基本的一种调度</strong>，在一般的操作系统中都必须配置进程调度。进程调度的<strong>频率很高</strong>。</p><h5 id="三层调度的联系、对比"><a href="#三层调度的联系、对比" class="headerlink" title="三层调度的联系、对比"></a>三层调度的联系、对比</h5><p><img src="https://img-blog.csdnimg.cn/direct/bd53dc778d504131bec2acd21d641f03.png"></p><h4 id="调度的算法评价指标"><a href="#调度的算法评价指标" class="headerlink" title="调度的算法评价指标"></a>调度的算法评价指标</h4><h5 id="CPU利用率"><a href="#CPU利用率" class="headerlink" title="CPU利用率"></a>CPU利用率</h5><p>CPU利用率指CPU“忙碌”的时间占总时间的比例。<br>$$<br>利用率&#x3D;\frac{忙碌的时间}{总时间}<br>$$</p><h5 id="系统吞吐量"><a href="#系统吞吐量" class="headerlink" title="系统吞吐量"></a>系统吞吐量</h5><p>系统吞吐量是指单位时间内完成作业的数量。<br>$$<br>系统吞吐量&#x3D;\frac{总共完成了多少道作业}{总共花了多少时间}<br>$$</p><h5 id="周转时间"><a href="#周转时间" class="headerlink" title="周转时间"></a>周转时间</h5><p>周转时间是指作业被提交给系统开始道作业完成为止过程所需要花费的时间。</p><p>包括四个部分：</p><ol><li>作业再外存后备队列上等待作业调度（高级调度）的时间</li><li>进程在就绪队列上等待进程调度（低级调度）的时间</li><li>进程在CPU上执行的时间</li><li>进程等待I&#x2F;O操作完成的时间</li></ol><p>$$<br>周转时间&#x3D;作业完成时间 - 作业提交时间<br>$$</p><p>$$<br>带权周转时间&#x3D;\frac{作业周转时间}{作业实际运行的时间}<br>$$</p><p>由上可得带权周转时间<strong>越小</strong>，用户满意度<strong>越高</strong>。<br>$$<br>平均带权周转时间&#x3D;\frac{各作业带权周转时间之和}{作业数}<br>$$</p><h5 id="等待时间"><a href="#等待时间" class="headerlink" title="等待时间"></a>等待时间</h5><p>等待时间指进程&#x2F;作业处于等待处理机状态时间之和，等待时间越长，用户满意度越低。</p><p>对于<font color="#ff0000">进程来说</font>，等待时间就是指进程建立后<strong>等待被服务的时间之和</strong>，在等待I&#x2F;O完成的期间其实进程也是在被服务的，所以不计入等待时间。</p><p>对于<font color="#ff0000">作业来说</font>，不仅要考虑建立进程后的等待时间，还要加上作业在外存后备队列中等待的时间。</p><h5 id="响应时间"><a href="#响应时间" class="headerlink" title="响应时间"></a>响应时间</h5><p>响应时间是指从用户提交请求道首次产生响应所用的时间。</p><h4 id="调度算法"><a href="#调度算法" class="headerlink" title="调度算法"></a>调度算法</h4><h5 id="先来先服务（FCFS）"><a href="#先来先服务（FCFS）" class="headerlink" title="先来先服务（FCFS）"></a>先来先服务（FCFS）</h5><p>先来先服务算法是根据作业的先后顺序进行服务，为非抢占式算法。</p><p>作业&#x2F;进程调度时，考虑的是哪个作业先到达后备队列；用于进程调度时，考虑的是哪个进程先到达就绪队列。</p><p>优点：公平、算法实现简单。</p><p>缺点：FCFS算法对<strong>长作业有利，对短作业不利</strong>。</p><h5 id="短作业优先（SIF）"><a href="#短作业优先（SIF）" class="headerlink" title="短作业优先（SIF）"></a>短作业优先（SIF）</h5><p>短作业优先是将最短的作业&#x2F;进程优先得到服务。短作业优先即可用于作业调度也可用于进程调度。当用于进程调度时称为“短进程优先”（SPF）。</p><p>优点：可以获得一个很短的平均等待时间、平均周转时间。</p><p>缺点：<strong>对短作业有利，对长作业不利，可能产生饥饿现象</strong>。若长作业一直得不到服务会产生“饿死”现象。</p><h5 id="高响应比优先（HRRN）"><a href="#高响应比优先（HRRN）" class="headerlink" title="高响应比优先（HRRN）"></a>高响应比优先（HRRN）</h5><p>高响应比是根据响应比来将响应比最高的作业&#x2F;进程来为其服务。<br>$$<br>响应比&#x3D;\frac{等待时间+要求服务时间}{要求服务时间}<br>$$<br>高响应比即可用于作业调度，也可用于进程调度。</p><p>优点：综合考虑了等待时间和运行时间，既有SIF的优点，也有FCFS的优点，且避免了长作业饥饿的问题。</p><h5 id="时间片轮转（RR）"><a href="#时间片轮转（RR）" class="headerlink" title="时间片轮转（RR）"></a>时间片轮转（RR）</h5><p>时间片轮转算法就是可以公平地、轮流地为各个进程服务，让每个进程在一定时间间隔内都可以得到响应。</p><p>算法具体是按照各进程<strong>到达就绪队列的顺序</strong>，轮流让每个进程执行一个时间片（如100ms），若进程未在一个时间片内执行完，则剥夺处理机重新排队。因此时间片轮转属于<strong>抢占式</strong>算法。</p><p>如果时间片太大，使得每个进程都可以在一个时间片内就完成，则时间片轮转算法将会<strong>退化成先来先服务</strong>调度算法，并且会增大进程响应时间。因此时间片不能太大。</p><p>由于进程调度、切换是有时间代价的，因此如果<strong>时间片太小</strong>，会导致进程切换过于频繁，系统会花大量的时间来处理进程切换，从而导致实际用于进程执行的实际比例减少。</p><p>优点：公平；响应快，适用于分时操作系统；</p><p>缺点：由于高频率的进程切换，因此有一定开销；不区分任务的紧急程度。</p><h5 id="优先级调度"><a href="#优先级调度" class="headerlink" title="优先级调度"></a>优先级调度</h5><p>优先级调度算法就是根据任务的紧急程度来决定处理顺序。</p><p>该算法会给每个作业设置各自的优先级，调度时选择优先级最高的作业。</p><p>优先级调度算法会根据优先级是否可以动态改变又分为静态优先级和动态优先级两种。</p><ul><li>静态优先级：创建进程时确定，之后一直不变。</li><li>动态优先级：创建进程时有一个初始值，之后会根据情况动态地调整优先级。</li></ul><h5 id="多级反馈队列调度算法"><a href="#多级反馈队列调度算法" class="headerlink" title="多级反馈队列调度算法"></a>多级反馈队列调度算法</h5><p>多级反馈队列调度算法是前几种算法的折中权衡。</p><p>该算法会设置多级就绪队列，各级队列优先级从高到低，时间片从小到大。</p><p>进程到达时候先进入第1级队列，按FCFS原则排队等待被分配时间片，若用完时间片进程还未结束，则进程进入下一级队列队尾。</p><p>只有第k级队列为空时，才会从k+1级队头的进程分配时间片用于进程调度。</p><p>该算法属于<strong>抢占式</strong>算法，在k级队列进程运行过程中，1~k-1级队列中进入一个新进程就会进行抢占。</p><h3 id="同步与互斥"><a href="#同步与互斥" class="headerlink" title="同步与互斥"></a>同步与互斥</h3><p>同步就是让进程之间有<strong>直接制约关系</strong>，它是为了某些进程需要在某些为止上协调它们的工作次序而产生的制约关系。</p><p>进程互斥是因为有<strong>临界资源</strong>（要求一个时间段内只允许一个进程使用）必须互斥的访问，即不能同时访问同个临界资源，此时产生了间接制约关系，因此称为进程互斥。</p><p>对于临界资源的访问可分成四部分：</p><ul><li>进入区。负责检测是否可以进入临界区。</li><li>临界区。访问临界资源的那段代码。</li><li>退出区。负责解除正在访问临界资源的标志。</li><li>剩余区。做其他处理。</li></ul><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">do</span><span class="token punctuation">&#123;</span>    entry selection<span class="token punctuation">;</span><span class="token comment">//进入区</span>    critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>    exit selection<span class="token punctuation">;</span><span class="token comment">//退出区</span>    remainder selection<span class="token punctuation">;</span><span class="token comment">//剩余区</span><span class="token punctuation">&#125;</span><span class="token keyword">while</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span></code></pre><p><strong>注：进入区和退出区是负责实现互斥的代码段。</strong></p><h3 id="实现临界区互斥的基本方法"><a href="#实现临界区互斥的基本方法" class="headerlink" title="实现临界区互斥的基本方法"></a>实现临界区互斥的基本方法</h3><h4 id="软件实现"><a href="#软件实现" class="headerlink" title="软件实现"></a>软件实现</h4><h5 id="单标志法"><a href="#单标志法" class="headerlink" title="单标志法"></a>单标志法</h5><p>该算法会设置一个公用变量turn，用于指示被允许进入临界区的进程编号。</p><p>如下面有p0和p1两个进程。</p><pre class="language-c" data-language="c"><code class="language-c">P0<span class="token operator">:</span><span class="token keyword">while</span><span class="token punctuation">(</span>turn<span class="token operator">!=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>turn <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><pre class="language-c" data-language="c"><code class="language-c">P1<span class="token operator">:</span><span class="token keyword">while</span><span class="token punctuation">(</span>turn<span class="token operator">!=</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>turn <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><p>若turn&#x3D;&#x3D;0时进入P0进程，turn&#x3D;&#x3D;1时进入P1进程。</p><p>仅当退出的时候会修改turn的值。</p><h5 id="双标志检查法"><a href="#双标志检查法" class="headerlink" title="双标志检查法"></a>双标志检查法</h5><p>该算法会设置一个布尔型数组flag[]，数组中存放<strong>标记各进程想进入临界区的意愿</strong>。只有当对方为false时才能使用临界区。</p><p>如下面有p0和p1两个进程。</p><pre class="language-c" data-language="c"><code class="language-c">P0<span class="token operator">:</span><span class="token keyword">while</span><span class="token punctuation">(</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>flag<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> true<span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>flag<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> false<span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><pre class="language-c" data-language="c"><code class="language-c">P1<span class="token operator">:</span><span class="token keyword">while</span><span class="token punctuation">(</span>flag<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> true<span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> false<span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><p>若flag[1]&#x3D;&#x3D;false时进入P0进程，flag[0]&#x3D;&#x3D;false时进入P1进程。</p><p>仅当退出的时候会修改flag的值。</p><p>由于双标志先检查法在并发环境进入区的“检查”和“上锁”不是同时完成，有可能会违法“忙则等待”原则。</p><p><strong>双标志后检查法是先上锁后检查。但在并发环境下会出现饥饿现象，导致谁都无法使用临界区</strong></p><h5 id="Peterson算法"><a href="#Peterson算法" class="headerlink" title="Peterson算法"></a>Peterson算法</h5><p>Peterson算法就是结合双标志法、单标志法的思想，设置turn和flag两变量实现。</p><p>如下面有p0和p1两个进程。</p><pre class="language-c" data-language="c"><code class="language-c">P0<span class="token operator">:</span>flag<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> true<span class="token punctuation">;</span> turn <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//进入区</span><span class="token keyword">while</span><span class="token punctuation">(</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> turn<span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>flag<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> false<span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><pre class="language-c" data-language="c"><code class="language-c">P1<span class="token operator">:</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> true<span class="token punctuation">;</span> turn <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment">//进入区</span><span class="token keyword">while</span><span class="token punctuation">(</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> turn<span class="token operator">==</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//进入区</span>critical selection<span class="token punctuation">;</span><span class="token comment">//临界区</span>flag<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> false<span class="token punctuation">;</span><span class="token comment">//退出区</span>remainder slection<span class="token punctuation">;</span><span class="token comment">//剩余区</span></code></pre><p>仅有当flag[0]&#x3D;&#x3D;true且turn&#x3D;&#x3D;1时才执行P0进程，很好的防止并发情况下会卡在while循环的情况，但仍然未能解决其他未能进入临界区的进程退出进入区的情况。</p><h4 id="硬件实现"><a href="#硬件实现" class="headerlink" title="硬件实现"></a>硬件实现</h4><h5 id="中断屏蔽方法"><a href="#中断屏蔽方法" class="headerlink" title="中断屏蔽方法"></a>中断屏蔽方法</h5><p>通过开关中断来实现互斥，即进程进入临界区后关中断，退出临界区再开中断。</p><p>优点：简单、高效。</p><p>缺点：由于开关中断指令只能在内核态中运行，因此不适用于多处理机。</p><h4 id="互斥锁"><a href="#互斥锁" class="headerlink" title="互斥锁"></a>互斥锁</h4><p>解决临界区最简单的工具就是互斥锁（mutex lock），当进程进入临界时候会获得锁；退出后释放锁。</p><p>互斥锁的主要缺点就是忙等待，当有一个进程在临界区，其他任何进程进入临界区必须进行等待。</p><p>而需要连续循环忙等待的互斥锁就称为<strong>自旋锁</strong>（spin lock）。</p><p>优点：等待期间不需要切换进程上下文，多核处理器系统中，若上锁时间段，则等待代价很低。</p><h4 id="信号量"><a href="#信号量" class="headerlink" title="信号量"></a>信号量</h4><p>信号量机制是可以很好的用来解决互斥和同步问题的机制，它只能被两个标准原语wait(S)和signal(S)访问，也可以记作P(S)、P(S)。</p><p>信号量实际就是一个变量（可以为整数，也可以是更复杂的记录型变量）。</p><h5 id="整型信号量"><a href="#整型信号量" class="headerlink" title="整型信号量"></a>整型信号量</h5><p>对于整型信号量只有三种操作，即初始化、p操作、v操作。</p><p>C语言模拟实现信号量的<code>wait(s),signal(s)</code></p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token keyword">int</span> S<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token comment">//wait原语，相对于“进入区”</span>    <span class="token keyword">while</span><span class="token punctuation">(</span>S<span class="token operator">&lt;=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//如果资源数不够。就一直循环等待</span>    S <span class="token operator">=</span> S<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//如果资源数够，就占用一个资源</span><span class="token punctuation">&#125;</span><span class="token keyword">void</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">int</span> S<span class="token punctuation">)</span><span class="token punctuation">&#123;</span><span class="token comment">//signal原语，相对于“退出区”</span>    S <span class="token operator">=</span> S<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">//使用完资源后，在退出区释放资源</span><span class="token punctuation">&#125;</span></code></pre><h5 id="记录型信号量"><a href="#记录型信号量" class="headerlink" title="记录型信号量"></a>记录型信号量</h5><p>记录型信号量使用一个记录型的数据结构来表示。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token comment">//记录型信号量的定义</span><span class="token keyword">typedef</span> <span class="token keyword">struct</span><span class="token punctuation">&#123;</span>    <span class="token keyword">int</span> value<span class="token punctuation">;</span><span class="token comment">//剩余资源数</span>    <span class="token keyword">struct</span> <span class="token class-name">process</span> <span class="token operator">*</span>L<span class="token punctuation">;</span><span class="token comment">//等待队列</span><span class="token punctuation">&#125;</span> semaphore<span class="token punctuation">;</span></code></pre><p>相应的<code>wait(S)</code>和<code>signal(S)</code>操作。</p><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">wait</span><span class="token punctuation">(</span>semaphore S<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    S<span class="token punctuation">.</span>value<span class="token operator">--</span><span class="token punctuation">;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>S<span class="token punctuation">.</span>value <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>        <span class="token function">block</span><span class="token punctuation">(</span>S<span class="token punctuation">.</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//若资源数不够将进程从运行态进入阻塞态</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><pre class="language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> <span class="token function">signal</span><span class="token punctuation">(</span>semaphore S<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    S<span class="token punctuation">.</span>value<span class="token operator">++</span><span class="token punctuation">;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>S<span class="token punctuation">.</span>value <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>        <span class="token function">wakeup</span><span class="token punctuation">(</span>S<span class="token punctuation">.</span>L<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//释放资源后若等待队列中还有进程，则将该进程从阻塞态变为就绪态</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span></code></pre><h5 id="信号量机制实现进程互斥"><a href="#信号量机制实现进程互斥" class="headerlink" title="信号量机制实现进程互斥"></a>信号量机制实现进程互斥</h5><p>实现进程互斥只需在临界区使用一个互斥信号量<code>mutex</code>即可实现，初值为1.</p><pre class="language-c" data-language="c"><code class="language-c">semaphore mutex <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">//初始化信号量</span><span class="token function">P1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token function">P</span><span class="token punctuation">(</span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//使用临界资源前需要加锁</span>    临界区代码段<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token function">V</span><span class="token punctuation">(</span>mutex<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//使用临界资源后需要解锁</span>    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">&#125;</span></code></pre><h5 id="信号量机制实现进程同步"><a href="#信号量机制实现进程同步" class="headerlink" title="信号量机制实现进程同步"></a>信号量机制实现进程同步</h5><p>通过设置同步信号量S，<strong>初始为0</strong>，在“前操作”之后执行V(S)（来唤醒后操作），在“后操作”之前执行P(S)（确保前操作没执行完前block）。</p><pre class="language-c" data-language="c"><code class="language-c">semaphore S<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span><span class="token function">P1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    代码<span class="token number">1</span><span class="token punctuation">;</span>    代码<span class="token number">2</span><span class="token punctuation">;</span>    <span class="token function">V</span><span class="token punctuation">(</span>S<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//S++，并唤醒等待队列中进程</span>    代码<span class="token number">3</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token function">P</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">P</span><span class="token punctuation">(</span>S<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//S--，若此时P1未执行导相应代码则进入阻塞态</span>    代码<span class="token number">4</span><span class="token punctuation">;</span>    代码<span class="token number">5</span><span class="token punctuation">;</span>    代码<span class="token number">6</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></code></pre><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><p>死锁的定义：</p><p>死锁就是指当多个进程并发执行时因竞争资源而产生的一种僵局（互相等待），导致各进程都阻塞，无法向前推进的现象。</p><h5 id="死锁、饥饿、死循环的区别和共同点"><a href="#死锁、饥饿、死循环的区别和共同点" class="headerlink" title="死锁、饥饿、死循环的区别和共同点"></a>死锁、饥饿、死循环的区别和共同点</h5><p><img src="https://img-blog.csdnimg.cn/direct/9d94e6d944dd453fba0cd53b4ed08a27.png"></p><h4 id="死锁产生的的必要条件"><a href="#死锁产生的的必要条件" class="headerlink" title="死锁产生的的必要条件"></a>死锁产生的的必要条件</h4><ul><li>互斥条件。只有当对必须互斥使用资源的争抢才会导致死锁（如哲学家的筷子、打印机设备）。</li><li>不剥夺条件。进程所获得的资源在未使用完之前，不能由其他进程强行夺走，只能主动释放。</li><li>请求和保持条件。进程已经保持了至少一个资源，但又提出了新的资源请求，而该资源又被其他进程占有，此时请求进程被阻塞。</li><li>循环等待条件。存在一种进程资源的循环等待链。</li></ul><p>处理死锁总共有三种方法：</p><ol><li>死锁预防。设置某些限制条件，破环产生死锁的4个必要条件中的一个或多个。</li><li>避免死锁。在资源的动态分配过程中，用某种方法防止系统进入不安全状态。</li><li>死锁的检测及解除。允许进程发生死锁，但发生死锁后会采取措施解除死锁。</li></ol><h4 id="死锁预防"><a href="#死锁预防" class="headerlink" title="死锁预防"></a>死锁预防</h4><ol><li><p>破环互斥条件</p><p>将只能互斥使用的资源改造未共享使用。如SPOOLing技术。</p><p><img src="https://img-blog.csdnimg.cn/direct/aa2dc756555c4f9685b07065981791da.png"></p></li><li><p>破环不剥夺条件</p><p>方案一：当某个进程请求新的资源得不到满足时，它必须立即释放保持的所有资源，待以后需要时再重新申请。也就是即使某些资源尚未使用完，也需要主动释放。</p><p>方案二：当某个进程需要的资源被其他进程所占有的时候，可以由操作系统协助，将想要的资源强行剥夺。这种一般需要考虑各个进程的优先级。</p></li><li><p>破环请求和保持条件</p><p>可以采用静态分配资源，即进程在运行前一次申请完它所需要的全部资源，在它资源未满足前，不让它投入运行。</p></li><li><p>破环循环等待条件。</p><p>可采用顺序资源分配法。首先给系统中的资源编号，规定每个进程必须按编号递增的顺序请求资源，同类资源一次申请完。</p></li></ol><h4 id="避免死锁"><a href="#避免死锁" class="headerlink" title="避免死锁"></a>避免死锁</h4><p>避免死锁就是在进程运行之前会施加一些限制条件来避免使用资源后会进入死锁状态。</p><h5 id="安全序列、不安全状态、死锁的联系"><a href="#安全序列、不安全状态、死锁的联系" class="headerlink" title="安全序列、不安全状态、死锁的联系"></a>安全序列、不安全状态、死锁的联系</h5><p>所谓<strong>安全序列</strong>，就是指如果系统按照这种序列分配资源，则每个进程都能顺利完成。只要能找出一个安全序列，系统就是安全状态。当然，安全序列可能有多个。</p><p>如果分配资源后，系统中找不出任何一个安全序列，系统就进入了<strong>不安全状态</strong>，就意味着之后如果分配了资源之后可能所有进程都无法顺利的执行下去。</p><p>若系统处于<strong>安全状态</strong>，就<font color="#ff0000">一定不会发生</font>死锁。</p><p>若系统进入<strong>不安全状态</strong>，就<font color="#ff0000">可能会发生</font>死锁。</p><h5 id="安全性算法"><a href="#安全性算法" class="headerlink" title="安全性算法"></a>安全性算法</h5><p>安全性算法即每一轮都从编号较小的进程开始检测，通过循环来获得安全序列，从此达到避免死锁。</p>]]>
    </content>
    <id>https://lsworl.github.io/2024/01/19/cao-zuo-xi-tong-jin-cheng-yu-xian-cheng/</id>
    <link href="https://lsworl.github.io/2024/01/19/cao-zuo-xi-tong-jin-cheng-yu-xian-cheng/"/>
    <published>2024-01-19T14:21:49.000Z</published>
    <summary>系统整理操作系统中进程、线程、调度、同步互斥、信号量和死锁等核心知识点。</summary>
    <title>操作系统-进程与线程</title>
    <updated>2026-05-23T07:33:06.000Z</updated>
  </entry>
</feed>
