Skip to main content

Working with C/C++

This chapter contains the following sections. Please read as needed:

Setting Up the Development Environment

Please refer to the Raspberry Pi Pico C/C++ Getting Started to download and install the Pico VS Code.

Example

The C/C++ examples are located in the examples/C directory of the example package.

ExampleBasic Program DescriptionDependency Library
01_MQTTMQTT example-

01_MQTT

Example Description

  • This example demonstrates how the RP2350-POE-ETH uses the Pico SDK (C project) to connect to a public MQTT broker (broker.emqx.io:1883 by default) over Ethernet and implements:
    • Subscribe topic: Sub/ed229f15
    • Publish topic: Pub/ed229f15
    • Publish a device status JSON every 10 seconds (including MAC, Client ID, temperature, VSYS voltage, USB/POE insertion status, uptime, network parameters, etc.)
  • The program uses DHCP to obtain an IP address automatically and resolves the domain broker.emqx.io via DNS to obtain the broker IP before connecting. It also supports switching to static IPv4 and a static broker IP.
  • Serial logs are output via USB CDC (the project configures pico_enable_stdio_usb(main 1)), which can be used for debugging and viewing MQTT interactions.
  • Open-source usage notes (strongly recommended to modify on first use to avoid conflicts among multiple people/devices):
    • MQTT Brokers typically require that the Client ID be globally unique at any given time: if two devices use the same MQTT_CLIENT_ID to log in, the later one will kick off the earlier one, resulting in frequent disconnections.
    • If a fixed Topic is used, data from multiple devices will be mixed together and can easily be mis-subscribed or mis-controlled by others.
    • It is recommended to modify at least: MQTT_CLIENT_ID, MQTT_PUBLISH_TOPIC, and MQTT_SUBSCRIBE_TOPIC. If you are using your own Broker or have enabled authentication, also modify MQTT_USERNAME and MQTT_PASSWORD.

The default configuration is located in mqtt_app/mqtt_app.c (macro definition area):

#define MQTT_USERNAME "w6300_test"
#define MQTT_CLIENT_ID "ed229f15"
#define MQTT_PASSWORD "0123456789"

#define MQTT_PUBLISH_TOPIC "Pub/ed229f15"
#define MQTT_SUBSCRIBE_TOPIC "Sub/ed229f15"

Hardware Connection

  • Connect the development board to your computer via a USB cable (for power, programming, and USB CDC serial log viewing).
  • Plug an Ethernet cable into the RJ45 port and ensure the network can reach the public internet (able to access broker.emqx.io:1883).
  • Optional: Use a PoE Ethernet cable to power the board (the program will report poe_inserted status in the JSON payload).

Code Analysis

  • Entry file: main.c

    • This project uses main() as the entry point; after board/peripheral/network initialization, it enters the infinite loop of mqtt_service_run() (internal while(1)), which handles MQTT keep‑alive, packet reception, and periodic publishing.
    • Core flow of main() (text description)
      • System initialization: DEV_Module_Init() (clock/stdio, etc.) + WS2812_init() (status LED)
      • Power/temperature related initialization: bsp_adc_voltage_detection_init(...) (VSYS sampling configuration) + adc_set_temp_sensor_enabled(true) (temperature channel)
      • IO initialization: usb_detect_io_init() / poe_detect_io_init()
      • Business start: mqtt_app_init() (Ethernet + DHCP/DNS + MQTT) → mqtt_service_run() (MQTT main loop: keep‑alive, packet reception, periodic publishing)
      • Error handling: if any step returns a non‑zero value, it enters while(1) and lights the error indicator (LED_INDICATOR_ERROR())
  • MQTT logic file: mqtt_app/mqtt_app.c

    • Key configurations (macro definitions)
      • Broker
        • g_dns_target_domain = "broker.emqx.io": Broker domain name (default uses DNS resolution).
        • PORT_MQTT (1883): MQTT TCP port.
        • If DNS is disabled on your network or you want to use a fixed Broker IP: change #if 0 to #if 1 in mqtt_connect() to use MQTT_BROKER_IP_0~3.
      • MQTT_CLIENT_ID / MQTT_USERNAME / MQTT_PASSWORD: Account information for connecting to the broker (the example uses fixed values by default; also supports auto‑generation based on the Unique ID)
      • MQTT_PUBLISH_TOPIC / MQTT_SUBSCRIBE_TOPIC: Publish/subscribe topics
      • MQTT_PUBLISH_PERIOD (10s): Periodic publish interval
    • Static/Dynamic switching (it is recommended to change only this part of the source code to switch)
      • Network addressing: Static IP ↔ DHCP (dynamic)
        • Default (DHCP)
static eth_NetInfo g_net_info = {
.ip = {192, 168, 1, 100},
.sn = {255, 255, 255, 0},
.gw = {192, 168, 1, 1},
.dns = {8, 8, 8, 8},
.dhcp = NETINFO_DHCP,
.ipmode = NETINFO_DHCP_V4
};
  • Note: In DHCP mode, the above .ip/.sn/.gw/.dns are placeholder values; they will be overwritten by parameters assigned by the router after ethchip_dhcp_run() succeeds. The gateway and DNS are also provided by the router.
  • DHCP entry (after a lease is successfully obtained, reconnection is triggered in the DHCP callback)
int mqtt_app_init(void)
{
if (g_net_info.dhcp == NETINFO_DHCP) {
ethchip_dhcp_init(ethchip_dhcp_assign, ethchip_dhcp_assign, ethchip_dhcp_conflict);
return ethchip_dhcp_run();
}
return mqtt_connect();
}
  • Broker address: Static IP ↔ DNS (dynamic domain name resolution)
    • Default (DNS resolution)
