Self-hosted service
JelmerT/cc2538-bsl avatar
JelmerT/cc2538-bsl

cc2538-bsl puts TI's serial boot loader on the command line

Python cross-platform script to upload firmware via the serial boot loader onto the CC13xx, CC2538 and CC26xx SoC.

619 stars199 forksPythonBSD-3-Clause

At a glance

What is it?
cc2538-bsl is a Python script that talks to the serial boot loader of the Texas Instruments CC2538, CC26xx and CC13xx SoCs, so you can erase, program, verify and read flash over a plain USB to serial converter. Most of the documentation is about how to get the chip into its boot loader in the first place, which is the part that is genuinely hard.
Who is it for?
cc2538-bsl suits a developer working with TI CC2538, CC13xx or CC26xx hardware who needs to flash a board over the serial pins rather than over JTAG, and it saves you from the vendor's own flashing tools on a device where the JTAG header may not be exposed. It is the wrong tool if you need to read protected flash, if your board is wired in a way the ROM boot loader cannot reach, such as the CC2650 Sensortag, or if you want a supported vendor workflow.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 92 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the script actually talks to

The repository contains a Python script that communicates with the boot loader of the Texas Instruments CC2538, CC26xx and CC13xx SoCs. It can be used to erase, program, verify and read the flash of those chips with a simple USB to serial converter.

That last clause is the whole design constraint. This is not a debug probe and not a JTAG programmer. It is a client for the boot loader the chip already contains, reached over the UART pins. Anything the ROM boot loader will not do is out of reach, and the documentation is mostly about that boundary.

The hardware requirements are short. You need a Python interpreter, with Linux and Mac users expected to be fine, and Windows users pointed at the Python download. You need a USB to serial converter, and if you use the SmartRF06 board with an Evaluation Module mounted on it you can use the on board FTDI chip, making sure the Enable UART jumper is set. For a different platform the README notes that cheap USB to UART converters exist, but you must use one with 3.3V voltage levels.

There is also a Docker route for people who do not want to install dependencies. The README points at the git-developer/ti-cc-tool project, which runs the script as a one-liner in a container with the dependencies already present.

The audience is therefore embedded developers with a board on the bench, a serial adapter, and a firmware image they need to get onto the chip without a JTAG probe.

Three dependencies, and how the firmware type is guessed

The script uses pyserial to talk to the serial port and the chip, and that is the only hard dependency. IntelHex is needed if you want to program a device from an Intel Hex file, and python-magic is used to auto-detect whether the firmware is a raw binary or an Intel Hex.

The detection logic is where a small design decision shows. With python-magic installed, the script inspects the file itself. Without it, the script falls back to looking at the filename extension, and the README calls that sub-optimal. If the extension is .hex, .ihx or .ihex the firmware is assumed to be Intel Hex, and in all other cases it is treated as raw binary.

That fallback is the kind of thing to be aware of when a flash fails in a confusing way. A binary image that happens to be named something.hex will be handed to the Intel Hex parser, and a Hex file named something.bin will be pushed as raw bytes. The mitigation is to install python-magic, which the README flags as possibly needing extra dependencies depending on the operating system.

The packaging reflects the same split. The pyproject file lists pyserial as the only required dependency, with the others as optional extras: intelhex, python-magic and pytest for the test suite. The console script is registered as cc2538-bsl, pointing at the main_cli entry point in the cc2538_bsl package, and the version is not written down but derived by setuptools-scm, which means the version follows the repository history rather than a number someone updates by hand.

CC2538: the Image Valid bits decide whether you can talk to the chip

The CC2538 section explains a mechanism that catches people out.

A chip without a valid image, as it comes from the factory, will automatically start the boot loader. After you upload an image, the Image Valid bits are set to 0 to indicate that a valid image is present in flash, and on the next reset the boot loader will not be started and the image is executed immediately.

So the first flash works, and the second attempt may find a chip that boots your firmware instead of the boot loader. The fix is the boot loader backdoor, and the script currently only checks this on firmware for the 512K model.

