前言
这次的项目实际上是对之前的教程的补充,以对温度传感器和LCD进行更深入的了解。
LVGL相关
有关LVGL的安装和有关教程可以参考这个文章 将LVGL8.2移植到CH32V407,这篇文章里详细的说明了如何从零开始在赤菟开发板上让LVGL能够成功运行。
对于很多人来说,他们可能是第一次听说LGVL,那什么是LGVL呢?LVGL全称Light and Versatile Graphics Library,是一个自由的,开源的GUI图片库,界面精美,资源消耗小,可移植度高,响应式布局等特点,全库采用纯 c 语言开发。
开发板代码
在在赤菟开发板的显示屏上显示实时温度中,我们已经能够在LCD显示屏上显示出温度传感器测得的实时温度,那么在这个实验中,我们会改进之前的代码,以用来在显示图上用折线来表现温度变化的过程。
因为温度传感器是在MCU里的,所以我们选择了ADC1作为接口。这个在之前的工程中已经讲过了。首先要做的就是要把在赤菟开发板的显示屏上显示实时温度中的代码复制到新建的工程的main主函数里。接着对代码进行修改。因为我们这是在LVGL的程序来显示图像,所以要修改代码里的main(void)部分。
/********************************** (C) COPYRIGHT *******************************
* File Name : main.c
* Author : WCH
* Version : V1.0.0
* Date : 2021/06/06
* Description : Main program body.
*******************************************************************************/
#include "debug.h"
#include "lcd.h"
/* Global Variable */
s16 Calibrattion_Val = 0;
/*********************************************************************
* @fn ADC_Function_Init
*
* @brief Initializes ADC collection.
*
* @return none
*/
void ADC_Function_Init(void)
{
ADC_InitTypeDef ADC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_BufferCmd(ADC1, DISABLE); //disable buffer
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
Calibrattion_Val = Get_CalibrationValue(ADC1);
ADC_BufferCmd(ADC1, ENABLE); //enable buffer
ADC_TempSensorVrefintCmd(ENABLE);
}
/*********************************************************************
* @fn Get_ADC_Val
*
* @brief Returns ADCx conversion result data.
*
* @param ch - ADC channel.
* ADC_Channel_0 - ADC Channel0 selected.
* ADC_Channel_1 - ADC Channel1 selected.
* ADC_Channel_2 - ADC Channel2 selected.
* ADC_Channel_3 - ADC Channel3 selected.
* ADC_Channel_4 - ADC Channel4 selected.
* ADC_Channel_5 - ADC Channel5 selected.
* ADC_Channel_6 - ADC Channel6 selected.
* ADC_Channel_7 - ADC Channel7 selected.
* ADC_Channel_8 - ADC Channel8 selected.
* ADC_Channel_9 - ADC Channel9 selected.
* ADC_Channel_10 - ADC Channel10 selected.
* ADC_Channel_11 - ADC Channel11 selected.
* ADC_Channel_12 - ADC Channel12 selected.
* ADC_Channel_13 - ADC Channel13 selected.
* ADC_Channel_14 - ADC Channel14 selected.
* ADC_Channel_15 - ADC Channel15 selected.
* ADC_Channel_16 - ADC Channel16 selected.
* ADC_Channel_17 - ADC Channel17 selected.
*
* @return none
*/
u16 Get_ADC_Val(u8 ch)
{
u16 val;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
val = ADC_GetConversionValue(ADC1);
return val;
}
/*********************************************************************
* @fn Get_ADC_Average
*
* @brief Returns ADCx conversion result average data.
*
* @param ch - ADC channel.
* ADC_Channel_0 - ADC Channel0 selected.
* ADC_Channel_1 - ADC Channel1 selected.
* ADC_Channel_2 - ADC Channel2 selected.
* ADC_Channel_3 - ADC Channel3 selected.
* ADC_Channel_4 - ADC Channel4 selected.
* ADC_Channel_5 - ADC Channel5 selected.
* ADC_Channel_6 - ADC Channel6 selected.
* ADC_Channel_7 - ADC Channel7 selected.
* ADC_Channel_8 - ADC Channel8 selected.
* ADC_Channel_9 - ADC Channel9 selected.
* ADC_Channel_10 - ADC Channel10 selected.
* ADC_Channel_11 - ADC Channel11 selected.
* ADC_Channel_12 - ADC Channel12 selected.
* ADC_Channel_13 - ADC Channel13 selected.
* ADC_Channel_14 - ADC Channel14 selected.
* ADC_Channel_15 - ADC Channel15 selected.
* ADC_Channel_16 - ADC Channel16 selected.
* ADC_Channel_17 - ADC Channel17 selected.
*
* @return val - The Data conversion value.
*/
u16 Get_ADC_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
u16 val;
for(t=0;t<times;t++)
{
temp_val+=Get_ADC_Val(ch);
Delay_Ms(5);
}
val = temp_val/times;
return val;
}
/*********************************************************************
* @fn Get_ConversionVal
*
* @brief Get Conversion Value.
*
* @param val - Sampling value
*
* @return val+Calibrattion_Val - Conversion Value.
*/
u16 Get_ConversionVal(s16 val)//获得转化值
{
if((val+Calibrattion_Val)<0|| val==0) return 0;
if((Calibrattion_Val+val)>4095||val==4095) return 4095;
return (val+Calibrattion_Val);
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
/*******************************************************************************
* Function Name : main
* Description : Main program.
* Input : None
* Return : None
*******************************************************************************/
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init();
lcd_init();
lcd_fill(0,0,239,239,WHITE);//修改文字显示位置和显示屏背景
lcd_set_color(WHITE,BLACK);//修改文字和背景颜色
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
Delay_Ms(500);
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
lcd_show_string(30, 120, 24, "temprature:%0d",TempSensor_Volt_To_Temper(val_mv));//显示温度的具体数值,位置以及字体大小
Delay_Ms(2);
}
}
将代码里的修改lcd背景和文字颜色,lcd_show_string部分代码删除,因为这些代码是在显示屏上显示温度的部分。LVGL则是将图像显示在屏幕上,所以不需要修改背景的颜色和显示文字。
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init()
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
Delay_Ms(500);
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
Delay_Ms(2);
}
}
LVGL部分
在LVGL的官方网站里可以找到有关折线的代码,代码和示例图都在下面。
#include "../../lv_examples.h"
#if LV_USE_CHART && LV_BUILD_EXAMPLES
void lv_example_chart_1(void)
{
/*Create a chart*/
lv_obj_t * chart;
chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 200, 150);
lv_obj_center(chart);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
/*Add two data series*/
lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_series_t * ser2 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_SECONDARY_Y);
/*Set the next points on 'ser1'*/
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 30);
lv_chart_set_next_value(chart, ser1, 70);
lv_chart_set_next_value(chart, ser1, 90);
/*Directly set points on 'ser2'*/
ser2->y_points[0] = 90;
ser2->y_points[1] = 70;
ser2->y_points[2] = 65;
ser2->y_points[3] = 65;
ser2->y_points[4] = 65;
ser2->y_points[5] = 65;
ser2->y_points[6] = 65;
ser2->y_points[7] = 65;
ser2->y_points[8] = 65;
ser2->y_points[9] = 65;
lv_chart_refresh(chart); /*Required after direct set*/
}
#endif
然后通过这个代码可以得知,这段代码用ser1和ser2两种方式画折线,我们选择ser1来构建点。删去了和ser2有关的代码后如下所示。
void lv_example_chart_1(void)
{
/*Create a chart*/
lv_obj_t * chart;
chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 200, 150);
lv_obj_center(chart);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
/*Add two data series*/
lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
/*Set the next points on 'ser1'*/
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 10);
lv_chart_set_next_value(chart, ser1, 30);
lv_chart_set_next_value(chart, ser1, 70);
lv_chart_set_next_value(chart, ser1, 90);
lv_chart_refresh(chart); /*Required after direct set*/
}
LVGL屏幕有四个参数如下图所示。这四个参数分别对应主X轴,主Y轴,副X轴,副Y轴。
删去这行代码,因为这行代码是给定的点用的。
lv_chart_refresh(chart); /*Required after direct set*/
因为我们是要不断刷新图像,所以我们使用了这两行代码来代替lv_chart_refresh(chart)。LVGL Porting 的时候,需要再定时器中调用 lv_tick_inc 函数。当然,你也可以不用 SystemTick,使用其他的硬件 Timer 也可以达到同样的效果。lv_timer_handler()是查看源码可能是一个时间调度器。
lv_tick_inc(1);
lv_task_handler();
又因为太小,所以我们修改了显示的大小让温度的显示更加的清晰。
lv_obj_set_size(chart, 180, 180);
将最高温度设定为40,最低设定为0之后,这个温度计可以满足大部分的需求了。
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 40);
之后将设置显示刷新方式为CIRCULAR,因为CIRCULAR是循环添加,可以更好的显示出温度的变化过程。
lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_CIRCULAR);
为了让大家能够直观的看到温度的数值,我们还需要添加记号和标签。
void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len,
lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size);
axis:对应轴
major_len:主标记的长度
minor_len:副标记的长度
major_cnt:主标记的个数
minor_cnt:副标记的个数
label_en:是否增加标签显示
draw_size:需要显示标记和标签的大小,像素为单位,20pixel + draw_size是实际显示的长度。
修改之后的可使用的代码如下。
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 4, 4, 5, 5, true, 30);
最后删除delay函数,使屏幕上时间的刷新变快。
之后我们将代码合并之后如下。
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init();
lcd_init();
lv_init();// 硬件初始化:包括显示设备、输入设备
lv_port_disp_init();//初始化画画的缓存
lv_obj_t * chart;
chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 180, 180);//画面的尺寸
lv_obj_center(chart);
lv_chart_set_point_count(chart, 10);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 40);
lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_CIRCULAR);
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 4, 4, 5, 5, true, 30);
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
lv_chart_set_next_value(chart, ser1, TempSensor_Volt_To_Temper(val_mv));
lv_tick_inc(1);
lv_task_handler();
}
}
实验代码
#include "debug.h"
#include "lvgl.h"
#include "lcd.h"
#include "lv_port_disp.h"
#include "lv_examples.h"
s16 Calibrattion_Val = 0;
void ADC_Function_Init(void)
{
ADC_InitTypeDef ADC_InitStructure={0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE );
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_BufferCmd(ADC1, DISABLE); //disable buffer
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
Calibrattion_Val = Get_CalibrationValue(ADC1);
ADC_BufferCmd(ADC1, ENABLE); //enable buffer
ADC_TempSensorVrefintCmd(ENABLE);
}
u16 Get_ADC_Val(u8 ch)
{
u16 val;
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));
val = ADC_GetConversionValue(ADC1);
return val;
}
u16 Get_ConversionVal(s16 val)//获得转化值
{
if((val+Calibrattion_Val)<0|| val==0) return 0;
if((Calibrattion_Val+val)>4095||val==4095) return 4095;
return (val+Calibrattion_Val);
}
u16 Get_ADC_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
u16 val;
for(t=0;t<times;t++)
{
temp_val+=Get_ADC_Val(ch);
Delay_Ms(5);
}
val = temp_val/times;
return val;
}
int main(void)
{
u16 ADC_val;
s32 val_mv;
SystemCoreClockUpdate();//时钟初始化
Delay_Init();//初始化延迟
USART_Printf_Init(115200);//串口初始化
ADC_Function_Init();
lcd_init();
lv_init();// 硬件初始化:包括显示设备、输入设备
lv_port_disp_init();//初始化画画的缓存
/*Create a chart*/
lv_obj_t * chart;
chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 180, 180);
lv_obj_center(chart);
lv_chart_set_point_count(chart, 10);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 40);
lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_CIRCULAR);
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 4, 4, 5, 5, true, 30);
while(1)
{
ADC_val = Get_ADC_Average( ADC_Channel_TempSensor, 10 );
ADC_val = Get_ConversionVal(ADC_val);
val_mv = (ADC_val*3300/4096);//计算温度
lv_chart_set_next_value(chart, ser1, TempSensor_Volt_To_Temper(val_mv));
lv_tick_inc(1);
lv_task_handler();
}
}
实验结果