Raspberry Pi Pico 2 性能调优
Raspberry Pi Pico 2 是一款功能强大的微控制器开发板,搭载 RP2350 芯片(双核 Cortex-M33,最高 150MHz,520KB SRAM)。但在资源有限的嵌入式环境中,编写高效的代码仍然至关重要。性能调优 可以帮助你最大限度地利用 Pico 2 的硬件资源,确保程序运行得更快、更稳定,并降低功耗。本文将介绍一些常见的性能调优技巧,并通过实际案例帮助你在 Arduino 风格 (基于 Arduino-Pico 核心)和 C/C++ 风格 (基于官方 Pico SDK)中应用这些技巧。
1. 什么是性能调优?
性能调优是指通过优化代码和硬件配置,提升程序的运行效率,减少资源占用(如内存、CPU 时间、功耗等)。对于 Pico 2 来说,性能调优的目标是让程序在有限资源下运行得更快、更稳定,同时发挥双核与硬件加速单元(如 PIO、浮点单元)的优势。
备注 :性能调优不仅仅是让代码运行得更快,还包括减少内存占用、降低功耗以及提高实时响应能力。
2. 性能调优的基本原则
在开始优化之前,请牢记以下原则:
先正确,后优化 :确保程序功能正确,再考虑性能。
避免不必要的计算 :减少重复计算和冗余操作。
减少内存使用 :优先使用栈(局部变量)或静态分配,避免不必要的全局变量。
优化循环 :将循环中不变的计算外提,使用递减计数等。
选择合适的数据类型 :使用 uint8_t、int16_t 等节省内存并提高缓存效率。
利用硬件特性 :双核、PIO、DMA、浮点单元等可以大幅提升特定任务的性能。
3. 优化代码结构
3.1 减少全局变量的使用
全局变量占用 SRAM 的静态数据段,且在整个程序生命周期内存在。尽量使用局部变量或静态局部变量。
Arduino 风格
1 2 3 4 5 6 7 8 9 10 11 12 int globalCounter = 0 ;void loop () { globalCounter++; }void loop () { static int localCounter = 0 ; localCounter++; }
C/C++ 风格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int global_flag = 0 ;int main () { while (true ) { global_flag = 1 ; } }int main () { static int flag = 0 ; while (true ) { flag = 1 ; } }
提示 :如果多个函数需要共享少量数据,可以封装成模块,通过 getter/setter 访问,而不是直接暴露全局变量。
3.2 避免在循环中进行复杂操作
将循环中不变的计算移到循环外部,可以显著提升效率。
Arduino 风格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void loop () { for (int i = 0 ; i < 100 ; i++) { float result = analogRead (26 ) * 3.3 / 4095.0 ; } }void loop () { const float factor = 3.3 / 4095.0 ; uint16_t raw = analogRead (26 ); float voltage = raw * factor; for (int i = 0 ; i < 100 ; i++) { } }
C/C++ 风格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const float ADC_SCALE = 3.3f / 4095.0f ;int main () { adc_init(); adc_gpio_init(26 ); adc_select_input(0 ); while (true ) { uint16_t raw = adc_read(); float voltage = raw * ADC_SCALE; } }
3.3 使用位运算代替算术运算(针对乘除2的幂)
1 2 3 4 5 6 7 8 9 int x = y * 4 ;int x = y << 2 ;int x = y / 8 ;int x = y >> 3 ;
现代编译器通常会自动优化此类操作,但显式写出有时能帮助编译器生成更高效的代码。
3.4 使用 inline 函数减少调用开销
对于短小且频繁调用的函数,使用 inline 可以消除函数调用开销。
Arduino 风格(C++)
1 2 3 4 5 6 7 inline int square (int x) { return x * x; }void loop () { int a = square (5 ); }
C/C++ 风格
1 2 3 4 5 6 7 static inline int square (int x) { return x * x; }int main () { int a = square(5 ); }
注意 :inline 只是建议,编译器可能忽略。过度内联会增加代码体积。
4. 使用合适的数据类型
选择合适的数据类型可以减少内存占用并提高计算速度。Pico 2 原生支持 32 位整数和单精度浮点硬件加速,但对于小范围数值,使用更小的类型仍然有益。
数据类型
大小(字节)
范围
适用场景
uint8_t
1
0~255
传感器原始字节、小计数器
int16_t
2
-32768~32767
温度、角度等常用范围
float
4
±1.18e-38 ~ ±3.4e38
需要小数运算(硬件加速)
int
4
-2^31~2^31-1
通用,但若范围小则浪费内存
Arduino 风格示例
1 2 3 4 5 int sensorValue = analogRead (26 ); uint16_t sensorValue = analogRead (26 );
C/C++ 风格示例
1 2 3 4 5 int raw = adc_read();uint16_t raw = adc_read();
5. 优化内存使用
Pico 2 的 SRAM 较大(520KB),但合理优化仍然重要,尤其是当程序包含大型缓冲区、图形界面或音频数据时。
5.1 将常量数据存储在 Flash 中(XIP)
Pico 2 支持 XIP(Execute in Place),代码和 const 数据默认存放在 Flash 中,不占用 SRAM。利用这一特性可以释放大量 SRAM。
Arduino 风格
1 2 3 4 5 6 7 8 const char longString[] = "这是一个很长的字符串,存储在Flash中。" ;const uint16_t sineTable[] = {0 , 159 , 318 , 477 , ...};void setup () { Serial.begin (115200 ); Serial.println (longString); }
注意 :Arduino-Pico 核心中,const 全局变量会自动放入 Flash。不需要使用 PROGMEM(但 PROGMEM 也可用,映射为 const)。
C/C++ 风格
1 2 3 4 5 6 7 8 const char message[] = "Hello from Flash" ;const int coefficients[] = {1 , 2 , 3 , 4 };int main () { stdio_init_all(); printf ("%s\n" , message); }
5.2 使用 F() 宏减少字符串内存占用(Arduino 风格)
在打印字符串时,使用 F() 宏可以将字符串常量存储在 Flash 中,而不是在 SRAM 中拷贝一份。
1 2 3 4 void setup () { Serial.begin (115200 ); Serial.println (F ("这个字符串直接存储在Flash中,不占用SRAM" )); }
5.3 避免动态内存分配
动态内存(malloc/free)可能导致碎片和不确定的分配时间。在性能敏感或长时间运行的系统中,尽量使用静态分配或内存池。
6. 利用 Pico 2 的硬件特性
Pico 2 具备许多硬件加速特性,合理利用可以大幅提升性能。
6.1 双核并行处理
将实时性要求高或计算密集的任务放在核心 1 上,主核心处理通信、UI 等。
C/C++ 风格示例(使用 SDK)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include "pico/stdlib.h" #include "pico/multicore.h" void core1_entry () { while (true ) { gpio_put(25 , !gpio_get(25 )); sleep_ms(500 ); } }int main () { stdio_init_all(); gpio_init(25 ); gpio_set_dir(25 , GPIO_OUT); multicore_launch_core1(core1_entry); while (true ) { printf ("Main core running\n" ); sleep_ms(1000 ); } }
Arduino 风格 :可以使用 setup1() 和 loop1() 函数(Arduino-Pico 核心支持)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void setup () { Serial.begin (115200 ); pinMode (25 , OUTPUT); }void loop () { Serial.println ("Main" ); delay (1000 ); }void setup1 () { }void loop1 () { digitalWrite (25 , !digitalRead (25 )); delay (500 ); }
6.2 使用 PIO 卸载时序敏感任务
PIO(可编程 I/O)可以独立于 CPU 执行精确时序协议(如 WS2812、DHT22、自定义串行协议),几乎不占用 CPU 时间。
示例:使用 PIO 驱动 WS2812 LED(C/C++ 风格,需引入 PIO 程序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "pico/stdlib.h" #include "hardware/pio.h" #include "ws2812.pio.h" #define WS2812_PIN 0 int main () { stdio_init_all(); PIO pio = pio0; uint sm = pio_claim_unused_sm(pio, true ); uint offset = pio_add_program(pio, &ws2812_program); ws2812_program_init(pio, sm, offset, WS2812_PIN, 800000 , false ); uint32_t led_data = 0x00FF00 ; while (true ) { pio_sm_put_blocking(pio, sm, led_data); sleep_ms(500 ); } }
Arduino 风格 :可以使用 Adafruit_NeoPixel 库,该库内部可选 PIO 加速(在 Pico 上)。
6.3 使用 DMA 进行高速数据传输
DMA(直接内存访问)可以在不占用 CPU 的情况下在内存和外设之间传输数据。例如,使用 DMA 自动填充 PWM 输出缓冲区、从 ADC 连续采集等。
6.4 启用浮点硬件加速
RP2350 支持单精度浮点硬件运算。在编译时启用 -mfloat-abi=hard(SDK 默认已启用),float 运算会比软件模拟快很多。
7. 实际案例:优化传感器数据读取与显示
假设你正在开发一个环境监测系统,需要从 ADC 读取温度传感器数据并实时更新 LCD 屏幕。以下展示优化前后的对比。
7.1 原始代码(存在效率问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <LiquidCrystal.h> LiquidCrystal lcd (12 , 11 , 5 , 4 , 3 , 2 ) ;void setup () { lcd.begin (16 , 2 ); Serial.begin (9600 ); }void loop () { int raw = analogRead (26 ); float voltage = raw * 3.3 / 4095.0 ; float temperature = voltage * 100.0 ; lcd.setCursor (0 , 0 ); lcd.print ("Temp: " ); lcd.print (temperature); lcd.print (" C" ); delay (1000 ); }
问题 :
delay() 阻塞 CPU
每次循环都重新计算转换因子 3.3/4095.0
频繁调用 LCD 打印,可能刷新过快不必要
7.2 优化后的代码(非阻塞 + 预计算 + 降低刷新率)
Arduino 风格优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <LiquidCrystal.h> LiquidCrystal lcd (12 , 11 , 5 , 4 , 3 , 2 ) ;const float ADC_TO_VOLT = 3.3f / 4095.0f ;unsigned long lastUpdate = 0 ;const unsigned long UPDATE_INTERVAL = 2000 ; void setup () { lcd.begin (16 , 2 ); Serial.begin (115200 ); lcd.print (F ("Initializing..." )); }void loop () { unsigned long now = millis (); if (now - lastUpdate >= UPDATE_INTERVAL) { lastUpdate = now; uint16_t raw = analogRead (26 ); float voltage = raw * ADC_TO_VOLT; float temperature = voltage * 100.0f ; lcd.setCursor (0 , 0 ); lcd.print (F ("Temp: " )); lcd.print (temperature, 1 ); lcd.print (" C" ); Serial.print (F ("Temperature: " )); Serial.println (temperature); } }
C/C++ 风格优化(使用 SDK)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include "pico/stdlib.h" #include "hardware/adc.h" #include <stdio.h> #define LCD_UPDATE_MS 2000 int main () { stdio_init_all(); adc_init(); adc_gpio_init(26 ); adc_select_input(0 ); const float adc_scale = 3.3f / 4095.0f ; absolute_time_t last_update = get_absolute_time(); while (true ) { absolute_time_t now = get_absolute_time(); if (absolute_time_diff_us(last_update, now) >= LCD_UPDATE_MS * 1000 ) { last_update = now; uint16_t raw = adc_read(); float voltage = raw * adc_scale; float temperature = voltage * 100.0f ; printf ("Temperature: %.2f C\n" , temperature); } tight_loop_contents(); } return 0 ; }
优化要点 :
移除了 delay(),使用非阻塞定时
将转换因子提升为常量
降低了 LCD 刷新频率(2秒一次),减少 I2C/SPI 通信开销
使用 F() 宏(Arduino)减少 SRAM 占用
8. 性能分析与调试工具
工具
用途
逻辑分析仪
测量 GPIO 翻转时序,评估代码执行时间
Pico SDK 的性能计数器
使用 timer_hw->timerawl 或 hardware/timer.h 中的 time_us_64() 测量代码段耗时
编译器输出 map 文件
查看内存占用和函数大小
串口打印时间戳
简单评估任务执行间隔
示例:测量代码段执行时间(C/C++ 风格)
1 2 3 4 uint64_t start = time_us_64();uint64_t end = time_us_64();printf ("Execution time: %llu us\n" , end - start);
9. 总结
通过优化代码结构、选择合适的数据类型、减少内存占用以及利用 Pico 2 的硬件特性(双核、PIO、DMA、浮点单元),你可以显著提升程序的性能。性能调优不仅能让程序运行得更快,还能降低功耗并提高实时响应能力。
优化方向
关键技巧
代码结构
减少全局变量、外提循环不变计算、使用 inline
数据类型
使用 uint8_t/int16_t 节省内存
内存优化
const 放入 Flash,使用 F() 宏,避免动态分配
硬件加速
双核并行、PIO 卸载时序任务、DMA 传输
非阻塞设计
用 millis()/定时器代替 delay()
10. 附加资源与练习
练习 1 :使用双核分别实现 LED 闪烁和串口打印,观察两个任务的独立运行。
练习 2 :编写一个 PIO 程序驱动 4 位数码管(或 WS2812),比较 PIO 版本与纯 CPU 模拟版本的 CPU 占用率。
练习 3 :优化一个现有项目,将阻塞式 delay 全部改为非阻塞定时,并增加一个按钮响应任务。
资源 :
通过不断实践和调优,你将能够编写出更高效、更稳定的 Pico 2 应用程序,充分发挥这颗芯片的强大潜力。祝你编程愉快!