static uint8_t g_dns_target_domain[] = "broker.emqx.io";
#if 0
g_mqtt_broker_ip[0] = MQTT_BROKER_IP_0;
g_mqtt_broker_ip[1] = MQTT_BROKER_IP_1;
g_mqtt_broker_ip[2] = MQTT_BROKER_IP_2;
g_mqtt_broker_ip[3] = MQTT_BROKER_IP_3;
#else
ethchip_dns_init();
ethchip_dns_get_domain_ip(g_net_info.dns, g_dns_target_domain, g_mqtt_broker_ip);
#endif
  • Switch to static Broker IP: Change #if 0 to #if 1 in the code above; it will then use MQTT_BROKER_IP_0~3
  • MQTT identity: Static client_id/username ↔ Dynamically generated (based on Unique ID)
    • Default (static client_id / username)
#if 1
snprintf(g_mqtt_client_id, sizeof(g_mqtt_client_id), "%s", MQTT_CLIENT_ID);
#else
snprintf(g_mqtt_client_id, sizeof(g_mqtt_client_id), "%s%02X%02X%02X%02X",
MQTT_CLIENT_ID_PREFIX, board_id.id[4], board_id.id[5], board_id.id[6], board_id.id[7]);
#endif

#if 1
snprintf(g_mqtt_username, sizeof(g_mqtt_username), "%s", MQTT_USERNAME);
#else
snprintf(g_mqtt_username, sizeof(g_mqtt_username), "%s%02X%02X%02X%02X",
MQTT_USERNAME_PREFIX, board_id.id[4], board_id.id[5], board_id.id[6], board_id.id[7]);
#endif
  • Switch to dynamic generation: Change the two #if 1 above to #if 0; and it will compose a unique identity for each device using the prefix + Unique ID.

  • Note: The current publish/subscribe topics are fixed (multiple devices will have mixed data).

  • message_arrived(MessageData *msg_data)

    • printf("%.*s\n", ...): Prints the received message content.
    • mqtt_parse_and_execute_commands(...): Passes the subscribed message to the command parsing module (located in mqtt_execute_commands.c), which executes instructions according to the JSON fields and replies.
static const mqtt_cmd_map_t g_cmd_map[] = {
{"adc", adc_op_handle},
{"led", led_op_handle},
{"io_dect", io_dect_op_handle},
};
  • Supported control commands (payload is JSON; executes only if the field exists and value >= 0)
    • {"adc":1}: Reads VSYS voltage and ADC raw value, and publishes a JSON to MQTT_PUBLISH_TOPIC
{"vsys_voltage":5.021,"vsys_adc_raw":2048}
  • {"led":N}: Sets the onboard WS2812 color (N ranges from 0-8), and publishes the result to MQTT_PUBLISH_TOPIC
    • Success example:
{"led_color":2,"led_name":"RED"}
  • Failure example (out of range):
{"error":"invalid_led_color","value":99,"valid_range":"0-8"}
  • {"io_dect":1}: Reads USB/POE insertion detection IOs and publishes the status to MQTT_PUBLISH_TOPIC
{"usb_inserted":true,"poe_inserted":false}
  • build_mqtt_payload(...)
    • Reads temperature, Flash capacity, system uptime, VSYS voltage, USB/POE insertion status, network parameters, etc., and composes a JSON string as the publish payload.
    • Key error handling: if voltage reading fails, it falls back to 0 to avoid reporting abnormal values.

Operation Result

  • Import and compile the 01_MQTT project using VS Code. After compilation, flash the .uf2 file from the build directory, or directly flash 01_MQTT.uf2 file from the firmware/C directory for quick verification.

  • After programming the example, open the USB serial port (common serial tools default to 115200 baud) and you should see logs similar to:

DEV_Module_Init OK

========== Device Identity ==========
MAC: 02:08:DC:xx:xx:xx
ClientID: ed229f15
UserName: w6300_test
======================================

DHCP initialization succeed
DHCP success
DHCP assign succeed
mqtt disconnect
mqtt connect
DNS initialized successfully
DNS success
Target domain : broker.emqx.io
IP of target domain : 34.243.217.54
MQTT connected
Published
Subscribed
Published
Published
...
  • Verify using any MQTT client (MQTTX recommended)

    • MQTTX client settings:

    photo0

    • RP2350-POE-ETH connection parameters (consistent with macro configuration):
      • Host: broker.emqx.io
      • Port: 1883
      • Username: w6300_test
      • Password: 0123456789
      • Client ID: ed229f15
    • Subscribe to topic: Pub/ed229f15. You will receive the JSON reported by the board every 10 seconds, for example:
{
"device": { "chip": "RP2350A", "mac": "02:08:DC:xx:xx:xx", "client_id": "ed229f15" },
"system": {
"cpu_mhz": 150,
"ram_kb": 520,
"flash_mb": 16,
"temp_c": 36.2,
"vsys_voltage_v": 5.021,
"vsys_adc_raw": 2048,
"usb_inserted": true,
"poe_inserted": false,
"uptime": "0:00:01:23"
},
"network": { "mode": "DHCP", "ip": "192.168.1.100", "subnet": "255.255.255.0", "gateway": "192.168.1.1", "dns": "8.8.8.8" },
"message": "Hello, waveshare!"
}
  • Publish any string or JSON to the topic Sub/ed229f15:
    • Publish any string: the board will print it verbatim to the serial port (see the message_arrived(...) code snippet earlier).

    • Publish a JSON command (e.g., {"led":2}): it will execute the corresponding action and republish the result to Pub/ed229f15 (it is recommended to subscribe to Pub/ed229f15 to see the execution reply).

      photo photo1