The backdoor is enabled with the 8-bit boot loader backdoor field in the CCA area in flash. If you set this field to 0xF3FFFFFF the boot loader will be enabled when pin PA3 is pulled low during boot, which translates to holding down the select button on the SmartRF06 board while pushing the EM reset button.

There is also a recovery path, and it is expensive. If you locked yourself out, or there is already an image flashed on your SoC, you will need a JTAG programmer to erase the image. That resets the image valid bits and enables the boot loader on the next reset. The SmartRF06EB contains both a JTAG programmer and a USB to UART converter on board, which is why it is the board most of this documentation assumes.

The operational lesson is straightforward: include the backdoor in the firmware you flash, or keep a JTAG programmer within reach.

CC13xx and CC26xx: the bootloader lives in the CCFG

For the newer families, the boot loader is configured through the BL_CONFIG register in CCFG. BOOTLOADER_ENABLE should be set to 0xC5 to enable the boot loader in the first place.

That is enough if the chip has not been programmed with a valid image. If a valid image is present, the remaining fields of BL_CONFIG and the ERASE_CONF register must also be configured correctly: select a DIO by setting BL_PIN_NUMBER, select an active level for the DIO by setting BL_LEVEL, enable failure analysis by setting BL_ENABLE to 0xC5, and make sure the BANK_ERASE command is enabled, since the BANK_ERASE_DIS_N bit in the ERASE_CONF register in CCFG must be set, with BANK_ERASE enabled by default.

The README gives a concrete example of binding the boot loader backdoor to KEY_SELECT on the SmartRF06EB, with BOOTLOADER_ENABLE set to 0xC5, BL_LEVEL set to 0x00 for active low, BL_PIN_NUMBER set to 0x0B for DIO 11, and BL_ENABLE set to 0xC5 to enable failure analysis. The equivalent macros in the vendor SDK are named alongside each value.

If you are using CC13xx/CC26xxware, the relevant settings are under startup_files/ccfg.c, which is the case if you are using Contiki.

Three hardware notes follow, and they are the practical limits of the whole approach. The physical wiring on the CC2650 Sensortag does not meet the ROM boot loader's requirements in terms of serial interface configuration, so interacting with the Sensortag through this script is, in the README's words, impossible and will remain so. For CC1350STK sensortags it is possible to solder cables to R21 and R22 to flash using the serial boot loader. And for the ITead SONOFF Zigbee 3.0 USB Dongle Plus, the CC2652P based ZBDongle-P adapter, you need to invoke the toggle to activate the boot loader with --bootloader-sonoff-usb if you do not want to open its enclosure and press the boot button on the PCB.

Failure analysis is a debug convenience with a security cost

The README is unusually direct about the risk in the configuration it just taught you.

These settings are very useful for development, but enabling failure analysis in a deployed firmware may allow a malicious user to read out the contents of your device's flash or to erase it. Do not enable this in a deployment unless you understand the security implications.

That sentence deserves a moment, because it describes a debug backdoor that is deliberately left in the firmware. The boot loader backdoor is the mechanism that lets you recover a locked chip, and the same mechanism lets anyone who can hold a pin at the right level read or erase the flash of a deployed device. There is no separation between a development convenience and a production vulnerability here, only a decision about which configuration you ship.

The chain in the README makes the point concrete. Enabling failure analysis means the boot loader is activated if, at the time of reset, failure analysis is enabled and the selected DIO is found to be at the active level. On a board with a user-reachable button, that is a physical access path to the flash.

So the same four CCFG values that make development convenient are the ones to review before a device goes out. A production build should either disable failure analysis or bind the backdoor to a pin that the enclosure makes unreachable.

Port selection is manual more often than you would like

The usage section is short but contains the operational detail that trips people up.

The script will automatically select the first serial looking port from a USB to UART converter in /dev on OSX and Linux for uploading. The README warns to be careful, because on the SmartRF06B board under Linux this might be the JTAG interface as opposed to the UART interface. In that case you select the correct serial port manually with the -p option.

This is a hardware ambiguity rather than a software bug. A board that exposes both a JTAG interface and a UART interface over the same FTDI chip presents two serial devices, and the one that appears first is not necessarily the one you want.

