跳到主要内容

MSPM0 代码生成

代码生成器的 libxr mspm0 setup 命令把 TI SysConfig 工程接入 LibXR:解析工程根目录的 .syscfg 文件,生成 C++ 初始化代码。时钟、引脚、DMA 通道和外设由 SysConfig 配置,main.c 调用 SYSCFG_DL_init() 完成初始化;生成的代码以 SysConfig 生成的宏构造 LibXR 对象,XRobot 工程另外生成 XR_REGISTER 硬件注册。


快速使用​

在 SysConfig 工程根目录下执行:

libxr mspm0 setup -d .

工程根目录有一份 .syscfg 文件。该命令依次完成以下步骤:

  1. 工程目录还没有 .gitignore 时创建一个,忽略 build、.history、.cache、CMakeFiles 和 .config.yaml;已有的 .gitignore 保持不变;
  2. 解析 .syscfg,写出 .config.yaml;
  3. 在 User/ 下生成 app_main.cpp、app_main.h 和 libxr_config.yaml。

--xrobot 和 --no-xrobot 都不写时,沿用输出文件现在的选择,新文件不使用 XRobot;使用 XRobot 时生成 XR_REGISTER 注册和 XROBOT_MAIN();,命令结束前提示运行 xrobot gen 更新 User/xrobot_main.hpp。已有的 app_main.cpp 不是 libxr gen 生成时,setup 给出警告并列出将被替换的行,User Code 区域之外的内容全部重新生成。


SysConfig 输出的来源​

解析读取两个来源:.syscfg 本身(外设实例、参数和引脚分配),以及 SysConfig 生成的 ti_msp_dl_config.h/.c(实际写出的宏名、CPUCLK_FREQ、ULPCLK 分频和各实例的时钟配置)。

设置了环境变量 SYSCONFIG_TOOL 和 MSPM0_SDK_INSTALL_DIR 时,解析对 .syscfg 运行一次 SysConfig,输出写入临时目录;这两个变量与 BSP 的 cmake/MSPM0SysConfig.cmake 使用的是同一组。没有设置这两个变量时,解析复用工程 build*、cmake-build* 或 out 目录下比 .syscfg 新的生成输出。复用只比较修改时间:.syscfg 被替换为修改时间更旧的文件时仍会复用旧输出,此时重新构建一次,或设置这两个变量后再运行。

BSP 的 CMake 在 configure 阶段对 .syscfg 运行 SysConfig,输出在构建目录的 syscfg/ 下,.syscfg 改动后自动重新生成,见 MSPM0 环境配置。


示例输出​

以下是在 bsp-mspm0g3507-mini 的副本上删去 User/ 下的三个生成文件后的首次运行,设置了两个环境变量:

$ libxr mspm0 setup -d . --xrobot
[信息] 正在处理 mspm0g3507_minidb48.syscfg……
[信息] 正在对 mspm0g3507_minidb48.syscfg 运行 SysConfig
[信息] 配置已导出到:.\.config.yaml
[信息] 系统:裸机
[信息] .\User\libxr_config.yaml 不存在,按默认设置新建
[信息] 未设置 terminal_source;使用 uart0
[信息] 已生成 User:已写入 app_main.cpp、app_main.h、libxr_config.yaml
[信息] 下一步:运行 `xrobot gen` 更新 User/xrobot_main.hpp;libxr 的 setup 不更新它

===== [配置摘要] =====

MCU:MSPM0 MSPM0G3507

GPIO(4 个引脚):
LED1:PB8,OUTPUT
LED2:PA16,OUTPUT
KEY1:PB24,INPUT,FALL 中断
KEY2:PB20,INPUT,FALL 中断

已启用的外设:
UART:2 个实例
UART_0
UART_1
I2C:2 个实例
I2C_0
I2C_1
SPI:1 个实例
SPI_1
PWM:1 个实例
PWM_TIMA1
Other:5 个实例
ADC12_0
ADC12_1
DAC0
MCAN_0
QEI_TIMG8
代码块已折叠

