Using JTAG to Peek Inside Your MCU
There are various ways to debug a microcontroller design. This month Stuart looks specifically at JTAG as a debug tool.
Some years back, I got a call from a coworker, a software engineer who was trying to debug a problem. The board he was using had an 8088 microprocessor and the usual EPROM, RAM, and I/O hardware. Debug was done with a box, about the size of a shoebox, which functioned as a hardware emulator. These boxes had a cable with a header that plugged into the CPU socket (CPUs were in DIP packages), replacing the CPU. The problem was that an intermittent failure would occur, but when he plugged in the emulator, it disappeared. He was thinking it was a hardware issue. I hooked up a logic analyzer and traced it down to a software problem. The problem occurred when accessing a hardware IC that had multiple interrelated registers.
The simplified software functionality was this:
- Software makes first access to the hardware.
- Software makes second access to the hardware.
- Software continues.
The problem was that an interrupt occurred between step 1 and step 2, and the Interrupt Service Routine (ISR) also wrote to the same IC. So when the ISR exited and the software executed step 2, the hardware was in the wrong state. The solution was to disable interrupts around step 1 and step 2. But the larger issue in debugging the problem was that the emulator changed the timing of the circuit just enough that the failure never occurred—at least not in the time we spent trying to reproduce it. Those emulators were a nearly perfect plug-in equivalent to the microprocessor that was being emulated. Nearly perfect. But not quite.
The point is that the more your debug system—architecture, software, and hardware—changes the normal flow of code operation, the more unlike normal operation the system works. Add a statement to send a debug output to a serial port or to set a debug port pin and you’ve changed the timing of whatever function you are using. Add a statement like that to an ISR and you’ve permanently altered ISR execution. Or if you make the statement conditional on the existence of some other factor such as a specific value in a variable, then you’ve made the ISR execution time variable. Which might or might not create problems of its own. In modern MCUs, nearly any debug system is going to affect the hardware architecture or the software operation. Adding debug capability always comes with a cost.
Debugging Principles
The goal of debugging any MCU-based system is, of course, to verify that the hardware and the software are working as they should. In the old days of 8088 microprocessors, the address and data buses were connected to pins on the device, and every I/O or memory operation could be monitored with a logic analyzer. Emulators, as mentioned, plugged directly into the CPU socket, allowing software engineers to trace code and set breakpoints. But CPUs aren’t in sockets anymore, and on an MCU design, the hardware and memory accesses are buried inside the IC; there is no way to directly probe them.
In previous articles, such as Circuit Cellar, July 2016, I covered some easy things that can be done to provide debug outputs for tracing and analyzing code execution. I may readdress that topic in the future with more sophisticated methods, but for this article, I want to look specifically at JTAG.
JTAG: Joint Test Action Group (JTAG) is a specification for accessing internal parts of a device, whether it is a field programmable gate array (FPGA), memory device, or MCU. JTAG is standardized by the IEEE as standard 1149. JTAG was not originally designed specifically for MCU debugging, but the standard is flexible enough to allow for additional functionality beyond the original intent.
Operation: JTAG operates by sending a command to a register inside the device and then reading out a result. Commands and data are sent serially. JTAG requires six connections for operation:
GND: Ground connection
TDI: Test data in—serial data to the device
TDO: Test data out—serial data from the device
TMS: Test mode select
TCK: Test clock
TRST: Reset; not always needed
I mentioned that any MCU Debug feature will have a cost; for JTAG, it’s tying up four pins of the MCU. Since pins are limited, this is a hardware cost. If you don’t need those pins, then you don’t really suffer a cost. But if your design does need those pins as input/output pins, then adding JTAG debug means that whatever those pins are used for in the design, must be dedicated to JTAG during debug.
Serial Wire Debug: ARM-based MCUs may include a Serial Wire Debug (SWD) connection that reduces the number of pins needed to two. This reduces the pin count needed for debug. SWD has a bidirectional data pin and a clock pin. An optional third pin can be used for trace output. SWD uses JTAG protocol, just multiplexed onto a single bidirectional data pin. SWD was developed specifically for ARM processors. SWD requires that you have a JTAG probe that can recognize and use SWD instead of the larger JTAG.
Example Circuit: Figure 1 shows an example circuit based on the Texas Instruments (TI) TM4C1233D5 MPU. As with previous examples, this is part of a larger board, and I deleted everything not relevant to this example. D1 is a heartbeat LED that blinks once/second, D2 is a diagnostic LED. J3 is a JTAG connector that is wired to a JTAG debugger. S1 is a push-button switch for testing breakpoints. S1 is debounced in software. J2 is a connection to one of the TM4C1233D5 serial ports, normally used for downloading code. Since I am using JTAG both for downloading and debug on this project, J2 is unused here.
For this article, I used a TI TM4C123 Launchpad board as a JTAG probe. The Launchpad has two MCUs on it, one is used for development of firmware (similar to an Arduino) and the other is dedicated to JTAG. The JTAG is the Stellaris ICDI interface, a TI-unique implementation. The JTAG on the Launchpad is there to program the development MCU but can be connected to a different target board to program an external MCU. That’s how it is connected here.
The Launchpad board connects to a host PC using USB. The software for this example was developed using TI Code Composer Studio and the debugging examples use the Code Composer debug capability. The pushbutton just flashes the diagnostic LED when pressed and debounced for 20ms.
Basic Breakpoint: As an initial test, I used the JTAG to set a breakpoint. I used the TI LM Flash programmer utility to download the code to the target board, then I use the Code Composer Studio debug window to set a breakpoint where the LED flash trigger is initiated when the button is pressed. Figure 2 shows the breakpoint window on Code Composer Studio, indicating which line of code is to halt execution. The default breakpoint action is to remain halted.
Figure 3 shows a snippet of code from the debug screen, highlighting the statement where the breakpoint is set. When I pushed the button, the code stopped and the heartbeat LED stopped blinking. At this point, I clicked the “variables” tab on the breakpoint window to look at the value of the PBticker debounce counter (Figure 4). You can see that the variable is 21, which is the expected value. The debounce counter is zeroed when the push-button pin is high (button not pressed) and increments when the pin is low, up to a value of 20. Then the value is incremented to 21 and the LED is flashed. Once PBticker reaches 21, it is not incremented until the button is released and the value goes back to zero. This ensures that there is just one LED blink per button press. Finally, I used run/resume in the debugger to continue operation. The diagnostic LED flashes, the heartbeat LED starts blinking, and all is back to normal. When a breakpoint is reached, you can view memory, change the value of variables, and view register values.
Continuous Monitoring: I created a variable TickCounter that is incremented in the systick ISR (interrupt service routine). I then added this to the debugger “expressions” tab. You can monitor the variable while the program is running. Figure 5 shows a snapshot of the variable while the debugger is running. TickCounter runs continuously, so the value is random, it’s just what I captured at that instant.
JTAG LIMITATIONS
Speed: As mentioned, a variable can be monitored while the program runs. However, it’s not real-time, the debugger updates the value periodically, perhaps four times per second. Actual real-time monitoring of anything isn’t possible with JTAG because every command requires sending multiple 32-bit serial words to the MCU JTAG pins. The JTAG probe I was using clocks the serial data at about 700kHz. Even if a JTAG command could consist of as few as four 32-bit words, the maximum transfer rate would be about 5400 commands/second. In addition, the USB interface is speed-limited. So while something in the target board could be monitored regularly, there is nothing resembling real-time execution tracing over JTAG. And the more variables you want to monitor in real time, the slower the update rate has to be.
This limitation applies not to just the specific case of monitoring a variable, but to anything involving code monitoring. You can, for example, view memory contents after a breakpoint, but there is no way to see all the memory as it changes; the JTAG interface just can’t transfer the data that quickly. Although JTAG has some continuous monitoring capability, most examination of variables and memory happens after a breakpoint.
Timer Issues: Let’s say you use a breakpoint as described. Everything stops at the breakpoint and resumes when you tell it to. Works great for a simple example like this. But let’s say you have timers running, generating interrupts. You spend a few seconds looking at variable values while the MCU is stopped. Meantime, the timer is still running. If the timer output is controlling a PWM drive to a motor or a heater, it’s no longer controlled. The MCU is stopped so whatever feedback loop was run through the software is now open-loop. If that timer is controlling the motor to a linear drive screw, it may hit the end of travel before you get it stopped, causing damage.
The solution to this seems easy: Just disconnect motors and heaters and similar things that can cause damage while you are debugging. But what if one of those things is what you are trying to debug? In cases like that, you may have to be creative in simulating the real system so you can debug the software. In the case of the linear drive motor, maybe you make a load that simulates the actual load but doesn’t have any stops to smash into. If it’s a heater, maybe you have an overheat safety switch to shut off the heater if things get too hot.
You could have a timer that is counting input events; while the software is stopped, that counter will keep on counting as long as the external source is generating events. When the software resumes, that count may be out of range in some way, which could cause software errors (this is all hypothetical, of course). You might need a way to inhibit the external circuitry that is generating events while you are examining the debug results.
In addition to timer outputs that will continue to run after a breakpoint, any timer interrupts will still occur. So when the code is resumed, there may be a stackup of interrupts to be serviced, and the timers may not be at the values the timer ISR expects. So the ISR code may need to include checks for legitimate timer values, or else it may need a way to know that timer interrupts happened during a breakpoint so invalid timer values can be discarded.
Other Run-On Issues: Timers aren’t the only thing that can cause issues. Say you’ve got another MPU or an external device of some kind, maybe even something as simple as an RS232 serial interface, which is communicating with the MPU you are debugging. If the external device keeps sending data, when you resume, you will find overflow errors, possibly the other system will indicate lost communication errors, a communication that was ongoing when the breakpoint happened will be interrupted. The resume will result in the code starting at the breakpoint, but now there may be multiple errors to deal with. This might be a good way to test your error-handling code, but if that’s not what you wanted to debug, it can cause problems.
One way to deal with this is to stop and restart external systems so you can get back to work on whatever problem you were trying to solve. You might want a global button you can push (whether it’s a physical button or a message of some kind) that says to the external devices “Debugging breakpoint happened, go to a safe state.” In general this issue can apply to any interface or any external device, system, or even single pin that needs to be updated periodically.
Some of the timer and other run-on problems may be eliminated if analyzing a breakpoint results in a change to the code that is then downloaded and started; that results in a reset. But if code is resumed after a breakpoint, all these potential issues apply.
Pin Requirements: As mentioned, JTAG requires at least four signal pins (TMS, TCK, TDO, TDI). It also requires a ground connection, although that uses pins that already exist on the MCU. This means that four MPU pins must be dedicated to JTAG use (or at least two if using an ARM with SWD capability). That’s four fewer pins for your application. On some MCUs, such as the TM4C1233D5, you can configure the JTAG pins for general-purpose I/O pins in software. But if you do that as part of software initialization, you may lock the pins out so that the JTAG can’t get control when it’s plugged in. So you might need another pin (that’s pin number 5) to disable the piece of code that disables JTAG functionality so that you can debug.
In this case, the extra pin would be, say, grounded with a jumper to notify the software not to disable the JTAG functionality. Of course, if you must do that, it means you need those four pins for something else. So that jumper has to also disable whatever those pins are driving, putting whatever it is into a safe state while debugging. You could also debug with a special version of the code that doesn’t disable JTAG, but you still need to do something with whatever those pins connect to. If you have configuration inputs (jumpers to select options) you might use the JTAG pins for that so that at least the JTAG signals won’t be driving something into a weird state.
Some AVR devices use a programmable fuse to enable/disable JTAG. So while I’ve focused on the specifics of the TM4C1233D5 device, other MPUs have other ways of enabling or disabling the JTAG interface.
Target MCU Capability: JTAG functionality is limited by the capability of the target MCU. JTAG is a standard, but it only defines some standard registers and the communication protocol. The actual debug capability is defined by the MCU itself. For example, the TM4C1233D5 has eight breakpoint registers for setting up to eight breakpoints without altering the code. Other MCUs must insert some kind of instruction into the code to add a breakpoint.
The TM4C1233D5 also implements a single-wire viewer (SWV) that can be used to generate real-time outputs to trace and profile program execution. This avoids the JTAG speed limitations, but it’s still serial, so the amount of data that can be sent is still limited. And you need a debugger that can configure and use the SWV. The TI XDS110 debugger supports SWV (the manual calls it SWO for serial wire output) and the Segger J-Link debugger supports it. Other debuggers probably do as well.
OTHER JTAG TOOLS
This examination of JTAG has necessarily been limited to one MCU, one toolchain, and one JTAG probe. I picked the toolset I used here because I already had them and their cost was low. Of course, there are other tools, Keil, IAR Systems, and Segger all make development suites, and can support JTAG debuggers from third-party vendors such as Segger, Blackhawk, and Lauterbach. Microcontroller manufacturers typically provide JTAG debuggers for the devices that support them, such as the TI XDS110, the ST AEK-MCU-SPC5LNK, and the Microchip Atmel-ICE.
Different debugging
Many MCUs support JTAG, including some AVR devices, some Microchip PIC devices, and others. Although I’ve focused on JTAG here, there are some MCUs that do not support JTAG but do support some form of debug capability; this includes some AVR devices, for example. Those typically require a specific debugger, of course. Different devices support different debug capability, but you can simplify and accelerate debug of a new design with these tools.
RESOURCES
IAR Systems | IAR.com
Keil | Keil.com
Microchip | microchip.com
Segger | segger.com
STMicroelectronics | ST.com
Texas Instruments | TI.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • FEBRUARY 2026 #427 – Get a PDF of the issue
Sponsor this ArticleStuart Ball recently retired from a 40+ year career as an electrical engineer and engineering manager. His most recent position was as a Principal Engineer at Seagate Technologies.





