eefocus_3891719 发表于 2024-11-30 23:55:07

【Avnet | NXP FRDM-MCXN947试用活动】测评8-GUI控制电机

本帖最后由 eefocus_3891719 于 2024-12-1 00:07 编辑

# 背景

终于来到了最后一个任务,设计一个基于LVGL的GUI应用,可以在UI上拖动滑动条控制电机转速,点击按键切换点击旋转方向。

GUI 设计采用 NXP 出品的 Gui Guider 工具,我电脑上安装的版本是 v1.8.0 版本。

# 界面设计

打开 Gui Guider 工具,当前 v1.8.0 只支持 lvgl v8.3.10 版本,然后选择模拟器,屏幕分辨率为 320x240。

## 1. Gui Guider 界面设计

创建工程时没有截图,在打开工程后以在下方状态栏找到 **Project** 控件点击展开,工程属性如下图所示:

!(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235025nnqp0hc2h8n0fnaa.png)

设计的最终界面如下图所示,屏幕最上方是标题 **MCXN947 Motor Control**,中间是一个 NXP 图片,再往下是3个按键,分别控制电机左转、停止、右转,最下方是两个文字标签和一个活动条,左右文字标签分别在电机左转和右转的时候单独显示。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235056mfeyxiia0q4241uq.png)

我最早接触 Gui Guider 是 v1.3.1 版本,那个时候 Gui Guider 软件界面和当前版本有挺大差异的。当前软件版本的设计界面如上图所示:

1. UI编辑区,点击左边的星星图标展开或者收缩控件列表;以前的版本是固定在左侧的;
2. 界面差异很大的是 Event区,以前放在上图(4)的位置,和 **Attributes** 各自成一个 Tab,如今挪到下方的状态栏,一开始还找不到,令人着急;
3. 此外添加 Event 的方式也大有改善,可以给一个事件添加多个目标,参见下图。

## 2. 设置 Event

当前版本添加 Event 的方式很优秀,可以给一个对象添加多个事件,每个事件可以作用于多个目标控件,每个目标控件可以修改多个属性。

下图演示了 **Right** 按键添加 **clicked** 时间,作用于3个目标控件,并且添加了一个自定义代码,详细的描述是:

1. **Right** 按键点击时,标签 **label_speed_left** 不可见;
2. **Right** 按键点击时,标签 **label_speed_right** 变成可见状态;
3. **Right** 按键点击时,滑动条 **slider_speed** 添加了一个标志,即 **LV_OBJ_FLAG_CLICKABLE** ,即可以点击(我的代码逻辑是 Stop 按键单击后不允许滑动条拖动,再点击 Left 或者 Right 按键之后才可以拖动);

!(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235136h2twq9v28tu2uwg6.png)

虽说 Gui Guider 方便了 LVGL UI 设计,但是也有一些不足之处,我的使用经验如下:

1. 部分控件支持的属性不够丰富,例如给目标 slider 控件设置 value 值,居然找不到;
2. 添加 custom_code,代码自动补全功能不够完整,令**疼;
3. 添加了目标控件之后不能改变顺序,这一点还是有一些必要的,因为 custom_code 依赖一些控件状态,对代码顺序是由要求的;
4. 不能从外部修改 **generated** 目录下的文件,因为每次生成都会被 Gui Guider 生成的文件覆盖掉,这个还有一些缺陷,毕竟在编辑器里写代码要比在 Gui Guider 里写代码速度快;
   
   !(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235225u532ht4ftk354524.png)

## 3. 模拟器运行

在 UI 设计完毕,现在电脑运行模拟器,如下图点击菜单栏上的编译按键,选择 **C** 语言版本,编译通过之后弹出模拟器界面。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235300vbbbbuzrb7mbm7mh.png)

编译通过,运行模拟器。

