IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网) -> 正文阅读

[嵌入式]STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)

STM32+ESP8266(ESP-12F)实现在线OTA升级(腾讯云物联网)

一、硬件及软件准备

1、完整工程源码

下载地址:https://download.csdn.net/download/qq_44062900/85060833

2、硬件:STM32单片机、ESP8266(ESP-12F)

在这里插入图片描述

注:ESP8266刷的固件是标准的AT指令固件,不同版本AT固件的AT指令略有不同,请注意!

二、实现效果

  1. 通过开机时按下S2按键进入BootLoader
  2. 按下S3按键上报固件版本以获取升级的固件
  3. 按下S4按键开始下载OTA固件
  4. 按下S5按键开始校验固件、写入到APP区并运行

1、串口打印效果图

在这里插入图片描述

2、云端效果图

在这里插入图片描述

三、FLASH分区

本文使用的STM32型号为STM32F103C8T6,FLASH大小为64KB。FLASH分区如下所示:

分区名称分区起始地址分区大小
BootLoader 程序0x80000000x3C00(15KB)
APP版本信息0x8003C000x400(1KB)
APP程序0x80040000x6000(24KB)
OTA分区0x800A0000x6000(24KB)

注意这里的BootLoader程序与官方的BootLoader程序不同。官方的BootLoader程序在出厂时已经固化到ROM中,不能修改。这里的BootLoader程序是用户自行编写的BootLoader,用于OTA升级操作,会占用FLASH大小。

  1. BootLoader分区
    用于实现OTA升级操作,与普通编写的业务程序无本质区别。这里的BootLoader大小根据编写的BootLoader程序大小而定。如果BootLoader程序比较小,可以适当减小该分区的大小,以保证APP程序分区有足够的空间。
  2. APP版本信息分区
    这里用了1KB大小来存储APP版本信息,在刷写BootLoader之后,这个分区是没有任何版本信息的,所以在第一次运行BootLoader时会判断APP版本信息是否被写入过,如果没有被写入过则会自动写入“1.0”的APP版本到该分区中。在升级成功后会将新版本的APP版本信息写入到这个分区。记录APP版本信息主要是用在获取OTA固件时需要上传到云平台,云平台根据当前的APP版本才能知道推送哪个版本升级固件。为什么要1KB的大小?因为STM32F103C8T6的扇区大小是1KB,在写入FLASH时要先擦除FLASH才能写入,而擦除FLASH只能按扇区大小擦除。所以这里最小只能分配1KB。不同容量的MCU的扇区大小不同,可以查看手册。
  3. APP程序分区
    APP分区是用户实现的业务逻辑程序,就是想要系统实现什么功能,把APP程序写入到这个分区就可以了,但这个写入操作一般不是用户自己手动烧写,是由BootLoader程序来写入。当然,这里可以通过将BootLoader和APP程序合并后一起写入,但是有点麻烦,这里不采用这种方式。这里的APP程序是通过云平台上传后,然后进入BootLoader后下载并写入。这个分区是运行的主分区,如果没有手动进入BootLoader程序,则会自动跳转到这个分区。
  4. OTA分区
    这个分区用于存放下载的APP程序,本质上与要运行的APP程序无任何差别,在升级完成后会将该分区的数据擦除。为什么要OTA分区?不能直接将下载的APP程序写入APP区吗?当然可以直接将下载的APP程序写入APP区,但是这需要在数据传输过程中APP程序下载完整,如果有一个字节出现错误,则会导致校验失败,APP程序自然就崩溃了。这种情况是很常见的,比如说在升级过程中突然断电。这种情况下不能回滚到之前的版本,这种情况是致命的,当然你可以尝试重新下载并写入,直到升级成功。为了保险起见,OTA分区的作用就来了,首先将下载的OTA固件放入这个分区,固件校验通过后再将APP程序从OTA分区搬运到APP分区中。即使传输过程出现错误导致固件不完整,用户也可以执行之前的APP程序。

四、OTA升级流程

在这里插入图片描述

腾讯云官方关于固件升级文档介绍:https://cloud.tencent.com/document/product/1081/39359
有关如何在控制台创建项目、产品及设备请参考此文:https://blog.csdn.net/qq_44062900/article/details/118067766

