Raspberry Pi Pico W 物联网项目:RP2040 双核编程实战
Raspberry Pi Pico W 物联网项目:RP2040 双核编程实战
Raspberry Pi Pico W 可能是性价比最高的 IoT 开发板之一——RP2040 芯片内置双核 ARM Cortex-M0+,加上板载 Infineon CYW43439 WiFi 芯片,只要几十块钱就能跑一个完整的双核物联网项目。
但很多人拿到 Pico W 后只用一个核在跑,浪费了这颗芯片一半的性能。今天这篇我们就来好好利用 RP2040 的双核特性:一个核心专注 WiFi 网络通信,一个核心专注传感器数据采集,做一个完整的物联网温度监控节点。
硬件清单
| 器件 | 型号 | 数量 | 参考价格 |
|---|---|---|---|
| 开发板 | Raspberry Pi Pico W | 1 | ¥25-35 |
| 温湿度传感器 | DHT22 / SHT30 | 1 | ¥8-20 |
| 面包板 | 830 孔 | 1 | ¥5 |
| 杜邦线 | 母对母 | 若干 | ¥3 |
| Micro USB 数据线 | 供电 + 烧录 | 1 | ¥5 |
| 3.3V 有源蜂鸣器(可选) | 报警 | 1 | ¥2 |
总成本不到 60 元,比大部分开发板都便宜。
RP2040 双核架构速览
RP2040 内部有两个完全对称的 Cortex-M0+ 核心,运行频率最高 133 MHz。两个核心共享 264KB SRAM,但各自有独立的寄存器组和向量表。
核心之间的通信机制有三种:
- FIFO(先进先出队列):每个核有一个 32 位宽的 FIFO,共 8 级深度,支持中断触发
- Spinlock(自旋锁):32 个硬件自旋锁,保证共享资源的原子访问
- 共享内存:通过 SRAM 直接读写,配合 spinlock 做同步
pico-sdk 把这些封装成了简洁的 API:
// 启动 core1 的函数
multicore_launch_core1(core1_entry);
// 通过 FIFO 发送数据(32 位)
multicore_fifo_push_blocking(value);
// 从 FIFO 接收数据
uint32_t value = multicore_fifo_pop_blocking();
// 获取/释放自旋锁
uint32_t lock = spin_lock_get_num(spin_lock_instance(num));
实战项目架构
我们要做的是一个双核分工明确的 IoT 温度监控节点:
┌─────────────────────────────────────────┐
│ RP2040 双核架构 │
├──────────────────┬──────────────────────┤
│ Core 0 │ Core 1 │
│ (传感器核心) │ (网络核心) │
│ │ │
│ • 读取 DHT22 │ • WiFi 连接路由器 │
│ • 数据滤波处理 │ • MQTT 发布数据 │
│ • 越限报警判断 │ • 接收远程指令 │
│ • 蜂鸣器控制 │ • OTA 固件检查 │
└──────────────────┴──────────────────────┘
↓ FIFO 通信 ↓
为什么这么分?因为 WiFi 协议栈(LwIP + cyw43)在连接和传输时会有不确定的阻塞时间,如果跟传感器采集跑在同一个核上,可能导致采集间隔抖动。拆到两个核上,传感器采样可以做到严格的定时精度。
项目完整代码
首先准备 CMakeLists.txt:
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(pico_w_dual_core_iot C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(dual_core_iot
main.c
dht22.c
)
target_include_directories(dual_core_iot PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
# 启用 WiFi 支持
target_compile_definitions(dual_core_iot PRIVATE
CYW43_LWIP=1
PICO_CYW43_ARCH_DEFAULT=1
)
target_link_libraries(dual_core_iot
pico_stdlib
hardware_adc
hardware_pwm
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_mqtt
hardware_spi
)
pico_enable_stdio_usb(dual_core_iot 1)
pico_enable_stdio_uart(dual_core_iot 0)
pico_add_extra_outputs(dual_core_iot)
DHT22 驱动 dht22.c:
#include "pico/stdlib.h"
#include "hardware/gpio.h"
// DHT22 单总线协议实现
// 简化版:实际项目中建议用硬件 PWM 或 PIO 更稳定
typedef struct {
float temperature;
float humidity;
uint8_t valid;
} dht22_reading_t;
// 等待引脚电平变化,超时返回 0
static int wait_for_pin(uint pin, int level, uint32_t timeout_us) {
uint32_t start = time_us_32();
while (gpio_get(pin) != level) {
if (time_us_32() - start > timeout_us) return 0;
}
return 1;
}
dht22_reading_t dht22_read(uint pin) {
dht22_reading_t result = {0, 0, 0};
uint8_t data[5] = {0};
gpio_init(pin);
gpio_set_dir(pin, GPIO_OUT);
// 发送开始信号:拉低 > 18ms,再拉高
gpio_put(pin, 0);
sleep_ms(20);
gpio_put(pin, 1);
sleep_us(40);
// 切换为输入
gpio_set_dir(pin, GPIO_IN);
// DHT22 响应:拉低 80us,拉高 80us
if (!wait_for_pin(pin, 0, 100)) return result;
if (!wait_for_pin(pin, 1, 100)) return result;
if (!wait_for_pin(pin, 0, 100)) return result;
// 读取 40 位数据
for (int i = 0; i < 40; i++) {
if (!wait_for_pin(pin, 1, 100)) return result;
uint32_t high_start = time_us_32();
if (!wait_for_pin(pin, 0, 100)) return result;
uint32_t high_duration = time_us_32() - high_start;
// > 70us 是 1,否则是 0
if (high_duration > 70) {
data[i / 8] |= (1 << (7 - i % 8));
}
}
// 校验和
if ((data[0] + data[1] + data[2] + data[3]) & 0xFF == data[4]) {
result.humidity = (data[0] << 8 | data[1]) / 10.0f;
result.temperature = (data[2] << 8 | data[3]) / 10.0f;
result.valid = 1;
}
return result;
}
主程序 main.c——双核协调的核心:
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/sync.h"
#include "pico/cyw43_arch.h"
#include "lwip/netif.h"
#include "lwip/ip4_addr.h"
#include "lwip/apps/mqtt.h"
#include "lwip/apps/mqtt_priv.h"
// WiFi 配置
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASS "YourWiFiPassword"
// MQTT Broker 配置
#define MQTT_BROKER_IP "192.168.1.100"
#define MQTT_PORT 1883
#define MQTT_CLIENT_ID "pico_w_node_01"
#define MQTT_TOPIC_TEMP "home/sensor/temperature"
#define MQTT_TOPIC_HUM "home/sensor/humidity"
// 传感器引脚
#define DHT22_PIN 2
#define BUZZER_PIN 3
// 共享数据(通过 FIFO 传输)
#define CMD_READ_SENSOR 0x01
#define DATA_READY 0x10
#define TEMP_THRESHOLD 35.0f // 温度报警阈值
// Core 0 全局变量
static float latest_temp = 0;
static float latest_hum = 0;
static spin_lock_t *data_lock = NULL;
// 模拟 DHT22 读取(实际项目中用上面的 dht22.c)
extern dht22_reading_t dht22_read(uint pin);
// ====== Core 1 入口:网络核心 ======
static mqtt_client_t mqtt_client;
static int wifi_connected = 0;
static void mqtt_connection_cb(mqtt_client_t *client, void *arg,
mqtt_connection_status_t status) {
if (status == MQTT_CONNECT_ACCEPTED) {
printf("[Core1] MQTT connected!\n");
} else {
printf("[Core1] MQTT connect failed: %d\n", status);
}
}
static void publish_sensor_data(float temp, float hum) {
if (!wifi_connected) return;
char payload[32];
snprintf(payload, sizeof(payload), "%.1f", temp);
mqtt_publish(&mqtt_client, MQTT_TOPIC_TEMP, payload,
strlen(payload), 1, 0, NULL, NULL);
snprintf(payload, sizeof(payload), "%.1f", hum);
mqtt_publish(&mqtt_client, MQTT_TOPIC_HUM, payload,
strlen(payload), 1, 0, NULL, NULL);
}
void core1_network_loop(void) {
cyw43_arch_enable_sta_mode();
printf("[Core1] Connecting to WiFi: %s\n", WIFI_SSID);
if (cyw43_arch_wifi_connect_timeout_ms(
WIFI_SSID, WIFI_PASS, CYW43_AUTH_WPA2_AES_PSK, 30000)) {
printf("[Core1] WiFi connect failed!\n");
// 通知 core0 联网失败
multicore_fifo_push_blocking(0xDEAD);
return;
}
printf("[Core1] WiFi connected! IP: %s\n",
ip4addr_ntoa(netif_ip4_addr(netif_list)));
wifi_connected = 1;
multicore_fifo_push_blocking(0xCAFE); // 通知 core0 联网成功
// 初始化 MQTT
ip_addr_t broker;
ipaddr_aton(MQTT_BROKER_IP, &broker);
struct mqtt_connect_client_info_t ci = {
.client_id = MQTT_CLIENT_ID,
.keep_alive = 60,
};
mqtt_client_set_config(&mqtt_client, &ci);
mqtt_client_connect(&mqtt_client, &broker, MQTT_PORT,
mqtt_connection_cb, NULL, &ci);
// 主循环:等待 core0 的数据并通过 MQTT 发布
while (1) {
cyw43_arch_poll();
// 非阻塞检查 FIFO 是否有传感器数据
if (multicore_fifo_rvalid()) {
uint32_t cmd = multicore_fifo_pop_blocking();
if (cmd == DATA_READY) {
// 通过 spinlock 安全读取共享数据
uint32_t irq = spin_lock_blocking(data_lock);
float temp = latest_temp;
float hum = latest_hum;
spin_unlock(data_lock, irq);
publish_sensor_data(temp, hum);
printf("[Core1] Published: T=%.1f°C, H=%.1f%%\n", temp, hum);
}
}
mqtt_cyclic_timer(&mqtt_client);
sleep_ms(10);
}
}
// ====== Core 0:主核(传感器 + 控制) ======
int main() {
stdio_init_all();
sleep_ms(2000); // 等待串口稳定
printf("=== RP2040 Dual-Core IoT Node ===\n");
// 初始化自旋锁
data_lock = spin_lock_init(1);
// 启动 Core 1 网络线程
multicore_launch_core1(core1_network_loop);
// 等待 Core 1 报告 WiFi 状态
printf("[Core0] Waiting for WiFi connection...\n");
uint32_t wifi_status = multicore_fifo_pop_blocking();
if (wifi_status != 0xCAFE) {
printf("[Core0] WiFi failed, falling back to local mode\n");
} else {
printf("[Core0] WiFi OK, starting sensor loop\n");
}
gpio_init(BUZZER_PIN);
gpio_set_dir(BUZZER_PIN, GPIO_OUT);
// 传感器主循环
while (1) {
dht22_reading_t reading = dht22_read(DHT22_PIN);
if (reading.valid) {
// 通过 spinlock 安全写入共享数据
uint32_t irq = spin_lock_blocking(data_lock);
latest_temp = reading.temperature;
latest_hum = reading.humidity;
spin_unlock(data_lock, irq);
printf("[Core0] T=%.1f°C, H=%.1f%%\n",
reading.temperature, reading.humidity);
// 温度越限报警
if (reading.temperature > TEMP_THRESHOLD) {
gpio_put(BUZZER_PIN, 1);
sleep_ms(200);
gpio_put(BUZZER_PIN, 0);
printf("[Core0] WARNING: Temperature > %.1f°C!\n",
TEMP_THRESHOLD);
}
// 通知 Core1 发送数据
multicore_fifo_push_blocking(DATA_READY);
} else {
printf("[Core0] Sensor read failed, retrying...\n");
}
// 每 5 秒采样一次
sleep_ms(5000);
}
return 0;
}
代码说明
双核启动流程:
- Core 0 先初始化串口、自旋锁
multicore_launch_core1()启动 Core 1 执行core1_network_loop()- Core 1 连接 WiFi 后通过 FIFO 通知 Core 0
- Core 0 收到确认后开始传感器采集循环
数据同步机制:
- 共享变量
latest_temp/latest_hum用spin_lock保护 - 数据就绪通知通过
multicore_fifo_push_blocking(DATA_READY)传递 - 这种 FIFO 通知 + Spinlock 保护共享数据 的模式是 RP2040 双核编程的标准做法
编译和烧录
# 设置 pico-sdk 路径
export PICO_SDK_PATH=/path/to/pico-sdk
# 创建构建目录
mkdir build && cd build
# 配置编译(启用 USB 输出)
cmake .. -DPICO_BOARD=pico_w -DPICO_COPY_TO_RAM=1
# 编译
make -j4
# 烧录:按住 Pico W 的 BOOTSEL 键再插 USB
# 把 dual_core_iot.uf2 复制到 RPI-RP2 磁盘即可
cp dual_core_iot.uf2 /media/$USER/RPI-RP2/
烧录后打开串口终端(115200 波特),你应该看到类似输出:
=== RP2040 Dual-Core IoT Node ===
[Core0] Waiting for WiFi connection...
[Core1] Connecting to WiFi: YourWiFiSSID
[Core1] WiFi connected! IP: 192.168.1.42
[Core0] WiFi OK, starting sensor loop
[Core0] T=25.3°C, H=58.2%
[Core1] Published: T=25.3°C, H=58.2%
常见问题排查
问题 1:Core 1 启动后死机
症状:Core 0 正常运行,Core 1 的 printf 没有任何输出。
排查:
- 检查
multicore_launch_core1()传入的函数不能有返回值,必须是void类型且永不返回 - Core 1 不能使用
stdio_init_all()(串口已在 Core 0 初始化) - 如果 Core 1 里用了
sleep_ms(),确保 pico-sdk 的 alarm 池已被 Core 0 初始化
// ❌ 错误:Core 1 函数有返回值
int core1_entry(void) { ... return 0; }
// ✅ 正确:void 类型 + 无限循环
void core1_entry(void) {
while (1) {
// ...
}
}
问题 2:WiFi 连接失败
症状:cyw43_arch_wifi_connect_timeout_ms() 返回非 0。
排查清单:
| 可能原因 | 解决方法 |
|---|---|
| CMake 没启用 WiFi | 确保链接 pico_cyw43_arch_lwip_threadsafe_background |
| WiFi 密码错误 | 用其他设备确认 SSID 和密码 |
| 电源不足 | Pico W WiFi 峰值电流 ~200mA,确保 USB 供电充足 |
| 天线问题 | Pico W 板载 PCB 天线,远离金属物体 |
问题 3:FIFO 满了导致阻塞
症状:Core 0 的 multicore_fifo_push_blocking() 卡住不动。
FIFO 只有 8 级深度,如果 Core 1 处理不过来就会满。解决方案:
// 非阻塞检查,不卡死
if (multicore_fifo_wready()) {
multicore_fifo_push_blocking(DATA_READY);
} else {
printf("[Core0] FIFO full, Core1 is slow!\n");
}
问题 4:传感器数据偶尔出错
DHT22 是单总线时序传感器,对时序要求很严格。双核环境下如果 Core 0 被中断打断,可能导致读取出错。建议:
// 读取期间屏蔽中断
uint32_t irq = save_and_disable_interrupts();
dht22_reading_t reading = dht22_read(DHT22_PIN);
restore_interrupts(irq);
或者更推荐的做法:用 RP2040 的 PIO(可编程 IO) 来处理 DHT22 时序,这样完全不受 CPU 中断影响,也是 pico-sdk 官方推荐的方式。
进阶:用 PIO 代替软件读取传感器
pico-sdk 的 PIO 引擎是 RP2040 的杀手级特性——它相当于一个微型协处理器,可以独立于 CPU 处理精确时序的 IO 操作。
用 PIO 读取 DHT22 的好处:
- 不受 CPU 调度影响,即使双核都满载,PIO 依然能准确采样
- 降低 CPU 占用,软件方案读取一次 DHT22 需要阻塞 ~5ms,PIO 只需启动后等待完成
- 可以并发读取多个传感器,每个传感器分配一个 PIO 状态机
// PIO 程序(DHT22 时序检测)
.program dht22
.side_set 1
; 简化的 PIO 时序示意
; 实际项目请参考 pico-examples 中的 PIO 示例
项目扩展思路
这个双核架构可以扩展到很多场景:
- 多传感器监控站:Core 0 轮询 DHT22 + 光照 + 气压传感器,Core 1 汇总上传
- 本地决策 + 云端同步:Core 0 做边缘推理(如 PID 控制),Core 1 负责数据上报和远程配置
- OTA 固件更新:Core 1 后台检查新版本,Core 0 继续维持设备正常运行
- WiFi + BLE 双模:Pico W 虽然没有内置 BLE,但可以外接 nRF24 模块做本地通信,Core 1 同时管理 WiFi 和 BLE
总结
RP2040 的双核编程听起来复杂,但 pico-sdk 提供的 multicore_* API 让上手非常简单。核心要点就是三条:
- 合理分工:把 I/O 密集型和计算密集型任务拆开
- 用 FIFO 做通知:轻量、高效、不会丢数据
- 用 Spinlock 保护共享数据:避免两个核心同时读写同一片 SRAM
几十块钱的 Pico W,双核 + WiFi,做 IoT 原型开发真的香。动手试试吧!