智能手表(Smart Watch)项目

news2024/10/5 14:08:41

文章目录

  • 前言
  • 一、智能手表(Smart Watch)简介
  • 二、系统组成
  • 三、软件框架
  • 四、IAP_F411 App
    • 4.1 MDK工程结构
    • 4.2 设计思路
  • 五、Smart Watch App
    • 5.1 MDK工程结构
    • 5.2 片上外设
    • 5.3 板载驱动BSP
    • 5.4 硬件访问机制-HWDataAccess
      • 5.4.1 LVGL仿真和MDK工程的互相移植
      • 5.4.2 HWDataAccess具体使用方式
    • 5.5 LVGL页面管理-PageManager
      • 5.5.1 PageManager框架
      • 5.5.2 如何在ui app中使用PageManager
    • 5.6 多线程任务


前言

本文主要用于介绍智能手表(Smart Watch)项目的整体概况以及设计思路等。


一、智能手表(Smart Watch)简介

这是一个基于 STM32F411CUE6 和 FreeRTOS + LVGL 的多功能智能手表,主要功能大致有温湿度检测、心率检测、气压海拔检测、抬腕亮屏、无线蓝牙、计算器以及秒表等。

本次手表项目使用到的片上外设包括GPIO, IIC, SPI, USART, TIM, ADC, DMA, 具体的对应PCB板上器件的驱动,例如LCD, EEPROM等。

考虑到使用 keil 软件配置一个新的MCU过程比较繁琐,这里使用到了 CubeMX 用于生成基于HAL库开发的MDK工程。一方面,这是官方主推的方式,另一方面,也是考虑到后续项目有可能会在不同的MCU上进行移植的因素。HAL (硬件抽象层)库提供了更高层次的抽象,使得代码与具体的硬件实现关系较小,我们只需要做少量的修改就可以很容易地将代码移植到其他型号的 STM32 微控制器上。

在这里插入图片描述在这里插入图片描述

二、系统组成

系统框图如下所示,主控使用STM32F411CEU6,操作系统使用FreeRTOS,图形库使用的LVGL。传感器部分:手势识别使用6轴MPU6050,心率血氧使用的是EM7028,海拔测量用的气压计SPL06-001,电子指南针使用LSM303DLHC,蓝牙芯片使用KT6368A,有SPP功能,可以无线升级。
在这里插入图片描述

三、软件框架

整体软件架构如下所示

在这里插入图片描述

整体工程代码分为Bootloader(IAP_F411)和APP(Smart Watch)两部分,为的是方便用户戴在手上进行不用拆解的升级,BOOT区后面划分了一个Flag区,用于记录是否是完整的APP,这个位置是APP传输完成后才记录的,为的是保证程序完整性。

在这里插入图片描述

四、IAP_F411 App

IAP_F411 的主要功能是初始化系统,检测条件以决定是否跳转到应用程序(APP)或进入Bootloader模式进行固件升级,我们先来看一下IAP_F411的目录结构。

4.1 MDK工程结构

如下所示为IAP_F411 的MDK工程结构,我们先来看一下板载驱动BSP侧。

BSP侧为板载驱动模块,主要由KEY按键驱动模块、KT6328蓝牙BLE驱动模块、LCD显示屏驱动模块以及电源管理模块组成。

Core为使用CubeMX生成的外设初始化配置,SYSTEM为自定义的延时模块,Ymodem 是一种用于计算机之间传输文件的协议,在这里使用该协议来传输APP升级包。

