实现功能
1. 接收电量探测设备发送的电量信息
2. 将接收到的电量信息通过图标进行显示
3. 能够查看历史电量
实现思路
1. 电量探测设备通过Lora模块将电量数据发送到EVB-AIoT连接的Lora模块
2. 本程序通过TencentOS的AT和UART模块接收电量数据,并且进行保存
3. 本程序的另一个线程根据当前时间从保存的数据中读取并进行图标的绘制
4. 通过板子的F1、F2 2个按钮对本程序进行控制,单个按钮短按或者长按来显示不同时间的数据,2个按钮同时按来切换显示的模式
引脚
关键代码
1. AT指令收发
代码语言:javascript复制// Send
int atk_lora_send(const char *buf, size_t len, char * expected, char * recv_buf, size_t recv_buf_size)
{
int try = 0;
at_echo_t echo;
char cmd[128] = {0};
if (len > sizeof(cmd) - 2) {
return -1;
}
memcpy(cmd, buf, sizeof(cmd) > len ? len : sizeof(cmd));
memcpy(cmd len, "rn", 2);
tos_at_echo_create(&echo, NULL, 0, expected);
while (try < 10) {
tos_at_cmd_exec(&echo, 3000, cmd);
if (echo.status == AT_ECHO_STATUS_OK || echo.status == AT_ECHO_STATUS_EXPECT) {
PRINTF("%s %drn", __FUNCTION__, __LINE__);
return 0;
}
}
return -1;
}
// Recv
static void atk_lora_at_event_callback(atk_lora_recv_event_t * event_handler,
char * incoming_data_buffer, uint32_t size)
{
int offset = 0;
int err = 0;
int is_recv_finish = 0;
uint8_t data;
memset(incoming_data_buffer, 0x00, size);
k_tick_t begin = tos_systick_get();
while ((tos_systick_get() - begin) < event_handler->timeout
&& 0 == is_recv_finish
&& 0 == err)
{
tos_at_uart_read(&data, 1);
switch (data)
{
case 'r':
continue;
case 'n':
is_recv_finish = 1;
break;
default:
if (offset < size)
{
incoming_data_buffer[offset ] = data;
continue;
}
printf(" ERR:incomming data is big ,please give more space for incoming_data_buffern");
err = -1;
break;
}
}
// printf("[%p] Received err: %d finished: %d prefix: %s data: %sn",
// event_handler, err, is_recv_finish, event_handler->event, incoming_data_buffer);
if (0 != is_recv_finish && event_handler->handler)
{
event_handler->handler(event_handler->event, incoming_data_buffer, offset);
}
}
2. 屏幕绘制
代码语言:javascript复制void draw_axis(uint32_t frameBuffer[APP_IMG_HEIGHT][APP_IMG_WIDTH], uint32_t fg_color, uint32_t bg_color)
{
int y_max = g_y_max;
int y_min = g_y_min;
// Draw axises
{
draw_line(CONF_Y_AXIS_LABEL_WIDTH, CONF_X_AXIS_LABEL_HEIGHT,
CONF_Y_AXIS_LABEL_WIDTH, APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT,
frameBuffer, fg_color);
draw_line(CONF_Y_AXIS_LABEL_WIDTH, APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT,
CONF_Y_AXIS_LABEL_WIDTH - CONF_AXIS_ARROW_HALF_WIDTH,
APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT - CONF_AXIS_ARROW_HEIGHT,
frameBuffer, fg_color);
draw_line(CONF_Y_AXIS_LABEL_WIDTH, APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT,
CONF_Y_AXIS_LABEL_WIDTH CONF_AXIS_ARROW_HALF_WIDTH,
APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT - CONF_AXIS_ARROW_HEIGHT,
frameBuffer, fg_color);
draw_string(CONF_Y_AXIS_LABEL_WIDTH,
APP_IMG_HEIGHT - CONF_Y_AXIS_NAME_HEIGHT, g_y_label, 24,
frameBuffer, fg_color, bg_color);
draw_line(CONF_Y_AXIS_LABEL_WIDTH, CONF_X_AXIS_LABEL_HEIGHT,
APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH, CONF_X_AXIS_LABEL_HEIGHT,
frameBuffer, fg_color);
draw_line(APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH, CONF_X_AXIS_LABEL_HEIGHT,
APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH - CONF_AXIS_ARROW_HEIGHT,
CONF_X_AXIS_LABEL_HEIGHT CONF_AXIS_ARROW_HALF_WIDTH,
frameBuffer, fg_color);
draw_line(APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH, CONF_X_AXIS_LABEL_HEIGHT,
APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH - CONF_AXIS_ARROW_HEIGHT,
CONF_X_AXIS_LABEL_HEIGHT - CONF_AXIS_ARROW_HALF_WIDTH,
frameBuffer, fg_color);
draw_string_v_center_align(APP_IMG_WIDTH - CONF_X_AXIS_NAME_WIDTH,
CONF_X_AXIS_LABEL_HEIGHT, CONF_X_AXIS_NAME, 24,
frameBuffer, fg_color, bg_color);
}
// Draw scales
{
// Draw Y scales
int y_scale_step = (y_max - y_min) / CONF_Y_AXIS_N_SCALES;
int y_step = (APP_IMG_HEIGHT - CONF_X_AXIS_LABEL_HEIGHT -
CONF_Y_AXIS_NAME_HEIGHT - CONF_AXIS_ARROW_HEIGHT -
CONF_X_AXIS_0_POINT_OFFSET) /
CONF_Y_AXIS_N_SCALES;
int y_offset = 0;
int y_scale = y_min;
for (int i = 0; i < CONF_Y_AXIS_N_SCALES 1; i)
{
draw_line(CONF_Y_AXIS_LABEL_WIDTH,
CONF_X_AXIS_LABEL_HEIGHT y_offset CONF_Y_AXIS_0_POINT_OFFSET,
CONF_Y_AXIS_LABEL_WIDTH - 10,
CONF_X_AXIS_LABEL_HEIGHT y_offset CONF_Y_AXIS_0_POINT_OFFSET,
frameBuffer, fg_color);
char scale[16] = {0};
snprintf(scale, sizeof(scale), "%d", y_scale);
draw_string_v_center_right_align(CONF_Y_AXIS_LABEL_WIDTH - 10,
CONF_X_AXIS_LABEL_HEIGHT y_offset CONF_Y_AXIS_0_POINT_OFFSET,
scale, 24, frameBuffer, fg_color, bg_color);
y_offset = y_step;
y_scale = y_scale_step;
}
// Draw X scales
int x_step = CONF_X_AXIS_STEP;
int x_scale_step = CONF_X_AXIS_SCALE_STEP;
int x_offset = 0;
for (int i = 0; i < CONF_X_AXIS_N_SCALES 1; i)
{
draw_line(
CONF_Y_AXIS_LABEL_WIDTH x_offset CONF_X_AXIS_0_POINT_OFFSET,
CONF_X_AXIS_LABEL_HEIGHT,
CONF_Y_AXIS_LABEL_WIDTH x_offset CONF_X_AXIS_0_POINT_OFFSET,
CONF_X_AXIS_LABEL_HEIGHT - 10,
frameBuffer, fg_color);
char scale[32] = {0};
if (K_NULL != g_k_tick_t_2_x_scale_label)
{
g_k_tick_t_2_x_scale_label(i, scale, sizeof(scale));
} else
{
k_tick_t_2_format_time_of_day(i, scale, sizeof(scale));
}
draw_string_h_center_align(
CONF_Y_AXIS_LABEL_WIDTH x_offset CONF_X_AXIS_0_POINT_OFFSET,
10, scale, 16, frameBuffer, fg_color, bg_color);
x_offset = x_step;
}
}
g_fg_color = fg_color;
g_bg_color = bg_color;
}
void axis_redraw(uint32_t frameBuffer[APP_IMG_HEIGHT][APP_IMG_WIDTH])
{
int x_begin = CONF_Y_AXIS_LABEL_WIDTH CONF_X_AXIS_0_POINT_OFFSET;
int x_offset_point = CONF_X_AXIS_STEP / CONF_X_AXIS_SCALE_SECTION_N_POINTS;
int y_max = g_y_max;
int y_min = g_y_min;
axis_clear_data(frameBuffer);
draw_axis(frameBuffer, g_fg_color, g_bg_color);
for (int i = 0; i < g_index - 1; i)
{
draw_line(x_begin i * x_offset_point, get_y_value(y_max, y_min, g_values[i]),
x_begin (i 1) * x_offset_point, get_y_value(y_max, y_min, g_values[i 1]),
frameBuffer, g_data_color);
}
if (g_index_loop)
{
int index_begin = g_index 1;
if (index_begin >= N_G_VALUES)
{
for (int i = 0; i < N_G_VALUES - 1; i)
{
draw_line(x_begin i * x_offset_point, get_y_value(y_max, y_min, g_values[i]),
x_begin (i 1) * x_offset_point, get_y_value(y_max, y_min, g_values[i 1]),
frameBuffer, g_data_color);
}
} else
{
int offset = 0;
for (int i = index_begin; i < N_G_VALUES - 1; i, offset)
{
draw_line(x_begin offset * x_offset_point, get_y_value(y_max, y_min, g_values[i]),
x_begin (offset 1) * x_offset_point, get_y_value(y_max, y_min, g_values[i 1]),
frameBuffer, g_data_color);
}
draw_line(x_begin offset * x_offset_point, get_y_value(y_max, y_min, g_values[N_G_VALUES - 1]),
x_begin (offset 1) * x_offset_point, get_y_value(y_max, y_min, g_values[0]),
frameBuffer, g_data_color);
offset;
for (int i = 0; i < index_begin - 1; i, offset)
{
draw_line(x_begin offset * x_offset_point, get_y_value(y_max, y_min, g_values[i]),
x_begin (offset 1) * x_offset_point, get_y_value(y_max, y_min, g_values[i 1]),
frameBuffer, g_data_color);
}
}
} else
{
for (int i = 0; i < g_index - 1; i)
{
draw_line(x_begin i * x_offset_point, get_y_value(y_max, y_min, g_values[i]),
x_begin (i 1) * x_offset_point, get_y_value(y_max, y_min, g_values[i 1]),
frameBuffer, g_data_color);
}
}
}
3. 按钮事件
代码语言:javascript复制static void buttons_main(void * arg)
{
while (g_run)
{
k_tick_t cur = get_sys_time();
for (buttons_id_t id = BTN_ID_1; id < BTN_ID_COUNT; id)
{
int event = 0;
tos_mutex_pend(&g_button_mutex);
if (has_status(g_btn_contexts[id].comming_pressed_status))
{
if (g_btn_contexts[id].comming_pressed_status != g_btn_contexts[id].pressed_status)
{
if (BTN_STATUS_PRESSED == g_btn_contexts[id].comming_pressed_status)
{
g_btn_contexts[id].pressed_time = get_sys_time();
event |= BTN_EVENT_PRESSED;
} else
{
if ((cur - g_btn_contexts[id].pressed_time) < BTN_LONG_PRESSED_TIME)
{
event |= BTN_EVENT_CLICK;
}
event |= BTN_EVENT_RELEASED;
g_btn_contexts[id].in_long_pressed = 0;
}
g_btn_contexts[id].pressed_status = g_btn_contexts[id].comming_pressed_status;
} else
{
if (BTN_STATUS_PRESSED == g_btn_contexts[id].pressed_status)
{
if ((cur - g_btn_contexts[id].pressed_time) >= BTN_LONG_PRESSED_TIME
&& !g_btn_contexts[id].in_long_pressed)
{
event |= BTN_EVENT_LONG_PRESS;
g_btn_contexts[id].in_long_pressed = 1;
}
}
}
g_btn_contexts[id].comming_pressed_status = BTN_STATUS_NON_INIT;
} else // No comming status
{
if (BTN_STATUS_PRESSED == g_btn_contexts[id].pressed_status)
{
if ((cur - g_btn_contexts[id].pressed_time) >= BTN_LONG_PRESSED_TIME
&& !g_btn_contexts[id].in_long_pressed)
{
event |= BTN_EVENT_LONG_PRESS;
g_btn_contexts[id].in_long_pressed = 1;
}
}
}
tos_mutex_post(&g_button_mutex);
if (event > 0)
{
g_btn_contexts[id].callback(event);
}
}
tos_task_delay(10);
}
}
static void btn_ctrl_main(void * arg)
{
while (g_run)
{
btn_ctrl_do_what_t do_what = BTN_CTRL_NOTHING;
tos_mutex_pend(&g_btn_ctrl_mutex);
if (BTN_CTRL_F1_F2_PRESSED != g_btn_ctrl_status.do_what)
{
if ((g_btn_ctrl_status.btn_status[BTN_ID_1] & BTN_EVENT_PRESSED)
&& (g_btn_ctrl_status.btn_status[BTN_ID_2] & BTN_EVENT_PRESSED))
{
g_btn_ctrl_status.do_what = do_what = BTN_CTRL_F1_F2_PRESSED;
g_btn_ctrl_status.btn_status[BTN_ID_1] = BTN_EVENT_PRESSED;
g_btn_ctrl_status.btn_status[BTN_ID_2] = BTN_EVENT_PRESSED;
} else if (BTN_EVENT_LONG_PRESS & g_btn_ctrl_status.btn_status[BTN_ID_1])
{
g_btn_ctrl_status.do_what = BTN_CTRL_NOTHING;
do_what = BTN_CTRL_F1_LONG_PRESSED;
g_btn_ctrl_status.btn_status[BTN_ID_1] = BTN_EVENT_LONG_PRESS;
} else if (BTN_EVENT_LONG_PRESS & g_btn_ctrl_status.btn_status[BTN_ID_2])
{
g_btn_ctrl_status.do_what = BTN_CTRL_NOTHING;
do_what = BTN_CTRL_F2_LONG_PRESSED;
g_btn_ctrl_status.btn_status[BTN_ID_2] = BTN_EVENT_LONG_PRESS;
} else if (BTN_EVENT_CLICK & g_btn_ctrl_status.btn_status[BTN_ID_1])
{
g_btn_ctrl_status.do_what = BTN_CTRL_NOTHING;
do_what = BTN_CTRL_F1_CLICK;
g_btn_ctrl_status.btn_status[BTN_ID_1] = BTN_EVENT_RELEASED;
} else if (BTN_EVENT_CLICK & g_btn_ctrl_status.btn_status[BTN_ID_2])
{
g_btn_ctrl_status.do_what = BTN_CTRL_NOTHING;
do_what = BTN_CTRL_F2_CLICK;
g_btn_ctrl_status.btn_status[BTN_ID_2] = BTN_EVENT_RELEASED;
}
} else
{
if ((g_btn_ctrl_status.btn_status[BTN_ID_1] & BTN_EVENT_RELEASED)
&& (g_btn_ctrl_status.btn_status[BTN_ID_2] & BTN_EVENT_RELEASED))
{
g_btn_ctrl_status.do_what = BTN_CTRL_NOTHING;
do_what = BTN_CTRL_F1_F2_RELEASED;
g_btn_ctrl_status.btn_status[BTN_ID_1] = BTN_EVENT_RELEASED;
g_btn_ctrl_status.btn_status[BTN_ID_2] = BTN_EVENT_RELEASED;
}
}
tos_mutex_post(&g_btn_ctrl_mutex);
btn_ctrl_do_what(do_what);
tos_task_delay(10);
}
}
代码地址
EVB_AIoT软件
https://gitee.com/delightedok/tos-electricity-consumption.git
模拟软件
https://gitee.com/delightedok/electricity-consumption-terminal.git
遇到的困难
1. 报名的时候打算用KV来对数据进行保存和读取,但是在实践中的时候发现GPIO_AD_B0_02被ELCDIF使用了;而如果使用NOR Flash驱动的时候,如果把framebuffer定义在NCACHE_REGION的时候,初始化BSS会出现Hard fault错误;最后尝试了SD卡,但是在发送CMD8数据的时候收不到SD卡的应答,所以最后选择了仅把数据放在内存中
2. 使用AT框架进行数据的收发的时候发现如果想使用同步的方式对数据进行读写,会出现读到的数据不完整的问题,看AT的代码认为是框架读到了跟expected相同的数据后就做出了返回,没有等待rn的到来,最后选择了使用异步的方式来处理返回的数据
3. 由于Lora模块占用了Wifi模块ESP8266的座子,因此时间同步选择了通过Lora模块来进行
后续
1. 移植到开源UI库中,例如LVGL
2. 数据保存到SD卡中
总结
感谢这次的比赛让我熟悉单片机层面的知识,对于嵌入式的初学者,这块板子个人感觉确实不错,有很多SDK的demo帮助我们调试和理解。感谢大佬们的分享和帮助。