<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[会飞的猪的小屋]]></title><description><![CDATA[哈喽~欢迎光临]]></description><link>https://blog.flyyingpiggy2020.cn</link><image><url>https://blog.flyyingpiggy2020.cn/innei.svg</url><title>会飞的猪的小屋</title><link>https://blog.flyyingpiggy2020.cn</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Thu, 09 Jul 2026 18:00:22 GMT</lastBuildDate><atom:link href="https://blog.flyyingpiggy2020.cn/feed" rel="self" type="application/rss+xml"/><pubDate>Thu, 09 Jul 2026 18:00:22 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[[git] git hook]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/debug/git-git_hook">https://blog.flyyingpiggy2020.cn/posts/debug/git-git_hook</a></blockquote><div><pre class="language-bash lang-bash"><code class="language-bash lang-bash">
#!/bin/sh
#pre-commit
set -eu

echo &quot;Running project formatter...&quot;

if command -v pwsh &gt;/dev/null 2&gt;&amp;1; then
    pwsh -NoProfile -File &quot;tools/format_code.ps1&quot;
elif command -v powershell.exe &gt;/dev/null 2&gt;&amp;1; then
    powershell.exe -NoProfile -ExecutionPolicy Bypass -File &quot;tools/format_code.ps1&quot;
elif command -v powershell &gt;/dev/null 2&gt;&amp;1; then
    powershell -NoProfile -ExecutionPolicy Bypass -File &quot;tools/format_code.ps1&quot;
else
    echo &quot;PowerShell was not found. Run tools/format_code.ps1 manually before committing.&quot;
    exit 1
fi

if ! git diff --quiet --; then
    echo &quot;&quot;
    echo &quot;Formatting changed files or unstaged changes are present.&quot;
    echo &quot;Review the diff, stage the formatted files, then commit again:&quot;
    echo &quot;&quot;
    echo &quot;  git diff&quot;
    echo &quot;  git add &lt;files&gt;&quot;
    echo &quot;  git commit&quot;
    echo &quot;&quot;
    git diff --name-only --
    exit 1
fi

