[PICO][Adv]代码优化

Raspberry Pi Pico 2 代码优化

在嵌入式编程中,代码优化是一个重要的环节。通过优化代码,我们可以提高程序的执行效率、减少内存占用,并使代码更易于维护和理解。Raspberry Pi Pico 2 搭载的 RP2350 芯片性能强劲(双核 Cortex-M33,最高 150MHz),但合理优化仍然有助于释放更多算力、降低功耗,并确保程序在资源受限的场景下稳定运行。本文将逐步介绍 Pico 2 代码优化的基本概念和技巧,并通过实际案例展示如何应用这些技巧,涵盖 Arduino 风格(基于 Arduino-Pico 核心)和 C/C++ 风格(基于官方 Pico SDK)。


1. 为什么需要优化 Pico 2 代码?

虽然 Pico 2 比传统 Arduino 强大得多(520KB SRAM,支持浮点运算),但在以下场景中优化仍然至关重要:

  • 提高响应速度:实时控制系统(如电机驱动、PID 调节)需要极低延迟。
  • 降低功耗:电池供电的设备中,优化可减少 CPU 活跃时间,延长续航。
  • 释放 CPU 资源:让双核可以处理更多并发任务,或留出空闲核心做其他工作。
  • 减少内存占用:虽然 SRAM 较大,但复杂项目(如 GUI、音频处理)仍可能吃紧。
  • 提升代码可读性:优化往往伴随着代码结构的清晰化。

2. 代码优化的基本原则

2.1 减少全局变量的使用

全局变量会占用 SRAM 的静态数据段,且在程序运行期间一直存在。尽量使用局部变量或 static 局部变量,可以节省内存并提高代码的可读性。

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
// 不推荐:全局变量占用 .bss 段
int global_flag = 0;

int main() {
while (true) {
global_flag = 1;
}
}

// 推荐:将变量限制在函数作用域内
int main() {
static int flag = 0; // 静态局部变量,同样持久但作用域小
while (true) {
flag = 1;
}
}

提示:如果多个函数需要共享数据,可以封装在一个模块中,通过 getter/setter 访问,而不是直接暴露全局变量。

2.2 使用适当的数据类型

选择合适的数据类型可以减少内存占用并提高运算速度。Pico 2 支持 32 位原生整数,但对于小范围数值,使用 uint8_tint16_t 等仍能节省内存。

数据类型 大小(字节) 范围 适用场景
uint8_t 1 0~255 传感器原始字节、计数器小值
int16_t 2 -32768~32767 温度、角度等常用范围
float 4 约 ±3.4e38 需要小数运算(Pico 2 硬件支持浮点)
int 4 -2^31~2^31-1 通用,但若范围小则浪费内存

Arduino 风格示例

1
2
3
4
5
// 不推荐的写法
int sensorValue = analogRead(26); // analogRead 返回 0-4095,int 可以但 uint16_t 更合适

// 推荐的写法
uint16_t sensorValue = analogRead(26);

C/C++ 风格示例

1
2
3
4
5
6
7
8
#include "pico/stdlib.h"
#include "hardware/adc.h"

// 不推荐
int raw = adc_read(); // adc_read 返回 0-4095,可用 uint16_t

// 推荐
uint16_t raw = adc_read();

2.3 避免在循环中重复计算

将循环中不变的计算移到循环外部,可以显著提升效率。

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() {
float factor = 3.3 / 4095.0; // 只计算一次
uint16_t raw = analogRead(26); // 读取一次
float result = raw * factor;
for (int i = 0; i < 100; i++) {
// 使用相同的 result
}
}

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 多次
}
}

2.4 使用位运算代替算术运算

对于乘除 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;

Pico 2 的编译器通常会自动优化此类操作,但显式写出有时能帮助编译器。

2.5 合理使用 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 只是建议,编译器可能忽略。过度内联会增加代码体积。

2.6 利用 Pico 2 的双核与 PIO

Pico 2 的双核可以并行处理任务。将计算密集型或实时性要求高的任务放在核心 1 上,主核心处理通信或 UI。

此外,PIO(可编程 I/O) 可以卸载精确时序协议(如 WS2812、DHT22),释放 CPU。


3. 实际案例:优化 LED 闪烁程序

让我们通过一个简单的 LED 闪烁程序来展示代码优化的实际应用。

3.1 原始代码(阻塞式)

1
2
3
4
5
6
7
8
9
10
void setup() {
pinMode(25, OUTPUT);
}

