Buildroot SDK:让嵌入式交叉编译,不再为 库依赖 发愁
用 Buildroot 生成 SDK,里面的 sysroot 就是开发板 文件系统的"ARM版副本",虚拟机交叉编译时指向它,就能引用和开发板上完全相同的库文件。
make sdk 会生成两样关键东西:
1. 完整的交叉编译工具链:arm-linux-gnueabihf-* 全套编译器、链接器。
2. 独立的sysroot 目录:里面完整复刻了开发板 /lib /usr/lib /usr/include,所有在 buildroot 里勾选的库的ARM版库文件+头文件 全部在这里。在虚拟机编译时,通过 --sysroot 指定这个目录,编译器就会自动去这里找依赖,和开发板的运行环境完全一致,不会出现架构、库版本不匹配的问题。
第一步:
生成 SDK:
在 Buildroot 根目录,执行 make sdk ,等待生成sdk压缩包

进入目录查看 cd output/images/

第二步:
创建 SDK 目录 (在普通用户的家目录里创建,不用sudo,好移植):
mkdir -p /home/leo/sdk/buildroot-sdk
解压 SDK:
tar -xf arm-buildroot-linux-gnueabihf_sdk-buildroot.tar.gz -C /home/leo/sdk/buildroot-sdk


查看编译器:
arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux-gnueabihf-gcc --version

查看 sysroot 路径,是不是刚才创建的路径:
arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-linux-gnueabihf-gcc -print-sysroot

sysroot -> 告诉交叉编译器:"别看本机的库(x86),去这个目录找 ARM 版本的库和头
文件"。
|
项目 |
状态 |
|
SDK 路径 |
✅ /home/leo/sdk/buildroot-sdk/ |
|
编译器 |
✅ GCC 4.9.4 |
|
sysroot |
✅ 自动正确 |
第三步, 配置 VSCode:
|
纯 VSCode |
VSCode + CMake |
|
|
配置方式 |
.vscode/c_cpp_properties.json |
CMakeLists.txt + 工具链文件 |
|
编译方式 |
手写 tasks.json 调 gcc |
CMake 自动管理 |
|
代码提示 |
✅ 有 |
✅ 更强 |
|
多文件项目 |
手动写编译命令 |
自动处理依赖 |
|
库依赖 |
手动 -lx264 -lavcodec... |
pkg-config 自动查找 |
|
团队协作 |
每个人配不一样 |
统一 CMakeLists.txt |
|
适合项目 |
单文件测试 |
多文件/多库项目 |
我选择用 VSCode + CMake
/home/leo/sdk/
├── buildroot-sdk/ ← 这里存放 SDK 本体
│ └── arm-buildroot-linux-gnueabihf_sdk-buildroot/
└── cmake-toolchains/ ← 这里存放 CMake 工具链文件
└── buildroot-arm.cmake
创建 目录和工具链文件:

