TI SysConfig Deep Dive: Generating Pin and Peripheral Configurations

If you’ve ever configured peripherals on STM32 with CubeMX, you know the anxiety: did the code generator just overwrite my changes? With Nordic’s nRF Connect SDK, you’ve wrestled with device tree overlays that silently fail. You’re staring at a .syscfg file in your CC2340R5 project and wondering what fresh configuration hell awaits.
Here’s the thing nobody tells you up front: SysConfig is a JavaScript file pretending to be a GUI tool. Once that clicks, everything about it (the build errors, the generated code, the merge conflicts) starts making sense. This article gives you that mental model, then walks you through configuring GPIO, UART, SPI, and I2C peripherals so you actually understand what’s happening under the hood.
If you haven’t set up your CC2340R5 project yet, start with our [getting started guide] before continuing here.
SysConfig Is a Code Generator, Not an IDE Feature
SysConfig is a declarative configuration layer between you and register-level peripheral initialization. You describe what you want (a UART at 115200 baud on these pins), and it generates the C code that makes it happen: pin muxing, driver initialization structs, clock configuration, all of it.
The closest analogy for STM32 developers: imagine CubeMX merged with a device tree system, but the underlying config file is JavaScript instead of XML or YAML. For nRF developers: think of it as Kconfig + device tree overlays, except with a GUI that actually catches your pin conflicts before you spend two hours with a logic analyzer.
TI moved to this model across the SimpleLink SDK for a practical reason: the CC23xx family shares a common driver layer with CC13xx and CC26xx parts. SysConfig abstracts the per-device differences so the same driver API works across families while the generated initialization code handles the hardware specifics.
How SysConfig Fits in Your Build
This is the single most important concept. SysConfig runs as a pre-build step. Every time you build in CCS or IAR, the SysConfig engine reads your .syscfg file and regenerates C source and header files before the compiler ever runs.
┌─────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ .syscfg │─────▶│ SysConfig │─────▶│ Generated Files │
│ (source) │ │ Engine │ │ │
│ │ │ (pre-build) │ │ ti_drivers_config.c│
│ JavaScript │ │ │ │ ti_drivers_config.h│
│ config │ │ Validates pins, │ │ ti_devices_config.c│
│ │ │ resolves mux │ │ ti_radio_config.c │
└─────────────┘ └──────────────────┘ └────────┬────────────┘
│
▼
┌─────────────────────┐
│ Compiler/Linker │
│ (your app code + │
│ generated code) │
└─────────────────────┘The key generated files:
ti_drivers_config.ccontains pin configs, peripheral instance arrays, and driver initialization tables.ti_drivers_config.hcontains#definemacros that map your instance names (CONFIG_LED,CONFIG_UART_0) to array indices.ti_devices_config.ccontains device-specific power and clock setup.ti_radio_config.ccontains BLE/proprietary RF configuration (only present if radio modules are used).
The golden rule: never hand-edit generated files. They get overwritten on every build. Your source of truth is the .syscfg file.
Navigating the SysConfig Editor
Double-click the .syscfg file in the CCS Project Explorer to open the SysConfig GUI. You’ll see four regions:
┌──────────────────────────────────────────────────────────┐
│ SysConfig Editor │
├──────────────┬───────────────────────┬───────────────────┤
│ Module List │ Configuration Panel │ Generated Code │
│ │ │ Preview │
│ ▸ GPIO │ Instance: CONFIG_LED │ │
│ ▸ UART2 │ ┌─────────────────┐ │ // ti_drivers_.. │
│ ▸ SPI │ │ Pin: DIO_6 │ │ GPIO_PinConfig │
│ ▸ I2C │ │ Mode: Output │ │ gpioPinCfgs[] │
│ ▸ Timer │ │ Init Value: Low │ │ = { ... }; │
│ ▸ Power │ └─────────────────┘ │ │
├──────────────┴───────────────────────┴───────────────────┤
│ ⚠ Warnings / Conflicts │
└──────────────────────────────────────────────────────────┘The Module List on the left shows all available peripheral drivers. Click the + to add a new module instance. The Configuration Panel in the center shows properties for the selected instance: pin assignments, modes, parameters. The Generated Code Preview on the right gives a real-time view of the C code your configuration produces. The Warnings/Conflicts bar at the bottom surfaces pin conflicts, validation errors, and deprecation notices.
Pro tip: The “Generated Files” tab on the right is your best debugging tool. Before chasing a runtime bug, check whether SysConfig actually produced what you expected.
Configuring GPIO Pins: Your First Module
Start simple. Let’s configure an LED output and a button input.
Step 1: In the Module List, click + next to GPIO. This creates a new instance with a default name.
Step 2: Rename the instance to CONFIG_LED. Set the mode to Output, assign it to DIO_6 (the LED pin on the LP-EM-CC2340R5 LaunchPad), and set the initial output value to Low.
Step 3: Add a second GPIO instance. Name it CONFIG_BUTTON, set the mode to Input, assign DIO_13, enable the internal pull-up, and set the interrupt trigger to Falling Edge.
Look at the generated code preview. Here’s the mapping:
.syscfg Source Generated ti_drivers_config.c
───────────────── ──────────────────────────────
GPIO.addInstance() ───▶ GPIO_PinConfig gpioPinConfigs[] = {
.$name = "CONFIG_LED" ───▶ /* CONFIG_LED */
.mode = "Output" ───▶ GPIO_CFG_OUTPUT |
.gpioPin = "DIO_6" ───▶ GPIO_CFG_OUT_LOW, // pin 6
};
Generated ti_drivers_config.h
──────────────────────────────
.$name = "CONFIG_LED" ───▶ #define CONFIG_LED 0
.$name = "CONFIG_BUTTON" ──▶ #define CONFIG_BUTTON 1Those #define values are array indices. In your application code, you use them like this:
GPIO_setConfig(CONFIG_LED, GPIO_CFG_OUTPUT | GPIO_CFG_OUT_LOW);
GPIO_write(CONFIG_LED, 1); // Turn on LED
GPIO_setConfig(CONFIG_BUTTON, GPIO_CFG_INPUT | GPIO_CFG_IN_PU);
GPIO_setCallback(CONFIG_BUTTON, buttonCallback);
GPIO_enableInt(CONFIG_BUTTON);The instance names you choose in SysConfig become the symbols your firmware references. Name them well.
Configuring Communication Peripherals: UART, SPI, I2C
The pattern you just learned with GPIO repeats for every peripheral. The specifics change; the workflow doesn’t.
UART
Add a UART2 module instance (the CC23xx SDK uses the UART2 driver, not the legacy UART driver). Name it CONFIG_UART_0. You’ll configure:
- TX Pin: DIO_3
- RX Pin: DIO_2
- Baud Rate: 115200
- Flow Control: None (or assign CTS/RTS pins if your hardware supports it)
SysConfig constrains your pin choices to valid UART-capable DIOs per the CC2340R5 datasheet. If you try to assign DIO_6 to UART TX and it’s not a valid mux option, SysConfig blocks it. This alone saves hours of debugging.
In your application code:
UART2_Handle uart;
UART2_Params params;
UART2_Params_init(¶ms);
params.baudRate = 115200;
uart = UART2_open(CONFIG_UART_0, ¶ms);SPI
Add an SPI module instance. Name it CONFIG_SPI_0. Assign SCLK, PICO (MOSI), and POCI (MISO) pins. For the chip select, you have a choice: let the SPI driver manage it (assign a CS pin in SysConfig) or handle it manually via a separate GPIO instance. For multi-device SPI buses, manual GPIO CS is usually the right call.
I2C
Same pattern. Add an I2C module, name it CONFIG_I2C_0, assign SDA and SCL pins. SysConfig handles the open-drain configuration automatically.
The consistency here is the point. Every module instance you add becomes a driver instance you open in code via <Driver>_open(CONFIG_INSTANCE_NAME, ¶ms). Once you’ve configured one peripheral in SysConfig, you can configure any of them.
Pin Conflict Detection Saves You From Yourself
This is where SysConfig earns its keep. Assign DIO_3 to both your UART TX and an SPI SCLK. Immediately, the bottom panel lights up with a conflict error, and both modules flag the contested pin in red.
On hardware without this tooling, you’d discover this conflict as a mysterious bus failure after your boards come back from assembly. SysConfig catches it at configuration time, before you even compile.
The Device Pin view (sometimes labeled “Pin Mux” depending on your SysConfig version) shows every DIO assignment on a single screen. This bird’s-eye view is invaluable during PCB schematic review: export or screenshot it and hand it to your hardware engineer.
Practical tip: Start with SysConfig’s suggested default pins. Only customize when your PCB layout or connector pinout demands specific assignments.
The .syscfg File Is Just JavaScript
Close the GUI. Right-click the .syscfg file and open it with a text editor. Here’s what you’ll see:
const GPIO = scripting.addModule("/ti/drivers/GPIO");
const LED = GPIO.addInstance();
LED.$name = "CONFIG_LED";
LED.mode = "Output";
LED.gpioPin.$assign = "DIO_6";
const UART2 = scripting.addModule("/ti/drivers/UART2");
const uart0 = UART2.addInstance();
uart0.$name = "CONFIG_UART_0";
uart0.uart.txPin.$assign = "DIO_3";
uart0.uart.rxPin.$assign = "DIO_2";It’s structured, readable, and, critically, diffable. This matters for teams. GUI-only configuration tools are version control nightmares: you can’t meaningfully review a binary config file in a pull request. With .syscfg, you see exactly what changed: “DIO_6 moved to DIO_7” or “added SPI instance.”
You can also script .syscfg generation for product variants. Have three hardware revisions with different pin assignments? Maintain three .syscfg files, or generate them programmatically from a shared template.
Common Pitfalls That Will Waste Your Afternoon
Editing generated files directly. You add a comment to ti_drivers_config.c, hit build, and it vanishes. The generated files are overwritten every build. Edit only the .syscfg source.
Renaming instances without updating application code. You rename CONFIG_LED to CONFIG_STATUS_LED in SysConfig. Your firmware still references CONFIG_LED. The build breaks with an undefined symbol error. The fix is obvious, but the cause isn’t always clear, especially in large projects with scattered references.
SDK version mismatch. You open a project built with CC23xx SDK 7.10 in SDK 7.40. Module paths or property names may have changed. SysConfig throws validation errors on modules it can’t resolve. Check the SDK release notes for migration steps, or regenerate your .syscfg from scratch if the delta is large.
Stale generated code. You make SysConfig changes, save, but don’t rebuild. Your compiled binary still uses the old generated files. Always do a full rebuild after SysConfig modifications. If the GUI seems stuck or shows stale output, delete the Debug/syscfg/ output folder and rebuild clean.
Build Your Configuration Muscle Memory
SysConfig is your single source of truth for pin and peripheral configuration on the CC2340. It replaces hand-written initialization code, catches hardware conflicts at design time, and produces version-controllable configuration files. The workflow is always the same: add a module, configure properties, check the generated code, use the instance name in your application.
Once this feels natural, push further. SysConfig handles DMA channel assignment, power policy configuration, and, if you’re building BLE applications, the entire radio configuration stack, which we cover in our [BLE development guide].
Start with the GPIO example above. Get an LED blinking with SysConfig-generated config. Then add a UART. Watch the generated code change with each modification. That feedback loop, config in and code out, is the mental model that makes everything else on the CC2340 click.
Hubble Network enables Bluetooth connectivity from any CC2340-class device directly to satellites—no gateways, no infrastructure. See how it works →