echo &quot;Formatting check passed.&quot;
</code></pre>
<p>使用<code>git hook</code>可以在git操作的各个阶段进行操作。</p><p>例如我这里的<code>pre-commit</code>可以在<code>commit</code>提交之前执行脚本。</p><p>这里执行了一次<code>clang-format</code>，判断代码是否格式化。</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/debug/git-git_hook#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/debug/git-git_hook</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/debug/git-git_hook</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Wed, 08 Jul 2026 02:56:51 GMT</pubDate></item><item><title><![CDATA[等比数列求公式]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/basic/sum_of_geo_seq">https://blog.flyyingpiggy2020.cn/posts/basic/sum_of_geo_seq</a></blockquote><div><p>在复习卷积时候用到的，已经忘记了。</p><p>等比数列的求和公式主要分为公比 $q \neq 1$ 和 $q = 1$ 两种情况。</p><p>常规求和公式（从第 1 项加到第 n 项）</p><p>假设等比数列的首项是 $a<em>1$，公比是 $q$，前 $n$ 项的和记为 $S</em>n$：</p><p>当 $q \neq 1$ 时：</p><p>$S<em>n = \frac{a</em>1(1 - q^n)}{1 - q}$</p><p>或者写成：</p><p>$S<em>n = \frac{a</em>1 - a_n q}{1 - q}$</p><p>当 $q = 1$ 时：</p><p>$S<em>n = n \cdot a</em>1$</p><p>从 $n=0$ 加到 $n=k$ 的情况</p><p>你提到的“最后是 $q^{k+1}$ 吗？”这个直觉非常准确。在最终的求和公式里确实会出现 $q^{k+1}$，但我们需要稍微区分一下“加式的最后一项”和“公式里的指数”。</p><p>假设通项公式为 $a \cdot q^n$，我们从 $n=0$ 开始累加到 $n=k$：</p><p>$S = a \cdot q^0 + a \cdot q^1 + a \cdot q^2 + \dots + a \cdot q^k$</p><p>这里有两个关键细节：</p><p>累加的最后一项： 是 $a \cdot q^k$，并不是 $q^{k+1}$。</p><p>总项数： 从 $0$ 数到 $k$，总共有 $k+1$ 项（这是一个常见的“差一错误”陷阱）。</p><p>根据等比数列的求和原理，公式中的指数代表的是总项数。将“首项为 $a$”、“项数为 $k+1$”代入求和公式中（假设 $q \neq 1$），最终结果为：</p><p>$S = \frac{a(1 - q^{k+1})}{1 - q}$</p><p>总结： 你的理解在公式推导的层面上是完全正确的！如果你从 $n=0$ 累加到 $n=k$，求和公式的分子中必然包含 $q^{k+1}$。</p><hr/><p>这是一道关于离散时间序列卷积的经典信号与系统题目。我们可以通过离散卷积的定义公式来分步求解。</p><p>已知两个序列：</p><p>$x[n] = (\frac{1}{2})^n u[n] + 2^n u[-n]$</p><p>$h[n] = u[n]$</p><p>目标是求卷积 $y[n] = x[n] * h[n]$。</p><ol start="1"><li>设立卷积和公式</li></ol><p>根据离散时间卷积的定义：</p><p>$y[n] = \sum_{k=-\infty}^{\infty} x[k] h[n-k]$</p><p>将 $x[n]$ 和 $h[n]$ 代入公式中：</p><p>$y[n] = \sum_{k=-\infty}^{\infty} \left[ (\frac{1}{2})^k u[k] + 2^k u[-k] \right] u[n-k]$</p><p>利用求和的线性性质，我们可以将其分为两部分分别求解：</p><p>$y[n] = y<em>1[n] + y</em>2[n]$</p><p>其中：</p><p>$y<em>1[n] = \sum</em>{k=-\infty}^{\infty} (\frac{1}{2})^k u[k] u[n-k]$</p><p>$y<em>2[n] = \sum</em>{k=-\infty}^{\infty} 2^k u[-k] u[n-k]$</p><ol start="3"><li>求解 $y_1[n]$</li></ol><p>分析阶跃函数确定的求和区间：</p><p>$u[k] = 1$ 要求 $k \ge 0$</p><p>$u[n-k] = 1$ 要求 $n-k \ge 0$，即 $k \le n$</p><p>因此， $y<em>1[n]$ 的非零区间要求同时满足 $k \ge 0$ 且 $k \le n$。这说明只有当 $n \ge 0$ 时求和才有意义（此时 $0 \le k \le n$）。如果 $n &lt; 0$，区间不重叠，$y</em>1[n] = 0$。</p><p>当 $n \ge 0$ 时，利用等比数列求和公式：</p><p>$y<em>1[n] = \sum</em>{k=0}^{n} (\frac{1}{2})^k = \frac{1 - (1/2)^{n+1}}{1 - 1/2} = 2 \left[ 1 - (\frac{1}{2})^{n+1} \right] = 2 - (\frac{1}{2})^n$</p><p>所以，$y_1[n]$ 可以写为：</p><p>$y_1[n] = \left[ 2 - (\frac{1}{2})^n \right] u[n]$</p><ol start="4"><li>求解 $y_2[n]$</li></ol><p>分析阶跃函数确定的求和区间：</p><p>$u[-k] = 1$ 要求 $-k \ge 0$，即 $k \le 0$</p><p>$u[n-k] = 1$ 要求 $n-k \ge 0$，即 $k \le n$</p><p>因此，$y_2[n]$ 的求和区间取决于 $n$ 与 $0$ 的大小关系：</p><p>情况 A：当 $n \ge 0$ 时</p><p>求和区间的上限受限于 $k \le 0$（因为 $k$ 必须同时小于等于 $0$ 且小于等于 $n$，此时 $0$ 更小）。</p><p>$y<em>2[n] = \sum</em>{k=-\infty}^{0} 2^k$</p><p>令 $m = -k$，则求和变为从 $0$ 到 $\infty$：</p><p>$y<em>2[n] = \sum</em>{m=0}^{\infty} 2^{-m} = \sum_{m=0}^{\infty} (\frac{1}{2})^m = \frac{1}{1 - 1/2} = 2$</p><p>情况 B：当 $n &lt; 0$ 时</p><p>求和区间的上限受限于 $k \le n$（因为此时 $n$ 比 $0$ 小）。</p><p>$y<em>2[n] = \sum</em>{k=-\infty}^{n} 2^k$</p><p>令 $m = n-k$，当 $k=n$ 时 $m=0$；当 $k \to -\infty$ 时 $m \to \infty$。</p><p>$y<em>2[n] = \sum</em>{m=0}^{\infty} 2^{n-m} = 2^n \sum_{m=0}^{\infty} (\frac{1}{2})^m = 2^n \left( \frac{1}{1 - 1/2} \right) = 2^{n+1}$</p><p>综合以上两种情况，$y_2[n]$ 可以分段表示为：</p><p>$y_2[n] = \begin{cases} 2, &amp; n \ge 0 \ 2^{n+1}, &amp; n &lt; 0 \end{cases}$</p><p>或者用阶跃函数写成：$y_2[n] = 2u[n] + 2^{n+1}u[-n-1]$。</p><ol start="5"><li>组合最终结果</li></ol><p>将 $y<em>1[n]$ 和 $y</em>2[n]$ 相加 $y[n] = y<em>1[n] + y</em>2[n]$：</p><p>当 $n \ge 0$ 时：</p><p>$y[n] = \left( 2 - (\frac{1}{2})^n \right) + 2 = 4 - (\frac{1}{2})^n$</p><p>当 $n &lt; 0$ 时：</p><p>$y[n] = 0 + 2^{n+1} = 2^{n+1}$</p><p>最终答案：</p><p>你可以将结果写成分段函数的形式：</p><p>$y[n] = \begin{cases} 4 - (\frac{1}{2})^n, &amp; n \ge 0 \ 2^{n+1}, &amp; n &lt; 0 \end{cases}$</p><p>或者合并写成使用单位阶跃函数的闭式表达式：</p><p>$y[n] = \left[ 4 - (\frac{1}{2})^n \right] u[n] + 2^{n+1} u[-n-1]$</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/basic/sum_of_geo_seq#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/basic/sum_of_geo_seq</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/basic/sum_of_geo_seq</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Wed, 08 Jul 2026 00:15:53 GMT</pubDate></item><item><title><![CDATA[at device源码阅读]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/at-device">https://blog.flyyingpiggy2020.cn/posts/rtthread/at-device</a></blockquote><div><p>AT设备你们是如何解析的呢？笔者第一次使用at设备，是一个4G模块，大夏龙雀的L511-2。</p><p>看了新公司的同事的代码，这颗模块实现MQTT的方式，是直接用了AT+MQTT的方式来下发。</p><p>我打算换一个架构，之前学习的是socket做网络应用，我想把AT设备封装成socket接口。</p><p>这部分代码就不自己造轮子了，据我了解rtthread直接有at socket，今天就花一天时间阅读一下它的源码。</p><p>这里得分为两部分，一个是<strong>AT协议的解析框架</strong>。第二个是<strong>如何把AT指令封装成socket</strong>。</p><p>我们先来看第一个。</p><h1 id="at">AT协议解析框架</h1><h2 id="">底层收发接口</h2><p>最底层的发送接口使用一个<code>weak</code>定义的函数<code>at_utils_send</code>实现：</p><pre class="language-c lang-c"><code class="language-c lang-c">
rt_weak rt_size_t at_utils_send(rt_device_t dev,
                                rt_off_t    pos,
                                const void *buffer,
                                rt_size_t   size)
{
    return rt_device_write(dev, pos, buffer, size);
}