简单来说就是以下步骤:

  1. 首先连接MQTT服务器,并且订阅“$ota/update/${productID}/${deviceName}”主题,其中“${productID}”和“${deviceName}”分别为产品ID和设备名,如"$ota/update/ZD0FYH1ONP/test"。
  2. 上报当前APP版本,这个很重要,云端是根据APP版本来推送OTA固件的。发布的主题为"$ota/report/${productID}/${deviceName}",上报的协议格式如下:
{
"type": "report_version",
"report":{
    "version": "1.0"
}
}
// type:消息类型
// version:上报的版本号
  1. 在控制台上传OTA升级固件,选择待升级的版本号,创建OTA升级任务。
  2. 云端自动下发OTA升级消息到设备,消息格式如下:
{
"file_size": 708482,
"md5sum": "36eb5951179db14a631463a37a9322a2",
"type": "update_firmware",
"url": "https://ota-1255858890.cos.ap-guangzhou.myqcloud.com",
"version": "1.1"
}
// type:消息类型为update_firmware
// version:升级版本
// url:下载固件的url
// md5asum:固件的MD5值
// file_size:固件大小,单位为字节
  1. 设备根据根下发的URL 下载固件,本文使用的是HTTP协议进行固件分包下载。
  2. 下载完成升级固件后,校验固件MD5值。如果校验通过,则将下载的固件写入到APP分区。
  3. 校验APP分区MD5值,向云端上传当前升级后的APP版本号以通知云端升级成功,运行APP程序。

五、OTA固件上传

注意:请确保已经在控制台中创建了项目、产品及设备。在上传固件前,请在设备端进行一次上传APP版本号操作,否则后面会找不到要升级的版本号。已上传版本号可以在设备详情页看到上传的版本号,如下图所示:
在这里插入图片描述

  1. 在进入项目后,依次点击"固件升级"、“添加固件”。

在这里插入图片描述

  1. 在弹出的对话框中填入固件信息,并上传固件,这里上传的固件格式为.bin,后面介绍如何在keil5中生成。
    在这里插入图片描述

  2. 上传固件后,点击"固件升级"开始创建升级任务。
    在这里插入图片描述

  3. 选择待升级的APP版本号,如果没有出现待升级的版本号,说明你在上传固件前设备未上报版本号,所以会找不到,解决方法是在上传固件前在设备端上传一次版本号即可。
    在这里插入图片描述

  4. 在保存后,云端会向设备端发送一条OTA升级消息,如果没有收到OTA升级消息,请检查是否成功订阅了"$ota/update/${productID}/${deviceName}"主题。在确定订阅成功后,手动再向云端上传一次版本号即可收到OTA升级消息。

六、BootLoader程序配置

修改BootLoader起始地址和大小。
在这里插入图片描述

七、应用程序配置

  1. 设置APP分区FLASH起始地址及大小
    这里根据划分的FLASH分区填写APP程序起始地址及大小。在这里插入图片描述

  2. 设置中断向量表偏移
    在main函数中设置中断向量表地址,中断向量表的地址为APP的起始地址。在这里插入图片描述

  3. 生成.bin二进制文件
    设置好后,点击重新编译即可生成二进制文件。 生成的二进制文件在工程目录中的"Objects"文件夹下。
    在这里插入图片描述
    在这里插入图片描述

八、核心代码

  1. ota.c
#include "ota.h"
#include <string.h>
#include <stdlib.h>
#include "usart.h"
#include "esp8266.h"
#include "iap.h"
#include "delay.h"
#include "tencent_mqtt.h"
#include "md5.h"


/*
函数功能:初始化OTA,连接MQTT服务器
参数:无
返回值:无
*/
void MQTT_OTA_Init(void)
{
	ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,SERVER_IP,SERVER_PORT);//连接TCP服务器并进入透传模式
	if(MQTT_Connect(CLIENT_ID,USER_NAME,PASSWORD,KEEPALIVE_TIME))//登录MQTT服务器
    {
        printf("MQTT Server Connect Failure\r\n");
    }
	else 
	{
		printf("MQTT Server Connect success\r\n");	
		if(MQTT_Subscribe_Topic(OTA_SUB_TOPIC,REQ_QOS_0,1))//订阅OTA下行消息
		{
			printf("OTA Topic Subscribe Failure\r\n");
		}
		else
		{
			printf("OTA Topic Subscribe success\r\n");
		}			
	}
}


/*
函数功能:上报固件版本以获取OTA固件
参数:无
返回值:无
*/
void Get_OTA_Firmware(ota_info_t *ota_info)
{
    char buff[100];
	usart3.flag=0;
	usart3.cnt=0;
	memset((char *)usart3.rx_buff,0,USART_RX_MAXLEN);//清空接收缓冲区
    snprintf(buff,sizeof(buff),OTA_VERSION_UPLOAD_TEMPLATE,ota_info->current_version);
	MQTT_Publish(OTA_PUB_TOPIC,(uint8_t*)buff);//上报固件版本
}