Jeff@DESKTOP-HU3NGJN MINGW64 /g/Jeff Projects/MCU/Smart Watch/Codes/IAP_F411
$ tree -L 2
.
|-- BSP
|   |-- KEY
|   |-- KT6328
|   |-- LCD
|   `-- POWER
|-- Core
|   |-- Inc
|   `-- Src
|-- Drivers
|   |-- CMSIS
|   `-- STM32F4xx_HAL_Driver
|-- IAP_F411.ioc
|-- KeilClear.bat
|-- MDK-ARM
|   |-- DebugConfig
|   |-- IAP_F411
|   |-- IAP_F411.uvguix.kingham
|   |-- IAP_F411.uvoptx
|   |-- IAP_F411.uvprojx
|   |-- RTE
|   `-- startup_stm32f411xe.s
|-- SYSTEM
|   |-- delay.c
|   |-- delay.h
|   `-- sys.h
`-- Ymodem
    |-- common.c
    |-- common.h
    |-- flash_if.c
    |-- flash_if.h
    |-- menu.c
    |-- menu.h
    |-- ymodem.c
    `-- ymodem.h

4.2 设计思路

1. 主要变量声明

uint8_t boot_in_menu_flag = 0;
extern pFunction Jump_To_Application;
extern uint32_t JumpAddress;
  • boot_in_menu_flag 用于指示是否进入Bootloader菜单。
  • Jump_To_Application 和 JumpAddress 用于指向用户应用程序的入口点和跳转地址。

2. 外设初始化

SCB->VTOR = FLASH_BASE;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
MX_TIM3_Init();
MX_ADC1_Init();
  • SCB->VTOR = FLASH_BASE: 设置向量表的基地址为Flash的起始位置。
  • HAL_Init(): 初始化硬件抽象层,配置系统时钟等。
  • SystemClock_Config(): 配置系统时钟。
  • 初始化各个外设: 包括GPIO、UART、SPI、定时器和ADC。

3. 硬件初始化

delay_init();
Key_Port_Init();
KT6328_GPIO_Init();
KT6328_Enable();
Power_Init();
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
LCD_Init();
LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);
delay_ms(10);
LCD_Set_Light(50);

初始化延时、按键、BLE模块(KT6328)、电源和PWM,以及LCD屏幕。

4. 检测按键输入

//开机启动时如果按下KEY1, 进入boot中IAP升级模式
  if(HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == 0)
  {
		// 延时判断是否真的按下
    delay_ms(500);
    if(HAL_GPIO_ReadPin(KEY1_PORT, KEY1_PIN) == 0)
    {
      
      LCD_ShowString(72, LCD_H/2, (uint8_t*)"Bootload", WHITE, BLACK, 24, 0);//12*6,16*8,24*12,32*16
      LCD_ShowString(32, LCD_H/2+48, (uint8_t*)"Smart Watch V1.0.0", WHITE, BLACK, 24, 0);

	  boot_in_menu_flag = 1;
			
      //go in boot menu
      FLASH_If_Init();
      Main_Menu();
    }
  }
  • 如果按键KEY1被按下(低电平),则进入Bootloader菜单进行固件更新。
  • LCD_ShowString 用于在LCD上显示Bootloader相关信息。

5. 检查应用程序并跳转

else
{
    uint32_t data1, data2;
    char *str_flag;
    uint32_t address = 0x08008000; // Flash 中数据的起始地址

    data1 = *(uint32_t *)address;
    data2 = *(uint32_t *)(address + sizeof(uint32_t));

    char str1[5], str2[5];
    memcpy(str1, &data1, sizeof(data1));
    memcpy(str2, &data2, sizeof(data2));
    str1[4] = '\0';
    str2[4] = '\0';

    char combined_str[9];
    strcpy(combined_str, str1);
    strcat(combined_str, str2);

    if (strcmp(combined_str, "APP FLAG") == 0)
    {
        //跳转到用户应用程序
        printf("APP FLAG OK, jump to app\r\n");
        SysTick->CTRL = 0X00; // 禁止SysTick
        SysTick->LOAD = 0;
        SysTick->VAL = 0;
        __disable_irq();
        JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
        Jump_To_Application = (pFunction) JumpAddress;
        __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
        Jump_To_Application();
    }
    else
    {
        LCD_ShowString(74, LCD_H/2, (uint8_t*)"No App!", WHITE, BLACK, 24, 0);
        LCD_ShowString(32, LCD_H/2+48, (uint8_t*)"Please Download", WHITE, BLACK, 24, 0);
        HAL_Delay(1000);
    }
}
  • 检查Flash中的标识:从特定地址(0x08008000)读取两个32位数据,拼接成字符串以确认是否存在合法的应用程序(APP FLAG)。
  • 如果存在,系统会禁用SysTick定时器,设置堆栈指针并跳转到应用程序的入口点。
  • 如果没有找到合法的应用程序,LCD会提示“没有应用程序”。

我们再来看一下无线蓝牙升级这部分代码的设计思路

1. 主菜单功能

void Main_Menu(void)
{
    //...
    while (1)
    {
        SerialPutString("\r\n================== Main Menu ============================\r\n\n");
        //...
        key = GetKey();

        if (key == 0x31) { SerialDownload(); }
        else if (key == 0x32) { SerialUpload(); }
        else if (key == 0x33) { 
            // execute the new program 
            // ...
        }
        else if ((key == 0x34) && (FlashProtection == 1)) { 
            // Disable write protection 
        }
        else {
            SerialPutString("Invalid Number ! ...");
        }
    }
}

主菜单功能为用户提供了选择升级、上传、执行程序和禁用Flash写保护的选项。用户可以通过输入数字选择相应的功能。0x31到0x34对应不同的操作。SerialDownload函数用于接收文件并将其写入Flash,成功后设置应用程序标志。SerialUpload函数用于上传当前Flash内容,它们都通过Ymodem协议发送与接收。

五、Smart Watch App

5.1 MDK工程结构

Jeff@DESKTOP-HU3NGJN MINGW64 /g/Jeff Projects/MCU/Smart Watch/Codes/Smart Watch
$ tree -L 2
.
|-- BSP							# 用于存放板载设备驱动
|   |-- AHT21
|   |-- BL24C02
|   |-- EM7028
|   |-- IIC
|   |-- KEY
|   |-- KT6328
|   |-- LCD
|   |-- LSM303DLH
|   |-- MPU6050
|   |-- OWDG
|   |-- POWER
|   |-- SPL06_001
|   `-- TOUCH
|-- Core						# 用于存放CubeMX生成的初始化文件
|   |-- Inc
|   `-- Src
|-- Drivers
|   |-- CMSIS
|   `-- STM32F4xx_HAL_Driver
|-- KeilClear.bat
|-- MDK-ARM 					#用于存放.s文件
|   |-- DebugConfig
|   |-- RTE
|   |-- Smart Watch.kingham
|   |-- Smart Watch.uvoptx
|   |-- Smart Watch.uvprojx
|   |-- output
|   `-- startup_stm32f411xe.s
|-- Middlewares
|   |-- LVGL					# LVGL的底层
|   `-- Third_Party				# FreeRTOS的底层
|-- SYSTEM						# 用于存放自定义的delay.c sys.h等
|   |-- delay.c
|   |-- delay.h
|   `-- sys.h
|-- Smart Watch.ioc
`-- User
    |-- Func					# 用于存放管理函数
    |-- GUI_App					# 用于存放用户的ui app
    |-- Tasks					# 用于存放任务线程的函数
    `-- version.h

5.2 片上外设

本次手表项目使用到的片上外设包括GPIO, IIC, SPI, USART, TIM, ADC, DMA, 具体的对PCB板上器件的驱动,例如LCD, EEPROM等,这里简述一下各个片上外设的用途:

  1. DMA这里主要是配合SPI,SPI通信不通过CPU而是通过DMA直接发送。
  2. IIC主要用来跟Back板的各个传感器进行通信,传感器都挂在一个总线上的。
  3. TIM主要是提供时基,另外一个就是给LCD调节背光。
  4. ADC只接了一个电池的分压,进行电池电压采样,预估剩余电量。
  5. USART接了蓝牙,方便进行IAP和与手机和电脑的助手通信。

5.3 板载驱动BSP

Jeff@DESKTOP-HU3NGJN MINGW64 /g/Jeff Projects/MCU/Smart Watch/Codes/Smart Watch/BSP
$ tree -L 2
.
|-- AHT21
|   |-- AHT21.c
|   `-- AHT21.h
|-- BL24C02
|   |-- BL24C02.c
|   |-- BL24C02.h
|   |-- DataSave.c
|   `-- DataSave.h
|-- EM7028
|   |-- HeartRatelib.lib
|   |-- HrAlgorythm.c
|   |-- HrAlgorythm.h
|   |-- em70x8.c
|   |-- em70x8.h
|   |-- libBp.lib
|   |-- libBpm.lib
|   |-- user_Queue.c
|   `-- user_Queue.h
|-- IIC
|   |-- iic_hal.c
|   `-- iic_hal.h
|-- KEY
|   |-- key.c
|   `-- key.h
|-- KT6328
|   |-- KT6328.c
|   `-- KT6328.h
|-- LCD
|   |-- ST7789.txt
|   |-- lcd.c
|   |-- lcd.h
|   |-- lcd_init.c
|   |-- lcd_init.h
|   |-- lcdfont.h
|   `-- pic.h
|-- LSM303DLH
|   |-- LSM303.c
|   `-- LSM303.h
|-- MPU6050
|   |-- eMPL
|   |-- mpu6050.c
|   `-- mpu6050.h
|-- OWDG
|   |-- WDOG.c
|   `-- WDOG.h
|-- POWER
|   |-- power.c
|   `-- power.h
|-- SPL06_001
|   |-- SPL06_001.c
|   `-- SPL06_001.h
`-- TOUCH
    |-- CST816.c
    `-- CST816.h

板载驱动BSP主要提供了最底层的实现接口,这里简述一下部分BSP:

IIC使用的是软件模拟的方式进行驱动,IIC总线的定义如下,定义了GPIO的口和CLK使能函数,在各个设备中直接用iic_bus_t进行创建IIC,然后调用iic_hal.c中的API即可。

typedef struct
{
        GPIO_TypeDef * IIC_SDA_PORT;
        GPIO_TypeDef * IIC_SCL_PORT;
        uint16_t IIC_SDA_PIN;
        uint16_t IIC_SCL_PIN;

}iic_bus_t;

void IICStart(iic_bus_t *bus);
void IICStop(iic_bus_t *bus);
unsigned char IICWaitAck(iic_bus_t *bus);
void IICSendAck(iic_bus_t *bus);
void IICSendNotAck(iic_bus_t *bus);
void IICSendByte(iic_bus_t *bus, unsigned char cSendByte);
unsigned char IICReceiveByte(iic_bus_t *bus);
void IICInit(iic_bus_t *bus);

uint8_t IIC_Write_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t data);
uint8_t IIC_Write_Multi_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg,uint8_t length,uint8_t buff[]);
unsigned char IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg);
uint8_t IIC_Read_Multi_Byte(iic_bus_t *bus, uint8_t daddr, uint8_t reg, uint8_t length, uint8_t buff[]);

lcd_init模块专注于LCD的初始化和基本配置,是具体的SPI通信协议实现层,主要涉及与LCD硬件的接口,包括引脚定义、GPIO初始化、LCD命令和数据写入等,部分接口定义如下:

void LCD_GPIO_Init(void);//初始化GPIO
void LCD_Writ_Bus(u8 dat);//模拟SPI时序
void LCD_WR_DATA8(u8 dat);//写入一个字节
void LCD_WR_DATA(u16 dat);//写入两个字节
void LCD_WR_REG(u8 dat);//写入一个指令
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2);//设置坐标函数
void LCD_Init(void);//LCD初始化
void LCD_Set_Light(uint8_t dc);
void LCD_Close_Light(void);
void LCD_ST7789_SleepIn(void);
void LCD_ST7789_SleepOut(void);
void LCD_Open_Light(void);

而lcd模块则提供了更高层次的图形绘制功能,包括基本的图形(点、线、矩形、圆等)和文本显示(字符、字符串、汉字等),部分接口定义如下:

void LCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 color);//指定区域填充颜色
void LCD_Color_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u16 *color);
void LCD_DrawPoint(u16 x,u16 y,u16 color);//在指定位置画一个点
void LCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color);//在指定位置画一条线
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2,u16 color);//在指定位置画一个矩形
void Draw_Circle(u16 x0,u16 y0,u8 r,u16 color);//在指定位置画一个圆

void LCD_ShowChinese(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示汉字串
void LCD_ShowChinese12x12(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个12x12汉字
void LCD_ShowChinese16x16(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个16x16汉字
void LCD_ShowChinese24x24(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个24x24汉字
void LCD_ShowChinese32x32(u16 x,u16 y,u8 *s,u16 fc,u16 bc,u8 sizey,u8 mode);//显示单个32x32汉字

void LCD_ShowChar(u16 x,u16 y,u8 num,u16 fc,u16 bc,u8 sizey,u8 mode);//显示一个字符
void LCD_ShowString(u16 x,u16 y,const u8 *p,u16 fc,u16 bc,u8 sizey,u8 mode);//显示字符串
u32 mypow(u8 m,u8 n);//求幂
void LCD_ShowIntNum(u16 x,u16 y,u16 num,u8 len,u16 fc,u16 bc,u8 sizey);//显示整数变量
void LCD_ShowFloatNum1(u16 x,u16 y,float num,u8 len,u16 fc,u16 bc,u8 sizey);//显示两位小数变量

void LCD_ShowPicture(u16 x,u16 y,u16 length,u16 width,const u8 pic[]);//显示图片

5.4 硬件访问机制-HWDataAccess

5.4.1 LVGL仿真和MDK工程的互相移植

为什么加入HWDataAccess.c,而不直接调用BSP的API呢,主要是为了方便移植和管理。

在这里插入图片描述

上面图片所示将User文件夹中的Func文件夹和GUI_APP文件夹,全部复制到LVGL仿真文件夹中,如下所示,即完成了仿真的移植。

在这里插入图片描述

同理,在LVGL仿真中,改完UI App后,想要移植回MDK工程,看下实物效果,也是直接将LVGL仿真中的user_test中的文件复制过去即可。

当然,MDK工程和LVGL仿真工程的移植过程需要改一个东西,就是HWDataAccess.h中的使能:


/***************************
 *  Hardware Define
 ***************************/
/**
 *  if not use, just set 0
 *
 *
 *  if just test ui, no hardware, just set HW_USE_HARDWARE 0
 *
 */

#define HW_USE_HARDWARE 1

#if HW_USE_HARDWARE
  #define HW_USE_RTC    1
  #define HW_USE_BLE    1
  #define HW_USE_BAT    1
  #define HW_USE_LCD    1
  #define HW_USE_IMU    1
  #define HW_USE_AHT21  1
  #define HW_USE_SPL06  1
  #define HW_USE_LSM303 1
  #define HW_USE_EM7028 1
#endif

如果是在仿真中,就把HW_USE_HARDWARE定义为0即可,MDK中自然就是定义为1。使用这个HWDataAccess就方便把硬件抽象出来了。

5.4.2 HWDataAccess具体使用方式

在HWDataAccess.c中,使用结构体进行各个硬件管理,如下代码所示,各个typedef定义在HWDataAccess.h中可以看到。

/***************************
 *  External Variables
 ***************************/
HW_InterfaceTypeDef HWInterface = {
    .RealTimeClock = {
        .GetTimeDate = HW_RTC_Get_TimeDate,
        .SetDate = HW_RTC_Set_Date,
        .SetTime = HW_RTC_Set_Time,
        .CalculateWeekday = HW_weekday_calculate
    },
    .BLE = {
        .Enable = HW_BLE_Enable,
        .Disable = HW_BLE_Disable
    },
    .Power = {
        .power_remain = 0,
        .Init = HW_Power_Init,
        .Shutdown = HW_Power_Shutdown,
        .BatCalculate = HW_Power_BatCalculate
    },
    .LCD = {
        .SetLight = HW_LCD_Set_Light
    },

    .IMU = {
        .ConnectionError = 1,
        .Steps = 0,
        .wrist_is_enabled = 0,
        .wrist_state = WRIST_UP,
        .Init = HW_MPU_Init,
        .WristEnable = HW_MPU_Wrist_Enable,
        .WristDisable = HW_MPU_Wrist_Disable,
        .GetSteps = HW_MPU_Get_Steps,
        .SetSteps = HW_MPU_Set_Steps
    },
    .AHT21 = {
        .ConnectionError = 1,
        .humidity = 67,
        .temperature = 26,
        .Init = HW_AHT21_Init,
        .GetHumiTemp = HW_AHT21_Get_Humi_Temp
    },
    .Barometer = {
        .ConnectionError = 1,
        .altitude = 19,
        .Init = HW_Barometer_Init,
    },
    .Ecompass = {
        .ConnectionError = 1,
        .direction = 45,
        .Init = HW_Ecompass_Init,
        .Sleep = HW_Ecompass_Sleep
    },
    .HR_meter = {
        .ConnectionError = 1,
        .HrRate = 0,
        .SPO2 = 99,
        .Init = HW_HRmeter_Init,
        .Sleep = HW_HRmeter_Sleep
    }
};

如何在UI层使用HWDataAccess呢,例如在HomePage中的调节LCD亮度的回调函数中,直接调用HWInterface.LCD.SetLight(ui_LightSliderValue)即可。

void ui_event_LightSlider(lv_event_t * e)
{
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_target(e);
    if(event_code == LV_EVENT_VALUE_CHANGED)
    {
        ui_LightSliderValue = lv_slider_get_value(ui_LightSlider);
        HWInterface.LCD.SetLight(ui_LightSliderValue);
    }
}

那么他是如何在有硬件的MDK工程中也能用,LVGL无硬件的仿真也能用呢?

HW_InterfaceTypeDef HWInterface = {
    // 省略前面
    .LCD = {
            .SetLight = HW_LCD_Set_Light
        },
    // 省略后面
}

首先看到HWInterface.LCD.SetLight定义的是函数HW_LCD_Set_Light,而这个函数的内容如下,即当HW_USE_LCD使能时,运行这个函数,能够正常调光,当LVGL仿真中不使能硬件HW_USE_HARDWARE时, HW_USE_LCD也不使能,则此函数执行空,工程也不会报错。

void HW_LCD_Set_Light(uint8_t dc)
{
    #if HW_USE_LCD
        LCD_Set_Light(dc);
    #endif
}

5.5 LVGL页面管理-PageManager

5.5.1 PageManager框架

在GUI_App文件夹中,Screen文件夹存放着所有的page,由于screen很多,所以有必要进行页面管理。这里开一个栈进行页面管理。

首先看到PageManager.h,Page_t结构体是用于描述一个LVGL页面的,里面的对象有初始化函数init,反初始化函数deinit以及一个用于存放lvgl对象的地址的lv_obj_t **page_obj。

PageStack_t结构体描述一个界面栈,用于存放Page_t页面结构体,top表示栈顶。

#ifndef PAGE_STACK_H
#define PAGE_STACK_H

#include "../../GUI_App/ui.h"

// 页面栈深度
#define MAX_DEPTH 6

// 页面结构体
typedef struct {
    void (*init)(void);
    void (*deinit)(void);
    lv_obj_t **page_obj;
} Page_t;


// 页面堆栈结构体
typedef struct {
    Page_t* pages[MAX_DEPTH];
    uint8_t top;
} PageStack_t;

extern PageStack_t PageStack;


Page_t* Page_Get_NowPage(void);
void Page_Back(void);
void Page_Back_Bottom(void);
void Page_Load(Page_t *newPage);
void Pages_init(void);


#endif // PAGE_STACK_H

在pop函数中,除了将top减1,还调用了页面deinit函数,负责反初始化当前页面。

stack->pages[--stack->top]->deinit();

Page_Back(), Page_Back_Bottom(), Page_Load()就是主要在代码中调用的函数了,分别的作用是Back到上一个界面,Back到最底部的Home界面,以及load新的界面。

5.5.2 如何在ui app中使用PageManager

这里以代码比较少的ui_ChargPage.c为例,这个page是使用square line生成的,一般会生成ui_ChargPage_screen_init函数。

首先我们需要注册一个Page结构体存储当前的页面,填充好初始化init,反初始化函数deinit以及LVGL页面对象&ui_ChargPage,然后deinit是用于删除定时器timer的,这里的timer主要用于刷当前页面的数据,所以不在当前页面时需要删除掉。

// 省略前面...

/ Page Manager //
Page_t Page_Charg = {ui_ChargPage_screen_init, ui_ChargPage_screen_deinit, &ui_ChargPage};

/// Timer //
// need to be destroyed when the page is destroyed
static void ChargPage_timer_cb(lv_timer_t * timer)
{
    if(Page_Get_NowPage()->page_obj == &ui_ChargPage)
    {
        // 刷新数据等操作
    }
}

/ SCREEN init 
void ui_ChargPage_screen_init(void)
{
    // 省略中间...
    // private timer
    ui_ChargPageTimer = lv_timer_create(ChargPage_timer_cb, 2000,  NULL);
}

/// SCREEN deinit 
void ui_ChargPage_screen_deinit(void)
{
  lv_timer_del(ui_ChargPageTimer);
}

// 省略后面...

5.6 多线程任务

本项目用的是CMSIS_OS_V2的API,Tasks文件及其作用如下所示。

├─Application/User/Tasks            # 用于存放任务线程的函数
│  ├─user_TaskInit.c                # 初始化任务
│  ├─user_HardwareInitTask.c        # 硬件初始化任务
│  ├─user_RunModeTasks.c            # 运行模式任务
│  ├─user_KeyTask.c                 # 按键任务
│  ├─user_DataSaveTask.c            # 数据保存任务
│  ├─user_MessageSendTask.c         # 消息发送任务
│  ├─user_ChargeCheckTask.c         # 充电检查任务
│  ├─user_SensUpdateTask.c          # 传感器更新任务
│  ├─user_ScrRenewTask.c            # 屏幕刷新任务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSRF | CSRF 漏洞介绍

关注这个漏洞的其他相关笔记:CSRF 漏洞 - 学习手册-CSDN博客 0x01:CSRF 漏洞简介 CSRF(Cross-Site request forgery,跨站请求伪造)也被称为 One Click Attack 或者 Session Riding,通常缩写为 CSRF 或者 X…

【Java】IntelliJ IDEA开发环境安装

一、下载 官方地址:https://www.jetbrains.com/idea/ 点击Download直接下载 二、安装 双击安装包,点击Next 选择安装路径,点击Next 勾选安装内容 安装完成。 三、创建项目 打开IDEA,填写项目名称,选择项目安装路径…

S7-200 SMART的数据类型说明

S7-200 SMART的数据主要分为: 与实际输入/输出信号相关的输入/输出映象区: I:数字量输入(DI)Q:数字量输出(DO)AI:模拟量输入AQ:模拟量输出 内部数据存储区…

STM32 Hal库SDIO在FATFS使用下的函数调用关系

STM32 Hal库SDIO在FATFS使用下的函数调用关系 本文并不将FATFS的相关接口操作,而是将HAL在使用FATFS通过SDIO外设管理SD卡时,内部函数的调用逻辑,有助于当我们使用CUBEMX生成FATFS读取SD卡的代码时无法运行时Debug。本文也会说明一些可能出现…

如何编写一个优雅的commit message

在Git中,git commit 命令扮演着至关重要的角色。它的主要作用是将暂存区(staging area)里的改动内容提交到本地仓库(repository)中,形成一个新的版本或提交(commit)。这个过程是 Git…

渗透测试入门学习——使用python脚本自动识别图片验证码,OCR技术初体验

写在前面 由于验证码在服务端生成后存储在服务器的session中,而标用于标识用户身份的sessionid存在于用户cookie中 所以本次识别验证码时需要用requests.session()创建会话对象,模拟真实的浏览器行为,保持与服务器的会话才能获取登录时服务…

wsl2 ubuntu 桥接以太网卡

注意:此方法需要至少 Windows 11 22H2。桥接模式就是将主机网卡与虚拟机虚拟的网卡利用虚拟网桥进行通信。 在桥接的作用下,类似于把宿主机虚拟为一个交换机,所有桥接设置的虚拟机连接到这个交换机的一个接口上,宿主机也同样插在这…

通信工程学习:什么是RARP反向地址解析协议

RARP:反向地址解析协议 RARP(Reverse Address Resolution Protocol,反向地址解析协议)是一种网络协议,其主要作用是在设备只知道物理地址(如MAC地址)时,允许其从网关服务器的地址解析…

致亲爱的Android studio

你的未来发展趋势: 可不可以把兼容性,什么的搞得更好。起因是我想写期末大作业,然后简单的把功能写的差不多了之后,我就想到处看看有没有一套比较好的类似于组件库的东西,但是没找到,然后就把目标锁定到了G…

Vue入门-Node.js安装

进入Node.js中文网 ​​​​​​​点击进入Node.js中文网 或者手动输入网址: https://www.nodejs.com.cn/download.html 点击下载64位安装包: 下载好之后双击进行安装 可选择个性化安装或默认安装 直接点【Next】按钮,此处可根据个人需求…

深度解析 HTTP

我的主页:2的n次方_ 1. HTTP 的简单介绍 HTTP :超文本传输协议,不仅能传输文本,还能传输图片,音频文件,视频 目前基本上都用的是 1.1 版本 https 可以认为是 http 的升级版,区别就是引入了…

【pytorch】张量求导4

再再接上文,看到作者有一个关于向量乘矩阵的描述。 经过搜索发现,现在的pytorch已经修复了这一问题,提供了mv()和matmul()两种方式实现矩阵和一维向量的乘积,可以参看这篇文章。 经过查阅pytorch的文件,找到了cuda侧…

如何利用 Kubernetes 取得成功

Kubernetes是一个开源编排平台,用于自动部署和管理容器化工作负载和服务,目前越来越受欢迎。 该平台由全球贡献者社区维护,其潜在优势包括提高资源效率、提高可扩展性和高可用性。 在过去几年中,Kubernetes 和相关的云原生技术已…

[Qt] 基于 Qt 的文件选择与图片显示功能实现

文章目录 基础版本:open1()功能解析:特点与限制: 增加路径记忆功能:open2()功能解析:特点与改进: 使用智能指针优化内存管理:open3()功能解析:特点与改进: 图片自适应窗口…

线程互斥函数的例子

代码 #include<stdio.h> #include<pthread.h> #include<sched.h> void *producter_f(void *arg); void *consumer_f(void *arg); int buffer_has_item0; pthread_mutex_t mutex; int running1; int main(void) {pthread_t consumer_t;pthread_t producter_t…

PCL 点云体素滤波

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 体素滤波实现 2.1.2 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xf…

Session会话管理技术

Session会话管理技术 会话: 两个交互,在开发中是指浏览器和服务器它们两个的交互 会话管理: 管理会话中产生的数据,一般是记录登录状态 补充: 状态管理,就是管理数据 1、 Session概述 Session用于记录用户的状态。Session指的是在一段时间内&#xff0c;单个客户端与Web服务…

【C++】—— 类和对象(中)

【C】—— 类和对象(中) 文章目录 【C】—— 类和对象(中)前言1. 类的默认成员函数2. 构造函数3. 析构函数4. 拷贝构造函数5. 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载 结语 前言 小伙伴们大家好呀&#xff0c;昨天的 【C】——类和对象(上) 大家理解的怎么样了 今天…

【AI人工智能】文心智能体,双人冒险游戏智能体创作分享

背景 最近半年&#xff0c;“AI agent”&#xff08;智能体&#xff09;这一词汇变得非常热门。许多人以为创建自己的智能体会很复杂&#xff0c;实际上&#xff0c;现有的平台已经大大降低了操作门槛。只要有创意&#xff0c;几乎每个人都可以轻松创建属于自己的智能体。今天…

WordPress响应式Git主题响应式CMS主题模板

兼容 IE9、谷歌 Chrome 、火狐 Firefox 等主流浏览器 扁平化的设计加响应式布局&#xff0c;兼容电脑、和各个尺寸手机的完美响应 主题设置面板新增多种AD位&#xff0c;PC端和移动设备各不相同 在主题设置选项中就可以进行基本的SEO设置&#xff1a;首页、分类、文章等页面…