之后再运行时,生成器只改写内容有变化的文件。配置摘要中的 Other 是 LibXR 还没有驱动的配置,见生成的对象与命名。


输出结构​

执行完成后,工程新增或修改这些文件:

.
├── .config.yaml # 解析生成的配置文件
├── main.c # BSP 提供:SYSCFG_DL_init() 之后调用 app_main()
├── User/
│ ├── app_main.cpp # 入口源文件
│ ├── app_main.h # 入口源文件的头文件
│ └── libxr_config.yaml # LibXR 配置文件
└── libxr/ # Git 子模块:LibXR 本体

app_main.cpp​

app_main.cpp 包含 LibXR 初始化函数 app_main()。/* User Code Begin N */ 与 /* User Code End N */ 之间的代码在重新生成时保留,其余部分每次重新生成。生成的外设对象都是 static 对象,app_main() 返回后仍然有效;长期使用的局部对象声明为 static。外设对象可以以基类指针传给线程或模块使用,做法见与XRobot集成。

下面是上例首次生成的 app_main.cpp 节选,省略处写作 // ...:

// Generated by `libxr gen`; do not edit by hand.
// Generated from mspm0g3507_minidb48.syscfg (MSPM0G3507); SysConfig configures the
// clocks, pins, DMA channels and peripherals (SYSCFG_DL_init() in main.c). Put
// application code between the "User Code Begin" and "User Code End" markers.
#include "app_main.h"

#include "libxr.hpp"
#include "mspm0_gpio.hpp"
#include "mspm0_i2c.hpp"
#include "mspm0_power.hpp"
#include "mspm0_pwm.hpp"
#include "mspm0_spi.hpp"
#include "mspm0_timebase.hpp"
#include "mspm0_uart.hpp"
#include "ti_msp_dl_config.h"
#include "xrobot_main.hpp"

using namespace LibXR;

/* User Code Begin 1 */
/* User Code End 1 */

// Input clocks: the BUSCLK of the power domain. PD0 from ULPCLK = CPUCLK_FREQ / 2
// (DL_SYSCTL_setULPCLKDivider) and PD1 from MCLK (CPUCLK_FREQ). The I2C driver resets
// the clock configuration of its peripherals, so the BUSCLK of the domain is the right
// argument.
static constexpr uint32_t PD0_BUSCLK_FREQ = (CPUCLK_FREQ / 2);
static constexpr uint32_t PD1_BUSCLK_FREQ = CPUCLK_FREQ;

// Ownership of the DMA channels that serve a UART transmitter;
// MSPM0_UART_MAIN_INIT checks these names against the ones SysConfig generated.
#define DMA_CH_UART0_TX_LIBXR_UART_IRQN UART_0_INST_INT_IRQN
#define DMA_CH_UART0_TX_LIBXR_UART_TX 1
// ...

// DMA buffers. A UART gets 2 x N bytes for its two transmit halves; the receive side
// runs on byte interrupts. The SPI DMA buffers are split in two halves as well; the
// I2C driver uses polling, its buffer is the staging area for DMA transfers.
alignas(size_t) static uint8_t uart0_tx_buf[2 * 128];
// ...
alignas(4) static uint8_t i2c0_buf[32];
// ...

