首页> 搜索结果页
"stm32 网络rgb" 检索
共 16 条结果
stm32开发之使用Keil MDK以及标准外设库创建STM32工程
通过上一节对标准外设库的介绍,想必各位读者对标准外设库已经有了基本的认识,然而由于标准外设库中文件众多,很多初学者在开始很长一段时间内甚至都无法完全自己建立一个工程,很多人只是依赖标准外设库或给定的工程模板。本节就介绍怎样利用Keil MDK开发环境和标准外设库来搭建自己的工程。 建立一个基于标准外设库其实并不复杂,网络上也有很多关于怎样在Keil MDK下建立工程的教程,方法也各异,本节所介绍的方法本着少改动、便于使用、方便更换器件等原则,给大家介绍怎样在Keil中创建一个工程,并以一个最简单的例子来说明怎样下载和调试程序。 1.1.1 开发工具与开发环境 1. 软件版本 本节所使用Keil MDK 为目前的最新版V4.21,具体版本信息如图 5‑6所示。其他版本差别不大,读者可以根据自己使用的版本进行操作或者从Keil的官网下载新版本。使用的标准外设库为目前最新的V3.5版本。使用的开发调试工具为JLINK-V8,使用的驱动版本为V4.08l。 图5‑6 MDK详细版本信息 2. 建立工程目录 首先先建立一个用于存放工程的文件夹,此处命名为STM32,在文件夹下分别建立Src、Lis、Obj,Doc四个文件夹,分别用于存放源代码、编译过程中产生的临时文件和输出文件及文档,而src底下建立user用于存放用户程序。 接下来拷贝标准外设库和相应的文件到工程目录中,首先拷贝STM32F10x_StdPeriph_Lib_V3.5.0\Libraries文件夹下的两个文件夹CMSIS和STM32F10x_StdPeriph_Driver到刚刚新建的STM32\Src目录下,接下来拷贝用户编写程序的程序文件,此文件可以从标准外设库自带的例程中拷贝,这里从STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\ADC\3ADCs_DMA目录下拷贝main.c、stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.h四个文件,这四个文件是在编程的时候用户往往需要根据实际需求进行修改的。因此拷贝至STM32\USER目录下,system_stm32f10x.c文件定义了系统时钟等参数并完成微控制器的初始化,标准外设库中已经有了,这里可以不拷贝。这样所需要的文件都已经具备了,如图 5‑7所示。 1.1.2 MDK的操作与设置 1. 新建工程 首先启动Keil uVision4,点击菜单栏Project-New uVision Project,选择工程的保存位置,保存位置选择在之前建立的MDK文件夹下,如图 5‑8所示。 图5‑8保存路径的选择 在弹出的界面中选择所使用的芯片信号,笔者使用的芯片型号为STM32F103ZET6,所以选择STM32F103ZE,如图 5‑9所示。 图5‑9选择芯片型号 芯片选择完成后软件会弹出一条提示,提示是否要拷贝STM32大容量启动代码并添加到工程,由于我们使用新版本标准外设库中提供的启动代码,因此此处选择否。如图 5‑10所示。 图5‑10拷贝启动代码选择 接下来就出现了一个空的工程窗口,建立了一个新的工程。 2. 添加程序文件 在左侧Project一栏中对Target 1两次点击之后可以重命名,这里命名为STM32,然后右击,如所示,单击Manage Comoonents。出现如所图 5‑11示界面。 图5‑11选择 Manage Comoonents 在这个界面里可以更为方便的添加工程的相关文件。也可以在对应的Group上右键Add Group和Add Files to Group只是这种方式更方便集中的添加和管理。按照图 5‑12所示,建立四个Group,点击Groups一栏右上角图标,依次新建User、CMSIS、LIB三个组,点击对应的组在点击右下方Add Files添加相应的文件到对应的组中。User组中添加User文件夹下的文件,CMSIS组中添加STM32 PJ\STM32\LIB\CMSIS\CM3\CoreSupport文件夹下core_cm3.c文件和STM32\LIB\CMSIS\CM3\DeviceSupport\ST\STM32F10x文件夹下system_stm32f10x.c文件以及\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm下的启动文件,根据是高密还是中等还是低密度芯片选择。在LIB组下添加STM32\LIB\STM32F10x_StdPeriph_Driver\src下的所有文件。至此,已经将对应的文件全部加入工程中。 图5‑12编辑组并添加对应文件 文件添加完成后的工程管理窗口如图 5‑13所示。其中CMSIS和LIB组中的文件图标上由个钥匙图案,表示该文件当前为只读属性,工程编译完成后前面会出现一个加号,点击展开可以显示当前文件所调用的文件。 图5‑13工程管理文件列表 3. 参数设置 接下来需要对工程进行一些参数设置,在中STM32上右击,选择第一项Options for Target ‘STM32’,如图 5‑14所示。 图5‑14选择进行参数设置 弹出窗口如图 5‑15所示。在这个窗口中共有10个选项卡,第一个选项卡Device用于选择使用的器件,由于在新建工程的时候已经选择这里可以不用在选择,当前的工程如果在后期要更换使用的芯片时可以在这个选项卡中更换芯片,同时还要注意更换对应的启动文件。在Target选项卡中,一般只需要填上使用的晶振频率即可,其他都可以保持默认。 图5‑15 Target选项设置 在如图 5‑16所示Output选项卡中主要注意两项,一是选择输出文件夹,选择之前工程目录中MDK文件夹下的Obj文件夹,在下面还有一个Create HEX File选项,用于选择是否生成hex文件,如果编译好的程序要写片,或者要提供给别人下载到芯片中而不希望别人知道源码时可以提供HEX文件。 图5‑16 Output选项设置 在Listing选项卡下同样可以选择Listing对应的文件夹,这里选择STM32\MDK\List文件夹,下方的复选框可以选择需要生成的调试信息,这里保持默认即可,如图 5‑17所示。 图5‑17 Listing 选项设置 C/C++选项卡中的设置比较重要,如图 5‑18所示,选项卡中主要有两个地方需要注意,首先是整个工程的宏定义,在前面标准外设库部分已经有过介绍,标准外设库中很多功能都是通过宏定义来实现的,由于标准外设库针对STM32一系列芯片,因此需要通过宏定义来进行针对性的选择。这种选择有两种方式,第一种是直接改动宏定义部分的库文件,缺点是需要更改一些功能时不是很方便,而且笔者建议在尽量不更改库文件的方式下实现功能的配置和使用,另一种方式就是在工程的设置中添加宏定义,这儿添加了两个宏定义,USE_STDPERIPH_DRIVER表示使用标准外设库进行程序开发, STM32F10X_HD表示使用的是STM32F10X系列大容量器件,本例中中使用的是STM32F103ZET6故填写此项。 需要说明的是在程序开发过程中不光库文件可以通过宏定义的方式进行程序功能配置,用户程序也可以仿照库文件的这种形式,通过相关的宏定义进行一些参数配置。STM32的标准外设库在程序组织性、层次性、规范性等方面都值得我们去学习的。在“Target”的选项卡中进行的宏定义是这对当前“Target”有效的,在每一个“Group”同样可以设置作用于Group的宏定义。 Language/Code Generation 一栏中主要是针对程序语言所所的一些优化和配置,默认情况下保持默认即可。 图5‑18 C/C++选项设置 接下来一个重要的设置就是设置工程所包含的头文件目录,如图 5‑19所示,点击Include Paths一栏右侧图标,弹出如所示的对话框。点击对话框右上角图标,添加工程所包括的头文件路径,添加头文件路径的时候要把当前工程目录中包含头文件的路径都添加进去,如所示,添加了以下几个路径: ..\USER ..\LIB\STM32F10x_StdPeriph_Driver\inc ..\LIB\CMSIS\CM3\CoreSupport ..\LIB\CMSIS\CM3\DeviceSupport\ST\STM32F10x 图5‑19添加头文件包含路径 如果有用户自己编写的头文件也要相应的加上引用路径。仔细观察可以发现在标注外设库中头文件的引用为:#include "stm32f10x.h",在C语言的学习中我们知道双引号表示编译器从用户目录开始搜索,如果未找到才回去系统目录去查找,MDK的安装目录中同样存在对应的库文件,路径为Keil\ARM\INC\ST\STM32F10x,但是由于不同的版本所内含的库文件不同,如目前最新的V4.21版本中则内置了V3.4版本的库文件,而之前的版本中很多内置了V2.0版本的库文件,因此为了保持工程所使用的库文件和引用的头文件的一致性,同时也方便工程文件拷贝到其他电脑上运行,建议大家都按照本书介绍的方式都从自己的目录中引用头文件。 4. 程序调试与下载设置 头文件引用路径添加完成后,就可以进行相关的编译工作了,在进行这一步工作之前,我们还要完成有关程序调试和下载的相关设置,有关调试的设置在Debug选项卡中,之前的Asm和Linker选项卡保持默认。Debug选项卡如图 5‑20所示。在此选项卡中主要完成程序调试的相关设置,选项卡主要分为两个部分,左侧是使用模拟器进行仿真与调试的方式,点选后软件会进入模拟器调试。右侧是使用仿真器连接硬件开发平台进行调试,首先从右侧选择所使用的仿真器,这里使用的是J-Link仿真器,故在下拉框中选择Cortex-M/R J-LINK/J-Trace,在两侧的下方可以通过勾选对应的复选框来选择是否需要在调试开始时下载程序和运行到主程序,其他部分的设置保持默认即可。 图5‑20调试工具设置 点击右侧图标进入J-Link设置界面,如果此时仿真器和硬件开发平台已经正确连接就会出现如图 5‑21所示的界面,同时系统系统任务栏中会自动弹出J-Link的控制软件。从界面中可以读出当前的J-Link SN号以及相应的版本信息,如果没有出现相关信息请检查相关驱动程序是否正确安装,并到系统的设备管理器中通用串行总线控制设备一项检查系统有没有识别出J-Link,右侧则可以读取所连接的设备信息,如果没有出现类似的信息请检查硬件连接。在此选项卡中可以进行J-Link的端口、速度等设置,这里只需要保持默认就可以了。 图5‑21 J-Link 连接 本章之前已经做过介绍,STM32F10X内核集成了串行/JTAG调试接口SWJ-DP(Serial Wire and JTAG)。这是标准的ARM CoreSight调试接口,包括JTAG-DP接口(使用5个引脚)和SW-DP(使用两个引脚)。引脚分配如表 5‑9所示。两种方式都可以进行程序的调试和下载,但是SW方式更节省端口,只需要两根线就可以,多余的I/O可以释放用作普通用途,SW方式和JTAG方式在普通程序调试下载与调试过程中并无明显差别,如果想使用SW的方式调试程序只需要在Port的下拉列表中选择SW即可,如图 5‑22所示。选择完成后如果硬件连接正确同样能够读取芯片的信息。 表 5‑9  SWJ调试端口引脚 SWJ-DP端口 引脚名称 JTAG 调试接口 SW 调试接口 引脚分配 类型 描述 类型 调试功能 JTMS/SWDIO 输入 JTAG模式选择 输入/输出 串行数据输入/输出 PA13 JTCK/SWCLK 输入 JTAG时钟 输入 串行时钟 PA14 JTDI 输入 JTAG数据输入 —— —— PA15 JTDO/TRACESWO 输出 JTAG数据输出 —— 跟踪时为TRACESWO信号 PB3 JNTRST 输入 JTAG模块复位 —— —— PB4   图5‑22使用SW方式 设置好了Debug选项卡中有关选项后还需要设置Flash Download选项卡,如图 5‑23所示。本选项卡主要设置烧写Flash时的相关参数,这里主要设置两个部分,一个是设置程序下载的一些配置,这儿选择Erase Sectors表示下载程序的时候擦除对应分区,勾选后面三项,分别表示下载程序,下载后校验,程序下载后复位并运行。家下来需要选择所使用的Flash,点击Add,添加所使用的芯片类型,笔者使用的是STM32F103ZET6,Flash大小是512k,因此选择STM32F10X High-density Flash。至此完成使用J-Link完成程序调试的配置。 图5‑23 Flash烧写参数设置 最后选择程序下载按钮所对应的工具配置,配置同Debug界面如图 5‑24所示,同样选择Cortex-M/R J-LINK/J-Trace这样就可以在后面的使用中一键将程序下载到Flash中。至此已经全部完成了程序调试和下载的必要设置。 图5‑19设置程序下载菜单对应的工具
文章
芯片  ·  内存技术
2015-08-13
基于STM32+Qt上位机的RGB调色器,全开源!
前言使用uFUN STM32开发板配合Qt上位机,实现任意颜色的混合,Qt的上位机下发RGB数值,范围0-255,uFUN开发板进行解析,然后输出不同占空比的PWM,从而实现通过RGB三原色调制出任意颜色。Qt上位机界面:STM32下位机基于uFUN开发板的STM32程序串口驱动,串口中断,数据的接收和解析。定时器的使用,PWM方式驱动RGBQt上位机基于Qt 5.8.0开发,采用串口协议和uFUN开发板进行通讯,数据协议固定,串口波特115200,可自定义RGB的亮度,可通过调色板来选择任意颜色串口的使用,串口的搜索,串口参数的设置串口的打开关闭,串口数据的发送和接收串口自定义波特率的实现滑动条的使用,滑动条值的获取和设置,调色板RGB颜色值的获取按钮的触发,信号与槽多窗口的打开和关闭RGB简介RGB模型是目前常用的一种彩色信息表达方式,它使用红,绿,蓝三原色的亮度来定量表示颜色。该模型也称为加色混色模型,是以RGB三色光互相叠加来实现混色的方法,因而适合于显示器等发光体的显示。可以通过调整RGB三种原色的比例,来混合出任何你想要的颜色。uFUN开发板的硬件电路uFUN开发板上的RGB灯硬件电路也很简单,可以通过TIM5 / TIM2的通道1,通道2,通道3来控制,通过实际验证,发现PWM B和PWM G两个引脚的网络标号反了,如下图:定时器输出PWM配置使用TIM5或者TIM2都可以,但是在使用TIM5软件仿真的时候,发现没有PWM波输出,而实际有输出,不知道这是不是的Keil的一个BUG,我的是5.16a版本的。void RGB_LED_Init(void) { GPIO_InitTypeDef IO_Init; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef OC_Init; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); IO_Init.GPIO_Mode = GPIO_Mode_AF_PP; IO_Init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; IO_Init.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &IO_Init); TIM_DeInit(TIM5); TIM_TimeBaseStructure.TIM_Period = 256-1; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); OC_Init.TIM_OCMode = TIM_OCMode_PWM2;//输出模式 OC_Init.TIM_OutputState = ENABLE; //输出使能 OC_Init.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性 // OC_Init.TIM_Pulse = 50; TIM_OC1Init(TIM5, &OC_Init); TIM_OC2Init(TIM5, &OC_Init); TIM_OC3Init(TIM5, &OC_Init); TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_Cmd(TIM5, ENABLE); }这里的计数周期,设置成了255,即0-255对应占空比0-100,可以通过下面这个函数来设置对应通道的占空比://设置LED占空比 void SetDutyCycle(LEDtype LEDn, int dty) { switch(LEDn) { case R_LED: TIM_SetCompare2(TIM5, dty); break; case G_LED: TIM_SetCompare1(TIM5, dty); break; case B_LED: TIM_SetCompare3(TIM5, dty); break; default: TIM_SetCompare1(TIM5, 0); TIM_SetCompare2(TIM5, 0); TIM_SetCompare2(TIM5, 0); break; } }串口命令的解析Qt的上位机下发的数据格式如下:R+数值+G+数值+B+数值+*如:R12G123B45* R155G9B24*数值有1-3位,STM32接收到数据之后,可以解析出对应的数值,12 123 45 155 9 24然后控制对应的PWM输出。串口中断函数:uint8_t rx_buf[100]; uint8_t rx_len; void USART1_IRQHandler(void) //串口1中断服务程序 { uint8_t dat; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { dat = USART_ReceiveData(USART1); //读取接收到的数据 // USART_SendData(USART1, dat); if(dat == '*') { //R123G123B213* // printf("%s %d", rx_buf, rx_len); ParseCmd(rx_buf, rx_len); memset(rx_buf, 0, rx_len); rx_len = 0; } else { rx_buf[rx_len++] = dat; } } }串口数据解析,获取到RGB对应的数值:void ParseCmd(char *rx_buf, size_t len) { uint8_t R_Num, G_Num, B_Num; char R_Str[20], G_Str[20], B_Str[20]; char *R, *G, *B; len = strlen(rx_buf); R = strstr(rx_buf, "R"); G = strstr(rx_buf, "G"); B = strstr(rx_buf, "B"); strncpy(R_Str, R+1, G-R-1); R_Str[G-R-1] = '\0'; strncpy(G_Str, G+1, B-G-1); G_Str[B-G-1] = '\0'; strncpy(B_Str, B+1, len - (B - rx_buf)-1); B_Str[len - (B - rx_buf)-1] = '\0'; // printf("R:-%s-,\r\nG:-%s-,\r\nB:-%s-,\r\n", R_Str, G_Str, B_Str); R_Num = atoi(R_Str); G_Num = atoi(G_Str); B_Num = atoi(B_Str); // printf("%d %d %d", R_Num, G_Num, B_Num); SetDutyCycle(R_LED, R_Num); SetDutyCycle(G_LED, G_Num); SetDutyCycle(B_LED, B_Num); }
文章
数据格式
2022-11-14
《STM32库开发实战指南:基于STM32F4》----导读
目 录前 言[第1章 如何安装KEIL5 1.1 温馨提示 ](https://yq.aliyun.com/articles/89559/)1.2 获取KEIL5安装包 1.3 开始安装KEIL5 1.4 安装STM32芯片包 [第2章 如何用DAP仿真器下载程序 2.1 仿真器简介 ](https://yq.aliyun.com/articles/89590/)2.2 硬件连接 2.3 仿真器配置 2.4 选择目标板 2.5 下载程序 [第3章 初识STM32 3.1 什么是STM32 ](https://yq.aliyun.com/articles/89612)[3.2 STM32能做什么 3.2.1 智能手环 3.2.2 微型四轴飞行器 3.2.3 淘宝众筹 ](https://yq.aliyun.com/articles/89621)[3.3 STM32选型 3.3.1 STM32分类 3.3.2 STM32命名方法 3.3.3 选择合适的MCU ](https://yq.aliyun.com/articles/89642)第4章 寄存器 4.1 寄存器简介 4.2 STM32的外观 4.3 芯片里面有什么 4.4 存储器映射 4.5 寄存器映射 4.5.1 STM32的外设地址映射 4.5.2 C语言对寄存器的封装 第5章 新建工程——寄存器版 5.1 新建本地工程文件夹工程 5.1.1 新建本地工程文件夹 5.1.2 新建工程 5.2 下载程序 第6章 使用寄存器点亮LED 6.1 GPIO简介 6.2 GPIO框图剖析 6.2.1 基本结构分析 6.2.2 GPIO工作模式 6.3 实验:使用寄存器点亮LED 6.3.1 硬件连接 6.3.2 启动文件 6.3.3 stm32f4xx.h文件 6.3.4 main文件 6.3.5 下载验证 第7章 自己写库——构建库函数雏形 7.1 STM32函数库简介 7.2 采用库来开发及学习的原因 7.3 实验:构建库函数雏形 7.3.1 修改寄存器地址封装 7.3.2 定义访问外设的结构体指针 7.3.3 定义初始化结构体 7.3.4 定义引脚模式的枚举类型 7.3.5 定义GPIO初始化函数 7.3.6 使用函数点亮LED 7.3.7 下载验证 7.3.8 总结 第8章 初识STM32标准库 8.1 CMSIS标准及库层次关系 8.1.1 库目录、文件简介 8.1.2 各库文件间的关系 8.2 使用帮助文档 8.2.1 常用官方资料 8.2.2 初识库函数 第9章 新建工程——库函数版 9.1 新建本地工程文件夹 9.2 新建工程 9.3 配置魔术棒选项卡 9.4 下载器配置 9.5 选择Flash大小 第10章 GPIO输出——使用固件库点亮LED 10.1 硬件设计 10.2 软件设计 10.2.1 编程要点 10.2.2 代码分析 10.2.3 下载验证 10.3 STM32标准库补充知识 第11章 GPIO输入——按键检测 11.1 硬件设计 11.2 软件设计 11.2.1 编程要点 11.2.2 代码分析 11.2.3 下载验证 第12章 GPIO——位带操作 12.1 位带简介 12.1.1 外设位带区 12.1.2 SRAM位带区 12.1.3 位带区和位带别名区地址转换 12.2 GPIO位带操作 第13章 启动文件 13.1 启动文件简介 13.2 查找ARM汇编指令 13.3 启动文件代码讲解 第14章 RCC——使用HSE/HSI配置时钟 14.1 RCC主要作用——时钟部分 14.2 RCC框图剖析——时钟树 14.2.1 系统时钟 14.2.2 其他时钟 14.3 配置系统时钟实验 14.3.1 使用HSE 14.3.2 使用HSI 14.3.3 硬件设计 14.3.4 软件设计 14.3.5 下载验证 第15章 STM32中断应用概览 15.1 异常类型 15.2 NVIC简介 15.2.1 NVIC寄存器简介 15.2.2 NVIC中断配置固件库 15.3 优先级的定义 15.3.1 优先级定义 15.3.2 优先级分组 15.4 中断编程 第16章 EXTI——外部中断/事件控制器 16.1 EXTI简介 16.2 EXTI功能框图 16.3 中断/事件线 16.4 EXTI初始化结构体详解 16.5 外部中断控制实验 16.5.1 硬件设计 16.5.2 软件设计 16.5.3 下载验证 第17章 SysTick——系统定时器 17.1 SysTick简介 17.2 SysTick寄存器介绍 17.3 SysTick定时实验 17.3.1 硬件设计 17.3.2 软件设计 第18章 通信的基本概念 18.1 串行通信与并行通信 18.2 全双工、半双工及单工通信 18.3 同步通信与异步通信 18.4 通信速率 第19章 USART——串口通信 19.1 串口通信协议简介 19.1.1 物理层 19.1.2 协议层 19.2 STM32的USART简介 19.3 USART功能框图 19.4 USART初始化结构体详解 19.5 USART1接发通信实验 19.5.1 硬件设计 19.5.2 软件设计 19.5.3 下载验证 19.6 USART1指令控制RGB彩灯实验 19.6.1 硬件设计 19.6.2 软件设计 19.6.3 下载验证 第20章 DMA 20.1 DMA简介20.2 DMA功能框图 20.3 DMA数据配置 20.4 DMA初始化结构体详解 20.5 DMA存储器到存储器模式实验 20.5.1 硬件设计 20.5.2 软件设计 20.5.3 下载验证 20.6 DMA存储器到外设模式实验 20.6.1 硬件设计 20.6.2 软件设计 20.6.3 下载验证 第21章 常用存储器介绍 21.1 存储器种类 21.2 RAM存储器 21.2.1 DRAM 21.2.2 SRAM 21.2.3 DRAM与SRAM的应用场合 21.3 非易失性存储器 21.3.1 ROM存储器 21.3.2 Flash存储器 第22章 I2C——读写EEPROM 22.1 I2C协议简介 22.1.1 I2C物理层 22.1.2 协议层 22.2 STM32的I2C特性及架构 22.2.1 STM32的I2C外设简介 22.2.2 STM32的I2C架构剖析 22.2.3 通信过程 22.3 I2C初始化结构体详解 22.4 I2C——读写EEPROM实验 22.4.1 硬件设计 22.4.2 软件设计 22.4.3 下载验证 第23章 SPI——读写串行Flash 23.1 SPI协议简介 23.1.1 SPI物理层 23.1.2 协议层 23.2 STM32的SPI特性及架构 23.2.1 STM32的SPI外设简介 23.2.2 STM32的SPI架构剖析 23.2.3 通信过程 23.3 SPI初始化结构体详解 23.4 SPI——读写串行Flash实验 23.4.1 硬件设计 23.4.2 软件设计 23.4.3 下载验证 第24章 串行Flash文件系统FatFs 24.1 文件系统 24.2 FatFs文件系统简介 24.2.1 FatFs的目录结构 24.2.2 FatFs帮助文档 24.2.3 FatFs源码 24.3 FatFs文件系统移植实验 24.3.1 FatFs程序结构图 24.3.2 硬件设计 24.3.3 FatFs移植步骤 24.3.4 FatFs底层设备驱动函数 24.3.5 FatFs功能配置 24.3.6 FatFs功能测试 24.3.7 下载验证 24.4 FatFs功能使用实验 24.4.1 硬件设计 24.4.2 软件设计 24.4.3 下载验证 第25章 FMC——扩展外部SDRAM 25.1 SDRAM控制原理 25.1.1 SDRAM信号线 25.1.2 控制逻辑 25.1.3 地址控制 25.1.4 SDRAM的存储阵列 25.1.5 数据输入输出 25.1.6 SDRAM的命令 25.1.7 SDRAM的初始化流程 25.1.8 SDRAM的读写流程 25.2 FMC简介 25.3 FMC框图剖析 25.4 FMC的地址映射 25.5 SDRAM时序结构体 25.6 SDRAM初始化结构体 25.7 SDRAM命令结构体 25.8 FMC——扩展外部SDRAM实验 25.8.1 硬件设计 25.8.2 软件设计 25.8.3 下载验证 第26章 LTDC/DMA2D——液晶显示 26.1 显示器简介 26.1.1 液晶显示器 26.1.2 LED和OLED显示器 26.1.3 显示器的基本参数 26.2 液晶屏控制原理 26.2.1 液晶面板的控制信号 26.2.2 液晶数据传输时序 26.2.3 显存 26.3 LTDC液晶控制器简介 26.3.1 图像数据混合 26.3.2 LTDC结构框图剖析 26.4 DMA2D图形加速器简介 26.5 LTDC初始化结构体 26.6 LTDC层级初始化结构体 26.7 DMA2D初始化结构体 26.8 LTDC/DMA2D——液晶显示实验 26.8.1 硬件设计 26.8.2 软件设计 26.8.3 下载验证 第27章 LTDC——液晶显示中英文 27.1 字符编码 27.1.1 ASCII编码 27.1.2 中文编码 27.1.3 Unicode字符集和编码 27.1.4 UTF-32 27.1.5 UTF-16 27.1.6 UTF-8 27.1.7 BOM 27.2 字模简介 27.2.1 字模的构成 27.2.2 字模显示原理 27.2.3 如何制作字模 27.2.4 字模寻址公式 27.2.5 存储字模文件 27.3 LTDC——各种模式的液晶显示字符实验 27.3.1 硬件设计 27.3.2 显示ASCII编码的字符 27.3.3 显示GB2312编码的字符 27.3.4 显示任意大小的字符 27.3.5 下载验证 第28章 电容触摸屏——触摸画板 28.1 触摸屏简介 28.1.1 电阻触摸屏检测原理 28.1.2 电容触摸屏检测原理 28.2 电容触摸屏控制芯片 28.2.1 GT9157芯片的引脚 28.2.2 上电时序与I2C设备地址 28.2.3 寄存器配置 28.2.4 读取坐标信息 28.3 电容触摸屏——触摸画板实验 28.3.1 硬件设计 28.3.2 软件设计 28.3.3 下载验证 第29章 ADC——电压采集 29.1 ADC简介 29.2 ADC功能框图剖析 29.2.1 ADC功能 29.2.2 电压转换 29.3 ADC初始化结构体详解 29.4 独立模式单通道采集实验 29.4.1 硬件设计 29.4.2 软件设计 29.4.3 下载验证 29.5 独立模式多通道采集实验 29.5.1 硬件设计 29.5.2 软件设计 29.5.3 下载验证 29.6 三重ADC交替模式采集实验 29.6.1 硬件设计 29.6.2 软件设计 29.6.3 下载验证 第30章 TIM——基本定时器 30.1 TIM简介 30.2 基本定时器 30.3 基本定时器功能框图 30.4 定时器初始化结构体详解 30.5 基本定时器定时实验 30.5.1 硬件设计 30.5.2 软件设计 30.5.3 下载验证 第31章 TIM——高级定时器 31.1 高级控制定时器 31.2 高级控制定时器功能框图 31.3 输入捕获应用 31.3.1 测量脉宽或者频率 31.3.2 PWM输入模式 31.4 输出比较应用 31.5 定时器初始化结构体详解 31.6 PWM互补输出实验 31.6.1 硬件设计 31.6.2 软件设计 31.6.3 下载验证 31.7 PWM输入捕获实验 31.7.1 硬件设计 31.7.2 软件设计 31.7.3 下载验证 第32章 TIM——电容按键检测 32.1 电容按键原理 32.2 电容按键检测实验 32.2.1 硬件设计 32.2.2 软件设计 32.2.3 下载验证 第33章 SDIO——SD卡读写测试 33.1 SDIO简介 33.2 SD卡物理结构 33.3 SDIO总线 33.3.1 总线拓扑 33.3.2 总线协议 33.3.3 命令 33.3.4 响应 33.4 SD卡的操作模式及切换 33.4.1 SD卡的操作模式 33.4.2 卡识别模式 33.4.3 数据传输模式 33.5 STM32的SDIO功能框图 33.6 SDIO初始化结构体 33.7 SDIO命令初始化结构体 33.8 SDIO数据初始化结构体 33.9 SD卡读写测试实验 33.9.1 硬件设计 33.9.2 软件设计 33.9.3 下载验证 第34章 基于SD卡的FatFs文件系统 34.1 FatFs移植步骤 34.2 FatFs接口函数 34.3 FatFs功能测试 第35章 I2S——音频播放与录音输入 35.1 I2S简介 35.1.1 数字音频技术 35.1.2 I2S总线接口 35.1.3 音频数据传输协议标准 35.2 I2S功能框图 35.3 WM8978音频编译码器 35.4 WAV格式文件 35.4.1 RIFF文件规范 35.4.2 WAV文件 35.4.3 WAV文件实例分析 35.5 I2S初始化结构体详解 35.6 录音与回放实验 35.6.1 硬件设计 35.6.2 软件设计 35.6.3 下载验证 35.7 MP3播放器 35.7.1 MP3文件结构 35.7.2 MP3解码库 35.7.3 Helix解码库移植 35.7.4 MP3播放器功能实现 35.7.5 下载验证 第36章 ETH——LwIP以太网通信 36.1 互联网模型 36.2 以太网 36.2.1 PHY层 36.2.2 MAC子层 36.3 TCP/IP协议栈 36.3.1 需要协议栈的原因 36.3.2 各网络层的功能 36.4 以太网外设 36.4.1 SMI接口 36.4.2 MII和RMII接口 36.4.3 MAC数据包发送和接收 36.4.4 MAC过滤 36.5 PHY:LAN8720A 36.6 LwIP:轻型TCP/IP协议栈 36.7 ETH初始化结构体详解 36.8 以太网通信实验:无操作系统LwIP移植 36.8.1 硬件设计 36.8.2 移植步骤 36.8.3 下载验证 36.9 基于μCOS-III移植LwIP实验 第37章 CAN——通信实验 37.1 CAN协议简介 37.1.1 CAN物理层 37.1.2 协议层 37.2 STM32的CAN外设简介 37.3 CAN初始化结构体 37.4 CAN发送及接收结构体 37.5 CAN筛选器结构体 37.6 CAN——双机通信实验 37.6.1 硬件设计 37.6.2 软件设计 37.6.3 下载验证 第38章 RS-485通信实验 38.1 RS-485通信协议简介 38.2 RS-485——双机通信实验 38.2.1 硬件设计 38.2.2 软件设计 38.2.3 下载验证 第39章 电源管理——实现低功耗 39.1 STM32的电源管理简介 39.1.1 电源监控器 39.1.2 STM32的电源系统 39.1.3 STM32的功耗模式 39.2 电源管理相关的库函数及命令 39.2.1 配置PVD监控功能 39.2.2 WFI与WFE命令 39.2.3 进入停止模式 39.2.4 进入待机模式 39.3 PWR——睡眠模式实验 39.3.1 硬件设计 39.3.2 软件设计 39.3.3 下载验证 39.4 PWR——停止模式实验 39.4.1 硬件设计 39.4.2 软件设计 39.4.3 下载验证 39.5 PWR——待机模式实验 39.5.1 硬件设计 39.5.2 软件设计 39.5.3 下载验证 39.6 PWR——PVD电源监控实验 39.6.1 硬件设计 39.6.2 软件设计 39.6.3 下载验证 第40章 RTC——实时时钟 40.1 RTC简介 40.2 RTC功能框图解析 40.3 RTC初始化结构体讲解 40.4 RTC时间结构体讲解 40.5 RTC日期结构体讲解 40.6 RTC闹钟结构体讲解 40.7 RTC—日历实验 40.7.1 硬件设计 40.7.2 软件设计 40.7.3 下载验证 40.8 RTC—闹钟实验 40.8.1 硬件设计 40.8.2 软件设计 40.8.3 下载验证 第41章 DCMI——OV5640摄像头 41.1 摄像头简介 41.1.1 数字摄像头与模拟摄像头的区别 41.1.2 CCD与CMOS的区别 41.2 OV5640摄像头 41.2.1 OV5640传感器简介 41.2.2 OV5640引脚及功能框图 41.2.3 SCCB时序 41.2.4 OV5640的寄存器 41.2.5 像素数据输出时序 41.3 STM32的DCMI接口简介 41.3.1 DCMI整体框图 41.3.2 DCMI接口内部结构 41.3.3 同步方式 41.3.4 捕获模式及捕获率 41.4 DCMI初始化结构体 41.5 DCMI——OV5640摄像头实验 41.5.1 硬件设计 41.5.2 软件设计 41.5.3 下载验证 第42章 MDK的编译过程及文件类型全解 42.1 编译过程 42.1.1 编译过程简介 42.1.2 具体工程中的编译过程 42.2 程序的组成、存储与运行 42.2.1 CODE、RO、RW、ZI Data域及堆栈空间 42.2.2 程序的存储与运行 42.3 编译工具链 42.3.1 设置环境变量 42.3.2 armcc、armasm及armlink 42.3.3 armar、fromelf及用户指令 42.4 MDK工程的文件类型 42.4.1 uvprojx、uvoptx、uvguix及ini工程文件 42.4.2 源文件 42.4.3 Output目录下生成的文件 42.4.4 Listing目录下的文件 42.4.5 sct分散加载文件的格式与应用 42.5 实验:自动分配变量到外部SDRAM空间 42.5.1 硬件设计 42.5.2 软件设计 42.5.3 下载验证 42.6 实验:优先使用内部SRAM并把堆区分配到SDRAM空间 42.6.1 硬件设计 42.6.2 软件设计 42.6.3 下载验证 第43章 在SRAM中调试代码 43.1 在RAM中调试代码 43.2 STM32的启动方式 43.3 内部Flash的启动过程 43.4 实验:在内部SRAM中调试代码 43.4.1 硬件设计 43.4.2 软件设计 43.4.3 下载验证 第44章 读写内部Flash 44.1 STM32的内部Flash简介 44.2 对内部Flash的写入过程 44.3 查看工程的空间分布 44.4 操作内部Flash的库函数 44.5 实验:读写内部Flash 44.5.1 硬件设计 44.5.2 软件设计 44.5.3 下载验证 第45章 设置Flash的读写保护及解除 45.1 选项字节与读写保护 45.1.1 选项字节的内容 45.1.2 RDP读保护级别 45.1.3 PCROP代码读出保护 45.2 修改选项字节的过程 45.3 操作选项字节的库函数 45.4 实验:设置读写保护及解除 45.4.1 硬件设计 45.4.2 软件设计 45.4.3 下载验证
文章
存储  ·  芯片  ·  内存技术
2017-05-24
ESP32-C3入门教程 基础篇(五、RMT应用 — 控制SK6812全彩RGB 灯)
测试第五课,本来是准备测试一下PWM驱动 SK6812 RGB灯, 但是研究了一段时间,发现在ESP32-C3 有更好而且现成的方式 实现 SK6812 的控制, 使用PWM也不是不可以,只是对于初学者,需要多花好多时间, 所以本文还是先以ESP32-C3内置的 RMT 进行 SK6812 的控制,毕竟有现成的示例 前言在开发板上面,我画了一个 SK6812 RGB灯,当时因为对于 SK6812没有进一步的了解,所以写的是 PWMLED ,现在已经改过来了:本文我们来学习一下 SK6812全彩RGB 的使用以及 ESP32-C3 如何控制它。ESP32-C3系列博文连接:测试使用的开发板:自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)测试使用的开发环境:ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF——Windows和Ubuntu双环境)基础篇系列相关博文:ESP32-C3入门教程 基础篇(一、ADC采样)ESP32-C3入门教程 基础篇(二、GPIO中断、按键驱动测试)ESP32-C3入门教程 基础篇(三、UART模块 — 与Enocean无线模块串口通信)ESP32-C3入门教程 基础篇(四、I2C总线 — 与SHT21温湿度传感器通讯)一、 SK6812 LED基础介绍SK6812 灯珠集成了 控制电路与发光电路与一体的智能外控 LED 光源。 外形与 5050 LED 灯珠是一样的。 但是与普通的 LED 不同的是,他不是简单的通过高低电平来控制亮灭, 它通过 单线就能控制 RGB 三色的亮灭,采用了一个叫 单极性归零码 数据协议的通讯方式。1.1 SK6812 控制原理基础介绍在使用的产品的手册中都有,这里就截取我开发板使用的产品手册中的图片来说明一下: 对于使用者来说,我们需要知道的主要是理解这个协议,然后实现手册中提到的 “0”码 和“1”码,然后每一个灯珠,是由 24 bit 的数据结构组成。(注意这里说的 24 bit 是数据结构,举个例子, 如果我们使用SPI实现“0” 、“1” 码,SPI 总线发送一个字节,只是实现了一个 码,只是上面 24 bit 数据结构中的 1bit !后面会更加详细的说明这一点)我们把对应需要了解的参数都截图说明(根据自己选用的产品规格书来确定具体参数):额外添加点说明,不同比例的三原色光相加得到彩色称为相加混色,比如:红+绿=黄红+蓝=紫蓝+绿=青红+蓝+绿=白 举个例子:显示黄色,其RGB值为255, 255, 0。 那么上面的 24bit 数据结构为:1111 1111 1111 1111 0000 0000 (其中的1和0 是上面说的 “1”码 和“0”码)综合上面,不管使用哪一种方式,其中都是需要在 DIN 端给出符合时间要求的高低电平,才能正确的控制 SK6812:1.2 SK6812 控制方案知道了 SK6812 的控制原理,在开发板上面,我们使用一个IO口 连接 DIN端,作为信号的输入端,那么就需要实现 这个IO口实现符合时序的 高低电平,那么有哪些实现方式呢?1.2.1 GPIO 翻转最终目的是需要实现规定时间的高低电平,那么最直接想到的就是 直接把 GPIO 翻转速度设置成最大,然后直接置位 复位 GPIO 实现高低电平。 = =!但是仔细想一下上面的时间要求是 us 级别的,那么对于不同的芯片,不仅是因为主频不同,IO口的翻转速度当然也会不同,在网络上查看到(仅供参考):1、STM32看到一篇博文直接操作寄存器222ns 采用库函数指令会延迟300ns左右,参考博文 无聊测一下IO口翻转速度 STM32F103RCT6以主频为72MHz为例,指令控制GPIO翻转,最高可达18MHz。直接操作寄存器,单指令周期的,407超频200M的时候,IO口刚好100M上述事件自己通过公式算一下即可:f=1/T。(T的单位是秒(s),f的单位是赫兹(Hz))。2、ESP32ESP32的IO口速度,简单查找没有找到说明。3、ESP8266ESP8266的GPIO有效翻转大约须要2.5us(0.4MHz)ESP8266的GPIO0的翻转速度最快,配合寄存器操作可以实现算下来,目前来说MCU的发展,还是有能够直接控制 IO 口电平实现的条件,这种简单粗暴的方式需要经过反复的测试调整,因为对于时间的控制还需要考虑很多因素。所以这里介绍一下,不过多探究。1.2.2 SPI方式SPI方式,SPI通讯的速度目前器件可以达到大几十Mbps,一般情况下,SPI模块的最大时钟频率为系统时钟频率的1/2。 SPI 的基础知识网上很多,在我博文《总线协议记录》也有记录。所以通过 SPI 总线发送是比较可行的一种方式。 只要将 SPI 的时钟调整为 8MHz左右(小于等于8Mhz),这样不同的 MCU下,都可以实现。采用 8Mhz SPI,发送一个字节所需时间1.25us,满足上面 SK6812 的24bit 数据结构 一个bit 的时间:再根据 “1” 码 < 1us && >0.6us 的 高电平, >0.2us的低电平,得出,SPI发送一个字节 11111100b 即表示 “1” 码,0XF0。同理可得,SPI发送字节 11000000b 即表示 “0” 码,0xC0。那么还是按照上面原理部分举的例子,显示黄色的 24bit 数据结构为:1111 1111 1111 1111 0000 0000 那么SPI总线发送如下的24个字节数据(十六进制),就能使得LED显示为黄色:F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 F0 C0 C0 C0 C0 C0 C0 C0 C0 1.2.2 PWM方式PWM 方式也是一种常见的控制高低电平的方式,通过上面我们得知,“1”码 和 “0” 码的高低电平比例为3:1. 那么就是调节占空比 ,“1”码的占空比为 75% ,“0”码的占空比为 25%。 那么剩下的只需要把 PWM 的周期设置成 SK6812 的码元周期 > 1.2us 左右。 注意占空比多少,可以根据实际情况调整。给出2个网上的结论,仅供参考:cycle(周期)=1.2us,占空比=50%为1,占空比=30%为0; (833Khz ,感觉还行)周期设置为3MHz,占空比 = 66% 为1,占空比=33%为0; (0.33us周期,估计写错了,1MHZ还差不多, 1us)关于ESP32 -C3 使用PWM 方式,不确定可不可以用,毕竟 ESP32 -C3 的PWM按照我们的博文流程我们还没有学习测试,下一篇博文写一下 ESP32 -C3 的PWM学习测试记录。1.2.3 RMT方式(ESP32)RMT,是ESP32 系列特有的一个红外发送和接收控制器,红外协议转化为信号,体现在IO上也就是高低电平。将上面讲到的 “1” 码 和 “0” 码当成红外信号,也可以实现 SK6812 的控制。下面就先来了解下 ESP32-C3 的 RMT。二、 ESP32-C3 RMT介绍2.1 RMT 基础介绍在乐鑫官方 ESP32-C3 芯片手册《esp32-c3_technical_reference_manual_cn》文档中对于 RMT 有详细的介绍:在官方网站也有关于 RMT 相关API的详细介绍:乐鑫官方ESP32-C3 RMT部分说明所以详细的资料还是可以通过上面的途径查看,这里我们需要关注的一点就是:RMT 是如何控制 SK6812 的?通过前面的SK6812 控制原理我们知道了,控制 SK2812 就是实现符合时间规定的高低电平,那么在ESP32-C3 芯片手册中,有提到 RMT 是如何实现此功能的,对于部分如下图:(当然如果要了解更深还是要好好查看官方的资料)结合官网图片就能更容易理解:2.2 RMT 使用介绍(API相关)RMT 的使用基本步骤如下,但是本文我们是需要控制 SK6812 ,所以只需要了解发送相关的配置及使用:首先要了解的是一个结构体,发送配置的结构体rmt_tx_config_t:上述结构体内容 依次是:RMT载波频率、RMT输出的电平、空闲电平状态、占空比、最大循环计数、载波使能、循环发送使能、空闲电平输出使能。通过初始化结构体的示例,可以更好的理解:RMT 输出结构体默认配置如下:对于控制 SK6812,目前了解到 RMT 的输入配置就可以了。三、 RMT 示例测试3.1 IDF 示例测试在 IDF 示例程序中,官方提供了控制 WS2812 的示例 RMT Transmit Example -- LED Strip:程序的过程比较简单,SK6812的驱动和ws2812的驱动是一样的,相关的代码在components/led_strip/src/led_strip_rmt_ws2812.c 文件中。针对自己的开发板,然后对于示例工程,简单修改一下既可以看到效果,因为示例大家都一样,这里就使用截图表示需要修改的地方:在示例中EXAMPLE_CHASE_SPEED_MS 太快了,闪得我眼睛有点花,把这个时间改成了300:#define EXAMPLE_CHASE_SPEED_MS (300)//在我的开发板上面,本来确实是只有一个LED,但是为了测试,我飞线焊接了一个:测试结果,示例的现象就是,LED不同颜色的交替闪烁,并没有渐变效果,这里上几张图勉强看看:3.2 示例改渐变效果最开始也没有一点一点的去分析驱动代码,示例代码也就看看 RMT 的配置,后面的 SK6812 驱动部分并没有仔细研究,所以测试是闪烁效果,后来想想还是不得劲,不渐变闪烁,这不得亮瞎眼= =!所以还是得改改,所以看了看示例,其实也就是简单的修改(最后一个vTaskDelay(50)不需要,这里是以前改过的代码忘了去掉了):根据上面图示的说明,把所有时间改成如下,是基于例程基础 最平滑 最快速的渐变了:没视频看不到= =! 上张图勉强应付一下:四、 SK6812 驱动代码说明2022/6/16 更新 by 矜辰所致 最近 ESP32-C3 的学习博主已经更新完了 蓝牙 GATT篇章,正准备写一篇蓝牙的小应用,计划要通过手机与开发板进行蓝牙连接,控制板子上的灯,能够渐变当然是最好了,忽然发现 SK6812 的驱动函数忘了怎么用了…… 在官方示例中,给了最原始的驱动,但是感觉当时没有理解透彻,所以回过头来重新看一看。4.1 驱动函数简析我们在使用中,需要定义一个LED变量,比如:static led_strip_t *strip;我们来看一看 led_strip_t ,他是 led_strip_s 结构体类型:struct led_strip_s { /** 设置灯的颜色 */ esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); /** 更新灯的颜色 */ esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms); /** 清除灯的颜色 */ esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms); /** 删除灯这个对象 */ esp_err_t (*del)(led_strip_t *strip); };在示例中我们都使用到了这几个函数,简单记录一下这几个函数的说明:设置灯的颜色:/* 参数含义: 灯的句柄,我们开始定义的变量 需要设置的灯的位置下标,从0开始,如果有很多灯,一般都是使用for循环赋值 红色的值 绿色的值 蓝色的值 */ static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)更新灯的值:使用上面函数设置完LED颜色值后,需要调用ws2812_refresh 将颜色更新到灯条: static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms)清除灯的颜色:等于把等熄灭:static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms)SK2812设备注册函数:另外还有一个函数需要注意,就是用来注册新的SK2812设备的函数:led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config)使用此函数来注册设备,比如:4.2 设置指定颜色设置指定延时,我们可以直接使用 ws2812_set_pixel 函数,对于我们定义的结构体变量,根据示例使用即可,设置指定颜色就很简单了:上图颜色 绿色 和 红色 搞反了!set_pixel(strip, 0, 255, 0, 0)是绿色,第二个是红色 !红色 和 绿色 对应位置好像反了,是因为ESP-IDF 驱动中的颜色处理驱动函数是反过来的:为了与正常的颜色值对应,改成如下即可:当然为了对应颜色代码(比如 #0033FF 形式)我们可以简单的做几个函数处理一下:struct WS2812_COLOR { uint8_t lamp; uint8_t ligth_rank; uint8_t lamp_speed; uint32_t red; uint32_t green; uint32_t blue; }; static led_strip_t *strip; struct WS2812_COLOR WS2812_RGB; void RGB16for10(struct WS2812_COLOR *RGB, uint32_t reb_16) { uint32_t rgb_16 = reb_16; RGB->blue = rgb_16 & 0Xff; rgb_16 = rgb_16 >> 8; RGB->green = rgb_16 & 0xff; rgb_16 = rgb_16 >> 8; RGB->red = rgb_16 & 0xff; } void set_rgb(uint32_t rgb_24bit, uint8_t ligth_rank) { RGB16for10(&WS2812_RGB, rgb_24bit); ligth_rank = 21 - ligth_rank; for (int i = 0; i < LED_STRIP_NUM; i++) { strip->set_pixel(strip, i, WS2812_RGB.red / ligth_rank, WS2812_RGB.green / ligth_rank, WS2812_RGB.blue / ligth_rank); } strip->refresh(strip, 10); } 在使用的时候,可以直接使用 set_rgb 函数:如果设置其他颜色,需要查对应的表格了,在网上找了一张表格,简单看看,网上也有很多 RGB 查询工具,一搜索就可以出来:4.2 几个渐变驱动渐变的示例,通过上文官方的示例也可以做到,我这里发现一片好的文章,文章博主写了几种好的方式,博文连接如下:ESP32使用外设RMT控制WS2812灯条程序选自上面推荐博文:程序一:/** * @brief sin()函数从0到2π的样本值,一共255个点,最大值为255,最小值为0 * * 离散信号函数:SinValue(k)=(255*sin(2*k*π/255)+255)/2 (四舍五入取整数) * */ uint8_t const SinValue[256]={ 128, 131, 134, 137, 140, 143, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174, 177, 180, 182, 185, 188, 191, 194, 196, 199, 201, 204, 206, 209, 211, 214, 216, 218, 220, 223, 225, 227, 229, 230, 232, 234, 236, 237, 239, 240, 242, 243, 245, 246, 247, 248, 249, 250, 251, 252, 252, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 253, 253, 252, 251, 250, 249, 249, 247, 246, 245, 244, 243, 241, 240, 238, 237, 235, 233, 231, 229, 228, 226, 224, 221, 219, 217, 215, 212, 210, 208, 205, 203, 200, 198, 195, 192, 189, 187, 184, 181, 178, 175, 172, 169, 166, 163, 160, 157, 154, 151, 148, 145, 142, 139, 136, 132, 129, 126, 123, 120, 117, 114, 111, 107, 104, 101, 98, 95, 92, 89, 86, 83, 80, 77, 74, 72, 69, 66, 63, 61, 58, 55, 53, 50, 48, 45, 43, 41, 38, 36, 34, 32, 30, 28, 26, 24, 22, 21, 19, 17, 16, 14, 13, 12, 10, 9, 8, 7, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 5, 6, 6, 7, 9, 10, 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 125, 128 }; void WS2812B_ColourGradualChange1(led_strip_t *strip, uint16_t LED_Number, uint16_t GradualChangeRate) { uint32_t Green=0,Red=0,Blue=0; uint8_t i,ir,ib; for(i=0;i<255;i++) { ir=i+85; ib=i+170; Green=SinValue[i]; Red=SinValue[ir]; Blue=SinValue[ib]; for(int j=0; j < LED_Number; j ++){ // 设置ws2812的RGB的值 ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue)); } // 给WS2812发送RGB的值 ESP_ERROR_CHECK(strip->refresh(strip, 100)); vTaskDelay(pdMS_TO_TICKS(GradualChangeRate)); } } ———————————————— 版权声明:本文为CSDN博主「milk_docker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/tian_milk/article/details/123585610程序二:// 必须包含math.h库 #include <math.h> void WS2812B_ColourGradualChange2(led_strip_t *strip,uint16_t LED_Number,uint16_t GradualChangeRate) { uint32_t Green=0,Red=0,Blue=0; for(uint16_t i=0; i<628; i++) { // 使用sin()函数分别计算三原色的值 Green = (int)(127*sin(i/100.0)+127); Red = (int)(127*sin((i+209.3)/100)+127); Blue = (int)(127*sin((i+418.7)/100)+127); for(int j=0; j < LED_Number; j ++){ // 设置ws2812的RGB的值 ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue)); } // 给WS2812发送RGB的值 ESP_ERROR_CHECK(strip->refresh(strip, 100)); vTaskDelay(pdMS_TO_TICKS(GradualChangeRate)); } } ———————————————— 版权声明:本文为CSDN博主「milk_docker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/tian_milk/article/details/123585610第三个参数为渐变时间,测试过最小时间可能需要设置为 10 ,也就是10ms(设置为5ms灯不会亮……)程序三:void WS2812B_TrottingHorseLamp1(led_strip_t *strip, uint16_t LED_Number, uint16_t GradualChangeRate) { uint32_t Green=0,Red=0,Blue=0; uint8_t i,ir,ib; for(i=0;i<256;i++) { ir=i+85; ib=i+170; for(int j=0; j < LED_Number; j ++){ // 根据灯珠的位置计算RGB的值 Green = SinValue[(i+10*j)%256]; Red = SinValue[(ir+10*j)%256]; Blue = SinValue[(ib+10*j)%256]; // 设置ws2812的RGB的值 ESP_ERROR_CHECK(strip->set_pixel(strip, j, Red, Green, Blue)); } // 给WS2812发送RGB的值 ESP_ERROR_CHECK(strip->refresh(strip, 100)); vTaskDelay(pdMS_TO_TICKS(GradualChangeRate)); } } ———————————————— 版权声明:本文为CSDN博主「milk_docker」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/tian_milk/article/details/123585610最后这个自己没有测试过,有机会再用起来。结语第一次的时候求速度,回头再看一遍感触颇多。<3 学习就得做到温故而知新 O(∩_∩)O <3
文章
传感器  ·  Ubuntu  ·  API  ·  芯片  ·  Windows
2022-09-15
android开发性能分析
1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾。不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结、我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只给出啥啥啥不能用,啥啥啥该咋用等,却很少有较为系统的进行真正性能案例分析的,大多数都是嘴上喊喊或者死记住规则而已(当然了,这话我自己听着都有些刺耳,实在不好意思,其实关于性能优化的优质博文网上也还是有很多的,譬如Google官方都已经推出了优化专题,我这里只是总结下自的感悟而已,若有得罪欢迎拍砖,我愿挨打,因为我之前工作的一半时间都是负责性能优化)。 当然了,本文不会就此编辑这么一次,因为技术在发展,工具在强大(写着写着Android Studio 1.4版本都推送了),自己的经验也在增加,所以本文自然不会覆盖所有性能优化及分析;解决的办法就是该文章会长期维护更新,同时在评论区欢迎你关于性能优化点子的探讨。 Android应用的性能问题其实可以划分为几个大的模块的,而且都具有相对不错的优化调试技巧,下面我们就会依据一个项目常规开发的大类型来进行一些分析讲解。 PS:之前呆过一家初创医疗互联网公司,别提性能优化了,老板立完新项目后一个月就要求见到上线成品,这种压迫下谈何性能优化,纯属扯蛋,所以不到三个月时间我主动选择撤了,这种现象后来我一打听发现在很多初创公司都很严重,都想速成却忽略了体验。 PPPS:本文只是达到抛砖引玉的作用,很多东西细究下去都是值得深入研究的,再加上性能优化本来就是一个需要综合考量的任务,不是说会了本文哪一点就能做性能分析了,需要面面俱到才可高效定位问题原因。 【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】 2 应用UI性能问题分析 UI可谓是一个应用的脸,所以每一款应用在开发阶段我们的交互、视觉、动画工程师都拼命的想让它变得自然大方美丽,可是现实总是不尽人意,动画和交互总会觉得开发做出来的应用用上去感觉不自然,没有达到他们心目中的自然流畅细节;这种情况之下就更别提发布给终端用户使用了,用户要是能够感觉出来,少则影响心情,多则卸载应用;所以一个应用的UI显示性能问题就不得不被开发人员重视。 2-1 应用UI卡顿原理 人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这种流畅的帧率规定为60fps。 有了上面的背景,我们开发App的帧率性能目标就是保持在60fps,也就是说我们在进行App性能优化时心中要有如下准则: <code class="hljs brainfuck has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">换算关系:60帧/秒</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">16ms/帧;</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">准则:尽量保证每次在16ms内处理完所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul> 从上面可以看出来,所谓的卡顿其实是可以量化的,每次是否能够成功渲染是非常重要的问题,16ms能否完整的做完一次操作直接决定了卡顿性能问题。 当然了,针对Android系统的设计我们还需要知道另一个常识;虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾回收完成之后所有线程才能够继续执行(这个细节下面小节会有详细介绍)。也就是说当在16ms内进行渲染等操作时如果刚好遇上大量GC操作则会导致渲染时间明显不足,也就从而导致了丢帧卡顿问题。 有了上面这两个简单的理论基础之后我们下面就会探讨一些UI卡顿的原因分析及解决方案。 2-2 应用UI卡顿常见原因 我们在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿,究其原因,很多都是丢帧导致的;通过上面卡顿原理的简单说明我们从应用开发的角度往回推理可以得出常见卡顿原因,如下: 人为在UI线程中做轻微耗时操作,导致UI线程卡顿; 布局Layout过于复杂,无法在16ms内完成渲染; 同一时间动画执行的次数过多,导致CPU或GPU负载过重; View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重; View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染; 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作; 冗余资源及逻辑等导致加载和执行缓慢; 臭名昭著的ANR; 可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些人可能会觉得自己的应用用着还蛮OK的,其实那是因为你没进行一些瞬时测试和压力测试,一旦在这种环境下运行你的App你就会发现很多性能问题。 2-3 应用UI卡顿分析解决方法 分析UI卡顿我们一般都借助工具,通过工具一般都可以直观的分析出问题原因,从而反推寻求优化方案,具体如下细说各种强大的工具。 2-3-1 使用HierarchyViewer分析UI性能 我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析,如下: <code class="hljs ruby has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">xxx<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">@ThinkPad</span><span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">:~</span><span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">$ </span>hierarchyviewer /<span class="hljs-regexp" style="color: rgb(0, 136, 0); box-sizing: border-box;">/通过命令启动HierarchyViewer</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 选中一个Window界面item,然后点击右上方Hierarchy window或者Pixel Perfect window(这里不介绍,主要用来检查像素属性的)即可操作。 先看下Hierarchy window,如下: 一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存psd的PhotoShop分层素材;右侧剧中显示选中View的当前属性状态;右下角显示当前View在Activity中的位置等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(不会自动刷新的)。当我们选择一个View后会如下图所示: 类似上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色原点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。 当然了,在自定义View的性能调试时,HierarchyViewer上面的invalidate Layout和requestLayout按钮的功能更加强大,它可以帮助我们debug自定义View执行invalidate()和requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。 可以发现,有了HierarchyViewer调试工具,我们的UI性能分析变得十分容易,这个工具也是我们开发中调试UI的利器,在平时写代码时会时常伴随我们左右。 2-3-2 使用GPU过度绘制分析UI性能 我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析): 可以发现,开启后在我们想要调试的应用界面中可以看到各种颜色的区域,具体含义如下: 颜色 含义 无色 WebView等的渲染区域 蓝色 1x过度绘制 绿色 2x过度绘制 淡红色 3x过度绘制 红色 4x(+)过度绘制 由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚丽的界面是不现实的,所以大家有个度即可,我们的开发性能优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一,可见还是比较宽松的规定),因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。 2-3-3 使用GPU呈现模式图及FPS考核UI性能 Android界面流畅度除过视觉感知以外是可以考核的(测试妹子专用),常见的方法就是通过GPU呈现模式图或者实时FPS显示进行考核,这里我们主要针对GPU呈现模式图进行下说明,因为FPS考核测试方法有很多(譬如自己写代码实现、第三方App测试、固件支持等),所以不做统一说明。 通过开发者选项中GPU呈现模式图工具来进行流畅度考量的流程是(注意:如果是在开启应用后才开启此功能,记得先把应用结束后重新启动)在设置->开发者选项->GPU呈现模式(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面上下滑动列表后的图表): 当然,也可以在执行完UI滑动操作后在命令行输入如下命令查看命令行打印的GPU渲染数据(分析依据:Draw + Process + Execute = 完整的显示一帧时间 < 16ms): <code class="hljs css has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-tag" style="color: rgb(0, 0, 0); box-sizing: border-box;">adb</span> <span class="hljs-tag" style="color: rgb(0, 0, 0); box-sizing: border-box;">shell</span> <span class="hljs-tag" style="color: rgb(0, 0, 0); box-sizing: border-box;">dumpsys</span> <span class="hljs-tag" style="color: rgb(0, 0, 0); box-sizing: border-box;">gfxinfo</span> <span class="hljs-attr_selector" style="color: rgb(0, 136, 0); box-sizing: border-box;">[应用包名]</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 打开上图可视化工具后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别展示了StatusBar、NavgationBar、Activity区域等的GPU渲染时间信息,随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算啥问题的)。 可以发现,这个工具是有局限性的,他虽然能够看出来有帧耗时超过基准线导致了丢帧卡顿,但却分析不到造成丢帧的具体原因。所以说为了配合解决分析UI丢帧卡顿问题我们还需要借助traceview和systrace来进行原因追踪,下面我们会介绍这两种工具的。 2-3-4 使用Lint进行资源及冗余UI布局等优化 上面说了,冗余资源及逻辑等也可能会导致加载和执行缓慢,所以我们就来看看Lint这个工具是如何发现优化这些问题的(当然了,Lint实际的功能是非常强大的,我们开发中也是经常使用它来发现一些问题的,这里主要有点针对UI性能的说明了,其他的雷同)。 在Android Studio 1.4版本中使用Lint最简单的办法就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果: 可以看见,Lint检测完后给了我们很多建议的,我们重点看一个关于UI性能的检测结果;上图中高亮的那一行明确说明了存在冗余的UI层级嵌套,所以我们是可以点击跳进去进行优化处理掉的。 当然了,Lint还有很多功能,大家可以自行探索发挥,这里只是达到抛砖引玉的作用。 2-3-5 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析 关于Android的内存管理机制下面的一节会详细介绍,这里我们主要针对GC导致的UI卡顿问题进行详细说明。 Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作,常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频繁的对象创建与释放操作,也就是俗称的内存抖动现象,或者短时间内已经存在大量内存暂用介于阈值边缘,接着每当有新对象创建时都会导致超越阈值触发GC操作。 如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当普通使用时不会有问题,压力测试必现卡顿。具体内存参考图如下:   与此抖动图对应的LogCat抓取如下: <code class="hljs asciidoc has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//截取其中比较密集一段LogCat,与上图Memory检测到的抖动图对应,其中xxx为应用包名</span> <span class="hljs-code" style="box-sizing: border-box;">...... 10-06 00:59:45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515(3MB) AllocSpace objects, 65(2028KB) LOS objects, 80% free, 17MB/89MB, paused 3.505ms total 60.958ms 10-06 00:59:45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396(193KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.079ms total 100.522ms ......</span> 10-06 00:59:48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693(172KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.227ms total 101.692ms ......</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul> 我们知道,类似上面logcat打印一样,触发垃圾回收的主要原因有以下几种: GC_MALLOC——内存分配失败时触发; GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发; GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ; GC_EXTERNAL_ALLOC——外部内存分配失败时触发; 可以看见,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下: 检查代码,尽量避免有些频繁触发的逻辑方法中存在大量对象分配; 尽量避免在多次for循环中频繁分配对象; 避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等); 对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理。 当然了,有了上面说明GC导致的性能后我们就该定位分析问题了,可以通过运行DDMS->Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表,如下: 点击上面第一个表格中的任何一项就能够在第二个表格中看见导致该内存分配的栈信息,通过这个工具我们可以很方便的知道代码分配了哪类对象、在哪个线程、哪个类、哪个文件的哪一行。譬如我们可以通过Allocation Tracker分别做一次Paint对象实例化在onDraw与构造方法的一个自定义View的内存跟踪,然后你就明白这个工具的强大了。 PS一句,Android Studio新版本除过DDMS以外在Memory视图的左侧已经集成了Allocation Tracker功能,只是用起来还是没有DDMS的方便实用,如下图: 2-3-6 使用Traceview和dmtracedump进行分析优化 关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,他是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现下图,如下: 花花绿绿的一幅图我们怎么分析呢?下面我们解释下如何通过该工具定位问题: 整个界面包括上下两部分,上面是你测试的进程中每个线程运行的时间线,下面是每个方法(包含parent及child)执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面的性能面板很复杂,其实这才是TraceView的核心图表,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点,所以我们先看几个重要的属性说明,如下: 属性名 含义 name 线程中调运的方法名; Incl CPU Time 当前方法(包含内部调运的子方法)执行占用的CPU时间; Excl CPU Time 当前方法(不包含内部调运的子方法)执行占用的CPU时间; Incl Real Time 当前方法(包含内部调运的子方法)执行的真实时间,ms单位; Excl Real Time 当前方法(不包含内部调运的子方法)执行的真实时间,ms单位; Calls+Recur Calls/Total 当前方法被调运的次数及递归调运占总调运次数百分比; CPU Time/Call 当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间; Real Time/Call 当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注) 有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题: 方法调运一次需要耗费很长时间导致卡顿; 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。 譬如我们来举个实例,有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可;当然了,我们也可以通过该工具来trace我们自定义View的一些方法来权衡性能问题,这里不再一一列举喽。 可以看见,Traceview能够帮助我们分析程序性能,已经很方便了,然而Traceview家族还有一个更加直观强大的小工具,那就是可以通过dmtracedump生成方法调用图。具体做法如下: <code class="hljs rsl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">dmtracedump -g result.png target.<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">trace</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//结果png文件 目标trace文件</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 通过这个生成的方法调运图我们可以更加直观的发现一些方法的调运异常现象。不过本人优化到现在还没怎么用到它,每次用到Traceview分析就已经搞定问题了,所以说dmtracedump自己酌情使用吧。 PS一句,Android Studio新版本除过DDMS以外在CPU视图的左侧已经集成了Traceview(start Method Tracing)功能,只是用起来还是没有DDMS的方便实用(这里有一篇AS MT个人觉得不错的分析文章(引用自网络,链接属于原作者功劳)),如下图: 2-3-7 使用Systrace进行分析优化 Systrace其实有些类似Traceview,它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。 我们在分析UI性能时一般只关注图形性能(所以必须选择Graphics和View,其他随意),同时一般对于卡顿的抓取都是5s,最多10s。启动Systrace进行数据抓取可以通过两种方式,命令行方式如下: <code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">python systrace<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.py</span> --time=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> -o mynewtrace<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.html</span> sched gfx view wm</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 图形模式:  打开DDMS->Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:   在Chrome中浏览分析该文件我们可以通过键盘的W-A-S-D键来搞定,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下: 可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的任务,具体如下:  可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续详细分析,总之很蛋疼。 PS:如果你想使用Systrace很轻松的分析定位所有问题,看明白所有的行含义,你还需要具备非常扎实的Android系统框架的原理才可以将该工具使用的得心应手。 2-3-8 使用traces.txt文件进行ANR分析优化 ANR(Application Not Responding)是Android中AMS与WMS监测应用响应超时的表现;之所以把臭名昭著的ANR单独作为UI性能卡顿的分析来说明是因为ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免他的出现,当然了,万一出现了那就用下面介绍的方法来分析吧。 我们应用开发中常见的ANR主要有如下几类: 按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR); 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示); 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示); 当ANR发生时除过logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,发生ANR后我们可以通过如下命令得到ANR trace文件: <code class="hljs haskell has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-title" style="box-sizing: border-box;">adb</span> pull /<span class="hljs-typedef" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">data</span>/anr/traces.txt ./</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 然后我们用txt编辑器打开可以发现如下结构分析: <code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">//显示进程id、ANR发生时间点、ANR发生进程包名 ----- pid <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">19073</span> at <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2015</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">08</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">17</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">24</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">38</span> ----- Cmd line: <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.example</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.yanbo</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.myapplication</span> //一些GC等object信息,通常可以忽略 ...... //ANR方法堆栈打印信息!重点! DALVIK THREADS (<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">18</span>): <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"main"</span> prio=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span> tid=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> Sleeping | group=<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"main"</span> sCount=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> dsCount=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> obj=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7497dfb8</span> self=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7f9d09a000</span> | sysTid=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">19073</span> nice=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> cgrp=default sched=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> handle=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7fa106c0a8</span> | state=S schedstat=( <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">125271779</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">68162762</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">280</span> ) utm=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11</span> stm=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span> core=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> HZ=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span> | stack=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7fe90d3000</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x7fe90d5000</span> stackSize=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>MB | held mutexes= at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Thread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.sleep</span>!(Native method) - sleeping on <<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x0a2ae345</span>> (a java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Object</span>) at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Thread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.sleep</span>(Thread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1031</span>) - locked <<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x0a2ae345</span>> (a java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Object</span>) //真正导致ANR的问题点,可以发现是onClick中有<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">sleep</span>导致。我们平时可以类比分析即可,这里不详细说明。 at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Thread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.sleep</span>(Thread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">985</span>) at <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.example</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.yanbo</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.myapplication</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MainActivity</span>$1<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.onClick</span>(MainActivity<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">21</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.View</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.performClick</span>(View<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4908</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.View</span>$PerformClick<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.run</span>(View<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">20389</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Handler</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.handleCallback</span>(Handler<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">815</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Handler</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.dispatchMessage</span>(Handler<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">104</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Looper</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.loop</span>(Looper<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">194</span>) at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5743</span>) at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.reflect</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Method</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.invoke</span>!(Native method) at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.reflect</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Method</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.invoke</span>(Method<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">372</span>) at <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.android</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.internal</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ZygoteInit</span>$MethodAndArgsCaller<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.run</span>(ZygoteInit<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">988</span>) at <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.android</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.internal</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ZygoteInit</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>(ZygoteInit<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">783</span>) ...... //省略一些不常关注堆栈打印 ......</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li></ul> 至此常见的应用开发中ANR分析定位就可以解决了。 2-4 应用UI性能分析解决总结 可以看见,关于Android UI卡顿的性能分析还是有很多工具的,上面只是介绍了应用开发中我们经常使用的一些而已,还有一些其他的,譬如Oprofile等工具不怎么常用,这里就不再详细介绍。 通过上面UI性能的原理、原因、工具分析总结可以发现,我们在开发应用时一定要时刻重视性能问题,如若真的没留意出现了性能问题,不妨使用上面的一些案例方式进行分析。但是那终归是补救措施,在我们知道上面UI卡顿原理之后我们应该尽量从项目代码架构搭建及编写时就避免一些UI性能问题,具体项目中常见的注意事项如下: 布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。 列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。 自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。 避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等。 当然了,上面只是列出了我们项目中常见的一些UI性能注意事项而已,相信还有很多其他的情况这里没有说到,欢迎补充。还有一点就是我们上面所谓的UI性能优化分析总结等都是建议性的,因为性能这个问题是一个涉及面很广很泛的问题,有些优化不是必需的,有些优化是必需的,有些优化掉以后又是得不偿失的,所以我们一般着手解决那些必须的就可以了。 【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】 3 应用开发Memory内存性能分析优化 说完了应用开发中的UI性能问题后我们就该来关注应用开发中的另一个重要、严重、非常重要的性能问题了,那就是内存性能优化分析。Android其实就是嵌入式设备,嵌入式设备核心关注点之一就是内存资源;有人说现在的设备都在堆硬件配置(譬如国产某米的某兔跑分手机、盒子等),所以内存不会再像以前那么紧张了,其实这句话听着没错,但为啥再牛逼配置的Android设备上有些应用还是越用系统越卡呢?这里面的原因有很多,不过相信有了这一章下面的内容分析,作为一个移动开发者的你就有能力打理好自己应用的那一亩三分地内存了,能做到这样就足以了。关于Android内存优化,这里有一篇Google的官方指导文档,但是本文为自己项目摸索,会有很多不一样的地方。 3-1 Android内存管理原理 系统级内存管理: Android系统内核是基于Linux,所以说Android的内存管理其实也是Linux的升级版而已。Linux在进程停止后就结束该进程,而Android把这些停止的进程都保留在内存中,直到系统需要更多内存时才选择性的释放一些,保留在内存中的进程默认(不包含后台service与Thread等单独UI线程的进程)不会影响整体系统的性能(速度与电量等)且当再次启动这些保留在内存的进程时可以明显提高启动速度,不需要再去加载。 再直白点就是说Android系统级内存管理机制其实类似于Java的垃圾回收机制,这下明白了吧;在Android系统中框架会定义如下几类进程、在系统内存达到规定的不同level阈值时触发清空不同level的进程类型。 可以看见,所谓的我们的Service在后台跑着跑着挂了,或者盒子上有些大型游戏启动起来就挂(之前我在上家公司做盒子时遇见过),有一个直接的原因就是这个阈值定义的太大,导致系统一直认为已经达到阈值,所以进行优先清除了符合类型的进程。所以说,该阈值的设定是有一些讲究的,额,扯多了,我们主要是针对应用层内存分析的,系统级内存回收了解这些就基本够解释我们应用在设备上的一些表现特征了。 应用级内存管理: 在说应用级别内存管理原理时大家先想一个问题,假设有一个内存为1G的Android设备,上面运行了一个非常非常吃内存的应用,如果没有任何机制的情况下是不是用着用着整个设备会因为我们这个应用把1G内存吃光然后整个系统运行瘫痪呢? 哈哈,其实Google的工程师才不会这么傻的把系统设计这么差劲。为了使系统不存在我们上面假想情况且能安全快速的运行,Android的框架使得每个应用程序都运行在单独的进程中(这些应用进程都是由Zygote进程孵化出来的,每个应用进程都对应自己唯一的虚拟机实例);如果应用在运行时再存在上面假想的情况,那么瘫痪的只会是自己的进程,不会直接影响系统运行及其他进程运行。 既然每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。 接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收一般都会有明显的log打印出回收类型,常见的如下: GC_MALLOC——内存分配失败时触发; GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发; GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ; GC_EXTERNAL_ALLOC——外部内存分配失败时触发; 通过上面这几点的分析可以发现,应用的内存管理其实就是一个萝卜一个坑,坑都一般大,你在开发应用时要保证的是内存使用同一时刻不能超过坑的大小,否则就装不下了。 3-2 Android内存泄露性能分析 有了关于Android的一些内存认识,接着我们来看看关于Android应用开发中常出现的一种内存问题—-内存泄露。 3-2-1 Android应用内存泄露概念 众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。如下就是一个最常见简单的泄露例子(其它的泄露不再一一列举了): <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">MainActivity</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Activity</span> {</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> DbManager mDbManager; <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>(Bundle savedInstanceState) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate(savedInstanceState); setContentView(R.layout.activity_main); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露</span> mDbManager = DbManager.getInstance(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul> 可以看见,上面例子中我们让一个单例模式的对象持有了当前Activity的强引用,那在当前Acvitivy执行完onDestroy()后,这个Activity就无法得到垃圾回收,也就造成了内存泄露。 内存泄露可以引发很多的问题,常见的内存泄露导致问题如下: 应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC); 应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值); 应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM); 造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。 3-2-2 Android应用内存泄露察觉手段 知道了内存泄露的概念之后肯定就是想办法来确认自己的项目是否存在内存泄露了,那该如何察觉自己项目是否存在内存泄露呢?如下提供了几种常用的方式: 察觉方式 场景 AS的Memory窗口 平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。 DDMS-Heap内存监测工具 同上,大的泄露才能有感知。 dumpsys meminfo命令 常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。 leakcanary神器 比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。 AS的Memory窗口如下,详细的说明这里就不解释了,很简单很直观(使用频率高): DDMS-Heap内存监测工具窗口如下,详细的说明这里就不解释了,很简单(使用频率不高): dumpsys meminfo命令如下(使用频率非常高,非常高效,我的最爱之一,平时一般关注几个重要的Object个数即可判断一般的泄露;当然了,adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态): leakcanary神器使用这里先不说,下文会专题介绍,你会震撼的一B。有了这些工具的定位我们就能很方便的察觉我们App的内存泄露问题,察觉到以后该怎么定位分析呢,继续往下看。 3-2-3 Android应用内存泄露leakcanary工具定位分析 leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早的发觉内存泄露、配置简单、抓取贴心,缺点在于还存在一些bug,不过正常使用百分之九十情况是OK的,其核心原理与MAT工具类似。 关于leakcanary工具的配置使用方式这里不再详细介绍,因为真的很简单,详情点我参考官方教程学习使用即可。 PS:之前在优化性能时发现我们有一个应用有两个界面退出后Activity没有被回收(dumpsys meminfo发现一直在加),所以就怀疑可能存在内存泄露。但是问题来了,这两个Activity的逻辑十分复杂,代码也不是我写的,相关联的代码量也十分庞大,更加郁闷的是很难判断是哪个版本修改导致的,这时候只知道有泄露,却无法定位具体原因,使用MAT分析解决掉了一个可疑泄露后发现泄露又变成了概率性的。可以发现,对于这种概率性的泄露用MAT去主动抓取肯定是很耗时耗力的,所以决定直接引入leakcanary神器来检测项目,后来很快就彻底解决了项目中所有必现的、偶现的内存泄露。 总之一点,工具再强大也只是帮我们定位可能的泄露点,而最核心的GC ROOT泄露信息推导出泄露问题及如何解决还是需要你把住代码逻辑及泄露核心概念去推理解决。 3-2-4 Android应用内存泄露MAT工具定位分析 Eclipse Memory Analysis Tools(点我下载)是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GC ROOT位置即可,哎呀,关于这个工具的使用我是真的不想说了,自己搜索吧,实在简单、传统的不行了。 PS:这是开发中使用频率非常高的一个工具之一,麻烦务必掌握其核心使用技巧,虽然Android Studio已经实现了部分功能,但是真的很难用,遇到问题目前还是使用Eclipse Memory Analysis Tools吧。 原谅我该小节的放荡不羁!!!!(其实我是困了,呜呜!) 3-2-5 Android应用开发规避内存泄露建议 有了上面的原理及案例处理其实还不够,因为上面这些处理办法是补救的措施,我们正确的做法应该是在开发过程中就养成良好的习惯和敏锐的嗅觉才对,所以下面给出一些应用开发中常见的规避内存泄露建议: Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用(譬如上面概念部分给出的示例)。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换(原理我前面有一篇关于Context的文章有解释)。 非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。 警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。 对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。 创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。 不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。 避免代码设计模式的错误造成内存泄露;譬如循环引用,A持有B,B持有C,C持有A,这样的设计谁都得不到释放。 关于规避内存泄露上面我只是列出了我在项目中经常遇见的一些情况而已,肯定不全面,欢迎拍砖!当然了,只有我们做到好的规避加上强有力的判断嗅觉泄露才能让我们的应用驾驭好自己的一亩三分地。 3-3 Android内存溢出OOM性能分析 上面谈论了Android应用开发的内存泄露,下面谈谈内存溢出(OOM);其实可以认为内存溢出与内存泄露是交集关系,具体如下图: 下面我们就来看看内存溢出(OOM)相关的东东吧。 3-3-1 Android应用内存溢出OOM概念 上面我们探讨了Android内存管理和应用开发中的内存泄露问题,可以知道内存泄露一般影响就是导致应用卡顿,但是极端的影响是使应用挂掉。前面也提到过应用的内存分配是有一个阈值的,超过阈值就会出问题,这里我们就来看看这个问题—–内存溢出(OOM–OutOfMemoryError)。 内存溢出的主要导致原因有如下几类: 应用代码存在内存泄露,长时间积累无法释放导致OOM; 应用的某些逻辑操作疯狂的消耗掉大量内存(譬如加载一张不经过处理的超大超高清图片等)导致超过阈值OOM; 可以发现,无论哪种类型,导致内存溢出(OutOfMemoryError)的核心原因就是应用的内存超过阈值了。 3-3-2 Android应用内存溢出OOM性能分析 通过上面的OOM概念和那幅交集图可以发现,要想分析OOM原因和避免OOM需要分两种情况考虑,泄露导致的OOM,申请过大导致的OOM。 内存泄露导致的OOM分析: 这种OOM一旦发生后会在logcat中打印相关OutOfMemoryError的异常栈信息,不过你别高兴太早,这种情况下导致的OOM打印异常信息是没有太大作用,因为这种OOM的导致一般都如下图情况(图示为了说明问题数据和场景有夸张,请忽略): 从图片可以看见,这种OOM我们有时也遇到,第一反应是去分析OOM异常打印栈,可是后来发现打印栈打印的地方没有啥问题,没有可优化的余地了,于是就郁闷了。其实这时候你留心观察几个现象即可,如下: 留意你执行触发OOM操作前的界面是否有卡顿或者比较密集的GC打印; 使用命令查看下当前应用占用内存情况; 确认了以上这些现象你基本可以断定该OOM的log真的没用,真正导致问题的原因是内存泄露,所以我们应该按照上节介绍的方式去着手排查内存泄露问题,解决掉内存泄露后红色空间都能得到释放,再去显示一张0.8M的优化图片就不会再报OOM异常了。 不珍惜内存导致的OOM分析: 上面说了内存泄露导致的OOM异常,下面我们再来看一幅图(数据和场景描述有夸张,请忽略),如下: 可见,这种类型的OOM就很好定位原因了,一般都可以从OOM后的log中得出分析定位。 如下例子,我们在Activity中的ImageView放置一张未优化的特大的(30多M)高清图片,运行直接崩溃如下: <code class="language-txt hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">//抛出OOM异常 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.873</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/art: Throwing OutOfMemoryError <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.940</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/art: Throwing OutOfMemoryError <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"</span> //堆栈打印 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: FATAL EXCEPTION: main <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: Process: <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.example</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.application</span>, PID: <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.RuntimeException</span>: Unable to start activity ComponentInfo{<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.example</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.application</span>/<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.example</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.myapplication</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MainActivity</span>}: android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.InflateException</span>: Binary XML file line <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#21: Error inflating class <unknown></span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.performLaunchActivity</span>(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2610</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.handleLaunchActivity</span>(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2684</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.access</span>$800(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">177</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span>$H<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.handleMessage</span>(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1542</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Handler</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.dispatchMessage</span>(Handler<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">111</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Looper</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.loop</span>(Looper<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">194</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.app</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ActivityThread</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>(ActivityThread<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5743</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.reflect</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Method</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.invoke</span>(Native Method) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at java<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.lang</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.reflect</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Method</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.invoke</span>(Method<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">372</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.android</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.internal</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ZygoteInit</span>$MethodAndArgsCaller<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.run</span>(ZygoteInit<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">988</span>) <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.android</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.internal</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.os</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ZygoteInit</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.main</span>(ZygoteInit<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">783</span>) //出错地点,原因是<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">21</span>行的ImageView设置的src是一张未优化的<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">31</span>M的高清图片 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: Caused by: android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.InflateException</span>: Binary XML file line <span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">#21: Error inflating class <unknown></span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">09</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">04.958</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11703</span>/? E/AndroidRuntime: at android<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.view</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.LayoutInflater</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.createView</span>(LayoutInflater<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.java</span>:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">633</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul> 通过上面的log可以很方便的看出来问题原因所在地,那接下来的做法就是优化呗,降低图片的相关规格即可(譬如使用BitmapFactory的Option类操作等)。 PS:提醒一句的是记得应用所属的内存是区分Java堆和native堆的! 3-3-3 Android应用规避内存溢出OOM建议 还是那句话,等待OOM发生是为时已晚的事,我们应该将其扼杀于萌芽之中,至于如何在开发中规避OOM,如下给出一些我们应用开发中的常用的策略建议: 时刻记得不要加载过大的Bitmap对象;譬如对于类似图片加载我们要通过BitmapFactory.Options设置图片的一些采样比率和复用等,具体做法点我参考官方文档,不过过我们一般都用fresco或Glide开源库进行加载。 优化界面交互过程中频繁的内存使用;譬如在列表等操作中只加载可见区域的Bitmap、滑动时不加载、停止滑动后再开始加载。 有些地方避免使用强引用,替换为弱引用等操作。 避免各种内存泄露的存在导致OOM。 对批量加载等操作进行缓存设计,譬如列表图片显示,Adapter的convertView缓存等。 尽可能的复用资源;譬如系统本身有很多字符串、颜色、图片、动画、样式以及简单布局等资源可供我们直接使用,我们自己也要尽量复用style等资源达到节约内存。 对于有缓存等存在的应用尽量实现onLowMemory()和onTrimMemory()方法。 尽量使用线程池替代多线程操作,这样可以节约内存及CPU占用率。 尽量管理好自己的Service、Thread等后台的生命周期,不要浪费内存占用。 尽可能的不要使用依赖注入,中看不中用。 尽量在做一些大内存分配等可疑内存操作时进行try catch操作,避免不必要的应用闪退。 尽量的优化自己的代码,减少冗余,进行编译打包等优化对齐处理,避免类加载时浪费内存。 可以发现,上面只是列出了我们开发中常见的导致OOM异常的一些规避原则,还有很多相信还没有列出来,大家可以自行追加参考即可。 3-4 Android内存性能优化总结 无论是什么电子设备的开发,内存问题永远都是一个很深奥、无底洞的话题,上面的这些内存分析建议也单单只是Android应用开发中一些常见的场景而已,真正的达到合理的优化还是需要很多知识和功底的。 合理的应用架构设计、设计风格选择、开源Lib选择、代码逻辑规范等都会决定到应用的内存性能,我们必须时刻头脑清醒的意识到这些问题潜在的风险与优劣,因为内存优化必须要有一个度,不能一味的优化,亦不能置之不理。 【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】 4 Android应用API使用及代码逻辑性能分析 在我们开发中除过常规的那些经典UI、内存性能问题外其实还存在很多潜在的性能优化、这种优化不是十分明显,但是在某些场景下却是非常有必要的,所以我们简单列举一些常见的其他潜在性能优化技巧,具体如下探讨。 4-1 Android应用String/StringBuilder/StringBuffer优化建议 字符串操作在Android应用开发中是十分常见的操作,也就是这个最简单的字符串操作却也暗藏很多潜在的性能问题,下面我们实例来说说。 先看下面这个关于String和StringBuffer的对比例子: <code class="hljs javascript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//性能差的实现</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> str1 = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Name:"</span>; <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> str2 = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GJRS"</span>; <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> Str = str1 + str2; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//性能好的实现</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> str1 = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Name:"</span>; <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">String</span> str2 = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GJRS"</span>; StringBuffer str = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> StringBuilder().append(str1).append(str2);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul> 通过这个例子可以看出来,String对象(记得是对象,不是常量)和StringBuffer对象的主要性能区别在于String对象是不可变的,所以每次对String对象做改变操作(譬如“+”操作)时其实都生成了新的String对象实例,所以会导致内存消耗性能问题;而StringBuffer对象做改变操作每次都会对自己进行操作,所以不需要消耗额外的内存空间。 我们再看一个关于String和StringBuffer的对比例子: <code class="hljs go has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//性能差的实现</span> StringBuffer str = <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">new</span> StringBuilder().<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">append</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Name:"</span>).<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">append</span>(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GJRS"</span>); <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//性能好的实现</span> String Str = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Name:"</span> + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GJRS"</span>;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul> 在这种情况下你会发现StringBuffer的性能反而没有String的好,原因是在JVM解释时认为 String Str = "Name:" + "GJRS";就是String Str = "Name:GJRS";,所以自然比StringBuffer快了。 可以发现,如果我们拼接的是字符串常量则String效率比StringBuffer高,如果拼接的是字符串对象,则StringBuffer比String效率高,我们在开发中要酌情选择。当然,除过注意StringBuffer和String的效率问题,我们还应该注意另一个问题,那就是StringBuffer和StringBuilder的区别,其实StringBuffer和StringBuilder都继承自同一个父类,只是StringBuffer是线程安全的,也就是说在不考虑多线程情况下StringBuilder的性能又比StringBuffer高。 PS:如果想追究清楚他们之间具体细节差异,麻烦自己查看实现源码即可。 4-2 Android应用OnTrimMemory()实现性能建议 OnTrimMemory是Android 4.0之后加入的一个回调方法,作用是通知应用在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验(冷启动速度是热启动的2~3倍)。系统会根据当前不同等级的内存使用情况调用这个方法,并且传入当前内存等级,这个等级有很多种,我们可以依据情况实现不同的等级,这里不详细介绍,但是要说的是我们应用应该至少实现如下等级: TRIM_MEMORY_BACKGROUND  内存已经很低了,系统准备开始根据LRU缓存来清理进程。这时候如果我们手动释放一些不重要的缓存资源,则当用户返回我们应用时会感觉到很顺畅,而不是重新启动应用。 可以实现OnTrimMemory方法的系统组件有Application、Activity、Fragement、  Service、ContentProvider;关于OnTrimMemory释放哪些内存其实在架构阶段就要考虑清楚哪些对象是要常驻内存的,哪些是伴随组件周期存在的,一般需要释放的都是缓存。  如下给出一个我们项目中常用的例子: <code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onTrimMemory</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> level) { <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { clearCache(); } }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul> 通常在我们代码实现了onTrimMemory后很难复显这种内存消耗场景,但是你又怕引入新Bug,想想办法测试。好在我们有一个快捷的方式来模拟触发该水平内存释放,如下命令: <code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">adb <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">shell</span> dumpsys gfxinfo packagename -cmd trim <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">value</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> packagename为包名或者进程id,value为ComponentCallbacks2.java里面定义的值,可以为80、60、40、20、5等,我们模拟触发其中的等级即可。 4-3 Android应用HashMap与ArrayMap及SparseArray优化建议 在Android开发中涉及到数据逻辑部分大部分用的都是Java的API(譬如HashMap),但是对于Android设备来说有些Java的API并不适合,可能会导致系统性能下降,好在Google团队已经意识到这些问题,所以他们针对Android设备对Java的一些API进行了优化,优化最多就是使用了ArrayMap及SparseArray替代HashMap来获得性能提升。 HashMap: HashMap内部使用一个默认容量为16的数组来存储数据,数组中每一个元素存放一个链表的头结点,其实整个HashMap内部结构就是一个哈希表的拉链结构。HashMap默认实现的扩容是以2倍增加,且获取一个节点采用了遍历法,所以相对来说无论从内存消耗还是节点查找上都是十分昂贵的。 SparseArray: SparseArray比HashMap省内存是因为它避免了对Key进行自动装箱(int转Integer),它内部是用两个数组来进行数据存储的(一个存Key,一个存Value),它内部对数据采用了压缩方式来表示稀疏数组数据,从而节约内存空间,而且其查找节点的实现采用了二分法,很明显可以看见性能的提升。 ArrayMap: ArrayMap内部使用两个数组进行数据存储,一个记录Key的Hash值,一个记录Value值,它和SparseArray类似,也会在查找时对Key采用二分法。 有了上面的基本了解我们可以得出结论供开发时参考,当数据量不大(千位级内)且Key为int类型时使用SparseArray替换HashMap效率高;当数据量不大(千位级内)且数据类型为Map类型时使用ArrayMap替换HashMap效率高;其他情况下HashMap效率相对高于二者。 4-4 Android应用ContentProviderOperation优化建议 ContentProvider是Android应用开发的核心组件之一,有时候在开发中需要使用ContentProvider对多行数据进行操作,我们的做法一般是多次调运相关操作方法,殊不知这种实现方式是非常低性能的,取而代之的做法应该是使用批量操作,具体为了使批量更新、插入、删除数据操作更加方便官方提供了ContentProviderOperation工具类。所以在我们开发中遇到类似情景时请务必使用批量操作,具体的优势如下: 所有的操作都在一个事务中执行,可以保证数据的完整性。 批量操作在一个事务中执行,所以只用打开、关闭一个事务。 减轻应用程序与ContentProvider间的多次频繁交互,提升性能。 可以看见,这对于数据库操作来说是一个非常有用的优化措施,烦请务必重视(我们项目优化过,的确有很大提升)。 4-5 Android应用其他逻辑优化建议 关于API及逻辑性能优化其实有多知识点的,这里无法一一列出,只能给出一些重要的知识点,下面再给出一些常见的优化建议: 避免在Android中使用Java的枚举类型,因为编译后不但占空间,加载也费时,完全没有static final的变量好用、高效。 Handler发送消息时尽量使用obtain去获取已经存在的Message对象进行复用,而不是新new Message对象,这样可以减轻内存压力。 在使用后台Service时尽量将能够替换为IntentService的地方替换为此,这样可以减轻系统压力、省电、省内存、省CPU占用率。 在当前类内部尽量不要通过自己的getXXX、setXXX对自己内部成员进行操作,而是直接使用,这样可以提高代码执行效率。 不要一味的为了设计模式而过分的抽象代码,因为代码抽象系数与代码加载执行时间成正比。 尽量减少锁个数、减小锁范围,避免造成性能问题。 合理的选择使用for循环与增强型for循环,譬如不要在ArrayList上使用增强型for循环等。 哎呀,类似的小优化技巧有很多,这里不一一列举了,自行发挥留意即可。 【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】 5 Android应用移动设备电池耗电性能分析 有了UI性能优化、内存性能优化、代码编写优化之后我们在来说说应用开发中很重要的一个优化模块—–电量优化。 5-1 Android应用耗电量概念 在盒子等开发时可能电量优化不是特别重视(视盒子待机真假待机模式而定),但是在移动设备开发中耗电量是一个非常重要的指标,如果用户一旦发现我们的应用非常耗电,不好意思,他们大多会选择卸载来解决此类问题,所以耗电量是一个十分重要的问题。 关于我们应用的耗电量情况我们可以进行定长时间测试,至于具体的耗电量统计等请参考此文,同时我们还可以直接通过Battery Historian Tool来查看详细的应用电量消耗情况。最简单常用办法是通过命令直接查看,如下: <code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">adb <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">shell</span> dumpsys batterystats</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul> 其实我们一款应用耗电量最大的部分不是UI绘制显示等,常见耗电量最大原因基本都是因为网络数据交互、GPS定位、大量内存性能问题、冗余的后台线程和Service等造成。 5-2 Android应用耗电量优化建议 优化电量使用情况我们不仅可以使用系统提供的一些API去处理,还可以在平时编写代码时就养成好的习惯。具体的一些建议如下: 在需要网络的应用中,执行某些操作前尽量先进行网络状态判断。 在网络应用传输中使用高效率的数据格式和解析方法,譬如JSON等。 在传输用户反馈或者下载OTA升级包等不是十分紧急的操作时尽量采用压缩数据进行传输且延迟到设备充电和WIFI状态时进行。 在有必要的情况下尽量通过PowerManager.WakeLock和JobScheduler来控制一些逻辑操作达到省电优化。 对定位要求不太高的场景尽量使用网络定位,而不是GPS定位。 对于定时任务尽量使用AlarmManager,而不是sleep或者Timer进行管理。 尽可能的减少网络请求次数和减小网络请求时间间隔。 后台任务要尽可能少的唤醒CPU,譬如IM通信的长连接心跳时间间隔、一些应用的后台定时唤醒时间间隔等要设计合理。 特殊耗电业务情况可以进行弹窗等友好的交互设计提醒用户该操作会耗用过多电量。 可以看见,上面只是一些常见的电量消耗优化建议。总之,作为应用开发者的我们要意识到电量损耗对于用户来说是非常敏感的,只有我们做到合理的电量优化才能赢得用户的芳心。 【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】 6 Android应用开发性能优化总结 性能优化是一个很大的话题,上面我们谈到的只是应用开发中常见的性能问题,也是应用开发中性能问题的冰山一角,更多的性能优化技巧和能力不是靠看出来,而是靠经验和实战结果总结出来的,所以说性能优化是一个涉及面非常广的话题,如果你想对你的应用进行性能你必须对你应用的整个框架有一个非常清晰的认识。 当然了,如果在我们开发中只是一味的追求各种极致的优化也是不对的。因为优化本来就是存在风险的,甚至有些过度的优化会直接导致项目的臃肿,所以不要因为极致的性能优化而破坏掉了你项目的合理架构。 总之一句话,性能优化适可而止,请酌情优化。
文章
Java  ·  Linux  ·  Android开发  ·  开发者  ·  异构计算
2016-06-27
基于uFUN开发板的RGB调色器
前言使用uFUN开发板配合Qt的上位机,实现任意颜色的混合,Qt的上位机下发RGB数值,范围0-255,uFUN开发板进行解析,然后输出不同占空比的PWM,从而实现通过RGB三原色调制出任意颜色。Qt的上位机界面:演示视频优酷视频链接:http://player.youku.com/embed/XNDEyNzEwNjY2NA==RGB简介GB模型是目前常用的一种彩色信息表达方式,它使用红,绿,蓝三原色的亮度来定量表示颜色。该模型也称为加色混色模型,是以RGB三色光互相叠加来实现混色的方法,因而适合于显示器等发光体的显示。可以通过调整RGB三种原色的比例,来混合出任何你想要的颜色。uFUN开发板的硬件电路uFUN开发板上的RGB灯硬件电路也很简单,可以通过TIM5 / TIM2的通道1,通道2,通道3来控制,通过实际验证,发现PWM B和PWM G两个引脚的网络标号反了,如下图:定时器输出PWM配置使用TIM5或者TIM2都可以,但是在使用TIM5软件仿真的时候,发现没有PWM波输出,而实际有输出,不知道这是不是的Keil的一个BUG,我的是5.16a版本的。void RGB_LED_Init(void) { GPIO_InitTypeDef IO_Init; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef OC_Init; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); IO_Init.GPIO_Mode = GPIO_Mode_AF_PP; IO_Init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; IO_Init.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &IO_Init); TIM_DeInit(TIM5); TIM_TimeBaseStructure.TIM_Period = 256-1; TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); OC_Init.TIM_OCMode = TIM_OCMode_PWM2;//输出模式 OC_Init.TIM_OutputState = ENABLE; //输出使能 OC_Init.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性 // OC_Init.TIM_Pulse = 50; TIM_OC1Init(TIM5, &OC_Init); TIM_OC2Init(TIM5, &OC_Init); TIM_OC3Init(TIM5, &OC_Init); TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM5, TIM_OCPreload_Enable); TIM_Cmd(TIM5, ENABLE); }这里的计数周期,设置成了255,即0-255对应占空比0-100,可以通过下面这个函数来设置对应通道的占空比://设置LED占空比 void SetDutyCycle(LEDtype LEDn, int dty) { switch(LEDn) { case R_LED: TIM_SetCompare2(TIM5, dty); break; case G_LED: TIM_SetCompare1(TIM5, dty); break; case B_LED: TIM_SetCompare3(TIM5, dty); break; default: TIM_SetCompare1(TIM5, 0); TIM_SetCompare2(TIM5, 0); TIM_SetCompare2(TIM5, 0); break; } }串口命令的解析Qt的上位机下发的数据格式如下:R+数值+G+数值+B+数值+*如:R12G123B45* R155G9B24*数值有1-3位,STM32接收到数据之后,可以解析出对应的数值,12 123 45 155 9 24然后控制对应的PWM输出。串口中断函数:uint8_t rx_buf[100]; uint8_t rx_len; void USART1_IRQHandler(void) //串口1中断服务程序 { uint8_t dat; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { dat = USART_ReceiveData(USART1); //读取接收到的数据 // USART_SendData(USART1, dat); if(dat == '*') { //R123G123B213* // printf("%s %d", rx_buf, rx_len); ParseCmd(rx_buf, rx_len); memset(rx_buf, 0, rx_len); rx_len = 0; } else { rx_buf[rx_len++] = dat; } } }串口数据解析,获取到RGB对应的数值:void ParseCmd(char *rx_buf, size_t len) { uint8_t R_Num, G_Num, B_Num; char R_Str[20], G_Str[20], B_Str[20]; char *R, *G, *B; len = strlen(rx_buf); R = strstr(rx_buf, "R"); G = strstr(rx_buf, "G"); B = strstr(rx_buf, "B"); strncpy(R_Str, R+1, G-R-1); R_Str[G-R-1] = '\0'; strncpy(G_Str, G+1, B-G-1); G_Str[B-G-1] = '\0'; strncpy(B_Str, B+1, len - (B - rx_buf)-1); B_Str[len - (B - rx_buf)-1] = '\0'; // printf("R:-%s-,\r\nG:-%s-,\r\nB:-%s-,\r\n", R_Str, G_Str, B_Str); R_Num = atoi(R_Str); G_Num = atoi(G_Str); B_Num = atoi(B_Str); // printf("%d %d %d", R_Num, G_Num, B_Num); SetDutyCycle(R_LED, R_Num); SetDutyCycle(G_LED, G_Num); SetDutyCycle(B_LED, B_Num); }
文章
数据格式
2022-11-13
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
一、环境介绍MCU: STM32F103C8T6程序开发IDE: keil5STM32程序风格:  采用寄存器方式开发,注释齐全,执行效率高,方便移植手机APP:  采用QT设计,程序支持跨平台编译运行(Android、IOS、Windows、Linux都可以编译运行,对应平台上QT的环境搭建,之前博客已经发了文章讲解)硬件包含:  SRM32F103C8T6最小系统板、红外热释电人体感应模块、DHT11温湿度传感器、0.96寸单色OLED显示屏、ESP8266、继电器、RGB大功率白灯.完整工程源码下载地址(包含手机APP源码、Windows系统上位机源码、STM32工程、下载工具、原理图):  https://download.csdn.net/download/xiaolong1126626497/19702853二、功能介绍这是基于STM32设计的智能插座+人体感应灯。硬件包含:  1. SRM32F103C8T6最小系统板:  基础的系统板,引出了所有IO口2. 红外热释电人体感应模块: 用来检测人体3. DHT11温湿度传感器: 检测环境的温度、湿度4. 0.96寸单色OLED显示屏 : 显示状态信息。比如: WIFI状态、RTC时钟、插座状态、温湿度值5. ESP8266: 用来与手机APP之间通信6. 继电器:  模拟插座开关 7. RGB大功率白灯: 模拟正常的灯泡 支持的功能如下:1. 使用热释电人体感应模块检测人体,检测到人体自动开灯,30秒(时间可以根据要求调整)没有检测到人体就自动关灯。2. 检测环境温湿度,使用OLED显示屏在界面上实时显示出来。 如果环境温度高于阀值,强制关闭插座、如果湿度高于阀值,也会强制关闭插座;防止火灾隐患。  温度最高阀值设置为: 30°,湿度阀值为80%, 这些都可以根据设计要求调整。   并且RGB灯也会根据不同的温度阀值亮不同颜色的灯。 比如: 温度高于30°亮红色、温度20°黄色 、温度10°青色3. 设置ESP8266WIFI模块为AP模式(路由器模式),手机或者电脑可以连接到ESP8266.搭建局域网。4. 设计手机APP和电脑客户端软件,可以实时显示收到的温湿度数据(3秒上传一次).可以显示历史. 点击手机APP上的按钮,可以用来控制插座开关。5. OLED一共有4个页面。 RTC实时时钟显示页面、温湿度显示页面、智能插座开关状态页面、WIFI热点信息页面6. OLED显示屏的第一页是实时时钟页面,时间可以通过手机APP来校准。 在手机APP上有一个RTC校准按钮,点击一下就可以校准设备上的时间。三、使用的相关硬件介绍3.1 DTH11 温湿度传感器 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。3.2  热释电传感器热释电红外传感器在结构上引入场效应管,其目的在于完成阻抗变换。由于热释电元输出的是电荷信号,并不能直接使用,因而需要用电阻将其转换为电压形式。故引入的N沟道结型场效应管应接成共漏形式来完成阻抗变换。热释电红外传感器由传感探测元、干涉滤光片和场效应管匹配器三部分组成。设计时应将高热电材料制成一定厚度的薄片,并在它的两面镀上金属电极,然后加电对其进行极化,这样便制成了热释电探测元。热释电红外传感器的外形如上图所示。其可以检测人体发出的红外线信号,并将其转换成电信号输出。传感器顶部的长方形窗口加有滤光片,可以使人体发出的9~10μm波长的红外线通过,而其它波长的红外线被滤除,这样便提高了抗干扰能。热释电红外传感器由滤光片、热释电探测元和前置放大器组成,补偿型热释电传感器还带有温度补偿元件,图所示为热释电传感器的内部结构。为防止外部环境对传感器输出信号的干扰,上述元件被真空封装在一个金属营内。热释电传感器的滤光片为带通滤光片,它封装在传感器壳体的顶端,使特定波长的红外辐射选择性地通过,到达热释电探测元+在其截止范围外的红外辐射则不能通过。    热释电探测元是热释电传感器的核心元件,它是在热释电晶体的两面镀上金属电极后,加电极化制成,相当于一个以热释电晶体为电介质的平板电容器。当它受到非恒定强度的红外光照射时,产生的温度变化导致其表面电极的电荷密度发生改变,从而产生热释电电流。前置放大器由一个高内阻的场效应管源极跟随器构成,通过阻抗变换,将热释电探测元微弱的电流信号转换为有用的电压信号输出。3.3 ESP8266串口WIFI模块ESP8266系列无线模块是高性价比WIFI SOC模组,该系列模块支持标准的IEEE802.11b/g/n协议,内置完整的TCP/IP协议栈。用户可以使用该系列模块为现有的设备添加联网功能,也可以构建独立的网络控制器。能卓越ESP8266EX 芯片内置超低功耗 Tensilica L106 32 位 RISC 处理器,CPU 时钟速度最⾼可达 160 MHz,⽀持实时操作系统 (RTOS) 和 Wi-Fi 协议栈,可将⾼达 80% 的处理能⼒应用于编程和开发。高度集成ESP8266 芯片高度集成天线开关、射频巴伦、功率放大器、低噪声接收放大器、滤波器等射频模块。模组尺寸小巧,尤其适用于空间受限的产品设计。认证齐全RF 认证:SRRC、FCC、CE-RED、KCC、TELEC/MIC、IC 和 NCC 认证;环保认证:RoHS、REACH;可靠性认证:HTOL、HTSL、μHAST、TCT、ESD。丰富的产品应用ESP8266 模组既可以通过 ESP-AT 指令固件,为外部主机 MCU 提供 Wi-Fi 连接功能;也可以作为独立 Wi-Fi MCU 运行,用户通过基于 RTOS 的 SDK 开发带 Wi-Fi 连接功能的产品。用户可以轻松实现开箱即用的云连接、低功耗运行模式,以及包括 WPA3 在内的 Wi-Fi 安全支持等功能。3.4 OLED显示屏OLED显示屏是利用有机电自发光二极管制成的显示屏。由于同时具备自发光有机电激发光二极管,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。有机发光二极管 (OLED)显示器越来越普遍,在手机、媒体播放器及小型入门级电视等产品中最为显著。不同于标准的液晶显示器,OLED 像素是由电流源所驱动。若要了解 OLED 电源供应如何及为何会影响显示器画质,必须先了解 OLED 显示器技术及电源供应需求。本文将说明最新的 OLED 显示器技术,并探讨主要的电源供应需求及解决方案,另外也介绍专为 OLED 电源供应需求而提出的创新性电源供应架构。背板技术造就软性显示器  高分辨率彩色主动式矩阵有机发光二极管 (AMOLED) 显示器需要采用主动式矩阵背板,此背板使用主动式开关进行各像素的开关。液晶 (LC) 显示器非晶硅制程已臻成熟,可供应低成本的主动式矩阵背板,并且可用于 OLED。许多公司正针对软性显示器开发有机薄膜晶体管 (OTFT) 背板制程,此一制程也可用于 OLED 显示器,以实现全彩软性显示器的推出。不论是标准或软性 OLED,都需要运用相同的电源供应及驱动技术。若要了解 OLED 技术、功能及其与电源供应之间的互动,必须深入剖析这项技术本身。OLED 显示器是一种自体发光显示器技术,完全不需要任何背光。OLED 采用的材质属于化学结构适用的有机材质。  OLED 技术需要电流控制驱动方法  OLED 具有与标准发光二极管 (LED) 相当类似的电气特性,亮度均取决于 LED 电流。若要开启和关闭 OLED 并控制 OLED 电流,需要使用薄膜晶体管 (TFT)的控制电路。OLED为自发光材料,不需用到背光板,同时视角广、画质均匀、反应速度快、较易彩色化、用简单驱动电路即可达到发光、制程简单、可制作成挠曲式面板,符合轻薄短小的原则,应用范围属于中小尺寸面板。显示方面:主动发光、视角范围大;响应速度快,图像稳定;亮度高、色彩丰富、分辨率高。工作条件:驱动电压低、能耗低,可与太阳能电池、集成电路等相匹配。适应性广:采用玻璃衬底可实现大面积平板显示;如用柔性材料做衬底,能制成可折叠的显示器。由于OLED是全固态、非真空器件,具有抗震荡、耐低温(-40℃)等特性,在军事方面也有十分重要的应用,如用作坦克、飞机等现代化武器的显示终端。3.5 LED大功率灯模块LED灯是一块电致发光的半导体材料芯片,用银胶或白胶固化到支架上,然后用银线或金线连接芯片和电路板,四周用环氧树脂密封,起到保护内部芯线的作用,最后安装外壳,所以 LED 灯的抗震性能好。LED(Light Emitting Diode),发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,使整个晶片被环氧树脂封装起来。半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。但这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。而光的波长也就是光的颜色,是由形成P-N结的材料决定的。LED可以直接发出红、黄、蓝、绿、青、橙、紫、白色的光。3.6 STM32F103C8T6最小系统板STM32F系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3。该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。芯片集成定时器Timer,CAN,ADC,SPI,I2C,USB,UART等多种外设功能。内核--ARM 32位的Cortex-M3--最高72MHz工作频率,在存储器的0等待周期访问时可达1.25DMips/MHZ(DhrystONe2.1)--单周期乘法和硬件除法存储器--从16K到512K字节的闪存程序存储器(STM32F103XXXX中的第二个X表示FLASH容量,其中:“4”=16K,“6”=32K,“8”=64K,B=128K,C=256K,D=384K,E=512K)--最大64K字节的SRAM电源管理--2.0-3.6V供电和I/O引脚--上电/断电复位(POR/PDR)、可编程电压监测器(PVD)--4-16MHZ晶振--内嵌经出厂调校的8MHz的RC振荡器--内嵌带校准的40KHz的RC振荡器--产生CPU时钟的PLL--带校准的32KHz的RC振荡器低功耗--睡眠、停机和待机模式--Vbat为RTC和后备寄存器供电模数转换器--2个12位模数转换器,1us转换时间(多达16个输入通道)--转换范围:0至3.6V--双采样和保持功能--温度传感器DMA--2个DMA控制器,共12个DMA通道:DMA1有7个通道,DMA2有5个通道--支持的外设:定时器、ADC、SPI、USB、IIC和UART--多达112个快速I/O端口(仅Z系列有超过100个引脚)--26/37/51/80/112个I/O口,所有I/O口一块映像到16个外部中断;几乎所有的端口均可容忍5V信号调试模式--串行单线调试(SWD)和JTAG接口--多达8个定时器--3个16位定时器,每个定时器有多达4个用于输入捕获/输出比较/PWM或脉冲计数的通道和增量编码器输入--1个16位带死区控制和紧急刹车,用于电机控制的PWM高级控制定时器--2个看门狗定时器(独立的和窗口型的)--系统时间定时器:24位自减型计数器--多达9个通信接口:2个I2C接口(支持SMBus/PMBus)3个USART接口(支持ISO7816接口,LIN,IrDA接口和调制解调控制)2个SPI接口(18M位/秒)CAN接口(2.0B主动)USB 2.0全速接口计算单元CRC计算单元,96位的新批唯一代码封装ECOPACK封装3.7 杜邦线3.8 继电器四、STM32核心代码4.1  STM32:  main.c #include "stm32f10x.h" #include "beep.h" #include "delay.h" #include "led.h" #include "key.h" #include "sys.h" #include "usart.h" #include <string.h> #include <stdlib.h> #include "exti.h" #include "timer.h" #include "rtc.h" #include "wdg.h" #include "oled.h" #include "fontdata.h" #include "adc.h" #include "FunctionConfig.h" #include "dht11.h" #include "HumanDetection.h" #include "esp8266.h" /* 函数功能: 绘制时钟表盘框架 */ void DrawTimeFrame(void) { u8 i; OLED_Circle(32,32,31);//画外圆 OLED_Circle(32,32,1); //画中心圆 //画刻度 for(i=0;i<60;i++) { if(i%5==0)OLED_DrawAngleLine(32,32,6*i,31,3,1); } OLED_RefreshGRAM(); //刷新数据到OLED屏幕 } /* 函数功能: 更新时间框架显示,在RTC中断里调用 */ char TimeBuff[20]; void Update_FrameShow(void) { /*1. 绘制秒针、分针、时针*/ OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-6-90,27,0);//清除之前的秒针 OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-90,27,1); //画秒针 OLED_DrawAngleLine2(32,32,rtc_clock.min*6-6-90,24,0); OLED_DrawAngleLine2(32,32,rtc_clock.min*6-90,24,1); OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-6-90,21,0); OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-90,21,1); //绘制电子钟时间 sprintf(TimeBuff,"%d",rtc_clock.year); OLED_ShowString(65,16*0,16,TimeBuff); //年份字符串 OLED_ShowChineseFont(66+32,16*0,16,4); //显示年 sprintf(TimeBuff,"%d/%d",rtc_clock.mon,rtc_clock.day); OLED_ShowString(75,16*1,16,TimeBuff); //月 if(rtc_clock.sec==0)OLED_ShowString(65,16*2,16," "); //清除多余的数据 sprintf(TimeBuff,"%d:%d:%d",rtc_clock.hour,rtc_clock.min,rtc_clock.sec); OLED_ShowString(65,16*2,16,TimeBuff); //秒 //显示星期 OLED_ShowChineseFont(70,16*3,16,5); //星 OLED_ShowChineseFont(70+16,16*3,16,6); //期 OLED_ShowChineseFont(70+32,16*3,16,rtc_clock.week+7); //具体的值 } /* 函数功能: 温湿度显示 */ void ShowTemperatureAndHumidity(u8 temp,u8 humi) { sprintf(TimeBuff,"T: %d",temp); OLED_ShowString(40,16*1,16,TimeBuff); sprintf(TimeBuff,"H: %d%%",humi); OLED_ShowString(40,16*2,16,TimeBuff); } /* 函数功能: OLED所有显示页面信息初始化 */ u8 ESP8266_Stat=0; //存放ESP8266状态 1 OK ,0 error u8 ESP8266_WIFI_AP_SSID[10]; //存放WIFI的名称 #define STM32_96BIT_UID (0x1FFFF7E8) //STM32内部96位唯一芯片标识符寄存器地址 /* 函数功能: ESP8266 WIFI 显示页面 */ char ESP8266_PwdShow[20]; char ESP8266_IP_PortAddr[30]; //存放ESP8266 IP地址与端口号地址 u8 Save_ESP8266_SendCmd[30]; //保存WIFI发送的命令 u8 Save_ESP8266_SendData[50]; //保存WIFI发送的数据 void ESP8266_ShowPageTable(void) { if(ESP8266_Stat)OLED_ShowString(0,16*0,16,"WIFI STAT:ERROR"); else OLED_ShowString(0,16*0,16,"WIFI STAT:OK"); //显示字符串 //OLED_ShowString(0,2,(u8*)" "); //清除一行的显示 memset((u8*)ESP8266_PwdShow,0,20); //清空内存 sprintf((char*)ESP8266_PwdShow,"WIFI:%s",ESP8266_WIFI_AP_SSID); ESP8266_PwdShow[15]='\0'; OLED_ShowString(0,16*1,16,ESP8266_PwdShow); memset((u8*)ESP8266_PwdShow,0,20); //清空内存 sprintf((char*)ESP8266_PwdShow,"PWD:%s",wifiap_password); OLED_ShowString(0,16*2,16,ESP8266_PwdShow); OLED_ShowString(0,16*3,16,"192.168.4.1:8089"); } void OledShowPageTableInit(void) { u8 data[100],i; u32 stm32_uid=0; u8 *uid; /*1. ESP8266 WIFI相关信息初始化*/ OLED_ShowString(0,2,16," "); //清空一行的显示 OLED_ShowString(0,2,16,"WifiInit..."); //显示页面提示信息 /*1.1 设置WIFI AP模式 */ if(ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50)) { ESP8266_Stat=1; //OK } else { ESP8266_Stat=0; //ERROR } /*1.2 重启模块 */ ESP8266_SendCmd("AT+RST\r\n","OK",20); /*1.3 延时3S等待重启成功*/ DelayMs(1000); DelayMs(1000); DelayMs(1000); //得到WIFI的名称 uid=(u8*)STM32_96BIT_UID; //转为指针为1个字节 for(i=0;i<96;i++) { stm32_uid+=*uid++; //计算校验和,得到WIFI数字编号 } printf("stm32_uid=%d\r\n",stm32_uid); sprintf((char*)data,"%d",stm32_uid); strcpy((char*)ESP8266_WIFI_AP_SSID,"wbyq_"); strcat((char*)ESP8266_WIFI_AP_SSID,(char*)data); printf("请用设备连接WIFI热点:%s,%s,%s\r\n",(u8*)ESP8266_WIFI_AP_SSID,(u8*)wifiap_encryption,(u8*)wifiap_password); /*1.4 配置模块AP模式无线参数*/ memset(data,0,100); //清空数组 sprintf((char*)data,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ESP8266_WIFI_AP_SSID,wifiap_password); ESP8266_SendCmd(data,"OK",1000); /*1.5 设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/ ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20); /*1.6 开启Server模式(0,关闭;1,打开),端口号为portnum */ memset(data,0,100); //清空数组 sprintf((char*)data,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum); ESP8266_SendCmd(data,"OK",50); /*1.7 获取当前模块的IP*/ ESP8266_GetWanip((u8*)ESP8266_IP_PortAddr); strcat(ESP8266_IP_PortAddr,":"); strcat(ESP8266_IP_PortAddr,portnum); printf("IP地址:%s\r\n",ESP8266_IP_PortAddr); OLED_ShowString(0,2,16," "); //清空一行的显示 OLED_ShowString(0,2,16,"WifiInitOk"); //显示页面提示信息 } /* 函数功能: 显示开关状态 */ void Show_Switch(int state) { //清屏 OLED_Clear(0); if(state) { sprintf(TimeBuff,"Socket:ON"); OLED_ShowString(20,16*2,16,TimeBuff); } else { sprintf(TimeBuff,"Socket:OFF"); OLED_ShowString(20,16*2,16,TimeBuff); } } //int main1(void) //{ // HumanDetection_Init(); //热释电模块初始化 // UsartInit(USART1,72,115200);//串口1的初始化 // LED_Init(); //初始化LED // while(1) // { // //热释电状态 为真表示有人 // if(HumanState) // { // LED1=0; //开灯 // } // else //为假 // { // LED1=1; //关灯 // } // printf("%d\n",HumanState); // } //} int main(void) { u8 rlen; char *p; u8 temp; //温度 u8 humi; //湿度 u8 stat; u8 key_val; u32 TimeCnt=0; u32 wifi_TimeCnt=0; u16 temp_data; //温度数据 u8 page_cnt=0; //显示的页面 char *time; u8 socket_state=0; UsartInit(USART1,72,115200);//串口1的初始化 BEEP_Init(); //初始化蜂鸣器 LED_Init(); //初始化LED KEY_Init(); //按键初始化 printf("正在初始化OLED...\r\n"); OLED_Init(0xc8,0xa1); //OLED显示屏初始化--正常显示 //OLED_Init(0xc0,0xa0); //OLED显示屏初始化--翻转显示 OLED_Clear(0x00); //清屏 UsartInit(USART3,36,115200); //WIFI的波特率为115200 Timer2Init(72,10000); //10ms中断一次,辅助串口3接收数据--WIFI数据 printf("正在初始化ESP8266..\r\n"); //ESP8266初始化 OledShowPageTableInit(); //清屏 OLED_Clear(0); printf("正在初始化RTC...\r\n"); RTC_Init(); //RTC初始化 DrawTimeFrame(); //画时钟框架 USART3_RX_STA=0; //清空串口的接收标志位 USART3_RX_CNT=0; //清空计数器 printf("初始化DHT11...\r\n"); DHT11_Init(); //初始化DHT11 printf("热释电模块初始化...\r\n"); HumanDetection_Init(); //热释电模块初始化 //OLED_Clear(0); printf("开始进入while(1)\r\n"); while(1) { key_val=KEY_GetValue(); if(key_val) { page_cnt++; printf("page_cnt:%d\r\n",page_cnt); //清屏 OLED_Clear(0); //时钟页面 if(page_cnt==0) { DrawTimeFrame(); //画时钟框架 RTC->CRH|=1<<0; //开启秒中断 } //温湿度页面 else if(page_cnt==1) { RTC->CRH&=~(1<<0); //关闭秒中断 //温湿度 ShowTemperatureAndHumidity(temp,humi); } //ESP8266显示 else if(page_cnt==2) { ESP8266_ShowPageTable(); } else if(page_cnt==3) { Show_Switch(socket_state); } else { DrawTimeFrame(); //画时钟框架 RTC->CRH|=1<<0; //开启秒中断 page_cnt=0; } } //时间记录 DelayMs(10); TimeCnt++; wifi_TimeCnt++; if(TimeCnt>=100) //1000毫秒一次 { TimeCnt=0; //读取温湿度数据 DHT11_Read_Data(&temp,&humi); //湿度大于90就关闭开关 if(humi>90) { socket_state=0; if(page_cnt==3) { Show_Switch(socket_state); } } //温湿度页面 if(page_cnt==1) { //温湿度 ShowTemperatureAndHumidity(temp,humi); } } if(wifi_TimeCnt>=300) //3000毫秒一次 { wifi_TimeCnt=0; //温湿度1秒上传一次 sprintf((char*)Save_ESP8266_SendData,"#%d,%d",temp,humi); //拼接数据 sprintf((char*)Save_ESP8266_SendCmd,"AT+CIPSEND=0,%d\r\n",strlen((char*)Save_ESP8266_SendData)); ESP8266_SendCmd(Save_ESP8266_SendCmd,(u8*)"OK",200); //WIFI设置发送数据长度 ESP8266_SendData(Save_ESP8266_SendData,(u8*)"OK",100); //WIFI发送数据 } //热释电状态 为真表示有人 if(HumanState) { LED1=0; //开灯 } else //为假 { LED1=1; //关灯 } /*轮询扫描数据*/ if(USART3_RX_STA) //WIFI 接收到一次数据了 { rlen=USART3_RX_CNT; //得到本次接收到的数据长度 USART3_RX_BUF[rlen]='\0'; //添加结束符 // printf("接收的数据: %s\r\n",USART3_RX_BUF); //发送到串口 { /*判断是否收到客户端发来的数据 */ p=strstr((char*)USART3_RX_BUF,"+IPD"); if(p!=NULL) //正常数据格式: +IPD,0,7:LED1_ON +IPD,0表示第0个客户端 7:LED1_ON表示数据长度与数据 { /*解析上位机发来的数据*/ p=strstr((char*)USART3_RX_BUF,":"); if(p!=NULL) { p+=1; //向后偏移1个字节 if(*p=='*') //设置RTC时间 { p+=1; //向后偏移,指向正确的时间 time=p; rtc_clock.year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1; rtc_clock.mon=(time[4]-48)*10+(time[5]-48)*1; rtc_clock.day=(time[6]-48)*10+(time[7]-48)*1; rtc_clock.hour=(time[8]-48)*10+(time[9]-48)*1; rtc_clock.min=(time[10]-48)*10+(time[11]-48)*1; rtc_clock.sec=(time[12]-48)*10+(time[13]-48)*1; RTC_SetTime(rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec); if(page_cnt==0) { OLED_Clear(0); //OLED清屏 DrawTimeFrame();//画时钟框架 } } else if(strcmp(p,"LED1_ON")==0) { socket_state=1; } else if(strcmp(p,"LED1_OFF")==0) { socket_state=0; } if(page_cnt==3) { Show_Switch(socket_state); } } } } USART3_RX_STA=0; USART3_RX_CNT=0; } } } 4.2  STM32:  rtc.c#include "rtc.h" //定义RTC标准结构体 struct RTC_CLOCK rtc_clock; /* 函数功能: RTC初始化函数 */ void RTC_Init(void) { //检查是不是第一次配置时钟 u8 temp=0; if(BKP->DR1!=0X5051)//之前使用的不是LSE { RCC->APB1ENR|=1<<28; //使能电源时钟 RCC->APB1ENR|=1<<27; //使能备份时钟 PWR->CR|=1<<8; //取消备份区写保护 RCC->BDCR|=1<<16; //备份区域软复位 RCC->BDCR&=~(1<<16); //备份区域软复位结束 RCC->BDCR|=1<<0; //开启外部低速振荡器 while((!(RCC->BDCR&0X02))&&temp<250)//等待外部时钟就绪 { temp++; DelayMs(10); }; if(temp>=250) //return 1;//初始化时钟失败,晶振有问题 { RCC->CSR|=1<<0; //开启外部低速振荡器 while(!(RCC->CSR&(1<<1)));//外部低速振荡器 RCC->BDCR|=2<<8; //LSI作为RTC时钟 BKP->DR1=0X5050; //标记使用LSI作为RTC时钟 } else { RCC->BDCR|=1<<8; //LSE作为RTC时钟 BKP->DR1=0X5051; //标记使用LSE作为RTC时钟 } RCC->BDCR|=1<<15;//RTC时钟使能 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步 RTC->CRH|=0X01; //允许秒中断 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 RTC->CRL|=1<<4; //允许配置 RTC->PRLH=0X0000; RTC->PRLL=32767; //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767 RTC_SetTime(2021,4,25,20,36,20); //设置时间 RTC->CRL&=~(1<<4); //配置更新 while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成 printf("FIRST TIME\n"); }else//系统继续计时 { while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步 RTC->CRH|=0X01; //允许秒中断 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 printf("OK\n"); } STM32_NVIC_SetPriority(RTC_IRQn,2,2); //优先级 } extern void Update_FrameShow(void); /* 函数功能: RTC闹钟中断服务函数 */ void RTC_IRQHandler(void) { u32 SecCnt; if(RTC->CRL&1<<0) { SecCnt=RTC->CNTH<<16;//获取高位 SecCnt|=RTC->CNTL; //获取低位 RTC_GetTime(SecCnt); //转换标准时间 RTC_GetWeek(SecCnt); // printf("%d-%d-%d %d:%d:%d week:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec,rtc_clock.week); Update_FrameShow(); //更新显示 RTC->CRL&=~(1<<0); //清除秒中断标志位 } if(RTC->CRL&1<<1) { // printf("闹钟时间到达!....\n"); // BEEP=1; // DelayMs(500); // BEEP=0; RTC->CRL&=~(1<<1); //清除闹钟中断标志位 } } //闰年的月份 static int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31}; //平年的月份 static int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31}; /* 函数功能: 设置RTC时间 函数形参: u32 year; 2018 u32 mon; 8 u32 day; u32 hour; u32 min; u32 sec; */ void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec) { u32 i; u32 SecCnt=0; //总秒数 /*1. 累加已经过去的年份*/ for(i=2017;i<year;i++) //基准年份:20170101000000 { if(RTC_GetYearState(i)) { SecCnt+=366*24*60*60; //闰年一年的秒数 } else { SecCnt+=365*24*60*60; //平年一年的秒数 } } /*2. 累加过去的月份*/ for(i=0;i<mon-1;i++) { if(RTC_GetYearState(year)) { SecCnt+=mon_r[i]*24*60*60; //闰年一月的秒数 } else { SecCnt+=mon_p[i]*24*60*60; //平年一月的秒数 } } /*3. 累加过去的天数*/ SecCnt+=(day-1)*24*60*60; /*4. 累加过去小时*/ SecCnt+=hour*60*60; /*5. 累加过去的分钟*/ SecCnt+=min*60; /*6. 累加过去的秒*/ SecCnt+=sec; /*7. 设置RTC时间*/ //设置时钟 RCC->APB1ENR|=1<<28;//使能电源时钟 RCC->APB1ENR|=1<<27;//使能备份时钟 PWR->CR|=1<<8; //取消备份区写保护 //上面三步是必须的! RTC->CRL|=1<<4; //允许配置 RTC->CNTL=SecCnt&0xffff; RTC->CNTH=SecCnt>>16; RTC->CRL&=~(1<<4);//配置更新 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 } /* 函数功能: 获取RTC时间 函数参数: u32 sec 秒单位时间 */ void RTC_GetTime(u32 sec) { u32 i; rtc_clock.year=2017; //基准年份 /*1. 计算当前的年份*/ while(1) { if(RTC_GetYearState(rtc_clock.year)) { if(sec>=366*24*60*60) //够一年 { sec-=366*24*60*60; rtc_clock.year++; } else break; } else { if(sec>=365*24*60*60) //够一年 { sec-=365*24*60*60; rtc_clock.year++; } else break; } } /*2. 计算当前的月份*/ rtc_clock.mon=1; for(i=0;i<12;i++) { if(RTC_GetYearState(rtc_clock.year)) { if(sec>=mon_r[i]*24*60*60) { sec-=mon_r[i]*24*60*60; rtc_clock.mon++; } else break; } else { if(sec>=mon_p[i]*24*60*60) { sec-=mon_p[i]*24*60*60; rtc_clock.mon++; } else break; } } /*3. 计算当前的天数*/ rtc_clock.day=1; while(1) { if(sec>=24*60*60) { sec-=24*60*60; rtc_clock.day++; } else break; } /*4. 计算当前的小时*/ rtc_clock.hour=0; while(1) { if(sec>=60*60) { sec-=60*60; rtc_clock.hour++; } else break; } /*5. 计算当前的分钟*/ rtc_clock.min=0; while(1) { if(sec>=60) { sec-=60; rtc_clock.min++; } else break; } /*6. 计算当前的秒*/ rtc_clock.sec=sec; } /* 函数功能: 判断年份是否是平年、闰年 返回值 : 0表示平年 1表示闰年 */ u8 RTC_GetYearState(u32 year) { if((year%4==0&&year%100!=0)||year%400==0) { return 1; } return 0; } /* 函数功能: 获取星期 */ void RTC_GetWeek(u32 sec) { u32 day1=sec/(60*60*24); //将秒单位时间转为天数 switch(day1%7) { case 0: rtc_clock.week=0; break; case 1: rtc_clock.week=1; break; case 2: rtc_clock.week=2; break; case 3: rtc_clock.week=3; break; case 4: rtc_clock.week=4; break; case 5: rtc_clock.week=5; break; case 6: rtc_clock.week=6; break; } } 4.3 ESP8266.c#include "esp8266.h" /* 函数功能:向ESP82668266发送命令 函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败 */ u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; USART3_RX_CNT=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack)) { res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 } USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266发送命令后,检测接收到的应答 函数参数:str:期待的应答结果 返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置) */ u8* ESP8266_CheckCmd(u8 *str) { char *strx=0; if(USART3_RX_STA) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 //printf("RX=%s",USART3_RX_BUF); } return (u8*)strx; } /* 函数功能:向ESP8266发送指定数据 函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值:0,发送成功(得到了期待的应答结果)luojian */ u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266退出透传模式 返 回 值:0,退出成功; 1,退出失败 */ u8 ESP8266_QuitTrans(void) { while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT\r\n","OK",20);//退出透传判断. } /* 函数功能:获取ESP8266模块的连接状态 返 回 值:0,未连接;1,连接成功. */ u8 ESP8266_ConstaCheck(void) { u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS\r\n:"); res=*p; //得到连接状态 return res; } /* 函数功能:获取ip地址 函数参数:ipbuf:ip地址输出缓存区 */ void ESP8266_GetWanip(u8* ipbuf) { u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 { ipbuf[0]=0; return; } p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); } 五、QT设计的上位机代码:  Android手机APP+Windows系统上位机
文章
传感器  ·  编解码  ·  网络协议  ·  开发工具  ·  Android开发  ·  芯片  ·  网络架构  ·  SoC  ·  Windows  ·  内存技术
2021-12-20
基于STM32设计的智能插座+人体感应灯(ESP8266+人体感应+手机APP)
一、环境介绍MCU: STM32F103C8T6程序开发IDE: keil5STM32程序风格:  采用寄存器方式开发,注释齐全,执行效率高,方便移植手机APP:  采用QT设计,程序支持跨平台编译运行(Android、IOS、Windows、Linux都可以编译运行,对应平台上QT的环境搭建,之前博客已经发了文章讲解)硬件包含:  SRM32F103C8T6最小系统板、红外热释电人体感应模块、DHT11温湿度传感器、0.96寸单色OLED显示屏、ESP8266、继电器、RGB大功率白灯.完整工程源码下载地址(包含手机APP源码、Windows系统上位机源码、STM32工程、下载工具、原理图):  https://download.csdn.net/download/xiaolong1126626497/19702853二、功能介绍这是基于STM32设计的智能插座+人体感应灯。硬件包含:  1. SRM32F103C8T6最小系统板:  基础的系统板,引出了所有IO口2. 红外热释电人体感应模块: 用来检测人体3. DHT11温湿度传感器: 检测环境的温度、湿度4. 0.96寸单色OLED显示屏 : 显示状态信息。比如: WIFI状态、RTC时钟、插座状态、温湿度值5. ESP8266: 用来与手机APP之间通信6. 继电器:  模拟插座开关 7. RGB大功率白灯: 模拟正常的灯泡 支持的功能如下:1. 使用热释电人体感应模块检测人体,检测到人体自动开灯,30秒(时间可以根据要求调整)没有检测到人体就自动关灯。2. 检测环境温湿度,使用OLED显示屏在界面上实时显示出来。 如果环境温度高于阀值,强制关闭插座、如果湿度高于阀值,也会强制关闭插座;防止火灾隐患。  温度最高阀值设置为: 30°,湿度阀值为80%, 这些都可以根据设计要求调整。   并且RGB灯也会根据不同的温度阀值亮不同颜色的灯。 比如: 温度高于30°亮红色、温度20°黄色 、温度10°青色3. 设置ESP8266WIFI模块为AP模式(路由器模式),手机或者电脑可以连接到ESP8266.搭建局域网。4. 设计手机APP和电脑客户端软件,可以实时显示收到的温湿度数据(3秒上传一次).可以显示历史. 点击手机APP上的按钮,可以用来控制插座开关。5. OLED一共有4个页面。 RTC实时时钟显示页面、温湿度显示页面、智能插座开关状态页面、WIFI热点信息页面6. OLED显示屏的第一页是实时时钟页面,时间可以通过手机APP来校准。 在手机APP上有一个RTC校准按钮,点击一下就可以校准设备上的时间。三、使用的相关硬件介绍3.1 DTH11 温湿度传感器 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。3.2  热释电传感器热释电红外传感器在结构上引入场效应管,其目的在于完成阻抗变换。由于热释电元输出的是电荷信号,并不能直接使用,因而需要用电阻将其转换为电压形式。故引入的N沟道结型场效应管应接成共漏形式来完成阻抗变换。热释电红外传感器由传感探测元、干涉滤光片和场效应管匹配器三部分组成。设计时应将高热电材料制成一定厚度的薄片,并在它的两面镀上金属电极,然后加电对其进行极化,这样便制成了热释电探测元。热释电红外传感器的外形如上图所示。其可以检测人体发出的红外线信号,并将其转换成电信号输出。传感器顶部的长方形窗口加有滤光片,可以使人体发出的9~10μm波长的红外线通过,而其它波长的红外线被滤除,这样便提高了抗干扰能。热释电红外传感器由滤光片、热释电探测元和前置放大器组成,补偿型热释电传感器还带有温度补偿元件,图所示为热释电传感器的内部结构。为防止外部环境对传感器输出信号的干扰,上述元件被真空封装在一个金属营内。热释电传感器的滤光片为带通滤光片,它封装在传感器壳体的顶端,使特定波长的红外辐射选择性地通过,到达热释电探测元+在其截止范围外的红外辐射则不能通过。    热释电探测元是热释电传感器的核心元件,它是在热释电晶体的两面镀上金属电极后,加电极化制成,相当于一个以热释电晶体为电介质的平板电容器。当它受到非恒定强度的红外光照射时,产生的温度变化导致其表面电极的电荷密度发生改变,从而产生热释电电流。前置放大器由一个高内阻的场效应管源极跟随器构成,通过阻抗变换,将热释电探测元微弱的电流信号转换为有用的电压信号输出。3.3 ESP8266串口WIFI模块ESP8266系列无线模块是高性价比WIFI SOC模组,该系列模块支持标准的IEEE802.11b/g/n协议,内置完整的TCP/IP协议栈。用户可以使用该系列模块为现有的设备添加联网功能,也可以构建独立的网络控制器。能卓越ESP8266EX 芯片内置超低功耗 Tensilica L106 32 位 RISC 处理器,CPU 时钟速度最⾼可达 160 MHz,⽀持实时操作系统 (RTOS) 和 Wi-Fi 协议栈,可将⾼达 80% 的处理能⼒应用于编程和开发。高度集成ESP8266 芯片高度集成天线开关、射频巴伦、功率放大器、低噪声接收放大器、滤波器等射频模块。模组尺寸小巧,尤其适用于空间受限的产品设计。认证齐全RF 认证:SRRC、FCC、CE-RED、KCC、TELEC/MIC、IC 和 NCC 认证;环保认证:RoHS、REACH;可靠性认证:HTOL、HTSL、μHAST、TCT、ESD。丰富的产品应用ESP8266 模组既可以通过 ESP-AT 指令固件,为外部主机 MCU 提供 Wi-Fi 连接功能;也可以作为独立 Wi-Fi MCU 运行,用户通过基于 RTOS 的 SDK 开发带 Wi-Fi 连接功能的产品。用户可以轻松实现开箱即用的云连接、低功耗运行模式,以及包括 WPA3 在内的 Wi-Fi 安全支持等功能。3.4 OLED显示屏OLED显示屏是利用有机电自发光二极管制成的显示屏。由于同时具备自发光有机电激发光二极管,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。有机发光二极管 (OLED)显示器越来越普遍,在手机、媒体播放器及小型入门级电视等产品中最为显著。不同于标准的液晶显示器,OLED 像素是由电流源所驱动。若要了解 OLED 电源供应如何及为何会影响显示器画质,必须先了解 OLED 显示器技术及电源供应需求。本文将说明最新的 OLED 显示器技术,并探讨主要的电源供应需求及解决方案,另外也介绍专为 OLED 电源供应需求而提出的创新性电源供应架构。背板技术造就软性显示器  高分辨率彩色主动式矩阵有机发光二极管 (AMOLED) 显示器需要采用主动式矩阵背板,此背板使用主动式开关进行各像素的开关。液晶 (LC) 显示器非晶硅制程已臻成熟,可供应低成本的主动式矩阵背板,并且可用于 OLED。许多公司正针对软性显示器开发有机薄膜晶体管 (OTFT) 背板制程,此一制程也可用于 OLED 显示器,以实现全彩软性显示器的推出。不论是标准或软性 OLED,都需要运用相同的电源供应及驱动技术。若要了解 OLED 技术、功能及其与电源供应之间的互动,必须深入剖析这项技术本身。OLED 显示器是一种自体发光显示器技术,完全不需要任何背光。OLED 采用的材质属于化学结构适用的有机材质。  OLED 技术需要电流控制驱动方法  OLED 具有与标准发光二极管 (LED) 相当类似的电气特性,亮度均取决于 LED 电流。若要开启和关闭 OLED 并控制 OLED 电流,需要使用薄膜晶体管 (TFT)的控制电路。OLED为自发光材料,不需用到背光板,同时视角广、画质均匀、反应速度快、较易彩色化、用简单驱动电路即可达到发光、制程简单、可制作成挠曲式面板,符合轻薄短小的原则,应用范围属于中小尺寸面板。显示方面:主动发光、视角范围大;响应速度快,图像稳定;亮度高、色彩丰富、分辨率高。工作条件:驱动电压低、能耗低,可与太阳能电池、集成电路等相匹配。适应性广:采用玻璃衬底可实现大面积平板显示;如用柔性材料做衬底,能制成可折叠的显示器。由于OLED是全固态、非真空器件,具有抗震荡、耐低温(-40℃)等特性,在军事方面也有十分重要的应用,如用作坦克、飞机等现代化武器的显示终端。3.5 LED大功率灯模块LED灯是一块电致发光的半导体材料芯片,用银胶或白胶固化到支架上,然后用银线或金线连接芯片和电路板,四周用环氧树脂密封,起到保护内部芯线的作用,最后安装外壳,所以 LED 灯的抗震性能好。LED(Light Emitting Diode),发光二极管,是一种能够将电能转化为可见光的固态的半导体器件,它可以直接把电转化为光。LED的心脏是一个半导体的晶片,晶片的一端附在一个支架上,一端是负极,另一端连接电源的正极,使整个晶片被环氧树脂封装起来。半导体晶片由两部分组成,一部分是P型半导体,在它里面空穴占主导地位,另一端是N型半导体,在这边主要是电子。但这两种半导体连接起来的时候,它们之间就形成一个P-N结。当电流通过导线作用于这个晶片的时候,电子就会被推向P区,在P区里电子跟空穴复合,然后就会以光子的形式发出能量,这就是LED灯发光的原理。而光的波长也就是光的颜色,是由形成P-N结的材料决定的。LED可以直接发出红、黄、蓝、绿、青、橙、紫、白色的光。3.6 STM32F103C8T6最小系统板STM32F系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3。该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。芯片集成定时器Timer,CAN,ADC,SPI,I2C,USB,UART等多种外设功能。内核--ARM 32位的Cortex-M3--最高72MHz工作频率,在存储器的0等待周期访问时可达1.25DMips/MHZ(DhrystONe2.1)--单周期乘法和硬件除法存储器--从16K到512K字节的闪存程序存储器(STM32F103XXXX中的第二个X表示FLASH容量,其中:“4”=16K,“6”=32K,“8”=64K,B=128K,C=256K,D=384K,E=512K)--最大64K字节的SRAM电源管理--2.0-3.6V供电和I/O引脚--上电/断电复位(POR/PDR)、可编程电压监测器(PVD)--4-16MHZ晶振--内嵌经出厂调校的8MHz的RC振荡器--内嵌带校准的40KHz的RC振荡器--产生CPU时钟的PLL--带校准的32KHz的RC振荡器低功耗--睡眠、停机和待机模式--Vbat为RTC和后备寄存器供电模数转换器--2个12位模数转换器,1us转换时间(多达16个输入通道)--转换范围:0至3.6V--双采样和保持功能--温度传感器DMA--2个DMA控制器,共12个DMA通道:DMA1有7个通道,DMA2有5个通道--支持的外设:定时器、ADC、SPI、USB、IIC和UART--多达112个快速I/O端口(仅Z系列有超过100个引脚)--26/37/51/80/112个I/O口,所有I/O口一块映像到16个外部中断;几乎所有的端口均可容忍5V信号调试模式--串行单线调试(SWD)和JTAG接口--多达8个定时器--3个16位定时器,每个定时器有多达4个用于输入捕获/输出比较/PWM或脉冲计数的通道和增量编码器输入--1个16位带死区控制和紧急刹车,用于电机控制的PWM高级控制定时器--2个看门狗定时器(独立的和窗口型的)--系统时间定时器:24位自减型计数器--多达9个通信接口:2个I2C接口(支持SMBus/PMBus)3个USART接口(支持ISO7816接口,LIN,IrDA接口和调制解调控制)2个SPI接口(18M位/秒)CAN接口(2.0B主动)USB 2.0全速接口计算单元CRC计算单元,96位的新批唯一代码封装ECOPACK封装3.7 杜邦线3.8 继电器四、STM32核心代码4.1  STM32:  main.c #include "stm32f10x.h" #include "beep.h" #include "delay.h" #include "led.h" #include "key.h" #include "sys.h" #include "usart.h" #include <string.h> #include <stdlib.h> #include "exti.h" #include "timer.h" #include "rtc.h" #include "wdg.h" #include "oled.h" #include "fontdata.h" #include "adc.h" #include "FunctionConfig.h" #include "dht11.h" #include "HumanDetection.h" #include "esp8266.h" /* 函数功能: 绘制时钟表盘框架 */ void DrawTimeFrame(void) { u8 i; OLED_Circle(32,32,31);//画外圆 OLED_Circle(32,32,1); //画中心圆 //画刻度 for(i=0;i<60;i++) { if(i%5==0)OLED_DrawAngleLine(32,32,6*i,31,3,1); } OLED_RefreshGRAM(); //刷新数据到OLED屏幕 } /* 函数功能: 更新时间框架显示,在RTC中断里调用 */ char TimeBuff[20]; void Update_FrameShow(void) { /*1. 绘制秒针、分针、时针*/ OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-6-90,27,0);//清除之前的秒针 OLED_DrawAngleLine2(32,32,rtc_clock.sec*6-90,27,1); //画秒针 OLED_DrawAngleLine2(32,32,rtc_clock.min*6-6-90,24,0); OLED_DrawAngleLine2(32,32,rtc_clock.min*6-90,24,1); OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-6-90,21,0); OLED_DrawAngleLine2(32,32,rtc_clock.hour*30-90,21,1); //绘制电子钟时间 sprintf(TimeBuff,"%d",rtc_clock.year); OLED_ShowString(65,16*0,16,TimeBuff); //年份字符串 OLED_ShowChineseFont(66+32,16*0,16,4); //显示年 sprintf(TimeBuff,"%d/%d",rtc_clock.mon,rtc_clock.day); OLED_ShowString(75,16*1,16,TimeBuff); //月 if(rtc_clock.sec==0)OLED_ShowString(65,16*2,16," "); //清除多余的数据 sprintf(TimeBuff,"%d:%d:%d",rtc_clock.hour,rtc_clock.min,rtc_clock.sec); OLED_ShowString(65,16*2,16,TimeBuff); //秒 //显示星期 OLED_ShowChineseFont(70,16*3,16,5); //星 OLED_ShowChineseFont(70+16,16*3,16,6); //期 OLED_ShowChineseFont(70+32,16*3,16,rtc_clock.week+7); //具体的值 } /* 函数功能: 温湿度显示 */ void ShowTemperatureAndHumidity(u8 temp,u8 humi) { sprintf(TimeBuff,"T: %d",temp); OLED_ShowString(40,16*1,16,TimeBuff); sprintf(TimeBuff,"H: %d%%",humi); OLED_ShowString(40,16*2,16,TimeBuff); } /* 函数功能: OLED所有显示页面信息初始化 */ u8 ESP8266_Stat=0; //存放ESP8266状态 1 OK ,0 error u8 ESP8266_WIFI_AP_SSID[10]; //存放WIFI的名称 #define STM32_96BIT_UID (0x1FFFF7E8) //STM32内部96位唯一芯片标识符寄存器地址 /* 函数功能: ESP8266 WIFI 显示页面 */ char ESP8266_PwdShow[20]; char ESP8266_IP_PortAddr[30]; //存放ESP8266 IP地址与端口号地址 u8 Save_ESP8266_SendCmd[30]; //保存WIFI发送的命令 u8 Save_ESP8266_SendData[50]; //保存WIFI发送的数据 void ESP8266_ShowPageTable(void) { if(ESP8266_Stat)OLED_ShowString(0,16*0,16,"WIFI STAT:ERROR"); else OLED_ShowString(0,16*0,16,"WIFI STAT:OK"); //显示字符串 //OLED_ShowString(0,2,(u8*)" "); //清除一行的显示 memset((u8*)ESP8266_PwdShow,0,20); //清空内存 sprintf((char*)ESP8266_PwdShow,"WIFI:%s",ESP8266_WIFI_AP_SSID); ESP8266_PwdShow[15]='\0'; OLED_ShowString(0,16*1,16,ESP8266_PwdShow); memset((u8*)ESP8266_PwdShow,0,20); //清空内存 sprintf((char*)ESP8266_PwdShow,"PWD:%s",wifiap_password); OLED_ShowString(0,16*2,16,ESP8266_PwdShow); OLED_ShowString(0,16*3,16,"192.168.4.1:8089"); } void OledShowPageTableInit(void) { u8 data[100],i; u32 stm32_uid=0; u8 *uid; /*1. ESP8266 WIFI相关信息初始化*/ OLED_ShowString(0,2,16," "); //清空一行的显示 OLED_ShowString(0,2,16,"WifiInit..."); //显示页面提示信息 /*1.1 设置WIFI AP模式 */ if(ESP8266_SendCmd("AT+CWMODE=2\r\n","OK",50)) { ESP8266_Stat=1; //OK } else { ESP8266_Stat=0; //ERROR } /*1.2 重启模块 */ ESP8266_SendCmd("AT+RST\r\n","OK",20); /*1.3 延时3S等待重启成功*/ DelayMs(1000); DelayMs(1000); DelayMs(1000); //得到WIFI的名称 uid=(u8*)STM32_96BIT_UID; //转为指针为1个字节 for(i=0;i<96;i++) { stm32_uid+=*uid++; //计算校验和,得到WIFI数字编号 } printf("stm32_uid=%d\r\n",stm32_uid); sprintf((char*)data,"%d",stm32_uid); strcpy((char*)ESP8266_WIFI_AP_SSID,"wbyq_"); strcat((char*)ESP8266_WIFI_AP_SSID,(char*)data); printf("请用设备连接WIFI热点:%s,%s,%s\r\n",(u8*)ESP8266_WIFI_AP_SSID,(u8*)wifiap_encryption,(u8*)wifiap_password); /*1.4 配置模块AP模式无线参数*/ memset(data,0,100); //清空数组 sprintf((char*)data,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ESP8266_WIFI_AP_SSID,wifiap_password); ESP8266_SendCmd(data,"OK",1000); /*1.5 设置多连接模式:0单连接,1多连接(服务器模式必须开启)*/ ESP8266_SendCmd("AT+CIPMUX=1\r\n","OK",20); /*1.6 开启Server模式(0,关闭;1,打开),端口号为portnum */ memset(data,0,100); //清空数组 sprintf((char*)data,"AT+CIPSERVER=1,%s\r\n",(u8*)portnum); ESP8266_SendCmd(data,"OK",50); /*1.7 获取当前模块的IP*/ ESP8266_GetWanip((u8*)ESP8266_IP_PortAddr); strcat(ESP8266_IP_PortAddr,":"); strcat(ESP8266_IP_PortAddr,portnum); printf("IP地址:%s\r\n",ESP8266_IP_PortAddr); OLED_ShowString(0,2,16," "); //清空一行的显示 OLED_ShowString(0,2,16,"WifiInitOk"); //显示页面提示信息 } /* 函数功能: 显示开关状态 */ void Show_Switch(int state) { //清屏 OLED_Clear(0); if(state) { sprintf(TimeBuff,"Socket:ON"); OLED_ShowString(20,16*2,16,TimeBuff); } else { sprintf(TimeBuff,"Socket:OFF"); OLED_ShowString(20,16*2,16,TimeBuff); } } //int main1(void) //{ // HumanDetection_Init(); //热释电模块初始化 // UsartInit(USART1,72,115200);//串口1的初始化 // LED_Init(); //初始化LED // while(1) // { // //热释电状态 为真表示有人 // if(HumanState) // { // LED1=0; //开灯 // } // else //为假 // { // LED1=1; //关灯 // } // printf("%d\n",HumanState); // } //} int main(void) { u8 rlen; char *p; u8 temp; //温度 u8 humi; //湿度 u8 stat; u8 key_val; u32 TimeCnt=0; u32 wifi_TimeCnt=0; u16 temp_data; //温度数据 u8 page_cnt=0; //显示的页面 char *time; u8 socket_state=0; UsartInit(USART1,72,115200);//串口1的初始化 BEEP_Init(); //初始化蜂鸣器 LED_Init(); //初始化LED KEY_Init(); //按键初始化 printf("正在初始化OLED...\r\n"); OLED_Init(0xc8,0xa1); //OLED显示屏初始化--正常显示 //OLED_Init(0xc0,0xa0); //OLED显示屏初始化--翻转显示 OLED_Clear(0x00); //清屏 UsartInit(USART3,36,115200); //WIFI的波特率为115200 Timer2Init(72,10000); //10ms中断一次,辅助串口3接收数据--WIFI数据 printf("正在初始化ESP8266..\r\n"); //ESP8266初始化 OledShowPageTableInit(); //清屏 OLED_Clear(0); printf("正在初始化RTC...\r\n"); RTC_Init(); //RTC初始化 DrawTimeFrame(); //画时钟框架 USART3_RX_STA=0; //清空串口的接收标志位 USART3_RX_CNT=0; //清空计数器 printf("初始化DHT11...\r\n"); DHT11_Init(); //初始化DHT11 printf("热释电模块初始化...\r\n"); HumanDetection_Init(); //热释电模块初始化 //OLED_Clear(0); printf("开始进入while(1)\r\n"); while(1) { key_val=KEY_GetValue(); if(key_val) { page_cnt++; printf("page_cnt:%d\r\n",page_cnt); //清屏 OLED_Clear(0); //时钟页面 if(page_cnt==0) { DrawTimeFrame(); //画时钟框架 RTC->CRH|=1<<0; //开启秒中断 } //温湿度页面 else if(page_cnt==1) { RTC->CRH&=~(1<<0); //关闭秒中断 //温湿度 ShowTemperatureAndHumidity(temp,humi); } //ESP8266显示 else if(page_cnt==2) { ESP8266_ShowPageTable(); } else if(page_cnt==3) { Show_Switch(socket_state); } else { DrawTimeFrame(); //画时钟框架 RTC->CRH|=1<<0; //开启秒中断 page_cnt=0; } } //时间记录 DelayMs(10); TimeCnt++; wifi_TimeCnt++; if(TimeCnt>=100) //1000毫秒一次 { TimeCnt=0; //读取温湿度数据 DHT11_Read_Data(&temp,&humi); //湿度大于90就关闭开关 if(humi>90) { socket_state=0; if(page_cnt==3) { Show_Switch(socket_state); } } //温湿度页面 if(page_cnt==1) { //温湿度 ShowTemperatureAndHumidity(temp,humi); } } if(wifi_TimeCnt>=300) //3000毫秒一次 { wifi_TimeCnt=0; //温湿度1秒上传一次 sprintf((char*)Save_ESP8266_SendData,"#%d,%d",temp,humi); //拼接数据 sprintf((char*)Save_ESP8266_SendCmd,"AT+CIPSEND=0,%d\r\n",strlen((char*)Save_ESP8266_SendData)); ESP8266_SendCmd(Save_ESP8266_SendCmd,(u8*)"OK",200); //WIFI设置发送数据长度 ESP8266_SendData(Save_ESP8266_SendData,(u8*)"OK",100); //WIFI发送数据 } //热释电状态 为真表示有人 if(HumanState) { LED1=0; //开灯 } else //为假 { LED1=1; //关灯 } /*轮询扫描数据*/ if(USART3_RX_STA) //WIFI 接收到一次数据了 { rlen=USART3_RX_CNT; //得到本次接收到的数据长度 USART3_RX_BUF[rlen]='\0'; //添加结束符 // printf("接收的数据: %s\r\n",USART3_RX_BUF); //发送到串口 { /*判断是否收到客户端发来的数据 */ p=strstr((char*)USART3_RX_BUF,"+IPD"); if(p!=NULL) //正常数据格式: +IPD,0,7:LED1_ON +IPD,0表示第0个客户端 7:LED1_ON表示数据长度与数据 { /*解析上位机发来的数据*/ p=strstr((char*)USART3_RX_BUF,":"); if(p!=NULL) { p+=1; //向后偏移1个字节 if(*p=='*') //设置RTC时间 { p+=1; //向后偏移,指向正确的时间 time=p; rtc_clock.year=(time[0]-48)*1000+(time[1]-48)*100+(time[2]-48)*10+(time[3]-48)*1; rtc_clock.mon=(time[4]-48)*10+(time[5]-48)*1; rtc_clock.day=(time[6]-48)*10+(time[7]-48)*1; rtc_clock.hour=(time[8]-48)*10+(time[9]-48)*1; rtc_clock.min=(time[10]-48)*10+(time[11]-48)*1; rtc_clock.sec=(time[12]-48)*10+(time[13]-48)*1; RTC_SetTime(rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec); if(page_cnt==0) { OLED_Clear(0); //OLED清屏 DrawTimeFrame();//画时钟框架 } } else if(strcmp(p,"LED1_ON")==0) { socket_state=1; } else if(strcmp(p,"LED1_OFF")==0) { socket_state=0; } if(page_cnt==3) { Show_Switch(socket_state); } } } } USART3_RX_STA=0; USART3_RX_CNT=0; } } } 4.2  STM32:  rtc.c#include "rtc.h" //定义RTC标准结构体 struct RTC_CLOCK rtc_clock; /* 函数功能: RTC初始化函数 */ void RTC_Init(void) { //检查是不是第一次配置时钟 u8 temp=0; if(BKP->DR1!=0X5051)//之前使用的不是LSE { RCC->APB1ENR|=1<<28; //使能电源时钟 RCC->APB1ENR|=1<<27; //使能备份时钟 PWR->CR|=1<<8; //取消备份区写保护 RCC->BDCR|=1<<16; //备份区域软复位 RCC->BDCR&=~(1<<16); //备份区域软复位结束 RCC->BDCR|=1<<0; //开启外部低速振荡器 while((!(RCC->BDCR&0X02))&&temp<250)//等待外部时钟就绪 { temp++; DelayMs(10); }; if(temp>=250) //return 1;//初始化时钟失败,晶振有问题 { RCC->CSR|=1<<0; //开启外部低速振荡器 while(!(RCC->CSR&(1<<1)));//外部低速振荡器 RCC->BDCR|=2<<8; //LSI作为RTC时钟 BKP->DR1=0X5050; //标记使用LSI作为RTC时钟 } else { RCC->BDCR|=1<<8; //LSE作为RTC时钟 BKP->DR1=0X5051; //标记使用LSE作为RTC时钟 } RCC->BDCR|=1<<15;//RTC时钟使能 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步 RTC->CRH|=0X01; //允许秒中断 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 RTC->CRL|=1<<4; //允许配置 RTC->PRLH=0X0000; RTC->PRLL=32767; //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767 RTC_SetTime(2021,4,25,20,36,20); //设置时间 RTC->CRL&=~(1<<4); //配置更新 while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成 printf("FIRST TIME\n"); }else//系统继续计时 { while(!(RTC->CRL&(1<<3)));//等待RTC寄存器同步 RTC->CRH|=0X01; //允许秒中断 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 printf("OK\n"); } STM32_NVIC_SetPriority(RTC_IRQn,2,2); //优先级 } extern void Update_FrameShow(void); /* 函数功能: RTC闹钟中断服务函数 */ void RTC_IRQHandler(void) { u32 SecCnt; if(RTC->CRL&1<<0) { SecCnt=RTC->CNTH<<16;//获取高位 SecCnt|=RTC->CNTL; //获取低位 RTC_GetTime(SecCnt); //转换标准时间 RTC_GetWeek(SecCnt); // printf("%d-%d-%d %d:%d:%d week:%d\n",rtc_clock.year,rtc_clock.mon,rtc_clock.day,rtc_clock.hour,rtc_clock.min,rtc_clock.sec,rtc_clock.week); Update_FrameShow(); //更新显示 RTC->CRL&=~(1<<0); //清除秒中断标志位 } if(RTC->CRL&1<<1) { // printf("闹钟时间到达!....\n"); // BEEP=1; // DelayMs(500); // BEEP=0; RTC->CRL&=~(1<<1); //清除闹钟中断标志位 } } //闰年的月份 static int mon_r[12]={31,29,31,30,31,30,31,31,30,31,30,31}; //平年的月份 static int mon_p[12]={31,28,31,30,31,30,31,31,30,31,30,31}; /* 函数功能: 设置RTC时间 函数形参: u32 year; 2018 u32 mon; 8 u32 day; u32 hour; u32 min; u32 sec; */ void RTC_SetTime(u32 year,u32 mon,u32 day,u32 hour,u32 min,u32 sec) { u32 i; u32 SecCnt=0; //总秒数 /*1. 累加已经过去的年份*/ for(i=2017;i<year;i++) //基准年份:20170101000000 { if(RTC_GetYearState(i)) { SecCnt+=366*24*60*60; //闰年一年的秒数 } else { SecCnt+=365*24*60*60; //平年一年的秒数 } } /*2. 累加过去的月份*/ for(i=0;i<mon-1;i++) { if(RTC_GetYearState(year)) { SecCnt+=mon_r[i]*24*60*60; //闰年一月的秒数 } else { SecCnt+=mon_p[i]*24*60*60; //平年一月的秒数 } } /*3. 累加过去的天数*/ SecCnt+=(day-1)*24*60*60; /*4. 累加过去小时*/ SecCnt+=hour*60*60; /*5. 累加过去的分钟*/ SecCnt+=min*60; /*6. 累加过去的秒*/ SecCnt+=sec; /*7. 设置RTC时间*/ //设置时钟 RCC->APB1ENR|=1<<28;//使能电源时钟 RCC->APB1ENR|=1<<27;//使能备份时钟 PWR->CR|=1<<8; //取消备份区写保护 //上面三步是必须的! RTC->CRL|=1<<4; //允许配置 RTC->CNTL=SecCnt&0xffff; RTC->CNTH=SecCnt>>16; RTC->CRL&=~(1<<4);//配置更新 while(!(RTC->CRL&(1<<5)));//等待RTC寄存器操作完成 } /* 函数功能: 获取RTC时间 函数参数: u32 sec 秒单位时间 */ void RTC_GetTime(u32 sec) { u32 i; rtc_clock.year=2017; //基准年份 /*1. 计算当前的年份*/ while(1) { if(RTC_GetYearState(rtc_clock.year)) { if(sec>=366*24*60*60) //够一年 { sec-=366*24*60*60; rtc_clock.year++; } else break; } else { if(sec>=365*24*60*60) //够一年 { sec-=365*24*60*60; rtc_clock.year++; } else break; } } /*2. 计算当前的月份*/ rtc_clock.mon=1; for(i=0;i<12;i++) { if(RTC_GetYearState(rtc_clock.year)) { if(sec>=mon_r[i]*24*60*60) { sec-=mon_r[i]*24*60*60; rtc_clock.mon++; } else break; } else { if(sec>=mon_p[i]*24*60*60) { sec-=mon_p[i]*24*60*60; rtc_clock.mon++; } else break; } } /*3. 计算当前的天数*/ rtc_clock.day=1; while(1) { if(sec>=24*60*60) { sec-=24*60*60; rtc_clock.day++; } else break; } /*4. 计算当前的小时*/ rtc_clock.hour=0; while(1) { if(sec>=60*60) { sec-=60*60; rtc_clock.hour++; } else break; } /*5. 计算当前的分钟*/ rtc_clock.min=0; while(1) { if(sec>=60) { sec-=60; rtc_clock.min++; } else break; } /*6. 计算当前的秒*/ rtc_clock.sec=sec; } /* 函数功能: 判断年份是否是平年、闰年 返回值 : 0表示平年 1表示闰年 */ u8 RTC_GetYearState(u32 year) { if((year%4==0&&year%100!=0)||year%400==0) { return 1; } return 0; } /* 函数功能: 获取星期 */ void RTC_GetWeek(u32 sec) { u32 day1=sec/(60*60*24); //将秒单位时间转为天数 switch(day1%7) { case 0: rtc_clock.week=0; break; case 1: rtc_clock.week=1; break; case 2: rtc_clock.week=2; break; case 3: rtc_clock.week=3; break; case 4: rtc_clock.week=4; break; case 5: rtc_clock.week=5; break; case 6: rtc_clock.week=6; break; } } 4.3 ESP8266.c#include "esp8266.h" /* 函数功能:向ESP82668266发送命令 函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败 */ u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; USART3_RX_CNT=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack)) { res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 } USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266发送命令后,检测接收到的应答 函数参数:str:期待的应答结果 返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置) */ u8* ESP8266_CheckCmd(u8 *str) { char *strx=0; if(USART3_RX_STA) //接收到一次数据了 { USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 //printf("RX=%s",USART3_RX_BUF); } return (u8*)strx; } /* 函数功能:向ESP8266发送指定数据 函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值:0,发送成功(得到了期待的应答结果)luojian */ u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime) { u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 { while(--waittime) //等待倒计时 { DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 { if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; USART3_RX_CNT=0; } } if(waittime==0)res=1; } return res; } /* 函数功能:ESP8266退出透传模式 返 回 值:0,退出成功; 1,退出失败 */ u8 ESP8266_QuitTrans(void) { while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT\r\n","OK",20);//退出透传判断. } /* 函数功能:获取ESP8266模块的连接状态 返 回 值:0,未连接;1,连接成功. */ u8 ESP8266_ConstaCheck(void) { u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS\r\n:"); res=*p; //得到连接状态 return res; } /* 函数功能:获取ip地址 函数参数:ipbuf:ip地址输出缓存区 */ void ESP8266_GetWanip(u8* ipbuf) { u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 { ipbuf[0]=0; return; } p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); } 五、QT设计的上位机代码:  Android手机APP+Windows系统上位机
文章
传感器  ·  编解码  ·  网络协议  ·  开发工具  ·  Android开发  ·  芯片  ·  网络架构  ·  SoC  ·  Windows  ·  内存技术
2021-12-20
ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)
前面的7节课把开发板上基本的外设都测试过一边,接下来马上就要进入wifi和蓝牙应用的测试了 在此之前,还需要把掉电数据保存的功能给实现, 在STM32中,可以使用内部的flash或者有些自带的EEPROM 在 ESP32-C3 上,使用非易失性存储 (NVS) 库的方式,进行简单数据的掉电保存 前言ESP32-C3系列博文连接:测试使用的开发板:自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)测试使用的开发环境:ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF——Windows和Ubuntu双环境)基础篇系列相关博文:ESP32-C3入门教程 基础篇(一、ADC采样)ESP32-C3入门教程 基础篇(二、GPIO中断、按键驱动测试)ESP32-C3入门教程 基础篇(三、UART模块 — 与Enocean无线模块串口通信)ESP32-C3入门教程 基础篇(四、I2C总线 — 与SHT21温湿度传感器通讯)ESP32-C3入门教程 基础篇(五、RMT应用 — 控制SK6812全彩RGB 灯)ESP32-C3入门教程 基础篇(六、TIMG 硬件定时器 与 软件定时器)ESP32-C3入门教程 基础篇(七、LEDC — LED PWM 控制器)因为是对ESP32-C3 内部 Flash的操作,所以这里我们不要用到其他外设。1、NVS基础介绍 通俗的来说,NVS 就是在 flash 上分配的一块内存空间 ,提供给用户保存掉电不丢失的数据 。 本文主要主要的目的是基于官方的SDK,学会使用 NVS,相关的知识简单提一下,比如分区表等(后期需要应用到的时候再来详细说明)。1.1 基本介绍对于ESP32-C3 NVS的介绍,乐鑫的官网的说明链接如下:乐鑫官方ESP32-C3 NVS部分说明简单通过官方介绍了解一下,还是使用几张官方的图表示一下:1.2 分区表 我们说NVS是内存上的一块区域,那么他是地址是多少? 大小是多少? 这就不得不提一下分区表相关的内容了,官方说明地址如下:ESP32-C3 的 flash 分区表根据官方介绍,我们列出这里需要用到的:分区表中的每个条目都包括以下几个部分:Name(标签)、Type(app、data 等)、SubType 以及在 flash 中的偏移量(分区的加载地址)。Type 字段可以指定为 app (0x00) 或者 data (0x01),也可以直接使用数字 0-254(或者十六进制 0x00-0xFE)。注意,0x00-0x3F 不得使用(预留给 esp-idf 的核心功能)。SubType 字段长度为 8 bit,内容与具体分区 Type 有关。目前,esp-idf 仅仅规定了 “app” 和 “data” 两种分区类型的子类型含义。所以根据上面分区表的介绍说明,对于我们使用的 ESP32-C3,芯片启动会自动打印系统信息,对应的 NVS 说明如下图:对于NVS分区如何生成,请参考我的另一篇博文:ESP32-C3入门教程 网络 篇(二、 Wi-Fi 配网 — Smart_config方式 和 BlueIF方式)在博文最后面,因为默认的分区表满足不了需求,告知了如何修改分区表。1.3 NVS使用步骤 本文的NVS测试,是基于默认的分区表,所以在使用过程,我们不需要再进行分区表的操作。NVS所需要用到的API,在nvs_flash.h 文件中,路径为:esp-idf/components/nvs_flash/include/nvs_flash.h初始化 NVS,使用函数nvs_flash_init:在示例中:打开NVS,使用nvs_open函数: 在示例中,第二个参数应该是表示打开的区域是可以读也可以写的 ,只读的是NVS_READONLY:读写操作,使用nvs_get_*(*号表示不同的数据类型,比如nvs_get_i32、nvs_get_u16) 读操作,使用nvs_set_*进行写操作:在示例中对于读写应用如下:写入值后,需要条用nvs_commit函数确保值写入成功。关闭NVS,完成写入后,使用nvs_close关闭。2、示例测试2.1 基础示例测试在官方例程中,我们参考的示例程序有2个,如下图:示例nvs_rw_value:在我们上面介绍NVS的使用步骤中的举例,就是用的nvs_rw_value工程,基本的工程没什么好修改的,测试的结果如下,每次重启 restart_counter 的值就会增加1,如下图:示例nvs_rw_blob:第二个工程测试效果如下:先看了测试效果,我们来简单说明一下源码,第一个函数save_restart_counter函数,和示例nvs_rw_blob基本一样,不多说。我们来看第二个函数save_run_time,在这个函数中,我们使用了一个nvs_get_blob和nvs_set_blob的函数,注意到他们都有一个 void*类型得参数,表面这两个nvs的操作能够适用于任意类型的数据。上面的save_run_time函数,我们直接上添加了注释的源码:/* Save new run time value in NVS by first reading a table of previously saved values and then adding the new value at the end of the table. Return an error if anything goes wrong during this process. */ esp_err_t save_run_time(void) { nvs_handle_t my_handle; esp_err_t err; // Open 正常的操作步骤,打开nvs,第一个命名空间,读写,句柄名称 err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle); if (err != ESP_OK) return err; /* Read the size of memory space required for blob unsigned int required_size 读nvs,读取键值对为 "run_time" 处的内容放入 变量required_size */ size_t required_size = 0; // value will default to 0, if not set yet in NVS /*先使用一次nvs_get_blob函数,但是第三个参数输出地址使用的是NULL, 表示读出的数据不保存,因为这里使用只是为了看一下 "run_time" 处是否 有数据,只是先读一下数据,看一下读完以后 required_size 还是不是0 */ err = nvs_get_blob(my_handle, "run_time", NULL, &required_size); if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; /* Read previously saved blob if available 这里申请一块地址,定义为 run_time ,地址上存放的示uint32_t数据,大小为 required_size大小 + sizeof(uint32_t) */ uint32_t* run_time = malloc(required_size + sizeof(uint32_t)); /* 如果上面读到的 required_size 大于0 ,说明"run_time" 处以前是保存过数据的 那么,就先读出来,保存在刚才申请的 地址 run_time 处(第3个参数)。 */ if (required_size > 0) { err = nvs_get_blob(my_handle, "run_time", run_time, &required_size); if (err != ESP_OK) { free(run_time); return err; } } /* Write value including previously saved blob if available 不管 required_size 有过还是没有过,进行这步操作,都会使得 required_size 增加 增加 sizeof(uint32_t) 大小,因为示例本意示重启一次,计数必须加一次 */ required_size += sizeof(uint32_t); /* 每一次保存的是使用数组形式保存数据:类似于 uint32_t run_time[] 数组 给数组赋值 */ run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS; /* 最后把需要保存的数组处理完成以后 调用 nvs_set_blob 函数进行保存 */ err = nvs_set_blob(my_handle, "run_time", run_time, required_size); free(run_time); if (err != ESP_OK) return err; // Commit err = nvs_commit(my_handle); if (err != ESP_OK) return err; // Close nvs_close(my_handle); return ESP_OK; }搞清楚了上面这个函数,nvs_rw_blob示例基本就没问题了,接下来的print_what_saved(void)函数中,使用了不同的 get 函数,从同一个命名空间的不同 键值处取出了不同的数据(这个具体下一节我们会做测试),其他的倒没什么特别的:2.2 数据的删除前面说过,NVS其实也就是在 Flash 空间上开辟了一块区域,那么这块区域肯定示有地址的,只是 ESP32-C3 使用 NVS方式,对使用者而言内存地址是不透明的,只是通过命名空间 和键值对自动分配(下一节会说明)。既然有保存,那么也得知道删除,因为不删除,数据可能会一直存在与那个地址空间。示例和官方说明只是说明了如何使用 NVS 保存数据掉电不丢失,却没有针对性的说明如何清除数据。我们通过nvs.h找到了两个函数:esp_err_t nvs_erase_key(nvs_handle_t handle, const char* key); esp_err_t nvs_erase_all(nvs_handle_t handle);使用这两个函数,我们测试一下,测试函数基于 示例nvs_rw_blob:,然后加入按键驱动:ESP32-C3 学习测试(二、GPIO中断、按键驱动测试)我们通过按键操作,对示例中保存的数据进行删除,在按键驱动中,我们修改一下代码:static void button_single_click_cb(void *arg){ uint8_t *num = (uint8_t *)arg; uint8_t gpio_num = *num; ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK\n", gpio_num); printf("nvs_erase_key test!\r\n"); // nvs_erase_key(my_handle,"restart_conter"); nvs_handle_t my_handle; // Open nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle); nvs_erase_key(my_handle,"restart_conter"); // err = nvs_commit(my_handle); // nvs_close(my_handle); } 图示如下:测试效果如下图:然后换一个 key 试一试:最后测试一下nvs_erase_all,如下图:2.3 命名空间,键值对在上面例程中用到过命名空间,可以区分不同的存储区域,但是还有一个 key 参数,应该示键值对,感觉和命名空间一样,也是用来区分不同数据的,如图:在前面的多个示例测试中,我们也多少对命名空间(函数中一般用参数const char* name表示)和键值对(函数中一般用参数const char* key表示)有一定理解,首先在示例中,使用了同一个命名空间,通过不同的键值对获取不同的数据:针对 命名空间 和 键值对 做几个简单的测试,使用按钮连按,保存新的 命名空间的数值,在按键操作中保存新的数据:这个数据也是上电的时候读取:测试结果如下:总结一下,基于前面的测试示例,画了一张图,如下:2.4 字符串数据类型的保存上面我们示例中使用的数据基本都是整形,虽然我们示例中使用了nvs_set_blob,也还是传入的整形数据,我们来测试下,保存字符串,数值类型的数据。和上面的例程一样,通过按键操作保存一个字符串:static void button_long_press_start_cb(void *arg){ uint8_t *num = (uint8_t *)arg; uint8_t gpio_num = *num; ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START\n", gpio_num); char test_str[]="this is my test str,boom!"; printf("nvs_new_name test!\r\n"); nvs_handle_t my_handle; nvs_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle); nvs_set_str(my_handle,"str_test",test_str); nvs_commit(my_handle); nvs_close(my_handle); }在主函数中,新建一个读取函数:esp_err_t my_test_str(void) { nvs_handle_t my_handle; esp_err_t err; char get_char[30] = {0}; // Open err = nvs_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle); if (err != ESP_OK) return err; // Read size_t required_size = 0; err = nvs_get_str(my_handle, "str_test",NULL,&required_size); // err = nvs_get_i32(my_handle, "str_test", &restart_counter); // if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; // err = nvs_get_blob(my_handle, "run_time", NULL, &required_size); if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err; if (required_size > 0) { err = nvs_get_str(my_handle, "str_test", get_char, &required_size); printf("test str is: %s \nsize is %d \n",get_char,required_size); if (err != ESP_OK) { return err; } } else{ printf("no str data now!!!\n"); } // Close nvs_close(my_handle); return ESP_OK; }当然还是需要在app_main中调用my_test_str,测试效果如下:在后期的 ESP32-C3 wifi 学习和使用的时候,保存的 SSID 和 密码 就会经常使用的 NVS 的字符串操作。
文章
存储  ·  传感器  ·  Ubuntu  ·  API  ·  开发工具  ·  数据安全/隐私保护  ·  芯片  ·  Windows  ·  内存技术
2022-09-16
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(一) 嵌入式Linux开发基本概念以及开发流程介绍
1.linux开发初了解1.1 嵌入式Linux开发的基本概念嵌入式Linux系统,就相当于一套完整的PC软件系统。嵌入式Linux开发有哪些内容?Windows里含有: 简单地说,嵌入式LINUX系统里含有① 电脑一开机,那些界面是谁显示的?是BIOS,它做什么?一些自检,然后从硬盘上读入windows,并启动它。类似的,这个BIOS对应于嵌入式Linux里的bootloader。Bootloader的作用就是去Flash、SD卡等设备上读入Linux内核,并启动它。② Windows系统必需的软件,比如IE、文件浏览器等保存在哪里?在C盘上,里面有各种系统软件。对应的,系统运行必需的文件在Linux下我们称之为根文件系统。③ windows能识别出C盘、D盘,那么肯定有读写硬盘的能力。这个能力我们称之为驱动程序。当然不仅仅是操作硬盘,还有网卡、USB等等其他硬件。嵌入式Linux能从Flash上读出并执行应用程序,肯定也得有Flash的驱动程序啊,当然也不仅仅是Flash。④ Windows启动之后,我们就是聊QQ、玩游戏了,这些就是APP,它们存在磁盘上同样的,嵌入式Linux系统中我们也有各种APP,它们位于根文件系统上。① bootloader:用于启动Linux内核② Linux内核(含有驱动程序):提供进程管理、文件管理、硬件驱动等③ 根文件系统:系统运行必需的文件、程序④ APP:保存于根文件系统上1.1.1关于Git的背景介绍什么是Git?Git由Linus Torvalds在2005年创建,用于Linux内核的开发,其他内核开发人员也为它的初始开发做出了贡献。Git是一个免费和开源的 分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有内容。Git 和其它版本控制系统(包括 Subversion 和近似工具)的主要差别在于 Git 对待数据的方法。 从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce、Bazaar 等等) 将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (它们通常称作 基于差异(delta-based) 的版本控制)。参考引用: 请点击我们为什么要用Git来管理源码?Git的开发流程和代码管理方式适用于国内外各大中小企业,Git也适用于个人发布自己程序源码以及博客网站,同时在文件代码管理上可以很方便的获取同步最新,多人协作开发,以及查看历史提交,集成编译等。1.1.2关于repo的背景介绍什么是repo?repo是Git之上构建的工具。Repo帮助管理许多Git存储库,将其上载到修订控制系统,并自动执行部分开发工作流程。Repo并不是要取代Git,而只是为了使其更易于使用Git。repo命令是可执行的Python脚本,您可以将其放置在路径中的任何位置。1.1 3 一些关于此背景知识的介绍制作根文件系统有归多种方法:① 使用Busybox手工制作Busybox本身包含了很了Linux命令,但是要编译其他程序的话需要手工下载、编译,如果它需要某些依赖库,你还需要手工下载、编译这些依赖库。如果想做一个极简的文件系统,可以使用Busybox手工制作。② 使用Buildroot自动制作它是一个自动化程序很高的系统,可以在里面配置、编译内核,配置编译u-boot、配置编译根文件系统。在编译某些APP时,它会自动去下载源码、下载它的依赖库,自动编译这些程序。Buildroot的语法跟一般的Makefile语法类似,很容易掌握。③ 使用YoctoNXP、ST等公司的官方开发包是使用Yocto,但是Yocto语法复杂,并且Yocto动辄10GB,下载安装都很困难,普通笔记本编译可能需要2-3天甚至更久,非常不适合初学者(我们不推荐使用yocto构建文件系统)。基于上述特点,我们选择Buildroot。Buildroot是一组Makefile和补丁,可简化并自动化地为嵌入式系统构建完整的、可启动的Linux环境(包括bootloader、Linux内核、包含各种APP的文件系统)。Buildroot运行于Linux平台,可以使用交叉编译工具为多个目标板构建嵌入式Linux平台。Buildroot可以自动构建所需的交叉编译工具链,创建根文件系统,编译Linux内核映像,并生成引导加载程序用于目标嵌入式系统,或者它可以执行这些步骤的任何独立组合。例如,可以单独使用已安装的交叉编译工具链,而Buildroot仅创建根文件系统。参考网址Buildroot用户手册 请点击Buildroot源码下载位置 请点击目录结构的位置以及作用请参考网址请点击学习更多关于buildroot知识请参考 请点击百问网提供的Buildroot现支持如下包:busybox taglib libopenh264 qt5_gl_available skeleton_init_systemd tremor libopusenc qt5_jscore_available alsa_utils libarchive libtheora qt5 bluez_alsa zlib libvpx qt5_version_latest faad2 libzlib libyuv qt5base ffmpeg has_zlib live555 qt5base_network flac libassuan mediastreamer qt5base_concurrent fluidsynth libgcrypt x264 qt5base_sql gstreamer1 libgpg_error x264_cli qt5base_sqlite_none jack2 Libgpgme libcurl qt5base_test lame Libksba libnice qt5base_xml mjpegtools Openssl libnl qt5base_gui musepack libopenssl libpcap qt5base_widgets pulseaudio sqlite libshout qt5base_opengl twolame libconfig libsoup qt5base_opengl_es2 wavpack libconfuse libsrtp qt5base_opengl_lib bzip2 libfuse libtirpc qt5base_linuxfb lz4 liblockfile neon qt5base_eglfs xz bayer2rgb_neon ortp qt5base_default_qpa="" gdb bullet rtmpdump qt5base_printsupport gdb_server cairo bctoolbox qt5base_fontconfig gdb_debugger cairo_pdf elfutils qt5base_harfbuzz gdb_tui cairo_png glibmm qt5base_gif spidev_test cairo_svg libb64 qt5base_jpeg strace fontconfg libbsd qt5base_png libtool freetype libcap qt5base_dbus cifs_utils gdk_pixbuf libcroco qt5base_icu dosfstools giflib libevdev qt5base_openssl e2fsprogs harfbuzz libffi qt5base_tslib mmc_utils jasper libglib2 qt5canvas3d nfs_utils jpeg libical qt5charts glmark2_flavor_any kmsxx libnpth qt5connectivity libva_utils libdrm libsigc qt5declarative fb_test_app libmediaart liburcu qt5declarative_quick fbdump libpng llvm qt5enginio fbset libraw mpdecimal qt5graphicaleffects librsvg protobuf qt5imageformats psplash libsvg tzdata qt5location xkeyboard_config libsvg_cairo icu qt5multimedia dbus libva libfribidi qt5quickcontrols evtest opencv3 ncurses qt5quickcontrols2 has_libegl pcre qt5script gptfdisk has_libgles pcre2 qt5scxml i2c_tools openjpeg popt qt5sensors iostat pango readline qt5serialbus linux_serial_test pipewire slang qt5serialport memtool pixman bluez_tools qt5svg parted tiff bluez5_utils qt5virtualkeyboard spi_tools wayland bmon qt5webchannel sysstat webp iperf3 qt5websockets uboot_tools dtc iproute2 qt5xmlpatterns has_udev libaio iw attr usbmount libgudev lrzsz keyutils usbutils libinput mii_diag kmod luajit libpciaccess mjpg_streamer kmod_tools python3 libusb mosquitto start_stop_daemon alsa_lib libxkbcommon net_tools swupdate fdk_aac mtdev openobex systemd libcodec2 tslib openssh util_linux libcuefile expat pppd myir_hmi_gui libreplaygain json_glib rpcbind rs485read libsamplerate jsoncpp tcpdump libsndfile libxml2 wget libvorbis bitstream wireless_tools mp4v2 libass opkg opus libcamera bash portaudio libmms gnupg2 sbc libmpeg2 gnupg2_gpgv speex libogg lockfile_progs我们都做了哪些改进?我们基于buildroot官方 2020.02长期支持版本进行适配imx6ull开发板,在此基础上针对ST yocto发行系统做了大量的裁剪,在保证最小系统的基础上增加对qt5.12 库的支持,同时也支持opencv3编程 mqtt库 swupdate ota升级等等比较常用的应用,我们也会提供如何在buildroot新增自己的软件包教程,同时源码保存在gitee上,链接地址为 请点击 大家可以在上面提交issue 或者pull request。什么是init系统服务?init(为英语:initialization的简写)是 Unix 和 类Unix 系统中用来产生其它所有进程的程序。它以守护进程的方式存在,其进程号为1。Linux系统在引导时加载Linux内核后,便由Linux内核加载init程序,由init程序完成余下的引导过程,比如加载运行级别,加载服务,引导Shell/图形化界面等等。什么是systemv守护进程?System V(缩写为SysV)在大多数Linux发行版中使用最广泛,在systemv中,有一个第一个程序在内核加载后运行。该程序称为init。Init做一些事情,其中之一就是加载一系列脚本来启动各种系统服务,例如网络,ssh守护程序等。System V中的运行级别描述了某些状态。例如:运行级别0:暂停运行级别1:单用户模式运行级别6:重新启动systemv的问题在于它需要仔细调整。假设您有一个要在启动时运行的网络文件系统(NFS)客户端。在网络正常工作之前运行NFS没有任何意义。因此,您必须确保它等待启动,直到网络已经正常工作为止。Systemv init这样做的方法是为服务启动设置严格的顺序。每个服务都分配有一个优先级编号,init会按优先级顺序启动服务。如果需要确保网络连接后启动服务,则必须手动为服务分配更高的优先级。这必须由某人(通常是软件包维护者)针对计算机上运行的每项服务来完成。所有System V初始化脚本都存储在/etc/rc.d/init.d/或/etc/init.d目录中。这些脚本用于控制系统的启动和关闭。通常,您会在这个目录找到启动ssh服务器或网络的脚本,使用/etc/init.d/S50sshd start 开启某个服务或者使用/etc/init.d/S50sshd stop关闭某个服务。什么是systemd守护进程?systemd这一名字源于Unix中的一个惯例:在Unix中常以“d”作为系统守护进程(英语:daemon,亦称后台进程)的后缀标识。除此以外,systemd亦是借代英文术语D体系,而这一术语即是用于描述一个人具有快速地适应环境并解决困难的能力。systemd是Linux电脑操作系统之下的一套中央化系统及设置管理程序(init),包括有守护进程、程序库以及应用软件,由Lennart Poettering带头开发。其开发目标是提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果,最终代替现在常用的System V与BSD风格init程序。目前绝大多数的Linux发行版都已采用systemd代替原来的System V。将service(服务)、target(运行模式,类似于运行级别)、mount、timer、snapshot、path、socket、swap等称为Unit。比如,一个auditd服务(就是auditd.service)就是一个Unit,一个multi-user.target运行模式也是一个Unit,其中不同的服务通过 systemctl 来进行统一管理,例如重启一个sshd服务,需要执行 systemctl restart sshd 命令,同样的如果添加一个启动程序需要自己定义一个 service服务才可以。你可以用我们提供的buildroot做些什么?快速实现自己的想法,增加自己的应用,学习了解busybox以及各种包是如何编译生成,用于调试内核或者应用程序等等。深入了解学习更多关于buildroot知识请参考 请点击buildroot下进入menuconfig包选择配置配置界面book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make menuconfigbuildroot下单独编译内核book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make linux-rebuildbuildroot下进入内核make menuconfig配置选项界面book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make linux-menuconfigbuildroot下单独编译u-bootbook@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make uboot-rebuildbuildroot下单独编译某个软件包book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make <pkg>-rebuildbuildroot下进入busybox配置界面book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make busybox-menuconfigbuildroot下生成系统sdk,最后生成的目录在output/images/目录下book@100ask:~/100ask_stm32mp157_pro-sdk/Buildroot_2020.02.x$ make sdk深入了解学习更多关于buildroot知识请参考 请点击1.1.4关于Linux内核的背景介绍Linux内核(英语:Linux kernel)是一种开源的类Unix操作系统宏内核。整个Linux操作系统家族基于该内核部署在传统计算机平台(如个人计算机和服务器,以Linux发行版的形式)和各种嵌入式平台,如路由器、无线接入点、专用小交换机、机顶盒、FTA接收器、智能电视、数字视频录像机、网络附加存储(NAS)等。工作于平板电脑、智能手机及智能手表的Android操作系统,它的底层操作系统也是Linux。尽管在桌面计算机的占用率较低,但基于Linux的操作系统统治了几乎从移动设备到主机的其他全部领域。实际Linux的发行版Ubuntu,其易用性也逐渐接近Windows。Linux kernel官网:请点击linux Kernel维基百科:请点击在线阅读linux kernel源码:请点击Git仓库地址: 请点击更多关于Linux内核资料请参考页面:请点击NXP官方kernel源码Git仓库地址: 请点击上述Git仓库是专为100ask_imx6ull系列开发板制定的Linux内核,它有如下特性:最后重启开发板reboot,它就使用新的zImage、dtb、模块了。1.1.5关于Bootloader的背景介绍Bootloader是在操作系统运行之前运行的一段代码,用于引导操作系统。通常每个操作系统都有一组专属的引导加载程序。引导加载程序通常可以通过多种方式引导操作系统内核,还有各种命令用于调试或修改内核运行环境。U-Boot是一个开源的主引导加载程序,用于引导设备的操作系统内核,并含有多种命令以便调试系统。它适用于多种计算机体系结构,包括68k,ARM,Blackfin,MicroBlaze,MIPS,Nios,SuperH,PPC,RISC-V和x86。U-boot官网 请点击源码下载页面 请点击Git仓库地址 请点击uboot更多使用讲解请参考页面 请点击NXP官方uboot源码Git地址: 请点击注意:我们使用的版本针对板子进行过修改,u-boot官网下载的源码不能直接使用。1.2 日常工作中开发流程是怎样?Bootloader、Linux内核、APP等等软件,需要在Ubuntu中编译;但是阅读、修改这些源码时,在Windows下会比较方便。所以,我们需要在Windows、Ubuntu上都存有源码。① 在Windows上阅读、研究、修改(使用Source insight会很方便),修改后上传到Ubuntu(使用Filezilla)② 在Ubuntu上编译、制作(使用MobaXterm远程登录Ubuntu会很方便)③ 把制作好的可执行程序下载到开发板上运行、测试(使用MobaXterm连接开发板的串口)。在整个开发过程中,我们会用到Windows、Ubuntu、开发板,如下图所示:这里只是列出要做的事情,后面会一一讲到。安装VMware、下载Ubuntu映象安装Windows上各个APP这些APP有:SouceInsight、FileZilla、MobaXterm、Notepad++下载源码和工具链工具链:必须下载或上传到Ubuntu,并设置好PATH环境变量。(简单地说,工具链就是编译器)源码:u-boot、Linux内核,学习到时再下载也行,Windows和Ubuntu各存一份。注意:根文件系统是使用buildroot制作的,它无需放在Windows上。连接开发板① 连接电源线到开发板,② 开发板的串口线,接到Windows电脑;并用MobaXterm连接串口。③ 开发板的网线,接到路由器或交换机,网络的设置请参考以下文档(可先不做):烧写系统如果开发板系统崩溃,或是新到手的板子上没有系统,请根据后面的开发板使用手册手册来烧写系统。1.3 常用的软件有如下软件:软件名 说明vmware 虚拟机软件,安装时需要用到管理员权限,详细的安装过程见后文Source insight 阅读、编写源码的工 具,即装即用;推荐初学者使用Visual Studio Code 阅读、编写源码的工具,需要进行很多配置;不推荐初学者使用MobaXterm 串口工具、远程登录工具Filezilla 文件传输工具,在Windows和Ubuntu之间传输文件Notepad++ 文本编辑工具,比记事本好用注意:Visual Studio Code的配置比较麻烦,建议初学者使用Source insight来阅读、编写源码。
文章
存储  ·  Ubuntu  ·  Unix  ·  Linux  ·  开发工具  ·  虚拟化  ·  git  ·  网络架构  ·  Windows  ·  内存技术
2022-12-12
跳转至:
华章出版社
495 人关注 | 1015 讨论 | 10119 内容
+ 订阅
  • 带你读《Java并发编程的艺术》之一:并发编程的挑战
查看更多 >
开发与运维
5632 人关注 | 131529 讨论 | 302793 内容
+ 订阅
  • 使用轻量服务器搭建我的世界联机服务器
  • 2023mac电脑免费系统优化软件CleanMyMac
  • 阿里云国际站注册充值流程
查看更多 >
IoT
122961 人关注 | 2889 讨论 | 23782 内容
+ 订阅
  • 设计模式 - 结构型模式_享元模式
  • 未来前端的机会在哪里?
  • 【每日一题Day33】LC799香槟塔 | 动态规划
查看更多 >
大数据
188350 人关注 | 29454 讨论 | 80638 内容
+ 订阅
  • 设计模式 - 结构型模式_适配器模式
  • 设计模式 - 结构型模式_外观模式
  • IPSSL证书 公网IP地址SSL证书申请 IP地址HTTPS
查看更多 >
人工智能
2798 人关注 | 11409 讨论 | 97301 内容
+ 订阅
  • Java数组
  • redis几个常见问题要会
  • 数据结构 | 十大排序超硬核八万字详解【附动图演示、算法复杂度性能分析】
查看更多 >