Skip to content

Instantly share code, notes, and snippets.

@hwhw
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.

Approach

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, https://github.com/JelmerT/cc2538-bsl 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: https://gist.github.com/hwhw/fc43892785aa84913d03495c97b0f25a#gistcomment-3518720

Prerequisites

In order to flash the CC2538 using OpenOCD, you need

  • a reasonably recent version of OpenOCD (https://openocd.org/)
  • 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.
  • https://github.com/JelmerT/cc2538-bsl 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 cc2538-bsl.py), 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: http://www.ti.com/lit/ug/swru319c/swru319c.pdf 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 https://gist.github.com/hwhw/fc43892785aa84913d03495c97b0f25a#gistcomment-3518720

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 https://gist.github.com/hwhw/fc43892785aa84913d03495c97b0f25a#gistcomment-3518720

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 cc2538-bsl.py

https://github.com/JelmerT/cc2538-bsl 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.

@chuvihi
Copy link

chuvihi commented Mar 26, 2022

Indeed, this is a version of OpenOCD that has the flash loader for the cc2538 (in contrib/loaders/flash, plus glue code). That's marvellous! I'll add a corresponding note at the top of this document.

Hi, I'm trying this way using fork OpenOCD but I'm getting the following error.

Open On-Chip Debugger 0.11.0+dev-01935-gd295721ee-dirty (2022-03-26-10:02)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
BCM2835 GPIO nums: swclk = 11, swdio = 25

Info : auto-selecting first available session transport "jtag". To override use 'transport select '.
Info : BCM2835 GPIO JTAG/SWD bitbang driver
Info : clock speed 100 kHz
Info : JTAG tap: cc2538.jrc tap/device found: 0x8b96402f (mfg: 0x017 (Texas Instruments), part: 0xb964, ver: 0x8)
Info : JTAG tap: cc2538.cpu enabled
Info : cc2538.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for cc2538.cpu on 3333
Info : Listening on port 3333 for gdb connections
Info : JTAG tap: cc2538.jrc tap/device found: 0x8b96402f (mfg: 0x017 (Texas Instruments), part: 0xb964, ver: 0x8)
Info : JTAG tap: cc2538.cpu enabled
Warn : Only resetting the Cortex-M core, use a reset-init event handler to reset any peripherals or configure hardware srst support.
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x0000136c msp: 0x20008000
** Programming Started **
Warn : no flash bank found for address 0x00000000
** Programming Finished **
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

I don't know what I'm doing wrong!

I've could connect using telnet but once I'm connected I don't know what I have to do.
I've sent the messages mww 0x400D300C 0x7F800 and mww 0x400D3008 0x0205 but I think these are not doing anything because if I send mdb 0x0027F800 2048 this is what I've got.

0x0027f800: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f820: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f840: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f860: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f880: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f8a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f8c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f8e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f900: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f920: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f940: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f960: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f980: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f9a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f9c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027f9e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fa00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fa20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fa40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fa60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fa80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027faa0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fac0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fae0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fb00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fb20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fb40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fb60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fb80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fba0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fbc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fbe0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fc00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fc20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fc40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fc60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fc80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fca0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fcc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fce0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fd00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fd20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fd40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fd60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fd80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fda0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fdc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fde0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fe00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fe20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fe40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fe60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fe80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fea0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fec0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027fee0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ff00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ff20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ff40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ff60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ff80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ffa0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ffc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0x0027ffe0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

@photovirus
Copy link

This comment by @miqmago proved to be immensely helpful.

That openocd fork has full support for flashing cc2538, and config file works as well. The only trouble was to compile openocd on my M1 Macbook, as homebrew has wrong paths for capstone (includes extra nested capstone directory). I fixed it in source files locally (<capstone/capstone.h> → <capstone.h>); kinda ugly, but worked.

Since there's little info on JTAG talking to CC2538, I decided to describe everything I did in detail so other people can find it.

I used a modified @miqmago's config to flash a firmware image, and it worked beautifully. Hardware side, I used a Flipper Zero, which turned out to have a CMSIS-DAP implementation with JTAG support (among other things), so my config looks like this:

adapter driver cmsis-dap
adapter speed 4000

transport select jtag

cmsis_dap_backend usb_bulk

source [find tcl/interface/cmsis-dap.cfg]
source [find tcl/target/cc2538.cfg]

Then connect to ocd...

% src/openocd -f ./flash.cfg -s ./tcl
Open On-Chip Debugger 0.11.0+dev-01935-gd295721ee (2022-12-18-04:20)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Warn : Interface already configured, ignoring
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : Using CMSIS-DAPv2 interface with VID:PID=0x0483:0x5740, serial=DAP_Rumo
Info : CMSIS-DAP: SWD  supported
Info : CMSIS-DAP: JTAG supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Serial# = DAP_Rumo
Info : CMSIS-DAP: Interface Initialised (JTAG)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 100 kHz

With connection set up, I fired up a telnet connection

% telnet localhost 4444
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> init
> targets
    TargetName         Type       Endian TapName            State
--  ------------------ ---------- ------ ------------------ ------------
 0* cc2538.cpu         cortex_m   little cc2538.cpu         running
> program ../JH_2538_2592_ZNP_USB_20211222.hex
cmsis-dap JTAG TLR_RESET
cmsis-dap JTAG TLR_RESET
cmsis-dap JTAG TLR_RESET
JTAG tap: cc2538.jrc tap/device found: 0x8b96402f (mfg: 0x017 (Texas Instruments), part: 0xb964, ver: 0x8)
JTAG tap: cc2538.cpu enabled
Only resetting the Cortex-M core, use a reset-init event handler to reset any peripherals or configure hardware srst support.
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x0000136c msp: 0x20008000
** Programming Started **
Flash write discontinued at 0x0023c800, next section at 0x0027ffd4
Adding extra erase range, 0x0027f800 .. 0x0027ffd3
** Programming Finished **

That was it, CC2538 flashed with JTAG and working as expected.

@dl8dtl
Copy link

dl8dtl commented Nov 16, 2023

Digging up that old discussion:
I have just migrated the CC2538 flash driver from jim.sh 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 jim.sh 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:
https://review.openocd.org/c/openocd/+/8019
(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