Embedded Systems Meets ChatGPT: 10 Prompts That Turn Datasheets, ESP32 Firmware, and I2C Debugging Into a Weekend Project
Every embedded developer knows the ritual: you buy an ESP32 board, a BME280 sensor, and a relay module, then spend three evenings in the datasheet looking for the register that controls oversampling. The datasheet is 50+ pages, the vendor's library is abandoned, and the I2C bus returns 0xFF for no obvious reason. This is where a well-crafted prompt saves you hours. Not because the model "knows" your board, but because it can structure datasheet logic, propose test code, and walk through a wiring diagram step by step.
Below are 10 prompts I use regularly in embedded work. They cover the full path: from choosing a microcontroller and reading a datasheet, through generating sketches and debugging buses, to power budgeting, FreeRTOS tasks, and OTA updates on ESP32. Each prompt is copy-paste ready, with an example and a note on when to apply it. I deliberately avoided "write me a blink sketch" — that's not a prompt, that's a command.
Prompt 1: Datasheet → register map
Task: Turn a 40-page PDF into a working register table with bit fields.
I'm working with the [chip name] sensor/driver. Here is the
section of the datasheet describing register 0x[XX]:
"""
[paste excerpt: register name, bit definitions, reset value]
"""
Build a Markdown table: bit
| name | R/W | reset | meaning.
Then write an Arduino function that writes the correct value
to this register via SPI, with a comment for each bit.
Assume SPI mode [0/1/2/3] and MSB-first.
Why it works: The model doesn't have to guess the register — you feed it the ground truth. It only does the mechanical formatting and code synthesis, which is where it's most reliable. In practice, this cuts the "read datasheet → write init function" loop from ~40 minutes to ~10.
Real example: For the BME280, the calibration data lives in registers 0x88–0xA1 and 0xE1–0xE7 (Bosch datasheet, section 4.2.2). Feeding that excerpt into the prompt yielded a working readCalibration() function on the first try — including the tricky dig_T1 little-endian 16-bit assembly.
Prompt 2: Sketch generation with pin constraints
Task: Get real Arduino code that respects your wiring, not a generic example.
Write an Arduino sketch for ESP32 (Arduino core 3.x).
Hardware:
- BME280 over I2C: SDA=GPIO21, SCL=GPIO22, addr 0x76
- SSD1306 OLED over I2C, addr 0x3C
- LED on GPIO2 (active HIGH)
Requirements:
- Read temperature/humidity every 2 s
- Print to Serial at 115200
- Blink LED if temperature > 30°C
Use Wire.h, Adafruit_BME280.h, Adafruit_SSD1306.h.
Do NOT use delay() in the loop — use millis().
Return only the sketch, with a short comment header.
Why it works: The pin map and the "no delay()" constraint are the two things LLMs most often get wrong in generic examples. Stating them explicitly turns a toy sketch into something you can flash.
Prompt 3: I2C bus debugging
Task: Diagnose a silent bus.
My ESP32 I2C bus returns no devices on the scan.
Wiring: SDA=GPIO21, SCL=GPIO22, 4.7 kOhm pull-ups to 3.3V.
Sensor: [name], VCC=3.3V.
Serial output from Wire.endTransmission():
2 (NACK on address)
Give me a 6-step diagnostic checklist in order of likelihood,
with the exact measurement or code change for each step.
Why it works: I2C failures are almost always physical (pull-ups, address, power) or logical (clock stretching, wrong mode). Forcing the model into an ordered checklist prevents the "try random things" loop.
Real example: On a custom board, the scan returned nothing. The checklist pointed at pull-up values first — the designer had used 10 kΩ, which is too weak for 400 kHz Fast Mode. Swapping to 4.7 kΩ (per the I2C-bus specification, UM10204, section 7.1) fixed it.
Prompt 4: SPI timing and mode verification
Task: Match CPOL/CPHA to a datasheet timing diagram.
Here is the SPI timing diagram description for [chip]:
"SCLK idles low. Data is sampled on the rising edge.
MSB is shifted out first. CS is active low."
Which SPI mode is this? Explain in 2 sentences.
Then show the exact SPISettings line for Arduino:
SPISettings(speed, bitOrder, dataMode).
Why it works: CPOL/CPHA is a classic source of "the sensor returns garbage." The model maps the prose description to mode 0/1/2/3 deterministically, and you get the exact SPISettings call.
Prompt 5: Power budget for battery-powered nodes
Task: Estimate runtime and pick a regulator.
Design a power budget for a battery node:
- ESP32 in deep sleep, wake every 60 s, active 3 s
- Active current: 120 mA (Wi-Fi off), 240 mA (Wi-Fi TX burst 0.5 s)
- Deep sleep current: 10 uA
- BME280: 0.7 uA in sleep, 700 uA during measurement (10 ms)
- Battery: 18650 Li-ion, 3000 mAh, 3.7 V nominal
Calculate average current and estimated runtime in days.
Show the formula. Then recommend a regulator topology.
Why it works: The model handles the arithmetic and — more importantly — makes the assumptions explicit. You immediately see whether the Wi-Fi burst or the sensor dominates. Typical ESP32 deep-sleep current is in the tens of microamps (Espressif datasheet lists ~10 µA), so the math is grounded in real specs.
Prompt 6: FreeRTOS task decomposition
Task: Split a monolithic loop into tasks with priorities.
Refactor this Arduino loop into FreeRTOS tasks for ESP32:
[paste loop]
Constraints:
- Sensor read every 1 s (low priority)
- MQTT publish every 5 s (medium)
- Button ISR must set a flag, no blocking
- Serial logging must not block the sensor task
Return: task functions, queue handles, xTaskCreate calls,
and a short note on stack sizes (words, not bytes).
Why it works: FreeRTOS on ESP32 has a quirk — stack size is in words, not bytes. Explicitly asking for that note saves a crash. Priorities and queues are where beginners get lost, and the model lays them out cleanly.
Prompt 7: OTA update strategy for ESP32
Task: Plan a safe OTA flow with rollback.
I need OTA updates on ESP32 (Arduino core 3.x) with rollback.
Requirements:
- Dual OTA partitions (ota_0, ota_1)
- Verify firmware hash before switching
- Revert to previous slot if the new image fails to boot
- Host updates on an HTTPS server with a version manifest
Draft: partition table (CSV), the update logic with
Update.h / HTTPClient, and the manifest JSON schema.
Why it works: OTA is where "it works on my desk" meets production. The dual-partition scheme and esp_ota_get_next_update_partition() are documented by Espressif, but assembling the flow is fiddly. The model produces a starting point you can harden.
Prompt 8: Modbus RTU sensor integration
Task: Go from register map to working Modbus master.
Integrate a Modbus RTU sensor on ESP32 (RS485, DE/RE on GPIO4).
Sensor register map:
- 0x0000: temperature, int16, scale 0.1 °C
- 0x0001: humidity, uint16, scale 0.1 %
- 0x0002: status flags (bitfield)
Write a ModbusMaster-style read function for holding registers.
Include CRC handling (note: Modbus CRC-16 is different from
Ethernet CRC-32). Show the request frame in hex.
Why it works: Modbus CRC-16 (polynomial 0xA001) is a classic gotcha. Calling it out prevents the model from confusing it with other CRC variants. The hex frame lets you verify against a logic analyzer.
Prompt 9: Unit-testing embedded logic on host
Task: Test pure logic without flashing.
I have this C function that converts raw ADC readings to
engineering units: [paste function]
Write a host-side test (gcc, no Arduino) with:
- Boundary cases (0, mid-scale, 1023)
- A linearity check
- A table of expected values
Use Unity or plain assert.h. Explain how to stub the ADC read.
Why it works: Most embedded bugs are math bugs, not hardware bugs. Moving that math to a host test catches them in seconds instead of re-flashing cycles. This is standard practice in professional firmware teams.
Prompt 10: Code review with a hardware lens
Task: Catch the bugs that compile but fail on the bench.
Review this ESP32 sketch as a senior embedded engineer.
Focus on: volatile for ISR-shared variables, ISR safety
(no Serial.print, no malloc), watchdog feeding,
I2C timeouts, and integer overflow in millis() math.
For each issue: line, why it's a bug, minimal fix.
[paste sketch]
Why it works: The volatile keyword, ISR restrictions, and the millis() overflow at ~49 days are the top three "compiles fine, crashes at 3 a.m." bugs. Naming them explicitly makes the review actionable rather than stylistic.
A note on verification
Every generated sketch should be treated as a draft, not a deliverable. Three habits make this workflow reliable:
| Check | Why |
|---|---|
| Compile with the real core version | Arduino core 2.x and 3.x differ in APIs (e.g., ledcSetup) |
| Verify register addresses against the datasheet | Models occasionally transpose nibbles |
| Scope the bus | I2C/SPI "works" is only true if the waveform matches |
If you want to go deeper into prompt patterns for technical domains — structured inputs, constraint blocks, verification loops — the ASI Biont blog has related material on prompt engineering without hallucinations and on LLM reasoning techniques.
Embedded work rewards precision: a wrong bit in a register costs an evening. The prompts above don't replace the datasheet — they turn it into something you can query, test, and ship. Pick one, run it against your current board, and see how much of the datasheet you can skip without skipping the parts that matter.
Comments