<?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[Felix Xu 日常学习实践笔记]]></title><description><![CDATA[日常有什么想法什么感想就写点笔记记录一下叭~]]></description><link>https://xufelix-dailynotes-zh.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 18 Jun 2026 21:53:42 GMT</lastBuildDate><atom:link href="https://xufelix-dailynotes-zh.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[用交叉编译 SDK 编译、配置 STM32MP135 的 Qt 环境]]></title><description><![CDATA[注意，本文绝对不要无脑CV，把文字看清楚再动手！
前言
感谢 kt 老师的代笔，给原本的内容加了很多详尽的解释。我自己琢磨这个环境花了 24h+ 的时间，交叉编译还是太过于变态了，qt 的环境配置绕了很久，自己又没有经验——不过万事开头难嘛，嵌入式不就是一直跟各种奇奇怪怪的环境纠缠嘛。
5.4版本的STM32MP1-SDK内部没有适配arm32架构编译用的qt工具链，所以无法对arm32目标架构的]]></description><link>https://xufelix-dailynotes-zh.hashnode.dev/sdk-qt</link><guid isPermaLink="true">https://xufelix-dailynotes-zh.hashnode.dev/sdk-qt</guid><category><![CDATA[embedded]]></category><category><![CDATA[Qt]]></category><category><![CDATA[Qt Creator]]></category><category><![CDATA[STM32]]></category><category><![CDATA[Linux]]></category><category><![CDATA[Cross Platform]]></category><category><![CDATA[compiler]]></category><dc:creator><![CDATA[Felix Xu]]></dc:creator><pubDate>Sat, 25 Apr 2026 12:19:09 GMT</pubDate><content:encoded><![CDATA[<img alt="QC-Kit配置界面" style="display:block;margin:0 auto" />

<p><strong>注意，本文绝对不要无脑CV，把文字看清楚再动手！</strong></p>
<h2>前言</h2>
<p>感谢 kt 老师的代笔，给原本的内容加了很多详尽的解释。我自己琢磨这个环境花了 24h+ 的时间，交叉编译还是太过于变态了，qt 的环境配置绕了很久，自己又没有经验——不过万事开头难嘛，嵌入式不就是一直跟各种奇奇怪怪的环境纠缠嘛。</p>
<p>5.4版本的STM32MP1-SDK内部没有适配arm32架构编译用的qt工具链，所以无法对arm32目标架构的qt代码进行编译。哪怕你配置好了<code>gcc</code> <code>g++</code>等路径的编译器，也会因为没有qt工具链出现编译失败问题。所以我们需要自己手动添加一个qt工具链，这有两种实现路径：</p>
<ol>
<li><p>下载3.1版本的STM32对应的SDK，其内部有qt工具链；</p>
</li>
<li><p>手动去qt官网获取<strong>qt5</strong>版本的源码，并将其编译成可使用的qt编译链，难度较高，本文讲解的就是这个编译的过程。</p>
</li>
</ol>
<h2>环境</h2>
<ul>
<li><p>主机系统：Win11</p>
</li>
<li><p>虚拟机：VMware® Workstation Pro 25H2u1 25.0.1.25219725</p>
</li>
<li><p>Linux：Ubuntu 24.04.4 LTS</p>
</li>
<li><p>开发板：知睿-MP135-嵌入式学习系统（STM32MP135）</p>
</li>
<li><p>Qt 版本：Qt 5.12.9</p>
</li>
<li><p>板载系统：Openstlinux，Buildroot 2022.02.3</p>
</li>
</ul>
<h2>准备工作</h2>
<h3>系统环境准备</h3>
<ul>
<li>安装旧版的<code>gcc-9</code>，并注册为默认编译器，因为qt5的版本比较老，用旧的编译器能减少很多问题。</li>
</ul>
<pre><code class="language-bash">sudo apt install gcc-9 g++-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9
sudo update-alternatives --set gcc /usr/bin/gcc-9
</code></pre>
<ul>
<li>安装好Python等基础库，这里可能没列全，缺了自己补。</li>
</ul>
<pre><code class="language-bash">sudo apt install python3
sudo apt install python-is-python3
</code></pre>
<h3>SDK 和 sysroot</h3>
<ul>
<li><p>按照上一课的要求完成STM32MP1-SDK的解压和安装，得到对应的 <code>arm-ostl-linux-gnueabi-gcc</code> 等编译器。<strong>sysroot 文件夹在 SDK 的安装路径下，也就是我们运行 SDK 安装包时指定的路径。而后续步骤中的各种</strong> <code>sysroot</code> <strong>参数，则是 SDK 路径下的</strong> <code>/sysroots/x86_64-ostl_sdk-linux/</code> <strong>这一路径。</strong></p>
<ul>
<li><p>如果之前看的是我的攻略，那这时候SDK就是在默认路径下，sysroot 就是 <code>/opt/st/stm32mp1/3.1-openstlinux-5.4-dunfell-mp1-20-06-24/sysroots/x86_64-ostl_sdk-linux/</code></p>
</li>
<li><p>如果之前参照了老师的文档，那SDK就是在<code>/home/用户名/STM32MPU_workspace/STM32MP1-Ecosystem/sysroots/x86_64-ostl_sdk-linux/</code></p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>这个路径可以记一下，后面会反复用到！</p>
</blockquote>
<ul>
<li>创建软链接。这里一定要用绝对路径。下面的代码不能直接cv，要将前置路径改成自己的 <code>sysroot</code>。</li>
</ul>
<pre><code class="language-bash">sudo ln -sf （你的sysroot）/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-gcc /usr/bin/arm-ostl-linux-gnueabi-gcc
sudo ln -sf （你的sysroot）/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-g++ /usr/bin/arm-ostl-linux-gnueabi-g++
sudo ln -sf （你的sysroot）/usr/bin/arm-ostl-linux-gnueabi/arm-ostl-linux-gnueabi-ar /usr/bin/arm-ostl-linux-gnueabi-ar
</code></pre>
<h3>准备好 Qt5 源码和路径</h3>
<blockquote>
<p>可以在板子上 <code>ls -R / | grep libQt5*.so*</code> 一下，查查板子有没有现成的qt库，如果版本没有差太多，可以直接找对应的版本，后面不需要把库导进板子；</p>
<p><code>/home/用户名/</code>的路径可以简化为<code>~/</code>，但是本文还是按照标准展开来写。</p>
</blockquote>
<ul>
<li><p>下载<a href="https://download.qt.io/archive/qt/">qt的源码</a>，注意必须是qt5的版本，稍微早一点的版本需要挂梯子才能访问。建议解压为 <code>/home/用户名/Qt/（版本编号）/Src</code>，便于 Qt Creator 识别。</p>
</li>
<li><p>然后创建一个用于<strong>安装qt5工具链的文件夹</strong>，比如<code>/home/用户名/Qt/（版本编号）/arm32-gcc</code></p>
</li>
</ul>
<h2>编译过程</h2>
<p>Qt 的编译过程非常麻烦，分三步，第一步用configure生成正确的编译环境，第二步正式编译，第三步把编译好的文件正确安装到路径下。如果你的板子没有Qt5的库，还要再把编译出来的库copy到板子中。</p>
<h3>configure</h3>
<ul>
<li>进入解压好的qt5源码里的文件夹，进入<code>/qtbase/mkspecs/</code>，复制其中的文件夹 <code>linux-arm-gnueabi-g++</code> 为 <code>linux-arm-ostl-g++</code>（这里相当于做一次备份），然后进入刚刚复制的文件夹 <code>linux-arm-ostl-g++</code>，改 <code>qmake.conf</code>的代码如下，其中有个地方需要自己修改，将<code>SYSROOT_PATH</code>改成自己的sysroot。</li>
</ul>
<blockquote>
<p>下文中的 <code>CFLAGS</code>，我们可以在初始化SDK环境的脚本中找到，CC变量后面，编译器后面那一大串东西就是 FLAG 的内容，这里已经写好了。</p>
</blockquote>
<pre><code class="language-plaintext">#
# qmake configuration for building with arm-ostl-linux-gnueabi-g++
#