// 代码
</code></pre>
<p>它内部调用<code>rt_device_write</code>，如果我们物理接口使用的是<code>device io</code>框架，那么可以直接使用。</p><h2 id="atclient-">at_client 初始化</h2><p>根据语义，主动的被称为Client，被动的被称为Server。在我们的应用场景中往往模块被当成Server，而调用模块的MCU当成Client。如果你是要自己做一个模块，那就是实现Server。</p><p>在rtthread的代码中，提供了<code>at_client.c</code>和<code>at_server.c</code>这样子两个文件。</p><p>作为使用模块的用户，这里启用<code>at_client.c</code></p><p>它支持多实例，用单向链表管理多个client。<code>g_at_client_list</code>就是链表，在里面挂载了一个个节点<code>at_client_t</code>。</p>
<details><summary>在参数初始化代码中我发现了有趣的一点，多个线程可以使用同一个entry函数</summary><pre class="language-c lang-c"><code class="language-c lang-c">
unsigned int at_client_num = rt_slist_len(&amp;g_at_client_list);
rt_snprintf(name, RT_NAME_MAX, &quot;%s%d&quot;, AT_CLIENT_THREAD_NAME, at_client_num);
client-&gt;parser = rt_thread_create(name,
                                      (void (*)(void *parameter))ient_parsercl,
                                      client,
                                      1024 + 512,
                                      RT_THREAD_PRIORITY_MAX / 3 - 1,
                                      5);

