If you are searching for how to set config parameters for AsyncWebServer library in Arduino, the first thing to understand is that you are likely working with the ESPAsyncWebServer library on an ESP32 or ESP8266. Standard 8-bit AVR Arduinos (like the Uno or Mega) lack the RAM and RTOS capabilities to run asynchronous TCP stacks. Furthermore, the configuration parameters for this library are not set at runtime in your .ino sketch. They are compile-time macros governed by the underlying AsyncTCP library and the ESP-IDF build system.
To tune buffer sizes, queue limits, and thread priorities, you must inject build flags before compilation. This guide details the exact hardware requirements, the critical configuration macros, and the debugging steps needed to stop the dreaded queue panics that plague poorly configured async servers.
Board Requirements and Hardware Pin Mapping
This guide targets the ESP32-WROOM-32 (DevKit V1) variant. The ESP32's dual-core architecture and 520 KB of SRAM make it the ideal candidate for handling concurrent asynchronous HTTP and WebSocket connections. We will pair the web server with a BME280 environmental sensor to provide real-time data to the web interface, giving the server actual work to do.
Parts List
- Microcontroller: ESP32-WROOM-32 DevKit V1 (30-pin or 38-pin variant)
- Sensor: BME280 Breakout Board (I2C version, 3.3V logic)
- Indicator: 5mm LED with 330Ω current-limiting resistor
- Power: 5V/2A USB power supply (do not rely on PC USB ports for WiFi-heavy loads)
Pin Mapping Table
| ESP32 GPIO | Component | Function | Notes |
|---|---|---|---|
| GPIO 21 | BME280 SDA | I2C Data | Default I2C SDA on ESP32 |
| GPIO 22 | BME280 SCL | I2C Clock | Default I2C SCL on ESP32 |
| GPIO 2 | LED Anode | Status Indicator | Built-in LED on most DevKits |
| GND | BME280 GND / LED Cathode | Common Ground | Ensure solid breadboard connection |
| 3V3 | BME280 VCC | Logic Power | Do NOT connect BME280 to 5V (VIN) |
Core AsyncWebServer and AsyncTCP Config Parameters
The ESPAsyncWebServer library relies on AsyncTCP to handle the low-level socket management. When your server crashes under load, it is almost always because the default FreeRTOS queue sizes and stack allocations are too small for your specific traffic profile. Below is the data-dense reference table for the critical compile-time macros you need to override.
| Macro Parameter | Default Value | RAM Impact | When to Modify |
|---|---|---|---|
CONFIG_ASYNC_TCP_QUEUE_SIZE |
64 | ~1.5 KB | Increase to 128 if handling >20 concurrent WebSocket clients or high-frequency sensor polling. |
CONFIG_ASYNC_TCP_STACK_SIZE |
8192 | 8 KB | Increase to 16384 if you are parsing large JSON payloads or doing heavy string manipulation inside request callbacks. |
CONFIG_ASYNC_TCP_RUNNING_CORE |
1 | 0 | Set to any (or 0) if Core 1 is bottlenecked by heavy WiFi/Bluetooth operations, allowing the OS to balance the load. |
CONFIG_ASYNC_TCP_USE_WDT |
1 (Enabled) | 0 | Disable (set to 0) ONLY during heavy OTA firmware updates to prevent the Watchdog Timer from panicking the core. |
CONFIG_ASYNC_TCP_MAX_ACK_TIME |
5000 | 0 | Lower to 2000 for highly reliable local networks to drop dead connections faster and free up socket memory. |
Setting Build Flags in PlatformIO and Arduino IDE
Because these parameters are C-preprocessor macros, you cannot change them using #define at the top of your .ino sketch; the underlying library files are compiled before your sketch is parsed. You must pass them as build flags.
The PlatformIO Method (Recommended)
PlatformIO is the professional standard for ESP32 development. Open your platformio.ini file and add the flags to the build_flags directive. This guarantees the macros are injected into the AsyncTCP compilation unit.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
build_flags =
-DCONFIG_ASYNC_TCP_QUEUE_SIZE=128
-DCONFIG_ASYNC_TCP_STACK_SIZE=16384
-DCONFIG_ASYNC_TCP_RUNNING_CORE=0
-DCONFIG_ASYNC_TCP_USE_WDT=1
lib_deps =
me-no-dev/ESPAsyncWebServer
me-no-dev/AsyncTCP
adafruit/Adafruit BME280 Library
The Arduino IDE 2.x Method
If you are strictly using the Arduino IDE, you cannot easily pass custom build flags without modifying core files. The most reliable workaround in Arduino IDE 2.x is to use the build.extra_flags property in a custom boards.txt entry, or simply switch to PlatformIO. Attempting to edit the AsyncTCP.h file directly inside the Arduino libraries folder is a bad practice; your changes will be wiped the moment the library manager updates the package.
Complete Compilable Code with Error Handling
The following sketch initializes the BME280 sensor, connects to WiFi, and sets up an asynchronous web server. It includes a chunked HTTP response for the main page and a WebSocket for live data streaming. Notice the strict pin definitions and hardware initialization error handling.
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
// --- Hardware Pin Definitions ---
#define PIN_STATUS_LED 2
#define I2C_SDA_PIN 21
#define I2C_SCL_PIN 22
// --- Network Credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// --- Global Objects ---
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
Adafruit_BME280 bme;
// --- Sensor Data ---
float tempC = 0.0;
float humidity = 0.0;
unsigned long lastWsBroadcast = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_STATUS_LED, OUTPUT);
digitalWrite(PIN_STATUS_LED, LOW);
// Initialize I2C with explicit pins
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
// Initialize BME280 with error handling
if (!bme.begin(0x76, &Wire)) {
Serial.println("[ERROR] Could not find a valid BME280 sensor, check wiring!");
while (1) {
digitalWrite(PIN_STATUS_LED, HIGH);
delay(100);
digitalWrite(PIN_STATUS_LED, LOW);
delay(100);
}
}
Serial.println("[OK] BME280 initialized.");
// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi...");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 40) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[OK] Connected! IP: " + WiFi.localIP().toString());
digitalWrite(PIN_STATUS_LED, HIGH);
} else {
Serial.println("\n[ERROR] WiFi connection failed. Restarting...");
ESP.restart();
}
// --- WebSocket Setup ---
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
} else if (type == WS_EVT_DISCONNECT) {
Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id());
}
});
server.addHandler(&ws);
// --- HTTP Route Setup ---
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String html = "<!DOCTYPE html><html><head><title>ESP32 Async Server</title></head>";
html += "<body><h1>ESP32 Environmental Monitor</h1>";
html += "<p>Temperature: <span id='temp'>--</span> °C</p>";
html += "<p>Humidity: <span id='hum'>--</span> %</p>";
html += "<script>var ws=new WebSocket('ws://'+window.location.hostname+'/ws');";
html += "ws.onmessage=function(evt){var d=JSON.parse(evt.data);";
html += "document.getElementById('temp').innerText=d.temp.toFixed(1);";
html += "document.getElementById('hum').innerText=d.hum.toFixed(1);};</script>";
html += "</body></html>";
request->send(200, "text/html", html);
});
server.begin();
Serial.println("[OK] AsyncWebServer started.");
}
void loop() {
ws.cleanupClients(); // Prevent memory leaks from stale connections
// Read sensors and broadcast via WebSocket every 1000ms
if (millis() - lastWsBroadcast > 1000) {
lastWsBroadcast = millis();
tempC = bme.readTemperature();
humidity = bme.readHumidity();
if (ws.count() > 0) {
String json = "{\"temp\":" + String(tempC) + ",\"hum\":" + String(humidity) + "}";
ws.textAll(json);
}
}
}
Debugging: Exact Error Strings and Ranked Causes
When configuring asynchronous servers, the ESP32 will inevitably panic if the RTOS queues overflow or the watchdog timer starves. If your serial monitor spits out the following exact error string, your configuration parameters are misaligned with your workload.
...
assert failed: xQueueGenericSend queue.c:1234 (pxQueue->pcHead != ((void *)0) || pxQueue->u.xSemaphore.xMutex)
Ranked Causes for Queue Panics
- Blocking Code in Callbacks: You placed a
delay(), a longSerial.print()loop, or a blocking I2C read directly inside anonRequestor WebSocket callback. The async paradigm requires callbacks to execute in microseconds. - Undersized TCP Queue: The default
CONFIG_ASYNC_TCP_QUEUE_SIZEof 64 is too small for the burst traffic your server is receiving, causing the RTOS queue to reject new packets and trigger the assertion. - Core Contention: The WiFi stack and the AsyncTCP task are both fighting for CPU time on Core 1, causing the Watchdog Timer (WDT) to assume the core has locked up.
The First 3 Things to Check When It Fails
- Verify Build Flags Compiled: Open the verbose build output in PlatformIO. Search for
-DCONFIG_ASYNC_TCP_QUEUE_SIZE. If it is missing, the compiler used the defaults, and yourplatformio.inisyntax is wrong. - Audit Callbacks for Blocking Calls: Search your entire sketch for
delay(). If any exist inside a server route or WebSocket event, move them to the mainloop()using non-blockingmillis()timers. - Measure the 3.3V Rail: WiFi transmission spikes can draw 300mA+. If your USB cable or breadboard power rail dips below 3.1V, the ESP32 will brownout, corrupting RAM and mimicking a queue panic. Use a multimeter to verify the 3.3V pin under load.
Extending and Simplifying the Build
Once your base configuration is stable, you will need to decide how to scale the project based on your end goal.
How to Extend: Serve Static Files via LittleFS
Hardcoding HTML strings in C++ is inefficient and consumes precious flash memory compilation time. To extend this build, format your ESP32's SPIFFS/LittleFS partition and upload your index.html, style.css, and script.js files. You can then replace the inline HTML route with a single line:
server.serveStatic("/", LittleFS, "/");
This offloads the string processing to the filesystem and drastically reduces the compiled binary size.
How to Simplify: Drop WebSockets for HTTP Polling
If your application only needs to update a dashboard every 5 seconds, WebSockets are overkill and consume unnecessary TCP sockets and RAM. Simplify the build by removing the AsyncWebSocket handler entirely. Instead, create a simple /api/data JSON endpoint and use the JavaScript setInterval() function with fetch() on the client side. This reduces the CONFIG_ASYNC_TCP_QUEUE_SIZE requirement back to the default 64 and frees up roughly 15 KB of SRAM.