!(https://www.eefocus.com/forum/data/attachment/forum/202411/30/235340cnr28f3fd785rpy7.png)

# 代码编写

上面说明 Event 添加方式不够灵活,还是需要手动修改代码。

我设计的GUI有以下逻辑:

1. 界面初始化之后,滑动条数值为0不允许滑动,且左右两个速度标签是隐藏的,只有当电机左转或者右转时才单独显示一个对应的标签;
2. 只有点击 **Left** 或者 **Right** 才能激活滑条,然后拖动滑条控制电机转动并调速;
3. 点击 **Stop** 让滑条归零,两个速度标签隐藏,并且电机停转。

## 1. 初始化界面

初始化界面的代码放在 `custom.c` 文件中,代码如下,添加了两个变量,分别表示电机转速和电机转动方向:

```c
/**********************
*STATIC VARIABLES
**********************/
uint32_t speed_value = 0;
uint32_t direction = DIR_LEFT;

/**
* Create a demo application
*/

void custom_init(lv_ui *ui)
{
        /* Add your codes here */

        // 滑条不允许滑动
        lv_obj_clear_flag(ui->pageStatic_slider_speed, LV_OBJ_FLAG_CLICKABLE);

        // 两个速度标签不显示
        lv_obj_add_flag(ui->pageStatic_label_speed_left, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(ui->pageStatic_label_speed_right, LV_OBJ_FLAG_HIDDEN);
}
```

## 2. 按键事件

三个按键和一个滑条的事件都放在 `events_init.c` 文件中。

### 1. 滑条滑动事件

首先获取滑条位置转换为电机速度,保存在变量 **speed_value** 中;然后根据电机方向变量 **direction** 的值,分别控制电机左转或右转,并分别显示左右标签页显示电机速度。

```c
static void pageStatic_slider_speed_event_handler(lv_event_t* e)
{
        lv_event_code_t code = lv_event_get_code(e);
        switch (code) {
                case LV_EVENT_VALUE_CHANGED:
                {
                        speed_value = lv_slider_get_value(guider_ui.pageStatic_slider_speed);

                        switch (direction) {
                                case DIR_LEFT:
                                lv_label_set_text_fmt(guider_ui.pageStatic_label_speed_left, "%d%%", speed_value);
                                motor_left(speed_value);
                                break;

                                case DIR_RIGHT:
                                lv_label_set_text_fmt(guider_ui.pageStatic_label_speed_right, "%d%%", speed_value);
                                motor_right(speed_value);
                                break;

                                default:
                                break;
                        }
                        break;
                }
                default:
                break;
        }
}
```

### 2. left 按键事件

1. 单击 **Left** 按键,首先取消左侧的速度标签隐藏属性,即让其可见;
2. 让右侧的速度标签页隐藏;
3. 让滑条可以单击;
4. 改变速度方向变量为 **DIR_LEFT** 方向;
5. 设置滑条数值为 **speed_value** 数值;
6. 让左侧速度标签页显示速度;
7. 调用 `motor_left(speed_value)` 函数让电机向左转动并设置速度;

```c
static void pageStatic_btn_left_event_handler(lv_event_t* e)
{
        lv_event_code_t code = lv_event_get_code(e);
        switch (code) {
                case LV_EVENT_CLICKED:
                {
                        lv_obj_clear_flag(guider_ui.pageStatic_label_speed_left, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_add_flag(guider_ui.pageStatic_label_speed_right, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_add_flag(guider_ui.pageStatic_slider_speed, LV_OBJ_FLAG_CLICKABLE);

                        direction = DIR_LEFT;
                        lv_slider_set_value(guider_ui.pageStatic_slider_speed, speed_value, LV_ANIM_ON);
                        lv_label_set_text_fmt(guider_ui.pageStatic_label_speed_left, "%d%%", speed_value);

                        motor_left(speed_value);
                        break;
                }
                default:
                break;
        }
}
```

### 3. right 按键事件

同 **Left** 按键事件,逻辑是反的,最后调用 `motor_right()` 函数让电机向右转动并设置转速。

```c
static void pageStatic_btn_right_event_handler(lv_event_t* e)
{
        lv_event_code_t code = lv_event_get_code(e);
        switch (code) {
                case LV_EVENT_CLICKED:
                {
                        lv_obj_add_flag(guider_ui.pageStatic_label_speed_left, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_clear_flag(guider_ui.pageStatic_label_speed_right, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_add_flag(guider_ui.pageStatic_slider_speed, LV_OBJ_FLAG_CLICKABLE);
                        direction = DIR_RIGHT;
                        lv_slider_set_value(guider_ui.pageStatic_slider_speed, speed_value, LV_ANIM_ON);
                        lv_label_set_text_fmt(guider_ui.pageStatic_label_speed_right, "%d%%", speed_value);

                        motor_right(speed_value);
                        break;
                }
                default:
                break;
        }
}
```

### 4. stop 按键

让两个速度标签都隐藏,设置速度变量为0,并设置滑条数值为0,且不允许拖动滑条,最后调用 `motor_stop()` 让电机停止转动。

```c
static void pageStatic_btn_stop_event_handler(lv_event_t* e)
{
        lv_event_code_t code = lv_event_get_code(e);
        switch (code) {
                case LV_EVENT_CLICKED:
                {
                        lv_obj_add_flag(guider_ui.pageStatic_label_speed_left, LV_OBJ_FLAG_HIDDEN);
                        lv_obj_add_flag(guider_ui.pageStatic_label_speed_right, LV_OBJ_FLAG_HIDDEN);

                        speed_value = 0;
                        lv_slider_set_value(guider_ui.pageStatic_slider_speed, speed_value, LV_ANIM_ON);

                        lv_obj_clear_flag(guider_ui.pageStatic_slider_speed, LV_OBJ_FLAG_CLICKABLE);

                        motor_stop();
                        break;
                }
                default:
                break;
        }
}
```

# 视频演示

参见B站视频:
(https://www.bilibili.com/video/BV1MnzHY7EBs/?vd_source=8f2bbf56b70c541bec2ea0b9f102ebee)

页: [1]
查看完整版本: 【Avnet | NXP FRDM-MCXN947试用活动】测评8-GUI控制电机