嵌入式Arm Linux实战 (一) - 交叉编译 + LVGL + Modbus通讯
本文介绍一个实战案例 —— 通过Windows交叉编译链,在Linux开发板上实现通过触摸屏和设备进行Modbus通讯
一. 交叉编译
什么是交叉编译
交叉编译是指在一种计算机平台(称为宿主机,如x86 PC)上,编译生成能在另一种不同架构或操作系统平台(称为目标机,如ARM设备)上直接运行的可执行代码的过程。它主要解决目标平台因资源有限、无操作系统或无法安装编译器而无法进行本地编译的问题,在嵌入式系统和移动应用开发中至关重要。
交叉编译工具下载
本文使用的是运行在Windows平台下的嵌入式Linux交叉编译工具,可以在Arm官网找到需要的工具链:
https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads
笔者下载的是13.3版本的arm-none-linux-gnueabihf工具包,其中最重要的exe文件在bin目录下,可以加入环境变量。
编译环境搭建
笔者用的是CMake + Ninja作为编译工具链,下载Windows环境下的CMake和Ninja工具链并加入环境变量。
编译时需要的include文件夹,可以从sdk里把开发板系统里的include文件夹拷贝出来,同理lib文件夹也可以拷贝出来,注意lib里的软连接没法直接拷贝,亲测可以用7zip压缩,在windows上以管理员权限解压缩。
把这些开发板的依赖全部放到sysroot文件夹,形成一个依赖路径:
--sysroot
|--include
|--lib
|--usr
|--include
|--lib
这里/usr路径某些情况下编译还是会指定,按需把include和lib下的路径拷贝到/usr/下即可(在开发板嵌入式Linux上,/usr/lib是/lib的软连接)
二. Modbus通讯库 —— libmodbus的编译
modbus通讯可以自己写,笔者用了libmodbus这个库,由于开发版sdk不具备这个动态库,所以需要自己编译。
交叉编译 libmodbus 库步骤
针对你的开发环境(从日志可知使用 arm-none-linux-gnueabihf 交叉工具链),以下是交叉编译 libmodbus 的详细步骤:
1. 下载 libmodbus 源码
从官网或 GitHub 获取源码:
wget https://libmodbus.org/releases/libmodbus-3.1.10.tar.gz # 最新稳定版
tar -zxvf libmodbus-3.1.10.tar.gz
cd libmodbus-3.1.10
2. 安装autoconf
libmodbus的编译依赖autoreconf,笔者用WSL安装并编译,教程如下:
autoreconf 属于 autoconf 工具包,同时生成 configure 脚本还需要 automake、libtool 等配套工具,只需通过 WSL 的包管理器一键安装:
1. 更新包列表(以 Ubuntu/Debian 系 WSL 为例)
sudo apt update && sudo apt upgrade -y
2. 安装 Autotools 全套工具
sudo apt install -y autoconf automake libtool make gcc g++ pkg-config
autoconf:提供autoreconf命令,用于生成configure脚本;automake:配合autoconf处理Makefile.am,生成Makefile.in;libtool:处理动态库/静态库的编译;gcc/g++:基础编译器(后续交叉编译也需要);pkg-config:辅助查找库依赖。
3. 验证安装成功
autoreconf --version
若输出类似 autoreconf (GNU Autoconf) 2.71 的版本信息,说明安装完成。
2. 配置交叉编译参数
创建编译目录并运行 configure 脚本,指定交叉工具链和安装路径:
# 创建输出目录(避免污染源码)
mkdir build && cd build
# 配置交叉编译(关键步骤)
../configure \
--host=arm-none-linux-gnueabihf \ # 目标架构(与你的工具链匹配)
--prefix=/path/to/install \ # 安装路径(自定义,如 ./output)
--enable-static \ # 生成静态库(可选)
--enable-shared \ # 生成动态库
CC=arm-none-linux-gnueabihf-gcc \ # 交叉编译器
AR=arm-none-linux-gnueabihf-ar \ # 交叉编译归档工具
LD=arm-none-linux-gnueabihf-ld # 交叉编译链接器
- 若提示
configure: error: cannot guess build type; you must specify one,需补充--build=x86_64-pc-linux-gnu(根据你的主机架构调整)。
3. 编译并安装
# 编译(-j 后面的数字为并行任务数,根据CPU核心数调整)
make -j4
# 安装到指定目录
make install
4. 安装依赖
编译成功后,/path/to/install 目录下会生成:
include/:头文件(modbus.h等)lib/:库文件(libmodbus.so动态库、libmodbus.a静态库)
把include文件夹下的头文件拷贝到windows下的/sysroot/include下,库文件拷贝到/sysroot/lib下;
同时,拷贝头文件和库文件各1份到开发板中的/usr/include以及/usr/lib下
常见问题解决
-
** configure: error: C compiler cannot create executables**
检查交叉工具链是否正确安装,CC变量是否指向正确的arm-none-linux-gnueabihf-gcc。 -
** 运行时提示 "error while loading shared libraries: libmodbus.so"**
确保开发板上已放置libmodbus.so,并通过export LD_LIBRARY_PATH=/usr/lib指定库路径。 -
** 静态链接**
若希望静态链接(避免开发板依赖动态库),编译项目时使用-static -lmodbus链接选项。
三. 项目实战
做完以上步骤后,下面就可以开始我们的开发,注意我们的开发板已经包含了定制过的lvgl(版本8.4)和其依赖,如果没有,还需要源码编译和安装。本项目用CMake+Ninja作为编译工具链
文件路径
--modbus_master //项目名称
|--lvgl8 //开发板配套的lvgl实用函数
|--lv_port_disp.c/.h
|--lv_port_indev.c/.h
|--lv_port_init.c.h
|--modbus //调用libmodbus的依赖
|--modbus-rtu.h
|--modbus-tcp.h
|--modbus.h
|--CMakeLists.txt
|--armlinux.cmake
|--main.c
|--main.h
|--modbus_comm.c
|--modbus_comm.h
lvgl8中比较重要的封装是lv_port_init,对lvgl的初始化进行了一次封装,关键代码如下:
/* 0, 90, 180, 270 */
static int g_indev_rotation = 0;
/* 0, 90, 180, 270 */
static int g_disp_rotation = 0;
void lv_port_init(void)
{
lv_init();
lv_port_disp_init(0, 0, g_disp_rotation);
lv_port_indev_init(g_indev_rotation);
}
这里封装了屏幕和触摸板的初始化,仅需要控制g_*参数就可以指定屏幕旋转方向
开发板的lvgl不使用lvgl内置的定时器而是自己封装了一个定时器,从而我们写的lvgl代码不再需要自定义定时器,相关重要配置写在lvgl/lv_conf.h中
#define LV_TICK_CUSTOM 1
#define LV_TICK_CUSTOM_INCLUDE "src/misc/lv_systick.h"
下面开始展示我们的代码:
main.h: 公共依赖
#ifndef __MAIN_H__
#define __MAIN_H__
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <malloc.h>
#include <math.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <lvgl/lvgl.h>
#include "lv_port_init.h"
#include "timestamp.h"
#define ALIGN(x, a) (((x) + (a - 1)) & ~(a - 1))
#endif
modbus_comm.h: 包含modbus功能封装的头文件
#ifndef MODBUS_COMM_H
#define MODBUS_COMM_H
#include "modbus/modbus.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
// Modbus从站配置(可根据需求修改)
#define MODBUS_SLAVE_IP "192.168.2.211"
#define MODBUS_SLAVE_PORT 502
#define REGISTER_ADDR 0 // 保持寄存器地址
#define REGISTER_NUM 1 // 读取寄存器数量
// 通讯状态枚举
typedef enum {
COMM_STATUS_DISCONNECTED = 0, // 断开
COMM_STATUS_CONNECTED, // 连接正常
COMM_STATUS_ERROR // 错误
} comm_status_t;
// Modbus句柄结构体(线程安全)
typedef struct {
modbus_t *ctx; // Modbus上下文
comm_status_t status; // 通讯状态
int reg_value; // 寄存器当前值
pthread_mutex_t lock; // 互斥锁(保护reg_value)
pthread_t comm_thread; // 通讯线程
bool thread_running; // 线程运行标志
} modbus_handler_t;
/**
* @brief 初始化Modbus句柄
* @param handler Modbus句柄指针
* @return 0-成功,-1-失败
*/
int modbus_comm_init(modbus_handler_t *handler);
/**
* @brief 启动Modbus通讯线程(自动连接+读取寄存器)
* @param handler Modbus句柄指针
* @return 0-成功,-1-失败
*/
int modbus_comm_start(modbus_handler_t *handler);
/**
* @brief 停止Modbus通讯线程
* @param handler Modbus句柄指针
*/
void modbus_comm_stop(modbus_handler_t *handler);
/**
* @brief 读取当前寄存器值(线程安全)
* @param handler Modbus句柄指针
* @param value 输出:寄存器值
* @return 0-成功,-1-失败
*/
int modbus_comm_read_value(modbus_handler_t *handler, int *value);
/**
* @brief 写入值到寄存器(线程安全)
* @param handler Modbus句柄指针
* @param value 要写入的数值
* @return 0-成功,-1-失败
*/
int modbus_comm_write_value(modbus_handler_t *handler, int value);
/**
* @brief 获取当前通讯状态
* @param handler Modbus句柄指针
* @return 通讯状态(comm_status_t)
*/
comm_status_t modbus_comm_get_status(modbus_handler_t *handler);
#endif // MODBUS_COMM_H
modbus_comm.c: modbus逻辑实现
#include "modbus_comm.h"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
// Modbus通讯线程函数(自动重连+读取寄存器)
static void *modbus_comm_thread_func(void *arg) {
modbus_handler_t *handler = (modbus_handler_t *)arg;
uint16_t reg_buf[REGISTER_NUM] = {0};
while (handler->thread_running) {
// 1. 检查连接状态,断开则尝试重连
if (handler->ctx == NULL) {
handler->status = COMM_STATUS_DISCONNECTED;
// 创建Modbus TCP上下文
handler->ctx = modbus_new_tcp(MODBUS_SLAVE_IP, MODBUS_SLAVE_PORT);
if (handler->ctx == NULL) {
fprintf(stderr, "Modbus: 创建上下文失败: %s\n", modbus_strerror(errno));
usleep(1000);
continue;
}
// 设置超时(1秒)
modbus_set_response_timeout(handler->ctx, 1, 0);
// 尝试连接从站
if (modbus_connect(handler->ctx) == -1) {
fprintf(stderr, "Modbus: 连接失败(%s:%d): %s\n",
MODBUS_SLAVE_IP, MODBUS_SLAVE_PORT, modbus_strerror(errno));
modbus_free(handler->ctx);
handler->ctx = NULL;
usleep(1000);
continue;
}
fprintf(stdout, "Modbus: 连接成功(%s:%d)\n", MODBUS_SLAVE_IP, MODBUS_SLAVE_PORT);
handler->status = COMM_STATUS_CONNECTED;
}
// 2. 读取保持寄存器
pthread_mutex_lock(&handler->lock);
int ret = modbus_read_registers(handler->ctx, REGISTER_ADDR, REGISTER_NUM, reg_buf);
// fprintf(stdout, "读取寄存器返回:%d\n", ret);
if (ret == REGISTER_NUM) {
// 读取成功,更新寄存器值
fprintf(stdout, "读取寄存器值:%d\n", reg_buf[0]);
handler->reg_value = reg_buf[0];
handler->status = COMM_STATUS_CONNECTED;
} else {
// 读取失败,断开连接
fprintf(stderr, "Modbus: 读取寄存器失败: %s\n", modbus_strerror(errno));
modbus_close(handler->ctx);
modbus_free(handler->ctx);
handler->ctx = NULL;
handler->status = COMM_STATUS_DISCONNECTED;
}
pthread_mutex_unlock(&handler->lock);
// 3. 根据通讯状态控制LED
// if (handler->status == COMM_STATUS_CONNECTED) {
// led_set_brightness(1); // 连接正常:LED亮
// } else {
// led_set_brightness(0); // 连接断开:LED灭
// }
// 4. 1秒读取一次
usleep(1000 * 1000);
}
return NULL;
}
int modbus_comm_init(modbus_handler_t *handler) {
if (handler == NULL) return -1;
// 初始化句柄默认值
handler->ctx = NULL;
handler->status = COMM_STATUS_DISCONNECTED;
handler->reg_value = 0;
handler->thread_running = false;
// 初始化互斥锁
if (pthread_mutex_init(&handler->lock, NULL) != 0) {
fprintf(stderr, "Modbus: 互斥锁初始化失败\n");
return -1;
}
return 0;
}
int modbus_comm_start(modbus_handler_t *handler) {
if (handler == NULL) return -1;
handler->thread_running = true;
// 创建通讯线程
if (pthread_create(&handler->comm_thread, NULL, modbus_comm_thread_func, handler) != 0) {
fprintf(stderr, "Modbus: 创建线程失败\n");
handler->thread_running = false;
return -1;
}
return 0;
}
void modbus_comm_stop(modbus_handler_t *handler) {
if (handler == NULL) return;
// 停止线程
handler->thread_running = false;
pthread_join(handler->comm_thread, NULL);
// 释放Modbus资源
if (handler->ctx != NULL) {
modbus_close(handler->ctx);
modbus_free(handler->ctx);
handler->ctx = NULL;
}
// 销毁互斥锁
pthread_mutex_destroy(&handler->lock);
}
int modbus_comm_read_value(modbus_handler_t *handler, int *value) {
if (handler == NULL || value == NULL) return -1;
pthread_mutex_lock(&handler->lock);
*value = handler->reg_value;
pthread_mutex_unlock(&handler->lock);
return 0;
}
int modbus_comm_write_value(modbus_handler_t *handler, int value) {
if (handler == NULL || handler->ctx == NULL) return -1;
pthread_mutex_lock(&handler->lock);
// 写入保持寄存器
int ret = modbus_write_register(handler->ctx, REGISTER_ADDR, (uint16_t)value);
if (ret == 1) {
// 写入成功,更新本地缓存
handler->reg_value = value;
pthread_mutex_unlock(&handler->lock);
return 0;
} else {
fprintf(stderr, "Modbus: 写入寄存器失败: %s\n", modbus_strerror(errno));
// 写入失败,断开连接(触发重连)
modbus_close(handler->ctx);
modbus_free(handler->ctx);
handler->ctx = NULL;
handler->status = COMM_STATUS_DISCONNECTED;
pthread_mutex_unlock(&handler->lock);
return -1;
}
}
comm_status_t modbus_comm_get_status(modbus_handler_t *handler) {
if (handler == NULL) return COMM_STATUS_ERROR;
return handler->status;
}
main.c:主程序实现,包括UI
#include "lvgl8/lv_port_init.h"
#include "modbus_comm.h"
#include "lvgl/lvgl.h"
#include "main.h"
#define FREETYPE_FONT_FILE ("/usr/share/fonts/AlibabaPuHuiTi-2-105-Heavy.otf")
// 全局变量
static modbus_handler_t modbus_handler;
static lv_obj_t *num_label; // 数字显示框
lv_ft_info_t ft_info;
static int quit = 0;
static void sigterm_handler(int sig) {
fprintf(stderr, "signal %d\n", sig);
quit = 1;
}
// -------------------------- UI事件回调 --------------------------
// 加按钮回调
static void btn_inc_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED) {
int curr_val = 0;
if (modbus_comm_read_value(&modbus_handler, &curr_val) == 0) {
// 写入新值(+1)
modbus_comm_write_value(&modbus_handler, curr_val + 1);
// 更新UI显示
char buf[16] = {0};
snprintf(buf, sizeof(buf), "%d", curr_val + 1);
lv_label_set_text(num_label, buf);
}
}
}
// 减按钮回调
static void btn_dec_cb(lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED) {
int curr_val = 0;
if (modbus_comm_read_value(&modbus_handler, &curr_val) == 0) {
// 写入新值(-1)
modbus_comm_write_value(&modbus_handler, curr_val - 1);
// 更新UI显示
char buf[16] = {0};
snprintf(buf, sizeof(buf), "%d", curr_val - 1);
lv_label_set_text(num_label, buf);
}
}
}
// LVGL定时器:定时更新数字显示(500ms)
static void ui_update_timer_cb(lv_timer_t *timer) {
(void)timer;
int curr_val = 0;
if (modbus_comm_read_value(&modbus_handler, &curr_val) == 0) {
char buf[16] = {0};
snprintf(buf, sizeof(buf), "%d", curr_val);
lv_label_set_text(num_label, buf);
}
}
// -------------------------- 创建UI界面 --------------------------
static void create_ui(void) {
// 1. 主容器(全屏)
lv_obj_t *main_cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(main_cont, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
lv_obj_center(main_cont);
lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(main_cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(main_cont, 20, 0);
// 2. 标题
lv_obj_t *title = lv_label_create(main_cont);
lv_label_set_text(title, "Modbus TCP 控制器");
lv_obj_set_style_text_font(title, ft_info.font, 0);
lv_obj_set_style_pad_bottom(title, 30, 0);
// 3. 数字显示框
num_label = lv_label_create(main_cont);
lv_label_set_text(num_label, "0");
lv_obj_set_style_text_font(num_label, &lv_font_montserrat_32, 0);
lv_obj_set_style_pad_bottom(num_label, 30, 0);
// 4. 按钮容器
lv_obj_t *btn_cont = lv_obj_create(main_cont);
lv_obj_set_size(btn_cont, lv_disp_get_hor_res(NULL) - 100, 80);
lv_obj_set_flex_flow(btn_cont, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(btn_cont, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
// 5. 减按钮
lv_obj_t *btn_dec = lv_btn_create(btn_cont);
lv_obj_set_size(btn_dec, 100, 60);
lv_obj_add_event_cb(btn_dec, btn_dec_cb, LV_EVENT_ALL, NULL);
lv_obj_t *dec_label = lv_label_create(btn_dec);
lv_label_set_text(dec_label, "-");
lv_obj_set_style_text_font(dec_label, &lv_font_montserrat_32, 0);
lv_obj_center(dec_label);
// 6. 加按钮
lv_obj_t *btn_inc = lv_btn_create(btn_cont);
lv_obj_set_size(btn_inc, 100, 60);
lv_obj_add_event_cb(btn_inc, btn_inc_cb, LV_EVENT_ALL, NULL);
lv_obj_t *inc_label = lv_label_create(btn_inc);
lv_label_set_text(inc_label, "+");
lv_obj_set_style_text_font(inc_label, &lv_font_montserrat_32, 0);
lv_obj_center(inc_label);
// 7. 创建定时更新定时器(500ms)
lv_timer_create(ui_update_timer_cb, 500, NULL);
}
// -------------------------- 主函数 --------------------------
int main(int argc, char *argv[]) {
signal(SIGINT, sigterm_handler);
// 一切LVGL应用的开始,必须加上这个初始化
lv_port_init();
/*****************************用户程序开始*************************************/
ft_info.name = FREETYPE_FONT_FILE; // 使用绝对路径指定字体文件
ft_info.weight = 50; // 字体大小
ft_info.style = FT_FONT_STYLE_NORMAL;
ft_info.mem = NULL;
system("echo none > /sys/class/leds/work/trigger");
// 初始化字体
if (!lv_ft_font_init(&ft_info)) {
printf("create failed.");
}
// 2. 初始化Modbus通讯
if (modbus_comm_init(&modbus_handler) != 0) {
fprintf(stderr, "Modbus初始化失败\n");
return -1;
}
// 3. 启动Modbus通讯线程
if (modbus_comm_start(&modbus_handler) != 0) {
fprintf(stderr, "Modbus线程启动失败\n");
return -1;
}
// 4. 创建UI界面
create_ui();
// 5. 主循环(阻塞)
while (1) {
lv_task_handler();
usleep(1000);
}
// 6. 清理资源(实际不会执行到)
modbus_comm_stop(&modbus_handler);
return 0;
}
工具链文件armlinux.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_C_COMPILER_WORKS TRUE CACHE BOOL "" FORCE)
set(CMAKE_CXX_COMPILER_WORKS TRUE CACHE BOOL "" FORCE)
set(CMAKE_CXX_COMPILER "path/to/arm-none-linux-gnueabihf-g++.exe")
set(CMAKE_C_COMPILER "path/to/arm-none-linux-gnueabihf-gcc.exe")
set(CMAKE_LINKER "path/to/arm-none-linux-gnueabihf-ld.exe")
set(CMAKE_AR "path/to/arm-none-linux-gnueabihf-ar.exe")
set(CMAKE_NM "path/to/arm-none-linux-gnueabihf-nm.exe")
set(CMAKE_SYSROOT "path/to/sysroot")
set(CMAKE_C_FLAGS "--sysroot=${CMAKE_SYSROOT} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SYSROOT}/lib/libc_nonshared.a --sysroot=${CMAKE_SYSROOT} -L ${CMAKE_SYSROOT}/usr/lib=${CMAKE_SYSROOT}/lib -L ${CMAKE_SYSROOT}/lib -lc -lm -lpthread -static-libgcc" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
set(CMAKE_EXE_LINK_DYNAMIC_C_FLAGS "")
set(CMAKE_LINK_DEF_FILE_FLAG "")
set(CMAKE_EXECUTABLE_SUFFIX "")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(Modbus_Master C) # 明确指定仅编译C语言,避免默认C++带来的额外依赖
include_directories(
${CMAKE_SYSROOT}/include/
${CMAKE_SYSROOT}/include/lvgl
${CMAKE_SYSROOT}/include/lvgl/lv_drivers
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/lvgl8
${PROJECT_SOURCE_DIR}/common
${PROJECT_SOURCE_DIR}/modbus
${PROJECT_SOURCE_DIR}/sys
)
add_definitions(-DUSE_RK3506=1)
add_definitions(-DUSE_DRM=1 -DUSE_EVDEV=1)
aux_source_directory(${PROJECT_SOURCE_DIR}/lvgl8 SRCS)
aux_source_directory(${PROJECT_SOURCE_DIR}/sys SRCS)
aux_source_directory(${PROJECT_SOURCE_DIR}/common SRCS)
aux_source_directory(${PROJECT_SOURCE_DIR}/modbus SRCS)
aux_source_directory(. SRCS)
add_executable(${PROJECT_NAME} ${SRCS})
target_link_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SYSROOT}/lib ${CMAKE_SYSROOT}/usr/lib=${CMAKE_SYSROOT}/lib)
target_link_libraries(${PROJECT_NAME}
${CMAKE_SYSROOT}/lib/libc_nonshared.a
lvgl lv_drivers freetype drm evdev modbus
)
编译步骤:
mkdir build
cd build
cmake .. -G"Ninja" --toolchain ..\armlinux.cmake
ninja
编译后生成可执行文件modbus_master,拷贝到开发板上便可运行,可以对modbus保持寄存器地址0进行数据读取和写入

浙公网安备 33010602011771号