MAKEFILE_GENERATOR      = UNIX
CONFIG                 += incremental
QMAKE_INCREMENTAL_STYLE = sublib

include(../common/linux.conf)
include(../common/gcc-base-unix.conf)
include(../common/g++-unix.conf)

# modifications to g++.conf
QMAKE_CC                = arm-ostl-linux-gnueabi-gcc
QMAKE_CXX               = arm-ostl-linux-gnueabi-g++ 
QMAKE_LINK              = arm-ostl-linux-gnueabi-g++
QMAKE_LINK_SHLIB        = arm-ostl-linux-gnueabi-g++

# add sysroot

SYSROOT_PATH = （你的sysroot）

# set params

QMAKE_CFLAGS            += --sysroot=$$SYSROOT_PATH -mcpu=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard
QMAKE_CXXFLAGS          += $$QMAKE_CFLAGS

# disable lto

QMAKE_CFLAGS_RELEASE += -fno-lto
QMAKE_CXXFLAGS_RELEASE += -fno-lto
QMAKE_LFLAGS_RELEASE += -fno-lto

# modifications to linux.conf
QMAKE_AR                = arm-ostl-linux-gnueabi-ar cqs
QMAKE_OBJCOPY           = arm-ostl-linux-gnueabi-objcopy
QMAKE_NM                = arm-ostl-linux-gnueabi-nm -P
QMAKE_STRIP             = arm-ostl-linux-gnueabi-strip
load(qt_config)
</code></pre>
<hr />
<ul>
<li>接下来开始configure，进入qt5源码文件夹（注意这里进入的是qt5源码解压的文件夹，不是本次hello工程的文件夹），打开控制台，在命令行里输入以下命令，生成编译环境，配置好交叉编译参数。注意，路径需要修改，最后一行skip是我们跳过编译的组件，加回去可能编译很久或者报错。下面是我自己的命令：</li>
</ul>
<pre><code class="language-bash">./configure \
-prefix /home/felix/Qt/5.12.9/arm32-gcc \
-extprefix /home/felix/Qt/5.12.9/arm32-gcc \
-sysroot /opt/st/stm32mp1/3.1-openstlinux-5.4-dunfell-mp1-20-06-24/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi \
-xplatform linux-arm-ostl-g++ \
-opensource -confirm-license \
-release \
-nomake examples -nomake tests \
-skip qtwebengine -skip qt3d -skip qtlocation
</code></pre>
<p>如果是自己的命令行需要修改 <code>-sysroot</code>，<code>-prefix</code>，<code>-extprefix</code>。sysroot 输入过很多遍了，两个prefix就是我们创建好安装qt5工具链的文件夹。</p>
<pre><code class="language-bash">./configure \
-prefix （安装文件夹） \
-extprefix （安装文件夹） \
-sysroot （你的sysroot） \
-xplatform linux-arm-ostl-g++ \
-opensource -confirm-license \
-release \
-nomake examples -nomake tests \
-skip qtwebengine -skip qt3d -skip qtlocation
</code></pre>
<p>这行命令敲下去后，看着他打点打满一行多，没有给出error，就是成功。</p>
<h3>编译，安装，导入库</h3>
<p>编译，安装qt5。参数都在前面设置过了。编译过程很久，大概需要十几二十分钟到半个多小时。</p>
<pre><code class="language-bash">make -j$(nproc)
make install
</code></pre>
<ul>
<li><p>到这时候，如果没有报错，在安装路径下的bin里面就可以找到qmake了。这里我们才实现了装好一个旧版本的qt5，可以兼容STM32MPU的32位架构的旧版qt。</p>
</li>
<li><p><strong>如果最开始，我们发现目标板子中没有Qt5的库</strong>，就把安装完的qt5目录下的/lib之内的库copy到板子的/opt/qt5/lib目录下。</p>
</li>
<li><p>最后收个尾，把配置成gcc-9的gcc切换回最新版本（我的Linux下是gcc-13），重复一遍先前的操作，把命令中的-9全部换掉就好，如下</p>
</li>
</ul>
<pre><code class="language-bash">sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 90 --slave /usr/bin/g++ g++ /usr/bin/g++-13
sudo update-alternatives --set gcc /usr/bin/gcc-13
</code></pre>
<h2>配置 Qt Creator 与工程</h2>
<h3>配置 KIT</h3>
<ul>
<li>进入 Qt Creator，打开设置，在左侧进入**构建套件（kit）**的页面，开始配置。每做完一项记得右上角 apply。</li>
</ul>
<ol>
<li><p>打开“编译器”模块，刷新，先确定<code>arm-ostl-linux-gnueabi-gcc</code>存在；</p>
</li>
<li><p>打开“Qt版本”，刷新，添加我们编译好的qt版本（5开头版本号的，不要弄错了）</p>
</li>
<li><p>打开“构建套件（Kit）”，添加一个，自己命名好：</p>
<ol>
<li><p>选好编译器，qt版本；</p>
</li>
<li><p>填上你的sysroot；</p>
</li>
<li><p>接着在最底下<code>cmake configuration</code>里加上<code>-DCMAKE_C_FLAGS:STRING=-mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7</code> 和 <code>-DCMAKE_CXX_FLAGS:STRING=-mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7</code> 两行内容。 这里没有报error就没问题，因为我们就没有添加debugger。</p>
</li>
</ol>
</li>
</ol>
<h3>搭建工程与验证</h3>
<ul>
<li><p>新建工程的时候，建议在 build system 中选择<strong>兼容qt5的cmake</strong>，一方面这可以支持VSCode，一方面这可以检测qt的工具链是否配置完整。</p>
</li>
<li><p>做好demo，左下角build，然后在工程文件夹中找到与工程同名的可执行文件，传到目标板。</p>
</li>
</ul>
<p>如果一切顺利，那么你就可以亲眼看到屏幕上——</p>
<pre><code class="language-plaintext">Hello Qt @ stm32mp135
</code></pre>
<h2>总结</h2>
<p>感谢kt的代笔，不总结了，累死了，累死了累死了累死了赶紧让老师给我发工资。。。</p>
]]></content:encoded></item><item><title><![CDATA[搭建知睿 STM32MP135 的交叉编译环境]]></title><description><![CDATA[前言
过程思路很简单，核心就是，先配好环境，然后做交叉编译，最后用 U 盘把编译完的文件放进文件导入到板子里面运行，但是作为新手还是稍微记录一下~
环境

主机系统：Win11

虚拟机：VMware® Workstation Pro 25H2u1 25.0.1.25219725

Linux：Ubuntu 24.04.4 LTS

开发板：知睿-MP135-嵌入式学习系统 （STM32MP135]]></description><link>https://xufelix-dailynotes-zh.hashnode.dev/stm32mp135</link><guid isPermaLink="true">https://xufelix-dailynotes-zh.hashnode.dev/stm32mp135</guid><category><![CDATA[embedded]]></category><category><![CDATA[STM32]]></category><category><![CDATA[embedded systems]]></category><dc:creator><![CDATA[Felix Xu]]></dc:creator><pubDate>Wed, 15 Apr 2026 20:04:01 GMT</pubDate><content:encoded><![CDATA[<h2>前言</h2>
<p>过程思路很简单，核心就是，先配好环境，然后做交叉编译，最后用 U 盘把编译完的文件放进文件导入到板子里面运行，但是作为新手还是稍微记录一下~</p>
<h2>环境</h2>
<ul>
<li><p>主机系统：Win11</p>
</li>
<li><p>虚拟机：VMware® Workstation Pro 25H2u1 25.0.1.25219725</p>
</li>
<li><p>Linux：Ubuntu 24.04.4 LTS</p>
</li>
<li><p>开发板：知睿-MP135-嵌入式学习系统 （STM32MP135）</p>
</li>
<li><p>板载系统：Buildroot 2022.02.3</p>
</li>
</ul>
<h2>配置交叉编译器</h2>
<h3>准备工作</h3>
<p>根据《知睿-MP135-嵌入式学习系统---实验指南》，先装上各种依赖库和常用工具</p>
<pre><code class="language-bash">sudo apt update
sudo apt install gawk wget git diffstat unzip texinfo gcc-multilib chrpath socat cpio python3 python3-pip python3-pexpect libssl-dev libgmp-dev libmpc-dev lz4 zstd
sudo apt install build-essential libncurses-dev libyaml-dev libssl-dev
sudo apt install coreutils bsdmainutils sed curl bc lrzsz corkscrew cvs subversion mercurial nfs-common nfs-kernel-server libarchive-zip-perl dos2unix texi2html libxml2-utils
</code></pre>
<p>然后，根据文档：必须安装其他配置才能支持每个 MMC 最多 16 个分区。 默认情况下，在 Linux 系统上，MMC 上最多允许 8 个分区。 所有软件包（入门软件包等）都需要 10 个以上的分区供存储设备使用。为了将每个设备的分区数扩展到 16，必须将以下选项添加到 modprobe：</p>
<pre><code class="language-bash">echo 'options mmc_block perdev_minors=16' &gt; /tmp/mmc_block.conf
sudo mv /tmp/mmc_block.conf /etc/modprobe.d/mmc_block.conf
</code></pre>
<p>然后下载 SDK：<code>en.SDK-x86_64-stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24.tar.xz</code>，在 Linux 系统中解压（注意不要直接在共享文件夹解压）。</p>
<ul>
<li><a href="https://pan.baidu.com/s/1h8-IpEJthIU-vPiZMa5IRA?pwd=b6er">下载SDK</a></li>
</ul>
<h3>安装 SDK</h3>
<p>解压 SDK，然后一路点进去找到脚本：<code>st-image-weston-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-openstlinux-5.4-dunfell-mp1-20-06-24.sh</code>，开权限，运行脚本，设定好你想要放 SDK 的路径——当然就按照他的默认路径也行（记得提前记录一下他的路径），然后等他解压。</p>
<p>安装成功后，进入 SDK 的安装路径，应该会看到这些文件：</p>
<pre><code class="language-plaintext">environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
site-config-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
sysroots
version-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
</code></pre>
<p>之后安装包就没用了，可以删了省空间。</p>
<h3>配置 SDK 环境</h3>
<p>SDK 的路径下，其中一个文件，看开头叫做 environment-setup，这就是初始化环境所需要的文件。在这个文件夹下启动命令行，开始敲命令：</p>
<pre><code class="language-bash">source ./environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
</code></pre>
<p>然后检查一下各个变量，看看有没有成功：</p>
<pre><code class="language-bash">\( echo \)ARCH
arm

