项目介绍
- 采集主板上的温湿度、环境光、六轴、磁力计4个传感器数据,并对其进行处理转换成常见单位的数值,再绘制到屏幕上。
- 通过环境光传感器采集当前亮度,依照人眼对亮度的感知曲线,自动调整屏幕亮度。
- 根据 温湿度传感器,实现恒温控制功能,进入恒温菜单,实现温度设定并显示当前温度曲线。
- 使用lcd和触摸配合lvgl GUI框架绘制所需的界面和实现对板载资源的控制。
- 使用FreeRTOS对板载资源调度和控制。
硬件介绍
- 主控 Cortex-M7 500Mhz的NXP MIMXRT1021CAG4A芯片。
- 板载资源:
- st7735 128x160lcd屏幕配合xpt2046电阻触摸。
- 温湿度、环境光、六轴、磁力计4个传感器。
- 板载一个ec11编码器、一个双轴按键摇杆。
- 板载一个rgb LED、一个加热电路。
- 用于xip 16M spi flash。
项目设计思路
本项目根据板载传感器种类使用lvgl设计了5个界面用于分区控制这些传感器,4个子菜单用于实现不同功能。
界面绘制
rt1021 对lvgl有着很好的支持,所以选择lvgl作为本次项目的GUI设计,GUI设计可以使用以下三种方式:
- squareline studio lvgl官方合作软件
- gui guider nxp官方 设计的lvgl设计软件
- 纯code 麻烦,不推荐
由于以前没学习过lvgl,所以本次选择lvgl 官方推荐的squareline studio有着更多的资料。
本次UI界面设计了五个界面,一个主菜单用于选择功能,四个子菜单用于传感器控制。
BSP驱动
本项目实现了以下资源的驱动
FreeRTOS的任务解析
重点说一下背光自动控制的实现。由于人眼对于不同亮度的敏感性不同。在低亮度下更加敏感,高亮度下不敏感。人眼对环境亮度和感知到的亮度可以简单地通过一个幂函数来模拟,由于没有具体的设备进行测量,无法得出幂次的大小,这里使用了简单的模拟人眼对亮度的感知曲线。
void updataBacklightTask(void *param)
{
float data;
static int8_t nowDuty=0,lastDuty=0;
float lightIntensity;
for(;;)
{
if(xQueueReceive(updateBacklightQueue,&lightIntensity,0)==pdTRUE)
{
data = powf(lightIntensity,2.0);
nowDuty = mapFloatToDuty(data);
if((nowDuty - lastDuty)>5)
{
nowDuty += 5;
if(nowDuty>= 100) nowDuty = 100;
}
if((nowDuty - lastDuty)<-5)
{
nowDuty -= 5;
if(nowDuty<10) nowDuty = 10;
}
adjustBacklight((uint8_t)nowDuty);
lastDuty = nowDuty;
}
vTaskDelay(100);
}
}
Lvgl适配
lvgl 主要要实现两个接口,一个是显示接口,用于lcd的屏幕区域刷新,一个是控件输入接口,用于控制屏幕上的控件。
- 显示接口:
void lv_port_disp_init(void)
{
static lv_disp_draw_buf_t disp_buf;
memset(s_frameBuffer, 0, sizeof(s_frameBuffer));
lv_disp_draw_buf_init(&disp_buf, s_frameBuffer[0], NULL, LCD_VIRTUAL_BUF_SIZE);
/*-------------------------
* Initialize your display
* -----------------------*/
DEMO_InitLcd();
/*-----------------------------------
* Register the display in LittlevGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set the resolution of the display*/
disp_drv.hor_res = LCD_WIDTH;
disp_drv.ver_res = LCD_HEIGHT;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = DEMO_FlushDisplay;
/*Set a display buffer*/
disp_drv.draw_buf = &disp_buf;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
static void DEMO_FlushDisplay(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
lv_coord_t x1 = area->x1+2;
lv_coord_t y1 = area->y1+1;
lv_coord_t x2 = area->x2+2;
lv_coord_t y2 = area->y2+1;
uint8_t data[4];
const uint8_t *pdata = (const uint8_t *)color_p;
uint32_t send_size = (x2 - x1 + 1) * (y2 - y1 + 1) * LCD_FB_BYTE_PER_PIXEL;
/*Column addresses*/
DEMO_SPI_LCD_WriteCmd(ST7735_CASET);
data[0] = (x1 >> 8) & 0xFF;
data[1] = x1 & 0xFF;
data[2] = (x2 >> 8) & 0xFF;
data[3] = x2 & 0xFF;
DEMO_SPI_LCD_WriteMultiData(data, 4);
/*Page addresses*/
DEMO_SPI_LCD_WriteCmd(ST7735_RASET);
data[0] = (y1 >> 8) & 0xFF;
data[1] = y1 & 0xFF;
data[2] = (y2 >> 8) & 0xFF;
data[3] = y2 & 0xFF;
DEMO_SPI_LCD_WriteMultiData(data, 4);
/*Memory write*/
// DEMO_SPI_LCD_WriteCmd(ILI9341_CMD_GRAM);
DEMO_SPI_LCD_WriteCmd(ST7735_RAMWR);
DEMO_SPI_LCD_WriteMultiData(pdata, send_size);
lv_disp_flush_ready(disp_drv);
}
- 输入设备接口:
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_touchpad_drv;
static lv_indev_drv_t indev_encoder_drv;
/*Initialize touchpad */
XPT2046_Init();
/*Initialize encoder */
EC11_Init();
/*Register a touchpad input device*/
lv_indev_drv_init(&indev_touchpad_drv);
indev_touchpad_drv.type = LV_INDEV_TYPE_POINTER;
indev_touchpad_drv.read_cb = DEMO_ReadTouch;
indev_touchpad = lv_indev_drv_register(&indev_touchpad_drv);
/*Register a encoder input device*/
lv_indev_drv_init(&indev_encoder_drv);
indev_encoder_drv.type = LV_INDEV_TYPE_ENCODER;
indev_encoder_drv.read_cb = DEMO_ReadEncoder;
indev_encoder = lv_indev_drv_register(&indev_encoder_drv);
group = lv_group_create();
lv_indev_set_group(indev_touchpad, group);
lv_indev_set_group(indev_encoder, group);
}
/* Will be called by the library to read the touchpad */
static void DEMO_ReadTouch(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
static uint16_t last_x = 0;
static uint16_t last_y = 0;
uint16_t x = 0;
uint16_t y = 0;
uint8_t irq = XPT2046_irq_read();
if (irq == 0) {
XPT2046_GetTouch_XY(&x, &y, 1);
xpt2046_corr(&x, &y);
if(x<0) x = 0;
if(y<0) y = 0;
if(y>LCD_HEIGHT) y = LCD_HEIGHT;
if(x>LCD_WIDTH) x = LCD_WIDTH;
data->point.x = x;
data->point.y = LCD_HEIGHT - y;
data->state = LV_INDEV_STATE_PR;
}
else
data->state = LV_INDEV_STATE_REL;
}
static void DEMO_ReadEncoder(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
// lv_indev_state_t encoder_act;
int16_t encoder_diff = 0;
int16_t differential_value = EC11_read_differential_value();
data->state = EC11_key_get_value() ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
/* 消抖 */
if(differential_value > 0)
{
encoder_diff = differential_value/4 + 1;
}
else if(differential_value < 0)
{
encoder_diff = differential_value/4 -1;
}
data->enc_diff = encoder_diff;
}
界面后端
除了需要绘制界面以外还需要对每个需要的控件编写对应的回调函数,用以处理各种功能。
- ui界面标签的数据更新,图表数据的更新
- arc和slider的使用时对应的事件回调
数据更新
由于使用了FreeRTOS进行调度资源,但是lvgl的api本事不是线程安全的,所以要么选择在其他非lvgl调度线程内使用互斥锁来操作lvgl api,要么选择直接使用lvgl的软件定时器来回调处理显示的数据,这里为了方便选择了后者。
// lvgl线程内创建
updata_data_task = lv_timer_create(updateDataTask,1000,NULL);
void updateDataTask(lv_timer_t *t)
{
// Temp & Humi
lv_label_set_text_fmt(ui_TempLabel,"%4.1f C",Temperature_humidity_value[0]);
lv_label_set_text_fmt(ui_HumiLabel,"%3.1f %%",Temperature_humidity_value[1]);
lv_chart_set_next_value(ui_Chart2,ui_Chart2_series_1,(lv_coord_t)Temperature_humidity_value[0]);
lv_chart_set_next_value(ui_Chart2,ui_Chart2_series_2,(lv_coord_t)Temperature_humidity_value[1]);
lv_chart_refresh(ui_Chart2);
// Motion Sensor
lv_label_set_text_fmt(ui_XLabel2,"X:%4.2f ",sixaxisdata.accelerationx);
lv_label_set_text_fmt(ui_YLabel2,"Y:%4.2f ",sixaxisdata.accelerationy);
lv_label_set_text_fmt(ui_ZLabel2,"Z:%4.2f ",sixaxisdata.accelerationz);
lv_label_set_text_fmt(ui_XLabel3,"X:%4.2f ",sixaxisdata.angular_ratex);
lv_label_set_text_fmt(ui_YLabel3,"Y:%4.2f ",sixaxisdata.angular_ratey);
lv_label_set_text_fmt(ui_ZLabel3,"Z:%4.2f ",sixaxisdata.angular_ratez);
lv_label_set_text_fmt(ui_sixtemp,"%3.1f C ",sixaxisdata.temp);
lv_label_set_text_fmt(ui_XLabel1,"X:%4.2f ",magneticdata.magneticx);
lv_label_set_text_fmt(ui_YLabel1,"Y:%4.2f ",magneticdata.magneticy);
lv_label_set_text_fmt(ui_ZLabel1,"Z:%4.2f ",magneticdata.magneticz);
lv_label_set_text_fmt(ui_sixtemp2,"%3.1f C ",magneticdata.temp);
// Light Sensor
lv_label_set_text_fmt(ui_BrightnessLabel,"%6.1f lux",lightIntensity);
// Setup Temperature
lv_label_set_text_fmt(ui_Label8,"%4.1f C",Temperature_humidity_value[0]);
lv_chart_set_next_value(ui_Chart1,ui_Chart1_series_1,(lv_coord_t)Temperature_humidity_value[0]);
lv_chart_refresh(ui_Chart2);
}
操作组件的回调函数
// arc 组件
void updateBackLight(lv_event_t * e)
{
lv_obj_t * target = lv_event_get_target(e);
adjustBacklight((uint8_t)lv_arc_get_value(target));
}
// switch 组件
void switchMode(lv_event_t * e)
{
BaseType_t backlight_stat;
extern TaskHandle_t g_updataBacklightTask;
lv_obj_t * target = lv_event_get_target(e);
if(!lv_obj_has_state(target, LV_STATE_CHECKED))
{
taskENTER_CRITICAL();
backlight_stat = xTaskCreate(updataBacklightTask,"backlight",400,NULL,tskIDLE_PRIORITY + 1,&g_updataBacklightTask);
PRINTF("switch off\r\n");
taskEXIT_CRITICAL();
}
else
{
taskENTER_CRITICAL();
vTaskDelete(g_updataBacklightTask);
PRINTF("switch on\r\n");
taskEXIT_CRITICAL();
}
}
// slider 组件
void updateSetPoint(lv_event_t * e)
{
lv_obj_t * target = lv_event_get_target(e);
heaterPID.setpoint = (float)lv_slider_get_value(target);
}
效果&功能展示
- 主界面
设置四个子菜单按钮用于菜单选择
- 温湿度界面
显示当前温湿度数值和温湿度的趋势曲线
- 亮度界面
显示当前的环境光光照强度,屏幕中间的开关打开变为为绿色时可自行调节屏幕亮度,并显示当前屏幕亮度占空比,当开关关闭变为红色时,启用自动调节背光的程序。
- 九轴界面
显示当前的信息
三轴加速度信息,单位mg
三轴角速度信息,单位mdps
三轴磁力信息,单位mG
- 温度设置界面
显示当前的温度变化曲线,和温度数值。通过下方的滑动进度条可以调节设置的温度,最大可到150 C,但由于是被动散热,因此PID调节参数会因为不同环境而发生改变,需自行调节。折线图上方显示当前设定的温度。
遇到的主要难题
- 板载的六轴有些虚焊,经常读不到id,通过逻辑分析仪分析后定位问题,经过手动加焊后功能正常。
- ui界面设计,由于第一次使用lvgl,对lvgl不太熟悉,好在可以使用squareline studio的方式来可视化编辑界面。
- 由于不了解lvgl,因此由于本身api不是线程安全的造成了线程卡死。查阅lvgl的官方文档后找到解决步骤放入了前面的项目介绍中。
- 由于不熟悉rt1021这颗芯片,大部分时间用于bsp的编写过程中,好在可以对着参考例程来学习不同外设的使用。体验效果很好。
未来的计划建议
- 通过本次项目学习了lvgl的使用,希望后续自己的项目中可以多加练习lvgl,制作出更多界面精美的设备
- 第一次系统性的学习了imxrt系列的单片机,希望自己后续的项目中可以使用到这个性能强悍的芯片。
- 未来有时间的话希望多多参加此类活动,不断学习到新的技术,扩充自己的知识面也同时锻炼自己的编码能力。