本帖最后由 用户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开发板没有触摸屏,无法通过触控的方式来触发设备控制,因此,只能通过按键来进行控制设备切换和开关控制。
修改内容:
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_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:
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:
lv_event_send(lv_obj_get_child(obj, cur_select_id), LV_EVENT_CLICKED, NULL);
break;
case KEY5_CODE:
cur_select_id = ITEM_SETTING_NUM - 1;
lv_event_send(lv_obj_get_child(obj, cur_select_id), LV_EVENT_FOCUSED, NULL);
break;
}
}
} 复制代码
上面实现了按键的响应,从代码中可知,按键按下后会向对应的控件发送一个事件。为了能让事件得到响应,还需要在控件创建时给它增加事件。
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_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)
{
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)
{
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) {
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上的显示,就把它们作为厅灯和餐厅灯。它们的关系图如下:
"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_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;
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
复制代码 至此,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; //!< Connection index
param.client_id = client_id; //!< Client index, this is return value after calling gatt_add_client.
param.att_idx = 0; //!< Attribute index in client uuid table registed when calling gatt_add_client.
param.p_data = cmd; //!< Data pointer to be written
param.data_len = 5; //!< Data length to be written
printf("...app_ble_light0_ctrl\r\n");
gatt_client_write_req(¶m);
} 复制代码 至此,就完成了控件到ble命令的连接。这里只贴了一个灯的控制代码,第二个灯的同理可得。
3. 运行演示
3.1 厅灯打开
3.2 厅灯关闭
3.2 餐厅灯打开
3.3 餐厅灯关闭
4. 演示视频