</code></pre></details><p>之前没有接触过这种写法，不同入参同样的entry，又学到了一招。</p><p>初始化函数为：<code>int at_client_init(const char *dev_name, rt_size_t recv_bufsz, rt_size_t send_bufsz)</code></p><p>这里的入参有3个，第一个<code>dev_name</code>也就是输出物理层设备的名称。</p><p>我看到代码里的写法，应该是一个输出设备只能注册一个client。不存在一个串口，挂载多个client这种现象。我仔细想一下，好像确实是这样，这个抽象是合理的。一个串口挂载两个设备就乱套了。必须物理层和AT设备是一对一的。</p><p>整体<code>at_client_init</code>过程不是很复杂，完整代码我就不贴了，不然文章就太长了。</p><p>它里面，首先根据<code>dev_name</code>判断设备是否注册，防止重复注册。</p><p>然后malloc申请一个长度为<code> sizeof(struct at_client) + recv_bufsz + send_bufsz</code>内存。</p><p>这种做法貌似在RTOS代码里面满常见的。只申请一次，其中控制句柄放在这个内存的最开始。<code>client = rt_malloc(sizeof(struct at_client) + recv_bufsz + send_bufsz);</code>然后再对这段内存做分配。而不是申请三次</p><pre class="language-c lang-c"><code class="language-c lang-c">//申请一次，后续分配这段内存
client = rt_malloc(sizeof(struct at_client) + recv_bufsz + send_bufsz);
...
client-&gt;recv_bufsz    = recv_bufsz;
client-&gt;recv_line_buf = ((char *)client) + sizeof(struct at_client);

client-&gt;send_bufsz = send_bufsz;
client-&gt;send_buf   = ((char *)client) + sizeof(struct at_client) + client-&gt;recv_bufsz;
...
</code></pre>
<p>最后把<code>client</code>添加到<code>list</code>里面，注册串口RX的回调之类的。
<code>rt_device_set_rx_indicate(client-&gt;device, at_client_rx_ind);</code></p><p>这个回调函数在串口收到数据后会被触发，里面是发送了事件标志组，去通知解析。</p><p><code>rt_event_send(&amp;client-&gt;event, at_client_rx_notice_event);</code></p>
<h2 id="">解析数据</h2><p>通过<code>at_client_rx_notice_event</code>事件来触发读取。把读到的数据放到自己的<code>recv_line_buff</code>中</p><pre class="language-c lang-c"><code class="language-c lang-c">

