
1. 为什么STM32CubeMX导出IAR工程会卡在“License Check Failed”这一步我第一次用STM32CubeMX导出IAR工程时整个流程走完双击打开IAR项目编译器直接弹出一个红色对话框fatal error[lms001]: license check failed. use the iar license manager to re...。当时手头只有IAR EWARM 8.40的试用版试用期还剩17天但就是死活过不了这道关。后来翻遍IAR官方文档、论坛帖子、甚至重装了三次IAR才发现问题根本不在许可证本身——而在于STM32CubeMX导出时默认调用的IAR版本与你本地安装的IAR版本不匹配。这个细节被绝大多数入门教程忽略。STM32CubeMX尤其是v6.5.0及之前版本在“Project Manager”页签下“Toolchain / IDE”下拉菜单里选“IAR ARM”它实际调用的是一个硬编码路径C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.4\arm\bin\iccarm.exe。注意它写死的是“8.4”而不是你当前安装的“9.30”或“8.50.1”。如果你装的是IAR EWARM 9.30它却去读8.4目录下的icclm.exe许可证管理器而该目录下根本没有对应版本的license文件于是报错。更隐蔽的是IAR的许可证机制是按主版本号隔离的。8.x和9.x的license文件不通用即使你有9.30的正式授权8.4的启动器也读不到。这就是为什么很多人明明激活了最新版IARCubeMX导出的工程却始终提示license失败——CubeMX不是在验证你的IAR是否授权而是在验证它自己想找的那个旧版本IAR是否存在且可授权。这个问题在Windows系统上尤为典型。macOS和Linux用户反而少遇到因为CubeMX在非Windows平台默认不提供IAR导出选项需手动配置。所以当你看到“License Check Failed”时第一反应不该是去重装IAR或重刷license而是立刻打开CubeMX的“Project Manager”页签点击右下角的“Settings”齿轮图标在弹出窗口中找到“IDE Settings” → “IAR EWARM” → “Path to IAR EWARM installation”把路径手动改成你真实安装的IAR目录比如C:\Program Files\IAR Systems\Embedded Workbench 9.3\arm。改完后务必点击“OK”保存再重新Generate Code——这次导出的.eww工作区文件才会真正指向你本机可用的IAR版本。提示CubeMX的“Settings”里改路径只是影响本次导出。如果你经常切换IAR版本比如同时维护老项目用8.4新项目用9.3建议在CubeMX安装目录下的STM32CubeMX.ini文件里手动添加一行IAR_PATHC:\Program Files\IAR Systems\Embedded Workbench 9.3\arm这样每次启动CubeMX都会默认加载该路径避免每次导出前都要手动设置。2. 导出后的IAR工程结构为何比Keil工程“多一层嵌套”这是设计缺陷还是刻意为之导出完成双击生成的.eww文件IAR Embedded Workbench打开后你会发现项目树里多了一个叫Core的Group里面又套着Inc、Src、Startup三个子Group而Inc下面才是main.h、stm32f1xx_hal_conf.h这些头文件Src下面才是main.c、stm32f1xx_it.c等源码。对比Keil MDK导出的工程它的Source Group是平铺的没有Core这一层。很多新手会本能地觉得“这结构太绕了删掉CoreGroup把文件拖到根目录下不就清爽了”——千万别这么干。这个嵌套结构是IAR官方在STM32CubeMX v6.0之后引入的模块化工程组织策略核心目的是解决“多芯片共用同一套HAL库”的冲突问题。举个例子你用CubeMX为STM32F103C8T6生成工程同时又为STM32F407ZGT6生成另一个工程。两个工程都用了HAL库但HAL库的底层驱动如stm32f1xx_hal_rcc.c和stm32f4xx_hal_rcc.c实现完全不同。如果所有源码都平铺在根目录IAR在编译时无法区分哪个RCC文件该给哪个芯片用极易出现符号重定义或函数未声明的错误。而CoreGroup的存在本质是一个作用域隔离层。CubeMX在生成IAR工程时会把所有与芯片型号强绑定的文件如启动文件startup_stm32f103xb.s、HAL库初始化代码system_stm32f1xx.c、以及Drivers/STM32F1xx_HAL_Driver/Src下的所有.c文件全部放进Core/Src把所有芯片无关的通用配置如Core/Inc里的main.h、stm32f1xx_hal_conf.h和用户自定义代码Core/Src/main.c单独归类。更重要的是IAR的.ewp项目文件里Core/Src路径被设为绝对路径引用而Core/Inc则被设为相对路径包含。这样当你把整个工程文件夹复制到另一台电脑只要CubeMX生成的Drivers目录结构不变IAR就能准确定位到正确的HAL源码。实测中我曾故意删除CoreGroup把所有文件拖到根目录编译能通过但烧录后LED不亮。调试发现HAL_RCC_OscConfig()函数返回HAL_ERROR追查下去是RCC_OscInitStruct.OscillatorType字段被意外覆盖。原因正是平铺后IAR编译器在预处理阶段把stm32f1xx_hal_rcc.h和stm32f4xx_hal_rcc.h两个头文件都纳入了搜索路径由于#include stm32f1xx_hal_rcc.h语句没加路径前缀编译器优先找到了F4版本的头文件因为F4的HAL库在Drivers目录下排得更靠前导致F1芯片的寄存器定义错乱。而保留Core结构IAR的Include路径只指向Core/Inc和Drivers/STM32F1xx_HAL_Driver/Inc彻底规避了头文件污染。注意如果你坚持要扁平化结构唯一安全的做法是——在IAR的“Options” → “C/C Compiler” → “Preprocessor” → “Additional include directories”里把Drivers/STM32F1xx_HAL_Driver/Inc的路径手动加到最前面并确保Drivers/STM32F4xx_HAL_Driver/Inc不在列表中。但这违背了CubeMX的设计哲学后续升级CubeMX或更换芯片时你得手动维护这个路径列表极易出错。3. IAR工程里那个神秘的“__weak”函数到底是谁在调用HAL库的弱定义机制如何影响你的中断处理逻辑打开stm32f1xx_it.c你会看到一堆形如void NMI_Handler(void) __weak、void HardFault_Handler(void) __weak的函数声明。CubeMX生成的代码里这些函数体都是空的只有一行while(1);。很多教程告诉你“这是中断服务函数的弱定义你只需要在main.c里重新写一遍同名函数编译器就会自动替换掉这个弱定义。”——这话没错但没说清谁在调用它们以及为什么必须用__weak。真相是这些__weak函数是IAR链接器ilinkarm.exe在生成最终.out文件时从startup_stm32f103xb.s汇编启动文件里调用的。打开启动文件你会看到类似这样的汇编代码DCB HardFault_Handler DCB MemManage_Handler DCB BusFault_Handler这里的DCB指令是把HardFault_Handler这个符号的地址写进中断向量表。而链接器在解析这个符号时会优先选择非弱定义的版本。如果你在main.c里写了void HardFault_Handler(void){ while(1); }链接器就用你写的如果你没写链接器就退而求其次用stm32f1xx_it.c里那个__weak版本。这就是弱定义的核心价值提供兜底实现避免链接失败。但问题来了CubeMX默认生成的stm32f1xx_it.c里HAL_GPIO_EXTI_IRQHandler()这类外设中断函数也是__weak的。这意味着如果你在main.c里写了void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)它会被正确调用但如果你不小心写成了void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)这是HAL库提供的回调函数不是中断服务函数IAR编译器不会报错因为HAL_GPIO_EXTI_Callback根本不在中断向量表里它只是一个普通函数没人调用它。结果就是按键按下EXTI中断触发HAL_GPIO_EXTI_IRQHandler()执行用的是CubeMX生成的弱定义版本它内部调用HAL_GPIO_EXTI_Callback()但因为你没重写这个回调它就执行了弱定义版本——也就是什么也不做。我踩过的坑是在CubeMX里配置了GPIO外部中断生成代码后我在main.c里写了void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)以为这样就能响应中断。结果调试发现HAL_GPIO_EXTI_IRQHandler()里断点能停但HAL_GPIO_EXTI_Callback()里断点永远不触发。最后查IAR的map文件才明白HAL_GPIO_EXTI_Callback这个符号在map里显示为UNDEFINED说明链接器根本没把它和任何代码段关联起来。真正的调用链是硬件中断 →startup_stm32f103xb.s→HAL_GPIO_EXTI_IRQHandler()弱定义→HAL_GPIO_EXTI_Callback()弱定义空函数。所以要让回调生效你必须在main.c里显式调用HAL_GPIO_EXTI_Callback()或者更规范的做法——在HAL_GPIO_EXTI_IRQHandler()里手动调用你的处理逻辑。实操技巧在IAR里快速定位中断函数调用关系右键点击HAL_GPIO_EXTI_IRQHandler→ “Find All References”它会列出所有调用位置。你会发现除了startup_stm32f103xb.s还有stm32f1xx_hal_gpio.c里的HAL_GPIO_Init()函数里有一处调用。这说明HAL库的初始化函数会把你的回调函数指针注册进去但前提是你的回调函数名必须严格匹配且不能是__weak修饰的。因此最佳实践是在main.c里定义void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)然后在MX_GPIO_Init()之后手动调用HAL_GPIO_EXTI_Callback(GPIO_PIN_0)来触发一次验证逻辑是否正确。4. CubeMX导出IAR工程后为什么串口printf输出乱码HAL库的重定向机制与IAR标准库的隐性冲突用CubeMX配置好USART1生成IAR工程照着教程在main.c里加了printf(Hello World\r\n);结果串口助手上显示的是一堆问号或方块。检查波特率、数据位、停止位都没问题甚至用逻辑分析仪抓波形发现发送的数据帧完全正确。问题出在IAR的C库实现上。IAR的__write函数负责重定向printf输出默认是把字符写入stdout而stdout在嵌入式环境下通常映射到一个叫__stdout的FILE结构体。CubeMX生成的main.c里HAL_UART_Transmit()函数是阻塞式的它需要一个UART_HandleTypeDef句柄。但__write函数的签名是size_t __write(int handle, char * buffer, size_t size)它根本不认识UART_HandleTypeDef。所以IAR的默认__write实现其实是把数据丢进一个叫__io_putchar()的底层函数里而这个函数在IAR的标准库里是空的——它等着你去重写。很多教程教你在main.c里加一个int fputc(int ch, FILE *f)函数里面调用HAL_UART_Transmit(huart1, (uint8_t*)ch, 1, HAL_MAX_DELAY)。这看起来很完美但实测会卡死。原因在于HAL_UART_Transmit()内部会调用HAL_GetTick()获取超时时间而HAL_GetTick()依赖SysTick中断。如果fputc被频繁调用比如printf(Hello %d, i);里有多个字符每次调用都触发一次HAL_UART_Transmit()而HAL_UART_Transmit()的HAL_MAX_DELAY参数会让它一直等待发送完成。如果此时SysTick中断被更高优先级的中断屏蔽比如你在NVIC里把USART中断优先级设得比SysTick高HAL_GetTick()就永远不更新HAL_UART_Transmit()无限等待整个系统卡死。解决方案是绕过HAL库直接操作USART寄存器。在main.c顶部加一个轻量级的__io_putcharint __io_putchar(int ch) { // 等待TXE标志置位发送寄存器为空 while (!(USART1-SR USART_SR_TXE)); // 写入数据 USART1-DR (uint8_t)ch; return ch; }这段代码不依赖HAL库不调用任何中断相关函数纯寄存器操作耗时极短几十纳秒。它直接把字符塞进USART1-DR由硬件自动完成发送。IAR的printf底层会自动调用__io_putchar无需修改fputc。但这里有个陷阱CubeMX生成的MX_USART1_UART_Init()函数里默认启用了huart1.Init.HwFlowCtl UART_HWCONTROL_NONE;即不启用硬件流控。而__io_putchar的轮询方式要求发送缓冲区必须足够快地清空。如果波特率设得太高比如3M波特率而晶振精度不够TXE标志可能延迟置位导致while循环卡住。实测下来对于STM32F103C8T6内部RC振荡器8MHz波特率不超过115200是安全的如果要用更高波特率必须在CubeMX里勾选“Use External Clock”并外接8MHz晶振否则__io_putchar会不可靠。经验总结在IAR工程里调试串口输出第一步永远是用HAL_UART_Transmit()发一个固定字符串如AT\r\n确认硬件通信正常第二步再启用printf重定向。如果重定向后乱码先检查__io_putchar是否被正确链接——在IAR的“Project” → “Options” → “Linker” → “Config”里确认“Override default library initialization”没被勾选否则IAR会跳过你的__io_putchar用默认的空实现。5. 如何让CubeMX生成的IAR工程支持FreeRTOSHAL库的时基源与SysTick的“双重占用”冲突怎么解在CubeMX里勾选“Middleware” → “FreeRTOS”生成IAR工程后编译能通过但烧录运行osDelay(1000)永远不返回LED灯不闪烁。用调试器看程序卡在vTaskStartScheduler()里xPortStartScheduler()函数内部死循环。这不是FreeRTOS配置错误而是HAL库和FreeRTOS对SysTick中断的争夺战。CubeMX生成的main.c里HAL_Init()函数会调用HAL_InitTick(TICK_INT_PRIORITY)这个函数的作用是配置SysTick定时器让它每1ms产生一次中断并在中断服务函数里调用HAL_IncTick()从而让HAL_GetTick()能正确计数。而FreeRTOS的vTaskStartScheduler()也会调用xPortStartScheduler()后者同样会配置SysTick让它按configTICK_RATE_HZ默认1000Hz频率中断并在中断里调用xPortSysTickHandler()进行任务调度。问题在于两个框架都想独占SysTick但SysTick只有一个。HAL库的HAL_IncTick()和FreeRTOS的xPortSysTickHandler()不能同时运行。CubeMX默认生成的代码把HAL_InitTick()放在main()开头而FreeRTOS的调度器在main()末尾启动结果就是SysTick被HAL库初始化后FreeRTOS再初始化时会覆盖HAL库的配置导致HAL_GetTick()永远不增加所有基于HAL的超时函数如HAL_UART_Transmit()的HAL_MAX_DELAY全部失效。官方给出的解决方案是在CubeMX的“Configuration”页签里找到“SYS” → “Timebase Source”把下拉菜单从“SysTick”改成“TIM1”或“TIM2”。这样HAL库就不再用SysTick改用一个通用定时器如TIM2来提供1ms时基。具体操作是在CubeMX里选中TIM2开启“Counter Mode”为“Up”设置Prescaler为7199假设系统时钟72MHzCounter Period为999这样TIM2每1ms溢出一次触发中断在中断里调用HAL_IncTick()。而SysTick则完全交给FreeRTOS使用。但这个方案有代价TIM2被占用后你就不能再用它做PWM或输入捕获。更优雅的做法是让HAL库和FreeRTOS共享SysTick。方法是在main.c里注释掉HAL_Init();后面的HAL_InitTick(TICK_INT_PRIORITY);调用然后在MX_FREERTOS_Init()函数里把FreeRTOS的xPortSysTickHandler()重定向为HAL库的HAL_IncTick()void xPortSysTickHandler(void) { HAL_IncTick(); // 这里不要调用HAL_SYSTICK_IRQHandler()因为FreeRTOS有自己的调度逻辑 }同时在FreeRTOSConfig.h里把configUSE_TICK_HOOK设为1并在main.c里实现void vApplicationTickHook(void)在里面调用HAL_IncTick()。这样SysTick中断只触发一次既更新HAL的tick又触发FreeRTOS调度一举两得。关键提醒无论采用哪种方案都必须在CubeMX的“Project Manager” → “Code Generator”里勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”。否则MX_TIM2_Init()等函数会被合并到main.c里导致TIM2的初始化代码和FreeRTOS的初始化代码耦合难以维护。分离成独立的.c/.h文件后你可以自由修改tim.c里的中断服务函数而不影响main.c的主体逻辑。6. IAR工程里那些“灰色”的头文件路径是怎么来的CubeMX的Driver路径管理机制深度拆解在IAR的“Project” → “Options” → “C/C Compiler” → “Preprocessor” → “Additional include directories”里你会看到一长串路径其中大部分是灰色的比如..\Drivers\STM32F1xx_HAL_Driver\Inc\Legacy、..\Middlewares\ST\STM32_USB_Device_Library\Core\Inc。这些灰色路径表示它们被IAR识别为“已存在但未被当前项目主动引用”。很多人会下意识地把这些灰色路径删掉觉得“反正没用”。结果编译时报错#include usb_core.h not found。这些灰色路径是CubeMX在生成IAR工程时根据你勾选的中间件Middleware和外设Peripherals自动推导出的依赖路径。以USB Device为例你在CubeMX里勾选“Middleware” → “USB Device”CubeMX就知道你需要usb_core.h而这个头文件位于Middlewares/ST/STM32_USB_Device_Library/Core/Inc目录下。但它不会把这个路径硬编码进.ewp文件而是写进一个叫iar_settings.xml的隐藏配置文件里。IAR在加载项目时会读取这个XML文件把所有依赖路径都列出来但只把当前项目里实际#include了的路径设为“激活”黑色其余设为“灰色”。灰色路径的价值在于前瞻性兼容。比如你当前项目只用到了USB Core没用到CDC类设备所以Middlewares/ST/STM32_USB_Device_Library/Class/cdc/Inc路径是灰色的。但当你在CubeMX里勾选“USB Device” → “CDC”后CubeMX重新生成工程它会把CDC的头文件路径从灰色变成黑色并在usbd_cdc_if.c里自动加入#include usbd_cdc.h。如果没有这些灰色路径做基础每次添加新功能都要手动加路径效率极低。更关键的是灰色路径是跨芯片移植的基石。假设你把STM32F103的工程复制一份准备移植到STM32F407。你只需用CubeMX打开新的.ioc文件把芯片型号改成F407然后点击“Generate Code”。CubeMX会自动更新所有路径Drivers/STM32F1xx_HAL_Driver变成Drivers/STM32F4xx_HAL_DriverMiddlewares/ST/STM32_USB_Device_Library的路径保持不变因为USB库是跨系列的而Middlewares/Third_Party/FatFs这类第三方库的路径则根据你是否勾选FatFs来决定是否保留。这一切都依赖于灰色路径的“占位符”机制。实操避坑如果你手动删除了灰色路径又想恢复最简单的方法不是重装CubeMX而是打开CubeMX进入“Project Manager”页签点击右下角“Advanced Settings”在弹出窗口里勾选“Show all available middleware and driver paths”然后点击“Regenerate Code”。CubeMX会重新扫描所有已安装的中间件包把缺失的灰色路径补全。这个操作比手动一个个加路径快十倍而且保证路径绝对准确——因为CubeMX知道每个中间件包的真实安装位置而你手动加的路径可能拼写错误或指向旧版本。7. CubeMX导出IAR工程后如何快速定位某个外设的初始化代码在哪个文件HAL库的代码生成规则图谱刚拿到CubeMX导出的IAR工程面对几十个.c文件新手常问“我想改USART1的波特率该去哪个文件里改”答案看似简单——main.c里的MX_USART1_UART_Init()函数。但如果你深入看会发现MX_USART1_UART_Init()里调用了HAL_UART_Init(huart1)而huart1这个结构体的初始化是在uart.c里完成的。更复杂的是uart.c里又包含了stm32f1xx_hal_uart.c而后者又依赖stm32f1xx_hal_rcc.c来使能USART1的时钟。CubeMX的代码生成遵循一套严格的分层命名规则掌握它你就能在3秒内定位任意外设的初始化入口顶层初始化函数全部在main.c里函数名格式为MX_PeripheralName_Function_Init()。例如MX_GPIO_Init()、MX_USART1_UART_Init()、MX_TIM2_Init()。这是你修改外设参数如波特率、预分频值的第一站。外设句柄定义在PeripheralName.c文件里如gpio.c、usart.c、tim.c。这里定义了UART_HandleTypeDef huart1、TIM_HandleTypeDef htim2等全局句柄并在HAL_PeripheralName_MspInit()函数里完成底层硬件配置如时钟使能、引脚复用、NVIC配置。这是你修改硬件资源如换引脚、改中断优先级的地方。HAL库驱动在Drivers/STM32Fxxx_HAL_Driver/Src目录下文件名格式为stm32fxxx_hal_PeripheralName.c如stm32f1xx_hal_uart.c。这里实现了HAL_UART_Init()、HAL_UART_Transmit()等标准API。除非你要深度定制协议如修改DMA传输模式否则一般不碰这里。时钟与系统配置在system_stm32fxxx.c里。SystemClock_Config()函数在这里它由MX_GPIO_Init()之前的HAL_Init()调用。这是你调整系统主频、PLL倍频系数的地方。这套规则的精妙之处在于解耦。比如你想把USART1从PA9/PA10换成PB6/PB7你只需要在main.c的MX_USART1_UART_Init()里把huart1.Instance USART1;保持不变在usart.c的HAL_UART_MspInit()函数里把__HAL_RCC_GPIOA_CLK_ENABLE()改成__HAL_RCC_GPIOB_CLK_ENABLE()把GPIO_InitStruct.Pin GPIO_PIN_9 | GPIO_PIN_10;改成GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7;把GPIO_InitStruct.Alternate GPIO_AF7_USART1;保持不变在CubeMX里重新配置引脚生成代码对比差异即可。而不用去动stm32f1xx_hal_uart.c里任何一行代码。这就是CubeMX“配置即代码”的设计哲学把硬件抽象层HAL和应用逻辑main.c彻底分开让开发者只关注业务不纠结寄存器。终极技巧在IAR里按CtrlShiftF打开全局搜索输入MX_USART1第一个结果一定是main.c里的初始化函数输入HAL_UART_MspInit第一个结果一定是usart.c里的硬件层初始化。这是比翻文件更快的定位方式。记住CubeMX生成的所有函数名都严格遵循MX_外设功能和HAL_外设_MspInit的命名约定绝不会出现例外。8. 为什么IAR工程里有些变量在调试时看不到值CubeMX的优化等级与调试信息的隐形博弈用IAR调试CubeMX生成的工程设置断点在main()函数里想看huart1.Init.BaudRate的值但调试窗口显示“ ”。检查IAR的“Project” → “Options” → “C/C Compiler” → “Optimization”发现Level是“Low (-Ol)”。按理说低优化应该能看到所有变量为什么还是不行根源在于CubeMX生成的main.c里huart1是一个全局变量定义在usart.c文件顶部UART_HandleTypeDef huart1;而huart1.Init.BaudRate的赋值是在MX_USART1_UART_Init()函数里完成的huart1.Init.BaudRate 115200;问题出在IAR的链接器行为上。当IAR编译器看到一个全局变量被定义但它的初始化值如BaudRate 115200是在另一个函数里动态赋值的它会把huart1放在.bss段未初始化数据段而不是.data段已初始化数据段。.bss段在程序启动时被清零而MX_USART1_UART_Init()函数在main()里才被调用所以在断点打在main()开头时huart1.Init.BaudRate确实还是0调试器自然显示“ ”——因为它还没被赋值。解决方案有两个强制初始化在usart.c里把huart1的定义改成UART_HandleTypeDef huart1 { .Init { .BaudRate 115200, .WordLength UART_WORDLENGTH_8B, .StopBits UART_STOPBITS_1, .Parity UART_PARITY_NONE, .Mode UART_MODE_TX_RX, .HwFlowCtl UART_HWCONTROL_NONE, .OverSampling UART_OVERSAMPLING_16 } };这样huart1就被放在.data段调试器在main()开始前就能看到它的初始值。调整断点位置把断点打在MX_USART1_UART_Init()函数的HAL_UART_Init(huart1);这一行之后此时huart1.Init.BaudRate已经被赋值调试器能正常显示。但更深层的问题是CubeMX默认生成的代码为了节省Flash空间把所有外设句柄都定义为未初始化全局变量这是嵌入式开发的常规做法。而IAR的调试信息生成默认只对.data段变量生成完整的DWARF调试符号对.bss段变量只生成类型信息不生成初始值。所以当你看到“ ”其实不是变量不存在而是调试信息不完整。高级调试技巧在IAR的“Project” → “Options” → “C/C Compiler” → “Debug”里勾选“Generate debug information for uninitialized data”然后重新编译。这时即使huart1在.bss段调试器也能显示它的地址和内容。但要注意这会增大.out文件体积量产时应关闭此选项。日常开发调试建议用方案1强制初始化既保证调试便利又不影响最终代码大小——因为初始化值会被编译器优化进.data段而.data段在烧录时是必须的不会额外增加Flash占用。