void loop() {
digitalWrite(25, HIGH);
delay(1000);
digitalWrite(25, LOW);
delay(1000);
}

问题delay() 阻塞 CPU,无法在等待期间执行其他任务。

3.2 优化后的代码(非阻塞)

使用 millis() 代替 delay(),让程序可以同时处理其他任务。

Arduino 风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned long previousMillis = 0;
const long interval = 1000;
int ledState = LOW;

void setup() {
pinMode(25, OUTPUT);
}

void loop() {
unsigned long currentMillis = millis();

if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
ledState = (ledState == LOW) ? HIGH : LOW;
digitalWrite(25, ledState);
}
// 其他任务可以在这里执行,不会被阻塞
}

C/C++ 风格

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"

#define LED_PIN 25

int main() {
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);

absolute_time_t previous = get_absolute_time();
const uint32_t interval_ms = 1000;
bool led_state = false;

while (true) {
absolute_time_t now = get_absolute_time();
if (absolute_time_diff_us(previous, now) >= interval_ms * 1000) {
previous = now;
led_state = !led_state;
gpio_put(LED_PIN, led_state);
}
// 可以在此添加其他任务
tight_loop_contents();
}
return 0;
}

提示:使用 millis()get_absolute_time() 实现非阻塞延时,可以让 Pico 2 在等待期间执行传感器读取、通信等任务,极大提高程序效率。

3.3 进一步优化:使用硬件定时器或 PIO

对于更复杂的时序要求,可以使用硬件定时器中断或 PIO 来完全卸载 LED 闪烁任务,CPU 几乎零开销。

使用硬件定时器(C/C++ 风格)

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
#include "pico/stdlib.h"
#include "hardware/timer.h"

volatile bool led_toggle = false;

bool timer_callback(repeating_timer_t *rt) {
led_toggle = true;
return true;
}

int main() {
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);

repeating_timer_t timer;
add_repeating_timer_ms(1000, timer_callback, NULL, &timer);

while (true) {
if (led_toggle) {
gpio_put(25, !gpio_get(25));
led_toggle = false;
}
// CPU 可以做更重要的事,甚至进入休眠
__wfi(); // 等待中断唤醒
}
}

4. 其他优化技巧

4.1 减少库的依赖

许多 Arduino 库为了通用性而体积庞大。如果只需要库中的一小部分功能,考虑自己实现精简版本。

4.2 使用 constPROGMEM(Flash 存储)

将只读数据放入 Flash,可以释放 SRAM。在 Arduino-Pico 核心中,PROGMEM 被映射为 const,但更推荐直接使用 const

1
const char message[] = "Hello, Pico 2!";  // 存储在 Flash

4.3 优化循环:使用递减计数

某些 CPU 架构中,递减到零的循环比递增比较更快。

1
2
3
4
5
// 可能稍慢
for (int i = 0; i < 100; i++) { ... }

// 可能稍快
for (int i = 100; i > 0; i--) { ... }

现代编译器通常会自动优化,但可以留意。

4.4 启用编译器优化

在 Arduino IDE 中,选择“更快”或“最快”优化级别。在 CMake(SDK)中,使用 -O2-O3 标志。


5. 总结

通过本文,我们学习了 Raspberry Pi Pico 2 代码优化的基本原则和技巧。优化代码不仅可以提高程序的执行效率,还可以减少内存占用并提升代码的可读性。在实际应用中,我们可以通过以下措施优化代码:

  • 减少全局变量,优先使用局部或静态局部变量
  • 选择合适的数据类型(uint8_tint16_t 等)
  • 避免在循环中重复计算
  • 使用 millis() 或硬件定时器代替 delay()
  • 利用双核并行处理或 PIO 卸载时序敏感任务
  • 启用编译器优化选项

6. 附加资源与练习

  • 练习 1:优化一个读取温度传感器(LM35)并通过串口每秒打印一次的程序,要求不阻塞主循环。
  • 练习 2:使用 PIO 实现一个 WS2812 LED 的驱动,对比 CPU 模拟时序与 PIO 的 CPU 占用率。
  • 练习 3:将一个包含多个 delay() 的程序(例如呼吸灯 + 按键扫描)改为非阻塞版本,并在两个核心上分别运行呼吸灯和按键扫描任务。
  • 资源

通过不断实践和优化,你将能够编写出更高效、更可靠的 Pico 2 应用程序。


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