RPM, 오일 그리고 냉각수 게이지를 디지털 판넬에 표시하기 위해 LVGL 라이브러리의 arc 관련 함수를 사용하면 쉽게 그릴 수 있는데, 기존 소스에서 게이지바와 눈금 바늘 사이에 약간에 오차가 있어 계산을 수정하면서 LVGL arc 함수를 정리하였다. 아래 이미지는 게이지바와 눈금 바늘을 표시하는 간단한 예제이다.
위 예제처럼 게이지바는 특정 범위에 들어오면 경고를 표시하기 위해 빨간 색으로 표시하는 구간이 있으며, 눈금 바늘은 게이지바의 끝에 위치해야 한다.
LVGL 라이브러리를 사용하여 호를 그리기 위해 lv_arc_create() 함수로 arc 인스턴스를 생성하도록 하고, 호의 위치와 크기를 지정하도록 한다.
*arc = lv_arc_create(lv_scr_act());
lv_obj_set_pos(*arc, x-radius, y-radius);
lv_obj_set_size(*arc, radius*2, radius*2);
그리고 호는 전경 및 배경 색상을 선택할 수 있다. 전경 및 배경 색상을 이용하여 게이지의 경고 범위를 설정하려고 한다. LVGL 라이브러리의 원래 의도는 사용자 설정 상태를 표시하는 것인데, 이 의도로 사용하지 않고 설정된 값의 경고 유무를 표시하려고 한다.
lv_obj_add_style(*arc, &arc_red_style, LV_PART_MAIN);
lv_obj_add_style(*arc, &arc_blue_style, LV_PART_INDICATOR);
LV_PART_MAIN은 배경 색상이며 LV_PART_INDICATOR는 전경 색상을 설정하며, 이것은 lvgl style 인스턴스를 객체로 받아 설정한다. 정상 범위 값은 파란색 게이지바로 표시하며, 경고 범위는 빨간색 게이지바로 표시하도록 하였다.
LVGL 라이브러리의 호를 그릴 때 시작 위치는 원점의 오른쪽 반지름 위치에서 시작하며, 시작 위치를 이동하기 위해서는 lv_arc_set_rotation() 함수를 사용하여 설정하도록 한다.
lv_arc_set_angles(*arc, 0, 0);
lv_arc_set_bg_angles(*arc, 0, 0);
lv_arc_set_rotation(*arc, arc_offset);
그리고 눈금 바늘은 약간의 꼼수로 표시하도록 한다. 전경 색상은 불투명도 0%(투명도 100%)로 설정하고 배경색상을 1도 각도만 출력하도록 하면 위 예제처럼 출력되는 것을 확인할 수 있다. 위 예제의 전체 코드는 아래와 같다.
#include "lvgl/lvgl.h"
#define RPM_VAL_RANGE 4000
#define RPM_VAL_WARNNING 3000
#define RPM_ARC_RANGE 240
#define RPM_ARC_OFFSET 150
#define RPM_ARC_WARNNING 180
#define RPM_ARC_RADIUS 212
#define RPM_MIDDLE_X 260
#define RPM_MIDDLE_Y 258
#define OIL_VAL_RANGE 100
#define OIL_VAL_WARNNING 10
#define OIL_ARC_RANGE 90
#define OIL_ARC_OFFSET 180
#define OIL_ARC_RADIUS 196
#define OIL_MIDDLE_X 716
#define OIL_MIDDLE_Y 240
#define COOLANT_VAL_RANGE 120
#define COOLANT_VAL_WARNNING 100
#define COOLANT_ARC_RANGE 90
#define COOLANT_ARC_OFFSET 180
#define COOLANT_ARC_WARNNING 75
#define COOLANT_ARC_RADIUS 196
#define COOLANT_MIDDLE_X 969
#define COOLANT_MIDDLE_Y 240
lv_style_t arc_red_style;
lv_style_t arc_blue_style;
lv_style_t arc_mark_style;
lv_style_t arc_opa0_style;
lv_obj_t *label_rpm;
lv_obj_t *arc_rpm;
lv_obj_t *mark_rpm;
lv_obj_t *label_oil;
lv_obj_t *arc_oil;
lv_obj_t *mark_oil;
lv_obj_t *label_coolant;
lv_obj_t *arc_coolant;
lv_obj_t *mark_coolant;
static void gauge_init_style(void)
{
lv_style_init(&arc_mark_style);
lv_style_set_arc_opa(&arc_mark_style, LV_OPA_100);
lv_style_set_arc_rounded(&arc_mark_style, 0);
lv_style_set_arc_color(&arc_mark_style, lv_color_make(250, 20, 20));
lv_style_set_arc_width(&arc_mark_style, 55);
lv_style_init(&arc_blue_style);
lv_style_set_arc_opa(&arc_blue_style, LV_OPA_50);
lv_style_set_arc_rounded(&arc_blue_style, 0);
lv_style_set_arc_color(&arc_blue_style, lv_color_make(0, 190, 255));
lv_style_set_arc_width(&arc_blue_style, 20);
lv_style_init(&arc_red_style);
lv_style_set_arc_opa(&arc_red_style, LV_OPA_50);
lv_style_set_arc_rounded(&arc_red_style, 0);
lv_style_set_arc_color(&arc_red_style, lv_color_make(250, 20, 20));
lv_style_set_arc_width(&arc_red_style, 20);
lv_style_init(&arc_opa0_style);
lv_style_set_arc_opa(&arc_opa0_style, LV_OPA_0);
lv_style_set_arc_rounded(&arc_opa0_style, 0);
}
static void gauge_draw(lv_obj_t **label, lv_obj_t **arc, lv_obj_t **mark, uint16_t x, uint16_t y, uint16_t radius, uint16_t arc_offset)
{
/* Label */
*label = lv_label_create(lv_scr_act());
lv_obj_set_pos(*label, x, y);
lv_label_set_text(*label, "0");
/* Gauge bar */
*arc = lv_arc_create(lv_scr_act());
lv_arc_set_angles(*arc, 0, 0);
lv_arc_set_bg_angles(*arc, 0, 0);
lv_arc_set_rotation(*arc, arc_offset);
lv_obj_set_pos(*arc, x-radius, y-radius);
lv_obj_set_size(*arc, radius*2, radius*2);
lv_obj_add_style(*arc, &arc_red_style, LV_PART_MAIN);
lv_obj_add_style(*arc, &arc_blue_style, LV_PART_INDICATOR);
lv_obj_remove_style(*arc, NULL, LV_PART_KNOB);
lv_obj_clear_flag(*arc, LV_OBJ_FLAG_CLICKABLE);
/* Gauge arrow */
*mark = lv_arc_create(lv_scr_act());
lv_arc_set_angles(*mark, 0, 0);
lv_arc_set_bg_angles(*mark, 0, 0);
lv_arc_set_rotation(*mark, arc_offset);
lv_obj_set_pos(*mark, x-radius, y-radius);
lv_obj_set_size(*mark, radius*2, radius*2);
lv_obj_add_style(*mark, &arc_opa0_style, LV_PART_MAIN);
lv_obj_add_style(*mark, &arc_mark_style, LV_PART_INDICATOR);
lv_obj_remove_style(*mark, NULL, LV_PART_KNOB);
lv_obj_clear_flag(*mark, LV_OBJ_FLAG_CLICKABLE);
}
static void gauge_update(uint8_t mode, uint16_t value)
{
char val_string[8] = {0, };
uint8_t val_check = 0;
uint16_t val_range, val_warn;
uint16_t arc_range, arc_offset, arc_warn, arc_value;
lv_obj_t *label, *arc, *mark;
switch(mode)
{
case 0:
val_range = RPM_VAL_RANGE;
val_warn = RPM_VAL_WARNNING;
arc_range = RPM_ARC_RANGE;
arc_offset = RPM_ARC_OFFSET;
arc_warn = RPM_ARC_WARNNING;
label = label_rpm;
arc = arc_rpm;
mark = mark_rpm;
break;
case 1:
val_range = OIL_VAL_RANGE;
val_warn = OIL_VAL_WARNNING;
arc_range = OIL_ARC_RANGE;
arc_offset = OIL_ARC_OFFSET;
arc_warn = 0;
label = label_oil;
arc = arc_oil;
mark = mark_oil;
break;
case 2:
val_range = COOLANT_VAL_RANGE;
val_warn = COOLANT_VAL_WARNNING;
arc_range = COOLANT_ARC_RANGE;
arc_offset = COOLANT_ARC_OFFSET;
arc_warn = COOLANT_ARC_WARNNING;
label = label_coolant;
arc = arc_coolant;
mark = mark_coolant;
break;
default:
printg("ERROR : invalid mode = %d\r\n", mode);
return;
}
sprintf(val_string, "%d", value);
lv_label_set_text(label, val_string);
arc_value = (value * arc_range) / val_range;
if(mode == 1) val_check = value <= val_warn;
else val_check = value > val_warn;
if(val_check)
{
lv_arc_set_angles(arc, 0, arc_warn);
lv_arc_set_bg_angles(arc, arc_warn, arc_value);
}
else
{
lv_arc_set_angles(arc, 0, arc_value);
lv_arc_set_bg_angles(arc, 0, 0);
}
lv_arc_set_angles(mark, arc_value, arc_value+1);
}
void exam_gague_draw(void)
{
gauge_init_style();
gauge_draw(&label_rpm, &arc_rpm, &mark_rpm, RPM_MIDDLE_X, RPM_MIDDLE_Y, RPM_ARC_RADIUS, RPM_ARC_OFFSET);
gauge_draw(&label_oil, &arc_oil, &mark_oil, OIL_MIDDLE_X, OIL_MIDDLE_Y, OIL_ARC_RADIUS, OIL_ARC_OFFSET);
gauge_draw(&label_coolant, &arc_coolant, &mark_coolant, COOLANT_MIDDLE_X, COOLANT_MIDDLE_Y, COOLANT_ARC_RADIUS, COOLANT_ARC_OFFSET);
}
uint8_t val1 = 0;
uint8_t val2 = 0;
void exam_gauge_update(void)
{
if(val1 <= 100)
{
gauge_update(0, val1*4000/100);
gauge_update(1, val1);
gauge_update(2, val1*120/100);
usleep(50 * 1000);
val1++;
if(val1 == 100) val2 = 100;
}
else
{
if(val2 > 0)
{
val2--;
if(val2 == 0) val1 = 0;
gauge_update(0, val2*4000/100);
gauge_update(1, val2);
gauge_update(2, val2*120/100);
usleep(50 * 1000);
}
}
}
기존 코드가 복잡하게 되어 있어, 사용방법이 까다로운 것 같았지만, 실제 코드를 정리해보니 기본 원리만 이해되면 간단히 사용할 수 있었다. 나중에 기억나지 않을 때 참고하면 될 듯 하다.