Skip to content

Instantly share code, notes, and snippets.

Last active November 16, 2023 20:20
Show Gist options
  • Save hwhw/fc43892785aa84913d03495c97b0f25a to your computer and use it in GitHub Desktop.
Save hwhw/fc43892785aa84913d03495c97b0f25a to your computer and use it in GitHub Desktop.
Flashing CC2538 using OpenOCD (&UART)

Flashing CC2538 using OpenOCD (&UART)

Problem description

CC2538 SoCs are Cortex-M4 based SoCs by Texas Instruments for 802.15.4 PAN networking, especially for Zigbee communication using their Zigbee Stack solution. It also is a solid foundation for hobbyist-grade solutions involving Zigbee communication, especially for running a self-implemented Zigbee coordinator (often called "Zigbee gateway" in commercial distribution of similar tech). Software-wise, this is usually done by running "ZNP" (Zigbee Network Processor) firmware on the SoC and using a separate controller/computer to act as the "ZAP" (Zigbee Application Processor). In a hobbyist setting, the former is usually a cheap module from china containing the chip, capacitors and an antenna or antenna connector, while the latter is e.g. a Raspberry Pi or an Intel NUC or whatever floats your boat. The API between both components is specified and so the software side on the ZAP does not need to bother with Zigbee communication states but can act on a higher level. Examples for such software are zigbee2mqtt by koenkk or zigbee-lua by yours truly. This software usually was built on the CC2530/31 chipsets - however, the CC2538 is compatible in the ZNP/ZAP API and generally much more crafty and stable as it has a faster CPU core and more available memory. RF wise, as per the specs, they are mostly on par.

While the CC2530/31 uses an 8051 based CPU core and has its own proprietary two-wire debugging protocol that is also used for flashing the firmware, the CC2538 uses cJTAG (and in a second stage optionally JTAG) for this purpose. The easy way to flash firmware onto the CC2538 is therefore the Segger J-Link. In its "Edu" variant, which probably fits hobbyists best license-wise, it is somewhat affordable for hobbyists - but still not a bargain.

The reason of this short gist is therefore a short description on how to use a generic approach to flash CC2538 devices using open source OpenOCD software.


The CC2538 allows debug access using cJTAG (this is active by default upon boot) and - after switching over - using JTAG. We will use this facility to write to CC2538 memory. Currently, there does not seem to be available code that allows for easy flashing a full firmware blob. So we're resorting to a trick and do this in two steps:

  • first we access the internal flash controller control registers. We will write a few values that will make the CC2538 erase its last flash page. This will, among other things, clear the "valid firmware present" indicator in said flash page, and will also set a flag that in combination will activate the integrated bootloader in the CC2538.
  • then after a reset, the CC2538 will be in bootloader mode. Via its UART port, can be used to flash a full firmware image.

NOTE: using a (slightly forked) variant of OpenOCD, erasing the flash page and then using the serial bootloader is obsolete: it has a flash loader for the CC2538. So flashing a firmware is now as easy as connecting JTAG and running OpenOCD. See this comment:


In order to flash the CC2538 using OpenOCD, you need

  • a reasonably recent version of OpenOCD (
  • an "interface" that OpenOCD can use. This can e.g. be FT2232 based USB/multiprotocol adapter, or in my case a Raspberry Pi (and in that case you would run OpenOCD on said RPi).
  • some telnet client to interact with OpenOCDs CLI.
  • for flashing after we used OpenOCD to prepare bootloader mode (not needed when using forked OpenOCD with CC2538 flash loader)

By the way: now is a good point in time to check if said bootloader mode is active on your CC2538 anyway (just wire up UART connection and run, which will save you lots of time since then you don't need to use OpenOCD.

Wiring up the CC2538 for (c)JTAG

From now on, information in the CC2538 reference manual is indispensible: When using OpenOCD in combination with a Raspberry Pi, you need to check the according OpenOCD interface configuration. By default, you'll find this in /usr/share/openocd/interface/raspberrypi2-native.cfg (for RPi >=2). The RPi pins used for JTAG communication are specified there. I suggest you go with the defaults. Note that there's two numbering schemes for RPi GPIOs, which will make things veeeeery awkward. At some point, you'll get this right, I guess. Default config is:

  • JTAG TCK - GPIO 11 - Pin 23
  • JTAG TMS - GPIO 25 - Pin 22
  • JTAG TDI - GPIO 10 - Pin 19
  • JTAG TDO - GPIO 9 - Pin 21

You need to wire up the full JTAG interface, as we will be using cJTAG just for switching over to JTAG mode. The CC2538 side is as follows:

  • JTAG TCK = cJTAG TCK - dedicated TCK pin 47.
  • JTAG TMS = cJTAG TMSC - dedicated TMS pin 46.
  • JTAG TDI - PB6 (=pin 49)
  • JTAG TDO - PB7 (=pin 48)

Also connect at least a common ground connection. You can also power the CC2538 using the RPi's 3.3V rail. Make double sure that you use the 3.3V rail, not the 5V rail.

Running OpenOCD

On the RPi (or whatever host you are using), run

openocd -f /usr/share/openocd/interface/raspberrypi2-native.cfg -f /usr/share/openocd/target/cc2538.cfg

(adapt to the interface you're using if it is not a RPi >= 2.)

It should output some status that will basically tell you it communicated successfully with the CC2538. Well, probably it won't on try #1 and you need to re-check your cables and so on, but at some point it hopefully will.

Use the OpenOCD CLI

In a different terminal - possibly even on another computer - run telnet localhost 4444 You should be greeted by the OpenOCD command line then. The following commands are to be entered on this OpenOCD command line interface.

erase flash CCA

using a fork of OpenOCD, this is not needed anymore, see

Set erase address:

mww 0x400D300C 0x7F800

(note that this was shifted in an earlier release of this document - with a current version of OpenOCD of today, this seems to work instead now)

Issue erase command (also set flags to unlock)

mww 0x400D3008 0x0205

That's it, the CCA should be erased and all flags should be cleared now. I tried to reconstruct this from my OpenOCD history. I sincerely hope that these are the right commands. You can check the CCA contents using the command mdb 0x0027F800 2048 and hopefully, everything is cleared. If you want to read up on what those register writes did, check the reference manual for the CC2538 and read up on the flash controller in the memory section.

Use the bootloader

using a fork of OpenOCD, this is not needed anymore, see

wiring up an UART

Use whatever UART adapter you like - e.g. maybe the built-in UART of the RPi you used for flashing? Or a simple USB-UART adapter? Make sure it has an adequate voltage level, then connect it as follows:

  • cc2538 UART RX is on PA0
  • cc2538 UART TX is on PA1
  • make sure you have a common ground connection and to power the cc2538 with a 3.3V source

run will show you information about a connected cc2538 in bootloader mode and will allow flashing. Be sure that you've read the documentation. Especially about the bootloader backdoor configuration (also have a look into the cc2538 reference manual to learn about the exact way how to configure the backdoor enable pin). Note that using the JTAG method described above you do not need the bootloader backdoor. It might still be handy to enable it if possible, as this saves a step and some wiring.

Copy link

dl8dtl commented Nov 16, 2023

Digging up that old discussion:
I have just migrated the CC2538 flash driver from to the review site of the official OpenOCD repository, so it might hopefully get included into the official tree later on. The only changes compared to the version are stylistic (OpenOCD's code checks are quite picky), and a small bit of documentation in the .texi file.
If someone wants to add a review:
(You can log in there with your github ID.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment