智能手表(Smart Watch)项目
文章目录
- 前言
- 一、智能手表(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*16LCD_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 menuFLASH_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; // 禁止SysTickSysTick->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等,这里简述一下各个片上外设的用途:
- DMA这里主要是配合SPI,SPI通信不通过CPU而是通过DMA直接发送。
- IIC主要用来跟Back板的各个传感器进行通信,传感器都挂在一个总线上的。
- TIM主要是提供时基,另外一个就是给LCD调节背光。
- ADC只接了一个电池的分压,进行电池电压采样,预估剩余电量。
- 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_LCDLCD_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 timerui_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 # 屏幕刷新任务
相关文章:

智能手表(Smart Watch)项目
文章目录 前言一、智能手表(Smart Watch)简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…...

设计模式~~~
简单工厂模式(静态工厂模式) 工厂方法模式 抽象工厂角色 具体工厂角色...

Golang | Leetcode Golang题解之第458题可怜的小猪
题目: 题解: func poorPigs(buckets, minutesToDie, minutesToTest int) int {if buckets 1 {return 0}combinations : make([][]int, buckets1)for i : range combinations {combinations[i] make([]int, buckets1)}combinations[0][0] 1iterations…...
欢聚时代(BIGO)Android面试题及参考答案
网络 TCP 和 UDP 协议的区别是什么? TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)是两种不同的传输层协议,它们有以下主要区别: 一、连接性 TCP 是面向连接的协议。在通信之前,需要通过三次握手建立连接,通信结束…...

[C语言]指针和数组
目录 1.数组的地址 2.通过指针访问数组 3.数组和指针的不同点 4.指针数组 1.数组的地址 数组的地址是什么? 看下面一组代码 #include <stdio.h> int main() { int arr[5] {5,4,3,2,1}; printf("&arr[0] %p\n", &arr[0]); printf(&qu…...

Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9
最近折腾小主机,搭建项目环境,记录相关步骤 数据无价,丢失难复 1. Centos Stream 9备份与恢复 1.1 系统备份 root权限用户执行进入根目录: cd /第一种方式备份命令: tar cvpzf backup.tgz / --exclude/proc --exclu…...
Linux的发展历史与环境
目录: 引言Linux的起源早期发展企业级应用移动与嵌入式系统现代计算环境中的Linux结论 引言 Linux,作为开源操作系统的代表,已经深刻影响了全球的计算环境。从其诞生之初到如今成为服务器、嵌入式系统、移动设备等多个领域的核心,…...
Jax(Random、Numpy)常用函数
目录 Jax vmap Array reshape Random PRNGKey uniform normal split choice Numpy expand_dims linspace jax.numpy.linalg[pkg] dot matmul arange interp tile reshape Jax jit jax.jit(fun, in_shardingsUnspecifiedValue, out_shardingsUnspecifiedVa…...

python-pptx 中 placeholder 和 shape 有什么区别?
在 python-pptx 库中,placeholder 和 shape 是两个核心概念。虽然它们看起来相似,但在功能和作用上存在显著的区别。为了更好地理解这两个概念,我们可以通过它们的定义、使用场景以及实际代码示例来剖析其差异。 Python-pptx 的官网链接&…...

王者农药更新版
一、启动文件配置 二、GPIO使用 2.1基本步骤 1.配置GPIO,所以RCC开启APB2时钟 2.GPIO初始化(结构体) 3.给GPIO引脚设置高/低电平(WriteBit) 2.2Led循环点亮(GPIO输出) 1.RCC开启APB2时钟。…...

各省份消费差距(城乡差距)数据(2005-2022年)
消费差距,特别是城乡消费差距,是衡量一个国家或地区经济发展均衡性的重要指标。 2005年-2022年各省份消费差距(城乡差距)数据(大数据).zip资源-CSDN文库https://download.csdn.net/download/2401_84585615/…...

[Linux] 进程创建、退出和等待
标题:[Linux] 进程创建、退出和等待 个人主页水墨不写bug (图片来源于AI) 目录 一、进程创建fork() 1) fork的返回值: 2)写时拷贝 编辑3)fork常规用法 4ÿ…...

微软推出针对个人的 “AI伴侣” Copilot 会根据用户的行为模式、习惯自动进化
微软推出了为每个人提供的“AI伴侣”Copilot,它不仅能够理解用户的需求,还能根据用户的日常习惯和偏好进行适应和进化。帮助处理各种任务和复杂的日常生活场景。 它能够根据用户的生活背景提供帮助和建议,保护用户的隐私和数据安全。Copilot…...

【QT】QT入门
个人主页~ QT入门 一、简述QT1、什么是QT2、QT的优势3、应用场景 二、QT的基本使用1、新建项目(1)选择项目模版(2)选择项目路径(3)选择构建系统(4)填写类信息设置界面(5&…...
Linux 6.11版本发布
Linux 6.11版本的发布是Linux社区的一个重要里程碑,它不仅在实时计算、性能优化方面取得了显著进展,还在安全性上迈出了关键一步。 一、实时计算与性能优化 1.io_uring子系统支持 Linux 6.11引入了io_uring子系统的增强功能,特别是支持了b…...
CSS 参考手册
CSS 参考手册 概述 CSS(层叠样式表)是一种用于描述HTML或XML文档样式的样式表语言。它用于控制网页的布局和外观,使网页设计更加美观和响应式。CSS可以定义文本颜色、字体、布局、响应式设计等,是网页设计和开发中不可或缺的一部分。 基础语法 CSS的基本语法由选择器和…...
数据采集工具sqoop介绍
文章目录 什么是sqoop?一、Sqoop的起源与发展二、Sqoop的主要功能三、Sqoop的工作原理四、Sqoop的使用场景五、Sqoop的优势六、Sqoop的安装与配置 sqoop命令行一、Sqoop简介与架构二、Sqoop特点三、Sqoop常用命令及参数四、使用示例五、注意事项 什么是sqoop? Sqoop是一款开…...

扫盲:写给UI设计师的SCADA系统知识点
一、SCADA是什么,及其组成。 SCADA(Supervisory Control And Data Acquisition,监控与数据采集系统)是一种用于实时监控、控制和数据采集的自动化系统。 SCADA的组成部分: - 人机界面(HMI*:提…...
类的特殊成员函数——三之法则、五之法则、零之法则
系统中的动态资源、文件句柄(socket描述符、文件描述符)是有限的,在类中若涉及对此类资源的操作,但是未做到妥善的管理,常会造成资源泄露问题,严重的可能造成资源不可用。或引发未定义行为,进而…...

计算机毕业设计 智慧物业服务系统的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...