项目介绍
该项目参加 贸泽电子的2025贸泽电子M-Design创意设计竞赛,旨在设计一款基于coremp135的智能手表,以满足用户对于便捷、多功能可穿戴设备的需求。coremp135是一款基于STM32MP135DAE7芯片的一体化Linux工控主机,具备高性能处理器和丰富的外设接口,为智能手表的设计提供了强大的硬件支持。
Coremp135
M5Stack CoreMP135 是一款基于 STM32MP135DAE7 芯片的高性能 Linux 工控主机,具备单核 ARM Cortex-A7 处理器、4Gb DDR3L 内存以及丰富的接口(如千兆网口、USB 接口、CAN FD 接口等),还自带 2.0 寸 IPS 触摸屏和扬声器,支持低功耗运行和多种供电方式。它适用于工业自动化、智能家居、多媒体娱乐、物联网网关等多种场景,同时提供完善的开发框架和示例代码,方便用户快速开发和部署。
HMI
M5Stack HMI模块是一款便携式人机交互设备,配备旋转编码器、按键和LED指示灯,通过I2C与M5主机通信,支持Arduino和UIFlow开发。它内置500mAh锂电池,具备PORT.B和PORT.C扩展接口,适合工业控制、智能家居等场景,方便用户进行参数设置和设备监控。
方案框图和项目设计思路
该框图展示了基于coremp135的智能手表设计项目中底板部分和主控部分的分工与协作。底板部分的进程负责使能底板、读取寄存器数据并进行数据结算,然后通过管道将指令传输给主控部分的进程。主控部分的进程包含两个线程,其中一个线程负责提供lvgl功能的心跳,确保lvgl的正常运行;另一个线程通过管道读取底板进程传输的数据,并根据读取到的命令向lvgl手动发送指令,lvgl接收到指令后完成相应的动作。整个流程体现了项目中数据处理、传输和显示控制的逻辑架构。
软件流程图和关键代码介绍
底板读取进程
这个进程是一个通过I2C接口读取设备寄存器数据,并将处理后的数据通过FIFO文件写入的程序。以下是详细的描述:
- 初始化I2C设备:
- 程序首先打开指定路径的I2C设备文件,并设置设备的地址。这是为了能够与特定的I2C设备进行通信。
- 创建线程:
- 程序创建一个线程,该线程负责周期性地读取I2C设备寄存器中的数据。线程会不断循环,每次读取数据后会短暂休眠,以减少资源占用。
- 读取I2C寄存器数据:
- 在线程中,程序会向I2C设备写入寄存器地址,然后从该寄存器读取数据。读取的数据被存储到一个全局变量中,供主线程使用。
- 创建FIFO文件:
- 主线程检查并创建一个FIFO文件,用于进程间通信。如果FIFO文件已存在,程序会先删除它,然后重新创建,以确保通信的正确性。
- 打开FIFO文件:
- 主线程以写模式打开FIFO文件,准备将数据写入。
- 循环写入数据到FIFO:
- 主线程进入循环,根据之前读取的I2C数据计算出要写入FIFO的值。如果读取的数据有效,程序会将计算出的值写入FIFO文件,供其他进程读取和处理。
- 结束进程:
- 当程序结束时,主线程会关闭FIFO文件,并删除FIFO文件,以清理资源。
这个进程的主要功能是作为数据采集和传输的中间件,从I2C设备获取数据并将其传递给其他通过FIFO文件通信的进程。
soc主控进程
流程图说明:
- 配置模拟器:
- 解析命令行参数和环境变量,配置模拟器的初始设置,如窗口大小、全屏模式等。
- 初始化LVGL:
- 调用
lv_init()
函数初始化LVGL库,为后续的图形界面操作做准备。
- 调用
- 初始化显示设备:
- 根据配置初始化不同的显示后端,如Linux帧缓冲设备、SDL、Wayland等,设置显示旋转角度,并初始化输入设备(如触摸屏或鼠标)。
- 创建演示:
- 加载和初始化演示界面或用户自定义界面。
- 打开FIFO文件(读模式):
- 以只读模式打开FIFO文件,准备读取其他进程写入的数据。
- 创建LVGL运行线程:
- 创建一个线程专门用于运行LVGL的任务处理循环。
- LVGL运行循环:
- 在线程中不断处理LVGL的任务,包括界面更新、事件处理等,确保图形界面的流畅运行。
- 创建call_timer线程:
- 创建另一个线程用于从FIFO文件读取数据,并根据读取的数据模拟屏幕切换操作。
- call_timer线程:循环读取FIFO数据:
- 在线程中循环读取FIFO文件中的数据,每次读取后根据数据内容执行相应的操作。
- call_timer线程:模拟屏幕切换:
- 根据从FIFO读取的命令数据,调用模拟屏幕切换的函数,实现界面的切换效果。
这个流程图展示了程序从初始化到运行的整个过程,包括多线程的创建和协作,以及通过FIFO文件进行进程间通信的机制。
关键代码
以下是这个进程中的关键代码段及其解释:
1. 配置模拟器
static void configure_simulator(int argc, char **argv) {
int opt = 0;
bool err = false;
/* Default values */
fullscreen = maximize = false;
window_width = atoi(getenv("LV_SIM_WINDOW_WIDTH") ? : "800");
window_height = atoi(getenv("LV_SIM_WINDOW_HEIGHT") ? : "480");
/* Parse the command-line options. */
while ((opt = getopt(argc, argv, "fmw:h:")) != -1) {
switch (opt) {
case 'f':
fullscreen = true;
if (LV_USE_WAYLAND == 0) {
fprintf(stderr, "The SDL driver doesn't support fullscreen mode on start\n");
exit(1);
}
break;
case 'm':
maximize = true;
if (LV_USE_WAYLAND == 0) {
fprintf(stderr, "The SDL driver doesn't support maximized mode on start\n");
exit(1);
}
break;
case 'w':
window_width = atoi(optarg);
break;
case 'h':
window_height = atoi(optarg);
break;
case ':':
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
exit(1);
case '?':
fprintf(stderr, "Unknown option -%c.\n", optopt);
exit(1);
}
}
}
这段代码用于解析命令行参数和环境变量,配置模拟器的初始设置,如窗口大小、全屏模式等。
2. 初始化LVGL
lv_init();
初始化LVGL库,为后续的图形界面操作做准备。
3. 初始化显示设备
static void lv_linux_disp_init(void) {
const char *device = getenv_default("LV_LINUX_FBDEV_DEVICE", "/dev/fb1");
lv_display_t * disp = lv_linux_fbdev_create();
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90);
#if LV_USE_EVDEV
lv_linux_init_input_pointer(disp);
#endif
lv_linux_fbdev_set_file(disp, device);
}
根据配置初始化Linux帧缓冲设备,设置显示旋转角度,并初始化输入设备(如触摸屏或鼠标)。
4. 创建演示
ui_init();
加载和初始化演示界面或用户自定义界面。
5. 打开FIFO文件(读模式)
fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("open FIFO");
exit(1);
}
以只读模式打开FIFO文件,准备读取其他进程写入的数据。
6. 创建LVGL运行线程
pthread_t lvgl_thread;
if (pthread_create(&lvgl_thread, NULL, (void*)lv_linux_run_loop, NULL) != 0) {
perror("pthread_create");
exit(1);
}
创建一个线程专门用于运行LVGL的任务处理循环。
7. LVGL运行循环
void lv_linux_run_loop(void) {
uint32_t idle_time;
while(1) {
idle_time = lv_timer_handler();
usleep(idle_time * 1000);
}
}
在线程中不断处理LVGL的任务,包括界面更新、事件处理等,确保图形界面的流畅运行。
8. 创建call_timer线程
pthread_t call_timer_thread;
if (pthread_create(&call_timer_thread, NULL, (void*)call_timer, NULL) != 0) {
perror("pthread_create");
exit(1);
}
创建另一个线程用于从FIFO文件读取数据,并根据读取的数据模拟屏幕切换操作。
9. call_timer线程:循环读取FIFO数据
void call_timer() {
ssize_t bytes_read;
int dir = 0;
int last_dir = 0;
while(1) {
bytes_read = read(fd, &dir, sizeof(dir));
if (bytes_read > 0) {
last_dir = dir;
LV_LOG_USER("read data is %d", last_dir);
simulate_left_swipe(last_dir);
}
}
}
在线程中循环读取FIFO文件中的数据,每次读取后根据数据内容执行相应的操作。
10. call_timer线程:模拟屏幕切换
void simulate_left_swipe(int e_code) {
lv_obj_t * active_screen = lv_scr_act();
lv_obj_send_event(active_screen, e_code, NULL);
}
根据从FIFO读取的命令数据,调用模拟屏幕切换的函数,实现界面的切换效果。
这些关键代码段展示了程序的主要功能和逻辑流程,包括初始化、多线程创建、FIFO文件操作以及屏幕切换模拟等。
功能展示图及说明
COREMP135板卡介绍
COREMP135 是一款基于 STM32MP135DAE7 芯片的工业控制主机,搭载单核 ARM Cortex-A7 处理器,配备 4Gb DDR3L 内存,预装 Debian 系统。它具备丰富的接口,包括 2 个千兆以太网接口、2 个 USB 2.0 接口、1 个 USB-C 接口、2 个 CAN FD 接口、1 个 PWR485 接口和 2 个 Grove 接口,支持高清视频输出和音频播放,还配备 2.0 英寸 IPS 触摸屏。它支持多种供电方式,内置 RTC 和电源管理芯片,可实现低功耗运行和定时唤醒功能,广泛应用于工业自动化、智能家居、物联网等领域。
其紧凑的尺寸(81.9×54.0×39.5mm)和轻巧的重量(98.3g)使其便于安装和部署,底部的 DIN 导轨底座设计进一步提升了其在工业环境中的实用性。
HMI
M5Stack 的 HMI 模块是一款用于人机交互操作的模块,具备拨轮旋转编码器、两个输入按键和两个 LED 指示灯,采用 STM32F030 作为采集及通信 MCU,通过 I2C 与 M5 主机通信。该模块还配备了 PORT.B 和 PORT.C 接口,并内置 500mAh 锂电池,适用于手持操作交互场景。它支持 Arduino 和 UIFlow 编程平台,广泛应用于工业控制、嵌入式系统和智能家居等领域。
板卡可以显示时钟
可以拨打电话(模拟)
可以播放音乐(模拟)
可以查看天气
可以设置闹钟
设计中遇到的难题和解决方法
底板i2c无响应
根据HMI 的 I2C 协议,设备地址 是 0x41,我需要读他的 0x10的寄存器。但是实际使用的时候 设备地址 0x40没有响应。
解决措施
查看原理图,发现主控和底板间由 电路隔离
bus输出的使能由 PI3引脚控制
在进程中将PI3 引脚拉高,重新读取 0x41地址的数据,发现读取成功。
# cd /sys/class/gpio/
# echo 131 > export
# ls
PI3 gpiochip0 gpiochip128 gpiochip32 gpiochip64 gpiochip96
export gpiochip112 gpiochip16 gpiochip48 gpiochip80 unexport
# cd PI3/
# ls
active_low direction power uevent
device edge subsystem value
# pwd
/sys/class/gpio/PI3
# echo "out" > direction
# echo "1" > value
# i2cdetect -y 2
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- 41 -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
lvgl手动发送数据 遇到段错误
由于缺少资料,在网上搜到的lvgl的 对外接口,手动发送数据一直是
但是实际使用的时候会发生段错误。
[User] (767.230, +448) main: lv_linux_run_loop main.c:313
[User] (767.231, +1) lv_linux_run_loop: run loop here 1 main.c:151
[User] (767.264, +33) simulate_left_swipe: simulate start
main.c:107
Segmentation fault
解决措施
重新阅读代码,确认lvgl 9 的 手动事件触发函数应该是
lv_obj_send_event(active_screen, e_code, NULL);
心得体会
参与基于coremp135的智能手表设计项目让我在嵌入式开发和智能穿戴设备设计方面取得了显著进步。我深入理解了硬件选型与软件架构设计的重要性,掌握了I2C通信、多线程编程和进程间通信等关键技术,并学会了在资源受限的环境中利用lvgl库打造用户友好的图形界面。目前项目中的功能虽为模拟,但已搭建起良好框架,未来可拓展健康监测、运动追踪、智能通知和移动支付等实用功能,使其真正成为人们生活中的得力助手。