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 W1¥25-35
温湿度传感器DHT22 / SHT301¥8-20
面包板830 孔1¥5
杜邦线母对母若干¥3
Micro USB 数据线供电 + 烧录1¥5
3.3V 有源蜂鸣器(可选)报警1¥2

总成本不到 60 元,比大部分开发板都便宜。

RP2040 双核架构速览

RP2040 内部有两个完全对称的 Cortex-M0+ 核心,运行频率最高 133 MHz。两个核心共享 264KB SRAM,但各自有独立的寄存器组和向量表。

核心之间的通信机制有三种:

  1. FIFO(先进先出队列):每个核有一个 32 位宽的 FIFO,共 8 级深度,支持中断触发
  2. Spinlock(自旋锁):32 个硬件自旋锁,保证共享资源的原子访问
  3. 共享内存:通过 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;
}

代码说明

双核启动流程

  1. Core 0 先初始化串口、自旋锁
  2. multicore_launch_core1() 启动 Core 1 执行 core1_network_loop()
  3. Core 1 连接 WiFi 后通过 FIFO 通知 Core 0
  4. Core 0 收到确认后开始传感器采集循环

数据同步机制

  • 共享变量 latest_temp / latest_humspin_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 示例

项目扩展思路

这个双核架构可以扩展到很多场景:

  1. 多传感器监控站:Core 0 轮询 DHT22 + 光照 + 气压传感器,Core 1 汇总上传
  2. 本地决策 + 云端同步:Core 0 做边缘推理(如 PID 控制),Core 1 负责数据上报和远程配置
  3. OTA 固件更新:Core 1 后台检查新版本,Core 0 继续维持设备正常运行
  4. WiFi + BLE 双模:Pico W 虽然没有内置 BLE,但可以外接 nRF24 模块做本地通信,Core 1 同时管理 WiFi 和 BLE

总结

RP2040 的双核编程听起来复杂,但 pico-sdk 提供的 multicore_* API 让上手非常简单。核心要点就是三条:

  1. 合理分工:把 I/O 密集型和计算密集型任务拆开
  2. 用 FIFO 做通知:轻量、高效、不会丢数据
  3. 用 Spinlock 保护共享数据:避免两个核心同时读写同一片 SRAM

几十块钱的 Pico W,双核 + WiFi,做 IoT 原型开发真的香。动手试试吧!