用 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 集成
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