\( echo \)CROSS_COMPILE
arm-ostl-linux-gnueabi-

\( echo \)CC
arm-ostl-linux-gnueabi-gcc -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/st/stm32mp1/3.1-openstlinux-5.4-dunfell-mp1-20-06-24/sysroots/cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi

\( \)CC --version
arm-ostl-linux-gnueabi-gcc (GCC) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
</code></pre>
<blockquote>
<p>CC 是什么？是 Cross-Compiler，交叉编译器，记住，后面要考哦（）</p>
</blockquote>
<p>这样，我们在<strong>同一个控制台</strong>中就可以编译文件了。注意，我们每次要启动交叉编译的时候，都要重复这个步骤——这么看，这个流程还是太麻烦了，也许我们可以稍微简化一下。可以用符号链接在自己平时的工作区做一个快捷方式：</p>
<p>语法：</p>
<pre><code class="language-bash">ln -s [link-target] [link-name]
</code></pre>
<p>比如在我的工作区：</p>
<pre><code class="language-bash">ln -s /opt/st/stm32mp1/3.1-openstlinux-5.4-dunfell-mp1-20-06-24  /home/felix/embedded/sdk-base
</code></pre>
<p>这样我们在上一级目录可以直接用符号链接等效地进入前面那一堆乱七八糟的路径。</p>
<p>还可以给那个 setup 脚本做个副本，取一个简单的名字——直接 cp 一下就好了。注意如果不是在 home，要有管理员权限：</p>
<pre><code class="language-bash">sudo cp environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi setup
</code></pre>
<p>然后下次启动 SDK 就简单了，直接用快捷方式+简化，比如从符号链接的上一层开始：</p>
<pre><code class="language-bash">cd sdk-base
source ./setup
</code></pre>
<p>就能够成功启动环境了。</p>
<h3>交叉编译</h3>
<p>然后我们准备一个 C 语言的 demo，比如 hello world，然后启动 SDK，按照 gcc 的语法开始编译：</p>
<pre><code class="language-bash">$CC -g  /your/src/dir/hello.c  -o  /your/target/dir/hello
</code></pre>
<p>控制台没有输出，目标路径生成文件，那就是成功了，至此，SDK 中的交叉编译就已经完成了，我们有了一个可以在板子上运行的二进制可执行文件。</p>
<h2>minicom 配置</h2>
<p>在开启串口前，首先把 brltty 卸载掉，否则看不到我们需要的设备，然后安装 minicom：</p>
<pre><code class="language-bash">sudo apt remove brltty
sudo apt install minicom
</code></pre>
<p>卸载 brltty 之后要重启一下 Linux。然后，我们把有串口的 USB 线从板子引出来，连接电脑，接入虚拟机，紧接着查一下 <code>/dev</code> 路径，看看有没有名称中包含 USB 的设备，检查电脑是否识别到串口：</p>
<pre><code class="language-bash">ls /dev | grep USB
</code></pre>
<p>如果查到了 <code>ttyUSB0</code> （或者其他数字）那就是连上了，这时候就可以管理员启动 minicom，连接板子：</p>
<pre><code class="language-bash">sudo minicom -D /dev/ttyUSB0
</code></pre>
<p>默认的比特率是 115200，我在跑的时候也是115200，不用给参数就能连上，如果要换其他比特率就加上 <code>-b 9600</code>。</p>
<p>如果到这里一切正常，应该就能在串口中与板子的命令行交互了。</p>
<h2>导入文件</h2>
<h3>U盘法</h3>
<p>这个是我最开始尝试成功的。准备一个U盘，确定这个U盘的文件系统是 FAT32（可以在主机上查看属性，如果不是的话去问问 AI 看看对应的兼容性和命令），然后把刚才编译好的可执行文件拷贝进去，然后把U盘插进板子。</p>
<p>如果仔细观察板子的 <code>/dev</code>，能够注意到，插入U盘的时候会出现 sda，sda1 ——这就是U盘对应的设备。接下来把他挂载到系统中。</p>
<blockquote>
<p>注意，接下来的操作都是通过串口在从机上操作的。</p>
</blockquote>
<p>首先，创建一个拿来挂载用的路径：</p>
<pre><code class="language-bash">mkdir -p /mnt/udisk
</code></pre>
<blockquote>
<p><code>-p</code> 的含义自己去查，感觉是挺有用的</p>
</blockquote>
<p>然后把 sda1 挂载到刚才做好的路径下：</p>
<pre><code class="language-bash">mount -t vfat /dev/sda1 /mnt/udisk
</code></pre>
<p>如果没有报错，就是成功了，接下来可以 cd 进挂载好的路径，ls 一下看看能不能读到U盘中的文件。如果一切正常，就可以试着跑一下。在嵌入式平台中，建议不用 <code>exec</code> 启动可执行文件，直接用文件名运行就好，比如</p>
<pre><code class="language-bash">./hello_arm
</code></pre>
<p>如果一切顺利的话——</p>
<img alt="成功！" style="display:block;margin:0 auto" />

