本帖最后由 用户1729505484947 于 2025-2-19 01:35 编辑

1. 任务分析

    根据课题1要求,需要实现3个主要任务:

    1. 实现带GUI的终端设备

    2. 实现蓝牙BLE一主多从连接

    3. 通过GUI控制连接的设备

2. 具体实现

    2.1 实现带GUI的终端设备

    FR3068x SDK包里的"lvgl_demo"已经包含了多个GUI示例,"控制面板"选项对应的界面刚好符合控制终端的场景。因此,在lvgl_demo的基础上修改就可以达到目的。由于FR3068E-C DEV1.3开发板没有触摸屏,无法通过触控的方式来触发设备控制,因此,只能通过按键来进行控制设备切换和开关控制。

image.png

    修改内容:

    1. 上电后,开机动画运行完成后直接跳转到"控制面板"界面。如上图。

        在 fr_lv_test_page.c 中,修改以下:

void logo_timer_cb(lv_timer_t* t)
  • {
  •         static uint8_t index = 0;
  •         lv_obj_t * obj = (lv_obj_t *)t->user_data;

  •         if (!lv_obj_is_valid(obj))
  •                 return;

  •         lock_timer_flag = 1;
  •         lv_img_set_src(lv_obj_get_child(obj,0), start_logo_src[index][0]);
  •         lv_img_set_src(lv_obj_get_child(obj,1), start_logo_src[index][1]);

  •         if (++index > 57)
  •         {
  •                 index = 0;
  •                 lock_timer_flag = 0;
  •                 lv_timer_del(t);
  •                 // user_jump_page(fr_list_page);
  •                 user_jump_page(fr_lv_86box_page2_func);
  •         }
  • }
  • 复制代码

        2. 通过"按键K1"和"按键K3"实现设备切换,"按键K2"进行开关控制。选中的设备UI上高亮显示,点击K1时更新开关图标。

            控件的切换和原始SDK中的菜单切换是一样的,按照 fr_lv_test_page.c 中的模板把相关代码搬到fr_lv_86box_page.c
    static void page2_event_handler(lv_event_t *e)
  • {
  •     lv_event_code_t event = lv_event_get_code(e);
  •     lv_obj_t *obj = lv_event_get_target(e);

  •     if (!lv_obj_is_valid(obj))
  •         return;

  •     if (event == LV_EVENT_DELETE) {

  •     } else if(event == LV_EVENT_KEY) {
  •         uint32_t *key = lv_event_get_param(e);
  •         switch(*key)
  •         {
  •             case KEY1_CODE:
  •                
  •                 break;

  •             case KEY3_CODE: // change item
  •                 if (++cur_select_id >= ITEM_SETTING_NUM)
  •                     cur_select_id = 0;

  •                 lv_event_send(lv_obj_get_child(obj, cur_select_id), LV_EVENT_FOCUSED, NULL);
  •                 break;

  •             case KEY4_CODE: // K2 Enter
  •                 lv_event_send(lv_obj_get_child(obj, cur_select_id), LV_EVENT_CLICKED, NULL);
  •                 break;

  •             case KEY5_CODE: //return //K1
  •                 //user_jump_page(fr_list_page);                if (--cur_select_id < 0)
  •                     cur_select_id = ITEM_SETTING_NUM - 1;

  •                 lv_event_send(lv_obj_get_child(obj, cur_select_id), LV_EVENT_FOCUSED, NULL);
  •                 break;
  •         }
  •         //printf("key_code %d \n", *key);
  •     }
  • }
  • 复制代码

            上面实现了按键的响应,从代码中可知,按键按下后会向对应的控件发送一个事件。为了能让事件得到响应,还需要在控件创建时给它增加事件。

    void fr_lv_86box_page2_func(lv_obj_t *parent, lv_point_t *top)
  • {
  •     if (!lv_obj_is_valid(parent))
  •         return;

  •     UI_PARENT_INIT(parent);

  •     lv_obj_t *card_obj3 = lv_obj_create(parent); //窗帘
  •     lv_obj_set_size(card_obj3, 174, 90);
  •     lv_obj_set_style_pad_all(card_obj3, 0, 0);
  •     lv_obj_set_style_border_width(card_obj3, 0, 0);
  •     lv_obj_set_style_border_color(card_obj3, lv_color_make(0, 0, 255), 0);
  •     lv_obj_set_style_bg_color(card_obj3, lv_color_make(0, 0, 0), 0);
  •     lv_obj_set_scrollbar_mode(card_obj3, LV_SCROLLBAR_MODE_OFF);
  •     lv_obj_set_pos(card_obj3, 273, 170);
  •     //lv_obj_set_style_radius(card_obj3, 50, LV_PART_MAIN);

  •     lv_obj_add_event_cb(card_obj3, btn_event_cb, LV_EVENT_FOCUSED, parent);
  •     lv_obj_add_event_cb(card_obj3, btn_event_cb, LV_EVENT_CLICKED, parent);
  • ...
  • }
  • 复制代码

            控件的响应代码如下:

    static void btn_event_cb(lv_event_t *e)
  • {
  •     //printf("click\r\n");

  •     lv_obj_t *obj = lv_event_get_target(e);
  •     uint8_t id = lv_obj_get_child_id(obj);
  •     lv_obj_t *new_child_obj;
  •     lv_obj_t *cur_child_obj;
  •     lv_obj_t *subobj  = lv_event_get_user_data(e);

  •     static uint8_t sw_state0 = 0;
  •     static uint8_t sw_state1 = 0;
  •     static uint8_t sw_state2 = 0;
  •     struct app_task_event *event;

  •     if (!lv_obj_is_valid(subobj))
  •         return;

  •     if (lv_event_get_code(e) == LV_EVENT_FOCUSED) //聚焦事件,则把原来的控制取消高亮
  •     {
  •         //printf("LV_EVENT_FOCUSED %d\r\n", id);

  •         if (last_select_id != id)
  •         {
  •             new_child_obj = lv_obj_get_child(subobj, last_select_id);
  •             lv_obj_set_style_border_width(new_child_obj, 0, 0);
  •         }

  •         //把当前选中的控制高亮

  •         cur_child_obj  = lv_obj_get_child(subobj, id);
  •         lv_obj_set_style_border_width(cur_child_obj, 2, 0);
  •         lv_obj_clear_state(cur_child_obj, LV_EVENT_FOCUSED);
  •     } else if (lv_event_get_code(e) == LV_EVENT_CLICKED) { // K2 Enter //如果是点了K2键,则更新按键图标
  •         printf("LV_EVENT_CLICKED %d\r\n", id);
  •         switch (id)
  •         {
  •             case ITEM_ID_CL:
  •                 sw_state0 += 10;
  •                 sw_state0 = sw_state0 % 110;
  •                 lv_bar_set_value(lv_obj_get_child(obj, 3), sw_state0, LV_ANIM_OFF);
  •                 break;
  •             case ITEM_ID_TD:
  •                 sw_state1++;
  •                 sw_state1 = sw_state1 % 2;
  •                 if (sw_state1) {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_add_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 } else {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_clear_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 }
  •                 event = app_task_event_alloc(APP_TASK_EVENT_LIGHT0_CTRL, 1, false);
  •                 if (event) {
  •                     memcpy(event->param, &sw_state1, 1);
  •                     app_task_event_post(event, false);
  •                 }
  •                 break;
  •             case ITEM_ID_CTD:
  •                 sw_state2++;
  •                 sw_state2 = sw_state2 % 2;
  •                 if (sw_state2) {
  •                     printf("sw_state2 = %d\r\n", sw_state2);
  •                     lv_obj_add_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 } else {
  •                     printf("sw_state2 = %d\r\n", sw_state2);
  •                     lv_obj_clear_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 }
  •                 event = app_task_event_alloc(APP_TASK_EVENT_LIGHT1_CTRL, 1, false);
  •                 if (event) {
  •                     memcpy(event->param, &sw_state2, 1);
  •                     app_task_event_post(event, false);
  •                 }
  •                 break;
  •             case ITEM_ID_KTKG:
  •                 sw_state1++;
  •                 sw_state1 = sw_state1 % 2;
  •                 if (sw_state1) {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_add_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 } else {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_clear_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 }
  •                 break;

  •             default:
  •                 break;
  •         }
  •     }
  •     last_select_id = id;
  • }
  • 复制代码

        2. 2 实现蓝牙BLE一主多从连接

            由于是需要多连接,这里启用的是BLE功能。根据终端的应用场景,它应该跟手机一样需要去控制别的设备,所以终端应该作为Central,而被控制设备作为Periphreal。本人手上只有额外的两个BLE开发板,且上面带了LED灯,只能用来模拟两个灯。根据UI上的显示,就把它们作为厅灯和餐厅灯。它们的关系图如下:

    未命名绘图.drawio.png


            "lvgl_demo"中的示例代码演示的是ble作为Periphreal。而我们需要开发板作为Central。因此,需要把BLE相关的代码全部注释掉,然后把"ble_simple_central"中的代码挪过来。

            挪过来的代码编译通过后就可以扫描到ble设备。但是我们需要让两个灯和开发板建立连接。

            代表灯的这两个Periphreal我烧录了一个固件让它们提供了一个服务,UUID为0xFF02,当它们收到控制包时会控制它们自己板上的LED灯亮或灭。由于这两个灯板的代码不在此次的测评范围内,这里就不放出来。只需要当作它们是两个BLE控制的灯就可以了。它们广播的设备名分别为"PhyLight-33445566"和"PhyLight-33445588"。

            从central的示例代码中可知,设备建立连接的点是在扫描到对应的的设备名之后。由于GUI中没有做增加设备的UI,只能在上电后扫描这两个设备并建立连接。在central代码的基础上需要做以下修改:

            1. 匹配两个设备名,并建立连接

            2. 注释掉gap_scan_stop(),确保两个设备都被扫描并连接。

            3. 通过 gap_get_connect_num()获取到两个设备都连接后再关闭扫描。

    static uint16_t gap_callback(struct gap_event *event)
  • {
  • ...
  •                 if (have_name_ad_type)
  •                 {
  •                     char periphreal_name[] = "PhyLight-33445566";
  •                     char periphreal_name1[] = "PhyLight-33445588";
  •                     if ((memcmp((void *)adv_name, (void *)periphreal_name, sizeof(periphreal_name) - 1) == 0) ||

                            (memcmp((void *)adv_name, (void *)periphreal_name1, sizeof(periphreal_name1) - 1) == 0))

                        {
  •                         //gap_scan_stop();

  •                         gap_conn_param_t conn_param;
  •                         memcpy(&conn_param.peer_addr.addr, &event->param.adv_rpt->addr.addr, 6);
  •                         conn_param.peer_addr.addr_type = event->param.adv_rpt->addr.addr_type;
  •                         conn_param.own_addr_type = GAP_ADDR_TYPE_STATIC;
  •                         conn_param.conn_intv_max = 8;
  •                         conn_param.conn_intv_min = 8;
  •                         conn_param.slave_latency = 0;
  •                         conn_param.supervision_to = 500;
  •                         conn_param.scan_intv = 40;
  •                         conn_param.scan_window = 40;
  •                         conn_param.phy_mode = GAPM_PHY_TYPE_LE_1M;
  •                         conn_param.ce_len_max = 2;
  •                         conn_param.ce_len_min = 2;

  •                         /* Initiate connection requests to peer devices */
  •                         gap_conn_start(&conn_param);      
  •                     }
  •                 }
  • ...

            caseGAP_EVT_MASTER_CONNECT:

  •             {
  •                 printf("master connect[%d], connect num: %d\r\n", event->param.connect.conidx, gap_get_connect_num());

  •                 if (gap_get_connect_num() ==2) {
  •                     gap_scan_stop();
  •                 }

  •                 gatt_mtu_exchange_req(client_id, event->param.connect.conidx, 247);

  •                 /* Discovery all attributes of the peer device , The GATT callback event is GATTC_MSG_SVC_REPORT and GATTC_MSG_CMP_EVT*/
  •                 gatt_discovery_all_peer_svc(client_id, event->param.connect.conidx);
  •             }
  •             break;
  • ...

    }
  • 复制代码

            确保外设端的服务能被发现,还需要把UUID改成和Periphreal中的一样,即0xFF02

    #define SP_CHAR1_UUID 0xFF02 //0xFFF3
    复制代码
           至此,UI中控制的切换和蓝牙连接都完成了,但我们还需要把按键触发和BLE的控制命令关联起来。

        2.3 通过GUI控制连接的设备

            从app_task.c中我们可以看到有关于事件的处理的示例,可以按照AT_CMD的处理方式来处理事件就可以把它们关联起来。

            首先,在点击开关后,在fr_lv_86box_page.c的按键响应中向app_task发送一个事件APP_TASK_EVENT_LIGHT0_CTRL,如下所示:

    static void btn_event_cb(lv_event_t *e)
  • {
  • ...
  •             case ITEM_ID_TD:
  •                 sw_state1++;
  •                 sw_state1 = sw_state1 % 2;
  •                 if (sw_state1) {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_add_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 } else {
  •                     printf("sw_state1 = %d\r\n", sw_state1);
  •                     lv_obj_clear_state(lv_obj_get_child(obj, 3), LV_STATE_CHECKED);
  •                 }

  •                 event = app_task_event_alloc(APP_TASK_EVENT_LIGHT0_CTRL, 1, false);
  •                 if (event) {
  •                     memcpy(event->param, &sw_state1, 1);
  •                     app_task_event_post(event, false);
  •                 }

  •                 break;
  • ...
  • }
  • 复制代码

            在app_task.c的事件响应中调用ble的写命令接口

    static void app_task_event_handler(void)
  • {
  • ...
  •     if (event) {
  •         switch(event->event_type) {
  •             case APP_TASK_EVENT_AT_CMD:
  •                 app_at_cmd_recv_handler(event->param, event->param_len);
  •                 break;

  •             case APP_TASK_EVENT_LIGHT0_CTRL:
  •                 printf("app_ble_light0_ctrl\r\n");
  •                 app_ble_light0_ctrl(event->param, event->param_len);
  •                 break;

  •             case APP_TASK_EVENT_LIGHT1_CTRL:
  •                 printf("app_ble_light1_ctrl\r\n");
  •                 app_ble_light1_ctrl(event->param, event->param_len);
  •                 break;
  • ...
  • }
  • 复制代码

            在app_ble.c中增加写命令的接口

    void app_ble_light0_ctrl(uint8_t *data, uint16_t length)
  • {
  •     struct gatt_client_write_param param;

  •     if (data[0]) {
  •         cmd[2] = 0xff;
  •     } else {
  •         cmd[2] = 0x00;
  •     }

  •     param.conidx = 0;         //!&lt; Connection index
  •     param.client_id = client_id;      //!&lt; Client index, this is return value after calling gatt_add_client.
  •     param.att_idx = 0;        //!&lt; Attribute index in client uuid table registed when calling gatt_add_client.
  •     param.p_data = cmd;        //!&lt; Data pointer to be written
  •     param.data_len = 5;      //!&lt; Data length to be written

  •     printf("...app_ble_light0_ctrl\r\n");

  •     gatt_client_write_req(¶m);
  • }
  • 复制代码

            至此,就完成了控件到ble命令的连接。这里只贴了一个灯的控制代码,第二个灯的同理可得。

    3. 运行演示

        3.1 厅灯打开

    image.png


        3.2 厅灯关闭

    image.png


        3.2 餐厅灯打开

    image.png


        3.3 餐厅灯关闭

    image.png


    4. 演示视频