/*
函数功能:从接收buff中获取ota信息
参数:char *rev_buff,ota_info_t *ota_info
返回值:无
*/
uint8_t Get_OTA_Info(char *rev_buff,ota_info_t *ota_info)
{
	char *data_pt_start=NULL;
	char *data_pt_end=NULL;
	char buff[100];
    uint8_t get_state=0;
	if(strstr(rev_buff,"\"file_size\""))//获取固件大小
	{
		data_pt_start=strstr(rev_buff,"\"file_size\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(buff,data_pt_end-data_pt_start-11,"%s",data_pt_start+12);
		ota_info->file_size=atoi(buff);
        get_state=1;
	}
	if(strstr(rev_buff,"\"md5sum\""))//获取MD5值
	{
		data_pt_start=strstr(rev_buff,"\"md5sum\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(ota_info->md5sum,data_pt_end-data_pt_start-10,"%s",data_pt_start+10);
        get_state=1;
	}
	if(strstr(rev_buff,"\"url\""))//获取下载URL
	{
		data_pt_start=strstr(rev_buff,"\"url\"");
		data_pt_end=strstr(data_pt_start,",");
		snprintf(ota_info->url,data_pt_end-data_pt_start-7,"%s",data_pt_start+7);
        get_state=1;
	}
	if(strstr(ota_info->url,"https://"))//获取OTA主机域名
	{
		data_pt_start=strstr(rev_buff,"https://");
		data_pt_end=strstr(data_pt_start+8,"/");
		snprintf(ota_info->host,data_pt_end-data_pt_start-7,"%s",data_pt_start+8);
		get_state=1;
	}

	if(strstr(rev_buff,"\"version\""))//获取固件版本
	{
		data_pt_start=strstr(rev_buff,"\"version\"");
		data_pt_end=strstr(data_pt_start,"}");
		snprintf(ota_info->ota_version,data_pt_end-data_pt_start-11,"%s",data_pt_start+11);
	}
    return get_state;
}

/*
函数功能:开始下载OTA固件
参数:ota_info_t *ota_info
返回值:u8
*/
static u8 DownLoad_OTA_Firmware(ota_info_t *ota_info)
{
	int i;
	char *data_pt_start=NULL;
	u8 time_cnt=0;
	u8 data_packet_num=0;
	if(ota_info->file_size<=APP1_FLASH_SIZE)//判断固件是否超过FLASH大小
	{
		if(!ESP_UnvarnishedMode_Init(WIFI_SSID,WIFI_PWD,ota_info->host,80))//连接OTA固件下载服务器
		{
			//计算分包下载次数
			if(ota_info->file_size%1024==0) data_packet_num=(ota_info->file_size/1024)-1;
            else  data_packet_num=ota_info->file_size/1024;
			for(i=0;i<=data_packet_num;i++)//分包下载
			{
					usart3.flag=RX_BUFF_EMPTY;//接收标志位清零
					usart3.cnt=0;
					memset(usart3.rx_buff,0,sizeof(usart3.rx_buff));//清空接收缓冲区
					snprintf(ota_info->http_request,sizeof(ota_info->http_request),
                        "GET http://%s HTTP/1.1\r\nHost:%s\r\nRange:bytes=%d-%d\r\n\r\n",
                    ota_info->url+8,ota_info->host,i*1024,(i+1)*1024-1);//序列化HTTP请求报文
					USARTx_Send_String(USART3,ota_info->http_request);//发起HTTP下载固件请求
					while(usart3.flag==RX_BUFF_EMPTY)//等待接收数据完成
					{
						Delay_Ms(100);
						if(++time_cnt>=30) return HTTP_REQUEST_TIMEOUT;//HTTP请求超时
					}
					time_cnt=0;
					if(strstr((char*)usart3.rx_buff,"\r\n\r\n"))//寻找数据包头部
					{
						data_pt_start=strstr((char*)usart3.rx_buff,"\r\n\r\n");
                        iap_write_flash(FLASH_OTA_ADDR+i*1024,(u8 *)data_pt_start+4,1024);
						printf("总大小:%d Bytes,写入第%d包,%d-%d Bytes已写入\r\n",ota_info->file_size,i+1,i*1024,(i+1)*1024-1);
					}
					else
                    {
                        printf("未找到头部数据\r\n");
                        return GET_OTA_DATA_PACKET_FAIL;//解析OTA数据包失败
                    }
			}
            printf("固件下载成功\r\n");
			return FIRMWARE_DOWNLOAD_SUCC;//固件下载成功
		}
		else 
        {
            printf("进入透传模式失败\r\n");
            return SERVER_CONNECT_FAIL;//服务器连接失败
        }
	}
	else
	{
		printf("OTA固件大小超出范围\r\n");
		return OTA_PACKET_SIZE_OVERFLOW;
	}
	
}

/*
函数功能:开始OTA更新
参数:ota_info_t *ota_info,char *check_md5
返回值:无
*/
void Start_OTA_Update(ota_info_t *ota_info,char *check_md5)
{
	if(ota_info->file_size!=0)
	{
		printf("Firmware Size:%d Bytes,Downloading...\r\n",ota_info->file_size);
		if(DownLoad_OTA_Firmware(ota_info)==FIRMWARE_DOWNLOAD_SUCC)//开始下载固件
		{
			Compute_string_md5((u8*)FLASH_OTA_ADDR,ota_info->file_size, check_md5);//计算APP2 MD5值
			printf("OTA Firmware MD5:%s\r\nFirmware Download Success\r\n",check_md5);
		}
		else printf("Firmware Download Failure\r\n");
        printf("Login MQTT Server...\r\n");
        MQTT_OTA_Init();//重新登录MQTT服务器
	}
    else printf("The updated firmware was not obtained\r\n");//未获取到更新固件
	
}

/*
函数功能:将下载的固件更新到APP1中
参数:ota_info_t *ota_info,char *check_md5
返回值:无
*/
void Update_FirmwareToApp(ota_info_t *ota_info,char *check_md5)
{
	if(strcmp(ota_info->md5sum,check_md5)==0)//比较MD5值
	{
		printf("OTA Firmware MD5 OK\r\nUpdate APP...\r\n");
        iap_write_flash(FLASH_APP1_ADDR,(u8*)FLASH_OTA_ADDR,ota_info->file_size);
		Compute_string_md5((u8*)FLASH_APP1_ADDR,ota_info->file_size, check_md5);//计算APP MD5值
		printf("APP MD5:%s\r\n",check_md5);
		if((strcmp(ota_info->md5sum,check_md5)==0)&&(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000))//判断是否为0X08XXXXXX.
		{
			printf("APP MD5 OK\r\n");
			ota_info->file_size=0;//清除固件
            iap_erase_all_bkp_sector();//擦除下载的OTA固件
            memcpy(ota_info->current_version,ota_info->ota_version,sizeof(ota_info->ota_version));
            USART_ITConfig(USART3,USART_IT_RXNE, DISABLE);//关闭USART3接收缓冲区非空中断
            Get_OTA_Firmware(ota_info);//上报固件版本,通知服务器OTA升级完成
            Write_CurrentFirmwareVersion(ota_info->ota_version);//写入APP版本
            printf("Jump Run APP...\r\n");
			iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
            
		}
		else printf("Update APP Failure\r\n");
	}
	else printf("OTA Firmware MD5 Error\r\n");
}

/*
函数功能:读取当前版本信息
参数:char *current_version
返回值:无
*/
void Read_CurrentFirmwareVersion(char *current_version)
{
    uint8_t *version_pt=(uint8_t*)FIRMWARE_VERSION_STORE_ADDR;
    for(uint8_t i=0;i<FIRMWARE_VERSION_SIZE;i++)
    {
        if(*(version_pt+i)==0xFF) continue;
        *(current_version+i)=*(version_pt+i);
    }
}

/*
函数功能:写入当前版本信息
参数:char *current_version
返回值:无
*/
void Write_CurrentFirmwareVersion(char *current_version)
{
    iap_write_flash(FIRMWARE_VERSION_STORE_ADDR,(uint8_t*)current_version,FIRMWARE_VERSION_SIZE);
}

  1. ota.h
#ifndef _OTA_H_
#define _OTA_H_

#include "sys.h"

#define BOOTLOADER_START_ADDR   0x8000000  //BootLoader起始地址
#define BOOTLOADER_SIZE         0x3C00  //BootLoader大小
#define FIRMWARE_VERSION_STORE_ADDR  (BOOTLOADER_START_ADDR+BOOTLOADER_SIZE)   //存储固件信息的FLASH地址
#define FIRMWARE_VERSION_SIZE   16 //存储固件信息的字节数
enum
{
	HTTP_REQUEST_TIMEOUT,
	GET_OTA_DATA_PACKET_FAIL,
	SERVER_CONNECT_FAIL,
	FIRMWARE_DOWNLOAD_SUCC,
	OTA_PACKET_SIZE_OVERFLOW
};


typedef struct ota_info
{
	u32 file_size;//文件大小
	char md5sum[100];//服务器下发的md5
	char url[512];//下载URL
	char host[256];//OTA服务器域名
	char http_request[512];//HTTP请求报文
    char current_version[FIRMWARE_VERSION_SIZE];//当前版本信息
	char ota_version[FIRMWARE_VERSION_SIZE];//OTA版本信息
}ota_info_t;


uint8_t Get_OTA_Info(char *rev_buff,ota_info_t *ota_info);
void MQTT_OTA_Init(void);
void Get_OTA_Firmware(ota_info_t *ota_info);
void Start_OTA_Update(ota_info_t *ota_info,char *check_md5);
void Update_FirmwareToApp(ota_info_t *ota_info,char *check_md5);
void Read_CurrentFirmwareVersion(char *current_version);
void Write_CurrentFirmwareVersion(char *current_version);
#endif

  1. main.c
#include "stm32f10x.h"
#include "delay.h"
#include "usart.h"
#include "key.h"
#include "iap.h"
#include "ota.h"


int main(void)
{
    ota_info_t ota_info;
    uint8_t key_val;
    char check_md5[100];
    Delay_Init();//延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组
    KEY_Init();//按键初始化
    if(Get_Key_Value(KEY_SINGLE_MODE)==KEY_S2_PRESS)//运行BootLoader程序
    {
        Usarts_Init();//初始化串口
        printf("Run BootLoader...\r\n");
        MQTT_OTA_Init();//登录MQTT服务器
        Read_CurrentFirmwareVersion(ota_info.current_version);//读取当前APP版本
        if(strlen(ota_info.current_version)==0)//从未写入APP版本信息
        {
            printf("The APP version was not written,Writting...\r\n");
            memcpy(ota_info.current_version,"1.0",4);
            Write_CurrentFirmwareVersion(ota_info.current_version);//写入APP版本
        }
		printf("\r\n****************************\r\nCurrent Firmware Version:%s\r\nS3-->Get OTA Firmware\r\nS4-->Download OTA Firmware\r\nS5-->Update APP\r\n****************************\r\n\r\n",ota_info.current_version);
    }
    else //运行APP程序
    {
        iap_load_app(FLASH_APP_ADDR);//运行APP
    }
    while(1)
    {
       if(usart3.flag==RX_BUFF_NO_EMPTY)
       {
           for(uint16_t i=0;i<=usart3.cnt;i++)
           {
                if(usart3.rx_buff[i]=='\0')	usart3.rx_buff[i]='0';
           }
           if(strstr((char*)usart3.rx_buff,"report_version_rsp")==NULL)
           {
               if(Get_OTA_Info((char*)usart3.rx_buff,&ota_info))
               {
                    printf("\r\n****************************\r\n  OTA Firmware Infomation  \r\nfile_size:%d\r\nmd5sum:%s\r\nurl:%s\r\nhost:%s\r\nversion:%s\r\n****************************\r\n\r\n",
                    ota_info.file_size,ota_info.md5sum,ota_info.url,ota_info.host,ota_info.ota_version);
               }
               else 
               {
                    printf("None update firmware\r\n");
               }
           }
           else printf("Upload firmware version success\r\n");
           
            
            usart3.flag=RX_BUFF_EMPTY;
            usart3.cnt=0;
            memset(usart3.rx_buff,0,sizeof(usart3.rx_buff));//清空接收缓冲区
       }
       key_val=Get_Key_Value(KEY_SINGLE_MODE);
       if(key_val==KEY_S3_PRESS)
       {
           Get_OTA_Firmware(&ota_info);//向服务器发起更新请求
       }
       else if(key_val==KEY_S4_PRESS)
       {
            Start_OTA_Update(&ota_info,check_md5);//下载OTA固件
       }
       else if(key_val==KEY_S5_PRESS)
       {
            Update_FirmwareToApp(&ota_info,check_md5);//更新APP程序
       }
    }
}


九、代码调试

  1. 有关如何在控制台创建项目、产品及设备请参考此文:STM32+ESP8266(ESP-12F)物联网温度计-腾讯云物联网
  2. 有关使用wireshark抓包工具调试代码请移步:STM32+ESP8266(ESP-12F)物联网温度计-移植paho MQTT协议连接阿里云

十、说明

说明:所发布工程只完成了基本的OTA升级部分,可能会存在一些bug,如有发现可以评论或私信告诉我,其他应用功能还需要自己根据实际需要进行更改。
如教程中存在错误,欢迎在评论区指正!

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:27:28  更:2022-04-04 12:30:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:26:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码