This file explains how to work on this MSPM0G3507 firmware project safely. The project uses CMake + GCC + OpenOCD, not a CCS project layout, but the SysConfig and DriverLib rules still matter.
core/contains the MSPM0 SDK surface used by this project: startup code, linker script, SysConfig output, DriverLib, CMSIS, FreeRTOS, and syscall glue.core/trobot.syscfgis the source of truth for clocks, pins, peripherals, DMA channels, interrupts, and generated initialization names.bsp/contains board support code for UART, SPI, LCD, flash, time, GPIO, and low-level helpers.components/contains optional reusable modules.components/utilsis a git submodule and is required by the current application. It supplies the C++ logger, task/queue wrappers, terminal, message, CRC, and VOFA helpers.app/contains the firmware entry point and application tasks.bsp/include/bsp/is the public BSP include surface.bsp/internal/is private to the BSP target and must not be included fromapp/or components.*.cfgfiles at the repository root select the OpenOCD probe interface.
The current boot and initialization sequence is:
- The GCC startup code initializes
.data,.bss, C/C++ constructors, and then callsmain(). main()callsSYSCFG_DL_init(), creates theapp_entrancetask, and starts the FreeRTOS scheduler.app_entrance()callsbsp_hw_init(), initializes UART0 and its TX DMA queue, enables the board GPIO interrupt, initializes the logger, and creates application tasks.bsp_hw_init()initializes the ST7735 LCD and verifies the W25Q128 device ID. A failed BSP assertion records its expression/message/file/line, breaks only when a debugger is attached, and then stops forever.
Keep this order in mind when adding code. A task that logs asynchronously needs its UART initialized first. LCD and flash users need the board/SPI GPIO state initialized first. Do not move scheduler-dependent initialization into global C++ constructors.
The current interrupt ownership is split across layers:
GROUP1_IRQHandleris owned byapp/main/main.ccfor the board key GPIO.UART0_IRQHandlerthroughUART3_IRQHandlerare owned bybsp/src/uart.c.TIMG8_IRQHandleris owned bybsp/src/uart.c; the generatedUART_RX_IDLEone-shot timer provides the UART RX idle timeout.SVC_Handler,PendSV_Handler, andSysTick_Handlerare owned by the FreeRTOS port.- All other startup-vector handlers are weak defaults until a module provides the exact symbol.
The generated configuration currently uses an 80 MHz CPU clock, a 1 kHz
FreeRTOS tick, UART0 as UART_DEBUG_INST, and SPI1 for the LCD and W25Q128.
These are orientation notes only; after any SysConfig change, the generated
header and FreeRTOSConfig.h are the authoritative values.
Use an ARM embedded GCC toolchain.
cmake -S . -B cmake-build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debugThe normal output files are:
cmake-build-debug/trobot.elfcmake-build-debug/trobot.hexcmake-build-debug/trobot.bincmake-build-debug/trobot.map
The app and BSP source lists use recursive CMake globs without
CONFIGURE_DEPENDS. After adding, removing, or renaming a source file, rerun
the CMake configure command before building. Adding or removing a component
directory also requires reconfiguration.
This project needs an OpenOCD build with TI MSPM0 support. Before flashing, check which OpenOCD executable is active:
where openocd
openocd --versionUse the first where openocd result to understand which executable will run.
Then verify that it can resolve this project's MSPM0 target scripts. A quick
script-resolution check that should not initialize the adapter is:
openocd -f daplink.cfg -c "shutdown"With a DAPLink/CMSIS-DAP probe connected:
cmake --build cmake-build-debug --target flash_and_verifyFor other probes, use the matching root config manually:
openocd -f xds110.cfg -c "init" -c "reset halt" -c "program cmake-build-debug/trobot.elf verify reset exit"
openocd -f stlink.cfg -c "init" -c "reset halt" -c "program cmake-build-debug/trobot.elf verify reset exit"If OpenOCD reports that it cannot find target/ti_mspm0.cfg, the wrong OpenOCD
is being used or its script search path is wrong. If it reports that it cannot
find a matching CMSIS-DAP device, the MSPM0 scripts were found and the remaining
problem is probe/USB/driver/hardware related.
Treat core/trobot.syscfg as the peripheral configuration source, but do not
modify it by default. Only edit core/trobot.syscfg when the user explicitly
asks for a change that requires SysConfig, such as pins, clocks, DMA,
UART/SPI/I2C/ADC/timer setup, or interrupt ownership.
When a user-requested change does require SysConfig:
- Edit
core/trobot.syscfgwith TI SysConfig when possible. - Preserve the metadata comments at the top of the file, including
@cliArgs,@v2CliArgs,@versions, device, package, and SDK product. - Regenerate the SysConfig outputs into
core/. - Re-read
core/ti_msp_dl_config.hbefore using generated names in code. - Check that the
.syscfg,ti_msp_dl_config.c,ti_msp_dl_config.h, and linker-script diffs form one consistent change set. A changed.syscfgwith unchanged generated output is not complete. - Build the project.
Do not guess generated names. Use the local macros and function spellings from
core/ti_msp_dl_config.h, such as SYSCFG_DL_init(), UART_DEBUG_INST,
DMA_UART0_TX_CHAN_ID, SPI1_INST, and GPIO_BOARD_LED_PIN.
Be careful with these generated or SysConfig-owned files:
core/ti_msp_dl_config.ccore/ti_msp_dl_config.hcore/device_linker.lds
It is acceptable for this repository to track those files because the CMake build consumes them directly. However, avoid manual edits unless the change is intentional, reviewed, and cannot reasonably be represented in SysConfig.
- Keep opening braces on the same line as functions, control-flow statements, types, and other block declarations.
- Use
snake_casefor functions, types, variables, parameters, and structure or class members. Preprocessor macro names and enum values are exempt and may useUPPER_SNAKE_CASE. - Keep implementations as simple and direct as practical. Avoid unnecessary abstraction, indirection, helper layers, and duplicated state.
- Prefer short, clear variable names. Do not construct excessively long or complicated identifier names when a concise name communicates the scope.
- There is no strict single-line length limit. Prefer readability and do not wrap otherwise clear code solely to satisfy a conventional column limit.
- Prefer existing BSP APIs over directly touching DriverLib from
app/. - Prefer DriverLib and SysConfig-generated macros over raw register writes.
- Keep app logic in
app/, board abstractions inbsp/, reusable C++ helpers incomponents/, and chip/toolchain integration incore/. - Do not edit vendored SDK, CMSIS, FreeRTOS, or DriverLib files unless fixing a project-blocking integration issue.
- Preserve interrupt handler names exactly as defined by the startup file and
ti_msp_dl_config.h. - For new IRQ users, confirm NVIC enable, interrupt priority, peripheral interrupt enable, and the exact handler symbol.
- Be conservative with RAM. This target has 32 KiB SRAM, and the current build already uses a meaningful portion of it.
- Avoid large stack buffers in tasks and ISRs.
- Check formatted output lengths before passing
snprintf/vsnprintfresults to UART or DMA send functions. - Do not call blocking or task-only FreeRTOS APIs from interrupts. Use
FromISRAPIs where needed. - C++ is built without exceptions, RTTI, or
__cxa_atexit. Do not use exceptions,dynamic_cast, or code that depends on runtime type information or global-object destruction.
- FreeRTOS uses
heap_4.cwith a 10 KiB configured heap. Both static and dynamic allocation APIs are enabled, but the current app and utility task wrappers use dynamic allocation. - FreeRTOS stack-depth arguments are words, not bytes. The current tick rate is 1 kHz, preemption is enabled, and time slicing is disabled.
- Despite its name,
os::task::static_create()currently allocates its callable thunk withpvPortMalloc()and creates the task withxTaskCreate(). Do not count it as a fully static task when budgeting RAM. bsp_time_delay()usesvTaskDelay()only from task context while the scheduler is running; before the scheduler or in an ISR it busy-waits.bsp_time_get_ms()is FreeRTOS tick time, not a persistent wall clock.- UART RX callbacks run in the TIMG8 idle ISR. Keep them short and use queues,
notifications, or other
FromISRhandoff APIs for substantial work. - Each UART has one RX callback set by
bsp_uart_set_callback(). It runs after the RX line becomes idle and receives at most the first 128 bytes; excess bytes from the same continuous receive are ignored. RX shares the generated 16-bit TIMG8UART_RX_IDLEone-shot timer across UART instances. It is armed only while receiving, so TIMG0 remains available to other code. - UART asynchronous TX uses fixed packet slots protected against concurrent
task/ISR producers. Its APIs return
falseif DMA is unavailable, a packet is too large, or all slots are occupied; callers that cannot tolerate loss must retry or provide backpressure. - The LCD and W25Q128 share SPI1. Device helpers hold a recursive FreeRTOS
mutex while the scheduler is active. For a multi-call LCD pixel stream, hold
bsp_spi_lock(SPI1_INST)across address setup, begin/write/end, and never access the shared bus from an ISR.
- Top-level
CMakeLists.txtowns the toolchain, target executable, link flags, post-build artifact generation, and flash target. core/CMakeLists.txtdefinesti_coreand imports DriverLib/CMSIS DSP archives.bsp/CMakeLists.txtbuilds the board support static library.components/CMakeLists.txtauto-loads component directories with their ownCMakeLists.txtand links aliases namedcomponents::<name>.app/CMakeLists.txtrecursively includes app subdirectories and builds app sources as an object library.- The top-level target uses C17/C++17 defaults, while the current
app,bsp, andutilstargets explicitly request C++23. New code must still obey the globally disabled exception/RTTI settings.
If adding a new component under components/<name>, provide a local
CMakeLists.txt and define a components::<name> alias so the aggregator links
it automatically.
Clone with submodules:
git clone --recursive <repo-url>If components/utils is missing:
git submodule update --init --recursiveThe root .gitignore intentionally ignores most components/* content while
keeping components/CMakeLists.txt, so remember that component code may live in
submodules.
Before handing off a firmware change:
cmake -S . -B cmake-build-debug -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debugIf the task affects flashing or debug configuration:
openocd -f daplink.cfg -c "shutdown"If hardware is not connected, report validation as build/config-only. Do not claim flashing or board behavior was verified without a connected board and probe.