extern "C" void app_main(void)
{
/* User Code Begin 2 */
/* User Code End 2 */

// Timebase and platform
static MSPM0Timebase timebase;
PlatformInit();
static MSPM0PowerManager power_manager;

// GPIO: SysConfig configured LED1 (PB8), LED2 (PA16) as output pins and KEY1 (PB24),
// KEY2 (PB20) as input pins. The interrupt edge comes from SysConfig; RegisterCallback
// and EnableInterrupt belong to the consumer (a Module or the User Code).
static MSPM0GPIO LED1(GPIO_LEDS_PIN_LED1_PORT, GPIO_LEDS_PIN_LED1_PIN,
GPIO_LEDS_PIN_LED1_IOMUX);
// ...
static MSPM0GPIO KEY1(GPIO_KEYS_PORT, GPIO_KEYS_PIN_KEY1_PIN, GPIO_KEYS_PIN_KEY1_IOMUX);
static MSPM0GPIO KEY2(GPIO_KEYS_PORT, GPIO_KEYS_PIN_KEY2_PIN, GPIO_KEYS_PIN_KEY2_IOMUX);

// UART: TX with DMA; the receive side runs on byte interrupts.
static MSPM0UART uart0(MSPM0_UART_MAIN_INIT(UART_0, DMA_CH_UART0_TX, uart0_tx_buf,
sizeof(uart0_tx_buf), 5, 128));
// ...

// I2C: polling; the buffer stages DMA transfers.
static MSPM0I2C i2c0(MSPM0_I2C_INIT(I2C_0, PD0_BUSCLK_FREQ, i2c0_buf, sizeof(i2c0_buf),
8), {I2C_0_BUS_SPEED_HZ});
// ...

// SPI: DMA for transfers longer than the threshold.
static MSPM0SPI spi1(MSPM0_SPI_INIT(SPI_1, PD1_BUSCLK_FREQ, DMA_CH_SPI1_RX,
DMA_CH_SPI1_TX, spi1_rx_buf, sizeof(spi1_rx_buf), spi1_tx_buf,
sizeof(spi1_tx_buf), 3));

// PWM: the channels of one timer share one period, which SysConfig configures.
static MSPM0PWM pwm_tima1_c0(MSPM0_PWM_CH(PWM_TIMA1, 0));
static MSPM0PWM pwm_tima1_c1(MSPM0_PWM_CH(PWM_TIMA1, 1));

// Terminal on uart0
STDIO::read_ = uart0.read_port_;
STDIO::write_ = uart0.write_port_;
static RamFS ramfs("XRobot");
static Terminal<32, 32, 5, 5> terminal(ramfs);
static auto terminal_task = Timer::CreateTask(terminal.TaskFun, &terminal, 10);
Timer::Add(terminal_task);
Timer::Start(terminal_task);

// Hardware registration
XR_REGISTER(power_manager, LibXR::PowerManager);

XR_REGISTER(LED1, LibXR::GPIO);
// ...

XR_REGISTER(pwm_tima1_c0, LibXR::PWM);
// ...

XR_REGISTER(spi1, LibXR::SPI);

XR_REGISTER(uart0, LibXR::UART);
// ...

XR_REGISTER(i2c0, LibXR::I2C);
// ...

XR_REGISTER(ramfs, LibXR::RamFS);

XR_REGISTER(terminal, LibXR::Terminal<32, 32, 5, 5>);

/* User Code Begin 3 */
/* User Code End 3 */
XROBOT_MAIN();
}
代码块已折叠

每个外设对象以 SysConfig 生成的宏构造,例如 GPIO 的 GPIO_LEDS_PIN_LED1_PORT、UART 的 UART_0。各对象用到的中断由 LibXR 驱动在构造时开启并设置优先级,SysConfig 中对同一中断的设置被驱动覆盖。引脚中断的边沿来自 SysConfig,回调的登记(RegisterCallback 和 EnableInterrupt)由使用方在模块或 User Code 区域中完成,例如为 KEY1 接上中断计数:

/* User Code Begin 1 */
#include <atomic>

static std::atomic<uint32_t> key1_irq_count{0};

static void OnKeyInterrupt(bool in_isr, std::atomic<uint32_t>* count)
{
(void)in_isr;
count->fetch_add(1, std::memory_order_relaxed);
}
/* User Code End 1 */

// ...

/* User Code Begin 3 */
KEY1.SetConfig({GPIO::Direction::FALL_INTERRUPT, GPIO::Pull::NONE});
KEY1.RegisterCallback(GPIO::Callback::Create(OnKeyInterrupt, &key1_irq_count));
KEY1.EnableInterrupt();
/* User Code End 3 */
代码块已折叠

