UART problems often look random: a silent console, unreadable text, a boot log that appears once and then disappears, or a device that transmits but never receives. This guide is a practical troubleshooting hub for embedded developers working with boards, USB-to-UART adapters, modules, and custom hardware. It focuses on the failure patterns that come up most often in real serial work: wiring mistakes, baud rate mismatch, voltage-level confusion, terminal settings, flow control surprises, and firmware-side mistakes. Use it as a fast path to isolate whether the problem is physical, electrical, configuration-related, or inside the code.
Overview
UART is simple enough to be a first-line debug interface and subtle enough to waste hours when one assumption is wrong. At a high level, a working serial link needs five things to line up:
- Correct wiring: TX must reach the other side's RX, RX must return to TX, and both devices need a common ground.
- Compatible electrical levels: 3.3 V UART and 5 V UART are not interchangeable just because the connector fits.
- Matching serial parameters: baud rate, data bits, parity, and stop bits must agree.
- A sane terminal setup: the right port, no unexpected local echo assumptions, and appropriate flow control.
- Firmware that actually enables the UART: pin mux, peripheral clocking, buffer handling, and console routing all matter.
When serial communication fails, do not debug all layers at once. The fastest approach is to narrow the fault domain in this order:
- Confirm power and ground.
- Confirm TX/RX path and voltage levels.
- Confirm terminal settings and adapter behavior.
- Confirm whether the target transmits anything at all.
- Confirm firmware configuration and timing assumptions.
That order matters because many "software" UART issues are actually board-level problems, and many "hardware" UART issues are caused by one terminal checkbox or a mismatched baud rate.
If you are building a new embedded workflow from scratch, it also helps to standardize your machine, cables, serial tools, and adapter choices. A repeatable setup reduces false leads and speeds up bring-up. For that broader environment work, see Developer Environment Setup Checklist for New Machines.
Topic map
This section maps the most common UART debugging branches so you can jump to the right line of investigation quickly.
1. No output at all
If the terminal is completely blank, start with the simplest questions:
- Is the target board powered and actually booting?
- Is the USB-to-UART adapter detected by the host?
- Did you choose the correct serial port?
- Is the target TX connected to the adapter RX?
- Do the devices share ground?
- Is the target using a different UART instance than the one you wired?
- Is the boot log disabled or routed elsewhere?
A blank console usually points to one of three conditions: the target is not sending, you are not listening to the right signal, or the signal is present but unreadable because of electrical or configuration mismatch.
2. Output appears as gibberish
Unreadable text is the classic baud rate mismatch symptom, but not the only one. Check:
- Baud rate on both sides.
- Data bits, parity, and stop bits, especially if one side is not using the common 8-N-1 format.
- Clock configuration on the microcontroller. A wrong system clock can produce a wrong UART divisor even when the code looks correct.
- Voltage level compatibility. Marginal signaling can corrupt some characters but not others.
- Noise, cable quality, and ground integrity.
If the text is consistently wrong in a repeatable pattern, the baud rate or clock tree is a strong suspect. If corruption is intermittent, think about signal integrity, resets, or buffer overruns.
3. You can receive but not transmit, or transmit but not receive
Half-working UART links are especially useful diagnostically because they usually narrow the fault to one direction:
- If the target transmits but never responds to typed commands, RX into the target may be miswired, blocked by pin mux, or ignored by firmware.
- If the target receives commands but you never see output, TX from the target may be on the wrong pin, disabled, or routed to another console.
- If one direction works only at low speed, look for weak level shifting, line capacitance, or timing issues.
This is also the point where loopback tests become valuable. A loopback on the adapter side can confirm whether the host terminal and adapter are functioning before you blame the target.
4. It works during boot, then stops
This pattern often indicates a software handoff problem rather than a wiring issue. Common causes include:
- The bootloader prints to one UART, but the application uses another.
- The application reconfigures the pins for a different peripheral.
- The system clock changes after boot without updating UART divisors.
- Power-saving modes disable the UART clock.
- An RTOS task, interrupt, or DMA configuration breaks the console path.
When a console disappears after startup, compare the boot stage and runtime stage separately. Treat them as two different configurations sharing the same pins only by assumption.
5. It works in one terminal tool but not another
That usually points to host-side settings rather than hardware. Check:
- Hardware flow control flags such as RTS/CTS.
- Line ending behavior such as CR, LF, or CRLF.
- Local echo, which can make it look like a target is replying when it is not.
- Port open timing or reset behavior on boards that reboot when a serial port is opened.
It is worth keeping one known-good terminal configuration documented in your project notes so you can return to a baseline quickly.
Related subtopics
UART debugging crosses hardware, firmware, and developer workflow. These subtopics are where most durable fixes come from.
UART wiring essentials
The minimum useful UART connection is usually TX, RX, and GND. Cross the data lines: target TX to adapter RX, target RX to adapter TX. Ground must be shared, even when both sides have their own power source. Without a common reference, the logic thresholds become unreliable and the link may fail completely or behave inconsistently.
Be careful with labeling. Some boards label pins from the board's perspective, while some cables or modules imply the host perspective. If in doubt, read the labeling as signal direction on that device and verify with documentation or a continuity check.
Voltage levels and electrical compatibility
A frequent uart wiring mistake is treating all serial headers as equivalent. UART signaling on microcontrollers is commonly 3.3 V logic, but many legacy boards and adapters still expose 5 V logic. Connecting a 5 V TX signal directly into a 3.3 V-only input can damage hardware or create long-term instability. Even when no immediate damage appears, a marginal electrical match can produce flaky results that look like software bugs.
If the voltage domains differ, use the right level shifting method for the board and speed involved. Also confirm that your USB-to-UART adapter actually uses the logic level you expect; some adapters expose selectable I/O voltage, while others do not.
Baud rate mismatch and clock errors
Baud rate mismatch is one of the highest-probability causes of unreadable output. But the fix is not always as simple as changing the terminal value. In embedded systems, the configured baud rate depends on the peripheral clock source, divisor registers, oversampling mode, and sometimes the accuracy of the oscillator itself.
If your code says 115200 but the output looks wrong, verify:
- The actual core or peripheral clock frequency at runtime.
- Whether a bootloader or startup code changes the clock tree.
- Whether the application assumes an external crystal that is not populated or not stable yet.
- Whether the UART peripheral is using the intended clock source.
A good test is to try a range of common terminal speeds and see whether one yields readable output. That does not replace fixing the clock configuration, but it can identify the mismatch direction quickly.
Terminal configuration and serial console behavior
Serial console troubleshooting often comes down to disciplined host-side checks. Use one terminal program you trust, note its exact settings, and avoid changing multiple variables at once. Confirm:
- Correct serial device path or COM port.
- 8 data bits, no parity, 1 stop bit unless your target requires something else.
- No hardware flow control unless the device explicitly uses RTS/CTS.
- Appropriate line endings for interactive commands.
- Whether opening the port resets the target board.
On some boards, opening a serial port toggles DTR or RTS and causes an automatic reset. That can be useful during flashing and confusing during logging. If your boot messages appear only when you reconnect, reset behavior is worth checking.
Loopback testing
A loopback test isolates the host and adapter from the target. Disconnect the target, connect the adapter TX to adapter RX, open the terminal, and type. If characters echo back, the host tool and adapter path are likely working. If loopback fails, stop there and fix the host side first.
Loopback cannot prove the target is healthy, but it is one of the fastest ways to rule out bad cables, wrong ports, or broken adapters.
Firmware-side causes of embedded serial debug failures
When the physical link looks correct, inspect the firmware path systematically:
- Was the UART peripheral clock enabled?
- Are the pins configured for the UART alternate function rather than GPIO?
- Are TX and RX assigned to the intended pins for that package and board revision?
- Is the interrupt handler installed and clearing flags correctly?
- Are transmit buffers blocking forever because of a flow-control assumption?
- Did a low-power state disable the peripheral?
- Is stdout or logging routed to a different backend?
For teams choosing an implementation style for board-level code, language and runtime decisions can affect how early and how predictably serial output appears during bring-up. For a broader comparison, see Embedded C vs MicroPython: Choosing a Stack for Microcontroller Projects.
Bootloader versus application console
Many boards effectively have two serial stories: the early boot path and the application path. If you see boot logs but lose the console later, document:
- Which stage produced visible output.
- Which UART instance each stage uses.
- What baud rate each stage expects.
- Whether the pin mux changes after initialization.
This distinction matters on SoCs, Linux-capable boards, and custom systems with multiple UART peripherals. Debugging becomes much faster when you stop assuming one console path spans the whole boot chain.
Noise, grounding, and board layout
Short UART runs on development benches are forgiving. Longer wires, noisy motors, weak grounds, or poor layout can produce framing errors and random corruption. If a setup works on one bench and not another, look beyond code:
- Shorten the wires.
- Improve the ground connection.
- Separate serial lines from noisy power paths.
- Reduce baud rate temporarily to see whether reliability improves.
- Check whether multiple grounds create unexpected current paths.
If lowering the baud rate makes the problem disappear, that is a useful clue even if it is not the final solution.
How to use this hub
Use this page as a repeatable triage checklist whenever embedded serial debug goes sideways. The goal is not to memorize every UART edge case. It is to create a stable order of operations so you can isolate faults quickly.
A practical 10-step UART debugging workflow
- Confirm the board is alive. Check power rails, boot indicators, and any known-good sign of life.
- Verify common ground. Do this before changing software settings.
- Cross TX and RX correctly. Do not trust cable colors; verify the actual signals.
- Confirm voltage compatibility. Treat 3.3 V and 5 V logic as a real design constraint.
- Run adapter loopback. Prove the host and adapter path first.
- Use one known-good terminal config. Start with standard 8-N-1 and no flow control.
- Test common baud rates. Especially if the clock tree or boot stage is uncertain.
- Check pin mux and peripheral enable. Make sure firmware matches the actual board pins.
- Compare bootloader and application behavior separately. Do not assume the same UART survives handoff.
- Reduce variables. Shorter cables, lower baud, direct connection, minimal firmware.
If you maintain team documentation, consider turning those ten steps into a project-specific serial bring-up checklist. A short internal note with exact adapter model, target voltage, terminal command, default baud, and expected boot text can save more time than a long architecture document.
This structured approach is similar to other debugging domains: first isolate the layer, then narrow the cause, then standardize the fix. That mindset also helps in non-embedded troubleshooting, such as API and browser debugging. See HTTP Status Code Reference for API Debugging and Monitoring and CORS Error Guide: Common Causes, Fixes, and Browser Debugging Steps for examples of the same method in another stack.
When to revisit
Return to this UART debugging hub whenever the inputs around your serial path change. UART issues often reappear not because the original fix was wrong, but because one layer moved underneath it.
Revisit your assumptions when:
- You switch to a new board revision or module.
- You replace the USB-to-UART adapter or cable.
- You change the power architecture or grounding arrangement.
- You update the bootloader, BSP, SDK, or HAL.
- You move pins, remap peripherals, or add level shifting.
- You introduce low-power modes, RTOS scheduling, DMA, or new logging code.
- You see field-only failures that do not reproduce on the bench.
The most practical habit is to keep a short serial debug record for each device family: pinout, expected voltage, known baud rates, boot console behavior, terminal settings, and any adapter quirks. That turns future debugging into verification rather than rediscovery.
As your embedded toolchain grows, this topic naturally expands into adjacent concerns such as boot logs, SWD/JTAG coexistence, Linux serial consoles, binary protocols over UART, and production test fixtures. Treat this article as the stable base layer: wiring first, electrical compatibility second, configuration third, firmware fourth. That order remains useful even as the system around it gets more complex.