- 通过优先级调度算法,管理每一个包要发送的UI内容。用户只需要初始化UI的信息,定义UI的优先级,并更新UI的内容,Priority_UI会自动调度UI的发送顺序。能在裁判系统有限的带宽下实现比较高的UI刷新率,下面是场上的第一视角UI展示。
UI.mp4
-
初始化链表:
- 把每个UI信息结构体的地址存入四个链表中:动态UI链表、不变UI链表、图形UI链表、字符UI链表
-
优先级排序算法:
- 高中低三种优先级有不同的权重,将该UI对应的优先级权重加上该UI被阻塞的时间,得到该UI的优先级值。
- 优先比较UI发送状态,没发送过的UI优先级比发送过的UI高,发送状态相同时,再比较优先级值。
- 这样就可以实现高优先级的UI优先发送,当高优先级的UI太多阻塞低优先级UI过久时,低优先级的UI也可以被发送出去。而且当在更新UI时,遍历到发送过的UI时就证明所有要更新的UI都已经发送过了。
- 每次更新UI前都用归并排序对动态UI链表进行排序,保证高优先级的在前面。使用归并排序的原因是归并排序的时间复杂度恒为O(nlogn)。
-
配置发送缓存:
由于不能同时发送图形和字符类的UI,所以要判断当前发送是发图形UI还是字符UI。
- 如果动态UI链表的第一位是字符类UI则本次发送字符UI。
- 如果动态UI链表的前X位有中优先级的字符类UI,则本次发送字符UI。(X需要用户配置)
- 如果动态UI链表的前7位有高优先级的字符类UI,则本次发送字符UI。
- 其他时候发送图形UI。
判断好发送类型后,将对应的字符UI或优先级最高的7个图新UI存入发送缓存。如果发送不满7个,会从不变UI链表中拿几个填满。
-
发送UI:
发送前要判断这次的发送模式,是初始化UI还是更新UI,不满足用户定义的初始化UI条件则发送更新UI。
- 初始化UI:遍历字符UI链表和图形UI链表,先发字符再发图形,把所有UI发送一次ADD
- 更新UI:对动态UI链表进行排序,配置发送缓存,发送。
-
更新UI信息:
更改对应结构体的内容,然后调用更新UI函数来更新现在的发送状态和更新的Tick。
- RoboMaster C型开发板
- 上海瓴控科技串口转TTL模块连接开发板4PIN串口
./Application
│
├── pack----------------------------包
│ │
│ └── priority_ui.c ------ 优先级UI的包
│
├── Driver---------------------------驱动
│
├── Config--------------------------配置
│
├── protocol------------------------协议
│ │
│ └── ui_protocol.c------UI协议:打包UI
│
├── Task-------------------------FreeRTOS
│
└── control-------------------------控制
│
└── ui.c----更新UI信息
UI链表初始化在任务开始前完成(main.c中调用),后续的UI更新函数在务中调用。
可以先通过南航开源的UI设计器中串口模拟的功能来验证代码是否正常跑通,在示例工程的控制层ui.c绘制了一些UI,正常运行的话效果如下。
- 把priority_ui.c中send_test置1就初始化UI,置0就正常更新UI
- 更改ui.c中标志位来切换UI显示的信息
UI.mp4
要移植的文件可以从release中下载
- 需要移植的文件:
- ui_protocol.c ----- UI的裁判系统协议文件
- ui_protocol.h
- priority_ui.c ------ 优先级UI的实现文件
- priority_ui.h
- ui.c --------------- [示例应用代码] 定义UI信息,更新UI信息
- ui.h
- 配置 ui_protocol.c
-
和裁判系统通信的串口
#define UI_huart huart4
-
判断红蓝发配置id
void client_info_update(uint8_t robot_id) { switch(robot_id) { case 1: client_info.robot_id = 1; client_info.client_id = 0x0101; break; case 101: client_info.robot_id = 101; client_info.client_id = 0x0165; break; default: break; } }
如果是用别的底层需要自行更换CRC和串口发送相关的外部函数
- 配置 priority_ui.c
-
使能自动命名功能(可选):如果你不想自己配置图形名称,可以在此处使能自动命名功能。
#define AUTO_UI_NAME_ENABLE // 自动命名
-
高中低优先级的权重值
#define HIGH_PRIORITY_WEIGHT 1000 // 高优先级权重 #define MID_PRIORITY_WEIGHT 500 // 中优先级权重 #define LOW_PRIORITY_WEIGHT 0 // 低优先级权重
-
字符抢占图形的排位
#define HIGH_CHAR_PRIORITY_LEVEL 7 // 高优先级字符抢图形UI的等级 #define MID_CHAR_PRIORITY_LEVEL 5 // 中优先级字符抢图形UI的等级
-
发送间隔时间(MS)
#define SEND_INTERVAL 100 // 100ms发一次
-
每次初始化的次数,一般设定为1就行,丢包率很低。
#define PER_INIT_UI_TIMES 1 // 每次初始化的UI次数
-
定义初始化UI的条件的函数,在每次发送前会调用此函数,如果该函数返回true,则把所有UI以ADD操作发送PER_INIT_UI_TIMES次,函数返回false则正常更新UI。比如PER_INIT_UI_TIMES设定为1,检测到打开遥控器就返回一次true,就实现了每次开控就初始化UI的效果。
bool Init_Ui_Condition() { if (/*初始化UI的条件*/) { return true; } else { return false; } }
- 配置 ui.c
-
定义一个动态ui结构体和一个不变ui结构体配置初始UI信息
- operate_type(操作类型)配置提示:
-
对于不变UI,可以不配置operate_type
-
对于一直要显示的动态UI:operate_type = MODIFY
-
对于会进行删除操作的动态UI:operate_type = DELETE
这样初始化UI时,才不会给这类UI发ADD,需要用户通过后续更新UI时发送ADD
-
- 图形名称说明:
- 如果你不想自己配置图形名称,可以在priority_ui.c的宏定义中使能自动命名功能
//存储动态UI信息 ui_info_t dynamic_ui_info [] = { [0] = { /*******不变配置*********/ .ui_config.priority = MID_PRIORITY, // UI优先级(仅动态UI需要配置) .ui_config.ui_type = LINE, // UI内容类型 /*******可变配置*********/ .ui_config.operate_type = MODIFY, // 操作类型 .ui_config.layer = 1, // 图层数,0~9 .ui_config.color = GREEN, // 颜色 .ui_config.width = 3, // 线条宽度 .ui_config.start_x = 0 // 起点 x 坐标 .ui_config.start_y = Client_mid_position_y + 0, // 起点 y 坐标 .ui_config.end_x = Client_mid_position_x - 176, // 终点 x 坐标 .ui_config.end_y = Client_mid_position_y + 305, // 终点 y 坐标 }, [1] = { ... }, ....... }; //存储不变UI信息 ui_info_t const_ui_info [] = { .... };
- operate_type(操作类型)配置提示:
-
初始化链表,可以在main.c中调用这个函数
void My_Ui_Init(void) { Init_Ui_List(dynamic_ui_info, sizeof(dynamic_ui_info)/sizeof(ui_info_t), const_ui_info, sizeof(const_ui_info)/sizeof(ui_info_t)); }
-
更新UI数据,将UI设为准备发送
Enqueue_Ui_For_Sending(&dynamic_ui_info[/*所更新的UI*/]);
-
确保Heap大小足够存放所有UI信息
- 链表一个节点的大小为8字节,四个链表的大小一共为:[8* 2* (动态UI个数+不变UI个数)] 字节
- Heap的大小至少为上面所需大小的两倍,空间不够时申请内存就会失败,初始化链表函数会返回UI_ERROR
- 在CubeMX->Project Manger->Minimum Heap Size 中调整Heap大小
本算法为了降低空间复杂度,没有检测用户是否更改了UI的结构体的内容,需要用户自己添加到发送队列中。后续可以尝试自动检测UI信息更改的时间戳,这样就不需要自己来检测跳变并且添加到发送队列了。
