恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
小内存STM32开发实战:从16KB Flash中挤出空间
首页
资讯中心
/
小内存STM32开发实战:从16KB Flash中挤出空间
小内存STM32开发实战:从16KB Flash中挤出空间
发布时间:2026/8/27 20:35:29
最近帮朋友评估一个温湿度采集小项目原方案用的是老经典STM32F103C8T664KB Flash、20KB RAM实际固件占不到20KBRAM用了3KB左右。我说你这不是杀鸡用牛刀嘛换成STM32C011F6试试16KB Flash、6KB RAM封装还小成本肉眼可见往下降。他第一反应和大多数人一样这么点内存能跑得动吗这大概是我这几年听过的关于“New STM32 Microcontrollers in Small Memory Sizes”这波产品最常见的问题。STM32近几代产品线里G0系列、C0系列、L0系列的低容量版本Flash普遍在16~64KBRAM在2~12KB之间。主频不高外设精简但便宜、省电、封装小、供货稳定。很多场景下它们才是真正适合量产的芯片而不是那个“买回来先用一半容量再说”的大内存型号。这篇文章不准备讲太多空泛的选型道理就结合我在C0和G0这些芯片上实际做过的几个小项目把选型、开发环境、Flash和RAM优化技巧、常见坑都捋一遍。适合刚入门想用最低成本把东西跑起来的同学也适合产品化阶段被成本压着、想把方案做“瘦”的工程师。1. 先搞清这批“小内存STM32”到底是谁1.1 C0、G0、L0的低容量版本盘点很多人提到小内存STM32第一反应还是十几年前的STM32F103C6T632KB Flash或者老掉牙的STM8。实际上ST这几年真正在小内存市场发力的是三条线。第一条是STM32C0系列。这是2022年前后推向市场的产品Cortex-M0内核主频最高48MHz。C011这个型号比较典型Flash 16KB、RAM 6KB封装能到TSSOP20甚至更小。往上一点有C031Flash最大32KB、RAM在12KB左右。C0系列定位很直接替代老的F0系列和一部分8位机市场价格压得很低外设保留了I2C、SPI、USART、ADC、定时器这些常用模块还带了触摸检测功能。第二条是STM32G0系列。G0比C0发布早一点也是M0内核主频可以到64MHz低容量型号如G030、G031Flash 16~32KBRAM 8KB左右。G0系列的外设比C0更全比如USB、CAN、DAC、比较器等在高配型号里能见到内部还带一个额外的12位ADC和温度传感器。如果你需要在小芯片上同时用几个串口或者带USBG0会比C0从容一些。第三条是STM32L0系列。L0是超低功耗的老牌选手同样M0内核但主频只有32MHz。低容量型号如L011Flash 16KB、RAM 2KB数据缓存也极其紧张。L0的核心优势是休眠功耗做得非常低配合小容量Flash适合电池供电的传感器节点、遥控器、门磁、检测探针这类场景。代价就是RAM实在太紧写代码时抠得最狠。我给这两张常见型号的对比表大家选型时可以直接对着看型号内核主频FlashRAM典型场景STM32C011M048MHz16KB6KB低成本家电面板、传感器节点STM32C031M048MHz32KB12KB左右遥控器、小型控制器STM32G031M064MHz32KB8KB带通信外设的小系统STM32L011M032MHz16KB2KB电池供电低功耗采集1.2 内存小不等于落后这类芯片为什么值得认真玩很多工程师对小内存芯片有刻板印象觉得配置这么寒酸能用在哪实际上小内存型号出货量非常大。原因无非几个方面。成本是第一驱动力。同样的功能C011和F103之间的差价在小批量可能感觉不明显但在年出货几十万片的产品里乘以一个系数就很可观了。而且C0还有一个好处内部时钟也能用省掉外部晶振BOM又能少两三个元件。功耗也是硬指标。大芯片即使闲置也会比小芯片多耗电而C0、G0、L0的休眠电流和运行电流都明显低于F1时代的产品。电池供电场景里这些差距会直接决定产品续航。封装和布线同样重要。TSSOP20甚至更小的封装在主板空间紧张时非常吃香。有些消费类产品PCB控制在指甲盖大小放一个LQFP48的F103根本不可能但是放一个TSSOP20的小芯片就很从容。第三点是可靠性。大内存芯片容易掩盖代码写得不讲究的问题——缓冲区开大、随便用全局数组、中断里做耗时的操作反正资源够。但小内存芯片逼着你精简代码、约束行为反而让固件更稳定、故障率更低。这一点我在后面几章会反复强调。2. 动手之前先算账选型与工程预算2.1 用CubeMX和map文件把资源算明白小内存芯片最忌讳看到型号就直接建工程写代码等写了一大半再去加功能结果Flash不够重新换芯片重画板子那成本就翻了。正确做法是先用表格把功能需求列清楚几个串口、几个I2C、几路ADC、是否需要PWM、是否要休眠唤醒、代码大概会有多少逻辑。之后最有效的资源预估方法是走一遍CubeMX。在STM32CubeMX里选好型号、配好时钟和外设生成一个最小工程编译一次从编译器输出或者map文件里看基础占用。以STM32CubeIDE为例编译完成后Console窗口能看到类似这样的信息Memory region Used Size Region Size %age Used FLASH: 12344 B 16 KB 75.34% RAM: 2048 B 6 KB 33.33%FLASH那一行显示的是代码加只读数据RAM对应的行显示RW-data加ZI-data。RW-data是已经初始化的全局变量ZI-data是未初始化的全局变量和栈。注意小芯片上这两个区域都要卡得比较紧建议留30%以上的余量给后期的bug修复和功能调整。把一个空工程都跑不通再去找原因远比一开始就选对芯片省时间。如果你用的是Keil编译后在Build Output窗口里也能看到Program Size: Code... RO-data... RW-data... ZI-data...。其中Code加RO-data决定了占用的Flash大小RW-data加ZI-data决定了占用的RAM大小。RO-data有点迷惑性它虽然叫只读数据但其实是放在Flash里的常量表比如const数组和字符串。2.2 封装、引脚和外设取舍小内存芯片一般封装引脚也少TSSOP20是常态还有更小的。引脚紧张时每个引脚都要精打细算。SWD调试至少占两个引脚一个UART占两个一个I2C占两个一个ADC输入占一个一个LED输出占一个这样已经十个引脚出去了。如果需要外部晶振又得占两个引脚。所以外设能省就省能做软件模拟的尽量模拟能复用复用。引脚复用表是小内存芯片使用者的好朋友。C0和G0内部有灵活的AF映射同一个外设的引脚往往有多个可选位置。比如一个I2C可以放在PA9/PA10也可以放在PB6/PB7。选型配置引脚时别只看CubeMX的默认分配打开数据手册的Alternate Function表把引脚尽量错开方便后续PCB走线。我见过不少新手用CubeMX自动分配结果两个功能挤在同一个引脚上又不知道怎么改最后只能把PCB绕得乱七八糟。时钟的选择也影响引脚数量。C0、G0、L0内部都有HSI时钟很多项目用内部时钟就够了省掉外部晶振和两个引脚。但要注意内部时钟精度不如外部晶振如果做串口通信且对波特率误差比较敏感高温环境下还是建议接一个外部晶振稳妥。这个分寸要在项目开始时定好等到PCB打样再改很麻烦。2.3 开发环境怎么搭CubeIDE、Keil还是VSCodeC0、G0、L0这几个系列都是较新的产品老的“标准外设库”在新芯片上早就用不了了全线走HAL和LL库。所以开发工具围绕这两套库来选。STM32CubeIDE是ST官方的免费IDE集成CubeMX图形化配置和GCC编译链对新手最友好。用CubeMX配置完引脚和时钟直接生成代码加自己的逻辑进去烧录调试一套搞定。缺点是IDE本身比较重启动慢对老电脑不太友好。Keil MDK在中小公司和学校用得最多工程模板多很多老教程都是基于Keil的。Keil的编译优化选项直观在线调试速度快。缺点是许可证比较麻烦社区版有代码大小限制对GCC新特性的支持也滞后。不过写STM32的大部分人用Keil确实能快速上手。VSCode加CMake加arm-none-eabi-gcc这套组合近几年讨论度很高。CubeMX可以直接生成Makefile工程然后在VSCode里装C/C插件和Cortex-Debug插件用ST-Link做调试。这套方案的好处是轻量、免费、Git友好适合喜欢把工程文件自己掌握的人。缺点是需要手动配置一些环境对新手有一定门槛。我的建议很简单新手老老实实从STM32CubeIDE开始界面里能看到CubeMX、编译、调试三合一少踩环境配置的坑。老手在团队协作或需要脚本化构建时再上VSCode和CMake。3. Flash紧张教你从16KB里挤出空间3.1 该上LL库还是HAL库体积和效率的权衡在16KB甚至更小的Flash上库的选择直接决定项目能不能装得下。HAL库的优点是通用抽象写起来不用管寄存器细节但代价是函数调用层级深配置流程冗长空工程占用的Flash明显偏大。有些HAL空工程编译出来能到8KB以上对64KB的大芯片来说无所谓对16KB的小芯片就是半壁江山被占了。LL库Low-Layer库是ST提供的轻量级库API更接近寄存器操作函数实现简短甚至很多是inline的编译后体积小、执行也快。CubeMX里可以在每个外设的配置页面单独选择HAL还是LL生成代码时会分别生成对应前缀的函数。我的实际感受是用C011跑一个小应用只开一个UART加一个I2C用HAL库优化后大概要9~10KB Flash改用LL库可以压到5KB以内。两者都没有额外加业务逻辑的情况下差距明显。所以小内存项目里我推荐初始化代码用HAL或CubeMX生成方便配置时钟和引脚业务逻辑里对性能有要求或频繁调用的函数直接用LL库。两种库可以混用只要别搞混句柄就行。如果你对寄存器操作足够熟悉还有一条更激进的路直接跳过HAL和LL用寄存器裸写。CubeMX仍保留生成SystemInit和时钟配置的功能之后外设初始化自己写。这个方式对维护不友好但确实能榨出不少空间。我一般只在启动引导或者对延时要求特别高的函数里这么干。3.2 编译器优化与启动配置能省出三分之一同样是代码不同优化选项出来的体积能差很多。Keil里在Options for Target的C/C页签下选-Oz或-OsGCC环境下在CMake或Makefile里加-Os和-ffunction-sections -fdata-sections -Wl,--gc-sections。-Os是优化体积不牺牲太多速度-ffunction-sections把每个函数放到独立段再配合--gc-sections把没被引用的函数和数据段从最终镜像里清除。打开这些选项后很多人会惊讶地发现代码体积直接缩了三分之一甚至更多。代价有两点一是-Os可能让代码运行顺序和源码不一致调试时单步会跳来跳去部分变量在watch窗口里看不到值。二是如果某个函数因为优化被错误裁剪排查起来比较头大。解决办法是在关键函数上加优化等级控制GCC用__attribute__((optimize(O0)))Keil用#pragma O0。启动配置方面在Keil工程里勾选Target页签的Use MicroLIB就能显著减小printf等标准库函数的体积。GCC环境则在链接参数里加--specsnano.specs。这个选项把C标准库换成了精简版很多函数体积小了很多基本不影响普通嵌入式代码。注意精简库的printf不支持浮点格式化所以别写%f我会在下一节细讲。3.3 别再让printf吃掉你的Flash调试时往串口发信息非常方便但printf这个函数全家桶在小内存芯片上是个“空间吞金兽”。即使不开浮点精简库里的printf仍然会拉进来数百字节到数KB的格式处理代码。如果你写printf(temp: %.2f\n, temp);那就更热闹了浮点格式化和浮点转字符串的代码全被链接进来Flash占用瞬间多出几KB。在小内存工程里我通常不直接依赖printf而是自己写一个极简的字符串发送函数配合整数和定点数打印。比如温度值用整数部分和小数部分分开打void send_string(const char *s) { while (*s) { while (!LL_USART_IsActiveFlag_TXE(USART2)); LL_USART_TransmitData8(USART2, *s); } } void send_temp(int16_t temp_x10) { char buf[12]; int i 0; int int_part temp_x10 / 10; int frac_part temp_x10 % 10; if (frac_part 0) frac_part -frac_part; buf[i] 0 (int_part / 10) % 10; buf[i] 0 int_part % 10; buf[i] .; buf[i] 0 frac_part; buf[i] \0; send_string(buf); }这个函数省去了所有标准库格式化代码逻辑也完全可控。串口发送多打几个字符并不会让人感知到延迟但Flash空间实实在在省下来了。我在小内存项目里已经习惯了“能不碰printf就不碰”的原则包括在HAL库环境下默认重定向printf的fputc也尽量不写除非项目不缺Flash。3.4 链接脚本和标准库裁剪的其他套路链接脚本是小内存优化的最后一道闸门。GCC工程里的.ld文件定义了FLASH和RAM的起点和长度比如MEMORY { FLASH (rx) : ORIGIN 0x00000000, LENGTH 16K RAM (xrw) : ORIGIN 0x20000000, LENGTH 6K }如果你的芯片Flash比CubeMX默认模板大或者小需要手动修改LENGTH否则要么烧不进去要么程序跑飞。还有一种情况是芯片带着Bootloader应用程序的起始地址要偏移同时在系统里要重新设置中断向量表链接脚本和启动代码都要对应改。堆和栈的大小也是优化点。启动文件的Stack_Size默认是0x400也就是1KB对小RAM芯片来说有点偏大。如果函数嵌套不深、中断不复杂可以把Stack_Size压到0x200甚至0x100。Heap_Size如果不打算用malloc直接设成0能省下RAM空间。这样能在6KB RAM的小芯片上多出几百字节给全局变量。最后还有一个很多人忽略的点CubeMX生成工程时会包含初始化每个外设的HAL代码如果你只用了UART和I2C但CubeMX默认勾选了GPIO、DMA、CRC等模块这些初始化代码也会占Flash。生成工程后把用不到的模块代码从编译列表里去掉或者直接用CubeMX禁用体积会明显下降。4. RAM也紧张小内存芯片的运行时生存术4.1 数据都放进Flashconst和查表法RAM小最直接的应对思路就是让RAM只放真正需要频繁修改的数据。像传感器校准表、OLED字模、sin查表、固定字符串、错误码对照表统统用const修饰这样编译器会把它们放到Flash区不占RAM。我见过不少人写OLED显示代码时把一个16x16的汉字字模数组定义为普通的uint8_t char_map[]这下好了所有字模不仅要占Flash的空间还要在启动时复制一份到RAM。如果你显示10个汉字每个32字节那RAM就被白占320字节。对6KB RAM的芯片来说这点空间可能比想象中珍贵。改成const uint8_t char_map[]RAM立刻释放。查表法也是小内存芯片的好帮手。比如做温度传感器的非线性校准没必要用浮点公式拟合放一张查表数组用线性插值就能算出结果。即使表比较大放在Flash里也只是增大Flash占用对RAM毫无压力。4.2 不用malloc用静态缓冲和复用设计小内存芯片上malloc和free是灾难的代名词。堆大小不好预估、分配后的内存碎片无法避免、调试时很难定位到底哪块内存泄漏了。嵌入式裸机场景里我几乎不用动态内存。更合适的做法是预先分配好最大需要的内存缓冲区通过状态机或标志位在不同的时间复用同一块内存。例如一个项目既要通过串口接收指令又要解析后更新OLED显示接收缓冲区和显示缓冲区可以共用一块内存在一个时刻只让其中一个“拥有”缓冲区。这种设计需要严格管理时序但能大幅降低RAM峰值占用。用union也可以实现复用typedef union { uint8_t uart_rx_buf[128]; uint8_t oled_frame_buf[128]; } shared_buf_t; static shared_buf_t shared_buf;这段代码的好处是两块缓冲区在物理上占用同一块RAM实际消耗从256字节变成128字节。需要接收串口数据时用uart_rx_buf需要刷新显示时用oled_frame_buf只要逻辑上错开就行。4.3 串口不定长接收的缓冲方案怎么选串口接收不定长数据是嵌入式开发里特别常见的需求但在小内存芯片上缓冲方案的选择会直接影响RAM开销。最简单的方案是单字节中断加环形缓冲区。每收到一个字节就进一次中断把数据塞入一个静态数组主循环再按帧格式解析。这种方案CPU占用高一点但RAM占用很小比如开一个64字节的环形数组就够用。代码也容易理解。#define RX_BUF_SIZE 64 static volatile uint8_t rx_buf[RX_BUF_SIZE]; static volatile uint8_t rx_head 0; static volatile uint8_t rx_tail 0; void USART2_IRQHandler(void) { if (LL_USART_IsActiveFlag_RXNE(USART2)) { uint8_t data LL_USART_ReceiveData8(USART2); uint8_t next (rx_head 1) % RX_BUF_SIZE; if (next ! rx_tail) { rx_buf[rx_head] data; rx_head next; } } }另一种方案是DMA加空闲中断。DMA自动把数据搬进缓冲区CPU完全不参与空闲中断表示一帧数据接收完毕。这个方案省CPU但缓冲区必须开得足够大否则数据多了会溢出。RAM紧张时我一般优先选环形缓冲方案把RAM留给真正需要的地方。这里有个热搜话题经常提到“stm32串口接收不定长数据”和“stm32 hal库串口空闲中断”在C0/G0这类M0芯片上同样适用只是要注意M0没有DWT单元HAL的空闲中断实现和F1/F4略有区别调试时留意一下就好。4.4 栈与堆的平衡HardFault多半是这里小内存芯片最容易莫名其妙HardFault的元凶一个是栈溢出另一个是数组越界。栈溢出尤其隐蔽因为在启动文件里分配的Stack_Size是固定区域一旦函数调用深度超过这个区域程序就会写到栈外边覆盖掉其他变量表现就是程序跑飞、随机死机、HardFault。一个常见的坑是在函数里定义一个大数组void process_data(void) { uint8_t temp_buffer[512]; // 这行在小RAM芯片上可能直接爆栈 ... }512字节在8KB RAM的芯片里好像不多但如果栈默认只有1KB这个局部数组就把栈吃了一半再叠加几次函数调用溢出在所难免。解决方法是把这种大缓冲区改成static或全局变量或者在链接脚本里适当加大Stack_Size。想准确判断栈够不够用调试时可以停下来观察SP寄存器结合反向追踪函数调用栈看SP离栈底还剩多少距离。如果逼近临界值就要调整栈大小或者减少嵌套调用。小内存芯片上栈宁可小一点也要确保“够用且不溢出”这需要实测不能拍脑袋。5. 实操16KB Flash/6KB RAM跑一个温湿度监测节点5.1 项目需求与外设分配这里用我最近做的一个小东西举例基于STM32C011的温湿度监测节点。功能很朴素I2C接口读取AHT20温湿度传感器0.96寸SSD1306 OLED显示温度、湿度和一个简单的运行状态图标串口每秒钟上报一次数据板上一个LED闪烁表示工作正常。外设需求很简单一个I2C挂AHT20和OLED两个从机一个USART波特率115200数据上报一个普通GPIO驱动LED内部时钟不接外部晶振SWD两个引脚用于烧录和调试。选型用C011F6TSSOP20封装板子画得很小。Flash预算给到13KB以内RAM预算给到4KB以内留出余量。5.2 CubeMX配置要点与关键代码在CubeMX里新建工程芯片选STM32C011F6。RCC配置界面用HSI作为系统时钟源时钟树拉到48MHz。I2C1配置为标准模式100kHz因为AHT20和SSD1306都支持100kHz没必要追求400kHz把稳定性搞低。USART2配置为1152008位数据1位停止位无校验使能接收中断。PA1配置为推挽输出初始电平拉低用来驱动LED。生成代码时工程类型选Makefile或者MDK-ARM取决于你自己用哪个IDE。生成代码后主循环的逻辑大概是这样先初始化DWT不对C011是M0没有DWT。这里用SysTick做延时CubeMX默认会配置SysTick为1ms中断HAL_Delay依赖它。但我在项目里封装了一个自己的延时函数不依赖HAL_Delay减少优先级问题的影响static volatile uint32_t g_tick 0; void SysTick_Handler(void) { g_tick; } void delay_ms(uint32_t ms) { uint32_t start g_tick; while ((g_tick - start) ms) { } }主程序里AHT20的读取逻辑很简单先发送测量命令等待一段时间然后读取6字节数据解算出温度和湿度。OLED显示用SSD1306的I2C驱动显示缓冲区定义成96x8字节放在全局变量里。串口上报数据就调用之前写的send_temp函数。这个工程编译后我实际得到的体积是这个水平指标数值Flash占用12.1KB / 16KBRAM占用3.4KB / 6KB基础空工程Flash8.8KB之后我又做了几个调整打开-Os优化、把HAL库里用不到的模块从工程移除、显示缓冲区里面暂时不显示的页面用const表存储。几次调整后Flash从12.4KB降到11.2KB左右虽然压缩幅度不算巨大但给小项目留出了足够的Bug修复空间。5.3 编译结果、烧录调试与最终资源占用烧录用ST-Link在STM32CubeIDE或Keil里点击下载就行。如果你习惯旧工具也可以直接拖进STM32CubeProgrammer烧录。C0系列没有SWO输出M0内核基本不支持ITM调试信息全靠串口。所以我在代码里特意把上报信息做成可读文本而不是只有机器能解析的原始字节。这样一条串口数据既能用PC端工具看也能接一个USB转串口模块直接观察。调试过程中最常遇到的问题出现过两类。一类是OLED显示偶尔花屏后来发现是I2C时序在48MHz下跑100kHz内部等待时间太短把时序参数里的上升沿和下降沿时间稍微放宽就解决了。另一类是串口上报的数据偶发性乱码排查后是波特率误差过大。因为用的内部HSI时钟高温环境下HSI精度下降导致波特率偏差变高后来把串口改成使用LSI校准或干脆接了一个外部晶振。如果你对波特率要求很严格内部时钟方案要慎重。6. 常见问题速查与避坑心得6.1 从编译到运行的问题排查表小内存芯片碰到的问题很多和大芯片不一样这里整理一个速查表都是我实际踩过的坑现象可能原因处理办法Keil/IDE下载报Cannot access targetSWD引脚被复用或芯片进入低功耗模式按住复位键尝试连接或拉低BOOT0后重新上电再下载程序一直HardFault_Handler栈溢出、数组越界、中断服务函数里执行非法指令用调试器看SP指针和调用栈检查是否爆栈检查数组边界HAL_Delay卡死或延时不准SysTick被其他中断抢占或时钟配置改变了SysTick频率检查NVIC优先级分配尽量避免在中断里调用HAL_Delay或改用SysTick自封装延时串口输出乱码波特率误差大或内部时钟精度不足检查实际时钟频率重新计算波特率寄存器值必要时换外部晶振Flash校验失败或烧录后程序不运行Flash地址偏移设置不对或启动文件链接脚本Flash长度不对检查链接脚本LENGTH值确认芯片型号和烧录地址一致编译后程序体积超限优化没开、printf拉入库、HAL库裁剪不彻底开-Os、用MicroLIB/nano.specs、改用LL库、去掉浮点打印程序运行时RAM被意外改写缓冲区越界写或栈溢出覆盖了其他变量用Memory窗口查看越界地址附近数据检查所有数组写入逻辑多数问题在调试器里都能定位到关键是别蒙着头改先把现象固定下来再根据表格里的排查顺序走一遍。6.2 几个针对小内存芯片的独家小贴士第一利用芯片唯一ID做设备区分。C0、G0、L0系列每颗芯片出厂都带96位唯一ID存放在芯片内部电子签名区域具体地址在各自的参考手册里能找到。我在生产测试中会读取这个ID作为设备序列号烧录到Flash里用于区分同一批次的设备不用额外增加EEPROM芯片。这个思路在很多需要设备身份标识的场景很实用。第二Flash读保护要慎用。量产完成程序后可以打开读保护防止固件被读出来逆向。但打开后调试器可能再也连不上下载新固件前要先解保护。对研发阶段来说不要着急开读保护否则每次调试都会卡在连接阶段。第三小内存芯片的发热和功耗要实际测。不要看数据手册的静态电流指标就以为电池能撑很久内部外设切换、GPIO翻转、传感器供电这些动态消耗才是大头。在产品原型阶段就把功耗实测一遍比事后优化要省很多时间。第四尽量留掉10%以上的Flash余量。16KB的芯片不要用到15.9KB哪怕当前功能全部稳定后期加一个bug修复补丁可能就要几百字节。我在工程规范里给自己定过规矩超过85%的Flash占用就要审视方案是否选错或者功能是否冗余。6.3 写在最后小内存教会我的事玩了好几年小内存芯片我觉得最明显的变化是写代码的习惯变了。以前用大芯片的时候写代码很随意临时状态随手全局变量字符串随便写数组容量按感觉来。换了小内存芯片后每写一个全局变量、每加一个函数都要想一下这块RAM是不是可以复用这个功能能不能用查表代替缓冲区能不能再小一点从这个角度说小内存芯片就像一个严格的教练逼着你学会约束自己。当你把16KB Flash、6KB RAM的芯片用得游刃有余再回到64KB甚至更大的芯片时写出来的代码质量和可靠性都会有明显提升。最后再分享一个我常用的验证思路如果你的产品最终要量产、要控成本早点用目标芯片做开发别用开发板上的大芯片写代码等到移植才发现资源不够那才是真正的心累。