面向对象编程(Object-Oriented Programming,简称 OOP) 是一种编程范式,它将数据和操作数据的方法封装在对象中。通过使用 OOP,你可以更好地组织代码,使其更易于理解和维护。本文将介绍如何在 Raspberry Pi Pico 2 开发中使用面向对象编程技术,涵盖 Arduino 风格(基于 Arduino-Pico 核心,使用 C++)和 C/C++ 风格(基于 Pico SDK,使用 C 语言模拟 OOP 或直接使用 C++)两种方式。
1. 什么是面向对象编程?
面向对象编程的核心概念是类和对象。类是对象的蓝图或模板,而对象是类的实例。类可以包含属性(变量)和方法(函数),这些属性和方法定义了对象的行为和状态。
在 Pico 2 项目中,OOP 可以帮助你将复杂的代码分解为更小、更易管理的部分。例如,如果你正在控制多个 LED 灯,你可以为每个 LED 创建一个类,而不是为每个 LED 编写重复的代码。
2. 创建一个简单的类
2.1 Arduino 风格:使用 C++ 类
让我们从一个简单的例子开始。假设我们有一个 LED,我们可以通过 Pico 2 的 GPIO 引脚控制它的开关。创建一个 LED 类来封装这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class LED { private: uint8_t pin;
public: LED(uint8_t p) : pin(p) { pinMode(pin, OUTPUT); }
void on() { digitalWrite(pin, HIGH); }
void off() { digitalWrite(pin, LOW); } };
|
使用类创建对象
1 2 3 4 5 6 7 8 9 10 11 12
| LED led1(25);
void setup() { }
void loop() { led1.on(); delay(1000); led1.off(); delay(1000); }
|
2.2 C/C++ 风格:使用结构体与函数指针
在纯 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"
typedef struct { uint8_t pin; void (*on)(struct LED* self); void (*off)(struct LED* self); } LED;
void LED_on(LED* self) { gpio_put(self->pin, 1); }
void LED_off(LED* self) { gpio_put(self->pin, 0); }
void LED_init(LED* self, uint8_t pin) { self->pin = pin; self->on = LED_on; self->off = LED_off; gpio_init(pin); gpio_set_dir(pin, GPIO_OUT); }
|
使用对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int main() { stdio_init_all();
LED led1; LED_init(&led1, 25);
while (true) { led1.on(&led1); sleep_ms(1000); led1.off(&led1); sleep_ms(1000); } return 0; }
|
提示:C 语言的模拟虽然稍显繁琐,但能实现封装和多态(通过函数指针),是很多嵌入式项目的选择。
3. 类的继承
继承允许你创建一个新类,继承现有类的属性和方法,从而重用代码。例如,我们创建一个 RGBLED 类,它可以控制一个 RGB LED(共阳极或共阴极)。RGBLED 可以继承 LED 类(或包含多个 LED 对象),并添加新方法。
3.1 Arduino 风格:C++ 继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class RGBLED : public LED { private: uint8_t redPin, greenPin, bluePin;
public: RGBLED(uint8_t r, uint8_t g, uint8_t b) : LED(r), redPin(r), greenPin(g), bluePin(b) { pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); }
void setColor(uint8_t r, uint8_t g, uint8_t b) { analogWrite(redPin, r); analogWrite(greenPin, g); analogWrite(bluePin, b); } };
|
使用 RGBLED
1 2 3 4 5 6 7 8 9 10 11 12
| RGBLED rgb(9, 10, 11);
void setup() { }
void loop() { rgb.setColor(255, 0, 0); delay(1000); rgb.setColor(0, 255, 0); delay(1000); rgb.setColor(0, 0, 255); delay(1000); }
|
3.2 C/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
|
typedef struct { LED red; LED green; LED blue; void (*setColor)(struct RGBLED* self, uint8_t r, uint8_t g, uint8_t b); } RGBLED;
void RGBLED_setColor(RGBLED* self, uint8_t r, uint8_t g, uint8_t b) { analogWrite(self->red.pin, r); analogWrite(self->green.pin, g); analogWrite(self->blue.pin, b); }
void RGBLED_init(RGBLED* self, uint8_t rPin, uint8_t gPin, uint8_t bPin) { LED_init(&self->red, rPin); LED_init(&self->green, gPin); LED_init(&self->blue, bPin); self->setColor = RGBLED_setColor; }
|
说明:C 语言中组合比继承更灵活,也更易于理解。
4. 实际应用:机器人组件封装
假设你正在开发一个简单的机器人,它有两个轮子和一个超声波传感器。我们可以为每个组件创建类,使主逻辑更加清晰。
4.1 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| class Wheel { private: uint8_t pinFwd, pinRev;
public: Wheel(uint8_t fwd, uint8_t rev) : pinFwd(fwd), pinRev(rev) { pinMode(pinFwd, OUTPUT); pinMode(pinRev, OUTPUT); }
void forward() { digitalWrite(pinFwd, HIGH); digitalWrite(pinRev, LOW); }
void backward() { digitalWrite(pinFwd, LOW); digitalWrite(pinRev, HIGH); }
void stop() { digitalWrite(pinFwd, LOW); digitalWrite(pinRev, LOW); } };
class UltrasonicSensor { private: uint8_t trigPin, echoPin;
public: UltrasonicSensor(uint8_t trig, uint8_t echo) : trigPin(trig), echoPin(echo) { pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); }
long getDistance() { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration = pulseIn(echoPin, HIGH); return duration * 0.034 / 2; } };
Wheel leftWheel(2, 3); Wheel rightWheel(4, 5); UltrasonicSensor sensor(6, 7);
void setup() { }
void loop() { long distance = sensor.getDistance(); if (distance > 20) { leftWheel.forward(); rightWheel.forward(); } else { leftWheel.stop(); rightWheel.stop(); } delay(100); }
|
4.2 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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include "pico/stdlib.h" #include "hardware/gpio.h" #include "hardware/pwm.h"
typedef struct { uint8_t pin_fwd; uint8_t pin_rev; void (*forward)(struct Wheel* self); void (*backward)(struct Wheel* self); void (*stop)(struct Wheel* self); } Wheel;
void Wheel_forward(Wheel* self) { gpio_put(self->pin_fwd, 1); gpio_put(self->pin_rev, 0); } void Wheel_backward(Wheel* self) { gpio_put(self->pin_fwd, 0); gpio_put(self->pin_rev, 1); } void Wheel_stop(Wheel* self) { gpio_put(self->pin_fwd, 0); gpio_put(self->pin_rev, 0); } void Wheel_init(Wheel* self, uint8_t fwd, uint8_t rev) { self->pin_fwd = fwd; self->pin_rev = rev; self->forward = Wheel_forward; self->backward = Wheel_backward; self->stop = Wheel_stop; gpio_init(fwd); gpio_set_dir(fwd, GPIO_OUT); gpio_init(rev); gpio_set_dir(rev, GPIO_OUT); }
typedef struct { uint8_t trig_pin; uint8_t echo_pin; long (*get_distance)(struct UltrasonicSensor* self); } UltrasonicSensor;
long UltrasonicSensor_get_distance(UltrasonicSensor* self) { gpio_put(self->trig_pin, 0); sleep_us(2); gpio_put(self->trig_pin, 1); sleep_us(10); gpio_put(self->trig_pin, 0); uint64_t duration = 0; while (gpio_get(self->echo_pin) == 0); absolute_time_t start = get_absolute_time(); while (gpio_get(self->echo_pin) == 1); duration = absolute_time_diff_us(start, get_absolute_time()); return duration * 0.034 / 2; } void UltrasonicSensor_init(UltrasonicSensor* self, uint8_t trig, uint8_t echo) { self->trig_pin = trig; self->echo_pin = echo; self->get_distance = UltrasonicSensor_get_distance; gpio_init(trig); gpio_set_dir(trig, GPIO_OUT); gpio_init(echo); gpio_set_dir(echo, GPIO_IN); }
int main() { stdio_init_all();
Wheel leftWheel, rightWheel; Wheel_init(&leftWheel, 2, 3); Wheel_init(&rightWheel, 4, 5);
UltrasonicSensor sensor; UltrasonicSensor_init(&sensor, 6, 7);
while (true) { long distance = sensor.get_distance(&sensor); if (distance > 20) { leftWheel.forward(&leftWheel); rightWheel.forward(&rightWheel); } else { leftWheel.stop(&leftWheel); rightWheel.stop(&rightWheel); } sleep_ms(100); } return 0; }
|
注意:在 C 语言中,我们通过函数指针模拟方法,并显式传递 self 指针,实现了类似面向对象的效果。
5. 总结
面向对象编程是一种强大的编程范式,它可以帮助你更好地组织和管理 Pico 2 代码。通过使用类和对象(C++)或结构体与函数指针(C),你可以将复杂的代码分解为更小、更易管理的部分。
| 特性 |
Arduino 风格(C++) |
C/C++ 风格(C 语言模拟) |
| 封装 |
使用 class 和 public/private |
使用结构体 + 函数指针 |
| 继承 |
使用 : 直接继承 |
通常使用组合(包含子对象) |
| 多态 |
虚函数表 |
显式函数指针 |
| 适用性 |
简洁、自然 |
资源消耗极小,适合纯 C 项目 |
6. 附加资源与练习
- 练习 1:创建一个
Button 类(C++)或结构体(C),用于读取按钮状态,并在按下时点亮 LED。
- 练习 2:扩展
RGBLED 类,添加一个 fade() 方法,使 LED 的颜色逐渐变化。
- 练习 3:使用面向对象思想,将 Pico 2 的 I2C 或 SPI 外设封装成类,简化传感器驱动开发。
- 资源:
通过不断实践和探索,你将能够更好地掌握 Pico 2 中的面向对象编程技术,并应用于更复杂的嵌入式项目。