生成的对象与命名​

生成的对象来自 LibXR 有驱动的配置:UART、控制器模式的 I2C 和 SPI、PWM;GPIO 引脚各生成一个对象。其余配置(ADC12、DAC12、MCAN、QEI、目标模式的 I2C、外设模式的 SPI 等)列入配置摘要的 Other,只作展示。

生成结果总是包含 static MSPM0PowerManager power_manager;,位置在 PlatformInit() 之后;使用 XRobot 时按 PowerManager 注册,且排在其他对象之前。power_manager 是保留名,GPIO 标签与它相同会使生成报错并提示在 SysConfig 中给引脚改名。生成的代码需要带 PowerManager 的 LibXR,即 v2.0.1 或更新版本。终端的 power reset|shutdown|bootloader 命令由用户代码调用 power_manager.RegisterCommand(ramfs) 注册,生成器不生成这条调用。

对象的命名:

配置对象名例子
GPIOSysConfig 中引脚的标签($name 去掉 PIN_ 前缀)LED1、KEY1
UART、I2C、SPI实例所用外设名的小写(UART_0 使用 UART0)uart0、i2c1、spi1
PWMpwm_<定时器>_c<通道号>pwm_tima1_c0

GPIO 引脚的 $name 去掉 PIN_ 前缀后不是合法标识符时(SysConfig 默认的 PIN_0 去掉后是 0),对象改用小写的组名加引脚名(如 gpio_btn_pin_0),并给出警告提示在 SysConfig 中给引脚命名。

定时器的每个 CCP 通道生成一个 PWM 对象,同一定时器的通道共用一个周期,周期由 SysConfig 配置。libxr_config.yaml 给同一定时器的通道设置了不同的频率时,生成器给出警告,以最后一次 SetConfig 为准。

各配置的生成要求:

  • UART 需要发送 DMA 通道,接收默认按字节中断(MSPM0_UART_MAIN_INIT)。UART Extend 实例配置了接收 DMA 时按循环 DMA 接收(MSPM0_UART_EXTEND_INIT,接收环的字节数见 rx_dma_buffer_size),接收通道须为 FULL-DMA 通道;UART Main 实例的接收 DMA 通道用不上,生成器给出警告。缺少发送 DMA 的 UART 使生成报错。
  • SPI 需要一对 DMA 通道,否则生成报错;接收通道须由 RX 触发(SysConfig 的 SPI > DMA Configuration 中,DMA Event 触发选 DL_SPI_DMA_INTERRUPT_RX),RX timeout 触发使生成报错。
  • I2C 以轮询工作,缓冲区是 DMA 传输的中转。

时钟​

生成的代码为用到的电源域定义 BUSCLK 常量:PD1 的 BUSCLK 是 CPUCLK_FREQ,PD0 的是 CPUCLK_FREQ 除以 ULPCLK 分频(生成代码里的 DL_SYSCTL_setULPCLKDivider,默认 1)。I2C 驱动自行重设所辖外设的时钟配置(BUSCLK、分频 1),构造实参取所在电源域的 BUSCLK;SPI 驱动沿用 SysConfig 写下的时钟配置,构造实参取 SysConfig 选择的时钟源(BUSCLK、MFCLK 的 4 MHz 或 LFCLK 的 32.768 kHz)除以分频,其他时钟源使生成报错。SysConfig 为 I2C 和 SPI 输出的宏不含时钟频率,工程缺少电源域或 CPUCLK_FREQ 时生成报错。UART 直接引用 SysConfig 的 INST_FREQUENCY 宏,PWM 的源时钟由驱动按 INST_CLK_FREQ 反推。


libxr_config.yaml​

User/libxr_config.yaml 保存代码生成的设置。文件不存在时按默认值新建;之后每次生成补上缺少的设置,已有的值保持不变。外设的设置段只在工程有对应外设时写入,段中每个对象一项,键为对象名(uart0、pwm_tima1_c0)。值为 null(YAML 中只写了键)时取默认值;缓冲区和队列必须是正整数。