if (event &amp; at_client_rx_notice_event)
{
    while (RT_EOK == at_client_getchar(client, &amp;ch))
    {
        if (client-&gt;recv_line_len &lt; client-&gt;recv_bufsz)
        {
            client-&gt;recv_line_buf[client-&gt;recv_line_len++] = ch;
        }
        else
        {
            is_full = RT_TRUE;
        }

        /* is newline or URC data */
        client-&gt;urc = get_urc_obj(client);
        if (client-&gt;urc != RT_NULL || (ch == &#x27;\n&#x27; &amp;&amp; last_ch == &#x27;\r&#x27;)
            || (client-&gt;end_sign != 0 &amp;&amp; ch == client-&gt;end_sign))
        {
            if (is_full)
            {
                LOG_E(&quot;read line failed. The line data length is out of buffer size(%d)!&quot;, client-&gt;recv_bufsz);
                rt_memset(client-&gt;recv_line_buf, 0x00, client-&gt;recv_bufsz);
                client-&gt;recv_line_len = 0;
                return -RT_EFULL;
            }

            /* Since the buffer state is uncertain, we proactively clear it; the overhead is negligible. */
            rt_event_send(&amp;client-&gt;event, at_client_rx_notice_event);
            goto __next;
        }
        last_ch = ch;
    }
}
</code></pre>
<p>这里判断了<code>urc</code></p><p>所谓urc就是指AT指令中，模块主动给MCU上报的信息，例如发送<code>AT+CSQ</code>收到的应答如下：</p><pre class="language-powershell lang-powershell"><code class="language-powershell lang-powershell">+CSQ: 20,99
OK
</code></pre>
<p>这种时候就需要解析应答的内容。</p><p>因为不同模块的应答格式不同，所以这个框架中采用了注册的方法，注册一个<code>urc_table</code>，然后这里是收到应答后去匹配，看看是不是<code>urc</code>的内容。</p>
<p><code>resp</code>是应答包。在AT协议中需要<code>OK</code>,<code>ERROR</code>,<code>FAIL</code>来判断发送命令的知行情况。</p><p>伪代码如下：</p>
<pre class="language-C lang-C"><code class="language-C lang-C">/**
 * Waiting for connection to external devices.
 *
 * @param client current AT client object
 * @param timeout millisecond for timeout
 *
 * @return 0 : success
 *        -2 : timeout
 *        -5 : no memory
 */
int at_client_obj_wait_connect(at_client_t client, rt_uint32_t timeout)
{
    rt_err_t      result     = RT_EOK;
    at_response_t resp       = RT_NULL;
    rt_tick_t     start_time = 0;

    if (client == RT_NULL)
    {
        LOG_E(&quot;input AT client object is NULL, please create or get AT Client object!&quot;);
        return -RT_ERROR;
    }

    resp = at_create_resp(64, 0, rt_tick_from_millisecond(300));
    if (resp == RT_NULL)
    {
        LOG_E(&quot;no memory for AT client(%s) response object.&quot;, client-&gt;device-&gt;parent.name);
        return -RT_ENOMEM;
    }

    start_time = rt_tick_get();

    while (1)
    {
        /* Check whether it is timeout */
        if (rt_tick_get() - start_time &gt; rt_tick_from_millisecond(timeout))
        {
            LOG_E(&quot;wait AT client(%s) connect timeout(%d tick).&quot;, client-&gt;device-&gt;parent.name, timeout);
            result = -RT_ETIMEOUT;
            break;
        }

        if (at_obj_exec_cmd(client, resp, &quot;AT&quot;) == RT_EOK)
        {
            break;
        }
    }

    at_delete_resp(resp);
    return result;
}

</code></pre>
<p>先是创建<code>resp</code>，然后调用<code>at_obj_exec_cmd</code>等到回复应答。</p>
<h1 id="socket">封装socket</h1></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/at-device#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/rtthread/at-device</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/rtthread/at-device</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Thu, 02 Jul 2026 02:40:19 GMT</pubDate></item><item><title><![CDATA[scons]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/scons">https://blog.flyyingpiggy2020.cn/posts/rtthread/scons</a></blockquote><div><p><code>scons --cdb build/compile_commands.json</code>这行命令可以用来用于clangd生成智能提示的json。</p><p><code>scons --target=mkd5</code>生成MDK工程。MDK工程的.c和.h文件添加通过编辑Sconsript文件实现。</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/scons#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/rtthread/scons</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/rtthread/scons</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Tue, 09 Jun 2026 06:58:49 GMT</pubDate></item><item><title><![CDATA[serial_v1和serial_v2的差别]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/serial-v1-serial-v2">https://blog.flyyingpiggy2020.cn/posts/rtthread/serial-v1-serial-v2</a></blockquote><div><p><code>RT_USING_SRIAL_V1</code>和<code>RT_USING_SRIAL_V2</code>是 RT-Thread串口驱动的两个版本</p><p>定义在</p><p><a href="https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/serial/Kconfig#L7-L15">https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/serial/Kconfig#L7-L15</a></p><pre class="language-text lang-text"><code class="language-text lang-text">choice RT_USING_SERIAL_VERSION
    prompt &quot;Choice Serial version&quot;
    default RT_USING_SERIAL_V1

    config RT_USING_SERIAL_V1
        bool &quot;RT_USING_SERIAL_V1&quot;

    config RT_USING_SERIAL_V2
        bool &quot;RT_USING_SERIAL_V2&quot;
endchoice
</code></pre>
<p>SCons会根据这个选择不同的源码</p><p><a href="https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/serial/SConscript#L14-L17">https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/serial/SConscript#L14-L17</a></p><pre class="language-text lang-text"><code class="language-text lang-text">if GetDepend([&#x27;RT_USING_SERIAL_V2&#x27;]):
    src += [&#x27;dev_serial_v2.c&#x27;]
else:
    src += [&#x27;dev_serial.c&#x27;]
</code></pre>
<p><strong>V1 和 V2 的主要区别：</strong></p><table><thead><tr><th> 项目 </th><th> Serial V1 </th><th> Serial V2 </th></tr></thead><tbody><tr><td> RT-Thread serial 核心文件 </td><td> dev_serial.c </td><td> dev<em>serial</em>v2.c </td></tr><tr><td> STM32 UART 驱动 </td><td> drv_usart.c </td><td> drv<em>usart</em>v2.c </td></tr><tr><td> 默认选择 </td><td> 是 </td><td> 否 </td></tr><tr><td> RX buffer </td><td> 全局 RT<em>SERIAL</em>RB_BUFSZ </td><td> 每个 UART 单独配置 </td></tr><tr><td> TX buffer </td><td> 较弱 </td><td> 每个 UART 单独配置 </td></tr><tr><td> buffer 策略 </td><td> 较传统 </td><td> 支持 drop / overwrite </td></tr><tr><td> Kconfig 可配置粒度 </td><td> 较粗 </td><td> 更细 </td></tr><tr><td> DMA 支持 </td><td> 有 </td><td> 更系统化 </td></tr><tr><td> 适合新项目 </td><td> 一般 </td><td> 更推荐 </td></tr></tbody></table><p><strong>BYPASS功能</strong></p><p>这个功能是RT-Thread serial 驱动里面的一个串口数据拦截机制。</p><p>开启 bypass 之后，串口的数据在被 rt_device_read()之前，会经过bypass pipe。</p><p>这个功能可以让某些模块提前看到串口字节。</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/rtthread/serial-v1-serial-v2#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/rtthread/serial-v1-serial-v2</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/rtthread/serial-v1-serial-v2</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Tue, 09 Jun 2026 00:37:35 GMT</pubDate></item><item><title><![CDATA[流式传输与帧传输]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/other/streaming-and-frame-transmission">https://blog.flyyingpiggy2020.cn/posts/other/streaming-and-frame-transmission</a></blockquote><div><p>流式传输和帧传输有区别。</p><p>流式传输不适合用pingpong。</p><p>在USB CDC 中用了pingpong缓冲。</p><p>我没想到的是256字节会分为4包每包64字节传输（因为USB 的断点大小最多64的关系）</p><p>这个就导致了，CPU占用率低的时候，中间能来得及触发Read，看上去没有什么问题。</p><p>占用率搞的时候，多包中间没Read。直接数据丢失。</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/other/streaming-and-frame-transmission#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/other/streaming-and-frame-transmission</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/other/streaming-and-frame-transmission</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Sat, 09 May 2026 00:30:47 GMT</pubDate></item><item><title><![CDATA[docker安装与使用]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/smart_home/docker1">https://blog.flyyingpiggy2020.cn/posts/smart_home/docker1</a></blockquote><div><h1 id="dcoker">dcoker解决了什么问题？</h1><p>不同的软件需要不同的依赖，某些依赖会互相干扰。</p><p>---</p><h3 id="1--docker--compose-">1. 安装 Docker 和 Compose 插件</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">sudo apt update
sudo apt install docker.io docker-compose-v2 -y
</code></pre><h3 id="2--docker-">2. 验证 Docker 是否安装成功</h3><p>输入：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">sudo docker ps
</code></pre><p>如果终端没有报错，而是打印出了 <code>CONTAINER ID   IMAGE   COMMAND...</code> 这样的表头，说明 Docker 已经运行</p><p>---</p><h3 id="4-ha">4. 安装HA</h3><p>既然 Docker 好了，我们现在就把 Home Assistant 和 MQTT 的配置文件写好，把它们跑起来。</p><p>依次输入：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 创建一个文件夹专门放智能家居的配置
mkdir ~/smarthome
cd ~/smarthome

# 使用 nano 编辑器创建 docker-compose.yml 文件
nano docker-compose.yml
</code></pre><p>编写完对应的配置后，敲下启动命令：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker compose up -d
</code></pre><p>它会自动去拉取 HA 和 Mosquitto 的镜像。由于 HA 镜像比较大（大概1个多G），这里可能需要耐心等上几分钟到十几分钟，具体取决于你的网速。</p>
<p>国内给docker换源</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json &lt;&lt;-&#x27;EOF&#x27;
{
  &quot;registry-mirrors&quot;: [
    &quot;https://docker.m.daocloud.io&quot;,
    &quot;https://docker.nju.edu.cn&quot;,
    &quot;https://dockerproxy.com&quot;,
    &quot;https://hub-mirror.c.163.com&quot;
  ]
}
EOF// 代码
</code></pre>
<p>换好后重启</p>
<pre class="language-bash lang-bash"><code class="language-bash lang-bash">sudo systemctl daemon-reload
sudo systemctl restart docker

</code></pre></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/smart_home/docker1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/smart_home/docker1</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/smart_home/docker1</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Fri, 24 Apr 2026 07:37:46 GMT</pubDate></item><item><title><![CDATA[miniconda管理python环境]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/smart_home/miniconda">https://blog.flyyingpiggy2020.cn/posts/smart_home/miniconda</a></blockquote><div><h3 id="1--miniconda-">1. 下载 Miniconda 安装脚本</h3><p>打开你的终端，根据你当前的设备选择对应的下载命令：</p><p><strong>如果你现在是在泰山派 (RK3576) 的板子上操作 (ARM64 架构)：</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-aarch64.sh -O miniconda.sh
</code></pre>
<p><strong>如果你现在是在 Windows 的 WSL2 或纯粹的 PC 虚拟机里操作 (x86_64 架构)：</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
</code></pre>
<h3 id="2-">2. 运行安装程序</h3><p>下载完成后，执行以下命令开始安装：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">bash miniconda.sh
</code></pre>
<p><strong>安装过程中的关键交互提示（按顺序）：</strong></p><ol start="1"><li><strong><code>Please, press ENTER to continue</code></strong> -&gt; 按下<strong>回车键</strong>。</li><li>此时会显示长篇的许可协议。按住键盘上的<strong>空格键</strong>快速翻页，直到最底部。</li><li><strong><code>Do you accept the license terms? [yes|no]</code></strong> -&gt; 输入 <strong><code>yes</code></strong> 并回车。</li><li><strong><code>Miniconda3 will now be installed into this location...</code></strong> -&gt; 提示安装路径（默认是 <code>~/miniconda3</code>），直接按<strong>回车键</strong>确认。</li><li>等待进度条走完进行解压安装。</li><li><strong>⚠️ 最关键的一步：<code>Do you wish the installer to initialize Miniconda3 by running conda init? [yes|no]</code></strong> -&gt; 务必输入 <strong><code>yes</code></strong> 并回车。这会自动配置你的环境变量。</li></ol><h3 id="3-">3. 使环境变量生效</h3><p>安装完成后，刚才的配置需要你重新加载一下终端才能生效。你可以关掉终端重新打开，或者直接输入：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">source ~/.bashrc
</code></pre>
<h3 id="4-">4. 验证安装</h3><p>如果在命令行的最前面出现了一个 <code>(base)</code> 字样，说明 Conda 已经成功接管了。
你可以输入以下命令确认：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">conda --version
</code></pre><p>如果打印出版本号（例如 <code>conda 24.x.x</code>），就说明安装大功告成。</p><p>---
Miniconda 默认会在你每次打开终端时，自动进入 <code>(base)</code> 虚拟环境。
但是，作为搞硬件和底层开发的人，我们经常需要编译 C/C++ 代码（比如使用 <code>make</code>、<code>cmake</code> 或者交叉编译工具链）。Conda 的默认环境有时会拦截掉系统的自带编译器或者库，导致莫名其妙的编译报错。</p><p><strong>为了保证系统环境的干净，强烈建议关闭 Conda 的开机自启：</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">conda config --set auto_activate_base false
</code></pre><p>执行完之后，下次打开终端就不会再看到默认的 <code>(base)</code> 了。当你想写 Python 代码或启动网关服务时，再手动激活即可。</p><h3 id="-">🚀 日常使用速查手册</h3><p>准备好之后，你就可以随时为不同的项目创建独立的“无菌室”了。比如你要开发那个蓝牙网关：</p><ul><li><strong>创建新环境（例如指定 Python 3.10 版本）：</strong></li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">  conda create -n ble_gateway python=3.10
</code></pre><ul><li><strong>进入该环境：</strong>
<code>bash
conda activate ble_gateway
</code>
此时终端前缀会变成 <code>(ble_gateway)</code></li><li><strong>退出当前环境，回到系统原生环境：</strong>
<code>bash
conda deactivate
</code></li><li><strong>查看你创建了哪些环境：</strong>
<code>bash
conda env list
</code></li></ul></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/smart_home/miniconda#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/smart_home/miniconda</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/smart_home/miniconda</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Fri, 24 Apr 2026 06:54:19 GMT</pubDate></item><item><title><![CDATA[WS2812B踩坑记录]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/posts/other/ws2812b">https://blog.flyyingpiggy2020.cn/posts/other/ws2812b</a></blockquote><div><p>驱动方式：定时器+DMA</p><p>现象：手一摸会闪烁</p><p>解决方法：IO设置为软件下拉</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/posts/other/ws2812b#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/posts/other/ws2812b</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/posts/other/ws2812b</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Thu, 23 Apr 2026 01:36:28 GMT</pubDate></item><item><title><![CDATA[回不去了]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://blog.flyyingpiggy2020.cn/notes/20">https://blog.flyyingpiggy2020.cn/notes/20</a></blockquote><div><p>今天调试一个继电器电机。用了24年写的电机业务逻辑的代码框架。</p><p>回头看之前自己写的代码框架，感慨写的真TM好！真TM解耦合。</p><p>把它抽象出了inc，dec，stop而不关心底层电机类型。route_curr行程获取不关心旋变、磁编还是霍尔。</p><p>用的时候和大脑按摩一样，太顺滑了。</p><p>回头看目前用AI写的驱动，例如WB2812B，因为AI写的速度太快了，我很多代码没完全阅读过。明明是我让AI写的，但是里面的辅助函数我却看不懂什么意思了。再也没有那种对代码随心所欲掌控的感觉了。</p><p>古法编程的代码一去不复返了。</p></div><p style="text-align:right"><a href="https://blog.flyyingpiggy2020.cn/notes/20#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://blog.flyyingpiggy2020.cn/notes/20</link><guid isPermaLink="true">https://blog.flyyingpiggy2020.cn/notes/20</guid><dc:creator><![CDATA[Lu Xianfan]]></dc:creator><pubDate>Fri, 17 Apr 2026 01:03:16 GMT</pubDate></item></channel></rss>