[PICO][Adv]性能调优

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_tint16_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; // 每次都重复计算因子和读取ADC
// 使用 result
}
}

// 推荐的写法
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++) {
// 使用相同的 voltage
}
}

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;
// 后续使用 voltage 多次
}
}

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); // analogRead 返回 0-4095,uint16_t 更合适

// 推荐的写法
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
// 存储在 Flash 中,不占用 SRAM
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 全局变量在 Flash 中
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) {
// 主核心处理串口命令、LCD 显示等
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() {
// 核心1 的初始化
}

void loop1() {
// 核心1 的任务
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" // 从 PIO 汇编生成的头文件

#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; // LM35 转换
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; // 每2秒更新一次LCD

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();

// 假设有 LCD 初始化函数
// lcd_init();

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;

// lcd_clear();
// lcd_print("Temp: %.1f C", temperature);
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->timerawlhardware/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. 附加资源与练习

通过不断实践和调优,你将能够编写出更高效、更稳定的 Pico 2 应用程序,充分发挥这颗芯片的强大潜力。祝你编程愉快!


[PICO][Adv]性能调优
https://ka5fxt.cn/2026/03/30/PICO-Adv-性能调优/
发布于
2026年3月30日
许可协议