下面是 buildroot-arm.cmake 工具链文件内容( BR_SDK 路径,根据自己的路径修改,复制粘贴进去,保存)
# Buildroot SDK 工具链文件, 根据自己的路径修改
set(BR_SDK /home/leo/sdk/buildroot-sdk/arm-buildroot-linux-gnueabihf_sdk-buildroot)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 编译器
set(CMAKE_C_COMPILER ${BR_SDK}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${BR_SDK}/bin/arm-linux-gnueabihf-g++)
# sysroot
set(CMAKE_SYSROOT ${BR_SDK}/arm-buildroot-linux-gnueabihf/sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
# 只在 sysroot 中搜索
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# CMakeLists.txt 如果用到 find_package + pkg_check_modules 时,需要下面的 pkg-config
set(PKG_CONFIG_EXECUTABLE ${BR_SDK}/bin/pkg-config)
set(PKG_CONFIG_PATH ${CMAKE_SYSROOT}/usr/lib/pkgconfig)
下面是 CMakeLists.txt 文件内容:
cmake_minimum_required (VERSION 3.5)
project (mqtt C CXX)
# 生成编译数据库(帮助编辑器理解代码)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# C 标准
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# C++ 标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 警告
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
# 根据构建类型 自动选择 优化级别,C C++ -g:生成调试信息
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g")
endif()
# 头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 源文件: 同时找 .c 和 .cpp
set (source_dir "${PROJECT_SOURCE_DIR}/src/")
file(GLOB c_sources "${source_dir}/*.c")
file (GLOB cpp_sources "${source_dir}/*.cpp")
add_executable(${PROJECT_NAME} ${c_sources} ${cpp_sources})
# x86 和 ARM 各自链接对应库
if(CMAKE_CROSSCOMPILING)
# ARM 交叉编译时,自动链接 buildroot-sdk 里的 mosquitto ,直接写库名
target_link_libraries(${PROJECT_NAME} mosquitto)
else()
# x86:用宿主系统库
# target_link_libraries(${PROJECT_NAME} )
endif()
# 如果用 find_package + pkg_check_modules 这种方式才需要 pkg-config
# find_package(PkgConfig)
# pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat)
# target_link_libraries(${PROJECT_NAME} ${FFMPEG_LIBRARIES})
# 打印编译器信息
message(STATUS "C Compiler: ${CMAKE_C_COMPILER}")
message(STATUS "CXX Compiler: ${CMAKE_CXX_COMPILER}")
message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
arm 编译脚本: build-arm.sh(用工具链文件,更简洁)
引用工具链文件 -> buildroot-arm.cmake
/home/leo/sdk/cmake-toolchains/buildroot-arm.cmake 根据自己的路径修改
#!/bin/bash
# ARM架构 构建目录
BUILD_DIR=build_arm
# 清理并构建
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
# 使用 ARM 交叉编译器, 模式: Debug/Release
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=/home/leo/sdk/cmake-toolchains/buildroot-arm.cmake \
-DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# 发布时,去掉调试信息(ARM 版本)
#arm-linux-gnueabihf-strip mqtt
x86 编译脚本:build-x86.sh
用系统编译器,不引用工具链文件
#!/bin/bash
# x86架构 构建目录
BUILD_DIR=build_x86
# 清理并构建
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
# 使用系统x86编译器, 模式: Debug/Release
cmake .. \
-DCMAKE_C_COMPILER=/usr/bin/gcc \
-DCMAKE_CXX_COMPILER=/usr/bin/g++ \
-DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
VSCode -> .vscode/settings.json:
{
"cmake.configureOnOpen": false,
"cmake.buildDirectory": "${workspaceFolder}/build_x86",
"cmake.sourceDirectory": "${workspaceFolder}"
}

-------------------------------------------------------------------------------------------------------------------------------
下面,编译测试:

编译 imx6ull 开发板 要用的MQTT 程序
查询 mosquitto.h 路径

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
//#include <mosquitto.h>
#include </home/leo/sdk/buildroot-sdk/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/mosquitto.h>
#define BROKER_ADDRESS "192.168.137.1" // 电脑IP
#define BROKER_PORT 1883
/* MQTT 服务器靠这个 ID 区分不同的设备,
如果两个相同的 ID 同时连接服务器,服务器会把前一个踢下线 */
#define CLIENTID "imx6ull_client"
#define WILL_TOPIC "dt_mqtt/will"
#define LED_TOPIC "dt_mqtt/led"
#define TEMP_TOPIC "dt_mqtt/temperature"
volatile sig_atomic_t stop = 0;
void handle_sigint(int sig) {
stop = 1;
}
void on_message(struct mosquitto *mosq, void *userdata,
const struct mosquitto_message *msg)
{
// 防御性编程:防止空指针和空载荷
if (msg->topic == NULL || msg->payload == NULL || msg->payloadlen == 0)
return;
// 将 payload 转换为字符串并确保以 '\0' 结尾
char payload_str[8] = {0};
strncpy(payload_str, (char *)msg->payload, sizeof(payload_str) - 1);
if (!strcmp(msg->topic, LED_TOPIC)) {
int fd;
if (!strcmp("2", payload_str)) { // 心跳模式
// 如果报错,检查自己开发板的trigger路径
fd = open("/sys/class/leds/red/trigger", O_WRONLY);
if (fd >= 0) {
write(fd, "heartbeat\n", 10); // 直接写入 heartbeat
close(fd);
}
}
else if (!strcmp("1", payload_str)) {
// 开灯 (先关闭触发模式,再设置亮度)
fd = open("/sys/class/leds/red/trigger", O_WRONLY);
if (fd >= 0) {
write(fd, "none\n", 5);
close(fd);
}
fd = open("/sys/class/leds/red/brightness", O_WRONLY);
if (fd >= 0) {
write(fd, "1\n", 2);
close(fd);
}
}
else if (!strcmp("0", payload_str)) {
// 关灯
fd = open("/sys/class/leds/red/trigger", O_WRONLY);
if (fd >= 0) {
write(fd, "none\n", 5);
close(fd);
}
fd = open("/sys/class/leds/red/brightness", O_WRONLY);
if (fd >= 0) {
write(fd, "0\n", 2);
close(fd);
}
}
}
}
void on_connect(struct mosquitto *mosq, void *userdata, int rc)
{
if (rc == 0) {
printf("MQTT 服务器连接成功!\n");
/* 发布上线消息:
NULL 表示我不关心这条消息的发送结果追踪,(通常 QoS=0 时填 NULL)
WILL_TOPIC:目标主题是WILL_TOPIC
"Online":消息内容
0:QoS(服务质量) , true:Retain(保留消息) */
mosquitto_publish(mosq, NULL, WILL_TOPIC, strlen("Online"), "Online", 0, true);
/* 订阅 LED 主题, NULL 表示不追踪, 0:订阅的 QoS 级别 */
mosquitto_subscribe(mosq, NULL, LED_TOPIC, 0);
printf("已订阅 %s\n", LED_TOPIC);
} else {
printf("连接失败, 错误码: %d\n", rc);
}
}
int main()
{
int i;
struct mosquitto *mosq;
/* 捕获 Ctrl+C 信号 */
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
/* 初始化 MQTT 库的运行环境 */
mosquitto_lib_init();
/* 创建一个 MQTT 客户端实例(对象),
true:表示这是一个全新的干净连接,服务器不保存该设备之前的订阅和离线消息,断线即丢
false:表示这是一个持久会话,断线后服务器会帮你暂存消息,重连后还能收到消息 */
mosq = mosquitto_new(CLIENTID, true, NULL);
if (!mosq) {
fprintf(stderr, "无法创建MQTT客户端\n");
return 1;
}
/* 注册“连接成功”的回调函数 */
mosquitto_connect_callback_set(mosq, on_connect);
/* 注册“收到消息”时的回调函数 */
mosquitto_message_callback_set(mosq, on_message);
/* 设置遗嘱消息:
WILL_TOPIC: 遗嘱要发到 WILL_TOPIC 主题
遗嘱内容的长度, 遗嘱的具体内容(载荷)
0:QoS(服务质量), true:Retain(保留消息)服务器会保留这条死讯 */
mosquitto_will_set(mosq, WILL_TOPIC, strlen("Unexpected disconnection"),
"Unexpected disconnection", 0, true);
// 设置用户名密码(Mosquitto 当前是匿名,这两行可以注释掉)
// mosquitto_username_pw_set(mosq, "mqtt1", "123456");
/* 向服务器发起连接请求, 30:Keep Alive(心跳时间),单位是秒
返回 0:表示底层网络握手请求成功发出(注意,只是拨号成功,不代表服务器验证通过)
真正的连接成功与否,是上面注册的 on_connect 回调函数来通知的 */
if (mosquitto_connect(mosq, BROKER_ADDRESS, BROKER_PORT, 30)) {
printf("无法连接服务器\n");
mosquitto_destroy(mosq); // 销毁
mosquitto_lib_cleanup(); // 清理资源
return 1;
}
/* 在后台启动一个新线程,专门负责处理所有的网络通信,
调用这个函数后,libmosquitto 库会调用操作系统的 pthread 接口,创建一个新的线程 */
mosquitto_loop_start(mosq);
// 每 10 秒发布温度
while (!stop) {
char temp_str[10] = {0};
int fd = -1;
fd = open("/sys/class/thermal/thermal_zone0/temp", O_RDONLY);
if (fd >= 0) {
ssize_t bytes_read = read(fd, temp_str, sizeof(temp_str) - 1);//预留一位给 \0
close(fd);
if (bytes_read > 0) {
// 去除末尾的换行符 \n,避免 printf 打印时多出一个空行
if (temp_str[bytes_read - 1] == '\n') {
temp_str[bytes_read - 1] = '\0';
}
mosquitto_publish(mosq, NULL, TEMP_TOPIC, strlen(temp_str),
temp_str, 0, true);
printf("发布温度: %s\n", temp_str);
} else {
perror("读取温度失败");
}
}
// 用循环代替纯 sleep,可以更快的响应 Ctrl+C 退出信号
for (i = 0; i < 10 && !stop; i++) {
sleep(1);
}
}
printf("\n正在断开连接...\n");
mosquitto_loop_stop(mosq, true); // 停止后台线程
mosquitto_destroy(mosq); // 销毁
mosquitto_lib_cleanup(); // 清理资源
return 0;
}
编译成功,见下图

移植到开发板
![]()
开启 PC端的 MQTT 服务器,运行 开发板上的 程序,

成功了。

这一套配置下来: C / C++ 程序,可以 x86 / ARM 双架构编译。
✅ Buildroot SDK 交叉编译环境 ✅ CMake 工具链文件 ✅ build-arm.sh / build-x86.sh 双架构编译 ✅ CMakeLists.txt 通用配置(自动区分 ARM/x86) ✅ VSCode + CMake 集成
更多推荐


所有评论(0)