各设置段:

段键与默认值说明
UARTtx_buffer_size: 128、rx_buffer_size: 128、tx_queue_size: 5UART Extend 实例另有 rx_dma_buffer_size: 128(偶数)
I2Cbuffer_size: 32、dma_enable_min_size: 8
SPItx_buffer_size: 32、rx_buffer_size: 32、dma_enable_min_size: 3
PWMfrequency(默认 null)null 时不调用 SetConfig,见生成的对象与命名
Terminalread_buff_size: 32、max_line_size: 32、max_arg_number: 5、max_history_number: 5终端的模板参数,见串口与终端

terminal_source 遵循 STM32 的约定:空值或 none 不生成终端;文件里还没有这个键时(第一次生成)选第一个 UART。tx_buffer_size 的取值还须满足驱动的断言:发送缓冲为 2 × N 字节,2 × N 是 2 * alignof(size_t) 的倍数(Cortex-M0+ 上即 N 为 4 的倍数),且 N 不超过单次 DMA 传输的 0xFFFF 字节。设置键不区分大小写,文件中已有的写法保持不变。

以下是 bsp-mspm0g3507-mini 首次生成的 User/libxr_config.yaml:

generator: 6.0.1
terminal_source: uart0
SPI:
spi1:
tx_buffer_size: 32
rx_buffer_size: 32
dma_enable_min_size: 3
I2C:
i2c0:
buffer_size: 32
dma_enable_min_size: 8
i2c1:
buffer_size: 32
dma_enable_min_size: 8
Terminal:
read_buff_size: 32
max_line_size: 32
max_arg_number: 5
max_history_number: 5
UART:
uart0:
tx_buffer_size: 128
rx_buffer_size: 128
tx_queue_size: 5
uart1:
tx_buffer_size: 128
rx_buffer_size: 128
tx_queue_size: 5
PWM:
pwm_tima1_c0:
frequency:
pwm_tima1_c1:
frequency:
代码块已折叠

I2C 的总线速率和 UART 的波特率来自 SysConfig 工程(如 I2C_0_BUS_SPEED_HZ),libxr_config.yaml 中没有对应的设置。


重新生成代码​

修改 .syscfg 或 User/libxr_config.yaml 后,在工程根目录重新运行 libxr mspm0 setup -d .(SysConfig 输出的来源同上),或分两步运行:

libxr parse -d .
libxr gen -i .config.yaml -o User/app_main.cpp

.config.yaml 由 libxr parse 写出,新克隆的工程里没有这个文件,先运行 libxr parse。不写 --xrobot 和 --no-xrobot 时,libxr gen 沿用输出文件现在的选择;生成后运行 xrobot gen 更新 User/xrobot_main.hpp。VS Code 扩展的 Generate LibXR Code 动作依次运行这两条命令,见 VS Code 扩展。


可选参数​

libxr mspm0 setup 的参数如下,libxr mspm0 setup --help 同样列出这些参数。

参数说明
-d, --directory工程目录:根目录下有一个 .syscfg 文件(默认:当前目录)
-o, --output输出的 C++ 文件(默认:DIRECTORY 下的 User/app_main.cpp)
--xrobot / --no-xrobot生成或不生成 XR_REGISTER 注册与 XROBOT_MAIN();,见 与XRobot集成;都不写时沿用输出文件现在的选择,新文件不使用 XRobot
--libxr-configlibxr_config.yaml 的路径或 URL(默认:输出文件旁边的 libxr_config.yaml)
--verbose输出调试日志

相关命令​

命令功能说明
libxr parse解析 .syscfg,写出 .config.yaml
libxr gen由 .config.yaml 生成入口源文件和 app_main.h,并更新 libxr_config.yaml
libxr pins打印型号的封装与引脚布局,见引脚布局

各命令的全部参数及默认值由 libxr <命令> --help 列出。

参考​