STM32F103RET6 4G开发板的LVGL GUI图形界面的移植和用法
LVGL(Light and Versatile Graphics Library)是一款开源嵌入式图形库,主打轻量、跨平台特性,专为资源受限的嵌入式设备设计,能快速开发出流畅、美观的 GUI(图形用户界面)。
STM32F103RET6 4G开发板的LVGL GUI图形界面的移植和用法
文档结尾有代码下载链接
🎯 实现效果








🌟 传感器的介绍
LVGL(Light and Versatile Graphics Library)是一款开源嵌入式图形库,主打轻量、跨平台特性,专为资源受限的嵌入式设备设计,能快速开发出流畅、美观的 GUI(图形用户界面)。
1.核心定位与核心优势
- LVGL 的核心价值在于平衡 “资源占用” 与 “GUI 体验”,让低性能嵌入式设备也能拥有接近高端设备的界面效果,核心优势如下:
极致轻量:最低仅需 64KB RAM 和 128KB Flash 即可运行,适配 8 位、16 位、32 位 MCU(如 STM32、ESP32、PIC 等),完全满足嵌入式设备的资源限制。 - 强跨平台性:不依赖特定硬件或操作系统,支持裸机(无 OS)、RTOS(FreeRTOS、RT-Thread 等)、Linux/Windows,可轻松移植到 LCD、OLED 等各类显示设备。
- 易用性高:提供简洁的 C 语言 API,支持链式调用;配套可视化开发工具,无需手动写大量界面代码,降低嵌入式 GUI 开发门槛。
- 高性能体验:支持硬件加速(如 DMA、GPU),自带反锯齿、透明度、平滑动画效果,界面刷新帧率可达 60FPS,触摸响应流畅。
2.典型适用场景
LVGL 专为嵌入式场景设计,覆盖各类需要 GUI 交互的设备,尤其匹配你之前的嵌入式项目(4G 物联网终端),常见场景包括:
- 物联网(IoT)终端:如温湿度监测屏、智能网关面板(对应你项目中 LCD 显示温湿度、GPS 数据的场景)。
- 工业控制设备:如 PLC 触摸屏、传感器控制台,支持显示实时数据、操作按钮、状态警告。
- 智能穿戴设备:如智能手表、手环,适配小尺寸屏幕,支持低功耗运行。
- 汽车电子:如车载中控副屏、仪表盘,支持多页面切换、动画过渡。
- 消费电子:如智能家居控制面板、小型家电显示界面。
🌟 驱动思路
LVGL 与 STM32 的交互主要依赖显示驱动(Disp)、输入驱动(Indev)、定时器驱动(Tick),三者共同构成驱动核心。整体流程为:STM32硬件初始化 → 实现LVGL抽象接口函数 → 向LVGL注册接口 → LVGL调用接口操作硬件。
步骤 1:显示驱动:LVGL ↔ STM32 LCD
显示驱动是最核心的部分,负责将 LVGL 生成的图像数据渲染到 STM32 外接的 LCD/OLED 屏幕上。
- 首先需根据 LCD 的接口类型初始化STM32的硬件外设:
- 初始化步骤:① 配置 LCD 引脚(GPIO 推挽输出 / 复用);② 初始化外设,设置时序;③ 初始化 LCD 控制器
- LVGL 输入接口实现
- LVGL 输入驱动需实现读取回调函数,并注册输入设备类型
- 注册输入驱动
步骤 2:输入驱动–LVGL ↔ STM32 触摸 / 按键
输入驱动负责将 STM32 的输入设备(如触摸屏幕、物理按键)信号转换为 LVGL 可识别的事件(如按下、滑动、释放)。
步骤 3.:定时器驱动(Tick Driver):LVGL 时间基准
LVGL 需要1ms 级的时间基准来实现动画、任务调度、触摸防抖等功能,这由 STM32 的定时器中断提供。
LVGL数据显示修改
其中,LVGL 显示的温度 / 湿度数据,来自代码中这两处:
标签显示:通过lv_label_set_text用DHT11_Data.temp_int(温度)和DHT11_Data.humi_int(湿度)设置文本。
折线图显示:通过lv_chart_set_next_value用上述两个变量更新图表数据。
因此,在这两处代码之前修改数据源,即可单独调整 LVGL 的显示内容。
🎯 单片机程序代码
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"
#include "lv_port_disp_template.h"
#include "lv_conf.h"
#include "lv_port_indev_template.h"
#include "lv_demo_stress.h"
#include "gui_guider.h"
#include "events_init.h"
#include "lv_analogclock.h"
#include "lv_demo_stress.h"
#include "timer.h"
#include "4g.h"
#include "dht11.h"
/*****************************************************
下面就是需要修改的地方,修改服务器的IP地址和端口号
*****************************************************/
#define SERVER4GIP "101.200.212.234"
#define SERVER4GPORT "1001"
/////////////////////////////////////////////////////
lv_ui guider_ui;
uint16_t lv_task_run_time = 0;
uint16_t timer3_count = 0;
uint16_t lv_run_count = 0;
uint16_t com_updata_count = 0;
uint16_t send_data_count = 0;
uint16_t get_gps_count1 = 0;
uint16_t get_gps_count2 = 0;
uint16_t get_sensor_count = 0;
uint16_t get_gps_flag = 0;
int main(void)
{
char label_buf[10];
uint8_t i = 0;
char temp_disp[128];
char *gpsStr;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
CSTX_4GCTR_Init(); //初始化CSTX_4G的供电引脚 对模块进行供电
uart_init(115200); //串口初始化为115200
Uart1_SendStr("UART1 Init Successful\r\n");
Key_Init();
LED_Init(); //初始化与LED连接的硬件接口
LED_Run(); //初始化跑马灯
uart2_init(115200);//初始化和EC200连接串口
Uart2_SendStr("UART2 Init Successful\r\n");
uart3_init(115200);
Uart3_SendStr("UART3 Init Successful\r\n");
printf("\r\n ############ http://www.csgsm.com/ ############\r\n ############("__DATE__ " - " __TIME__ ")############");
lv_init();
lv_port_disp_init();
lv_port_indev_init();
setup_ui(&guider_ui);
events_init(&guider_ui);
LCD_Fill(0,0,128,160,WHITE);
CSTX_4G_Init();//对设备初始化
Start_GPS();//早点开启GPS让他定位好
CSTX_4G_ConUDP();//关闭上一次连接
CSTX_4G_CreateUDPSokcet((u8 *)SERVER4GIP,(u8 *)SERVER4GPORT);//创建一个SOCKET连接
Clear_Buffer_UART1(); //清空串口1的数据
DHT11_Init(); //初始化温湿度 用PA11
printf("开始运行主循环\r\n");
TIM3_Int_Init(10-1,7200-1);//100000
TIM2_Int_Init(65535,7200-1);//100us
while(1)
{
//20ms 运行一次
if(lv_run_count >= 20)
{
TIM2->CNT = 0;
lv_task_handler();
if(TIM2->CNT > lv_task_run_time)
{
lv_task_run_time = TIM2->CNT;
}
//500 ms执行一次
if(com_updata_count >= 500)
{
com_updata_count = 0;
//如果页面处于是第一页
if(lv_scr_act() == guider_ui.screen)
{
i = i < 70?++i:0;
memset(label_buf,0,sizeof(label_buf));
sprintf(label_buf,"%d",DHT11_Data.temp_int);
lv_label_set_text(guider_ui.screen_label_2, label_buf);
memset(label_buf,0,sizeof(label_buf));
sprintf(label_buf,"%d",DHT11_Data.humi_int);
lv_label_set_text(guider_ui.screen_label_4, label_buf);
//第一根折线
lv_chart_set_next_value(guider_ui.screen_chart_1, guider_ui.screen_chart_1_0, DHT11_Data.temp_int);
//第二根折线
lv_chart_set_next_value(guider_ui.screen_chart_1, guider_ui.screen_chart_1_1, DHT11_Data.humi_int);
if( DHT11_Data.temp_int >= 34)
{
if(lv_obj_has_flag(guider_ui.screen_label_5, LV_OBJ_FLAG_HIDDEN) == true)
{
start_label_();//lv_obj_has_flag
lv_obj_clear_flag(guider_ui.screen_label_5, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(guider_ui.screen_label_5, "temp high");
}
}
if( DHT11_Data.humi_int >= 60)
{
if(lv_obj_has_flag(guider_ui.screen_label_5, LV_OBJ_FLAG_HIDDEN) == true)
{
start_label_();//lv_obj_has_flag
lv_obj_clear_flag(guider_ui.screen_label_5, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(guider_ui.screen_label_5, "humi high");
}
}
if(DHT11_Data.humi_int < 60 && DHT11_Data.temp_int < 34)
{
if(lv_obj_has_flag(guider_ui.screen_label_5, LV_OBJ_FLAG_HIDDEN) == false) end_label_();
}
}
}
lv_run_count = 0;
}
if(lv_scr_act() == guider_ui.screen)
{
//1200ms执行一次
if(send_data_count > 1200)
{
//printf("\r\n==111===\r\n");
memset(temp_disp,0,128); //清空需要装载的数组
sprintf(temp_disp,"temperature:%d.0#",DHT11_Data.temp_int); //温湿度打印到数组
CSTX_4G_Senddata(strlen((const char *)temp_disp),(uint8_t *)temp_disp);//发数据
send_data_count = 0;
}
//600ms执行一次
if(get_sensor_count > 600)
{
DHT11_Read_TempAndHumidity(); //读取温湿度
printf("DHT11 温度:%d 湿度:%d \r\n",DHT11_Data.temp_int,DHT11_Data.humi_int); //打印温湿度
get_sensor_count = 0;
}
//接收到数据后执行一次
if(buf_uart1.index>0 && buf_uart1.rx_flag == 1)
{
printf("=====Send PC Data=====\r\n"); //标识 PC 发送过来的数据
CSTX_4G_Senddata(buf_uart1.index,(uint8_t *)buf_uart1.buf); //发送串口1的数据到服务器
Clear_Buffer_UART1(); //清空串口1
LED1=!LED1; //第一个灯闪烁
}
if(get_gps_count1 >= 1500 && get_gps_flag == 0)
{
Get_GPS_LOC();
get_gps_count1 = 0;
get_gps_flag = 1;
get_gps_count2 = 0;
}
if(get_gps_count2 > 1500 && get_gps_flag == 1)
{
gpsStr=Get_GPS_RMC(); //获取GPRMC数据
CSTX_4G_Senddata(strlen((const char *)gpsStr),(uint8_t *)gpsStr);//发数据GPRMC到服务器显示
get_gps_count2 = 0;
get_gps_count1 = 0;
get_gps_flag = 0;
}
CSTX_4G_RECTCPData();//收数据,接收服务器下发的数据并打印到串口1进行显示
}
//1000ms执行一次
if(timer3_count >= 1000)
{
//printf("%d %d\r\n",GPIO_ReadInputDataBit(KEY1_INT_GPIO_PORT, KEY1_INT_GPIO_PIN),GPIO_ReadInputDataBit(KEY2_INT_GPIO_PORT, KEY2_INT_GPIO_PIN));
printf("\r\n[time]-->(%.2f)ms\r\n",lv_task_run_time/10.0f);
timer3_count = 0;
lv_task_run_time = 0;
}
}
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
lv_tick_inc(1);//在中断里面进行刷新
timer3_count++;
lv_run_count++;
com_updata_count++;
send_data_count++;
get_sensor_count++;
get_gps_count1++;
get_gps_count2++;
Key_Handler();
}
}
rs485.c
#include "4G.h"
#include "string.h"
#include "usart.h"
#include "led.h"
///////////////下面是液晶屏头文件/////////////////////
#include "Lcd_Driver.h"
#include "GUI.h"
#include "delay.h"
//#include "Picture.h"
#include "QDTFT_demo.h"
/////////////////////////////////////////////////////
char *strx,*extstrx;
CSTX_4G CSTX_4G_Status; //模块的状态信息
int errcount=0; //发送命令失败次数 防止死循环
int errCountData=0;
char ATSTR[BUFLEN]; //组建AT命令的函数
char GPRMCSTR[BUFLEN]; //转载GPS信息 GPRMC
/*****************************************************
清空模块反馈的信息
*****************************************************/
void Clear_Buffer(void)//清空缓存
{
printf(buf_uart2.buf);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QIURC");//返回+QIURC:,表明接收到TCP服务器发回的数据
if(strx)
{
Gui_DrawFont_GBK16(16,10,RED,WHITE, "RECEIVE DATA");
}
delay_ms(300);
buf_uart2.index=0;
memset(buf_uart2.buf,0,BUFLEN);
}
/*****************************************************
初始化模块 和单片机连接,获取卡号和信号质量
*****************************************************/
void CSTX_4G_Init(void)
{
//打印初始化信息
printf("start init EC200X\r\n");
//发第一个命令ATE1
Uart2_SendStr("ATE1\r\n");
delay_ms(300);
printf(buf_uart2.buf); //打印串口收到的信息
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK
Clear_Buffer();
while(strx==NULL)
{
//Gui_DrawFont_GBK16(16,70,RED,WHITE, "全程技术支持");
Gui_DrawFont_GBK16(16,10,RED,WHITE, "CONNECT 4G..");
printf("单片机正在连接模块......\r\n");
Clear_Buffer();
Uart2_SendStr("ATE1\r\n");
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回OK
}
Gui_DrawFont_GBK16(16,10,RED,WHITE, "CONNECT [OK]");
printf("****单片机和模块连接成功*****\r\n");
Uart2_SendStr("ATI\r\n");//获取模块的版本
delay_ms(300);
Clear_Buffer();
Uart2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"460");//返460,表明识别到卡了
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+CIMI\r\n");//获取卡号,类似是否存在卡的意思,比较重要。
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"460");//返回OK,说明卡是存在的
}
printf("我的卡号是 : %s \r\n",buf_uart2.buf+8);
Clear_Buffer();
Gui_DrawFont_GBK16(16,10,RED,WHITE, "SIMCARD [OK]");
Uart2_SendStr("AT+CGATT?\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CGATT: 1");//返1
Clear_Buffer();
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+CGATT?\r\n");//获取激活状态
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CGATT: 1");//返回1,表明注网成功
}
Gui_DrawFont_GBK16(16,10,RED,WHITE, "REGISTER[OK]");
Clear_Buffer();
Uart2_SendStr("AT+CSQ\r\n");//查看获取CSQ值
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+CSQ:");//返回CSQ
if(strx)
{
printf("信号质量是:%s 注意:信号最大值是31 \r\n",buf_uart2.buf+14);
}
}
/*
AT+QGPSLOC=0
+QGPSLOC: 035658.000,2603.8722N,11912.4159E,1.8,269.9,3,000.00,0.4,0.2,140821,10
OK
AT+QGPSLOC=1
+QGPSLOC: 035702.000,2603.871651,N,11912.416382,E,1.8,268.8,3,000.00,1.2,0.6,140821,11
OK
AT+QGPSLOC=2
+QGPSLOC: 035704.000,26.06451,119.20694,1.8,270.8,3,000.00,0.2,0.1,140821,11
OK
*/
void Get_GPS_LOC(void)
{
Clear_Buffer();
memset(GPRMCSTR,0,BUFLEN);
Uart2_SendStr("AT+QGPSLOC=2\r\n");//查询激活状态
delay_ms(200);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QGPSLOC:");//如果反馈错误就表示没有定位好
if(strx) //没有反馈错误就表示有经纬度了 然后来进行显示 反馈得到LOC就表示有位置了
{
strncpy(GPRMCSTR,strx+21,18);
printf("模块定位好了经纬度是 %s\r\n",GPRMCSTR);
memset(GPRMCSTR,0,BUFLEN);
strncpy(GPRMCSTR,strx+21,8);
//Gui_DrawFont_GBK16(0,90,RED,WHITE, (u8*) GPRMCSTR);
memset(GPRMCSTR,0,BUFLEN);
strncpy(GPRMCSTR,strx+30,9);
//Gui_DrawFont_GBK16(0,110,RED,WHITE, (u8*) GPRMCSTR);
}
Clear_Buffer();
}
/*
AT+QGPSGNMEA="RMC"
+QGPSGNMEA: $GNRMC,035645.00,A,2603.9111,N,11912.4140,E,0.336,,140821,,,A,V*19
OK
*/
char *Get_GPS_RMC(void)
{
Clear_Buffer();
memset(GPRMCSTR,0,BUFLEN);
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//查询激活状态
//delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"$GNRMC");//返1
while(strx==NULL)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPSGNMEA=\"RMC\"\r\n");//获取激活状态
delay_ms(100);
strx=strstr((const char*)buf_uart2.buf,(const char*)"$GNRMC");//返回1,表明注网成功
}
sprintf(GPRMCSTR,"%s",strx);
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "REGISTER[OK]");
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "STARTGPS[OK]");
Clear_Buffer();
GPRMCSTR[2]= 'P';
return GPRMCSTR;
}
void Start_GPS(void)
{
Clear_Buffer();
Uart2_SendStr("AT+QGPS=1\r\n");//查询激活状态
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返1
while(strx==NULL)
{
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"OK");//返回1,表明注网成功
}
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "REGISTER[OK]");
Gui_DrawFont_GBK16(16,10,RED,WHITE, "STARTGPS[OK]");
Clear_Buffer();
}
/*****************************************************
关闭之前存在的和服务器的链接 可能反馈失败
*****************************************************/
void CSTX_4G_ConUDP(void)
{
//关闭之前建立的链接
Uart2_SendStr("AT+QICLOSE=0\r\n");//关闭socekt连接
delay_ms(100);
Uart2_SendStr("AT+QICLOSE=1\r\n");//关闭socekt连接
delay_ms(100);
Uart2_SendStr("AT+QICLOSE=2\r\n");//关闭socekt连接
delay_ms(100);
Clear_Buffer();
}
/*****************************************************
建立TCP链接
*****************************************************/
void CSTX_4G_CreateUDPSokcet(u8 *ServerIP, u8 *Port)//创建sokcet
{
//void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 fc,u16 bc,u8 sizey,u8 mode)
Gui_DrawFont_GBK16(0,30,BLUE,WHITE,ServerIP);
Gui_DrawFont_GBK16(0,50,BLUE,WHITE,"SERVER PORT");
Gui_DrawFont_GBK16(88,50,BLUE,WHITE,Port); //显示IP地址和端口号 中文是16个像素 英文是8个像素
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "SERVER [OK]");
Gui_DrawFont_GBK16(16,10,RED,WHITE, "SERVER [..]");
memset(ATSTR,0,BUFLEN);
sprintf(ATSTR,"AT+QIOPEN=1,0,\"UDP\",\"%s\",%s,0,1\r\n",ServerIP,Port);
Uart2_SendStr(ATSTR);//创建连接TCP,输入IP以及服务器端口号码
delay_ms(300);
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QIOPEN: 0,566");//检查是否登陆成功
if(strx)
{
Gui_DrawFont_GBK16(16,10,RED,WHITE, "SERVER [NO]"); //链接服务器失败
return ; //如果连接服务器失败就反馈 后面不需要判断是否成功了
}
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QIOPEN: 0,0");//检查是否登陆成功
errcount=0;
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QIOPEN: 0,0");//检查是否登陆成功
delay_ms(100);
if(errcount>100) //超时退出死循环 表示服务器连接失败
{
errcount = 0;
break;
}
}
Gui_DrawFont_GBK16(16,10,RED,WHITE, "SERVER [OK]");
Clear_Buffer();
}
/*****************************************************
发送数据函数
*****************************************************/
void CSTX_4G_Senddata(int len,uint8_t *data)//发送字符串数据
{
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "SEND DATA...");
memset(ATSTR,0,BUFLEN);
sprintf(ATSTR,"AT+QISEND=0,%d\r\n",len);
Uart2_SendStr(ATSTR);
//等待模块反馈 >
strx=strstr((const char*)buf_uart2.buf,(const char*)">");//模块反馈可以发送数据了
while(strx==NULL)
{
errcount++;
strx=strstr((const char*)buf_uart2.buf,(const char*)">");//模块反馈可以发送数据了
if(errcount>100) //防止死循环跳出
{
errcount = 0;
break;
}
}
printf("SEND_DATA : %s",data);
Uart2_SendStr((char *)data);//发送真正的数据
strx=strstr((const char*)buf_uart2.buf,(const char*)"ERROR");//如果发送失败
if(strx)
{
errCountData++;
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "SEND DATA NO");
if(errCountData>3) //超时退出死循环 表示服务器连接失败
{
__set_FAULTMASK(1);//关闭总中断
NVIC_SystemReset();//请求单片机重启
}
return ; //发送数据失败了就不要去下面判断是否成功了
}
strx=strstr((const char*)buf_uart2.buf,(const char*)"SEND OK");//检查是否发送成功
errcount=0;
while(strx==NULL) //如果没有收到SEND OK就循环查询
{
errcount++;
strx=strstr((const char*)buf_uart2.buf,(const char*)"SEND OK");//检查是否发送成功
delay_ms(10);
if(errcount>10) //超时退出死循环 表示服务器连接失败
{
errcount = 0;
break;
}
}
Clear_Buffer(); //发送完毕清空
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "SEND DATA OK");
}
/*****************************************************
收到服务器下发的数据就直接打印
*****************************************************/
void CSTX_4G_RECTCPData(void)
{
strx=strstr((const char*)buf_uart2.buf,(const char*)"+QIURC");//返回+QIURC:,表明接收到TCP服务器发回的数据
if(strx)
{
Clear_Buffer();
//Gui_DrawFont_GBK16(16,10,RED,WHITE, "RECEIVE DATA");
}
}
🎯 代码下载链接
https://download.csdn.net/download/qq_41954594/92170392
更多推荐



所有评论(0)