Windows is called out separately: serial port selection under Windows needs testing. That is a candid admission, and it is consistent with the package metadata, which lists the operating systems as POSIX Linux and MacOS and sets the setuptools platforms to posix.

The Docker route sidesteps part of the problem, since a container with the host serial device passed through presents the port in a predictable way, and the ti-cc-tool project referenced by the README builds on that.

Packaging, licence and the maintenance picture

The project is a small Python package with a clean structure: a cc2538_bsl package directory, a tests directory, a pyproject.toml and a LICENSE.md file, plus a Travis configuration and a .gitignore.

The metadata is explicit about the floor. The package requires Python 3.6 or newer, declares pyserial as its only runtime dependency, and registers the console script cc2538-bsl against the main_cli entry point. The classifiers mark the development status as Production/Stable and the intended audience as Developers, with the licence classifier set to the BSD License and the LICENSE.md file named in the project table.

The licence is BSD-3-Clause, which is a permissive licence with no obligations beyond keeping the notice, and for a device-side flashing script that is the right kind of terms: you can put it in an internal tooling repository without a conversation about copyleft.

On maintenance, the repository is not archived and the last push was on 2026-07-22. There are no GitHub releases, which is consistent with a project whose version is derived from the git history by setuptools-scm, and with a script that people install from a checkout or a package index rather than from release artefacts.

The build badge in the README points at a Travis configuration, which dates the continuous integration setup, and the presence of a tests directory with pytest as an optional extra says the same thing: the project has a test suite, and it is exercised rather than assumed.

For an engineer, the practical summary is that this is a small, focused script with an honest README, whose hardest problem is not the flashing but the board-specific configuration that gets the chip into a state where flashing is possible.

Editorial conclusion

cc2538-bsl suits a developer working with TI CC2538, CC13xx or CC26xx hardware who needs to flash a board over the serial pins rather than over JTAG, and it saves you from the vendor's own flashing tools on a device where the JTAG header may not be exposed. It is the wrong tool if you need to read protected flash, if your board is wired in a way the ROM boot loader cannot reach, such as the CC2650 Sensortag, or if you want a supported vendor workflow. Verify first that the boot loader is enabled in the CCFG of the firmware already on the chip, because a chip that has a valid image will boot the image and ignore the serial boot loader entirely, and confirm that your USB to serial adapter is a 3.3V one before you connect anything.

Frequently asked questions

What do I need to install to run cc2538-bsl?

A Python interpreter, Python 3.6 or newer, plus pyserial for the serial port. You also need IntelHex to program from an Intel Hex file, and python-magic for reliable firmware type detection. The README notes that Docker can be used instead to run the script without installing dependencies.

Why can I not talk to my CC2538 after flashing it once?

After an image is uploaded the Image Valid bits are set to 0, so on the next reset the boot loader will not start and the image runs instead. You need to enable the boot loader backdoor in your firmware, and if you are already locked out you will need a JTAG programmer to erase the image and reset the valid bits.

How do I enable the serial boot loader on CC13xx and CC26xx chips?

Set BOOTLOADER_ENABLE to 0xC5 in the BL_CONFIG register in CCFG, and when a valid image is already present also set BL_PIN_NUMBER, BL_LEVEL and BL_ENABLE, and clear the BANK_ERASE_DIS_N bit in the ERASE_CONF register. The settings live in startup_files/ccfg.c when you use the vendor SDK.

Can cc2538-bsl flash a CC2650 Sensortag?

No. The physical wiring on the Sensortag does not meet the ROM boot loader's requirements in terms of serial interface configuration, so the README states that interacting with it through this script is and will remain impossible. For CC1350STK sensortags, cables can be soldered to R21 and R22 to flash over the serial boot loader.

Official sources

  1. Issues
  2. JelmerT/cc2538-bsl on GitHub
  3. License: BSD-3-Clause
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jelmert-cc2538-bsl.svg)](https://hysenlabs.com/projects/jelmert-cc2538-bsl)

Was this useful?