<h3>文本发送法</h3>
<p>对于我们用来demo的这种小文件，这个方法会快很多。思路就是，把整个可执行文件变成一大串字符串，通过串口方便地发送到从机，然后再转回文件。</p>
<p>在主机上把编译好的可执行文件转化为 base64 编码，输出到文件中：</p>
<pre><code class="language-bash">base64 hello &gt; hello_base64
</code></pre>
<blockquote>
<p>注意，接下来的操作都是通过串口在从机上操作的。</p>
</blockquote>
<p>然后打开文件，把这堆代码复制一下，接着进入串口，把这堆文字发送到从机上。这段文字包含了换行，因此最好的方法是用 cat：</p>
<pre><code class="language-bash">cat &lt;&lt; EOF &gt;&gt; hello_base64
</code></pre>
<p>这个 EOF 是指定的文件末尾标记，把刚才的那一堆文字复制进来，最后一行写上 EOF，这样刚才的字符串就成功保存到从机上的文件里面了。紧接着，解码，重新生成可执行文件，给权限：</p>
<pre><code class="language-bash">base64 -d hello_base64 &gt; hello
chmod +x hello
</code></pre>
<p>然后，同上一个方法，运行即可，是激动人心的 <code>hello world</code>！</p>
<h3>（补）网络 NFS 法</h3>
<p>这个方法也非常快，但是绕了很久，而且网络协议我不了解，就做个简单的记录。</p>
<p>用网线连接板子和电脑，然后先去 VMWare 的 Edit - Virtual Network Editor，进入编辑，然后把桥接的那一个选项删掉（比如 VMnet0），重新建立，在直连的对象中找到疑似网口的选项，选中保存，然后去虚拟机设置里面也把桥接设置好，当然可以多开一个 NAT 保证联网。</p>
<p>然后去串口中查一下板子的 IP：</p>
<pre><code class="language-bash">ip addr
</code></pre>
<p>然后，进入虚拟机，进入到网络的设置界面。我的 Ubuntu 可以建立多个预设，因此新建一个预设，把 IP 设置成和板子前三位相同的固定值，比如 <code>192.168.1.66</code>，子网掩码照常 <code>255.255.255.0</code>，网关和IP前三个数字相同，最后一位是1，比如 <code>192.168.1.1</code>。</p>
<p>这些设置完成后，插上网线，如果系统显示正常，就可以用从机 ping 一下虚拟机了。</p>
<p>接着，在主机上确定 NFS 共享的文件夹。首先安装 <code>nfs-kernel-server</code>，然后创建好文件夹，开777权限，然后去 <code>/etc/exports</code> 里面加一句 <code>/dir/to/folder *(rw,sync,no_root_squash,no_subtree_check)</code>，具体怎么设置权限自行查命令。</p>
<p>然后加载配置，重启服务，exportfs 应该能查到你刚才的设置项：</p>
<pre><code class="language-bash">sudo exportfs -arv
sudo systemctl restart nfs-kernel-server
</code></pre>
<p>然后，关掉防火墙，如果一股脑全关掉就是 <code>sudo ufw disable</code>，想要细化可以以后再说。</p>
<p>接着，去 <code>/etc/nfs.conf</code> 中解除掉这句注释：</p>
<pre><code class="language-plaintext">[nfsd]
...
vers4=y
</code></pre>
<p>到这步之后，上位机的权限就放开了，进行一轮重启：</p>
<pre><code class="language-bash">sudo systemctl restart rpcbind
sudo systemctl restart rpc-statd
sudo systemctl restart nfs-server
</code></pre>
<p>然后去下位机创建好文件夹，跑命令：</p>
<pre><code class="language-bash">mount -t nfs -o vers=4 192.168.1.66:/home/felix/embedded/board /mnt/nfs
</code></pre>
<p>正常的话，这里应该就可以完成文件夹的共享了。由于我进行了大量的回档，因此这些记录可能不全面，仅做记录。</p>
<h2>总结</h2>
<p>思路很清晰，配置交叉编译环境，与下位机建立通讯，把文件发送过去，能这么轻松，从机上自己的标准化 Linux 环境还是立了大功的。</p>
<p>流程挺长的，但是上板子毕竟还是和在模拟空间里面跑不一样，更有乐趣了，毕竟这样就可以在板子里面大闹天宫了嘛（bushi</p>
]]></content:encoded></item><item><title><![CDATA[记录在 Linux 中搭建 Arm 嵌入式仿真平台]]></title><description><![CDATA[前言
主要是参考了老师给的文档，因为很多细节还需要自己去琢磨琢磨——既然自己琢磨完了就记录一下叭，万一班上有同学需要呢（）
环境

主机系统：Win11；

虚拟机：VMware® Workstation Pro 25H2u1 25.0.1.25219725

Linux：Ubuntu 24.04.4 LTS


正文
准备环境
下载 Linux 源码
https://cdn.kernel.org]]></description><link>https://xufelix-dailynotes-zh.hashnode.dev/linux-arm</link><guid isPermaLink="true">https://xufelix-dailynotes-zh.hashnode.dev/linux-arm</guid><category><![CDATA[Linux]]></category><category><![CDATA[linux for beginners]]></category><category><![CDATA[ARM]]></category><category><![CDATA[embedded]]></category><dc:creator><![CDATA[Felix Xu]]></dc:creator><pubDate>Wed, 25 Mar 2026 19:08:11 GMT</pubDate><content:encoded><![CDATA[<h2>前言</h2>
<p>主要是参考了老师给的文档，因为很多细节还需要自己去琢磨琢磨——既然自己琢磨完了就记录一下叭，万一班上有同学需要呢（）</p>
<h2>环境</h2>
<ul>
<li><p>主机系统：Win11；</p>
</li>
<li><p>虚拟机：VMware® Workstation Pro 25H2u1 25.0.1.25219725</p>
</li>
<li><p>Linux：Ubuntu 24.04.4 LTS</p>
</li>
</ul>
<h2>正文</h2>
<h3>准备环境</h3>
<h4>下载 Linux 源码</h4>
<p><a href="https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.17.tar.gz">https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.19.17.tar.gz</a></p>
<p>这个大版本更新到2022为止，这个小版本就其中是最新的，感觉像是靠谱的样子（）</p>
<h4>在命令行中下载QEMU软件包</h4>
<pre><code class="language-shell">sudo apt install qemu-system-arm
</code></pre>
<p>可以检查一下QEMU的安装情况和支持的芯片：</p>
<pre><code class="language-shell">qemu-system-arm -M help
</code></pre>
<h4>安装编译器以及依赖项</h4>
<p>总之就是缺什么装什么</p>
<pre><code class="language-shell">sudo apt install build-essential gcc-arm-linux-gnueabi gcc-aarch64-linux-gnu systemtap flex bison build-essential libssl-dev libelf-dev
</code></pre>
<h4>下载 Busybox 源码</h4>
<p>官网不知道为什么一直进不去，这里我从github下载了压缩包，<strong>注意要去找到 stable 的 branch 下载，不然编译会报错</strong>，在 Linux 环境下解压，最终成功的是 1_36_stable 的 branch。</p>
<h3>编译系统</h3>
<p>把刚才下载好的 linux-5.19.9.tar.gz 解压，然后在<strong>解压目录下启动命令行（和 MakeFile同一级）</strong>，开始编译。首先生成 <code>.config</code> 文件：</p>
<pre><code class="language-bash">make vexpress_defconfig ARCH=arm O=./object
</code></pre>
<p>然后文档中给出了这么一句，似乎是准图形化的详细安装配置，我反正看不懂，没去动：</p>
<pre><code class="language-bash">make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig -j4 O=./object
</code></pre>
<p>然后正式开始编译：</p>
<pre><code class="language-bash">make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4 O=./object
</code></pre>
<p>等个几分钟，就编译完了。</p>
<h3>编译安装 Busybox</h3>
<ul>
<li>注意，代码仓库中，<code>tc</code>的代码是有问题的，会挂编译，我们需要去编译菜单中把<code>tc</code>关掉。</li>
</ul>
<p>进入 Busybox 的工作路径，开始 CMake：</p>
<pre><code class="language-bash">make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
</code></pre>
<p>在菜单中找到 Networking Utilities，进去，找到 <code>tc</code>，按空格把这个选项删掉，然后退出，保存，然后开始编译：</p>
<pre><code class="language-bash">make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
</code></pre>
<p>然后构建 install 目录：</p>
<pre><code class="language-bash">make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
</code></pre>
<p>最后，把 _install 目录下的文件 copy 到 Linux 根目录下的 rootfs 文件夹中。这里原本是没有 rootfs 文件夹的，要新建路径。</p>
<h3>搭建系统架构</h3>
<p>回到刚刚编译完的 Linux 根目录下的工作路径，开始配置：</p>
<h4>拷贝编译器的加载器和动态库</h4>
<pre><code class="language-bash">mkdir rootfs/lib
cp /usr/arm-linux-gnueabi/lib/* rootfs/lib/ -rfp
</code></pre>
<h4>创建设备文件</h4>
<p>接下来，切换工作目录，进入到 ./rootfs/dev 中，开始创建各种设备：</p>
<pre><code class="language-bash">mkdir ./rootfs/dev
cd ./rootfs/dev
sudo mknod -m 666 tty1 c 4 1
sudo mknod -m 666 tty2 c 4 2
sudo mknod -m 666 tty3 c 4 3
sudo mknod -m 666 tty4 c 4 4
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
</code></pre>
<h4>制作 SD 卡文件系统镜像</h4>
<p>创建镜像，格式化：</p>
<pre><code class="language-bash">dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
mkfs.ext3 rootfs.ext3
</code></pre>
<p>接下来，把这个 rootfs.ext3 挂载到主机 Linux 下，就像新的 SD 卡插入了电脑一样。这里你可以自己选择要挂载到哪里，后续挂到工作区也不是不行，但是注意要预先创建好路径：</p>
<pre><code class="language-bash">sudo mkdir /mnt/SD_A9
sudo mount -t ext3 rootfs.ext3 /mnt/SD_A9 -o loop
</code></pre>
<p>然后把 rootfs 文件夹下的内容复制进 SD 卡，复制完后就弹出。注意到，我们要拷贝整个 rootfs 文件下的文件，因此工作路径要回到小 Linux 的根目录下，所以开头先来两次 <code>cd ..</code>：</p>
<pre><code class="language-bash">cd ..
cd ..
sudo cp -rf rootfs/* /mnt/SD_A9
sudo umount /mnt/SD_A9
</code></pre>
<h3>验证！</h3>
<p>在 Linux 镜像的根目录下，启动 QEMU：</p>
<pre><code class="language-bash">qemu-system-arm -M vexpress-a9 \
-m 512M \
-kernel ./object/arch/arm/boot/zImage \
-dtb ./object/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd ./rootfs/dev/rootfs.ext3
</code></pre>
<p>如果一切顺利的话——</p>
<img src="https://cdn.hashnode.com/uploads/covers/6911ef250ab232d1197e0a44/5b855d43-140d-46cf-9874-4c8b06fad180.png" alt="Hello World!" style="display:block;margin:0 auto" />

<h2>总结</h2>
<p>大胆猜想，严谨求证，谨慎操作，这一趟装机下来能学到不少东西哇！</p>
]]></content:encoded></item><item><title><![CDATA[TI Driverlib 标准输出完整重定向的改进方案]]></title><description><![CDATA[MCU：MSPM0G3507
前段时间在课内做实验的时候碰到了比较丰富的交互需求，遂打开UART，#include "stdio.h"，然后开始重定向。虽然网上有广为流传的重定向方案，但是常年玩STM32的我有点迷惑：为什么TI Driverlib的重定向需要定义三个函数呢？
Trial
按照 STM32 重定向的方法，先对fputc进行重定向：
int fputc(int _c, FILE *_fp) {
    while((UART0 -> STAT & UART_STAT_TXFF_MA...]]></description><link>https://xufelix-dailynotes-zh.hashnode.dev/ti-driverlib-stdo-redirect</link><guid isPermaLink="true">https://xufelix-dailynotes-zh.hashnode.dev/ti-driverlib-stdo-redirect</guid><category><![CDATA[embedded]]></category><category><![CDATA[mcu]]></category><category><![CDATA[C]]></category><dc:creator><![CDATA[Felix Xu]]></dc:creator><pubDate>Mon, 10 Nov 2025 16:07:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762789248242/0a97aafa-1a3b-42ea-aae4-c6c0c13f26e3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>MCU：MSPM0G3507</strong></p>
<p>前段时间在课内做实验的时候碰到了比较丰富的交互需求，遂打开UART，<code>#include "stdio.h"</code>，然后开始重定向。虽然网上有广为流传的重定向方案，但是常年玩STM32的我有点迷惑：为什么TI Driverlib的重定向需要定义三个函数呢？</p>
<h2 id="heading-trial">Trial</h2>
<p>按照 STM32 重定向的方法，先对<code>fputc</code>进行重定向：</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">fputc</span><span class="hljs-params">(<span class="hljs-keyword">int</span> _c, FILE *_fp)</span> </span>{
    <span class="hljs-keyword">while</span>((UART0 -&gt; STAT &amp; UART_STAT_TXFF_MASK));
    UART0 -&gt; TXDATA = _c;
    <span class="hljs-keyword">return</span> _c;
}
</code></pre>
<p>观察到，在这种重定向的方案下，<code>printf</code>函数可以输出常字符串，但是无法进行变量的格式化输出。</p>
<p>根据网络上的方案，补充<code>fputs</code>和<code>puts</code>函数，稍作修改：</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">fputs</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *<span class="hljs-keyword">restrict</span> s, FILE *<span class="hljs-keyword">restrict</span> stream)</span> </span>{
    <span class="hljs-keyword">uint16_t</span> i, len;
    len = <span class="hljs-built_in">strlen</span>(s);
    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; len; i++) {
        fputc(s[i], stream);
    }
    <span class="hljs-keyword">return</span> len;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">puts</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *s)</span> </span>{
    <span class="hljs-keyword">int</span> count = <span class="hljs-built_in">fputs</span>(s, <span class="hljs-built_in">stdout</span>);
    count += <span class="hljs-built_in">fputs</span>(<span class="hljs-string">"\n"</span>, <span class="hljs-built_in">stdout</span>);
    <span class="hljs-keyword">return</span> count;
}
</code></pre>
<p>在这种重定向方法下，<code>printf</code>成功实现了完整的重定向，可以进行变量的格式化输出——但是，<code>sprintf</code>依然无法工作，为什么呢？</p>
<p>观察三个函数的输入参数，其中两个都包含了一个<code>FILE*</code>输入变量，但是我们在使用的时候却完全没用到。找到<code>FILE</code>的定义：</p>
<pre><code class="lang-c"><span class="hljs-class"><span class="hljs-keyword">struct</span> __<span class="hljs-title">sFILE</span> {</span>
    <span class="hljs-keyword">int</span> fd;                    <span class="hljs-comment">/* File descriptor */</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>* buf;        <span class="hljs-comment">/* Pointer to start of buffer */</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>* pos;        <span class="hljs-comment">/* Position in buffer */</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>* bufend;     <span class="hljs-comment">/* Pointer to end of buffer */</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>* buff_stop;  <span class="hljs-comment">/* Pointer to last read char in buffer */</span>
    <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>   flags;      <span class="hljs-comment">/* File status flags (see below) */</span>
};

<span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> __<span class="hljs-title">sFILE</span> <span class="hljs-title">FILE</span>;</span>
</code></pre>
<p>可见，在TI的库中，<strong>FILE类型并没有被简单地改为简单的存储指针，而是依然保留了“数据流”的形式</strong>。再结合debug中断点的触发情况，以及函数之间的调用关系，尝试对<code>FILE*</code>指针进行写入。若调用了<code>puts</code>，认为上层的标准输出走的是<code>printf()</code>，就向下传递空指针，将输出导向 UART。如果<code>stream</code>不是自己设定的空指针，就去编辑<code>stream</code>指向的缓冲区。</p>
<h2 id="heading-result">Result</h2>
<p>对重定向的三个函数进行如下修改：</p>
<pre><code class="lang-c"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">fputc</span><span class="hljs-params">(<span class="hljs-keyword">int</span> _c, FILE *_fp)</span> </span>{
    <span class="hljs-keyword">if</span>(!(_fp)) {
        <span class="hljs-keyword">while</span>((UART0 -&gt; STAT &amp; UART_STAT_TXFF_MASK));
        UART0 -&gt; TXDATA = _c;
    }
    <span class="hljs-keyword">else</span>
        *(_fp-&gt;pos) = _c;
    <span class="hljs-keyword">return</span> _c;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">fputs</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span>* <span class="hljs-keyword">restrict</span> s, FILE* <span class="hljs-keyword">restrict</span> stream)</span> </span>{
    <span class="hljs-keyword">uint16_t</span> i, len;
    len = <span class="hljs-built_in">strlen</span>(s);
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>; i &lt; len; i++) {
        fputc(s[i], stream);
        <span class="hljs-keyword">if</span>(stream) stream-&gt;pos++;
    }
    <span class="hljs-keyword">return</span> len;
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">puts</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *_ptr)</span> </span>{
    <span class="hljs-keyword">int</span> count = <span class="hljs-built_in">fputs</span>(_ptr,<span class="hljs-literal">NULL</span>);
    count += <span class="hljs-built_in">fputs</span>(<span class="hljs-string">"\n"</span>,<span class="hljs-literal">NULL</span>);
    <span class="hljs-keyword">return</span> count;
}
</code></pre>
<p>重定向成功，<code>sprintf</code>和<code>printf</code>均可以正常工作！</p>
<h2 id="heading-more">More…</h2>
<p>那个结构体我还没用完，估计在重定向输入流的时候会用到更多的元素。但是知道这些已经足够了，可以搞点花招，比如把<code>UARTx → TXDATA</code>直接丢到stream里面去，当然FIFO只有一个入口，不需要地址偏移，这么看也是有点麻烦；或者直接把自己的指定<code>buffer</code>设为默认输出区域，等等，虽然更复杂了，但是相对于仅仅把<code>FILE</code>作为一个独立指针，还是更加灵活有趣的！</p>
<p>实力尚浅，还请多多指教！</p>
]]></content:encoded></item></channel></rss>