{"id":334,"date":"2026-01-14T08:38:34","date_gmt":"2026-01-14T08:38:34","guid":{"rendered":"https:\/\/www.r3tr0.net\/?p=334"},"modified":"2026-01-14T08:54:53","modified_gmt":"2026-01-14T08:54:53","slug":"adding-esp32-wifi-module-to-raspberry-pico-rp2040-rp2350-cpu-part-1","status":"publish","type":"post","link":"https:\/\/www.r3tr0.net\/index.php\/2026\/01\/14\/adding-esp32-wifi-module-to-raspberry-pico-rp2040-rp2350-cpu-part-1\/","title":{"rendered":"Adding ESP32 WiFi Module to Raspberry Pico RP2040 \/ RP2350 CPU &#8211; Part 1"},"content":{"rendered":"\n<p>As I continue my journey building stuff based on Raspberry CPU, for one project I need to build a custom RP2350 board and to onboard on the PCB a WiFi Module. <\/p>\n\n\n\n<p>Here is the source of the project on GitHub <a href=\"https:\/\/github.com\/vibr77\/ESP32C3_RP2350_WiFi\" data-type=\"link\" data-id=\"https:\/\/github.com\/vibr77\/ESP32C3_RP2350_WiFi\">https:\/\/github.com\/vibr77\/ESP32C3_RP2350_WiFi<\/a><\/p>\n\n\n\n<p>One could argue that using the recent release of the RM2 (existing on PICOW &amp; PICO2W) would be the easiest solution. This is true, and the software integration (the driver) is also very mature and delivers very good performance. <\/p>\n\n\n\n<p>The alternative approach is to consider module that would bring WiFi and Bluetooth supports.  Many solutions on the market such as ATWINC1500 or the family of ESP32 products. <\/p>\n\n\n\n<p>The key criterias for my projects are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Form factor,<\/li>\n\n\n\n<li>PCB integration capacity<\/li>\n\n\n\n<li>Performance,<\/li>\n\n\n\n<li>Maturity &amp; openness,<\/li>\n\n\n\n<li>Cost<\/li>\n<\/ul>\n\n\n\n<p>For all these reasons, I have selected the ESP32C3-WROOM-02-N4 chip from expressif<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"225\" height=\"225\" src=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/ESP32C3.jpeg\" alt=\"\" class=\"wp-image-336\" srcset=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/ESP32C3.jpeg 225w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/ESP32C3-100x100.jpeg 100w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/ESP32C3-150x150.jpeg 150w\" sizes=\"auto, (max-width: 225px) 100vw, 225px\" \/><\/figure>\n\n\n\n<p>On-board PCB antenna or external antenna connector<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2.4 GHz Wi-Fi (802.11b\/g\/n) and Bluetooth\u00ae&nbsp;5 module<\/li>\n\n\n\n<li>Built around ESP32-C3 series of SoCs, RISC-V single-core microprocessor<\/li>\n\n\n\n<li>Flash up to 16 MB<\/li>\n\n\n\n<li>15 GPIOs<\/li>\n<\/ul>\n\n\n\n<p>This module is available widely and also proposed by many PCBA manufacturers such as JLCPCB and PCBWAY. And the average price for qty 10+ is approximately 2.5$ (quite cheap). <\/p>\n\n\n\n<p>It bring everything need including the crystal, the flash, &#8230; ready to be integrated.<\/p>\n\n\n\n<p>The form factor is also very small. <\/p>\n\n\n\n<p>This module matches the following criteria: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Form factor,<\/li>\n\n\n\n<li>PCB integration capacity<\/li>\n\n\n\n<li>Cost<\/li>\n<\/ul>\n\n\n\n<p>From a maturity and performance hardware standpoint, this family of component is widely used in IOT and many benchmarks are available and are showing on the paper good results. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Experimentation breadboard<\/h2>\n\n\n\n<p>I use the following breadboard to test the integration of the RP2350A (Pico2 and the ESP32C3)<\/p>\n\n\n\n<p>I use the following parts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Raspberry PICO2<\/li>\n\n\n\n<li>ESP32C3-Mini Board<\/li>\n\n\n\n<li>LMP1117T-3.3 <strong>IMPORTANT  because the Pico2 is not able to deliver enough current during the Connect Phase<\/strong><\/li>\n\n\n\n<li>PushButton Switch (Reset Circuit)<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"592\" src=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363-1024x592.jpeg\" alt=\"\" class=\"wp-image-377\" srcset=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363-1024x592.jpeg 1024w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363-300x173.jpeg 300w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363-768x444.jpeg 768w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363-600x347.jpeg 600w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363.jpeg 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Software integration<\/h2>\n\n\n\n<p> The main question is the Software integration with the Raspberry Pico CPU family and to be able to reach the performance of the very RM2.<\/p>\n\n\n\n<p>After search the web, I found the 2 GitHub repositories <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/espressif\/esp-hosted\">https:\/\/github.com\/espressif\/esp-hosted<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/espressif\/esp-at\">https:\/\/github.com\/espressif\/esp-at<\/a><\/li>\n<\/ul>\n\n\n\n<p>ESP-Hosted <strong>Copying the readme: <\/strong><\/p>\n\n\n\n<p><strong>ESP-Hosted<\/strong>&nbsp;is an open-source solution that enables Espressif SoCs\/modules (like ESP32) to act as&nbsp;<strong>wireless communication co-processors<\/strong>&nbsp;for external host systems.<\/p>\n\n\n\n<p>It allows&nbsp;<strong>host devices<\/strong>&nbsp;(Linux-based systems or microcontrollers, MCUs) to add Wi-Fi and Bluetooth\/BLE capabilities via&nbsp;<strong>standard interfaces<\/strong>&nbsp;like SPI, SDIO, or UART.<\/p>\n\n\n\n<p><strong>ESP-AT<\/strong> Espressif SoCs serve as add-on modules, easily integrating wireless connectivity into existing products. To reduce development costs, Espressif offers a set of&nbsp;<a href=\"https:\/\/docs.espressif.com\/projects\/esp-at\/en\/latest\/esp32\/AT_Command_Set\/index.html\">AT commands<\/a>&nbsp;(select your target chip from the dropdown menu in the top left corner) that enable users to interface with Espressif products.<br><\/p>\n\n\n\n<p><strong>Conclusion<\/strong><\/p>\n\n\n\n<p>Within the list of the 3 ESP-HOSTED firmware (NG,FG,MCU) and the AT firmware, After hours of testing, playing with the different versions of the firmware, I came to the following conclusions: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>out of the box Pico integration does not exists,<\/li>\n\n\n\n<li>MCU is the best suit firmware solution to integrating with RP2350\/RP2040<\/li>\n\n\n\n<li>A lot of work ahead  to have it work and even more work to tune this up<\/li>\n<\/ul>\n\n\n\n<p><strong>The AT firmware:<\/strong><\/p>\n\n\n\n<p>Of course, I took the shortcut by testing the ESP-AT firmware. It is true that the integration is straightforward based on UART communication. the schema is very simple with only 2 GPIOs (UART TX,UART RX) to have the dialog between the PICO and the ESP32. <\/p>\n\n\n\n<p>After flashing the ESP32C2 with the last AT firmware version, using either UART0 or UART1 on the PICO, the fun starts quite quickly<\/p>\n\n\n\n<p>AT Command on UART:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">### Basic Commands\n```\nAT                      - Test AT startup\nAT+RST                  - Restart module\nAT+GMR                  - Check version info\n```\n\n### WiFi Station Commands\n```\nAT+CWMODE=1             - Set WiFi mode to Station\nAT+CWLAP                - List available APs\nAT+CWJAP=&quot;SSID&quot;,&quot;PWD&quot;  - Connect to AP\nAT+CWQAP                - Disconnect from AP\nAT+CIPSTA?              - Query Station IP address\n```\n\n### WiFi AP Commands\n```\nAT+CWMODE=2             - Set WiFi mode to AP\nAT+CWSAP=&quot;SSID&quot;,&quot;PWD&quot;,5,3  - Configure AP (ch=5, enc=WPA2_PSK)\n```\n\n### TCP\/IP Commands\n```\nAT+CIPSTATUS            - Get connection status\nAT+CIPSTART=&quot;TCP&quot;,&quot;192.168.1.1&quot;,8080  - Start TCP connection\nAT+CIPSEND=10           - Send 10 bytes of data\nAT+CIPCLOSE             - Close connection\n```<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Pin Configuration <\/strong><\/p>\n\n\n\n<p><strong>RP2040\/RP2350  side<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>UART pins for AT communication:<\/li>\n\n\n\n<li><strong>**TX**<\/strong> (RP2040 GPIO36) \u2192 <strong>**RX**<\/strong> (ESP32 GPIO6)<\/li>\n\n\n\n<li><strong>**RX**<\/strong> (RP2040 GPIO37) \u2190 <strong>**TX**<\/strong> (ESP32 GPIO7)<\/li>\n\n\n\n<li><\/li>\n<\/ul>\n\n\n\n<p><strong> ESP32-C3 AT Firmware Side<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Port: UART1<\/li>\n\n\n\n<li>TX: GPIO7 (connects to RP2040 GPIO37)<\/li>\n\n\n\n<li>RX: GPIO6 (connects to RP2040 GPIO36)<\/li>\n\n\n\n<li>CTS: GPIO5 (not connected, hardware flow control disabled)<\/li>\n\n\n\n<li>RTS: GPIO4 (not connected, hardware flow control disabled)<\/li>\n\n\n\n<li>Baudrate: 115200 (default)<\/li>\n\n\n\n<li><\/li>\n<\/ul>\n\n\n\n<p>The following code can be used to test the ESP with AT Firmware setup and the Pico:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void handle_at_test(void) {\n    printf(&quot;\\n=== AT Command Test ===\\n&quot;);\n    printf(&quot;Testing basic AT communication...\\n\\n&quot;);\n    \n    \/\/ Clear any pending data first\n    uint8_t dummy[256];\n    while (esp32_uart_read(dummy, sizeof(dummy)) &gt; 0) {\n        sleep_ms(10);\n    }\n    \n    printf(&quot;Sending: AT\\\\r\\\\n&quot;);\n    size_t sent = esp32_uart_write_string(&quot;AT\\r\\n&quot;);\n    printf(&quot;Sent %zu bytes\\n&quot;, sent);\n    \n    \/\/ Wait for response with multiple attempts\n    printf(&quot;Waiting for response...\\n&quot;);\n    uint8_t rx_buf[256];\n    int total_len = 0;\n    \n    for (int attempt = 0; attempt &lt; 20; attempt++) {\n        sleep_ms(50);\n        int rx_len = esp32_uart_read(rx_buf + total_len, sizeof(rx_buf) - total_len - 1);\n        if (rx_len &gt; 0) {\n            printf(&quot;  [%d] Received %d bytes\\n&quot;, attempt, rx_len);\n            total_len += rx_len;\n        }\n        \n        \/\/ Check if we got OK\n        if (total_len &gt; 0) {\n            rx_buf[total_len] = &#039;\\0&#039;;\n            if (strstr((char*)rx_buf, &quot;OK&quot;) != NULL) {\n                break;\n            }\n        }\n    }\n    \n    if (total_len &gt; 0) {\n        rx_buf[total_len] = &#039;\\0&#039;;\n        printf(&quot;\\n\u2713 Response (%d bytes):\\n%s\\n&quot;, total_len, rx_buf);\n        \n        \/\/ Print hex dump for debugging\n        printf(&quot;\\nHex dump: &quot;);\n        for (int i = 0; i &lt; total_len &amp;&amp; i &lt; 32; i++) {\n            printf(&quot;%02X &quot;, rx_buf[i]);\n        }\n        printf(&quot;\\n&quot;);\n    } else {\n        printf(&quot;\\n\u2717 No response received\\n&quot;);\n        printf(&quot;\\nTroubleshooting:\\n&quot;);\n        printf(&quot;  1. Check UART wiring (TX\u2194RX crossed)\\n&quot;);\n        printf(&quot;  2. Verify ESP32 is running AT firmware\\n&quot;);\n        printf(&quot;  3. Use Option 17 to monitor ESP32 UART output\\n&quot;);\n        printf(&quot;  4. Try resetting ESP32 (Option 1)\\n&quot;);\n    }\n}<\/code><\/pre>\n\n\n\n<p>All the AT functions are available on the project GitHub repository, I have also made a small LWIP integration to be able to use a TCP\/IP stack,<\/p>\n\n\n\n<p>Everything is working smoothly and very fast, however the main drawback of this solution is that it relies on UART, and UART is a serial communication without a clock signal. Even with changing the baud rate to 921 600 bps (the extreme limit for reliability) you do not get strong performance at all. This is mainly due to the UART speed but also to the overhead firmware inside serial communication to transport packet.<\/p>\n\n\n\n<p>I manage to do some testing with a remote iperf server in the same subnet and I have collected the following results: <\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void handle_at_test(void) {\n    printf(&quot;\\n=== AT Command Test ===\\n&quot;);\n    printf(&quot;Testing basic AT communication...\\n\\n&quot;);\n    \n    \/\/ Clear any pending data first\n    uint8_t dummy[256];\n    while (esp32_uart_read(dummy, sizeof(dummy)) &gt; 0) {\n        sleep_ms(10);\n    }\n    \n    printf(&quot;Sending: AT\\\\r\\\\n&quot;);\n    size_t sent = esp32_uart_write_string(&quot;AT\\r\\n&quot;);\n    printf(&quot;Sent %zu bytes\\n&quot;, sent);\n    \n    \/\/ Wait for response with multiple attempts\n    printf(&quot;Waiting for response...\\n&quot;);\n    uint8_t rx_buf[256];\n    int total_len = 0;\n    \n    for (int attempt = 0; attempt &lt; 20; attempt++) {\n        sleep_ms(50);\n        int rx_len = esp32_uart_read(rx_buf + total_len, sizeof(rx_buf) - total_len - 1);\n        if (rx_len &gt; 0) {\n            printf(&quot;  [%d] Received %d bytes\\n&quot;, attempt, rx_len);\n            total_len += rx_len;\n        }\n        \n        \/\/ Check if we got OK\n        if (total_len &gt; 0) {\n            rx_buf[total_len] = &#039;\\0&#039;;\n            if (strstr((char*)rx_buf, &quot;OK&quot;) != NULL) {\n                break;\n            }\n        }\n    }\n    \n    if (total_len &gt; 0) {\n        rx_buf[total_len] = &#039;\\0&#039;;\n        printf(&quot;\\n\u2713 Response (%d bytes):\\n%s\\n&quot;, total_len, rx_buf);\n        \n        \/\/ Print hex dump for debugging\n        printf(&quot;\\nHex dump: &quot;);\n        for (int i = 0; i &lt; total_len &amp;&amp; i &lt; 32; i++) {\n            printf(&quot;%02X &quot;, rx_buf[i]);\n        }\n        printf(&quot;\\n&quot;);\n    } else {\n        printf(&quot;\\n\u2717 No response received\\n&quot;);\n        printf(&quot;\\nTroubleshooting:\\n&quot;);\n        printf(&quot;  1. Check UART wiring (TX\u2194RX crossed)\\n&quot;);\n        printf(&quot;  2. Verify ESP32 is running AT firmware\\n&quot;);\n        printf(&quot;  3. Use Option 17 to monitor ESP32 UART output\\n&quot;);\n        printf(&quot;  4. Try resetting ESP32 (Option 1)\\n&quot;);\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>At 115200 baud (default):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Duration: 10.09 seconds<\/li>\n\n\n\n<li>Total sent: 98,310 bytes (0.09 MB)<\/li>\n\n\n\n<li>Chunks sent: 97<\/li>\n\n\n\n<li>Send errors: 0<\/li>\n\n\n\n<li>Throughput: 9.52 KB\/s (77.98 Kbps \/ 0.078 Mbps)<\/li>\n\n\n\n<li>Theoretical max: ~11.5 KB\/s<\/li>\n\n\n\n<li>Actual: <strong>**9.5 KB\/s**<\/strong> (83% efficiency)<\/li>\n\n\n\n<li>AT command overhead: ~17%<\/li>\n<\/ul>\n\n\n\n<p>\u2705  <strong>At 460800 baud (recommended):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Duration: 10.00 seconds<\/li>\n\n\n\n<li>Total sent: 237,574 bytes (0.23 MB)<\/li>\n\n\n\n<li>Chunks sent: 233<\/li>\n\n\n\n<li>Send errors: 0<\/li>\n\n\n\n<li>Throughput: 23.20 KB\/s (190.05 Kbps \/ 0.190 Mbps)<\/li>\n\n\n\n<li>Theoretical max: ~46 KB\/s<\/li>\n\n\n\n<li>Actual: <strong>**20-23 KB\/s**<\/strong> (43-50% efficiency)<\/li>\n\n\n\n<li>AT command overhead: ~50-57%<\/li>\n\n\n\n<li><strong>2.1-2.4x speed improvement<\/strong> over 115200 baud<\/li>\n\n\n\n<li>Performance varies based on network conditions and server responsiveness<\/li>\n<\/ul>\n\n\n\n<p><strong>At 921600 baud (ultra-high-speed<\/strong>)<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Duration: 10.02 seconds<\/li>\n\n\n\n<li>Total sent: 212,998 bytes (0.20 MB)<\/li>\n\n\n\n<li>Chunks sent: 209<\/li>\n\n\n\n<li>Send errors: 0<\/li>\n\n\n\n<li>Throughput: 20.77 KB\/s (170.11 Kbps \/ 0.170 Mbps)<\/li>\n\n\n\n<li>Theoretical max: ~92 KB\/s <\/li>\n<\/ul>\n\n\n\n<p>I suspect some external factors limiting the results of the tests, but still the overall performance of the AT Firmware based on UART is low and suitable for small IoT projects (which is not my case). <\/p>\n\n\n\n<p><strong>To summarize the Experience of the AT firmware: <\/strong><\/p>\n\n\n\n<p>\u2705 Good:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Easy to integrate,<\/li>\n\n\n\n<li>very few GPIO used,<\/li>\n\n\n\n<li>Software integration is straightforward,<\/li>\n<\/ul>\n\n\n\n<p>\u274c Bad:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Performance are poor for non IoT project, when above 1MB throughput is required. the RM2 WiFi module using SPI can delivery 4-6 Mbps throuput.<\/li>\n\n\n\n<li><\/li>\n<\/ul>\n\n\n\n<p>I will keep my work on using the AT firmware for any small IoT project that does not require strong performance but straightforward integration.<\/p>\n\n\n\n<p><strong>The ESP-HOSTED-MCU firmware, the Cliff&#8230;<\/strong><\/p>\n\n\n\n<p>After spending a couple of hours on internet to find a clean integration of this firmware, I came to the conclusion that I will have to do it by myself&#8230; (maybe I miss a project that exists). <\/p>\n\n\n\n<p>There are some exemples of integration using the ESP32 communicating with another ESP32 but not track record of using a RP2040 or RP2350 to with the ESP32-HOSTED-MCU&#8230; <\/p>\n\n\n\n<p>I even tried to use a bit of AI with Claude Antropic, and every time the agent proposed to switch to the AT firmware to avoid the integration complexity&#8230; It is a signal that things will not be as easy as I thought.<\/p>\n\n\n\n<p>The truth is that the existing documentation is very light and does go into details, and it is not easy to get the full picture.<\/p>\n\n\n\n<p>My exploration:<\/p>\n\n\n\n<p>The firmware relies either on SPI or SDIO, and I choose to use SPI integration. On top the SPI GPIO (MISO,MOSI,CLK,CS), 2 additional GPIOs are needed: Data Ready DR, and Handshake. A total of 6 GPIOs (compared to the 2 of the AT Firmware). If you want a full integration including firmware update 4 additional GPIOs are needed (BOOT, EN, UART TX, UART RX). <\/p>\n\n\n\n<p>My project requires to be able to update the ESP32 firmware and I have done a 6+4 GPios integration. <\/p>\n\n\n\n<p>GPIO Description:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>SPI MISO: classic Master Input, Slave Output , SPI RX<\/li>\n\n\n\n<li>SPI MOSI, classic Master Output, Slave Input, SPI TX<\/li>\n\n\n\n<li>SPI CLK, clock signal<\/li>\n\n\n\n<li>SPI CS, Active Low Chip Enable signal (it will play an important role in)<\/li>\n\n\n\n<li>Handshake: Active High, High when the ESP32 is ready to start a SPI transaction,<\/li>\n\n\n\n<li>Data Ready: Active High, High when data are to be transferred from the ESP32 to the RP2040\/2350.<\/li>\n<\/ul>\n\n\n\n<p>Global Process flow:<\/p>\n\n\n\n<p>The RP2040\/RP2350 is the Host Master, the slave is the ESP32.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Master to send Data to the slave: the Master asserts (active low) the CS GPIOs when the handshake signal is HIGH (active High) to signal that a new SPI transaction will happen. Then the master sends data via the SPI. <strong>The Master deassert the CS GPIOs to signal the end of the transaction. This last point is extremely important, otherwise it will disturb the ESP32 ISR. <\/strong><\/li>\n<\/ul>\n\n\n\n<p>The Slave to send data to the Master: the slave signal with the <strong>DATA READY<\/strong> GPIO that a new data chunk is ready. The Master starts the same dialog as the previous one, and then the slave dessert the <strong>DATA READY<\/strong> GPIO<\/p>\n\n\n\n<p>The GPIO mapping used:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>SPI MOSI<\/strong> (Master Out): GPIO2<\/li>\n\n\n\n<li><strong>SPI MISO<\/strong> (Slave Out): GPIO5<\/li>\n\n\n\n<li><strong>SPI CS<\/strong>: GPIO10<\/li>\n\n\n\n<li><strong>SPI CLK<\/strong>: GPIO6<\/li>\n\n\n\n<li><strong>Handshake<\/strong>: GPIO3<\/li>\n\n\n\n<li><strong>Data Ready:<\/strong> GPIO04<\/li>\n\n\n\n<li><strong>BOOT:<\/strong> GPIO09<\/li>\n\n\n\n<li><strong>EN: <\/strong>Reset Pin<\/li>\n<\/ul>\n\n\n\n<p><strong>Connection Table<\/strong><\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary><\/summary>\n<div class=\"wp-block-group is-layout-constrained wp-block-group-is-layout-constrained\">\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:100%\">\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>RP2350B<\/strong><\/td><td><strong>Function<\/strong><\/td><td><strong>Direction<\/strong><\/td><td><strong>ESP32-C3<\/strong><\/td><\/tr><tr><td>GPIO04<\/td><td>SPI MISO<\/td><td>Input<\/td><td>GPIO05<\/td><\/tr><tr><td>GPIO06<\/td><td>SPI SCK<\/td><td>Output<\/td><td>GPIO06<\/td><\/tr><tr><td>GPIO07<\/td><td>SPI MOSI<\/td><td>Output<\/td><td>GPIO02<\/td><\/tr><tr><td>GPIO08<\/td><td>SPI CS<\/td><td>Output<\/td><td>GPIO10<\/td><\/tr><tr><td>GPIO09<\/td><td>Handshake<\/td><td>Input<\/td><td>GPIO03<\/td><\/tr><tr><td>GPIO10<\/td><td>Data Ready<\/td><td>Input<\/td><td>GPIO04<\/td><\/tr><tr><td>GPIO40<\/td><td>BOOT<\/td><td>Output<\/td><td>GPIO09<\/td><\/tr><tr><td>GPIO39<\/td><td>EN<\/td><td>Output<\/td><td>RESET<\/td><\/tr><tr><td>GND<\/td><td>Ground<\/td><td>GND<\/td><td>GND<\/td><\/tr><tr><td>GPIO04<\/td><td>UART TX<\/td><td>Output<\/td><td>GPIO20<\/td><\/tr><tr><td>GPIO05 <\/td><td>UART RX<\/td><td>Input<\/td><td>GPIO19<\/td><\/tr><\/tbody><\/table><\/figure>\n<\/div>\n<\/div>\n<\/div>\n<\/details>\n\n\n\n<p>One very common mistake when  doing prototyping is to forget to have a common ground between to active part dialoging via GPIO wire. In spent a few hours to understand this very obvious statement. The RP2350 and the ESP32 need to have a common ground do not forget it. <\/p>\n\n\n\n<p>The pin setup on the Pico can be changed very easily, however one needs to follow the at least the available UART \/ SPI0 &amp; SPI1 schemes. This is the same for the ESP32, for my need I changed the GPIO setup in the firmware sdkconfig file. You can check the proper ESP32 pin configuration during the boot upon the firmware using the UART terminal connexion with log level information.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"677\" src=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-1024x677.png\" alt=\"\" class=\"wp-image-350\" srcset=\"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-1024x677.png 1024w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-600x397.png 600w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-300x198.png 300w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-768x508.png 768w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-1536x1015.png 1536w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput-1200x793.png 1200w, https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/RaspberryPicoPinput.png 1740w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><strong>Now lets start the software integration<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Software integration<\/h2>\n\n\n\n<p>The project source code is available on GitHub here, <\/p>\n\n\n\n<p>The following capabilities has been developed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>SPI Communication<\/li>\n\n\n\n<li>WiFi Module Init<\/li>\n\n\n\n<li>WiFi Connexion \/ Disconnexion<\/li>\n\n\n\n<li>LWIP stack integration<\/li>\n\n\n\n<li>ESP32 Reset &amp; Firmware update. <\/li>\n<\/ul>\n\n\n\n<p>The project structure is the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">```<br>Project Source<br>\u251c\u2500\u2500 CMakeLists.txt               # Build configuration<br>\u251c\u2500\u2500 README.md                    # Project Description<br>\u251c\u2500\u2500 THROUGHPUT_TEST.md           # Network performance testing guide<br>\u251c\u2500\u2500 NETWORK_ARCHITECTURE.md      # lwIP integration details<br>\u251c\u2500\u2500 include\/                     # Header files<br>\u2502   \u251c\u2500\u2500 pin_config.h             # Gpio Pin definitions<br>\u2502   \u251c\u2500\u2500 esp32_control.h          # ESP32 control functions<br>\u2502   \u251c\u2500\u2500 esp32_spi.h              # SPI interface<br>\u2502   \u251c\u2500\u2500 esp32_uart.h             # UART interface<br>\u2502   \u251c\u2500\u2500 esp_hosted_mcu_control.h # ESP-Hosted-MCU protocol<br>\u2502   \u251c\u2500\u2500 esp_hosted_mcu_rpc.h     # RPC implementation<br>\u2502   \u251c\u2500\u2500 wifi_manager.h           # WiFi management<br>\u2502   \u251c\u2500\u2500 lwip_netif.h             # lwIP network interface<br>\u2502   \u251c\u2500\u2500 lwip_tcp_test.h          # TCP throughput testing<br>\u2502   \u251c\u2500\u2500 lwipopts.h               # lwIP configuration<br>\u2502   \u2514\u2500\u2500 firmware_uploader.h      # Firmware upload<br>\u2514\u2500\u2500 src\/                         # Source files<br>    \u251c\u2500\u2500 main.c                   # Main application<br>    \u251c\u2500\u2500 esp32_control.c          # ESP32 control implementation<br>    \u251c\u2500\u2500 esp32_spi.c              # SPI implementation<br>    \u251c\u2500\u2500 esp32_uart.c             # UART implementation<br>    \u251c\u2500\u2500 esp_hosted_mcu_control.c # ESP-Hosted-MCU implementation<br>    \u251c\u2500\u2500 esp_hosted_mcu_rpc.c     # RPC implementation<br>    \u251c\u2500\u2500 wifi_manager.c           # WiFi implementation<br>    \u251c\u2500\u2500 lwip_netif.c             # lwIP bridge to ESP-Hosted<br>    \u251c\u2500\u2500 lwip_tcp_test.c          # TCP throughput test<br>    \u2514\u2500\u2500 firmware_uploader.c      # Firmware upload implementation<br>```<\/pre>\n\n\n\n<p> The Typical End to End process flow from Boot to use of LWIP:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Step 1: Setup the GPIO configuration for Data Ready and Handshake,<\/li>\n\n\n\n<li>Step 2: Init the SPI communication protocol,<\/li>\n\n\n\n<li>Step 3: Reset the ESP32,<\/li>\n\n\n\n<li>Step 4: Handshake with the ESP32,<\/li>\n\n\n\n<li>Step 5: Configure the WiFi,<\/li>\n\n\n\n<li>Step 6: Init the Wifi Module,<\/li>\n\n\n\n<li>Step 7: Connect Wifi,<\/li>\n\n\n\n<li>Step 8: Enable the LWIP Stack,<\/li>\n\n\n\n<li>Step 9: use Data.<\/li>\n<\/ul>\n\n\n\n<p><strong>Step 1: Setup the GPIO configuration<\/strong><\/p>\n\n\n\n<p>The function to configure the GPIO is located in esp32_control.h<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void esp32_control_init(void);<\/code><\/pre>\n\n\n\n<p>It will configure the following GPIO (Direction, level):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>BOOT (BOOT MODE)<\/li>\n\n\n\n<li>EN (RESET)<\/li>\n\n\n\n<li>HANDSHAKE<\/li>\n\n\n\n<li>DATA READY<\/li>\n<\/ul>\n\n\n\n<p>The pin assignments are available in pin_config.h<\/p>\n\n\n\n<p><strong>Step 2: Init the SPI Communication Protocol<\/strong><\/p>\n\n\n\n<p>The function to init SPI is located in esp32_spi.c :<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void esp32_spi_init(void);<\/code><\/pre>\n\n\n\n<p>it uses the followings define in pin_config.h<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">#define ESP32_SPI_INSTANCE  spi1\n#define ESP32_SPI_MISO      8               \/\/ RX   ESP32 GPIO05\n#define ESP32_SPI_MOSI      11              \/\/ TX   ESP2  GPIO02\n#define ESP32_SPI_SCK       10              \/\/ CLK  ESP32 GPIO06\n#define ESP32_SPI_CS        9               \/\/ CS   ESP32 GPIO10<\/code><\/pre>\n\n\n\n<p>but also: <\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">#define ESP32_SPI_BAUDRATE  (25 * 1000 * 1000) \/<\/code><\/pre>\n\n\n\n<p>the SPI mode is 3, and after doing test on a breadboard, 25 Mhz for SPI clock frequency works perfectly, and using the clean pcb, one should be able to increase the speed to 40 MHz and thus the overall throughput of the solution <\/p>\n\n\n\n<p><strong>Step 3: Reset the ESP32<\/strong><\/p>\n\n\n\n<p>This step is of course not mandatory is you have already triggered a connexion to the ESP32.<\/p>\n\n\n\n<p>The reset function is located in esp32_control.c:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void esp32_reset(void);<\/code><\/pre>\n\n\n\n<p>It will simply toggle the EN pin for a few milliseconds to trigger a reset of the ESP32.<\/p>\n\n\n\n<p><strong>Step 4: Handshake with the ESP32<\/strong><\/p>\n\n\n\n<p>This is were the complexity starts, but it will be sorted out diligently;)<\/p>\n\n\n\n<p>The ESP32 is sending 2 type of Information:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Control packet,<\/li>\n\n\n\n<li>Data packet<\/li>\n<\/ul>\n\n\n\n<p>Control packet are used to send event from the ESP32 to the PICO (Init, Configuration Success, Connect , Disconnect,&#8230;)<\/p>\n\n\n\n<p>Data packet are regular TCP\/IP data packets<\/p>\n\n\n\n<p>Just right after the RESET, the ESP32 send a control packet called INIT and the Master (RP2350\/RP2040) should capture it when Data Ready is HIGH and when Handshake is HIGH (both active High signal). <\/p>\n\n\n\n<p>Both Control packet &amp; Data packet processing is managed by a central function through 2 circular buffers.<\/p>\n\n\n\n<p>The central function is driven by HANDSHAKE &amp; DATA READY ISR located in esp32_control.c<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">static void data_ready_irq_handler(uint gpio, uint32_t events);<\/code><\/pre>\n\n\n\n<p>This Interrupt function will monitor both EDGE RISE of HANDSHAKE &amp; DATA READY. <\/p>\n\n\n\n<p>Packet are process via the callback function defined during the INIT process located in esp32_control.c:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">void esp32_set_packet_callback(void (*callback)(void));<\/code><\/pre>\n\n\n\n<p>The callback is defined by:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-\">esp32_set_packet_callback(&amp;process_packets_immediate);<\/code><\/pre>\n\n\n\n<p>The function process_packets_immediate defined is main.c<\/p>\n\n\n\n<p>it will process by batch of 5 packets message queue to avoid the ISR blocking too long using the function defined in esp_hosted_mcu_control.c<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">esp_hosted_mcu_process_packets()<\/code><\/pre>\n\n\n\n<p>It will manage the split of packet according to the type control \/ data<\/p>\n\n\n\n<p>Here we are looking for INIT packet to be sent to the control packet queue. <\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\"> control_packet_enqueue(&amp;g_control_queue, &amp;rx_header, rx_payload, len);<\/code><\/pre>\n\n\n\n<p>The function located in esp_hosted_mcu_control.c is in charge of handshake execution:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">bool esp_hosted_mcu_handshake(void);<\/code><\/pre>\n\n\n\n<p><strong>Step 5: Init the WiFi<\/strong><\/p>\n\n\n\n<p>The following sequence of code is needed to be done to init the WiFi<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">printf(&quot;Initializing WiFi...\\n&quot;);\n    if (!esp_hosted_rpc_wifi_init()) {\n        printf(&quot;\u2717 WiFi init failed\\n&quot;);\n        return;\n    }\n    esp32_state=ESP32_STATE_WIFI_INIT;\n\n    \/\/ Set WiFi mode to STA\n    printf(&quot;Setting WiFi mode to STA...\\n&quot;);\n    if (!esp_hosted_rpc_set_wifi_mode(1)) {  \/\/ 1 = WIFI_MODE_STA\n        printf(&quot;\u2717 Failed to set WiFi mode\\n&quot;);\n        return;\n    }\n    \n    \/\/ Start WiFi  \n    printf(&quot;Starting WiFi...\\n&quot;);\n    if (!esp_hosted_rpc_wifi_start()) {\n        printf(&quot;\u2717 WiFi start failed\\n&quot;);\n        return;\n    }<\/code><\/pre>\n\n\n\n<p><strong>Step 6: Configure the Wifi <\/strong><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">\nprintf(&quot;\\nConfiguring WiFi credentials...\\n&quot;);\n    \n    \/\/ WiFi should already be initialized and started by handle_mcu_test() at boot\n    \/\/ Just configure credentials and connect\n    \n    rpc_wifi_config_t config;\n    memset(&amp;config, 0, sizeof(config));\n    \n    const char *ssid = &quot;SSID&quot;;\n    const char *password = &quot;PASSWORD&quot;;\n    \n    strncpy(config.ssid, ssid, sizeof(config.ssid) - 1);\n    strncpy(config.password, password, sizeof(config.password) - 1);\n    \n    \/\/ Update status: connecting\n    wifi_status_set_connecting(config.ssid);\n    \n    printf(&quot;Setting WiFi config for &#039;%s&#039;...\\n&quot;, config.ssid);\n    if (!esp_hosted_rpc_set_wifi_config(&amp;config)) {\n        printf(&quot;\u2717 Failed to set WiFi config\\n&quot;);\n        return;\n    }\n    printf(&quot;\u2713 WiFi config set\\n&quot;);\n    \n    \/\/ Brief delay for config processing\n    sleep_ms(500);<\/code><\/pre>\n\n\n\n<p><strong>Step 7 Connect to the WiFi<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-c\">\n    printf(&quot;\\nConnecting to &#039;%s&#039;...\\n&quot;, config.ssid);\n    if (esp_hosted_rpc_wifi_connect()) {\n        \/\/ WiFi connect returns true if request succeeded, but doesn&#039;t guarantee connection\n        \/\/ Wait for connection to complete with retries\n        wifi_ap_record_t ap_info;\n        bool connected = false;\n        bool connection_failed = false;\n        bool seen_connect_event = false;\n        \n        printf(&quot;Waiting for WiFi connection to complete&quot;);\n        for (int attempt = 0; attempt &lt; 30; attempt++) {  \/\/ 30 seconds timeout (1s per attempt)\n            printf(&quot;.&quot;);\n            fflush(stdout);\n            sleep_ms(1000);  \/\/ Wait 1 second between attempts\n            \n            \/\/ Process any RPC events (like disconnect events indicating auth failure)\n            \/\/ This will call esp32_event_handler() for any pending events\n            esp_hosted_mcu_process_queued_control_packets();\n            \n            \/\/ Check WiFi state machine\n            wifi_status_singleton_t* status = wifi_status_get();\n            \n            \/\/ Debug: show current state\n            if (attempt == 0 || status-&gt;connection_state != WIFI_STATE_CONNECTING) {\n                printf(&quot;[state=%d:%s]\\n&quot;, status-&gt;connection_state, \n                wifi_status_connection_state_str(status-&gt;connection_state));\n                fflush(stdout);\n            }\n            \n            \/\/ Track if we see a connect event\n            if (status-&gt;connection_state == WIFI_STATE_CONNECTED) {\n                seen_connect_event = true;\n            }\n            \n            \/\/ Only treat disconnect as failure if:\n            \/\/ 1. We&#039;ve waited at least 3 seconds (allow handshake time)\n            \/\/ 2. We either saw a connect event, or we&#039;re past second 5 with no connect\n            if (status-&gt;connection_state == WIFI_STATE_DISCONNECTED &amp;&amp; \n                (attempt &gt;= 3 || (attempt &gt;= 5 &amp;&amp; !seen_connect_event))) {\n                \/\/ Connection was explicitly rejected by ESP32\n                printf(&quot;\\n\u2717 WiFi connection failed with reason: %s\\n&quot;, \n                       wifi_status_disconnect_reason_str(status-&gt;disconnect_reason));\n                connection_failed = true;\n                break;\n            }\n            \n            if (status-&gt;connection_state == WIFI_STATE_CONNECTED) {\n                \/\/ Successfully connected - get AP info for display\n                if (esp_hosted_rpc_wifi_sta_get_ap_info(&amp;ap_info) &amp;&amp; ap_info.ssid[0] != &#039;\\0&#039;) {\n                    connected = true;\n                    break;\n                }\n            }\n        }\n        printf(&quot;\\n&quot;);\n        \n        if (connected) {\n            \/\/ Connection verified - AP info has non-empty SSID matching our request\n            printf(&quot;\u2713 Successfully connected to &#039;%s&#039;!\\n&quot;, config.ssid);\n            \n            wifi_status_set_connected(\n                (char*)ap_info.ssid, \n                ap_info.bssid,\n                ap_info.channel,\n                ap_info.rssi,\n                ap_info.authmode\n            );\n            \n            \/\/ Get network config and update status\n            rpc_dhcp_status_t dhcp_status;\n            if (esp_hosted_rpc_get_dhcp_status(&amp;dhcp_status) &amp;&amp; dhcp_status.dhcp_up) {\n                wifi_status_update_network(\n                    dhcp_status.ip,\n                    dhcp_status.gateway,\n                    dhcp_status.netmask,\n                    dhcp_status.dns_up ? dhcp_status.dns : NULL\n                );\n            }\n            \n            esp32_state=ESP32_STATE_WIFI_CONNECTED;\n        } else if (connection_failed) {\n            \/\/ Connection was explicitly rejected by ESP32 (disconnect event received)\n            printf(&quot;\u2717 Connection rejected by ESP32\\n&quot;);\n            wifi_status_set_disconnected(WIFI_REASON_AUTH_FAIL);\n        } else {\n            \/\/ Connection timeout - stuck in CONNECTING state after 30 seconds\n            \/\/ This usually indicates wrong password or network not responding\n            printf(&quot;\u2717 Connection timeout after 30 seconds\\n&quot;);\n            printf(&quot;WiFi remains in CONNECTING state - possible causes:\\n&quot;);\n            printf(&quot;  - Incorrect SSID or password (most likely)\\n&quot;);\n            printf(&quot;  - AP out of range\\n&quot;);\n            printf(&quot;  - Network authentication issues\\n&quot;);\n            printf(&quot;  - ESP32 WiFi not responding\\n&quot;);\n            wifi_status_set_disconnected(WIFI_REASON_AUTH_FAIL);\n        }<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>To be continued with the performance running and outcome in part 2<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>As I continue my journey building stuff based on Raspberry CPU, for one project I need to build a custom RP2350 board and to onboard on the PCB a WiFi Module. Here is the source of the project on GitHub https:\/\/github.com\/vibr77\/ESP32C3_RP2350_WiFi One could argue that using the recent release of the RM2 (existing on PICOW [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":377,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[31],"tags":[],"class_list":["post-334","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-experiment"],"jetpack_featured_media_url":"https:\/\/www.r3tr0.net\/wp-content\/uploads\/2026\/01\/IMG_0762-Large-e1768377490363.jpeg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/334","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/comments?post=334"}],"version-history":[{"count":26,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/334\/revisions"}],"predecessor-version":[{"id":384,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/posts\/334\/revisions\/384"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/media\/377"}],"wp:attachment":[{"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/media?parent=334"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/categories?post=334"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.r3tr0.net\/index.php\/wp-json\/wp\/v2\/tags?post=334"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}