diff --git a/.gitignore b/.gitignore index 9c48e50..e3ef72a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,11 @@ *.hpp *.c *.h +# 测试固件(MSPM0 工程文件和 SysConfig 输出、HPM 工程文件)随仓库提交。 +# The test fixtures (the MSPM0 project files and SysConfig output, the HPM project files) +# are committed with the repo. +!tests/data/mspm0/* +!tests/data/hpm/* __pycache__/ *.pyc *.pyo diff --git a/README.md b/README.md index 4cb8180..236748b 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,16 @@ LibXR 代码生成工具 / Code generator for LibXR [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FJiu-xiao%2FLibXR_CppCodeGenerator.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FJiu-xiao%2FLibXR_CppCodeGenerator?ref=badge_shield) LibXR_CppCodeGenerator 由各平台开发工具的工程生成使用 [LibXR](https://github.com/xrobot-org/libxr) -的 C++ 代码,以 pip 包 `libxr` 发布,命令为 `libxr`。目前支持 STM32:它读取 STM32CubeMX 工程的 -`.ioc` 文件,生成外设对象和入口函数 `app_main`,并把 LibXR 接入工程的 CMake 构建。 +的 C++ 代码,以 pip 包 `libxr` 发布,命令为 `libxr`。目前支持三个平台:STM32 读取 STM32CubeMX 工程的 +`.ioc` 文件,MSPM0 读取 SysConfig 工程的 `.syscfg`,HPM 读取 HPM Pinmux Tool 工程的 `.hpmpc`;三者都 +生成外设对象和入口函数 `app_main`,STM32 的命令另外把 LibXR 接入工程的 CMake 构建。 LibXR_CppCodeGenerator generates C++ code that uses [LibXR](https://github.com/xrobot-org/libxr) from the projects of each platform's development tool. It is published as the pip package -`libxr`, with the command `libxr`. STM32 is supported so far: it reads the `.ioc` file of an -STM32CubeMX project, generates the peripheral objects and the entry function `app_main`, and -adds LibXR to the project's CMake build. +`libxr`, with the command `libxr`. Three platforms are supported: STM32 reads the `.ioc` file of +an STM32CubeMX project, MSPM0 reads the `.syscfg` of a SysConfig project, and HPM reads the +`.hpmpc` of an HPM Pinmux Tool project; all three generate the peripheral objects and the entry +function `app_main`, and the STM32 commands also add LibXR to the project's CMake build. --- @@ -95,15 +97,15 @@ pip install . 以上三种方式只选其一,不要混用。系统中有多份安装时,命令行实际调用的版本可能与预期不同,而不同版本 生成的代码并不一致。当前使用的版本可通过 `libxr --version` 查看。BSP 所用的版本由 -`User/libxr_config.yaml` 顶层的 `generator: 6.0.0` 固定:生成器新建这个文件时写入当前的版本,重新 -生成时保留这个键;安装的版本应与之一致,例如 `pipx install libxr==6.0.0`。 +`User/libxr_config.yaml` 顶层的 `generator: 6.0.1` 固定:生成器新建这个文件时写入当前的版本,重新 +生成时保留这个键;安装的版本应与之一致,例如 `pipx install libxr==6.0.1`。 Use only one of these methods. With several installations present, the command line may run a different version than expected, and different versions generate different code. `libxr --version` shows the version in use. The version a BSP uses is pinned by -`generator: 6.0.0` at the top level of `User/libxr_config.yaml`: the generator writes the +`generator: 6.0.1` at the top level of `User/libxr_config.yaml`: the generator writes the current version when it creates this file, and regeneration keeps the key; install the same -version, e.g. `pipx install libxr==6.0.0`. +version, e.g. `pipx install libxr==6.0.1`. --- @@ -335,7 +337,7 @@ with the new value; the changes and comments in the file are kept on regeneratio ```yaml # User/libxr_config.yaml(节选 / excerpt) -generator: 6.0.0 +generator: 6.0.1 terminal_source: usart1 software_timer: priority: 2 @@ -408,6 +410,22 @@ USB: - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3} ``` +每一项可以加上 `interface`,作为这路 CDC 控制接口和数据接口的名字。几路 CDC 的 VID:PID 相同,主机按接口名 +区分它们;没有 `interface` 的一路使用 LibXR 的默认名 `XRUSB CDC Control` 和 `XRUSB CDC Data`: + +Each item can add `interface`, the name of the control and data interfaces of that CDC. The CDCs +share VID:PID, and the host tells them apart by the interface name; a CDC without `interface` +keeps the LibXR default names `XRUSB CDC Control` and `XRUSB CDC Data`: + +```yaml +USB: + usb_otg_hs: + enable: true + cdc: + - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3, interface: Console} + - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3, interface: Telemetry} +``` + `database.enable` 为 true 时,`app_main()` 生成 `STM32Flash flash(FLASH_REGIONS, FLASH_REGION_NUMBER)` 和 `DatabaseRaw database(flash)`,数据库以 `database` 注册;`FLASH_REGIONS` 来自 `User/flash_map.hpp`,数据库使用 Flash 末尾的两个扇区。模板参数 `N` 是 Flash 的最小写入单元,单位为 @@ -511,12 +529,16 @@ $ libxr stm32 cmake [INFO] LibXR.CMake already included in CMakeLists.txt. ``` -BSP 根目录还没有 `Modules/modules.yaml` 时,`libxr stm32 setup --xrobot` 在最后依次列出 XRobot 的设置 -命令:`xrobot init`、`xrobot module add`、`xrobot setup` 和 `xrobot instance add`。 +使用 XRobot 的工程,`libxr stm32 setup`、`libxr mspm0 setup` 和 `libxr hpm setup` 在最后给出 XRobot 的 +下一步:BSP 根目录还没有 `Modules/modules.yaml` 时,依次列出 XRobot 的设置命令 `xrobot init`、 +`xrobot module add`、`xrobot setup` 和 `xrobot instance add`;已经有了时,提醒运行 `xrobot gen`, +因为 `User/xrobot_main.hpp` 由 XRobot 生成,libxr 的 setup 不更新它。 -While the BSP root has no `Modules/modules.yaml` yet, `libxr stm32 setup --xrobot` ends by listing -the XRobot setup commands in order: `xrobot init`, `xrobot module add`, `xrobot setup` and -`xrobot instance add`. +For a project that uses XRobot, `libxr stm32 setup`, `libxr mspm0 setup` and `libxr hpm setup` end +with the next XRobot step: while the BSP root has no `Modules/modules.yaml` yet, they list the +XRobot setup commands in order, `xrobot init`, `xrobot module add`, `xrobot setup` and +`xrobot instance add`; once it exists, they remind to run `xrobot gen`, since XRobot generates +`User/xrobot_main.hpp` and the libxr setup does not update it. 详见 [与 XRobot 集成](https://xrobot.work/docs/code_gen/code-gen-xrobot-inter)。 @@ -644,6 +666,182 @@ sectors: --- +## 🧩 从 .syscfg 到代码 / From .syscfg to Code + +`libxr mspm0 setup` 面向 SysConfig 工程:根目录有一份 `.syscfg` 文件,`main.c` 调用 SysConfig 生成的 +`SYSCFG_DL_init()` 后进入 `app_main()`。命令写 `.gitignore`(已有的不动),解析工程到 `.config.yaml`, +生成 `User/app_main.cpp`、`app_main.h` 和 `libxr_config.yaml`。设置了环境变量 `SYSCONFIG_TOOL` 和 +`MSPM0_SDK_INSTALL_DIR` 时,解析运行一次 SysConfig;没有设置时复用构建目录中比 `.syscfg` 新的 SysConfig +输出。下例设置了这两个变量: + +`libxr mspm0 setup` works on a SysConfig project: one `.syscfg` file at the root, and a `main.c` +that calls the SysConfig-generated `SYSCFG_DL_init()` before entering `app_main()`. The command +writes `.gitignore` (an existing one stays), parses the project into `.config.yaml`, and generates +`User/app_main.cpp`, `app_main.h` and `libxr_config.yaml`. With the environment variables `SYSCONFIG_TOOL` +and `MSPM0_SDK_INSTALL_DIR` set, parsing runs SysConfig once; without them it reuses a SysConfig output +newer than the `.syscfg` in the build directories. The example has both variables set: + +```bash +$ libxr mspm0 setup +[INFO] Processing mspm0g3507_minidb48.syscfg... +[INFO] Running SysConfig on mspm0g3507_minidb48.syscfg +[INFO] Configuration exported to: .\.config.yaml +[INFO] System: bare metal +[INFO] .\User\libxr_config.yaml does not exist; creating it with the default settings +[INFO] terminal_source is not set; using uart0 +[INFO] Generated User: wrote app_main.cpp, app_main.h, libxr_config.yaml +[INFO] Next: run `xrobot gen` to bring User/xrobot_main.hpp up to date; libxr setup does not update it + +===== [Configuration Summary] ===== + +MCU: MSPM0 MSPM0G3507 + +GPIO (4 pins): + LED1: PB8, OUTPUT + LED2: PA16, OUTPUT + KEY1: PB24, INPUT, FALL interrupt + KEY2: PB20, INPUT, FALL interrupt + +Active Peripherals: + UART: 2 instance(s) + UART_0 + UART_1 + I2C: 2 instance(s) + I2C_0 + I2C_1 + SPI: 1 instance(s) + SPI_1 + PWM: 1 instance(s) + PWM_TIMA1 + Other: 5 instance(s) + ADC12_0 + ADC12_1 + DAC0 + MCAN_0 + QEI_TIMG8 +``` + +缓冲区大小等设置在 `libxr_config.yaml` 中。GPIO 以引脚标签命名(`LED1`),UART、I2C、SPI 以小写的外设名 +命名(`uart0`、`i2c0`),PWM 为 `pwm_tima1_c0`;`terminal_source` 指定终端使用的串口,未设置时用第一个 UART。 +生成总是包含 `static MSPM0PowerManager power_manager;`,使用 XRobot 时把它登记为 `PowerManager`。 +流程、设置与约束的完整说明见 [MSPM0 代码生成](https://xrobot.work/docs/code_gen/mspm0)。 + +The settings such as the buffer sizes live in `libxr_config.yaml`. GPIO objects are named after +the pin labels (`LED1`), UART, I2C and SPI after their lowercase peripheral names (`uart0`, `i2c0`), +and a PWM is `pwm_tima1_c0`; `terminal_source` names the UART the terminal uses, the first one when it is not +set. The generation always emits `static MSPM0PowerManager power_manager;`, registered as a +`PowerManager` with XRobot. The flow, the settings and their constraints are described in +[MSPM0 code generation](https://xrobot.work/en/docs/code_gen/mspm0). + +--- + +## 🧩 从 .hpmpc 到代码 / From .hpmpc to Code + +`libxr hpm setup` 面向 HPM SDK 工程:根目录有 `app.yaml`,`boards//` 下有唯一的 `.hpmpc` 文件。 +步骤与 MSPM0 相同,输出也是同样的三个文件;引脚从 `.hpmpc` 中 `selectPins` 的信号和 `main.c` 在预处理 +条件之外调用的引脚函数读出,`main` 完全没有调用引脚函数时用 `boards//pinmux.c` 的 `init_bsp_pins()`。 +生成的对象按平台命名:GPIO 为 `pa10` 这样的引脚名,其余为小写的实例(`i2c3`);GPTMR 输出为 +`pwm_gptmr0_ch1`,在 SoC 没有 PWM 外设时生成(HPM5301 生成,HPM5361 归入 Other): + +`libxr hpm setup` works on an HPM SDK project: `app.yaml` at the root and the single `.hpmpc` file +of `boards//`. The steps are the ones of MSPM0, and the output is the same three files; the +pins are read from the signals `selectPins` selects in the `.hpmpc` and from the pinmux functions +`main.c` calls outside preprocessor conditions, falling back to the `init_bsp_pins()` of +`boards//pinmux.c` when `main` calls no pinmux function at all. The generated objects are named after +the platform: GPIO takes the pin name (`pa10`) and the others the lowercase instance (`i2c3`), and +a GPTMR output becomes `pwm_gptmr0_ch1`, generated when the SoC has no PWM peripheral (HPM5301 +generates it, on HPM5361 it is listed under Other): + +```bash +$ libxr hpm setup +[INFO] Configuration exported to: .\.config.yaml +[INFO] System: bare metal +[INFO] .\User\libxr_config.yaml does not exist; creating it with the default settings +[INFO] Generated User: wrote app_main.cpp, app_main.h, libxr_config.yaml +[INFO] Next: run `xrobot gen` to bring User/xrobot_main.hpp up to date; libxr setup does not update it + +===== [Configuration Summary] ===== + +SoC: HPM5301 (QFN48) + +GPIO (2 pins): + pa10: PA10 + pa3: PA03 + +Active Peripherals: + I2C: 1 instance(s) + I2C3 + PWM: 1 instance(s) + GPTMR0 + Other: 7 instance(s) + UART0 + UART3 + SPI1 + ADC0 + ACMP + USBPHY + USB0 +``` + +支持 HPM5301(QFN48)和 HPM5361(LQFP100、LQFP64、QFN48)。UART、SPI 等外设目前只识别,生成时列入 +Other。生成总是包含 `static HPMPowerManager power_manager;`,使用 XRobot 时把它登记为 +`PowerManager`。流程、命名与设置的完整说明见 [HPM 代码生成](https://xrobot.work/docs/code_gen/hpm)。 + +HPM5301 (QFN48) and HPM5361 (LQFP100, LQFP64, QFN48) are supported. Peripherals such as UART and +SPI are recognized for now and listed under Other in the generation. The generation always emits +`static HPMPowerManager power_manager;`, registered as a `PowerManager` with XRobot. The flow, the +naming and the settings are described in [HPM code generation](https://xrobot.work/en/docs/code_gen/hpm). + +--- + +## 📌 引脚布局 / Pin Layout + +`libxr pins` 按型号给出芯片的封装和引脚布局:每个引脚的封装位置、名称、类型和全部可选信号。型号的前缀决定平台,目前支持 STM32、MSPM0 和 HPM;HPM 的型号就是 SoC 名(`HPM5301`),有多个封装的 SoC(HPM5361)用 `--package` 选择(`LQFP100`、`LQFP64`、`QFN48`)。输出为 YAML,`--format json` 输出 JSON。 + +`libxr pins` prints the package and pin layout of a chip model: for each pin its position on the package, name, type and all selectable signals. The prefix of the model chooses the platform; STM32, MSPM0 and HPM are supported. The model of an HPM is the SoC name (`HPM5301`), and a SoC with several packages (HPM5361) selects one with `--package` (`LQFP100`, `LQFP64`, `QFN48`). The output is YAML, or JSON with `--format json`. + +```bash +$ libxr pins MSPM0G3507SPMR +model: MSPM0G3507SPMR +platform: mspm0 +part: MSPM0G3507 +package: LQFP-64(PM) +pin_count: 64 +# ... +- position: '33' + name: PA0 + type: Default + signals: + - PA0 + - UART0.TX + - I2C0.SDA + # ... + iomux_pincm: 1 + modes: + PA0: 1 + UART0.TX: 2 + I2C0.SDA: 3 + # ... +``` + +- `libxr pins -d <工程目录>` 叠加工程已选的信号:STM32 读 CubeMX 的 `.ioc`,MSPM0 读根目录的 `.syscfg`(型号取自工程,封装取自 SysConfig 的 `--package` 或 `--board` 对应的 LaunchPad),HPM 读 `boards/` 下的 `.hpmpc` 和 `main.c` 在预处理条件之外调用的引脚函数(封装由工程解析得到,也可用 `--package` 指定)。输出多一个 `project` 段:`assignments` 是每个引脚选了什么(信号、外设、功能、GPIO 标签和是否在器件数据中匹配),`peripherals` 是每个外设用到的引脚和它在 `libxr_config.yaml` 中的设置条目(`config` 是列表,每项有段、键、文件中是否已存在和当前参数);文件默认为 `User/libxr_config.yaml`,可用 `-c` 指定。HPM 的 GPIO 是整段改名:`config` 一项的键为 `null`,`params` 是段内引脚名到新名的映射。 +- STM32 的封装由型号决定(`STM32H723VGT6` 为 LQFP100);MSPM0 的封装取自型号后缀中的代码(`MSPM0G3507SPMR` 的 `PM`),型号中没有时用 `--package` 给出(`LQFP-64`、`PM` 或 `LQFP-64(PM)`);HPM 的型号是 SoC 名,只有一种封装的 SoC(HPM5301)不必给出封装,`-d` 时封装取自 `.hpmpc`。 +- `pin_count` 是封装上的位置数。一个位置可以有多个条目:STM32G0 的 PA9 和 PA11 可以互换,共用一个位置。 +- `peripherals` 列出从信号名识别出的全部外设和每个功能可选的引脚(`USART1` 的 `TX` 可以接 `PA9`、`PB6`、`PB14`)。识别只看信号名,ETH、FMC、OctoSPI 这些 LibXR 没有对应抽象的外设也会列出。普通 GPIO 按端口列出(`GPIOA` 的 `P9` 是 `PA9`);STM32 的外部中断列为 `EXTI`,`LINE9` 可以来自任一端口的第 9 脚,STM32 的引脚另有 `gpio_modes`(Input、Output、Analog、EXTI 等);有输出通道的定时器带 `capabilities: [pwm]`。TI 的器件数据没有中断信息,所以 MSPM0 不列 `EXTI`。 +- MSPM0 的引脚还有 `iomux_pincm` 和每个信号的模式号 `modes`。 +- MSPM0 外设的设置取自 `.syscfg`(SysConfig 的配置脚本):每个已选外设的 `sysconfig` 有它的模块、名字(`UART_0`)和参数(`targetBaudRate`、`enabledInterrupts` 等),`project.sysconfig_file` 是读取的文件。只读,修改在 SysConfig 里进行;表达式原样保留为字符串,引脚的指定和求解器内部的项不收。 +- 数据来自厂商:STM32 来自 ST 的 [STM32_open_pin_data](https://github.com/STMicroelectronics/STM32_open_pin_data)(BSD-3-Clause),MSPM0 来自 TI SysConfig 的器件数据(TI 有限许可,只可用于 TI 器件),HPM 来自 HPM SDK 的 `hpm_iomux.h`、`hpm_soc_ip.h`(BSD-3-Clause)和 HPM5300 数据手册的引脚表。数据文件和各份许可证文本在 `src/libxr/pin_data/`,随包分发;用 `scripts/build_pin_data.py` 重新生成。 MSPM0 的 `.syscfg` 里可能只写了 `--board`(LaunchPad),封装由 MSPM0 SDK 的板子定义得到(`mspm0_boards.json`,BSD-3-Clause,许可证文本为 `LICENSE-TI-BOARDS.txt`)。 + +- `libxr pins -d ` overlays the signals a project has selected: the CubeMX `.ioc` for an STM32, the `.syscfg` at the root for an MSPM0 (the model comes from the project, and the package from SysConfig's `--package` or the LaunchPad its `--board` names), and the `.hpmpc` under `boards/` plus the pinmux functions `main.c` calls outside preprocessor conditions for an HPM (the package comes from the project, or from `--package`). The output gets a `project` section: `assignments` is what each pin selected (signal, peripheral, function, GPIO label and whether it matched the device data), and `peripherals` the pins each peripheral uses and its entries in `libxr_config.yaml` (`config` is a list whose items carry the section, the key, whether the file already has it and the current parameters); the file is `User/libxr_config.yaml` by default or the one given with `-c`. The GPIO rename of an HPM is one whole section: the `config` item has the key `null`, and `params` maps the pin names of the section to their new names. +- The package of an STM32 is part of the model (`STM32H723VGT6` is LQFP100); that of an MSPM0 comes from the code in the model suffix (`PM` of `MSPM0G3507SPMR`), or from `--package` when the model has none (`LQFP-64`, `PM` or `LQFP-64(PM)`); the model of an HPM is the SoC name, a SoC with one package (HPM5301) needs no package, and with `-d` the package comes from the `.hpmpc`. +- `pin_count` is the number of positions on the package. A position can have several entries: PA9 and PA11 of an STM32G0 can be swapped and share one position. +- `peripherals` lists every peripheral recognized from the signal names, with the pins that can carry each function (`TX` of `USART1` can use `PA9`, `PB6` or `PB14`). Recognition looks at the signal names only, so peripherals LibXR has no abstraction for, such as ETH, FMC and OctoSPI, are listed too. Plain GPIO is listed by port (`P9` of `GPIOA` is `PA9`); the external interrupts of an STM32 are listed as `EXTI`, where `LINE9` can come from pin 9 of any port, and an STM32 pin also has `gpio_modes` (Input, Output, Analog, EXTI, ...); a timer with output channels carries `capabilities: [pwm]`. TI's device data has no interrupt information, so an MSPM0 lists no `EXTI`. +- An MSPM0 pin also has `iomux_pincm` and `modes`, the mode of each signal. +- The settings of an MSPM0 peripheral come from the `.syscfg`, the configuration script of SysConfig: each selected peripheral has a `sysconfig` with its module, its name (`UART_0`) and its parameters (`targetBaudRate`, `enabledInterrupts`, ...), and `project.sysconfig_file` is the file read. They are read-only, edited in SysConfig; an expression is kept as written, as a string, and pin assignments and the solver's own items are left out. +- The data comes from the vendors: STM32 from ST's [STM32_open_pin_data](https://github.com/STMicroelectronics/STM32_open_pin_data) (BSD-3-Clause), MSPM0 from the device data of TI SysConfig (TI limited license, for TI devices only), and HPM from the `hpm_iomux.h` and `hpm_soc_ip.h` of the HPM SDK (BSD-3-Clause) and the pin tables of the HPM5300 datasheet. The data files and the license texts are in `src/libxr/pin_data/` and are distributed with the package; `scripts/build_pin_data.py` rebuilds them. A `.syscfg` of an MSPM0 may name only a `--board` (a LaunchPad); the package then comes from the board definitions of the MSPM0 SDK (`mspm0_boards.json`, BSD-3-Clause, license text in `LICENSE-TI-BOARDS.txt`). + +--- + ## 🚀 命令一览 / Commands `parse` 和 `gen` 按工程所属的平台选择解析器和生成器,只属于某个平台的命令放在平台名之下。 @@ -655,7 +853,10 @@ belong to one platform sit under its name. | --- | --- | --- | | `libxr parse` | 解析工程,写出工程 YAML | Parse a project into the project YAML | | `libxr gen` | 由工程 YAML 生成入口源文件 | Generate the entry source from the project YAML | +| `libxr pins` | 打印某个型号的封装和引脚布局 | Print the package and pin layout of a model | | `libxr stm32 setup` | 为 CubeMX 工程加入 LibXR,生成代码并接入 CMake | Add LibXR to a CubeMX project, generate the code and integrate CMake | +| `libxr mspm0 setup` | 为 SysConfig 工程生成 LibXR 代码 | Generate LibXR code for a SysConfig project | +| `libxr hpm setup` | 为 HPM 工程生成 LibXR 代码 | Generate LibXR code for an HPM project | | `libxr stm32 cubemx-gen` | 以脚本模式运行 STM32CubeMX,由 `.ioc` 重新生成 CubeMX 工程 | Run STM32CubeMX in script mode to regenerate the CubeMX project from the `.ioc` file | | `libxr stm32 cmake` | 把 LibXR 接入 CubeMX 的 CMake 工程 | Integrate LibXR into the CubeMX CMake project | | `libxr stm32 flash-info` | 打印某个型号的 Flash 布局 | Print the flash layout of a model | diff --git a/pyproject.toml b/pyproject.toml index 87d2d38..4bad990 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "libxr" -version = "6.0.0" +version = "6.0.1" description = "C++ code generator for LibXR-based embedded systems, supporting STM32 and modular robotics applications." requires-python = ">=3.10" authors = [ @@ -49,7 +49,7 @@ xr_stm32_toolchain_switch = "libxr.legacy:xr_stm32_toolchain_switch" where = ["src"] [tool.setuptools.package-data] -"libxr" = ["libxr_version.py", "STM32FlashLayoutRules.xml"] +"libxr" = ["libxr_version.py", "STM32FlashLayoutRules.xml", "pin_data/*"] [tool.ruff] line-length = 100 diff --git a/scripts/build_pin_data.py b/scripts/build_pin_data.py new file mode 100644 index 0000000..3987b39 --- /dev/null +++ b/scripts/build_pin_data.py @@ -0,0 +1,549 @@ +"""由厂商的引脚数据生成 libxr pins 使用的 src/libxr/pin_data/ 数据文件。 +Build the data files in src/libxr/pin_data/ that libxr pins reads, from the vendors' pin data. + +数据来源有两个: +There are two sources: + +- STM32:ST 公开的 STM32_open_pin_data 仓库(BSD-3-Clause)的 mcu/*.xml。 + STM32: mcu/*.xml of ST's public STM32_open_pin_data repository (BSD-3-Clause). +- MSPM0:TI SysConfig 安装目录下 dist/deviceData/MSPM0*/ 中的器件 JSON(TI 有限许可,只可用于 + TI 器件);以及 MSPM0 SDK 中 source/ti/boards/.meta/ 的板子定义(每块 LaunchPad 的器件和封装, + BSD-3-Clause),用于由 .syscfg 中的 --board 得到封装。 + MSPM0: the device JSON in dist/deviceData/MSPM0*/ of a TI SysConfig installation (TI limited + license, for TI devices only); and the board definitions in source/ti/boards/.meta/ of the MSPM0 + SDK (the device and package of each LaunchPad, BSD-3-Clause), which give the package of a + .syscfg that names a --board. + +用法 / Usage: + python scripts/build_pin_data.py --st-data --sysconfig \\ + --mspm0-sdk +""" + +import argparse +import gzip +import hashlib +import json +import re +import subprocess +import xml.etree.ElementTree as ET +from pathlib import Path + +REPOSITORY = Path(__file__).resolve().parents[1] +ST_NAMESPACE = "{http://dummy.com}" + + +def expand_group(stem: str) -> list[str]: + """展开文件名中的分组:STM32F407I(E-G)Hx 对应 STM32F407IEHx 和 STM32F407IGHx。 + Expand the group in a file name: STM32F407I(E-G)Hx stands for STM32F407IEHx and + STM32F407IGHx. + """ + match = re.fullmatch(r"(.*?)\(([^)]+)\)(.*)", stem) + if match is None: + return [stem] + return [ + match.group(1) + alternative + match.group(3) for alternative in match.group(2).split("-") + ] + + +def read_st_part(path: Path) -> tuple[str, list[list]]: + """读取一个 STM32 型号的 XML,返回封装名和引脚:[位置, 名称, 类型, [信号...], [GPIO 模式...]]。 + GPIO 模式取自 GPIO 信号的 IOModes(Input、Output、Analog、EVENTOUT、EXTI……),引脚没有 + GPIO 功能时为空。 + Read the XML of one STM32 part; return the package name and the pins as + [position, name, type, [signals...], [GPIO modes...]]. The GPIO modes come from the IOModes + of the GPIO signal (Input, Output, Analog, EVENTOUT, EXTI, ...) and are empty for a pin + without a GPIO function. + """ + root = ET.parse(path).getroot() + pins = [] + for pin in root.findall(f"{ST_NAMESPACE}Pin"): + signals = pin.findall(f"{ST_NAMESPACE}Signal") + modes = next( + (s.get("IOModes") for s in signals if s.get("Name") == "GPIO" and s.get("IOModes")), "" + ) + pins.append( + [ + pin.get("Position"), + pin.get("Name"), + pin.get("Type"), + [signal.get("Name") for signal in signals], + modes.split(",") if modes else [], + ] + ) + return root.get("Package"), pins + + +def git_commit(directory: Path) -> str: + """directory 这个 Git 仓库的当前提交。 + The current commit of the Git repository in directory. + """ + return subprocess.run( + ["git", "-C", str(directory), "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + + +def stm32_shard(name: str) -> str: + """一个 STM32 型号所属的数据分片(系列):STM32H723VGTx 为 H7,STM32WBA52CGUx 为 WBA5。 + The data shard (series) of an STM32 part: H7 for STM32H723VGTx, WBA5 for STM32WBA52CGUx. + """ + match = re.match(r"STM32([A-Z]+\d)", name) + return match.group(1) if match else "OTHER" + + +def build_stm32(st_data: Path) -> tuple[dict, dict[str, dict]]: + """STM32 数据:索引(来源,以及型号到分片和引脚集的映射),和按系列分片、按内容去重的引脚集。 + 读一个型号只需要索引和它所在的分片。 + The STM32 data: the index (the source, and a map from part to shard and pin set) and the pin + sets, split into shards by series and deduplicated by content. Reading one part needs only + the index and its shard. + """ + parts: dict[str, list[str]] = {} + shards: dict[str, dict] = {} + holder: dict[str, str] = {} + for path in sorted((st_data / "mcu").glob("*.xml")): + package, pins = read_st_part(path) + content = json.dumps([package, pins], separators=(",", ":")) + key = hashlib.sha1(content.encode()).hexdigest()[:10] + for name in expand_group(path.stem): + # 内容相同的引脚集只存一份,放在第一个用到它的系列的分片里。 + # A pin set of the same content is stored once, in the shard of the first series that + # uses it. + shard = holder.setdefault(key, stm32_shard(name)) + shards.setdefault(shard, {})[key] = {"package": package, "pins": pins} + parts[name] = [shard, key] + index = { + "source": { + "vendor": "STMicroelectronics", + "dataset": "STM32_open_pin_data", + "url": "https://github.com/STMicroelectronics/STM32_open_pin_data", + "commit": git_commit(st_data), + "license": "BSD-3-Clause", + }, + "parts": parts, + } + return index, shards + + +def family_prefixes(family: str) -> list[str]: + """一个 SysConfig 器件族目录名覆盖的型号前缀:MSPM0G350X 为 MSPM0G350, + MSPM0C1105_C1106 为 MSPM0C1105 和 MSPM0C1106。 + The model prefixes a SysConfig family directory covers: MSPM0G350 for MSPM0G350X, + MSPM0C1105 and MSPM0C1106 for MSPM0C1105_C1106. + """ + if family.endswith("X"): + return [family[:-1]] + first, *others = family.split("_") + return [first] + [first[: len(first) - len(other)] + other for other in others] + + +def read_mspm0_family(path: Path) -> tuple[dict, dict[str, int]]: + """读取一个 MSPM0 器件族的 JSON,返回其每个封装的引脚: + [位置, 名称, 类型, PINCM, [[信号, 模式]...]];以及每个外设实例的电源域编号(0 为 PD0, + 总线时钟是 ULPCLK;1 为 PD1,是 MCLK),UART0 -> 1。 + Read the JSON of one MSPM0 family; return the pins of each of its packages as + [position, name, type, PINCM, [[signal, mode]...]], and the power domain number of each + peripheral instance (0 is PD0, whose bus clock is ULPCLK; 1 is PD1, on MCLK), UART0 -> 1. + """ + data = json.loads(path.read_text(encoding="utf-8")) + muxes = {mux["devicePinID"]: mux["muxSetting"] for mux in data["muxes"]} + signals = data["peripheralPins"] + packages = {} + for package in data["packages"].values(): + balls = {pin["devicePinID"]: pin["ball"] for pin in package["packagePin"]} + pins = [] + for pin_id, pin in data["devicePins"].items(): + if pin_id not in balls: + continue + pincm = pin["attributes"].get("iomux_pincm", "") + pins.append( + [ + balls[pin_id], + pin["name"], + pin["devicePinType"], + int(pincm) if pincm.isdigit() else None, + [ + [ + signals[setting["peripheralPinID"]]["name"], + int(setting["mode"]) if setting["mode"].isdigit() else setting["mode"], + ] + for setting in muxes.get(pin_id, []) + ], + ] + ) + pins.sort(key=lambda pin: (len(pin[0]), pin[0])) + packages[package["name"]] = pins + power_domains = { + peripheral["name"]: attributes["POWER_DOMAIN_NUMBER"] + for peripheral in data["peripherals"].values() + if isinstance(attributes := peripheral.get("attributes", {}), dict) + and isinstance(attributes.get("POWER_DOMAIN_NUMBER"), int) + } + return packages, power_domains + + +def read_mspm0_extras(path: Path) -> dict: + """器件族 JSON 里 LibXR 的 UART 驱动要用的外设属性:UART Extend 实例(SYS_UARTADV, + MSPM0_UART_EXTEND_INIT 的接收 DMA 需要它)和 FULL-DMA 通道数(DMA 的 + SYS_N_DMA_FULL_CHANNEL,接收 DMA 必须用编号小于它的通道)。 + The peripheral attributes of a family JSON that the LibXR UART driver needs: the UART + Extend instances (SYS_UARTADV, which the receive DMA of MSPM0_UART_EXTEND_INIT requires) + and the number of FULL-DMA channels (SYS_N_DMA_FULL_CHANNEL of the DMA; the receive DMA must + use a channel below it). + """ + data = json.loads(path.read_text(encoding="utf-8")) + extend, full_channels = [], None + for peripheral in data["peripherals"].values(): + attributes = peripheral.get("attributes", {}) + if not isinstance(attributes, dict): + continue + if attributes.get("SYS_UARTADV") == "true": + extend.append(peripheral["name"]) + if ( + peripheral["name"] == "DMA" + and str(attributes.get("SYS_N_DMA_FULL_CHANNEL", "")).isdigit() + ): + full_channels = int(attributes["SYS_N_DMA_FULL_CHANNEL"]) + extras: dict = {"uart_extend": sorted(extend)} + if full_channels is not None: + extras["dma_full_channels"] = full_channels + return extras + + +def build_mspm0(sysconfig: Path) -> dict: + """MSPM0 数据:每个器件族的型号前缀、各封装的引脚和外设实例的电源域。 + The MSPM0 data: the model prefixes of each family, the pins of its packages and the power + domain of each peripheral instance. + """ + root = sysconfig / "dist" / "deviceData" + families = {} + for directory in sorted(root.glob("MSPM0*")): + packages, power_domains = read_mspm0_family(directory / f"{directory.name}.json") + families[directory.name] = { + "prefixes": family_prefixes(directory.name), + "packages": packages, + "power_domains": power_domains, + **read_mspm0_extras(directory / f"{directory.name}.json"), + } + version = (sysconfig / "dist" / "version.txt").read_text(encoding="utf-8").strip() + return { + "source": { + "vendor": "Texas Instruments", + "dataset": "SysConfig dist/deviceData", + "version": version, + "license": "TI limited license (TI devices only)", + }, + "families": families, + } + + +HPM_IOMUX = re.compile( + r"^#define\s+IOC_(P[A-Z]\d+)_FUNC_CTL_(\w+)\s+IOC_PAD_FUNC_CTL_ALT_SELECT_SET\((\d+)\)", re.M +) +HPM_PAD = re.compile(r"P[A-Z]\d+") + + +def read_hpm_signals(soc_dir: Path) -> dict[str, list[list]]: + """一个 HPM SoC 的 hpm_iomux.h:焊盘 -> [[信号名, ALT 编号]...],信号名与头文件的宏一致 + (UART0_TXD、GPTMR1_COMP_0、GPIO_A_00)。 + The hpm_iomux.h of one HPM SoC: pad -> [[signal name, ALT number]...], the names as the + header's macros spell them (UART0_TXD, GPTMR1_COMP_0, GPIO_A_00). + """ + signals: dict[str, list[list]] = {} + for pad, name, alt in HPM_IOMUX.findall((soc_dir / "hpm_iomux.h").read_text(encoding="utf-8")): + signals.setdefault(pad, []).append([name, int(alt)]) + return signals + + +def read_hpm_balls(datasheet: Path) -> tuple[dict[str, dict[str, str]], dict[str, list[str]]]: + """数据手册 PINMUX 表里的焊盘封装映射:焊盘 -> 封装 -> 球号;以及每个焊盘的模拟功能 + (ADC0_IN2 等,数字的 hpm_iomux.h 里没有它们;单元格里折行的名字拼回去)。 + The pad-package map from the PINMUX table of a datasheet: pad -> package -> ball; and the + analog functions of each pad (ADC0_IN2, ...; the digital hpm_iomux.h does not have them, + names wrapped inside a cell are joined back). + """ + import pdfplumber + + balls: dict[str, dict[str, str]] = {} + analog: dict[str, list[str]] = {} + with pdfplumber.open(datasheet) as pdf: + for page in pdf.pages: + for table in page.extract_tables(): + if ( + len(table) < 3 + or len(table[1]) < 5 + or not table[1][4] + or table[1][4] != "PINNAME" + ): + continue + packages = [re.sub(r"[^A-Z0-9]", "", str(name).upper()) for name in table[1][:4]] + for row in table[2:]: + pad = str(row[4]).strip() if row[4] else "" + if not HPM_PAD.fullmatch(pad): + continue + for index, package in enumerate(packages): + ball = str(row[index]).strip() if row[index] else "" + if not ball or ball == "-": + continue + balls.setdefault(pad, {})[package] = ball + names: list[str] = [] + for part in str(row[6] or "").split("\n"): + if part in ("", "-"): + continue + # 名字在单元格里折行:下半段以下划线开头,拼回上一个名字。 + # A name wraps inside the cell: the second half starts with an + # underscore and continues the previous name. + if part.startswith("_") and names: + names[-1] += part + else: + names.append(part) + if names: + known = analog.setdefault(pad, []) + known.extend(name for name in names if name not in known) + return balls, analog + + +# 数据手册订购型号里的封装代码(产品命名规则图):HPM5361ICB1 的 CB 是 LQFP100。 +# The package codes of the datasheet's ordering part numbers (the product naming convention +# figure): the CB of HPM5361ICB1 is LQFP100. +HPM_PACKAGE_CODES = {"CB": "LQFP100", "CF": "LQFP64", "EG": "QFN48"} +HPM_PART_NUMBER = re.compile(r"\b(HPM\d{2}[0-9A-Z]{2})I(CB|CF|EG)\d\b") +HPM_SOC_IP = re.compile(r"^#define\s+HPM_(\w+?)_BASE\b", re.M) +# 信号的外设名与 hpm_soc_ip.h 的实例名不同的:OPA0_INN0 的外设是 OPAMP0。 +# Peripherals whose signal name differs from the instance of hpm_soc_ip.h: the peripheral of +# OPA0_INN0 is OPAMP0. +HPM_IP_ALIASES = {"OPA": "OPAMP"} + + +def read_hpm_packages(datasheet: Path) -> dict[str, set[str]]: + """数据手册列出的订购型号:SoC -> 有货的封装(HPM5301 只有 HPM5301IEG1,即 QFN48)。引脚 + 表里有 LQFP80 一列,但没有哪个型号用这个封装。 + The ordering part numbers the datasheet lists: SoC -> the packages it ships in (HPM5301 only + as HPM5301IEG1, QFN48). The pin table has an LQFP80 column, but no part uses that package. + """ + import pdfplumber + + packages: dict[str, set[str]] = {} + with pdfplumber.open(datasheet) as pdf: + for page in pdf.pages: + for soc, code in HPM_PART_NUMBER.findall(page.extract_text() or ""): + packages.setdefault(soc, set()).add(HPM_PACKAGE_CODES[code]) + return packages + + +def hpm_ip_instances(soc_dir: Path) -> set[str]: + """SoC 的 hpm_soc_ip.h 声明的外设实例(UART0、ADC0、ACMP……)。 + The peripheral instances the hpm_soc_ip.h of a SoC declares (UART0, ADC0, ACMP, ...). + """ + return set(HPM_SOC_IP.findall((soc_dir / "hpm_soc_ip.h").read_text(encoding="utf-8"))) + + +def hpm_signal_instance(name: str) -> str: + """信号所属的外设实例,用 hpm_soc_ip.h 的写法(OPA0_INN0 为 OPAMP0,ADC1_IN3 为 ADC1)。 + The peripheral instance of a signal, spelled as in hpm_soc_ip.h (OPAMP0 for OPA0_INN0, + ADC1 for ADC1_IN3). + """ + instance = name.split("_", 1)[0] + kind = re.sub(r"\d+$", "", instance) + return HPM_IP_ALIASES.get(kind, kind) + instance[len(kind) :] + + +def build_hpm(sdk: Path, datasheet: Path, series: str) -> dict: + """HPM 数据:一个系列(HPM5300)里每个 SoC 的各封装引脚;信号取自 SDK 的 hpm_iomux.h + (BSD-3-Clause),球号取自该系列的数据手册 PINMUX 表(hpmicro.com)。 + The HPM data: the pins of each package of every SoC of one series (HPM5300); the signals + come from the SDK's hpm_iomux.h (BSD-3-Clause), the balls from that series' datasheet PINMUX + table (hpmicro.com). + + 数据手册的模拟功能是全系列共用的一张表,按各 SoC 的 hpm_soc_ip.h 只留它有的外设:HPM5301 + 没有 ADC1、DAC 和运放。封装取自数据手册的订购型号;手册没有列出的 SoC 不进数据。 + The analog functions of the datasheet are one table for the whole series; each SoC keeps + only the peripherals its hpm_soc_ip.h declares: a HPM5301 has no ADC1, DAC or op-amp. The + packages come from the ordering part numbers of the datasheet; a SoC the datasheet does not + list stays out of the data. + """ + balls, analog = read_hpm_balls(datasheet) + ordered = read_hpm_packages(datasheet) + soc_dirs = [ + soc_dir + for soc_dir in sorted((sdk / "soc" / series).glob("*")) + if (soc_dir / "hpm_iomux.h").is_file() + ] + # 系列里任一 SoC 声明过的外设:只有这些按实例过滤,JTAG、XPI0 等不在 hpm_soc_ip.h 里的保留。 + # The peripherals any SoC of the series declares: only these are filtered by instance; + # JTAG, XPI0 and others that hpm_soc_ip.h does not list stay. + series_ip = set().union(*(hpm_ip_instances(soc_dir) for soc_dir in soc_dirs)) + socs = {} + for soc_dir in soc_dirs: + if soc_dir.name not in ordered: + print( + f"{soc_dir.name}: not among the ordering part numbers of {datasheet.name}; skipped" + ) + continue + ip = hpm_ip_instances(soc_dir) + signals = read_hpm_signals(soc_dir) + # 模拟功能(ADC、ACMP、OPA 的输入输出)补进信号表;数字头文件里没有它们。 + # The analog functions (the inputs and outputs of ADC, ACMP and OPA) join the signal + # lists; the digital header does not have them. + for pad, names in analog.items(): + known = {name for name, _ in signals.get(pad, [])} + signals.setdefault(pad, []).extend([name, None] for name in names if name not in known) + for pad, entries in signals.items(): + signals[pad] = [ + entry + for entry in entries + if hpm_signal_instance(entry[0]) in ip + or hpm_signal_instance(entry[0]) not in series_ip + ] + packages = {} + for package in balls[next(iter(balls))]: + if package not in ordered[soc_dir.name]: + continue + pins = [ + [ + ball, + pad, + "GPIO", + None, + signals.get(pad, []), + ] + for pad, package_balls in sorted(balls.items()) + for ball in [package_balls.get(package)] + if ball and pad in signals + ] + pins.sort(key=lambda pin: (len(pin[0]), pin[0])) + packages[package] = pins + socs[soc_dir.name] = {"packages": packages} + return { + "source": { + "vendor": "HPMicro", + "dataset": f"hpm_sdk soc/{series}/*/hpm_iomux.h; {datasheet.stem} pin tables", + "commit": git_commit(sdk), + "license": "BSD-3-Clause", + }, + "socs": socs, + } + + +def read_boards(sdk: Path) -> dict: + """MSPM0 SDK 的板子定义:板名(文件名)到器件和封装。 + The board definitions of the MSPM0 SDK: the board name (the file name) to its device and + package. + + 定义文件是带注释的 JSON,这里只取 gpn(器件)和 pkg(封装)两个字段。 + The definition files are JSON with comments; only gpn (the device) and pkg (the package) are + read. + """ + boards = {} + for path in sorted((sdk / "source" / "ti" / "boards" / ".meta").glob("*.syscfg.json")): + text = path.read_text(encoding="utf-8") + device = re.search(r'"gpn":\s*"([^"]+)"', text) + package = re.search(r'"pkg":\s*"([^"]+)"', text) + if device and package: + boards[path.name.removesuffix(".syscfg.json")] = { + "device": device.group(1), + "package": package.group(1), + } + return boards + + +def board_license(sdk: Path) -> str: + """板子定义文件开头的版权和 BSD-3-Clause 许可证注释。 + The copyright and BSD-3-Clause license comment at the top of a board definition file. + """ + path = next((sdk / "source" / "ti" / "boards" / ".meta").glob("*.syscfg.json")) + text = path.read_text(encoding="utf-8") + comment = text[: text.index("*/")] + lines = [re.sub(r"^\s*/?\*+\$?\s?", "", line) for line in comment.splitlines()] + return "\n".join(lines).strip() + "\n" + + +def write_gzip_json(path: Path, data: dict) -> None: + """把 data 写成压缩的 JSON;不写时间戳,同样的数据得到同样的文件。 + Write data as compressed JSON; no timestamp is written, so the same + data gives the same file. + """ + # 键保持写入顺序(按型号名排序):相近的型号相邻,压缩得更小。 + # The keys keep the order written (sorted by part name): similar parts sit together, which + # compresses better. + text = json.dumps(data, separators=(",", ":"), ensure_ascii=False) + with open(path, "wb") as file, gzip.GzipFile(fileobj=file, mode="wb", mtime=0) as zipped: + zipped.write(text.encode("utf-8")) + + +def main() -> None: + """解析参数,生成数据文件和许可证文本。 + Parse the arguments and write the data files and the license texts. + """ + parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + parser.add_argument("--st-data", type=Path, help="STM32_open_pin_data checkout") + parser.add_argument("--sysconfig", type=Path, help="TI SysConfig installation") + parser.add_argument("--mspm0-sdk", type=Path, help="TI MSPM0 SDK checkout") + parser.add_argument("--hpm-sdk", type=Path, help="HPM SDK checkout") + parser.add_argument("--hpm-datasheet", type=Path, help="HPM series datasheet PDF") + parser.add_argument("--hpm-series", help="the HPM series the datasheet covers (HPM5300)") + parser.add_argument( + "--output", type=Path, default=REPOSITORY / "src" / "libxr" / "pin_data", help="output dir" + ) + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=True) + if (args.sysconfig is None) != (args.mspm0_sdk is None): + parser.error("--sysconfig and --mspm0-sdk go together") + if (args.hpm_sdk is None) != (args.hpm_datasheet is None): + parser.error("--hpm-sdk and --hpm-datasheet go together") + if args.hpm_sdk is not None and not args.hpm_series: + parser.error("--hpm-series names the series the datasheet covers (HPM5300)") + if args.st_data is None and args.sysconfig is None and args.hpm_sdk is None: + parser.error( + "give --st-data, --sysconfig with --mspm0-sdk, or --hpm-datasheet with --hpm-sdk" + ) + if args.st_data is not None: + # 先清掉旧的分片,不留下已经不存在的系列。 + # Remove the old shards first, so no series that no longer exists stays. + for old in args.output.glob("stm32*.json.gz"): + old.unlink() + index, shards = build_stm32(args.st_data) + write_gzip_json(args.output / "stm32.json.gz", index) + for shard, sets in shards.items(): + write_gzip_json(args.output / f"stm32-{shard}.json.gz", sets) + (args.output / "LICENSE-ST.txt").write_bytes((args.st_data / "LICENSE").read_bytes()) + if args.sysconfig is not None: + for old in ("mspm0.json.gz", "mspm0_boards.json", "LICENSE-TI-BOARDS.txt"): + (args.output / old).unlink(missing_ok=True) + write_gzip_json(args.output / "mspm0.json.gz", build_mspm0(args.sysconfig)) + boards = { + "source": { + "vendor": "Texas Instruments", + "dataset": "MSPM0 SDK source/ti/boards/.meta", + "commit": git_commit(args.mspm0_sdk), + "license": "BSD-3-Clause", + }, + "boards": read_boards(args.mspm0_sdk), + } + (args.output / "mspm0_boards.json").write_text( + json.dumps(boards, indent=1, sort_keys=True) + "\n", encoding="utf-8", newline="\n" + ) + (args.output / "LICENSE-TI-BOARDS.txt").write_text( + board_license(args.mspm0_sdk), encoding="utf-8", newline="\n" + ) + # 许可证原文随数据分发。TI 的文件不是 UTF-8,转成 UTF-8 写出。 + # The license texts are distributed with the data. TI's file is not UTF-8; it is written + # as UTF-8. + ti_bytes = (args.sysconfig / "dist" / "license.txt").read_bytes() + ti_text = ti_bytes.decode("cp1252").replace("\r\n", "\n") + (args.output / "LICENSE-TI.txt").write_text(ti_text, encoding="utf-8", newline="\n") + if args.hpm_sdk is not None: + (args.output / "hpm.json.gz").unlink(missing_ok=True) + write_gzip_json( + args.output / "hpm.json.gz", + build_hpm(args.hpm_sdk, args.hpm_datasheet, args.hpm_series), + ) + # SDK 的许可证原文随数据分发。 + # The SDK's license text is distributed with the data. + (args.output / "LICENSE-HPM.txt").write_bytes((args.hpm_sdk / "LICENSE").read_bytes()) + + +if __name__ == "__main__": + main() diff --git a/src/libxr/cli.py b/src/libxr/cli.py index 53bd827..7b3680c 100644 --- a/src/libxr/cli.py +++ b/src/libxr/cli.py @@ -52,6 +52,40 @@ def _has_ioc(directory: str) -> bool: return False +def _has_syscfg(directory: str) -> bool: + """directory 的根目录中有 .syscfg 文件(SysConfig 工程)时为 True;目录无法列出时为 False。 + True when the root of directory holds a .syscfg file (a SysConfig project); False when the + directory cannot be listed. + """ + try: + return any(name.endswith(".syscfg") for name in os.listdir(directory)) + except OSError: + return False + + +def _has_hpmpc(directory: str) -> bool: + """directory 是 HPM 工程(根目录有 app.yaml 且 boards/ 的某个板子目录里有 .hpmpc)时为 + True;目录无法列出时为 False。 + True when directory holds an HPM project (an app.yaml in the root and a .hpmpc in a board + directory under boards/); False when the directory cannot be listed. + """ + if not os.path.isfile(os.path.join(directory, "app.yaml")): + return False + try: + boards = os.listdir(os.path.join(directory, "boards")) + except OSError: + return False + for name in boards: + board = os.path.join(directory, "boards", name) + if os.path.isdir(board): + try: + if any(file.endswith(".hpmpc") for file in os.listdir(board)): + return True + except OSError: + continue + return False + + def _stm32_parse(args: argparse.Namespace) -> None: """用 STM32 解析器运行 libxr parse。 Run libxr parse with the STM32 parser. @@ -86,6 +120,78 @@ def _stm32_gen(args: argparse.Namespace) -> None: generate(args.input, args.output, use_xrobot, args.libxr_config) +def _mspm0_parse(args: argparse.Namespace) -> None: + """用 MSPM0 解析器运行 libxr parse。 + Run libxr parse with the MSPM0 parser. + """ + from libxr.peripheral_analyzer_mspm0 import parse_project + + parse_project(args.directory, args.output) + + +def _mspm0_gen(args: argparse.Namespace) -> None: + """用 MSPM0 生成器运行 libxr gen。 + Run libxr gen with the MSPM0 generator. + + args.xrobot 为 None 时沿用输出文件现在的选择:它由 --xrobot 生成时继续生成 XRobot 代码。 + With args.xrobot None the output file keeps its choice: XRobot code is generated again when + it was generated with --xrobot. + """ + from libxr.generator_code_mspm0 import generate + from libxr.generator_stm32_cmake import uses_xrobot + + use_xrobot = args.xrobot + if use_xrobot is None: + use_xrobot = uses_xrobot(args.output) + if use_xrobot: + logging.info( + tr( + f"{args.output} uses XRobot; generating with --xrobot " + "(--no-xrobot turns it off).", + f"{args.output} 使用了 XRobot,继续按 --xrobot 生成(--no-xrobot 可关闭)。", + ) + ) + generate(args.input, args.output, use_xrobot, args.libxr_config) + + +def _hpm_parse(args: argparse.Namespace) -> None: + """用 HPM 解析器运行 libxr parse。 + Run libxr parse with the HPM parser. + """ + from libxr.peripheral_analyzer_hpm import parse_project + + parse_project(args.directory, args.output) + + +def _hpm_gen(args: argparse.Namespace) -> None: + """用 HPM 生成器运行 libxr gen。 + Run libxr gen with the HPM generator. + + args.xrobot 为 None 时沿用输出文件现在的选择:它由 --xrobot 生成时继续生成 XRobot 代码。 + With args.xrobot None the output file keeps its choice: XRobot code is generated again when + it was generated with --xrobot. + """ + from libxr.generator_code_hpm import generate + from libxr.generator_stm32_cmake import uses_xrobot + + use_xrobot = args.xrobot + if use_xrobot is None: + use_xrobot = uses_xrobot(args.output) + if use_xrobot: + logging.info( + tr( + f"{args.output} uses XRobot; generating with --xrobot " + "(--no-xrobot turns it off).", + f"{args.output} 使用了 XRobot,继续按 --xrobot 生成(--no-xrobot 可关闭)。", + ) + ) + generate(args.input, args.output, use_xrobot, args.libxr_config) + + +# 识别顺序与 libxr pins 和 VS Code 扩展一致:一个 HPM 工程(app.yaml 加 boards/ 下的 .hpmpc) +# 即使根目录还有 .syscfg 也是 HPM。 +# The order of recognition matches libxr pins and the VS Code extension: an HPM project (an +# app.yaml with a .hpmpc under boards/) is HPM even when its root also holds a .syscfg. PLATFORMS = ( Platform( "stm32", @@ -94,6 +200,26 @@ def _stm32_gen(args: argparse.Namespace) -> None: _stm32_parse, _stm32_gen, ), + Platform( + "hpm", + lambda: tr( + "a directory with an app.yaml and a .hpmpc under boards/", + "根目录有 app.yaml 且 boards/ 下有 .hpmpc 文件的目录", + ), + _has_hpmpc, + _hpm_parse, + _hpm_gen, + ), + Platform( + "mspm0", + lambda: tr( + "a directory with a SysConfig .syscfg file in its root", + "根目录中含有 SysConfig .syscfg 文件的目录", + ), + _has_syscfg, + _mspm0_parse, + _mspm0_gen, + ), ) @@ -206,6 +332,63 @@ def cmd_stm32_setup(args: argparse.Namespace) -> None: ) +def _setup_without_cubemx(args: argparse.Namespace, parse, generate) -> None: + """没有 CubeMX 步骤的平台(HPM、MSPM0)的 setup,规则与 libxr stm32 setup 相同:写 + .gitignore(已有的不动),解析工程到 .config.yaml,生成 User/app_main.cpp(或 -o 给出的 + 文件)。--xrobot 和 --no-xrobot 都不给时沿用输出文件现在的选择,新文件不用 XRobot。结束时, + XRobot 工程还没有 Modules/modules.yaml 就给出 XRobot 的设置步骤;与 stm32 setup 一样不改 + User/xrobot_main.hpp,它由 xrobot setup / xrobot gen 生成。 + The setup of a platform without a CubeMX step (HPM, MSPM0), with the rules of libxr stm32 + setup: write .gitignore (an existing one stays), parse the project into .config.yaml and + generate User/app_main.cpp (or the file -o names). With neither --xrobot nor --no-xrobot + the output file keeps its choice, and a new file does not use XRobot. At the end an XRobot + project without Modules/modules.yaml gets the XRobot setup steps; like stm32 setup it + leaves User/xrobot_main.hpp alone, which xrobot setup / xrobot gen generates. + """ + from libxr.config_cubemx_project import create_gitignore_file, report_xrobot_steps + from libxr.generator_stm32_cmake import uses_xrobot + + output = args.output or os.path.join(args.directory, "User", "app_main.cpp") + use_xrobot = args.xrobot + if use_xrobot is None: + use_xrobot = uses_xrobot(output) + if use_xrobot: + logging.info( + tr( + f"{output} uses XRobot; generating with --xrobot (--no-xrobot turns it off).", + f"{output} 使用了 XRobot,继续按 --xrobot 生成(--no-xrobot 可关闭)。", + ) + ) + create_gitignore_file(args.directory) + parse(args.directory, None) + generate(os.path.join(args.directory, ".config.yaml"), output, use_xrobot, args.libxr_config) + report_xrobot_steps(args.directory, use_xrobot) + + +def cmd_hpm_setup(args: argparse.Namespace) -> None: + """libxr hpm setup:解析 HPM 工程并生成 User/app_main.cpp;没有 CubeMX 步骤(见 + _setup_without_cubemx())。 + libxr hpm setup: parse an HPM project and generate User/app_main.cpp; there is no CubeMX + step (see _setup_without_cubemx()). + """ + from libxr.generator_code_hpm import generate + from libxr.peripheral_analyzer_hpm import parse_project + + _setup_without_cubemx(args, parse_project, generate) + + +def cmd_mspm0_setup(args: argparse.Namespace) -> None: + """libxr mspm0 setup:解析 SysConfig 工程并生成 User/app_main.cpp;没有 CubeMX 步骤(见 + _setup_without_cubemx())。 + libxr mspm0 setup: parse a SysConfig project and generate User/app_main.cpp; there is no + CubeMX step (see _setup_without_cubemx()). + """ + from libxr.generator_code_mspm0 import generate + from libxr.peripheral_analyzer_mspm0 import parse_project + + _setup_without_cubemx(args, parse_project, generate) + + def cmd_stm32_cubemx_gen(args: argparse.Namespace) -> None: """libxr stm32 cubemx-gen:以脚本模式运行 STM32CubeMX 生成工程;出错时记录错误(调试日志另记 调用栈)并以状态 1 退出。 @@ -255,6 +438,15 @@ def cmd_stm32_flash_info(args: argparse.Namespace) -> None: print_flash_info(args.model) +def cmd_pins(args: argparse.Namespace) -> None: + """libxr pins:打印一个型号的封装和引脚布局。 + libxr pins: print the package and pin layout of a model. + """ + from libxr.pin_layout import print_pin_layout + + print_pin_layout(args.model, args.package, args.format, args.directory, args.libxr_config) + + def cmd_stm32_toolchain(args: argparse.Namespace) -> None: """libxr stm32 toolchain:切换默认 preset 的工具链和 clang 的标准库。 libxr stm32 toolchain: switch the toolchain of the default preset and the clang standard @@ -315,9 +507,11 @@ def _add_parse(commands) -> None: "--directory", default=".", help=tr( - "project directory; an STM32CubeMX project holds one .ioc file (default: current " - "directory)", - "工程目录,STM32CubeMX 工程含有一个 .ioc 文件(默认:当前目录)", + "project directory: an STM32CubeMX project holds one .ioc file, an MSPM0 project " + "one .syscfg file in its root and an HPM project an app.yaml plus a .hpmpc under " + "boards/ (default: current directory)", + "工程目录:STM32CubeMX 工程含有一个 .ioc 文件,MSPM0 工程的根目录含有一个 .syscfg " + "文件,HPM 工程有 app.yaml 且 boards/ 下有一个 .hpmpc(默认:当前目录)", ), ) parser.add_argument( @@ -441,6 +635,107 @@ def _add_stm32_setup(commands) -> None: ) +def _add_hpm_setup(commands) -> None: + """加入 hpm setup 子命令。 + Add the hpm setup subcommand. + """ + parser = _command( + commands, + "setup", + tr( + "set up an HPM project for LibXR: parse the Pinmux Tool project and generate " + "User/app_main.cpp (there is no CubeMX step)", + "把 HPM 工程配置为使用 LibXR:解析 Pinmux Tool 工程并生成 User/app_main.cpp" + "(没有 CubeMX 步骤)", + ), + cmd_hpm_setup, + ) + parser.add_argument( + "-d", + "--directory", + default=".", + help=tr( + "project directory: an app.yaml in the root and one .hpmpc under boards/ " + "(default: current directory)", + "工程目录:根目录有 app.yaml,boards/ 下有一个 .hpmpc(默认:当前目录)", + ), + ) + parser.add_argument( + "-o", + "--output", + default="", + help=tr( + "output C++ file (default: User/app_main.cpp in DIRECTORY)", + "输出的 C++ 文件(默认:DIRECTORY 中的 User/app_main.cpp)", + ), + ) + _add_xrobot_choice( + parser, + tr( + " (default: keep the choice of the existing output file; a new file uses no XRobot)", + "(默认:沿用已有输出文件的选择;新文件不使用 XRobot)", + ), + ) + parser.add_argument( + "--libxr-config", + default="", + help=tr( + "path or URL of libxr_config.yaml (default: libxr_config.yaml next to the output)", + "libxr_config.yaml 的路径或 URL(默认:输出文件所在目录中的 libxr_config.yaml)", + ), + ) + + +def _add_mspm0_setup(commands) -> None: + """加入 mspm0 setup 子命令。 + Add the mspm0 setup subcommand. + """ + parser = _command( + commands, + "setup", + tr( + "set up an MSPM0 project for LibXR: parse the SysConfig project and generate " + "User/app_main.cpp (there is no CubeMX step)", + "把 MSPM0 工程配置为使用 LibXR:解析 SysConfig 工程并生成 User/app_main.cpp" + "(没有 CubeMX 步骤)", + ), + cmd_mspm0_setup, + ) + parser.add_argument( + "-d", + "--directory", + default=".", + help=tr( + "project directory: a .syscfg file in its root (default: current directory)", + "工程目录:根目录有一个 .syscfg 文件(默认:当前目录)", + ), + ) + parser.add_argument( + "-o", + "--output", + default="", + help=tr( + "output C++ file (default: User/app_main.cpp in DIRECTORY)", + "输出的 C++ 文件(默认:DIRECTORY 中的 User/app_main.cpp)", + ), + ) + _add_xrobot_choice( + parser, + tr( + " (default: keep the choice of the existing output file; a new file uses no XRobot)", + "(默认:沿用已有输出文件的选择;新文件不使用 XRobot)", + ), + ) + parser.add_argument( + "--libxr-config", + default="", + help=tr( + "path or URL of libxr_config.yaml (default: libxr_config.yaml next to the output)", + "libxr_config.yaml 的路径或 URL(默认:输出文件所在目录中的 libxr_config.yaml)", + ), + ) + + def _add_stm32_cubemx_gen(commands) -> None: """加入 stm32 cubemx-gen 子命令。 Add the stm32 cubemx-gen subcommand. @@ -603,6 +898,72 @@ def _add_project_directory(parser) -> None: ) +def _add_pins(commands) -> None: + """加入 pins 子命令。 + Add the pins subcommand. + """ + parser = _command( + commands, + "pins", + tr( + "print the package and pin layout of a chip model (STM32, MSPM0, HPM)", + "打印芯片型号的封装和引脚布局(STM32、MSPM0、HPM)", + ), + cmd_pins, + epilog=tr("examples:", "示例:") + + "\n libxr pins STM32H723VGT6\n libxr pins MSPM0G3507SPMR" + + "\n libxr pins MSPM0G3507 --package LQFP-64 --format json" + + "\n libxr pins HPM5301" + + "\n libxr pins -d path/to/project", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "model", + nargs="?", + help=tr( + "chip model (default: the one of the project given with -d)", + "芯片型号(默认:-d 给出的工程的型号)", + ), + ) + parser.add_argument( + "-d", + "--directory", + help=tr( + "project directory: overlay the signals it has selected (an STM32CubeMX .ioc, the " + "root SysConfig .syscfg of an MSPM0, or the .hpmpc under boards/ of an HPM) and " + "their libxr_config.yaml settings", + "工程目录:叠加其中已选的信号(STM32CubeMX 的 .ioc、MSPM0 根目录的 SysConfig " + ".syscfg,或 HPM 在 boards/ 下的 .hpmpc)和它们在 libxr_config.yaml 中的设置", + ), + ) + parser.add_argument( + "-c", + "--libxr-config", + help=tr( + "libxr_config.yaml to read the settings from (default: User/libxr_config.yaml in " + "the project)", + "读取设置的 libxr_config.yaml(默认:工程中的 User/libxr_config.yaml)", + ), + ) + parser.add_argument( + "-p", + "--package", + help=tr( + "package, for a model that does not name it (MSPM0: LQFP-64, PM, ...; HPM: QFN48, " + "...); with -d it comes from the SysConfig or Pinmux Tool project", + "封装,用于型号中没有封装的情况(MSPM0:LQFP-64、PM 等;HPM:QFN48 等);用 -d 时" + "取自 SysConfig 或 Pinmux Tool 工程", + ), + ) + parser.add_argument( + "-f", + "--format", + choices=("yaml", "json"), + default="yaml", + help=tr("output format (default: yaml)", "输出格式(默认:yaml)"), + ) + + def _add_stm32_flash_info(commands) -> None: """加入 stm32 flash-info 子命令。 Add the stm32 flash-info subcommand. @@ -704,6 +1065,7 @@ def build_parser() -> argparse.ArgumentParser: commands = parser.add_subparsers(metavar="", required=True) _add_parse(commands) _add_gen(commands) + _add_pins(commands) stm32 = commands.add_parser( "stm32", help=tr("commands for STM32CubeMX projects", "STM32CubeMX 工程的命令"), @@ -715,11 +1077,25 @@ def build_parser() -> argparse.ArgumentParser: _add_stm32_cmake(stm32_commands) _add_stm32_flash_info(stm32_commands) _add_stm32_toolchain(stm32_commands) + hpm = commands.add_parser( + "hpm", + help=tr("commands for HPM Pinmux Tool projects", "HPM Pinmux Tool 工程的命令"), + description=tr("Commands for HPM Pinmux Tool projects.", "HPM Pinmux Tool 工程的命令。"), + ) + hpm_commands = hpm.add_subparsers(metavar="", required=True) + _add_hpm_setup(hpm_commands) + mspm0 = commands.add_parser( + "mspm0", + help=tr("commands for TI SysConfig projects", "TI SysConfig 工程的命令"), + description=tr("Commands for TI SysConfig projects.", "TI SysConfig 工程的命令。"), + ) + mspm0_commands = mspm0.add_subparsers(metavar="", required=True) + _add_mspm0_setup(mspm0_commands) # 每个子命令最后都有 --verbose。 # Every subcommand ends with --verbose. - for group in (commands, stm32_commands): + for group in (commands, stm32_commands, hpm_commands, mspm0_commands): for name, command in group.choices.items(): - if name != "stm32": + if name not in ("stm32", "hpm", "mspm0"): command.add_argument( "--verbose", action="store_true", diff --git a/src/libxr/config_cubemx_project.py b/src/libxr/config_cubemx_project.py index a9ba3e0..c51ab1a 100644 --- a/src/libxr/config_cubemx_project.py +++ b/src/libxr/config_cubemx_project.py @@ -835,11 +835,12 @@ def _report_next_steps(project_dir: str, xrobot: bool = False) -> None: CubeMX sources do not call it yet, the XRobot setup commands in order when an XRobot project has no Modules/modules.yaml yet, and the build commands when the project has CMake presets. - FreeRTOS 工程在定义 StartDefaultTask 的源文件中调用,其他工程在 Core/Src/main.c 的 main() - 中调用。xrobot 为真表示入口源文件按 --xrobot 生成。构建命令使用第一个同时有 configure 和 - build preset 的名字。 - A FreeRTOS project calls it in the source file defining StartDefaultTask, any other - project in main() of Core/Src/main.c. xrobot true means the entry source was generated with + ThreadX 工程在 App_ThreadX_Init() 中创建一个调用它的线程,FreeRTOS 工程在定义 + StartDefaultTask 的源文件中调用,其他工程在 Core/Src/main.c 的 main() 中调用。xrobot 为真 + 表示入口源文件按 --xrobot 生成。构建命令使用第一个同时有 configure 和 build preset 的名字。 + A ThreadX project creates a thread calling it in App_ThreadX_Init(), a FreeRTOS project + calls it in the source file defining StartDefaultTask, any other project in main() of + Core/Src/main.c. xrobot true means the entry source was generated with --xrobot. The build commands use the first name that has both a configure and a build preset. """ @@ -851,35 +852,75 @@ def _report_next_steps(project_dir: str, xrobot: bool = False) -> None: with open(os.path.join(sources, name), encoding="utf-8", errors="replace") as f: texts[name] = f.read() if not any(re.search(r"\bapp_main\s*\(\s*\)\s*;", text) for text in texts.values()): + threadx = next((name for name, text in texts.items() if "App_ThreadX_Init(" in text), None) task = next((name for name, text in texts.items() if "StartDefaultTask(" in text), None) - if task: - where_en = f"in the default task StartDefaultTask (Core/Src/{task})" - where_zh = f"在默认任务 StartDefaultTask(Core/Src/{task})中" - else: - where_en, where_zh = "in main() of Core/Src/main.c", "在 Core/Src/main.c 的 main() 中" - logging.info( - tr( - f'Next: #include "app_main.h" and call app_main() {where_en}, inside USER CODE ' - "sections, which CubeMX keeps when it regenerates the code.", - f'下一步:{where_zh} #include "app_main.h" 并调用 app_main(),写在 USER CODE 区域中,' - "CubeMX 重新生成代码时保留。", + keep_en = "inside USER CODE sections, which CubeMX keeps when it regenerates the code." + keep_zh = "写在 USER CODE 区域中,CubeMX 重新生成代码时保留。" + if threadx: + # App_ThreadX_Init() 在调度器启动前执行,app_main() 要在线程中运行。 + # App_ThreadX_Init() runs before the scheduler starts; app_main() runs in a thread. + english = ( + f"Next: in App_ThreadX_Init() (Core/Src/{threadx}), create a thread whose entry " + f'function includes "app_main.h" and calls app_main(), {keep_en} ' + "App_ThreadX_Init() runs before the scheduler starts, so it cannot call " + "app_main() directly." ) - ) - if xrobot and not os.path.isfile(os.path.join(project_dir, "Modules", "modules.yaml")): - logging.info( - tr( - "Next: Modules/modules.yaml does not exist yet; set up XRobot in this order:", - "下一步:还没有 Modules/modules.yaml,按以下顺序完成 XRobot 的设置:", + chinese = ( + f"下一步:在 App_ThreadX_Init()(Core/Src/{threadx})中创建一个线程,线程入口函数 " + f'#include "app_main.h" 并调用 app_main(),{keep_zh}App_ThreadX_Init() 在调度器' + "启动前执行,不能在其中直接调用 app_main()。" ) - ) - for command, english, chinese in XROBOT_STEPS: - logging.info(f" {command:<36} " + tr(english, chinese)) + else: + if task: + where_en = f"in the default task StartDefaultTask (Core/Src/{task})" + where_zh = f"在默认任务 StartDefaultTask(Core/Src/{task})中" + else: + where_en, where_zh = ( + "in main() of Core/Src/main.c", + "在 Core/Src/main.c 的 main() 中", + ) + english = f'Next: #include "app_main.h" and call app_main() {where_en}, {keep_en}' + chinese = f'下一步:{where_zh} #include "app_main.h" 并调用 app_main(),{keep_zh}' + logging.info(tr(english, chinese)) + report_xrobot_steps(project_dir, xrobot) preset = _build_preset(os.path.join(project_dir, "CMakePresets.json")) if preset: command = f"cmake --preset {preset} && cmake --build --preset {preset}" logging.info(tr(f"Build: {command}", f"构建:{command}")) +def report_xrobot_steps(project_dir: str, xrobot: bool) -> None: + """XRobot 工程(xrobot 为真)的后续步骤,各平台的 setup 共用:还没有 Modules/modules.yaml + 时依次给出 XRobot 的设置命令(XROBOT_STEPS,其中 xrobot setup 生成 User/xrobot_main.hpp); + 已经有了时提醒运行 xrobot gen,因为 libxr 的 setup 不更新 User/xrobot_main.hpp(用户 + 2026-10-07 决定统一提醒)。 + The next steps of an XRobot project (xrobot true), shared by the setup of every platform: + without Modules/modules.yaml the XRobot setup commands in order (XROBOT_STEPS, where xrobot + setup generates User/xrobot_main.hpp); with it a reminder to run xrobot gen, because the + libxr setup does not update User/xrobot_main.hpp (the user decided on 2026-10-07 to remind + on every platform). + """ + if not xrobot: + return + if os.path.isfile(os.path.join(project_dir, "Modules", "modules.yaml")): + logging.info( + tr( + "Next: run `xrobot gen` to bring User/xrobot_main.hpp up to date; libxr setup " + "does not update it", + "下一步:运行 `xrobot gen` 更新 User/xrobot_main.hpp;libxr 的 setup 不更新它", + ) + ) + return + logging.info( + tr( + "Next: Modules/modules.yaml does not exist yet; set up XRobot in this order:", + "下一步:还没有 Modules/modules.yaml,按以下顺序完成 XRobot 的设置:", + ) + ) + for command, english, chinese in XROBOT_STEPS: + logging.info(f" {command:<36} " + tr(english, chinese)) + + def _build_preset(path: str) -> str: """CMakePresets.json 中第一个同时有 configure 和 build preset 的名字;没有或文件不可读时为空字符串。 The first name in CMakePresets.json with both a configure and a build preset; an empty diff --git a/src/libxr/generator_code_hpm.py b/src/libxr/generator_code_hpm.py new file mode 100644 index 0000000..4d9b566 --- /dev/null +++ b/src/libxr/generator_code_hpm.py @@ -0,0 +1,748 @@ +"""libxr gen 对 HPM 工程:由工程 YAML 生成 User/app_main.cpp 和 libxr_config.yaml。 +libxr gen for an HPM project: generate User/app_main.cpp and libxr_config.yaml from the +project YAML. + +时钟和引脚都由 Pinmux Tool 配置(根目录 main.c 调用 init_bsp_pins 等函数),main.c 是 BSP 的 +财产,生成代码只写 User/ 下的 app_main。对象全部使用 SDK 的宏(HPM_GPIO0、GPIO_DI_GPIOx、 +IRQn_GPIO0_x、HPM_I2Cn、HPM_GPTMRn、clock_i2cn、clock_gptmrn),布局沿 bsp_stm32f103。设置来自 +libxr_config.yaml(首次生成把默认值写进该文件,已有值保持不变):I2C 的速率,PWM 的频率,GPIO +的改名表。 +Clocks and pins are configured by the Pinmux Tool (the root main.c calls init_bsp_pins and +friends), main.c stays BSP-owned, and the generated code is only the app_main under User/. +Every object uses the SDK macros (HPM_GPIO0, GPIO_DI_GPIOx, IRQn_GPIO0_x, HPM_I2Cn, HPM_GPTMRn, +clock_i2cn, clock_gptmrn), and the layout follows bsp_stm32f103. The settings come from +libxr_config.yaml (the first generation writes the defaults into the file and keeps the values +that are already there): the I2C speed, the PWM frequency and the renames of the GPIO pins. + +对象名:GPIO 默认按引脚命名(pb11,端口字母小写加行号,用户 2026-10-07 确认),libxr_config 的 +GPIO 段把它映射到新名字;I2C 用外设名的小写(i2c3),PWM 是 pwm_gptmr0_ch1。HPM 的 .hpmpc 不带 +速率和频率,I2C 默认 100 kHz,PWM 只在 libxr_config 给出频率时调用 SetConfig。GPIO 中断遵循 +STM32 的约定(模型 O1):生成器只创建对象(HPM 上带端口 IRQ),边沿来自 pinmux;RegisterCallback +和 EnableInterrupt 属于消费者(Module 或 User Code)。UART、SPI 和 MCAN 只被识别,不生成对象 +(适配层还没合入 libxr)。GPTMR 上的 PWM 走 HPMPWM 的 fallback 路径,只在 SoC 没有 PWM 外设时 +生成——解析按芯片引脚数据判定(HPM5361 有 PWM 外设,它的 GPTMR 归入 Other 只作展示)——对象 +和登记都无条件生成,不能包 #if:xrobot 的生成器无法判断编译选项,guard 里的 XR_REGISTER 会被 +它拒绝。 +Object names: a GPIO defaults to its pin name (pb11, the lower-case port letter plus the line, +confirmed by the user 2026-10-07) and the GPIO section of libxr_config maps it to a new name; an +I2C takes the lower-case peripheral name (i2c3) and a PWM is pwm_gptmr0_ch1. The .hpmpc of an +HPM carries no speed or frequency, so an I2C starts at 100 kHz and a PWM calls SetConfig only +when libxr_config gives a frequency. GPIO interrupts follow the STM32 convention (model O1): +the generator creates the object only (with its port IRQ on HPM), the edge comes from the +pinmux, and RegisterCallback and EnableInterrupt belong to the consumer (a Module or the User +Code). UART, SPI and MCAN are recognized but generate no objects (the adapters are still held +back from libxr). A PWM on a GPTMR takes the fallback path of HPMPWM and is generated only +when the SoC has no PWM peripheral — the parse decides from the chip's pin data (a HPM5361 has +one, so its GPTMRs land in Other for display only) — and both the object and its registration +are generated unconditionally; they must not sit inside #if, because xrobot's generator cannot +evaluate build options and rejects an XR_REGISTER inside a guard. +""" + +from __future__ import annotations + +import logging +import os +import re +import sys + +from xr_syntax.i18n import tr + +from libxr import generator_code_stm32 as stm32 +from libxr import pin_layout +from libxr.cpp_layout import Braces, layout +from libxr.generator_code_stm32 import CPP_KEYWORDS, GENERATED_NOTICE, INDENT + +# generator_code_stm32 的其余接口(登记表、设置、写入助手)经模块属性访问:测试会用 +# importlib.reload 重载它,直接导入的名字会指向重载前的旧全局对象。 +# The rest of the generator_code_stm32 interface (the registry, the settings, the writing +# helpers) is reached through module attributes: tests reload it with importlib.reload, and +# directly imported names would keep pointing at the pre-reload global objects. +# HPM 各设置段的默认值;STM32 段(USART/TIM/USB……)以及终端设置(HPM 生成器不创建 UART, +# 没有终端)不适用于 HPM,生成前从默认集合中剔除;文件里写了的照常读入。 +# The defaults of the HPM settings sections; the STM32 sections (USART/TIM/USB, ...) and the +# terminal settings (the HPM generator creates no UART, so there is no terminal) do not apply +# to HPM and are removed from the default set before generation; written in the file, they +# are read as usual. +HPM_DEFAULTS = {"I2C": {"speed": 100000}} +STM32_ONLY_SECTIONS = ( + "terminal_source", + "Terminal", + "ADC", + "TIM", + "CAN", + "FDCAN", + "USB", + "database", + "software_timer", + "USART", + "SYSTEM", +) +# HPM 的 I2C 驱动只接受这三个速率(hpm_i2c.hpp 的构造说明)。 +# The HPM I2C driver accepts only these three rates (the constructor notes of hpm_i2c.hpp). +I2C_SPEEDS = (100000, 400000, 1000000) +# GPTMR fallback 路径的占空比比较器固定为 0,重装比较器由驱动取另一个(hpm_pwm.cpp)。 +# The duty comparator of the GPTMR fallback path is fixed at 0; the driver takes the other +# one for the reload comparator (hpm_pwm.cpp). +PWM_DUTY_CMP = 0 + +IDENTIFIER = re.compile(r"[A-Za-z_]\w*") +# 生成的 app_main 里用到的名字:GPIO 对象不能叫这些。 +# The names the generated app_main uses: a GPIO object must not be called any of them. +GENERATED_NAMES = frozenset( + { + "app_main", + "main", + "timebase", + "power_manager", + "PlatformInit", + "LibXR", + "Thread", + "UINT32_MAX", + "HPMTimebase", + "HPMPowerManager", + "HPMGPIO", + "HPMI2C", + "HPMPWM", + "LibXRHpmPwmType", + "XR_REGISTER", + "XROBOT_MAIN", + } +) +# HPM SDK 的宏和枚举值的前缀(HPM_GPIO0、GPIO_DI_GPIOA、IRQn_GPIO0_A、clock_i2c3、 +# ATTR_ALIGN……)。gpio_led、board_led 这样的普通名字不在其中。 +# The prefixes of the HPM SDK macros and enumerators (HPM_GPIO0, GPIO_DI_GPIOA, IRQn_GPIO0_A, +# clock_i2c3, ATTR_ALIGN, ...). Ordinary names such as gpio_led or board_led are not. +SDK_NAME = re.compile( + r"(HPM_|IS_HPM_|IRQn_|IOC_|clock_|GPIO_(DI|DO|OE|IE|IF|PL|TP|AS)_|GPIOM_|ATTR_|SDK_DECLARE_" + r"|TRACE_|SAVE_|RESTORE_|ISR_NAME_|intc_[ms]_)\w*" +) +# hpm_common.h 和 RISC-V 头文件里的类函数宏(HPM SDK v1.13.0),前缀规则管不到的:对象叫这个 +# 名字时 static HPMGPIO MAX(...) 会被当成宏展开。 +# The function-like macros of hpm_common.h and the RISC-V headers (HPM SDK v1.13.0) that the +# prefix rule does not cover: an object of that name, static HPMGPIO MAX(...), would expand as +# the macro. +SDK_MACROS = frozenset( + { + "ARRAY_SIZE", + "MAX", + "MIN", + "NOP", + "WFI", + "MAKE_STATUS", + "NESTED_IRQ_ENTER", + "NESTED_IRQ_EXIT", + "MODE_SWITCH_FROM_M", + "COMPLETE_IRQ_HANDLING_S", + "ENTER_NESTED_IRQ_HANDLING_M", + "ENTER_NESTED_IRQ_HANDLING_S", + "EXIT_NESTED_IRQ_HANDLING_M", + "EXIT_NESTED_IRQ_HANDLING_S", + "FREERTOS_VECTOR_ISR_WRAPPER_NAME", + "fencei", + "fenceiorw", + "fencerw", + "read_csr", + "write_csr", + "set_csr", + "clear_csr", + "read_set_csr", + "read_clear_csr", + "read_fcsr", + "write_fcsr", + "clear_fcsr", + "enable_fpu", + "disable_fpu", + } +) + + +def _comment(text: str, indent: str = "") -> list[str]: + """一段注释折行成不超过 90 列的行,每行带 indent 缩进和 "// " 前缀。 + Wrap a comment into lines of at most 90 columns; every line carries the indent and the + "// " prefix. + """ + words, lines, line = text.split(), [], "" + for word in words: + if line and len(line) + 1 + len(word) > 85: + lines.append(f"{indent}// {line}") + line = word + else: + line = f"{line} {word}".strip() + if line: + lines.append(f"{indent}// {line}") + return lines + + +def _fail(message: str): + """记录错误并以状态 1 退出。 + Log an error and exit with status 1. + """ + logging.error(message) + sys.exit(1) + + +def _key(section: dict, name: str): + """section 中与 name 只差大小写的键(文件的写法);没有时为小写的 name。libxr pins 也这样找, + 两边对同一个文件读到同一段。 + The key of section that differs from name in case only (the file's spelling); the lower-case + name when there is none. libxr pins looks the same way, so both read the same entry of a + file. + """ + return next((key for key in section if str(key).lower() == name.lower()), name.lower()) + + +def _keyed_settings(group: str, name: str) -> dict: + """libxr_settings[group] 中 name 的设置段,键的大小写以文件为准。 + The settings section of name in libxr_settings[group], the case of the key as in the file. + """ + return stm32._settings(group, _key(stm32._settings(group), name)) + + +def _instance_settings(group: str, instance: str) -> dict: + """libxr_settings 中实例的设置段(键不区分大小写),缺少的和为 null 的值补成默认值(与 + MSPM0 一样)。 + The settings section of an instance in libxr_settings (the key in any case), with missing + and null values set to their defaults (as on MSPM0). + """ + section = _keyed_settings(group, instance) + for key, default in HPM_DEFAULTS[group].items(): + if section.get(key) is None: + section[key] = default + return section + + +def _object_name(name: str, kind: str) -> str: + """对象的 C++ 名字:小写、必须是合法标识符且不与其他对象重名;不满足时报错退出。 + The C++ name of an object: lower case, a valid identifier and unique among the objects; + otherwise an error is logged and generation exits. + """ + name = name.lower() + if not IDENTIFIER.fullmatch(name): + _fail( + tr( + f"{name} is not a valid C++ identifier; rename the instance in the Pinmux Tool", + f"{name} 不是合法的 C++ 标识符;请在 Pinmux Tool 中重命名该实例", + ) + ) + if name in stm32.registered_devices: + _fail( + tr( + f"Two objects are named {name} ({kind} and {stm32.registered_devices[name]}); " + "rename one of them in libxr_config.yaml", + f"两个对象都叫 {name}({kind} 与 {stm32.registered_devices[name]});请在 " + "libxr_config.yaml 中重命名其中一个", + ) + ) + return name + + +def _setting_int(key: str, value) -> int: + """正整数设置,读法与 STM32、MSPM0 相同(stm32._integer:字符串按 Python 整数字面量读, + 如 0x3E8);不是正整数时报错退出。 + A positive integer setting, read as on STM32 and MSPM0 (stm32._integer: a string is a + Python integer literal, such as 0x3E8); a value that is not a positive integer logs an + error and exits. + """ + try: + return stm32._integer(key, value, 1) + except stm32.LibXRConfigError as error: + _fail(str(error)) + + +def _peripherals(project_data: dict) -> dict[str, list]: + """工程 YAML 中的外设对象:kind -> [(对象名, 实例名, 记录, PWM 通道)],按 YAML 顺序,同时 + 登记进 XR_REGISTER 的对象表。 + The peripheral objects of the project YAML: kind -> [(object name, instance name, record, + PWM channel)], in YAML order, registered into the XR_REGISTER table as well. + """ + result: dict[str, list] = {} + for kind, records in project_data.get("Peripherals", {}).items(): + if kind == "Other": + continue + objects = [] + for instance, record in records.items(): + if kind == "PWM": + # GPTMR 的对象名 pwm_gptmr0_ch1;通道序号是 pinmux 的比较器序号。 + # The object name of a GPTMR is pwm_gptmr0_ch1; the channel index is the + # comparator index of the pinmux. + for channel in record.get("Channels", []): + index = channel["Index"] + name = _object_name(f"pwm_{str(instance).lower()}_ch{index}", "PWM") + objects.append((name, instance, record, index)) + stm32._register_device(name, "PWM") + else: + name = _object_name(str(record["Peripheral"]), kind) + objects.append((name, instance, record, None)) + stm32._register_device(name, kind) + result[kind] = objects + return result + + +def _gpio_names(project_data: dict) -> dict[str, dict]: + """GPIO 对象的名字:默认按引脚(pb11),libxr_config 的 GPIO 段把它映射到新名字,值为 + null 的改名保留默认名;名字必须合法且唯一,不满足时报错退出,改名单里没有的引脚给出警告。 + The names of the GPIO objects: the pin name (pb11) by default, which the GPIO section of + libxr_config maps to a new one while a null rename keeps the default; a name that is not a + valid unique identifier logs an error and generation exits, and a rename that names no pin is + warned about. + """ + gpio = project_data.get("GPIO", {}) + if not isinstance(stm32.libxr_settings.get("GPIO") or {}, dict): + _fail( + tr( + f"{stm32.libxr_config_origin}: GPIO {stm32.libxr_settings['GPIO']!r} is not a " + "mapping of a pin name to an object name", + f"{stm32.libxr_config_origin}:GPIO {stm32.libxr_settings['GPIO']!r} 不是引脚名到" + "对象名的映射", + ) + ) + renames = stm32._settings("GPIO") + # 改名表写出每个引脚(null 保留默认名),设置树里就能直接改;引脚不在工程里了时,它的 null + # 条目随之删除,写了名字的保留并给出警告。 + # The rename table lists every pin (null keeps the default name), so the settings tree can + # edit it directly; when a pin leaves the project its null entry goes, while one with a + # name stays and is warned about. + keys = {label: _key(renames, label) for label in gpio} + for key in [key for key in renames if key not in keys.values()]: + if renames[key] is None: + del renames[key] + else: + logging.warning( + tr( + f"{stm32.libxr_config_origin}: the GPIO rename {key} names no generated " + "pin; it is ignored", + f"{stm32.libxr_config_origin}:GPIO 改名 {key} 没有对应的生成引脚;忽略", + ) + ) + names: dict[str, dict] = {} + problems = [] + for label, pin in gpio.items(): + rename = renames.setdefault(keys[label], None) + if rename is not None and not isinstance(rename, str): + problems.append( + tr( + f"GPIO.{keys[label]} {rename!r} is not a name", + f"GPIO.{keys[label]} {rename!r} 不是名字", + ) + ) + continue + name = rename or label + problem = _gpio_name_problem(name) + if problem: + problems.append(f"GPIO.{keys[label]} {name!r}: {problem}") + continue + stm32._register_device(name, "GPIO") + names[name] = pin + if problems: + _fail( + tr( + f"{stm32.libxr_config_origin}: rename these GPIO objects:\n ", + f"{stm32.libxr_config_origin}:请重命名以下 GPIO 对象:\n ", + ) + + "\n ".join(problems) + ) + return names + + +def _gpio_name_problem(name: str) -> str: + """GPIO 对象名不能用的原因;能用时为空串。名字必须是合法的 C++ 标识符,不是关键字或保留 + 标识符,不与 SDK 的宏和函数、生成代码用到的名字或其他对象重名。 + Why a GPIO object name cannot be used; empty when it can. The name must be a valid C++ + identifier that is no keyword or reserved identifier and does not collide with an SDK macro + or function, a name the generated code uses or another object. + """ + if not IDENTIFIER.fullmatch(name): + return tr("not a valid C++ identifier", "不是合法的 C++ 标识符") + if name in CPP_KEYWORDS: + return tr("a C++ keyword", "是 C++ 关键字") + if "__" in name or re.match(r"_[A-Z]", name): + return tr("a reserved C++ identifier", "是 C++ 保留标识符") + if name in GENERATED_NAMES or name in SDK_MACROS or SDK_NAME.fullmatch(name): + return tr( + "a name of the HPM SDK or of the generated code", + "是 HPM SDK 或生成代码使用的名字", + ) + if name in stm32.registered_devices: + return tr( + f"already the name of a {stm32.registered_devices[name]} object", + f"已是一个 {stm32.registered_devices[name]} 对象的名字", + ) + return "" + + +def _pull_text(pad_ctls: dict) -> str: + """padCtls(PE/PS)的上下拉描述;没有上拉使能时为空。 + The pull description of padCtls (PE/PS); empty when the pull is not enabled. + """ + if str(pad_ctls.get("PE")) != "1": + return "" + return " with a pull-up" if str(pad_ctls.get("PS")) == "1" else " with a pull-down" + + +def _gpio_description(label: str, pin: dict) -> str: + """一个 GPIO 引脚在说明注释里的描述:方向来自 gpiom,上下拉来自 padCtls。 + The description of one GPIO pin in the notice comment: the direction comes from gpiom and + the pull from padCtls. + """ + pad = pin["Pad"] + direction = pin.get("Direction") + if direction == "OUTPUT": + return f"{label} ({pad}) as an output pin" + if direction == "INPUT": + return f"{label} ({pad}) as an input pin{_pull_text(pin.get('PadCtls') or {})}" + return f"{label} ({pad})" + + +def _gpio_section(project_data: dict, gpio: dict[str, dict]) -> list[str]: + """GPIO 对象:每个引脚一个 HPMGPIO,构造带端口 IRQ;中断的注册和使能属于消费者(O1),不 + 在这里生成。 + The GPIO objects: one HPMGPIO per pin, constructed with its port IRQ; registering and + enabling an interrupt belongs to the consumer (O1) and is not generated here. + """ + if not gpio: + return [] + functions = _functions_text(project_data) + summary = ", ".join(_gpio_description(label, pin) for label, pin in gpio.items()) + verb = "configured" if _main_calls(project_data) else "defines (main.c does not call it)" + lines = _comment( + f"GPIO: {functions} {verb} {summary}. The interrupt edge comes from the pinmux " + "and every object carries its port IRQ; RegisterCallback and EnableInterrupt belong " + "to the consumer (a Module or the User Code).", + INDENT, + ) + for label, pin in gpio.items(): + port = pin["Port"] + lines += layout( + f"static HPMGPIO {label}", + ["HPM_GPIO0", f"GPIO_DI_GPIO{port}", str(pin["Line"]), f"IRQn_GPIO0_{port}"], + ) + return lines + + +def _i2c_section(i2cs: list) -> list[str]: + """I2C 对象:速率是 libxr_config 中的设置,驱动只接受固定的几档。 + The I2C objects: the speed is a setting of libxr_config, and the driver accepts only the + fixed rates. + """ + if not i2cs: + return [] + lines = [f"{INDENT}// I2C: blocking transfers; the DMA-backed background path runs on dma_mgr."] + for name, instance, _record, _channel in i2cs: + speed = _setting_int(f"I2C.{name}.speed", _instance_settings("I2C", name)["speed"]) + if speed not in I2C_SPEEDS: + _fail( + tr( + f"libxr_config.yaml: I2C.{name}.speed {speed} is not one of " + f"{', '.join(map(str, I2C_SPEEDS))}; the HPM I2C driver supports only " + "these rates", + f"libxr_config.yaml:I2C.{name}.speed {speed} 不是 " + f"{', '.join(map(str, I2C_SPEEDS))} 之一;HPM 的 I2C 驱动只支持这几档", + ) + ) + lines += layout( + f"static HPMI2C {name}", + [f"HPM_{instance}", f"clock_{instance.lower()}", Braces(f"{speed}U")], + ) + return lines + + +def _pwm_section(pwms: list) -> list[str]: + """PWM 对象:GPTMR 的每个比较器输出一个 HPMPWM,占空比比较器固定为 0;对象只在 SoC 没有 + PWM 外设时生成(解析已判定,HPMPWM 在那里走 GPTMR fallback 路径),libxr_config.yaml 中 + 给出频率时才调用 SetConfig。对象和登记都不能包 #if:xrobot 的生成器无法判断编译选项, + guard 里的 XR_REGISTER 会被它拒绝。 + The PWM objects: one HPMPWM per comparator output of a GPTMR, with the duty comparator + fixed at 0; an object is generated only when the SoC has no PWM peripheral (decided by + the parse, where HPMPWM takes its GPTMR fallback path). SetConfig is called when the + frequency is set in libxr_config.yaml. Neither the objects nor the registrations may sit + inside #if: xrobot's generator cannot evaluate build options and rejects an XR_REGISTER + inside a guard. + """ + if not pwms: + return [] + lines = _comment( + "PWM on a GPTMR comparator: the SoC has no PWM peripheral, so HPMPWM takes its GPTMR " + "fallback path; the duty comparator is fixed at 0 and SetConfig is called when " + "libxr_config sets a frequency.", + INDENT, + ) + for name, instance, _record, channel in pwms: + lines += layout( + f"static HPMPWM {name}", + [ + f"reinterpret_cast(HPM_{instance})", + f"clock_{instance.lower()}", + str(channel), + str(PWM_DUTY_CMP), + "HPMPWM::Polarity::NORMAL", + ], + ) + # null 不调用 SetConfig;给出的频率必须是正整数(赫兹)。 + # null calls no SetConfig; a given frequency must be a positive integer (hertz). + frequency = _keyed_settings("PWM", name).setdefault("frequency", None) + if frequency is not None: + frequency = _setting_int(f"PWM.{name}.frequency", frequency) + lines += layout(f"{name}.SetConfig", [Braces(str(frequency))]) + return lines + + +def _terminal_section() -> list[str]: + """终端:HPM 生成器不创建 UART,terminal_source 没有可用的终端;设置了就给出警告。 + The terminal: the HPM generator creates no UART, so terminal_source has no terminal to + name; a set one is warned about. + """ + source = str(stm32.libxr_settings.get("terminal_source") or "") + if source and source != "none": + logging.warning( + tr( + f"terminal_source '{source}' has no effect: the HPM generator creates no " + "UART, so the terminal is not initialized", + f"terminal_source '{source}' 无效:HPM 生成器不创建 UART,终端不初始化", + ) + ) + return [] + + +def _registrations(use_xrobot: bool) -> list[str]: + """XRobot 登记表:每个对象一行 XR_REGISTER,按种类分组。 + The XRobot registrations: one XR_REGISTER line per object, grouped by kind. + """ + if not use_xrobot: + return [] + return stm32.generate_xrobot_registrations() + + +def _sections( + project_data: dict, peripherals: dict[str, list], gpio: dict[str, dict], use_xrobot: bool +) -> list[str]: + """app_main 函数体的各段:时基、GPIO、I2C、PWM、终端和登记。 + The sections of the app_main body: timebase, GPIO, I2C, PWM, terminal and the + registrations. + """ + blocks = [ + [ + f"{INDENT}// Timebase and platform", + f"{INDENT}static HPMTimebase timebase;", + f"{INDENT}PlatformInit();", + f"{INDENT}static HPMPowerManager power_manager;", + ], + _gpio_section(project_data, gpio), + _i2c_section(peripherals.get("I2C", [])), + _pwm_section(peripherals.get("PWM", [])), + _terminal_section(), + ] + registrations = _registrations(use_xrobot) + if registrations: + blocks.append([f"{INDENT}// Hardware registration"] + registrations) + return stm32.join_blocks([block for block in blocks if block]) + + +def _notice(project_data: dict) -> list[str]: + """文件开头的说明:由哪个 .hpmpc 生成、Pinmux Tool 和 BSP 各管什么、只改 User Code 区域。 + The notice at the start of the file: which .hpmpc it was generated from, what the Pinmux + Tool and the BSP own and that only the User Code regions are for editing. + """ + functions = _functions_text(project_data) + if _main_calls(project_data): + pins = f"configures the pins via {functions} in main.c" + else: + pins = f"defines the pins in {functions}, which main.c does not call," + return [GENERATED_NOTICE] + _comment( + f"Generated from {project_data.get('Hpmpc', 'the HPM Pinmux Tool project')}" + f" ({project_data.get('Mcu', {}).get('Type', '')}); the Pinmux Tool {pins} and main.c" + ' stays BSP-owned. Put application code between the "User Code Begin" and "User Code' + ' End" markers.' + ) + + +def _main_calls(project_data: dict) -> bool: + """main.c 是否真的调用了读到引脚的 pinmux 函数;为假时引脚取自 init_bsp_pins 兜底。旧的 + 工程 YAML 没有 MainCalls,按调用了处理。 + Whether main.c really calls the pinmux functions the pins were read from; false when the + pins come from the init_bsp_pins fallback. An older project YAML has no MainCalls and counts + as calling them. + """ + return bool(project_data.get("MainCalls", project_data.get("MainFunctions"))) + + +def _functions_text(project_data: dict) -> str: + """读到引脚的 pinmux 函数,写成 init_bsp_pins() 这样的列表;没有时为 "no pinmux function"。 + The pinmux functions the pins were read from, listed as init_bsp_pins(); "no pinmux + function" when there is none. + """ + names = project_data.get("MainFunctions") or [] + return ", ".join(f"{name}()" for name in names) or "no pinmux function" + + +def _includes(project_data: dict, use_xrobot: bool) -> list[str]: + """include 列表:libxr.hpp、hpm_soc.h、用到的 hpm 驱动头和时基头,XRobot 再加 + xrobot_main.hpp;均按字母顺序。 + The includes: libxr.hpp, hpm_soc.h, the hpm driver headers in use and the timebase one + and, with XRobot, xrobot_main.hpp; all in alphabetical order. + """ + peripherals = project_data.get("Peripherals", {}) + drivers = ["hpm_soc.h", "hpm_timebase.hpp", "hpm_power.hpp"] + if project_data.get("GPIO"): + drivers.append("hpm_gpio.hpp") + for kind in ("I2C", "PWM"): + if peripherals.get(kind): + drivers.append(f"hpm_{kind.lower()}.hpp") + if use_xrobot: + drivers.append("xrobot_main.hpp") + return [ + '#include "app_main.h"', + "", + '#include "libxr.hpp"', + *[f'#include "{header}"' for header in sorted(drivers)], + "", + "using namespace LibXR;", + ] + + +def generate_full_code(project_data: dict, use_xrobot: bool, existing_code: str) -> str: + """生成 app_main 源文件的完整内容,并填回已有代码中 User Code 区域的内容。 + Generate the full content of the app_main source file and put back the User Code bodies of + the existing code. + """ + peripherals = _peripherals(project_data) + gpio = _gpio_names(project_data) + default_3 = ( + [] + if use_xrobot + else [ + f"{INDENT}while (true)", + f"{INDENT}{{", + f"{INDENT * 2}Thread::Sleep(UINT32_MAX);", + f"{INDENT}}}", + ] + ) + lines = _notice(project_data) + lines += _includes(project_data, use_xrobot) + lines += ["", "/* User Code Begin 1 */", "/* User Code End 1 */"] + lines += ["", 'extern "C" void app_main(void)', "{"] + lines += [f"{INDENT}/* User Code Begin 2 */", f"{INDENT}/* User Code End 2 */", ""] + lines += _sections(project_data, peripherals, gpio, use_xrobot) + lines += ["", f"{INDENT}/* User Code Begin 3 */"] + default_3 + lines.append(f"{INDENT}/* User Code End 3 */") + if use_xrobot: + lines.append(f"{INDENT}XROBOT_MAIN();") + lines.append("}") + generated = "\n".join(lines) + "\n" + # 与生成代码里其他名字相同的 GPIO 名字(固定名单之外的):数一数出现次数。 + # GPIO names equal to another name of the generated code (beyond the fixed list): count + # the occurrences. + clashes = stm32.overused_names(gpio, generated, use_xrobot) + if clashes: + _fail( + tr( + f"{stm32.libxr_config_origin}: rename these GPIO objects; the generated code " + f"uses the names for something else: {', '.join(clashes)}", + f"{stm32.libxr_config_origin}:请重命名以下 GPIO 对象,生成代码把这些名字用于" + f"别处:{'、'.join(clashes)}", + ) + ) + return stm32._preserve_generated_regions(existing_code, generated) + + +def _check_soc(project_data: dict) -> None: + """工程 YAML 的 SoC 和封装必须在芯片引脚数据里(大写);不认识的 SoC 无法判断有没有 PWM + 外设,报错退出。 + The SoC and the package of the project YAML must be in the chip pin data (upper case); for + an unknown SoC it cannot be told whether it has a PWM peripheral, so an error is logged and + generation exits. + """ + soc = str(project_data.get("Mcu", {}).get("Type") or "") + try: + pin_layout.layout_hpm(soc, project_data.get("Package")) + except ValueError as error: + _fail(str(error)) + + +def initialize_registry(use_xrobot: bool) -> None: + """清空生成对象的登记表,使同一次进程中的下一次生成不带上次的对象;use_xrobot 为真时先 + 登记 power_manager(PowerManager)。 + Clear the registry of generated objects, so the next generation in the same process + carries no objects over; with use_xrobot, power_manager (PowerManager) is registered + first. + """ + stm32.registered_devices.clear() + stm32.registered_origins.clear() + stm32.used_headers.clear() + if use_xrobot: + stm32._register_device("power_manager", "PowerManager", tr("power manager", "电源管理器")) + + +def reset_settings() -> None: + """把生效的设置恢复为 HPM 的默认集合:STM32 专用的段剔除,I2C、PWM 和 GPIO 段补上。 + Restore the effective settings to the HPM default set: the STM32-only sections removed + and the I2C, PWM and GPIO sections added. + """ + stm32.reset_settings() + for key in STM32_ONLY_SECTIONS: + stm32.libxr_settings.pop(key, None) + stm32._settings("I2C") + stm32._settings("PWM") + stm32._settings("GPIO") + + +def generate( + input_path: str, output_path: str, use_xrobot: bool = False, libxr_config: str = "" +) -> None: + """生成 output_path 指定的 app_main 源文件,以及同一目录中的 app_main.h 和 libxr_config.yaml。 + Generate the app_main source file output_path, and app_main.h and libxr_config.yaml in the + same directory. + + 每次从默认设置开始,合并 libxr_config(libxr_config.yaml 的路径或 URL,为空时读取输出目录中 + 的文件)。全部文件先在内存中生成,没有错误时才写出,并且只写内容有变化的文件。已有输出文件 + 中 User Code 区域的内容被保留。出错时记录错误(调试日志另记调用栈)并以状态 1 退出。 + Every run starts from the default settings and merges libxr_config, the path or URL of + libxr_config.yaml, or the file in the output directory when empty. All files are generated + in memory first and written only when nothing failed, and only the files whose content + changed are written. The User Code bodies of an existing output file are kept. An error is + logged, with the traceback at debug level, and exits with status 1. + """ + try: + output_dir = os.path.dirname(output_path) or os.curdir + + reset_settings() + project_data = stm32.load_configuration(input_path) + _check_soc(project_data) + stm32.load_libxr_config(output_dir, libxr_config) + # load_configuration 又写入了 SYSTEM(HPM 只有裸机),再剔除一次。 + # load_configuration has written SYSTEM again (HPM is bare metal only); remove it + # once more. + stm32.libxr_settings.pop("SYSTEM", None) + stm32._settings("I2C") + stm32._settings("PWM") + stm32._settings("GPIO") + initialize_registry(use_xrobot) + + existing_code = "" + if os.path.exists(output_path): + with open(output_path, encoding="utf-8") as file: + existing_code = file.read() + stm32.reject_user_hardware_container(existing_code, use_xrobot) + if use_xrobot: + stm32.reject_user_xrobot_main(existing_code) + existing_code = stm32._without_default_loop(existing_code) + + files = { + os.path.basename(output_path): generate_full_code( + project_data, use_xrobot, existing_code + ), + "app_main.h": stm32.APP_MAIN_HEADER, + "libxr_config.yaml": stm32.libxr_config_text(), + } + + os.makedirs(output_dir, exist_ok=True) + written, unchanged = [], [] + for name, text in files.items(): + path = os.path.join(output_dir, name) + (written if stm32._write_if_changed(path, text) else unchanged).append(name) + stm32._report_files(output_dir, written, unchanged, []) + except SystemExit: + raise + except Exception as error: + logging.error(tr(f"Generation failed: {str(error)}", f"生成失败:{str(error)}")) + logging.debug(tr("Traceback:", "调用栈:"), exc_info=True) + sys.exit(1) diff --git a/src/libxr/generator_code_mspm0.py b/src/libxr/generator_code_mspm0.py new file mode 100644 index 0000000..458d5ce --- /dev/null +++ b/src/libxr/generator_code_mspm0.py @@ -0,0 +1,1089 @@ +"""libxr gen 对 MSPM0 工程:由工程 YAML 生成 User/app_main.cpp 和 libxr_config.yaml。 +libxr gen for an MSPM0 project: generate User/app_main.cpp and libxr_config.yaml from the +project YAML. + +时钟、引脚、DMA 通道和外设都由 SysConfig 配置(main.c 里的 SYSCFG_DL_init());生成代码只声明 +LibXR 对象并把它们登记进 XRobot。构造全部使用 SysConfig 的宏(UART_0_INST、GPIO_LEDS_PIN_LED1_PORT +……),布局沿 bsp_stm32f103。缓冲区大小、队列长度和终端设置来自 libxr_config.yaml(首次生成把 +默认值写进该文件,已有值保持不变)。 +Clocks, pins, DMA channels and peripherals are configured by SysConfig (SYSCFG_DL_init() in +main.c); the generated code only declares the LibXR objects and registers them with XRobot. +Every construction uses the SysConfig macros (UART_0_INST, GPIO_LEDS_PIN_LED1_PORT, ...), and +the layout follows bsp_stm32f103. The buffer sizes, queue lengths and terminal settings come +from libxr_config.yaml (the first generation writes the defaults into the file and keeps the +values that are already there). + +时钟(M5):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)除以它的 divideRatio,其他时钟源报错。SysConfig 不为 I2C 和 +SPI 导出时钟宏,工程 YAML 缺少电源域或 CPUCLK 时报错。UART 用 SysConfig 的 INST_FREQUENCY 宏, +PWM 的源时钟由驱动从 INST_CLK_FREQ 求出,都不需要生成器计算。 +Clocks (M5): the BUSCLK of PD1 is CPUCLK_FREQ, that of PD0 is CPUCLK_FREQ / the ULPCLK divider +(the DL_SYSCTL_setULPCLKDivider of the generated code, 1 by default). The I2C driver resets the +clock configuration itself (BUSCLK, divider 1), so the BUSCLK of the power domain is the right +argument; the SPI driver keeps the clock configuration SysConfig wrote, so its argument is the +clock source SysConfig chose (BUSCLK, MFCLK at 4 MHz or LFCLK at 32.768 kHz) divided by its +divideRatio, and any other source is an error. SysConfig exports no clock macro for an I2C or +an SPI, so a project YAML without the power domain or CPUCLK is an error. A UART takes the +INST_FREQUENCY macro of SysConfig and the source clock of a PWM is derived by the driver from +INST_CLK_FREQ, so neither needs a generator-side computation. + +设置(libxr_config.yaml):值为 null 时用默认值,缓冲区和队列必须是正整数,UART 的 +tx_buffer_size 还要满足驱动的对齐和 DMA 长度断言;PWM 的 frequency 为 null 时不调用 +SetConfig。terminal_source 沿用 STM32 的约定(空值或 none 不生成终端),只在文件里还没有这个键 +时选第一个 UART。设置键不分大小写,文件里的写法保持不变。 +Settings (libxr_config.yaml): a null value takes the default, the buffers and queues must be +positive integers and the tx_buffer_size of a UART must also meet the alignment and DMA length +assertions of the driver; a null PWM frequency calls no SetConfig. terminal_source follows the +STM32 convention (an empty value or none generates no terminal), and the first UART is chosen +only while the file has no such key. Settings keys match without regard to case, and the +spelling of the file is kept. + +对象名:GPIO 用引脚标签($name 去掉 PIN_ 前缀),UART/I2C/SPI 用外设名的小写(uart0),PWM 是 +pwm__c。GPIO 中断遵循 STM32 的约定(模型 O1):生成器只创建对象,边沿来自 SysConfig; +RegisterCallback、EnableInterrupt 和回调函数属于消费者(Module 或 User Code)。UART 没有 DMA TX +(或 SPI 没有一对 DMA 通道)时驱动没有对应的构造路径,生成报错。UART 默认用 +MSPM0_UART_MAIN_INIT(接收走字节中断);配了 DMA RX 的 UART Extend 实例用 +MSPM0_UART_EXTEND_INIT(接收走 FULL-DMA 通道上的循环 DMA,接收环大小是 rx_dma_buffer_size), +RX 通道不是 FULL-DMA 通道时报错;UART Main 实例上的 DMA RX 通道用不上,给出警告。 +Object names: a GPIO takes the pin label (the $name without its PIN_ prefix), a UART/I2C/SPI +the lower-case peripheral name (uart0), and a PWM is pwm__c. GPIO interrupts follow +the STM32 convention (model O1): the generator creates the object only, the edge comes from +SysConfig, and RegisterCallback, EnableInterrupt and the callback belong to the consumer (a +Module or the User Code). The driver has no construction path for a UART without DMA TX (or an +SPI without a pair of DMA channels), so generation reports an error then. A UART takes +MSPM0_UART_MAIN_INIT by default (receive on byte interrupts); a UART Extend instance with DMA RX +takes MSPM0_UART_EXTEND_INIT (receive by circular DMA on a FULL-DMA channel into a ring of +rx_dma_buffer_size bytes), an RX channel that is no FULL-DMA channel being an error; the DMA RX +channel of a UART Main instance cannot be used and is warned about. +""" + +from __future__ import annotations + +import logging +import os +import re +import sys + +from xr_syntax.cpp import CppDocument +from xr_syntax.i18n import tr + +from libxr import generator_code_stm32 as stm32 +from libxr.cpp_layout import Braces, layout +from libxr.generator_code_stm32 import GENERATED_NOTICE, INDENT + +# generator_code_stm32 的其余接口(登记表、设置、写入助手)经模块属性访问:测试会用 +# importlib.reload 重载它,直接导入的名字会指向重载前的旧全局对象。 +# The rest of the generator_code_stm32 interface (the registry, the settings, the writing +# helpers) is reached through module attributes: tests reload it with importlib.reload, and +# directly imported names would keep pointing at the pre-reload global objects. +# MSPM0 各设置段的默认值;STM32 段(USART/TIM/USB……)不适用于 MSPM0,生成前剔除。 +# The defaults of the MSPM0 settings sections; the STM32 sections (USART/TIM/USB, ...) do not +# apply to MSPM0 and are removed before generation. +MSPM0_DEFAULTS = { + "UART": {"tx_buffer_size": 128, "rx_buffer_size": 128, "tx_queue_size": 5}, + # 只有 MSPM0_UART_EXTEND_INIT 的 UART 有:接收 DMA 环的字节数(偶数)。 + # Only a UART of MSPM0_UART_EXTEND_INIT has it: the bytes of the receive DMA ring (even). + "UART_EXTEND": {"rx_dma_buffer_size": 128}, + "I2C": {"buffer_size": 32, "dma_enable_min_size": 8}, + "SPI": {"tx_buffer_size": 32, "rx_buffer_size": 32, "dma_enable_min_size": 3}, +} +STM32_ONLY_SECTIONS = ( + "ADC", + "TIM", + "CAN", + "FDCAN", + "USB", + "database", + "software_timer", + "USART", + "SYSTEM", +) +# UART 发送缓冲的一半 N(tx_buffer_size):驱动断言 2 x N 是 2 * alignof(size_t) 的倍数 +# (Cortex-M0+ 上 size_t 按 4 字节对齐,所以 N 是 4 的倍数),且 N 不超过单次 DMA 的 +# 0xFFFF 字节(mspm0_uart.cpp)。 +# The half N of the UART transmit buffer (tx_buffer_size): the driver asserts that 2 x N is a +# multiple of 2 * alignof(size_t) (size_t aligns to 4 bytes on the Cortex-M0+, so N is a +# multiple of 4) and that N is at most the 0xFFFF bytes of one DMA transfer (mspm0_uart.cpp). +UART_TX_ALIGN = 4 +UART_TX_MAX = 0xFFFF // UART_TX_ALIGN * UART_TX_ALIGN +# SPI 可用的固定时钟源的频率(M5):MFCLK 4 MHz,LFCLK 32.768 kHz。 +# The frequencies of the fixed clock sources an SPI can use (M5): MFCLK 4 MHz, LFCLK +# 32.768 kHz. +MFCLK_FREQ = 4000000 +LFCLK_FREQ = 32768 + +IDENTIFIER = re.compile(r"[A-Za-z_]\w*") +DIVIDER_NUMBER = re.compile(r"(?:DIVIDE|RATIO)_(\d+)$") + + +def _comment(text: str, indent: str = "") -> list[str]: + """一段注释折行成不超过 90 列的行,每行带 indent 缩进和 "// " 前缀。 + Wrap a comment into lines of at most 90 columns; every line carries the indent and the + "// " prefix. + """ + words, lines, line = text.split(), [], "" + for word in words: + if line and len(line) + 1 + len(word) > 85: + lines.append(f"{indent}// {line}") + line = word + else: + line = f"{line} {word}".strip() + if line: + lines.append(f"{indent}// {line}") + return lines + + +def _fail(message: str): + """记录错误并以状态 1 退出。 + Log an error and exit with status 1. + """ + logging.error(message) + sys.exit(1) + + +def _settings_of(group: str, name: str) -> dict: + """libxr_settings[group] 中对象 name 的设置段:键不分大小写,文件里已有的键保留它的写法 + (libxr pins -d 也这样找),没有时以小写的 name 新建。 + The settings section of the object name in libxr_settings[group]: the key is matched + without regard to case and a key already in the file keeps its spelling (libxr pins -d + finds it the same way); a missing one is created as the lower-case name. + """ + section = stm32._settings(group) + key = next((key for key in section if str(key).lower() == name.lower()), name.lower()) + return stm32._settings(group, key) + + +def _setting(section: dict, key: str, default): + """设置段 section 中 key 的值;缺少或为 null(YAML 中只写了键)时写入并返回 default。 + The value of key in the settings section; a missing or null value (only the key written in + YAML) is replaced by default, which is returned. + """ + if section.get(key) is None: + section[key] = default + return section[key] + + +def _instance_settings(group: str, instance: str, extend: bool = False) -> dict: + """实例的设置段,缺少的默认值补进段里,每个值都检查过:缓冲区和队列是正整数,DMA 阈值是非负 + 整数;UART 的发送缓冲还要满足驱动的断言(见 UART_TX_ALIGN 和 UART_TX_MAX)。返回检查后的 + 整数值。 + The settings of an instance with the missing defaults filled in, every value checked: the + buffers and queues are positive integers, a DMA threshold a non-negative integer, and the + UART transmit buffer also meets the assertions of the driver (see UART_TX_ALIGN and + UART_TX_MAX). The checked integers are returned. + """ + section = _settings_of(group, instance) + values = {} + defaults = dict(MSPM0_DEFAULTS[group]) + if extend: + defaults.update(MSPM0_DEFAULTS["UART_EXTEND"]) + for key, default in defaults.items(): + minimum = 0 if key == "dma_enable_min_size" else 1 + values[key] = stm32._integer( + f"{group}.{instance}.{key}", _setting(section, key, default), minimum + ) + if group == "UART": + size = values["tx_buffer_size"] + if size % UART_TX_ALIGN or size > UART_TX_MAX: + raise stm32._invalid_setting( + f"UART.{instance}.tx_buffer_size", + size, + f"a multiple of {UART_TX_ALIGN} up to {UART_TX_MAX} (the driver takes 2 x N " + f"bytes, a multiple of 2 * alignof(size_t), and sends at most " + f"{UART_TX_MAX + 3} bytes per DMA transfer)", + f"{UART_TX_ALIGN} 的倍数且不大于 {UART_TX_MAX}(驱动使用 2 x N 字节,必须是 " + f"2 * alignof(size_t) 的倍数,单次 DMA 最多发送 {UART_TX_MAX + 3} 字节)", + ) + if extend: + size = values["rx_dma_buffer_size"] + if size < 2 or size % 2 or size > 0xFFFF: + raise stm32._invalid_setting( + f"UART.{instance}.rx_dma_buffer_size", + size, + "an even number from 2 to 65534 (the driver receives into a ring of two halves, " + "at most one DMA transfer long)", + "2 到 65534 之间的偶数(驱动的接收环分两半,最长一次 DMA 传输)", + ) + return values + + +def _object_name(name: str, kind: str) -> str: + """对象的 C++ 名字:小写、必须是合法标识符且不与其他对象重名;不满足时报错退出。 + The C++ name of an object: lower case, a valid identifier and unique among the objects; + otherwise an error is logged and generation exits. + """ + name = name.lower() + if not IDENTIFIER.fullmatch(name): + _fail( + tr( + f"{name} is not a valid C++ identifier; rename the instance in SysConfig", + f"{name} 不是合法的 C++ 标识符;请在 SysConfig 中重命名该实例", + ) + ) + if name in stm32.registered_devices: + _fail( + tr( + f"Two objects are named {name} ({kind} and {stm32.registered_devices[name]}); rename " + "one of the instances in SysConfig", + f"两个对象都叫 {name}({kind} 与 {stm32.registered_devices[name]});请在 SysConfig 中" + "重命名其中一个实例", + ) + ) + return name + + +def _peripherals(project_data: dict) -> dict[str, list]: + """工程 YAML 中的外设对象:kind -> [(对象名, SysConfig 实例名, 记录, PWM 通道)],按 YAML + 顺序,同时登记进 XR_REGISTER 的对象表。 + The peripheral objects of the project YAML: kind -> [(object name, SysConfig instance name, + record, PWM channel)], in YAML order, registered into the XR_REGISTER table as well. + """ + result: dict[str, list] = {} + for kind, records in project_data.get("Peripherals", {}).items(): + if kind == "Other": + continue + objects = [] + for instance, record in records.items(): + if not record.get("Peripheral"): + _fail( + tr( + f"{instance}: the project YAML names no peripheral for it; assign one " + "in SysConfig and run `libxr parse` again", + f"{instance}:工程 YAML 没有给出它的外设;请在 SysConfig 中分配外设后" + "重新运行 `libxr parse`", + ) + ) + if kind == "PWM": + # PWM 的对象名 pwm_<定时器>_c,定时器是外设名(TIMG0),与 libxr pins -d 给出 + # 的设置键一致;实例名(PWM_0)不进名字。 + # The PWM object name pwm__c, the timer being the peripheral (TIMG0), + # as in the settings keys libxr pins -d gives; the instance name (PWM_0) does + # not enter the name. + timer = str(record["Peripheral"]).lower() + for channel in record.get("Channels", []): + name = _object_name(f"pwm_{timer}_c{channel}", "PWM") + objects.append((name, instance, record, channel)) + stm32._register_device(name, "PWM") + else: + name = _object_name(str(record["Peripheral"]), kind) + objects.append((name, instance, record, None)) + stm32._register_device(name, kind) + result[kind] = objects + return result + + +def _domain_clock(instance: str, record: dict, cpuclk) -> int: + """实例所在的电源域(0 或 1);工程 YAML 没有电源域或 CPUCLK 时报错退出:SysConfig 不为 + I2C 和 SPI 导出输入时钟的宏,生成器必须自己算出 BUSCLK。 + The power domain (0 or 1) of an instance; a project YAML without the power domain or + CPUCLK is an error: SysConfig exports no input clock macro for an I2C or an SPI, so the + generator has to compute the BUSCLK itself. + """ + domain = record.get("PowerDomain") + if domain not in (0, 1) or cpuclk is None: + missing = "CPUCLK" if cpuclk is None else f"the power domain of {record.get('Peripheral')}" + missing_zh = "CPUCLK" if cpuclk is None else f"{record.get('Peripheral')} 的电源域" + _fail( + tr( + f"{instance}: the project YAML has no {missing}, so the input clock of the " + "driver cannot be computed (SysConfig defines no clock macro for an I2C or " + "SPI); run `libxr parse` again with a SysConfig output that defines CPUCLK_FREQ", + f"{instance}:工程 YAML 里没有{missing_zh},算不出驱动的输入时钟(SysConfig 不为 " + "I2C 和 SPI 定义时钟宏);请用定义了 CPUCLK_FREQ 的 SysConfig 输出重新运行 " + "`libxr parse`", + ) + ) + return domain + + +def _spi_clock(instance: str, record: dict) -> tuple[str | None, str, int]: + """SPI 的输入时钟:(时钟源常量名,或 BUSCLK 时为 None;时钟源说明;分频数)。驱动沿用 + SysConfig 写下的时钟配置;不支持的时钟源报错退出。 + The input clock of an SPI: (the constant of the clock source, or None for the BUSCLK; a + description of the source; the divider). The driver keeps the clock configuration SysConfig + wrote; an unsupported clock source is an error. + """ + clock = record.get("SysConfigClock") or {} + match = DIVIDER_NUMBER.search(str(clock.get("divideRatio") or "")) + divider = int(match.group(1)) if match else 1 + sel = str(clock.get("clockSel") or "") + if sel == "" or sel.endswith("_BUSCLK"): + return None, "BUSCLK", divider + if sel.endswith("_MFCLK"): + return "MFCLK_FREQ", "MFCLK (4 MHz)", divider + if sel.endswith("_LFCLK"): + return "LFCLK_FREQ", "LFCLK (32.768 kHz)", divider + _fail( + tr( + f"{instance}: the SysConfig clock source {sel} is not supported (BUSCLK, MFCLK and " + "LFCLK are); choose one of them in SysConfig", + f"{instance}:不支持 SysConfig 时钟源 {sel}(支持 BUSCLK、MFCLK 和 LFCLK);请在 " + "SysConfig 中选择其中之一", + ) + ) + + +def _clocks(project_data: dict) -> tuple[list[str], dict[str, str]]: + """时钟常量和每个 I2C、SPI 实例的时钟实参:用到的电源域各一个 BUSCLK 常量;SysConfig 给 + SPI 配了非默认时钟(其他时钟源或分频)时再有 MFCLK/LFCLK 常量和该实例的 <实例>_CLK_FREQ, + 凡被引用的常量都会生成。用不到时钟时两者都为空。 + The clock constants and the clock argument of every I2C and SPI instance: one BUSCLK + constant per power domain in use; when SysConfig gave an SPI a non-default clock (another + source or a divider) there is also the MFCLK/LFCLK constant and the _CLK_FREQ of + that instance, and every constant that is referenced is generated. Both are empty when no + clock is needed. + """ + divider = project_data.get("ULPCLKDivider") or 1 + cpuclk = project_data.get("CPUCLK") + peripherals = project_data.get("Peripherals", {}) + domains: set[int] = set() + arguments: dict[str, str] = {} + spi_lines: list[str] = [] + sources: dict[str, int] = {} + for instance, record in peripherals.get("I2C", {}).items(): + domain = _domain_clock(instance, record, cpuclk) + domains.add(domain) + arguments[instance] = f"PD{domain}_BUSCLK_FREQ" + for instance, record in peripherals.get("SPI", {}).items(): + source, text, div = _spi_clock(instance, record) + if source is None: + domain = _domain_clock(instance, record, cpuclk) + domains.add(domain) + base, text = f"PD{domain}_BUSCLK_FREQ", f"the BUSCLK of PD{domain}" + else: + base = source + sources[source] = MFCLK_FREQ if source == "MFCLK_FREQ" else LFCLK_FREQ + if not record.get("SysConfigClock"): + arguments[instance] = base + continue + constant = f"{instance}_CLK_FREQ" + spi_lines += _comment( + f"{instance} runs from {text} divided by {div}, as SysConfig configured it; the SPI " + "driver keeps that clock configuration." + ) + expression = base if div == 1 else f"{base} / {div}" + spi_lines.append(f"static constexpr uint32_t {constant} = {expression};") + arguments[instance] = constant + lines: list[str] = [] + if domains: + domains_text = " and ".join( + f"PD{domain} from {'MCLK (CPUCLK_FREQ)' if domain == 1 else _ulpclk_text(divider)}" + for domain in sorted(domains) + ) + lines = _comment( + f"Input clocks: the BUSCLK of the power domain. {domains_text}. The I2C driver " + "resets the clock configuration of its peripherals, so the BUSCLK of the domain is " + "the right argument." + ) + for domain in sorted(domains): + expression = ( + "CPUCLK_FREQ" if domain == 1 or divider == 1 else f"(CPUCLK_FREQ / {divider})" + ) + lines.append(f"static constexpr uint32_t PD{domain}_BUSCLK_FREQ = {expression};") + for source in sorted(sources): + lines.append(f"static constexpr uint32_t {source} = {sources[source]};") + return lines + spi_lines, arguments + + +def _ulpclk_text(divider: int) -> str: + """PD0 的 ULPCLK 说明文字,随 SysConfig 写入的分频变化。 + The notice text of the PD0 ULPCLK, following the divider SysConfig wrote. + """ + return ( + f"ULPCLK = CPUCLK_FREQ / {divider} (DL_SYSCTL_setULPCLKDivider)" + if divider != 1 + else "ULPCLK = CPUCLK_FREQ (no ULPCLK divider)" + ) + + +def _uart_dma_defines(uarts: list, extend: set[str]) -> list[str]: + """UART DMA 通道的所有权标注:MSPM0_UART_MAIN_INIT 需要知道发送通道属于哪个 UART、服务于 + 发送端;MSPM0_UART_EXTEND_INIT 还要接收通道的同样标注、它是 FULL-DMA 通道、是否用半传输 + 中断(FULL-DMA 通道都低于 8,用),以及实例是 UART Extend。 + The ownership annotations of the UART DMA channels: MSPM0_UART_MAIN_INIT needs to know which + UART owns the transmit channel and that it serves the transmitter; MSPM0_UART_EXTEND_INIT + also needs the same for the receive channel, that it is a FULL-DMA channel, whether it uses + the half-transfer interrupt (every FULL-DMA channel is below 8, so it does) and that the + instance is a UART Extend. + """ + lines: list[str] = [] + for _name, instance, record, _channel in uarts: + channel = record.get("DMA_TX") + if channel: + lines.append(f"#define {channel}_LIBXR_UART_IRQN {instance}_INST_INT_IRQN") + lines.append(f"#define {channel}_LIBXR_UART_TX 1") + if instance in extend: + rx = record["DMA_RX"] + lines.append(f"#define {rx}_LIBXR_UART_IRQN {instance}_INST_INT_IRQN") + lines.append(f"#define {rx}_LIBXR_UART_RX 1") + lines.append(f"#define {rx}_LIBXR_FULL_CHANNEL 1") + lines.append(f"#define {rx}_LIBXR_HALF_INTERRUPT 1") + lines.append(f"#define {instance}_LIBXR_EXTEND_CAPABLE 1") + if lines and extend: + lines = [ + "// Ownership of the DMA channels that serve a UART; MSPM0_UART_MAIN_INIT and", + "// MSPM0_UART_EXTEND_INIT check these names against the ones SysConfig generated.", + ] + lines + elif lines: + lines = [ + "// Ownership of the DMA channels that serve a UART transmitter;", + "// MSPM0_UART_MAIN_INIT checks these names against the ones SysConfig generated.", + ] + lines + return lines + + +def _extend_uarts(project_data: dict, uarts: list) -> set[str]: + """用 MSPM0_UART_EXTEND_INIT 的 UART(SysConfig 实例名):配了 DMA RX 的 UART Extend 实例。 + RX 通道不是 FULL-DMA 通道时报错退出;UART Main 实例上的 DMA RX 给出警告(用 + MSPM0_UART_MAIN_INIT,RX 通道不用)。 + The UARTs (SysConfig instance names) that take MSPM0_UART_EXTEND_INIT: the UART Extend + instances with DMA RX. An RX channel that is no FULL-DMA channel logs an error and exits; DMA + RX on a UART Main instance is warned about (it takes MSPM0_UART_MAIN_INIT and the RX channel + stays unused). + """ + full = project_data.get("DMAFullChannels") + extend = set() + for _name, instance, record, _channel in uarts: + rx = record.get("DMA_RX") + if not rx: + continue + peripheral = record.get("Peripheral") + if not record.get("Extend"): + logging.warning( + tr( + f"{instance}: the DMA RX channel {rx} is not used; {peripheral} is a UART Main " + "instance, which receives on byte interrupts (MSPM0_UART_MAIN_INIT); DMA " + "reception needs a UART Extend instance", + f"{instance}:DMA RX 通道 {rx} 不会被使用;{peripheral} 是 UART Main 实例,接收" + "走字节中断(MSPM0_UART_MAIN_INIT);DMA 接收需要 UART Extend 实例", + ) + ) + continue + channel_id = record.get("DMA_RX_ID") + if channel_id is None or full is None or channel_id >= full: + where = ( + f"channel {channel_id}" if channel_id is not None else "a channel of unknown number" + ) + _fail( + tr( + f"{instance}: the DMA RX channel {rx} is {where}, but MSPM0_UART_EXTEND_INIT " + f"receives on a FULL-DMA channel (0 to {(full or 1) - 1}); choose one in " + "SysConfig (UART > DMA Configuration > DMA RX channel) and run `libxr parse` " + "again", + f"{instance}:DMA RX 通道 {rx} 是{where},但 MSPM0_UART_EXTEND_INIT 只能用 " + f"FULL-DMA 通道(0 到 {(full or 1) - 1})接收;请在 SysConfig(UART > DMA " + "Configuration 的 DMA RX 通道)中选择后重新运行 `libxr parse`", + ) + ) + extend.add(instance) + return extend + + +def _buffers(peripherals: dict, extend: set[str]) -> list[str]: + """DMA 缓冲区的定义:UART 发送缓冲是两个半区(2 x N,按 size_t 对齐),SPI 收发各一,I2C 是 + 轮询与 DMA 之间的暂存区。 + The DMA buffer declarations: a UART transmit buffer holds two halves (2 x N, aligned as + size_t), an SPI gets one for each direction and an I2C one staging area between polling and + DMA. + """ + lines: list[str] = [] + for name, instance, _record, _channel in peripherals.get("UART", []): + settings = _instance_settings("UART", name, instance in extend) + tx = settings["tx_buffer_size"] + lines.append(f"alignas(size_t) static uint8_t {name}_tx_buf[2 * {tx}];") + if instance in extend: + rx = settings["rx_dma_buffer_size"] + lines.append(f"alignas(4) static uint8_t {name}_rx_dma_buf[{rx}];") + for name, _instance, _record, _channel in peripherals.get("SPI", []): + settings = _instance_settings("SPI", name) + lines.append(f"alignas(4) static uint8_t {name}_rx_buf[{settings['rx_buffer_size']}];") + lines.append(f"alignas(4) static uint8_t {name}_tx_buf[{settings['tx_buffer_size']}];") + for name, _instance, _record, _channel in peripherals.get("I2C", []): + settings = _instance_settings("I2C", name) + lines.append(f"alignas(4) static uint8_t {name}_buf[{settings['buffer_size']}];") + if not lines: + return [] + if extend: + heading = [ + "// DMA buffers. A UART gets 2 x N bytes for its two transmit halves; a UART Extend with", + "// DMA RX also gets its receive ring. 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.", + ] + else: + heading = [ + "// 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.", + ] + return heading + lines + + +def _notice(project_data: dict) -> list[str]: + """文件开头的说明:由哪个 SysConfig 工程生成、SysConfig 负责什么、只改 User Code 区域。 + The notice at the start of the file: which SysConfig project it was generated from, what + SysConfig owns and that only the User Code regions are for editing. + """ + return [ + GENERATED_NOTICE, + f"// Generated from {project_data.get('Syscfg', 'the SysConfig project')}" + f" ({project_data.get('Mcu', {}).get('Type', '')}); 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.', + ] + + +def _includes(project_data: dict, use_xrobot: bool) -> list[str]: + """include 列表:libxr.hpp、用到的 mspm0 驱动头、ti_msp_dl_config.h,XRobot 再加 + xrobot_main.hpp;均按字母顺序。 + The includes: libxr.hpp, the mspm0 driver headers in use, ti_msp_dl_config.h and, with + XRobot, xrobot_main.hpp; all in alphabetical order. + """ + peripherals = project_data.get("Peripherals", {}) + drivers = ["mspm0_timebase.hpp", "mspm0_power.hpp"] + if project_data.get("GPIO"): + drivers.append("mspm0_gpio.hpp") + for kind in ("I2C", "PWM", "SPI", "UART"): + if peripherals.get(kind): + drivers.append(f"mspm0_{kind.lower()}.hpp") + drivers.append("ti_msp_dl_config.h") + if use_xrobot: + drivers.append("xrobot_main.hpp") + return [ + '#include "app_main.h"', + "", + '#include "libxr.hpp"', + *[f'#include "{header}"' for header in sorted(drivers)], + "", + "using namespace LibXR;", + ] + + +def _gpio_section(gpio: dict) -> list[str]: + """GPIO 对象:每个引脚一个 MSPM0GPIO;中断的注册和使能属于消费者(O1),不在这里生成。 + The GPIO objects: one MSPM0GPIO per pin; registering and enabling an interrupt belongs to + the consumer (O1) and is not generated here. + """ + if not gpio: + return [] + by_direction: dict[str, list[str]] = {} + for label, pin in gpio.items(): + by_direction.setdefault(pin.get("Direction", "OUTPUT").lower(), []).append( + f"{label} ({pin['Pin']})" + ) + summary = " and ".join( + f"{', '.join(pins)} as {direction} pins" for direction, pins in by_direction.items() + ) + lines = _comment( + f"GPIO: SysConfig configured {summary}. The interrupt edge comes from SysConfig; " + "RegisterCallback and EnableInterrupt belong to the consumer (a Module or the " + "User Code).", + INDENT, + ) + for label, pin in gpio.items(): + lines += layout(f"static MSPM0GPIO {label}", [pin["Port"], pin["PinMacro"], pin["IOMUX"]]) + return lines + + +def _uart_section(uarts: list, extend: set[str]) -> list[str]: + """UART 对象:发送走 DMA;MSPM0_UART_MAIN_INIT 接收走字节中断,extend 中的实例用 + MSPM0_UART_EXTEND_INIT 走循环 DMA 接收(见 _extend_uarts())。没有 DMA TX 的实例报错。 + The UART objects: DMA on the transmit side; MSPM0_UART_MAIN_INIT receives on byte + interrupts, and the instances in extend take MSPM0_UART_EXTEND_INIT with circular DMA + reception (see _extend_uarts()). An instance without DMA TX reports an error. + """ + if not uarts: + return [] + if extend: + heading = "TX with DMA; a UART Extend with DMA RX receives by circular DMA, the others " + heading += "on byte interrupts." + else: + heading = "TX with DMA; the receive side runs on byte interrupts." + lines = _comment(f"UART: {heading}", INDENT) + for name, instance, record, _channel in uarts: + if not record.get("DMA_TX"): + _fail( + tr( + f"{instance}: no DMA TX channel in the SysConfig project (UART > DMA " + "Configuration: Enable DMA TX with the TX trigger, which also gives the " + f"_CHAN_ID and {instance}_INST_DMA_TRIGGER macros, the latter " + "numbered _0, _1 when the instance has more than one trigger); the MSPM0 UART " + "driver constructs only with DMA TX. Enable it and run `libxr parse` again, " + "or drop the UART", + f"{instance}:SysConfig 工程里没有 DMA TX 通道(UART > DMA Configuration:" + f"启用 DMA TX 并选 TX 触发,SysConfig 随之生成 <通道>_CHAN_ID 和 " + f"{instance}_INST_DMA_TRIGGER 宏,实例有多个触发时带编号 _0、_1);MSPM0 的 " + "UART 驱动只支持带 DMA TX 的构造。请启用后重新运行 `libxr parse`,或删去" + "这个 UART", + ) + ) + settings = _instance_settings("UART", name, instance in extend) + tx_buf = f"{name}_tx_buf" + if instance in extend: + rx_buf = f"{name}_rx_dma_buf" + lines += layout( + f"static MSPM0UART {name}", + [ + f"MSPM0_UART_EXTEND_INIT({instance}", + record["DMA_TX"], + record["DMA_RX"], + tx_buf, + f"sizeof({tx_buf})", + str(settings["tx_queue_size"]), + rx_buf, + f"sizeof({rx_buf})", + f"{settings['rx_buffer_size']})", + ], + ) + continue + lines += layout( + f"static MSPM0UART {name}", + [ + f"MSPM0_UART_MAIN_INIT({instance}", + record["DMA_TX"], + tx_buf, + f"sizeof({tx_buf})", + str(settings["tx_queue_size"]), + f"{settings['rx_buffer_size']})", + ], + ) + return lines + + +def _i2c_section(i2cs: list, clocks: dict[str, str]) -> list[str]: + """I2C 对象:轮询驱动,缓冲是 DMA 传输的暂存区;时钟是所在电源域的 BUSCLK(clocks 中的 + 实参,见 _clocks())。 + The I2C objects: the polling driver whose buffer stages DMA transfers; the clock is the + BUSCLK of the power domain (the argument in clocks, see _clocks()). + """ + if not i2cs: + return [] + lines = [f"{INDENT}// I2C: polling; the buffer stages DMA transfers."] + for name, instance, _record, _channel in i2cs: + settings = _instance_settings("I2C", name) + lines += layout( + f"static MSPM0I2C {name}", + [ + f"MSPM0_I2C_INIT({instance}", + clocks[instance], + f"{name}_buf", + f"sizeof({name}_buf)", + f"{settings['dma_enable_min_size']})", + Braces(f"{instance}_BUS_SPEED_HZ"), + ], + ) + return lines + + +def _spi_section(spis: list, clocks: dict[str, str]) -> list[str]: + """SPI 对象:收发各一个 DMA 通道;时钟是 SysConfig 配给它的输入时钟(clocks 中的实参,见 + _clocks())。 + The SPI objects: one DMA channel for each direction; the clock is the input clock SysConfig + configured for it (the argument in clocks, see _clocks()). + """ + if not spis: + return [] + lines = [f"{INDENT}// SPI: DMA for transfers longer than the threshold."] + for name, instance, record, _channel in spis: + dma_rx, dma_tx = record.get("DMA_RX"), record.get("DMA_TX") + if not dma_rx or not dma_tx: + _fail( + tr( + f"{instance}: the MSPM0 SPI driver constructs only with one DMA channel for " + f"RX and one for TX, and the SysConfig project has RX {dma_rx or 'none'}, " + f"TX {dma_tx or 'none'}. Configure SPI > DMA Configuration (DMA Event 1/2 " + "triggers: one RX, one TX), run `libxr parse` again, or drop the SPI", + f"{instance}:MSPM0 的 SPI 驱动只支持收发各一个 DMA 通道的构造,而 SysConfig " + f"工程里 RX 为 {dma_rx or '没有'},TX 为 {dma_tx or '没有'}。请配置 SPI > DMA " + "Configuration(DMA Event 1/2 的触发:一个 RX,一个 TX)后重新运行 " + "`libxr parse`,或删去这个 SPI", + ) + ) + if str(record.get("DMA_RX_TRIGGER", "")).endswith("_RX_TIMEOUT"): + # 驱动按 SysConfig 配好的触发搬运每个接收字节;RX timeout 只在接收超时时请求 DMA, + # 计数的传输等不到结束。 + # The driver moves every received byte on the trigger SysConfig configured; RX + # timeout requests DMA only when reception times out, so a counted transfer never + # completes. + _fail( + tr( + f"{instance}: the DMA RX channel {dma_rx} is triggered by RX timeout; the " + "MSPM0 SPI driver needs the RX trigger (SPI > DMA Configuration: DMA Event " + "trigger DL_SPI_DMA_INTERRUPT_RX). Change it and run `libxr parse` again", + f"{instance}:DMA RX 通道 {dma_rx} 由 RX timeout 触发;MSPM0 的 SPI 驱动需要 RX " + "触发(SPI > DMA Configuration 的 DMA Event 触发选 DL_SPI_DMA_INTERRUPT_RX)。" + "请修改后重新运行 `libxr parse`", + ) + ) + settings = _instance_settings("SPI", name) + lines += layout( + f"static MSPM0SPI {name}", + [ + f"MSPM0_SPI_INIT({instance}", + clocks[instance], + dma_rx, + dma_tx, + f"{name}_rx_buf", + f"sizeof({name}_rx_buf)", + f"{name}_tx_buf", + f"sizeof({name}_tx_buf)", + f"{settings['dma_enable_min_size']})", + ], + ) + return lines + + +def _pwm_section(pwms: list) -> list[str]: + """PWM 对象:定时器的每个通道一个 MSPM0PWM;libxr_config.yaml 给出频率(正整数)时对该 + 通道调用 SetConfig,null 表示不调用。同一定时器的通道共用周期,频率不同时最后一次 SetConfig + 生效,给出警告。 + The PWM objects: one MSPM0PWM per channel of the timer; SetConfig is called on a channel + when libxr_config.yaml gives its frequency (a positive integer), and null means no call. + The channels of one timer share one period, so differing frequencies are warned about: the + last SetConfig wins. + """ + if not pwms: + return [] + lines = [ + f"{INDENT}// PWM: the channels of one timer share one period, which SysConfig configures.", + ] + frequencies: dict[str, dict[str, int]] = {} + for name, instance, _record, channel in pwms: + lines += layout(f"static MSPM0PWM {name}", [f"MSPM0_PWM_CH({instance}, {channel})"]) + settings = _settings_of("PWM", name) + frequency = settings.setdefault("frequency", None) + if frequency is not None: + frequency = stm32._integer(f"PWM.{name}.frequency", frequency) + frequencies.setdefault(instance, {})[name] = frequency + lines += layout(f"{name}.SetConfig", [Braces(str(frequency))]) + for instance, channels in frequencies.items(): + if len(set(channels.values())) > 1: + listed = ", ".join(f"{name} {value}" for name, value in channels.items()) + last = list(channels)[-1] + logging.warning( + tr( + f"libxr_config.yaml: the PWM channels of {instance} share one period but " + f"set different frequencies ({listed}); the last SetConfig ({last}) wins", + f"libxr_config.yaml:{instance} 的 PWM 通道共用一个周期,却设了不同的频率" + f"({listed});以最后一次 SetConfig({last})为准", + ) + ) + return lines + + +def _terminal_source(uarts: list) -> str: + """生效的终端设备,与 STM32 的约定相同:空值表示不生成终端,none 也一样(写法保持不变)。 + 只有 libxr_config.yaml 里还没有 terminal_source 这个键时(第一次生成),才选第一个 UART 并 + 写进文件;之后用户清空或写成 none,重新生成都不会再打开终端。 + The effective terminal device, with the STM32 convention: an empty value means no terminal, + and so does none (kept as written). Only when libxr_config.yaml has no terminal_source key + yet (the first generation) is the first UART chosen and written to the file; once the user + empties it or writes none, regenerating never turns the terminal back on. + """ + document = stm32.libxr_config_document + source = stm32._text("terminal_source", stm32.libxr_settings.get("terminal_source")) + if source == "" and uarts and (document is None or "terminal_source" not in document): + source = uarts[0][0] + stm32.libxr_settings["terminal_source"] = source + logging.info( + tr( + f"terminal_source is not set; using {source}", + f"未设置 terminal_source;使用 {source}", + ) + ) + source = source.lower() + return "" if source == "none" else source + + +def _terminal_section(peripherals: dict) -> list[str]: + """终端:terminal_source 指定的 UART 作为标准输入输出,RamFS 和 Terminal 由软件定时器任务 + 每 10 ms 运行一次(terminal_source 的取值见 _terminal_source())。 + The terminal: the UART named by terminal_source becomes the standard I/O, and the RamFS and + Terminal objects run in a software timer task every 10 ms (see _terminal_source() for the + values of terminal_source). + """ + uarts = peripherals.get("UART", []) + source = _terminal_source(uarts) + if not source: + return [] + names = {name for name, _instance, _record, _channel in uarts} + if source not in names: + logging.warning( + tr( + f"terminal_source '{source}' is not one of the generated UARTs " + f"({', '.join(sorted(names)) or 'none'}); the terminal is not initialized", + f"terminal_source '{source}' 不是生成的 UART 之一" + f"({'、'.join(sorted(names)) or '没有'});终端不初始化", + ) + ) + return [] + term_config = stm32._settings("Terminal") + params = [ + stm32._integer(f"Terminal.{key}", _setting(term_config, key, default)) + for key, default in ( + ("read_buff_size", 32), + ("max_line_size", 32), + ("max_arg_number", 5), + ("max_history_number", 5), + ) + ] + terminal_type = f"Terminal<{', '.join(map(str, params))}>" + stm32._register_device("ramfs", "RamFS") + stm32._register_device("terminal", terminal_type) + return [ + f"{INDENT}// Terminal on {source}", + f"{INDENT}STDIO::read_ = {source}.read_port_;", + f"{INDENT}STDIO::write_ = {source}.write_port_;", + f'{INDENT}static RamFS ramfs("XRobot");', + f"{INDENT}static {terminal_type} terminal(ramfs);", + f"{INDENT}static auto terminal_task = Timer::CreateTask(terminal.TaskFun, &terminal, 10);", + f"{INDENT}Timer::Add(terminal_task);", + f"{INDENT}Timer::Start(terminal_task);", + ] + + +def _sections( + peripherals: dict, gpio: dict, use_xrobot: bool, clocks: dict[str, str], extend: set[str] +) -> list[str]: + """app_main 函数体的各段:时基、GPIO、UART、I2C、SPI、PWM、终端和登记。 + The sections of the app_main body: timebase, GPIO, UART, I2C, SPI, PWM, terminal and the + registrations. + """ + blocks = [ + [ + f"{INDENT}// Timebase and platform", + f"{INDENT}static MSPM0Timebase timebase;", + f"{INDENT}PlatformInit();", + f"{INDENT}static MSPM0PowerManager power_manager;", + ], + _gpio_section(gpio), + _uart_section(peripherals.get("UART", []), extend), + _i2c_section(peripherals.get("I2C", []), clocks), + _spi_section(peripherals.get("SPI", []), clocks), + _pwm_section(peripherals.get("PWM", [])), + _terminal_section(peripherals), + ] + registrations = stm32.generate_xrobot_registrations() if use_xrobot else [] + if registrations: + blocks.append([f"{INDENT}// Hardware registration"] + registrations) + return stm32.join_blocks([block for block in blocks if block]) + + +def _check_gpio_name(label: str) -> None: + """GPIO 对象名(SysConfig 的引脚名)必须是合法、唯一、非关键字且非保留的 C++ 标识符;不满足 + 时报错退出,说明在 SysConfig 中改名。 + A GPIO object name (the pin name of SysConfig) must be a valid, unique C++ identifier that + is neither a keyword nor reserved; otherwise generation exits with an error that says to + rename the pin in SysConfig. + """ + if ( + not IDENTIFIER.fullmatch(label) + or label in stm32.CPP_KEYWORDS + or "__" in label + or re.match(r"_[A-Z]", label) + ): + problem = ("is not a valid C++ identifier", "不是可用的 C++ 标识符") + elif label in stm32.registered_devices: + problem = ( + f"is already used by the {stm32.registered_devices[label]} object", + f"已被 {stm32.registered_devices[label]} 对象使用", + ) + else: + return + _fail( + tr( + f"GPIO name {label} {problem[0]}; rename the pin in SysConfig", + f"GPIO 名字 {label} {problem[1]};请在 SysConfig 中给引脚改名", + ) + ) + + +def generate_full_code(project_data: dict, use_xrobot: bool, existing_code: str) -> str: + """生成 app_main 源文件的完整内容,并填回已有代码中 User Code 区域的内容。 + Generate the full content of the app_main source file and put back the User Code bodies of + the existing code. + """ + peripherals = _peripherals(project_data) + gpio = project_data.get("GPIO", {}) + for label in gpio: + _check_gpio_name(str(label)) + stm32._register_device(label, "GPIO") + uarts = peripherals.get("UART", []) + extend = _extend_uarts(project_data, uarts) + default_3 = ( + [] + if use_xrobot + else [ + f"{INDENT}while (true)", + f"{INDENT}{{", + f"{INDENT * 2}Thread::Sleep(UINT32_MAX);", + f"{INDENT}}}", + ] + ) + lines = _notice(project_data) + lines += _includes(project_data, use_xrobot) + lines += ["", "/* User Code Begin 1 */", "/* User Code End 1 */"] + clock_lines, clocks = _clocks(project_data) + if clock_lines: + lines += [""] + clock_lines + defines = _uart_dma_defines(uarts, extend) + if defines: + lines += [""] + defines + buffers = _buffers(peripherals, extend) + if buffers: + lines += [""] + buffers + lines += ["", 'extern "C" void app_main(void)', "{"] + lines += [f"{INDENT}/* User Code Begin 2 */", f"{INDENT}/* User Code End 2 */", ""] + lines += _sections(peripherals, gpio, use_xrobot, clocks, extend) + lines += ["", f"{INDENT}/* User Code Begin 3 */"] + default_3 + lines.append(f"{INDENT}/* User Code End 3 */") + if use_xrobot: + lines.append(f"{INDENT}XROBOT_MAIN();") + lines.append("}") + generated = "\n".join(lines) + "\n" + # 与生成代码里其他名字(timebase、ramfs、terminal、时钟常量……)相同的 GPIO 名字。 + # GPIO names equal to another name of the generated code (timebase, ramfs, terminal, the + # clock constants, ...). + clashes = stm32.overused_names(gpio, generated, use_xrobot) + if clashes: + _fail( + tr( + f"The generated code uses these GPIO names for something else: " + f"{', '.join(clashes)}; rename the pins in SysConfig", + f"生成代码把这些 GPIO 名字用于别处:{'、'.join(clashes)};请在 SysConfig 中给" + "引脚改名", + ) + ) + return stm32._preserve_generated_regions(existing_code, generated) + + +# 手写文件被替换时最多列出的行数。 +# At most this many lines are listed when a hand-written file is replaced. +HAND_WRITTEN_LINES = 20 + + +def _warn_hand_written(path: str, existing_code: str, generated_code: str) -> None: + """已有的 app_main 不是 libxr gen 生成的(前两行没有生成说明)时给出警告,列出 User Code + 区域之外、新文件里不再有的行:这些行会被替换。与 STM32 一样照常生成,不拒绝。 + Warn when the existing app_main was not generated by libxr gen (no generated-file notice in + its first two lines) and list the lines outside the User Code regions that the new file no + longer has: they are replaced. Generation goes on as on STM32; it is not refused. + """ + if not existing_code.strip() or GENERATED_NOTICE in existing_code.splitlines()[:2]: + return + source = existing_code.encode("utf-8", errors="surrogateescape") + spans = [region.body_span for region in CppDocument.parse(existing_code).user_regions()] + outside, start = [], 0 + for span in sorted(spans, key=lambda span: span.start): + outside.append(source[start : span.start]) + start = span.end + outside.append(source[start:]) + kept = {line.strip() for line in generated_code.splitlines()} + replaced = [ + line.strip() + for part in outside + for line in part.decode("utf-8", errors="replace").splitlines() + if line.strip() and line.strip() not in kept + ] + if not replaced: + return + listed = "\n".join(f" {line}" for line in replaced[:HAND_WRITTEN_LINES]) + more = len(replaced) - HAND_WRITTEN_LINES + listed += tr(f"\n ... and {more} more", f"\n ……另有 {more} 行") if more > 0 else "" + logging.warning( + tr( + f"{path} was not generated by libxr gen; everything outside the User Code regions " + f"is regenerated, and these {len(replaced)} line(s) of it are replaced; code to " + f"keep belongs in the User Code regions:\n{listed}", + f"{path} 不是 libxr gen 生成的;User Code 区域之外的内容全部重新生成,以下 " + f"{len(replaced)} 行被替换;要保留的代码应放在 User Code 区域中:\n{listed}", + ) + ) + + +def initialize_registry(use_xrobot: bool) -> None: + """清空生成对象的登记表和本次生成用到的头文件,使同一次进程中的下一次生成不带上次的对象; + use_xrobot 为真时先登记 power_manager(PowerManager)。 + Clear the registry of generated objects and the headers of this generation, so the next + generation in the same process carries no objects over; with use_xrobot, power_manager + (PowerManager) is registered first. + """ + stm32.registered_devices.clear() + stm32.registered_origins.clear() + stm32.used_headers.clear() + if use_xrobot: + stm32._register_device("power_manager", "PowerManager", tr("power manager", "电源管理器")) + + +def reset_settings() -> None: + """把生效的设置恢复为 MSPM0 的默认集合:STM32 专用的段剔除,UART 和 PWM 段补上。 + Restore the effective settings to the MSPM0 default set: the STM32-only sections removed + and the UART and PWM sections added. + """ + stm32.reset_settings() + for key in STM32_ONLY_SECTIONS: + stm32.libxr_settings.pop(key, None) + stm32._settings("UART") + stm32._settings("PWM") + + +def generate( + input_path: str, output_path: str, use_xrobot: bool = False, libxr_config: str = "" +) -> None: + """生成 output_path 指定的 app_main 源文件,以及同一目录中的 app_main.h 和 libxr_config.yaml。 + Generate the app_main source file output_path, and app_main.h and libxr_config.yaml in the + same directory. + + 每次从默认设置开始,合并 libxr_config(libxr_config.yaml 的路径或 URL,为空时读取输出目录中 + 的文件)。全部文件先在内存中生成,没有错误时才写出,并且只写内容有变化的文件。已有输出文件 + 中 User Code 区域的内容被保留。出错时记录错误(调试日志另记调用栈)并以状态 1 退出。 + Every run starts from the default settings and merges libxr_config, the path or URL of + libxr_config.yaml, or the file in the output directory when empty. All files are generated + in memory first and written only when nothing failed, and only the files whose content + changed are written. The User Code bodies of an existing output file are kept. An error is + logged, with the traceback at debug level, and exits with status 1. + """ + try: + output_dir = os.path.dirname(output_path) or os.curdir + + reset_settings() + project_data = stm32.load_configuration(input_path) + stm32.load_libxr_config(output_dir, libxr_config) + # load_configuration 又写入了 SYSTEM(MSPM0 只有裸机),再剔除一次。 + # load_configuration has written SYSTEM again (MSPM0 is bare metal only); remove it + # once more. + stm32.libxr_settings.pop("SYSTEM", None) + stm32._settings("UART") + stm32._settings("PWM") + initialize_registry(use_xrobot) + + existing_code = "" + if os.path.exists(output_path): + with open(output_path, encoding="utf-8") as file: + existing_code = file.read() + stm32.reject_user_hardware_container(existing_code, use_xrobot) + if use_xrobot: + stm32.reject_user_xrobot_main(existing_code) + existing_code = stm32._without_default_loop(existing_code) + + code = generate_full_code(project_data, use_xrobot, existing_code) + _warn_hand_written(output_path, existing_code, code) + files = { + os.path.basename(output_path): code, + "app_main.h": stm32.APP_MAIN_HEADER, + "libxr_config.yaml": stm32.libxr_config_text(), + } + + os.makedirs(output_dir, exist_ok=True) + written, unchanged = [], [] + for name, text in files.items(): + path = os.path.join(output_dir, name) + (written if stm32._write_if_changed(path, text) else unchanged).append(name) + stm32._report_files(output_dir, written, unchanged, []) + except SystemExit: + raise + except Exception as error: + logging.error(tr(f"Generation failed: {str(error)}", f"生成失败:{str(error)}")) + logging.debug(tr("Traceback:", "调用栈:"), exc_info=True) + sys.exit(1) diff --git a/src/libxr/generator_code_stm32.py b/src/libxr/generator_code_stm32.py index 81b0b97..c4a1746 100644 --- a/src/libxr/generator_code_stm32.py +++ b/src/libxr/generator_code_stm32.py @@ -720,6 +720,20 @@ def _gpio_object_name(port: str, gpio_data: dict) -> str: return _sanitize_cpp_identifier(gpio_data.get("Label", "") or port) +def overused_names(names, generated_code: str, use_xrobot: bool) -> list[str]: + """names 中在生成代码里出现次数超过声明(加 --xrobot 时的 XR_REGISTER 行)的名字:它们与 + 生成代码用到的其他名字相同。注释和字符串里的不算;各平台的 GPIO 名字检查共用。 + The names among names that occur in the generated code more often than their declaration + (plus the XR_REGISTER line with --xrobot): they equal another name the generated code uses. + Comments and strings do not count; the GPIO name checks of every platform share this. + """ + counts: dict[str, int] = {} + for occurrence in identifier_occurrences(generated_code): + counts[occurrence.text] = counts.get(occurrence.text, 0) + 1 + expected_uses = 2 if use_xrobot else 1 + return [name for name in names if counts.get(name, 0) > expected_uses] + + def check_gpio_names(project_data: dict, generated_code: str, use_xrobot: bool) -> None: """拒绝生成的 app_main 无法声明的 GPIO 对象名。 Reject GPIO object names that the generated app_main cannot declare. @@ -1102,7 +1116,8 @@ def _usb_settings(instance: str) -> tuple[str, dict] | None: enable 的默认值由 _default_usb_enables() 先行写入,仍然缺少时为 false;未启用的实例不再补其他 设置。已启用的实例按固定顺序补上缺少的设置:包大小、缓冲区和 FIFO 大小、dma_section、cdc 列表 - (每路 CDC 一项,含 tx_fifo_size、rx_fifo_size 和 queue_size,默认一路),以及描述符(默认 + (每路 CDC 一项,含 tx_fifo_size、rx_fifo_size、queue_size 和可选的 interface,默认一路), + 以及描述符(默认 1d50:6199 / 0x0100 / "XRUSB-DEMO-",1d50:6199 的分配记录见 https://github.com/openmoko/openmoko-usb-oui/commit/27f3846d77e0d0d10271b809b831f70040c6197a)。 旧版的 cdc_tx_fifo_size、cdc_rx_fifo_size 和 cdc_queue_size 转换为 cdc 的一项,并在原来的位置 @@ -1111,7 +1126,8 @@ def _usb_settings(instance: str) -> tuple[str, dict] | None: _default_usb_enables() writes the default enable first; when it is still missing it is false, and a disabled instance gets no other setting. An enabled instance gets its missing settings in a fixed order: packet size, buffer and FIFO sizes, dma_section, the cdc list (one - item per CDC with tx_fifo_size, rx_fifo_size and queue_size, one CDC by default), and the + item per CDC with tx_fifo_size, rx_fifo_size, queue_size and an optional interface, one CDC + by default), and the descriptor (default 1d50:6199 / 0x0100 / "XRUSB-DEMO-"; see the link above for the allocation of 1d50:6199). The earlier cdc_tx_fifo_size, cdc_rx_fifo_size and cdc_queue_size become one item of cdc, written back where they were. An ep0_packet_size @@ -1258,6 +1274,9 @@ def _check_cdc(prefix: str, cfg: dict, is_otg: bool) -> None: Check the cdc list and give each item its defaults: a non-empty list whose items are mappings with positive integer values. + 可选的 interface 是字符串,不补默认值。 + The optional interface is a string and gets no default. + CDC 的路数不得超过端点号 EP1 到 EP15 能容纳的路数;OTG 设备的接收 FIFO 为 EP0 和每路 CDC 的 OUT 端点各留 64 字节。 The number of CDCs may not exceed what the endpoint numbers EP1 to EP15 hold; the receive @@ -1280,6 +1299,8 @@ def _check_cdc(prefix: str, cfg: dict, is_otg: bool) -> None: raise _invalid_setting(f"{key}[{index}]", item, "a mapping", "映射") for name, default in _CDC_DEFAULTS.items(): _integer(f"{key}[{index}].{name}", item.setdefault(name, default)) + if "interface" in item: + item["interface"] = _text(f"{key}[{index}].interface", item["interface"]) if len(cdc) > _MAX_CDC: raise LibXRConfigError( tr( @@ -1486,13 +1507,16 @@ def _usb_buffer_plan(is_otg: bool, cdc_count: int, ep0: int, tx: int, rx: int) - def _cdc_items(cfg: dict) -> list[dict]: - """USB 设备的 CDC 串口设置列表:每个 CDC 一个映射,含 tx_fifo_size、rx_fifo_size 和 - queue_size,均为整数。 - The list of CDC serial port settings of a USB device: one mapping per CDC with - tx_fifo_size, rx_fifo_size and queue_size, all integers. + """USB 设备的 CDC 串口设置列表:每个 CDC 一个映射,含整数 tx_fifo_size、rx_fifo_size 和 + queue_size,以及接口名 interface(未设置时为空字符串)。 + The list of CDC serial port settings of a USB device: one mapping per CDC with the integers + tx_fifo_size, rx_fifo_size and queue_size and the interface name interface (an empty string + when it is not set). """ return [ - {name: _integer(f"cdc.{name}", item[name]) for name in _CDC_DEFAULTS} for item in cfg["cdc"] + {name: _integer(f"cdc.{name}", item[name]) for name in _CDC_DEFAULTS} + | {"interface": _text("cdc.interface", item.get("interface"))} + for item in cfg["cdc"] ] @@ -2163,6 +2187,11 @@ def endpoint(number: int) -> str: cdc_names.append(cdc_name) data_in = 2 * index + 1 data_out = index + 1 if is_otg else data_in + # 设置了 interface 时它同时作为控制接口和数据接口的名字;主机按它区分 VID:PID 相同的 + # 各路 CDC。 + # A set interface names both the control and the data interface; the host tells the + # CDCs that share VID:PID apart by it. + interface = cdc["interface"].replace('"', '\\"') lines += layout( f"static USB::CDCUart {cdc_name}", [ @@ -2172,7 +2201,8 @@ def endpoint(number: int) -> str: str(cdc["rx_fifo_size"]), str(cdc["tx_fifo_size"]), str(cdc["queue_size"]), - ], + ] + + ([f'"{interface}"'] * 2 if interface else []), ) def buffer(suffix: str) -> "str | Braces": diff --git a/src/libxr/peripheral_analyzer_hpm.py b/src/libxr/peripheral_analyzer_hpm.py new file mode 100644 index 0000000..93a588c --- /dev/null +++ b/src/libxr/peripheral_analyzer_hpm.py @@ -0,0 +1,425 @@ +"""libxr parse 对 HPM 工程:解析 Pinmux Tool 工程并写出工程 YAML。 +libxr parse for an HPM project: parse the Pinmux Tool project and write the project YAML. + +工程是 app.yaml 加 boards/ 下唯一的 .hpmpc。已选信号取根目录 main.c 的 main 在预处理条件外 +调用的 pinmux 函数(O2:条件编译里的不算,例如 rmcs 的 JTAG 共用引脚),从 .hpmpc 的 selectPins +读出引脚和信号,化成 hpm_iomux.h 的宏名后按 SoC 封装的布局核对(封装上没有的焊盘、焊盘不能 +承载的信号给出警告)。读 .hpmpc、找 main 的调用和合并各函数的选择与 libxr pins 共用 +pin_project 的实现。GPIO 的复用(GPIO.A.B[11])算引脚自己的普通用法:对象默认按引脚命名 +(pb11,用户 2026-10-07 确认),libxr_config 可改名;其余外设按 recognize_hpm 识别,LibXR 有 +驱动的(I2C;以及 SoC 没有 PWM 外设时 GPTMR 比较器上的 PWM——判定来自芯片引脚数据里有没有 PWM +信号,HPM5301 没有、HPM5361 有)进入 Peripherals,其余(UART、SPI、CAN、ACMP、只有捕获输入的 +GPTMR、有 PWM 外设的 SoC 上的 GPTMR……)记入 Other,只作展示。 +The project is app.yaml plus the single .hpmpc under boards/. The selected signals come from +the pinmux functions that main in the root main.c calls outside preprocessor conditions (O2: a +call inside a conditional does not count, such as the rmcs JTAG shared pins), read from the +selectPins of the .hpmpc and checked, as the macro names of hpm_iomux.h, against the layout of +the SoC package (a pad the package does not have, or a signal its pad cannot carry, is warned +about). Reading the .hpmpc, finding the calls of main and merging the selections of the +functions share the pin_project implementation with libxr pins. A GPIO mux (GPIO.A.B[11]) +counts as the pin's own plain use: the object defaults to the pin name (pb11, confirmed by the +user 2026-10-07) and libxr_config renames it; the other peripherals go through recognize_hpm, +and the ones LibXR has drivers for (I2C; a PWM on a GPTMR comparator when the SoC has no PWM +peripheral — decided by whether the chip's pin data has any PWM signal, HPM5301 has none and +HPM5361 has 20) enter Peripherals while the rest (UART, SPI, CAN, ACMP, a GPTMR with capture +inputs only, a GPTMR on a SoC with a PWM peripheral, ...) are recorded under Other for display +only. + +UART、SPI 和 MCAN 的驱动还压着没合(生成器认出它们,但不生成对象)。 +The UART, SPI and MCAN drivers are still held back (the generator recognizes them but +generates no objects). +""" + +from __future__ import annotations + +import logging +import os +import re +import sys +from pathlib import Path + +import yaml +from xr_syntax.i18n import tr + +from libxr import pin_layout, pin_project + + +def _fail(message: str): + """记录错误并以状态 1 退出。 + Log an error and exit with status 1. + """ + logging.error(message) + sys.exit(1) + + +def _gpio_entry(pad: str, pad_ctls: dict, gpiom: dict) -> dict | None: + """一个 GPIO 引脚的工程记录:端口字母、引脚号、方向(gpiom 写下的)和 pad 设置;焊盘名不是 + P<字母><数字> 时为 None。 + The project record of one GPIO pin: the port letter, the pin line, the direction the gpiom + manager wrote and the pad settings; None when the pad name is not P. + """ + port_line = pin_layout.gpio_port_and_line(pad) + if port_line is None or not re.fullmatch(r"P[A-Z]\d+", pad): + return None + entry = { + "Pad": pad, + "Port": port_line[0], + "Line": int(port_line[1]), + "PadCtls": pad_ctls or {}, + } + direction = str(gpiom.get("direction") or "") + if direction == "1": + entry["Direction"] = "OUTPUT" + elif direction == "0": + entry["Direction"] = "INPUT" + return entry + + +def _instance(signal: str) -> str: + """信号所属的外设实例(UART0_TXD 为 UART0);GPIO 的复用都算 GPIO。 + The peripheral instance a signal belongs to (UART0 for UART0_TXD); every GPIO mux counts + as GPIO. + """ + recognized = pin_layout.recognize_hpm(signal) + return recognized[0] if recognized else "GPIO" if signal.startswith("GPIO_") else signal + + +def _project(directory: str) -> tuple[Path, Path, pin_project.HpmProject]: + """directory 中唯一的 .hpmpc、根目录 main.c 和读出的工程;缺少或读不出时报错退出。SoC 和 + 封装必须在芯片引脚数据里(SoC 名不区分大小写)。 + The single .hpmpc of directory, the root main.c and the project read from it; a missing or + unreadable one logs an error and exits. The SoC and the package must be in the chip pin + data (the SoC name in any case). + """ + root = Path(directory) + hpmpcs = sorted(root.glob("boards/*/*.hpmpc")) + if len(hpmpcs) != 1: + names = ", ".join(path.as_posix() for path in hpmpcs) or tr("none found", "没有找到") + _fail( + tr( + f"{directory} must hold exactly one .hpmpc under boards/ ({names})", + f"{directory} 的 boards/ 下必须有且只有一个 .hpmpc({names})", + ) + ) + main_c = root / "main.c" + if not main_c.is_file(): + _fail( + tr( + f"{directory}: no root main.c; the HPM main sits at the project root", + f"{directory}:根目录没有 main.c;HPM 的 main 在工程根目录", + ) + ) + try: + project = pin_project.read_hpmpc(hpmpcs[0]) + except ValueError as error: + _fail(str(error)) + if project.soc is None: + _fail(tr(f"{hpmpcs[0]} does not name a SoC", f"{hpmpcs[0]} 没有给出 SoC")) + return hpmpcs[0], main_c, project + + +def _layout(project: pin_project.HpmProject) -> pin_layout.PinLayout: + """工程 SoC 封装的布局;SoC 或封装不在引脚数据里时报错退出。 + The layout of the project's SoC package; a SoC or package missing from the pin data logs an + error and exits. + """ + try: + return pin_layout.layout_hpm(project.soc, project.package) + except ValueError as error: + _fail(f"{project.path}: {error}") + + +def _report_calls( + hpmpc: Path, + main_c: Path, + project: pin_project.HpmProject, + called: list[str], + conditional: list[str], + active: list[str], +) -> None: + """main.c 调用情况的警告:条件编译里调用的函数、没有被调用的函数、调用全在条件里时读不到 + 引脚,以及完全没有调用时的 init_bsp_pins 兜底。 + The warnings about the calls of main.c: the functions called inside a conditional, the + functions not called, no pin read when every call sits inside a conditional, and the + init_bsp_pins fallback when there is no call at all. + """ + for name in conditional: + logging.warning( + tr( + f"{main_c}: {name}() is called inside a preprocessor condition; its pins are not " + "read (the generated objects must not depend on build options)", + f"{main_c}:{name}() 在预处理条件里调用;不读它的引脚(生成的对象不能依赖编译选项)", + ) + ) + if not called and conditional: + # 调用全在预处理条件里:不退回 init_bsp_pins(O2)。 + # Every call sits inside a condition: no init_bsp_pins fallback (O2). + logging.warning( + tr( + f"{main_c}: main calls pinmux functions only inside preprocessor conditions; no " + "pin is read", + f"{main_c}:main 只在预处理条件里调用 pinmux 函数;读不到引脚", + ) + ) + return + if not called: + detail = ( + tr("using the init_bsp_pins fallback", "退回 init_bsp_pins") + if active + else tr( + "no init_bsp_pins exists, so no pin is read", "没有 init_bsp_pins 可退,读不到引脚" + ) + ) + logging.warning( + tr( + f"{main_c}: main calls no pinmux function ({detail}); a pin function called " + "from elsewhere (board.c, a helper) is not visible", + f"{main_c}:main 没有调用任何 pinmux 函数({detail});从别处(board.c、辅助函数)" + "调用的引脚函数看不到", + ) + ) + return + for name in project.functions: + if name not in called and name not in conditional: + logging.warning( + tr( + f"{hpmpc}: the pinmux function {name}() is not called by main in " + f"{main_c.name}; its pins are not read", + f"{hpmpc}:pinmux 函数 {name}() 没有被 {main_c.name} 的 main 调用;不读它的引脚", + ) + ) + + +def parse_project(directory: str, output: str | None = None, summary: bool = True) -> None: + """解析 directory 中的 HPM 工程并写出 YAML;summary 为真时打印摘要。 + Parse the HPM project in directory and write the YAML; print a summary when summary is + set. + + 输出路径默认为该目录下的 .config.yaml。调用方(libxr parse)已确认目录存在且是 HPM 工程。 + The output defaults to .config.yaml in that directory. The caller, libxr parse, has + checked that the directory exists and is an HPM project. + """ + root = Path(directory) + hpmpc, main_c, project = _project(directory) + layout = _layout(project) + called, conditional = pin_project.hpm_main_calls(main_c, project.functions) + active = pin_project.hpm_active_functions(main_c, project.functions) + _report_calls(hpmpc, main_c, project, called, conditional, active) + selections, conflicts = pin_project.hpm_selections(project, active) + for conflict in conflicts: + logging.warning(pin_project.hpm_conflict_message(hpmpc, *conflict)) + + pins = {pin.name: pin for pin in layout.pins} + known = {_instance(signal) for pin in layout.pins for signal in pin.signals} + gpio: dict[str, dict] = {} + peripherals: dict[str, dict] = {} + other: dict[str, dict] = {} + # GPTMR 只在 SoC 没有 PWM 外设时生成对象(HPM5301);有 PWM 外设的(HPM5361)归入 Other + # 只作展示。判定来自芯片引脚数据里有没有 PWM 信号。 + # A GPTMR generates objects only when the SoC has no PWM peripheral (HPM5301); on a SoC + # with one (HPM5361) it lands in Other for display only. The decision comes from whether + # the chip's pin data has any PWM signal. + soc_has_pwm = pin_layout.hpm_soc_has_pwm(project.soc) + for selection in selections: + pad, signal, pad_ctls = selection["pad"], selection["signal"], selection["padCtls"] + canonical = pin_project.hpm_canonical(signal) + pin = pins.get(pad) + if pin is None: + logging.warning( + tr( + f"{hpmpc}: {pad} ({signal}) is not a pin of {project.soc} {layout.package}; " + "skipped", + f"{hpmpc}:{pad}({signal})不是 {project.soc} {layout.package} 的引脚;跳过", + ) + ) + continue + # 只核对引脚数据认识的外设:工具自己的伪信号(USBPHY.A.USB_DM 等,hpm_iomux.h 和数据 + # 手册的引脚表里都没有)不算错。 + # Only peripherals the pin data knows are checked: the tool's own pseudo signals + # (USBPHY.A.USB_DM and the like, in neither hpm_iomux.h nor the datasheet pin table) + # are no error. + if canonical not in pin.signals and _instance(canonical) in known: + logging.warning( + tr( + f"{hpmpc}: {pad} cannot carry {canonical} ({signal}) on {project.soc}", + f"{hpmpc}:{project.soc} 的 {pad} 不能承载 {canonical}({signal})", + ) + ) + if canonical.startswith("GPIO_"): + controller = pin_project.hpm_gpio_controller(selection["gpiom"]) + entry = _gpio_entry(pad, pad_ctls, selection["gpiom"]) + if entry is None: + logging.warning( + tr( + f"{hpmpc}: {pad} does not look like a GPIO pad; skipped", + f"{hpmpc}:{pad} 不是 GPIO 引脚的写法;跳过", + ) + ) + continue + if controller != pin_project.HPM_GPIO0_CONTROLLER: + logging.warning( + tr( + f"{hpmpc}: {pad} is assigned to GPIO controller {controller}, not the " + "GPIO0 HPMGPIO drives; no object is generated for it", + f"{hpmpc}:{pad} 分给了 GPIO 控制器 {controller},不是 HPMGPIO 驱动的 " + "GPIO0;不为它生成对象", + ) + ) + continue + gpio[f"p{entry['Port'].lower()}{entry['Line']}"] = entry + continue + recognized = pin_layout.recognize_hpm(canonical) + instance, kind, function = recognized or (canonical, "OTHER", "PIN") + comparator = re.fullmatch(r"COMP_(\d+)", function) + if kind == "I2C" or (kind == "GPTMR" and comparator and not soc_has_pwm): + section = "I2C" if kind == "I2C" else "PWM" + record = peripherals.setdefault(section, {}).setdefault( + instance, {"Peripheral": instance, "Pins": {}} + ) + if comparator: + # GPTMR 的比较器输出:序号是 PWM 通道(pwm_gptmr0_ch1)。 + # A GPTMR comparator output: its index is the PWM channel (pwm_gptmr0_ch1). + index = int(comparator.group(1)) + channels = record.setdefault("Channels", []) + taken = next((channel for channel in channels if channel["Index"] == index), None) + if taken is None: + channels.append({"Index": index, "Pad": pad}) + elif taken["Pad"] != pad: + # 同一比较器只有一个输出对象,第一个焊盘为准。 + # One comparator has one output object; the first pad wins. + logging.warning( + tr( + f"{hpmpc}: {instance} {function} is selected on both {taken['Pad']} " + f"and {pad}; the PWM object drives {taken['Pad']}", + f"{hpmpc}:{instance} 的 {function} 同时选在 {taken['Pad']} 和 {pad};" + f"PWM 对象以 {taken['Pad']} 为准", + ) + ) + continue + elif function in record["Pins"] and record["Pins"][function] != pad: + logging.warning( + tr( + f"{hpmpc}: {instance} {function} is assigned to both " + f"{record['Pins'][function]} and {pad}; the later one wins", + f"{hpmpc}:{instance} 的 {function} 同时分给了 " + f"{record['Pins'][function]} 和 {pad};以后一个为准", + ) + ) + record["Pins"][function] = pad + else: + # LibXR 还没有驱动的信号(UART、SPI、CAN、ACMP、GPTMR 的捕获输入……)只作展示。 + # Signals without a LibXR driver yet (UART, SPI, CAN, ACMP, the capture inputs of + # a GPTMR, ...) are display-only. + record = other.setdefault( + instance, {"Module": kind, "Peripheral": instance, "Pins": {}} + ) + record["Pins"][function] = pad + + _check_peripherals(hpmpc, peripherals, other) + data = { + "Platform": "hpm", + "Hpmpc": hpmpc.relative_to(root).as_posix(), + "Mcu": {"Family": "HPM", "Type": project.soc}, + "Package": layout.package, + "MainFunctions": active, + "MainCalls": list(called), + "GPIO": gpio, + "Peripherals": {**peripherals, "Other": other} if other else peripherals, + } + + output_path = output or os.path.join(directory, ".config.yaml") + if not gpio and not peripherals and not other: + logging.warning( + tr( + "the active pinmux functions select no GPIO or peripheral pin", + "活动的 pinmux 函数没有选择任何 GPIO 或外设引脚", + ) + ) + _save_to_yaml(data, str(output_path)) + if summary: + _print_summary(data) + + +def _check_peripherals(hpmpc: Path, peripherals: dict, other: dict) -> None: + """生成前的一致性检查:I2C 必须有 SCL 和 SDA,缺一个时不生成,改记入 Other;生成 PWM 的 + GPTMR 同时选了捕获输入(记在 Other)时给出警告(捕获不生成对象)。 + The consistency checks before generation: an I2C needs both SCL and SDA, and one missing + either is not generated but recorded under Other; a GPTMR that generates PWM and also + selects capture inputs (recorded under Other) is warned about (capture generates no + object). + """ + for instance, record in list(peripherals.get("I2C", {}).items()): + missing = [name for name in ("SCL", "SDA") if name not in record["Pins"]] + if missing: + logging.warning( + tr( + f"{hpmpc}: {instance} has no {' or '.join(missing)} pin; no I2C object is " + "generated for it", + f"{hpmpc}:{instance} 没有 {'、'.join(missing)} 引脚;不为它生成 I2C 对象", + ) + ) + del peripherals["I2C"][instance] + other[instance] = {"Module": "I2C", **record} + for instance in peripherals.get("PWM", {}): + captures = sorted(other.get(instance, {}).get("Pins", {})) + if captures: + logging.warning( + tr( + f"{hpmpc}: {instance} also selects {', '.join(captures)}; the capture inputs " + "generate no object", + f"{hpmpc}:{instance} 还选了 {'、'.join(captures)};捕获输入不生成对象", + ) + ) + for section in ("I2C", "PWM"): + if section in peripherals and not peripherals[section]: + del peripherals[section] + + +def _save_to_yaml(data: dict, output_path: str) -> bool: + """把工程数据写成 YAML,首行为 Pinmux Tool 工程的生成文件说明;成功时为 True。 + Write the project data as YAML with a generated-file notice for a Pinmux Tool project on + the first line; True on success. + """ + try: + created = os.path.dirname(output_path) + if created: + os.makedirs(created, exist_ok=True) + with open(output_path, "w", encoding="utf-8", newline="\n") as file: + file.write( + "# Generated by `libxr parse` from the HPM Pinmux Tool project; " + "do not edit by hand.\n" + ) + yaml.dump( + data, file, allow_unicode=True, sort_keys=False, default_flow_style=False, indent=2 + ) + logging.info( + tr(f"Configuration exported to: {output_path}", f"配置已导出到:{output_path}") + ) + return True + except (OSError, yaml.YAMLError) as error: + logging.error(tr(f"YAML export failed: {str(error)}", f"YAML 导出失败:{str(error)}")) + return False + + +def _print_summary(data: dict) -> None: + """向标准输出打印配置摘要:SoC、GPIO 引脚和各类外设实例。 + Print a configuration summary to standard output: the SoC, the GPIO pins and the + peripheral instances of each kind. + """ + print(tr("\n===== [Configuration Summary] =====", "\n===== [配置摘要] =====")) + mcu = data.get("Mcu", {}) + print( + tr( + f"\nSoC: {mcu.get('Type', '')} ({data.get('Package', '')})", + f"\nSoC:{mcu.get('Type', '')}({data.get('Package', '')})", + ) + ) + gpio = data.get("GPIO", {}) + print(tr(f"\nGPIO ({len(gpio)} pins):", f"\nGPIO({len(gpio)} 个引脚):")) + for label, pin in gpio.items(): + print(f" {label}: {pin['Pad']}") + print(tr("\nActive Peripherals:", "\n已启用的外设:")) + for section, group in data.get("Peripherals", {}).items(): + print(tr(f" {section}: {len(group)} instance(s)", f" {section}:{len(group)} 个实例")) + for name in group: + print(f" {name}") diff --git a/src/libxr/peripheral_analyzer_mspm0.py b/src/libxr/peripheral_analyzer_mspm0.py new file mode 100644 index 0000000..3cb5e61 --- /dev/null +++ b/src/libxr/peripheral_analyzer_mspm0.py @@ -0,0 +1,869 @@ +"""libxr parse 对 MSPM0 工程:解析 SysConfig 工程并写出工程 YAML。 +libxr parse for an MSPM0 project: parse the SysConfig project and write the project YAML. + +工程是根目录中唯一的 .syscfg。解析读两个来源:.syscfg 本身(外设实例、参数和引脚分配),以及 +SysConfig 生成的 ti_msp_dl_config.h/.c(实际生成的宏名、CPUCLK_FREQ、ULPCLK 分频和各实例的时钟 +配置)。设置了 SYSCONFIG_TOOL 和 MSPM0_SDK_INSTALL_DIR(与 CMake 的 MSPM0SysConfig.cmake 用的环境 +变量相同)时总是运行一次 SysConfig,生成到临时目录;没有设置时复用工程目录 build*、cmake-build* +或 out 下比 .syscfg 新的生成结果。复用只比较修改时间:换成一个修改时间更旧的 .syscfg 时,旧输出 +仍会被复用,这时请重新构建或设置这两个变量。 +The project is the single .syscfg in the root. Parsing reads two sources: the .syscfg itself +(peripheral instances, parameters and pin assignments) and the ti_msp_dl_config.h/.c that +SysConfig generates (the macro names actually emitted, CPUCLK_FREQ, the ULPCLK divider and the +clock configuration of each instance). With SYSCONFIG_TOOL and MSPM0_SDK_INSTALL_DIR set (the +environment variables the CMake MSPM0SysConfig.cmake uses as well) SysConfig always runs once, +into a temporary directory; without them a generated output newer than the .syscfg under +build*, cmake-build* or out in the project is reused. Reuse compares modification times only: a +.syscfg replaced by one with an older modification time still reuses the old output, so build +again or set the two variables then. + +外设分两类:LibXR 有驱动的(UART、控制器模式的 I2C 和 SPI、PWM)进入 Peripherals,gen 为它们 +生成对象;其余(ADC12、DAC12、MCAN、QEI、目标模式的 I2C、外设模式的 SPI……)记入 Other,只作 +展示。GPIO 引脚进入 GPIO 段。 +Peripherals split in two: the ones LibXR has drivers for (UART, I2C and SPI in controller mode, +PWM) go into Peripherals and gen generates objects for them; the rest (ADC12, DAC12, MCAN, QEI, +an I2C in target mode, an SPI in peripheral mode, ...) are recorded under Other for display +only. The GPIO pins go into the GPIO section. +""" + +from __future__ import annotations + +import logging +import os +import re +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + +import yaml +from xr_syntax.i18n import tr + +from libxr import pin_layout, pin_project +from libxr.generator_code_stm32 import CPP_KEYWORDS + + +def _save_to_yaml(data: dict, output_path: str) -> bool: + """把工程数据写成 YAML,首行为 SysConfig 工程的生成文件说明;成功时为 True。 + Write the project data as YAML with a generated-file notice for a SysConfig project on the + first line; True on success. + """ + try: + directory = os.path.dirname(output_path) + if directory: + os.makedirs(directory, exist_ok=True) + with open(output_path, "w", encoding="utf-8", newline="\n") as file: + file.write( + "# Generated by `libxr parse` from the SysConfig project file; " + "do not edit by hand.\n" + ) + yaml.dump( + data, file, allow_unicode=True, sort_keys=False, default_flow_style=False, indent=2 + ) + logging.info( + tr(f"Configuration exported to: {output_path}", f"配置已导出到:{output_path}") + ) + return True + except (OSError, yaml.YAMLError) as error: + logging.error(tr(f"YAML export failed: {str(error)}", f"YAML 导出失败:{str(error)}")) + return False + + +def _print_summary(data: dict) -> None: + """向标准输出打印配置摘要:器件、GPIO 引脚和各类外设实例。 + Print a configuration summary to standard output: the device, the GPIO pins and the + peripheral instances of each kind. + """ + print(tr("\n===== [Configuration Summary] =====", "\n===== [配置摘要] =====")) + mcu = data.get("Mcu", {}) + print( + tr( + f"\nMCU: {mcu.get('Family', '')} {mcu.get('Type', '')}", + f"\nMCU:{mcu.get('Family', '')} {mcu.get('Type', '')}", + ) + ) + gpio = data.get("GPIO", {}) + print(tr(f"\nGPIO ({len(gpio)} pins):", f"\nGPIO({len(gpio)} 个引脚):")) + for label, pin in gpio.items(): + direction = pin["Direction"] + if "Interrupt" in pin: + print( + tr( + f" {label}: {pin['Pin']}, {direction}, {pin['Interrupt']} interrupt", + f" {label}:{pin['Pin']},{direction},{pin['Interrupt']} 中断", + ) + ) + else: + print( + tr(f" {label}: {pin['Pin']}, {direction}", f" {label}:{pin['Pin']},{direction}") + ) + print(tr("\nActive Peripherals:", "\n已启用的外设:")) + for section, group in data.get("Peripherals", {}).items(): + print(tr(f" {section}: {len(group)} instance(s)", f" {section}:{len(group)} 个实例")) + for name in group: + print(f" {name}") + + +# SysConfig 运行的时限(秒)。 +# Time limit in seconds for a SysConfig run. +SYSCONFIG_TIMEOUT = 300 + +# 复用 SysConfig 输出时找的构建目录(工程根目录下的 glob)。 +# The build directories searched for a SysConfig output to reuse (globs in the project root). +BUILD_DIRECTORIES = ("build*", "cmake-build*", "out") + +# LibXR 有驱动、gen 生成对象的模块:模块名 -> Peripherals 段的键。 +# The modules LibXR has drivers for and gen generates objects for: module -> the key of the +# Peripherals section. +GENERATED_MODULES = {"UART": "UART", "I2C": "I2C", "SPI": "SPI", "PWM": "PWM"} + +SYSCFG_GPIO_PARAM = re.compile(r"^\.associatedPins\[(\d+)\]\.(\w+)$") +SYSCFG_PERIPH_MEMBER = re.compile(r"^\.peripheral\.(\w+)\.\$(assign|suggestSolution)$") +SYSCFG_PWM_CHANNEL = re.compile(r"^ccp(\d+)Pin$") +# 实例的时钟配置结构:gI2C_0ClockConfig,SPI 写成 gSPI_1_clockConfig;成员按列对齐,等号前的 +# 空格数不定。 +# The clock configuration struct of an instance: gI2C_0ClockConfig, written gSPI_1_clockConfig +# for an SPI; the members are aligned in columns, so the spaces before the equals sign vary. +CLOCK_CONFIG = re.compile(r"static const \w+ g(\w+?)_?[Cc]lockConfig\s*=\s*\{(.*?)\};", re.S) +CLOCK_MEMBER = r"\.{}\s*=\s*(\w+)" +HEADER_DEFINE = re.compile(r"^#define[ \t]+(\w+)(?:[ \t]+(.*?))?[ \t\r]*$", re.M) +DMA_TRIGGER = re.compile(r"\(?\s*DMA_\w+?_(TX|RX)_TRIG\s*\)?") +IDENTIFIER = re.compile(r"[A-Za-z_]\w*") + + +def _strip_comments(text: str) -> str: + """.syscfg 的正文:去掉块注释和行注释。 + The body of a .syscfg: block comments and line comments removed. + """ + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + return re.sub(r"^[ \t]*//.*$", "", text, flags=re.M) + + +class Instances: + """一个 .syscfg 的全部实例:变量 -> 模块、名字、外设、参数、引脚成员和 DMA 子实例。 + Every instance of one .syscfg: variable -> module, name, peripheral, parameters, pin + members and DMA sub-instances. + """ + + def __init__(self, text: str) -> None: + """解析 .syscfg 的脚本正文,按变量记下每个实例的全部赋值。外设和引脚都是 $assign 优先, + 求解器的 $suggestSolution 兜底(与 libxr pins 的读法相同)。 + Parse the script body of a .syscfg and record every assignment of each instance by its + variable. For the peripheral and the pins $assign wins and the solver's + $suggestSolution is the fallback, as libxr pins reads them. + """ + text = _strip_comments(text) + modules = dict(pin_project.SYSCFG_MODULE.findall(text)) + instances = dict(modules) + instances.update( + { + var: modules[module] + for var, module in pin_project.SYSCFG_INSTANCE.findall(text) + if module in modules + } + ) + self.of_var: dict[str, dict] = {} + for var, tail, value in pin_project.SYSCFG_ASSIGNMENT.findall(text): + if var not in instances: + continue + entry = self.of_var.setdefault( + var, + { + "module": instances[var], + "name": None, + "peripheral": None, + "params": {}, + "pins": {}, + "dma": {}, + "gpio": {}, + }, + ) + value = pin_project.syscfg_value(value) + if tail == ".$name": + entry["name"] = value + elif tail == ".peripheral.$assign": + entry["peripheral"] = value + elif tail == ".peripheral.$suggestSolution": + entry["peripheral"] = entry["peripheral"] or value + elif tail.startswith(".peripheral."): + member = SYSCFG_PERIPH_MEMBER.match(tail) + # $assign 覆盖同名成员的 $suggestSolution,反过来不覆盖。 + # $assign overrides a $suggestSolution of the same member, not the other way. + if member and (member.group(2) == "assign" or member.group(1) not in entry["pins"]): + entry["pins"][member.group(1)] = value + elif tail.startswith(".DMA_CHANNEL_"): + entry["dma"][tail] = value + elif tail.startswith(".associatedPins["): + index = int(re.match(r"^\.associatedPins\[(\d+)\]", tail).group(1)) + pin = entry["gpio"].setdefault(index, {}) + if tail == f".associatedPins[{index}].$name": + pin["name"] = value + elif tail in ( + f".associatedPins[{index}].pin.$assign", + f".associatedPins[{index}].pin.$suggestSolution", + ): + if tail.endswith(".$assign") or "pin" not in pin: + pin["pin"] = value + else: + param = SYSCFG_GPIO_PARAM.match(tail) + if param and not param.group(2).startswith("$"): + pin[param.group(2)] = value + elif "$" not in tail: + entry["params"][tail.lstrip(".")] = value + + def peripherals(self, module: str) -> dict[str, dict]: + """模块 module 的实例,按 .syscfg 中的出现顺序,实例名 -> 数据;没有名字的实例跳过。 + The instances of module, in .syscfg order, instance name -> its data; an instance + without a name is skipped. + """ + found = {} + for entry in self.of_var.values(): + if entry["module"] == module and entry["name"]: + found[entry["name"]] = entry + return found + + +def _fail(message: str): + """记录错误并以状态 1 退出。 + Log an error and exit with status 1. + """ + logging.error(message) + sys.exit(1) + + +def _read_output(directory: Path) -> tuple[str, str]: + """directory 中 ti_msp_dl_config.h 和 .c 的内容。 + The contents of ti_msp_dl_config.h and .c in directory. + """ + header = directory / "ti_msp_dl_config.h" + source = directory / "ti_msp_dl_config.c" + return header.read_text(encoding="utf-8", errors="replace"), source.read_text( + encoding="utf-8", errors="replace" + ) + + +def _sysconfig_problems(tool: str, sdk: str) -> list[tuple[str, str]]: + """SysConfig 环境的问题:没有设置的变量,或设置了但指错的路径;没有问题时为空。 + The problems of the SysConfig environment: a variable that is not set, or one that is set + to a wrong path; empty when there is none. + """ + problems = [] + if not tool: + problems.append(("SYSCONFIG_TOOL is not set", "没有设置 SYSCONFIG_TOOL")) + elif not os.path.isfile(tool): + problems.append( + ( + f"SYSCONFIG_TOOL={tool} is not a file (it names sysconfig_cli.bat or " + "sysconfig_cli.sh)", + f"SYSCONFIG_TOOL={tool} 不是文件(应指向 sysconfig_cli.bat 或 sysconfig_cli.sh)", + ) + ) + if not sdk: + problems.append(("MSPM0_SDK_INSTALL_DIR is not set", "没有设置 MSPM0_SDK_INSTALL_DIR")) + elif not os.path.isfile(os.path.join(sdk, ".metadata", "product.json")): + problems.append( + ( + f"MSPM0_SDK_INSTALL_DIR={sdk} has no .metadata/product.json", + f"MSPM0_SDK_INSTALL_DIR={sdk} 中没有 .metadata/product.json", + ) + ) + return problems + + +def _reusable_output(directory: Path, syscfg: Path) -> Path | None: + """工程构建目录(BUILD_DIRECTORIES)中比 .syscfg 新、.h 和 .c 都在的最新一份 SysConfig 输出 + 目录;没有时为 None。 + The newest SysConfig output directory in the build directories of the project + (BUILD_DIRECTORIES) that is newer than the .syscfg and holds both the .h and the .c; None + when there is none. + """ + syscfg_mtime = syscfg.stat().st_mtime + headers = [ + path + for pattern in BUILD_DIRECTORIES + for path in directory.glob(f"{pattern}/**/syscfg/ti_msp_dl_config.h") + if path.stat().st_mtime >= syscfg_mtime and path.with_suffix(".c").is_file() + ] + if not headers: + return None + return max(headers, key=lambda path: path.stat().st_mtime).parent + + +def _sysconfig_output(directory: Path, syscfg: Path) -> tuple[str, str]: + """SysConfig 生成的 ti_msp_dl_config.h 和 .c 的内容:SysConfig 环境可用时运行它到临时目录 + (用后删除),否则复用构建目录中比 .syscfg 新的输出;都不行时说明缺什么并退出。 + The contents of the ti_msp_dl_config.h/.c SysConfig generates: with a usable SysConfig + environment it runs into a temporary directory (removed afterwards), otherwise an output in + a build directory that is newer than the .syscfg is reused; when neither works, say what is + missing and exit. + """ + tool = os.environ.get("SYSCONFIG_TOOL", "") + sdk = os.environ.get("MSPM0_SDK_INSTALL_DIR", "") + problems = _sysconfig_problems(tool, sdk) + if not problems: + return _run_sysconfig(tool, sdk, syscfg) + english = "; ".join(problem[0] for problem in problems) + chinese = ";".join(problem[1] for problem in problems) + + reused = _reusable_output(directory, syscfg) + if reused is not None and (tool or sdk): + # 设置了却指错的环境变量要说出来,否则用户不知道 SysConfig 为什么没有运行。 + # A variable that is set but wrong is reported; otherwise nobody learns why SysConfig + # did not run. + logging.warning( + tr( + f"SysConfig cannot run: {english}; the build output is reused instead", + f"无法运行 SysConfig:{chinese};改为复用构建输出", + ) + ) + if reused is not None: + build_dir = reused.parent + label = ( + build_dir.relative_to(directory) if build_dir.is_relative_to(directory) else build_dir + ) + logging.info( + tr(f"Reusing the SysConfig output in {label}", f"复用 {label} 中的 SysConfig 输出") + ) + return _read_output(reused) + + _fail( + tr( + f"{directory}: no SysConfig output newer than {syscfg.name} found under " + f"{', '.join(BUILD_DIRECTORIES)}, and SysConfig cannot run: {english} (the " + "environment variables the CMake build uses). Build the project once, or set them " + "and run `libxr parse` again.", + f"{directory}:{'、'.join(BUILD_DIRECTORIES)} 下没有比 {syscfg.name} 新的 SysConfig " + f"输出,且无法运行 SysConfig:{chinese}(CMake 构建使用的环境变量)。请先构建一次" + "工程,或设置它们后重新运行 `libxr parse`。", + ) + ) + + +def _run_sysconfig(tool: str, sdk: str, syscfg: Path) -> tuple[str, str]: + """用 SysConfig 生成 syscfg 的输出到临时目录并读出 .h 和 .c;运行失败、超时或无法启动时 + 记录错误并退出。 + Generate the output of syscfg into a temporary directory with SysConfig and read the .h and + the .c; a failed, timed-out or unstartable run logs an error and exits. + """ + output = tempfile.mkdtemp(prefix="libxr-sysconfig-") + try: + command = [ + tool, + "--compiler", + "gcc", + "--product", + os.path.join(sdk, ".metadata", "product.json"), + "--output", + output, + str(syscfg), + ] + logging.info( + tr(f"Running SysConfig on {syscfg.name}", f"正在对 {syscfg.name} 运行 SysConfig") + ) + try: + result = subprocess.run( + command, capture_output=True, text=True, timeout=SYSCONFIG_TIMEOUT + ) + except subprocess.TimeoutExpired: + _fail( + tr( + f"SysConfig did not finish on {syscfg} within {SYSCONFIG_TIMEOUT} s ({tool})", + f"SysConfig 在 {SYSCONFIG_TIMEOUT} 秒内没有处理完 {syscfg}({tool})", + ) + ) + except OSError as error: + _fail( + tr( + f"Cannot run SysConfig ({tool}): {error}", + f"无法运行 SysConfig({tool}):{error}", + ) + ) + if result.returncode != 0: + _fail( + tr( + f"SysConfig failed on {syscfg}:\n{result.stdout}{result.stderr}", + f"SysConfig 在 {syscfg} 上运行失败:\n{result.stdout}{result.stderr}", + ) + ) + try: + return _read_output(Path(output)) + except OSError as error: + _fail( + tr( + f"SysConfig wrote no ti_msp_dl_config.h/.c for {syscfg}: {error}", + f"SysConfig 没有为 {syscfg} 写出 ti_msp_dl_config.h/.c:{error}", + ) + ) + finally: + shutil.rmtree(output, ignore_errors=True) + + +def _header_defines(header: str) -> dict[str, str]: + """ti_msp_dl_config.h 定义的宏,按出现顺序:宏名 -> 值(去掉首尾空白,没有值时为空串)。 + The macros ti_msp_dl_config.h defines, in order: name -> value (stripped, empty when the + macro has none). + """ + return {name: (value or "").strip() for name, value in HEADER_DEFINE.findall(header)} + + +def _cpuclk(defines: dict[str, str]) -> int | None: + """ti_msp_dl_config.h 里的 CPUCLK_FREQ;没有时为 None。 + The CPUCLK_FREQ of ti_msp_dl_config.h; None when it does not define one. + """ + match = re.fullmatch(r"\(?(\d+)U?\)?", defines.get("CPUCLK_FREQ", "")) + return int(match.group(1)) if match else None + + +def _ulpclk_divider(source: str) -> int: + """ti_msp_dl_config.c 里 DL_SYSCTL_setULPCLKDivider 的分频数;没有调用时为 1。 + The divider of DL_SYSCTL_setULPCLKDivider in ti_msp_dl_config.c; 1 without a call. + """ + match = re.search(r"DL_SYSCTL_setULPCLKDivider\s*\(\s*DL_SYSCTL_ULPCLK_DIV_(\d+)", source) + return int(match.group(1)) if match else 1 + + +def _clock_configs(source: str) -> dict[str, dict]: + """ti_msp_dl_config.c 里每个实例的时钟配置:实例名(I2C_0、SPI_1)-> clockSel 和 + divideRatio。 + The clock configuration of every instance in ti_msp_dl_config.c: instance name (I2C_0, + SPI_1) -> its clockSel and divideRatio. + """ + found: dict[str, dict] = {} + for name, body in CLOCK_CONFIG.findall(source): + sel = re.search(CLOCK_MEMBER.format("clockSel"), body) + div = re.search(CLOCK_MEMBER.format("divideRatio"), body) + found[name] = { + "clockSel": sel.group(1) if sel else None, + "divideRatio": div.group(1) if div else None, + } + return found + + +def _clock_is_default(clock: dict) -> bool: + """实例的 SysConfig 时钟配置是否为默认的 BUSCLK + 分频 1(驱动会重设时钟的外设,例如 I2C, + 这只描述 SysConfig 写下的初值)。 + Whether the SysConfig clock configuration of the instance is the default BUSCLK with divider + 1; for a peripheral whose driver resets the clock, such as an I2C, this only describes the + initial value SysConfig wrote. + """ + sel, div = clock.get("clockSel"), clock.get("divideRatio") + return sel in ( + None, + "DL_I2C_CLOCK_BUSCLK", + "DL_SPI_CLOCK_BUSCLK", + "DL_TIMER_CLOCK_BUSCLK", + "DL_UART_MAIN_CLOCK_BUSCLK", + ) and div in ( + None, + "DL_I2C_CLOCK_DIVIDE_1", + "DL_SPI_CLOCK_DIVIDE_RATIO_1", + "DL_TIMER_CLOCK_DIVIDE_1", + "DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1", + ) + + +def _device_names(power_domains: dict[str, int], defines: dict[str, str]) -> set[str]: + """GPIO 对象名不能用的器件名字:器件头文件的外设实例宏(UART0、SPI1、TIMG0、GPIOA……,取自 + P1 数据的电源域表)和 ti_msp_dl_config.h 定义的宏。 + The device names a GPIO object cannot take: the peripheral instance macros of the device + header (UART0, SPI1, TIMG0, GPIOA, ..., from the power domain table of the P1 data) and the + macros ti_msp_dl_config.h defines. + """ + names = {name for name in power_domains if not re.fullmatch(r"P[A-Z]\d+", name)} + names.update({"GPIOA", "GPIOB", "GPIOC"}) + names.update(defines) + return names + + +def _gpio_label(syscfg: Path, group: str, name: str) -> str: + """GPIO 对象名:引脚 $name 去掉 PIN_ 前缀(PIN_LED1 -> LED1);去掉后不是合法标识符时 + (SysConfig 的默认名 PIN_0 -> 0)退回小写的组名加引脚名(gpio_btn_pin_0),并提示怎样在 + SysConfig 中改名。 + The GPIO object name: the pin $name without its PIN_ prefix (PIN_LED1 -> LED1); when that + is not a valid identifier (the SysConfig default PIN_0 -> 0) it falls back to the + lower-case group plus pin name (gpio_btn_pin_0), with a hint on renaming in SysConfig. + """ + fallback, used = pin_project.mspm0_gpio_name(group, name) + if not used: + return fallback + label = name[4:] if name.startswith("PIN_") else name + logging.warning( + tr( + f"{syscfg.name}: the GPIO pin {name} of {group} gives no valid C++ name ({label}); " + f"the object is named {fallback}. Give the pin a name in SysConfig (GPIO > {group} " + "> the pin > Name, for example PIN_LED1 for LED1) to choose it.", + f"{syscfg.name}:{group} 的 GPIO 引脚 {name} 得不到合法的 C++ 名字({label});对象" + f"命名为 {fallback}。请在 SysConfig 中给引脚命名(GPIO > {group} > 该引脚 > Name," + "例如 PIN_LED1 得到 LED1)。", + ) + ) + return fallback + + +def _gpio_physical(pin: dict, port_value: str, pin_value: str) -> str | None: + """引脚的物理位置(PB8):.syscfg 的 pin.$assign/$suggestSolution;没有时(TI 例程里的端口 + 加引脚号写法,或求解器选的引脚)取自 SysConfig 输出的 PORT 和 PIN 宏的值(GPIOB、 + DL_GPIO_PIN_8)。都没有时为 None。 + The physical position of a pin (PB8): pin.$assign/$suggestSolution of the .syscfg; without + it (the port plus pin number form of TI's examples, or a pin the solver chose) it comes from + the values of the PORT and PIN macros of the SysConfig output (GPIOB, DL_GPIO_PIN_8). None + when neither gives it. + """ + if pin.get("pin"): + return str(pin["pin"]) + port = re.search(r"\bGPIO([A-Z])\b", port_value) + line = re.search(r"\bDL_GPIO_PIN_(\d+)\b", pin_value) + if port and line: + return f"P{port.group(1)}{int(line.group(1))}" + return None + + +def _gpio_entries( + syscfg: Path, instances: Instances, defines: dict[str, str], device_names: set[str] +) -> dict: + """.syscfg 中 GPIO 组的引脚:标签($name 去掉 PIN_ 前缀)-> 组、物理引脚、实际生成的宏和 + 电气设置。跳过的引脚都给出警告;与器件宏同名的标签报错退出。 + The pins of the GPIO groups in the .syscfg: label (the $name without its PIN_ prefix) -> + group, physical pin, the macros that were actually generated and the electrical settings. + Every skipped pin is warned about; a label named like a device macro is an error. + """ + entries: dict[str, dict] = {} + for entry in instances.of_var.values(): + if entry["module"] != "GPIO" or not entry["name"]: + continue + group_name = entry["name"] + # SysConfig 的宏以组名为前缀:组内所有引脚同端口时组级 PORT 宏(GPIO_KEYS_PORT),否则 + # 每个引脚自己的 PORT 宏(GPIO_LEDS_PIN_LED1_PORT);PIN 和 IOMUX 宏总是每引脚一个。 + # The macros of SysConfig take the group name as their prefix: a group-level PORT macro + # (GPIO_KEYS_PORT) when every pin of the group sits on the same port, else the PORT + # macro of each pin (GPIO_LEDS_PIN_LED1_PORT); the PIN and IOMUX macros are always + # per-pin. + group_port = f"{group_name}_PORT" in defines + for index in sorted(entry["gpio"]): + pin = entry["gpio"][index] + name = pin.get("name") + if not name: + logging.warning( + tr( + f"{syscfg.name}: pin {index} of the GPIO group {group_name} has no " + "name; it is skipped", + f"{syscfg.name}:GPIO 组 {group_name} 的第 {index} 个引脚没有名字;跳过", + ) + ) + continue + port_macro = f"{group_name}_PORT" if group_port else f"{group_name}_{name}_PORT" + pin_macro = f"{group_name}_{name}_PIN" + iomux_macro = f"{group_name}_{name}_IOMUX" + missing = [ + macro for macro in (port_macro, pin_macro, iomux_macro) if macro not in defines + ] + if missing: + logging.warning( + tr( + f"{syscfg.name}: {', '.join(missing)} missing from the SysConfig output; " + f"the GPIO {name} is skipped", + f"{syscfg.name}:SysConfig 输出里没有 {', '.join(missing)};" + f"跳过 GPIO {name}", + ) + ) + continue + physical = _gpio_physical(pin, defines[port_macro], defines[pin_macro]) + if physical is None: + logging.warning( + tr( + f"{syscfg.name}: cannot tell the pin of the GPIO {name} from " + f"{port_macro} and {pin_macro}; it is skipped", + f"{syscfg.name}:无法从 {port_macro} 和 {pin_macro} 得知 GPIO {name} " + "的引脚;跳过", + ) + ) + continue + record = { + "Group": group_name, + "Pin": physical, + "Port": port_macro, + "PinMacro": pin_macro, + "IOMUX": iomux_macro, + "Direction": pin.get("direction", "OUTPUT"), + "Resistor": pin.get("internalResistor", "NONE"), + } + if pin.get("interruptEn"): + # SysConfig 的触发极性默认是 DISABLE(GPIOPin.syscfg.js)。 + # The trigger polarity of SysConfig defaults to DISABLE (GPIOPin.syscfg.js). + record["Interrupt"] = pin.get("polarity", "DISABLE") + label = _gpio_label(syscfg, group_name, name) + if label in device_names or label in CPP_KEYWORDS: + _fail( + tr( + f"{syscfg.name}: the GPIO object name {label} (pin {name} of " + f"{group_name}) is a C++ keyword or a macro of the device or of the " + "SysConfig output; rename the pin in SysConfig", + f"{syscfg.name}:GPIO 对象名 {label}({group_name} 的引脚 {name})是 " + "C++ 关键字,或器件、SysConfig 输出中的宏;请在 SysConfig 中给引脚改名", + ) + ) + if label in entries: + logging.warning( + tr( + f"{syscfg.name}: two GPIO pins are named {label}; the later one wins", + f"{syscfg.name}:两个 GPIO 引脚都叫 {label};以后一个为准", + ) + ) + entries[label] = record + return entries + + +def _header_dma_channel(name: str, direction: str, defines: dict[str, str]) -> str | None: + """SysConfig 输出里实例 name 在 direction(TX/RX)方向的 DMA 通道名:每个 + _INST_DMA_TRIGGER 或 _INST_DMA_TRIGGER_n 前面紧挨着它的通道的 <通道>_CHAN_ID + (一个实例只有一个触发时 SysConfig 不写编号,两个及以上才写 _0、_1)。没有时为 None。 + The DMA channel name of the instance name in direction (TX/RX) in the SysConfig output: + every _INST_DMA_TRIGGER or _INST_DMA_TRIGGER_n directly follows the + _CHAN_ID of its channel (SysConfig writes no number while the instance has one + trigger, and _0, _1 from two on). None when there is none. + """ + channel = None + for macro, value in defines.items(): + if macro.endswith("_CHAN_ID"): + channel = macro[: -len("_CHAN_ID")] + elif re.fullmatch(rf"{re.escape(name)}_INST_DMA_TRIGGER(?:_\d+)?", macro): + trigger = DMA_TRIGGER.fullmatch(value) + if trigger and trigger.group(1) == direction and channel: + return channel + return None + + +def _dma_channel(name: str, entry: dict, direction: str, defines: dict[str, str]) -> str | None: + """实例的 DMA 通道名:UART 是 DMA_CHANNEL_TX/RX,SPI 是事件通道,按触发方向区分(RX 的触发 + 包括 RX_TIMEOUT);.syscfg 没写通道名时取自 SysConfig 输出。 + The DMA channel name of an instance: DMA_CHANNEL_TX/RX for a UART, the event channels of an + SPI told apart by their trigger direction (an RX trigger includes RX_TIMEOUT); when the + .syscfg does not name the channel it comes from the SysConfig output. + """ + channel = entry["dma"].get(f".DMA_CHANNEL_{direction}.$name") + if channel: + return channel + for event in (1, 2): + channel = entry["dma"].get(f".DMA_CHANNEL_EVENT{event}.$name") + trigger = str(entry["params"].get(f"enabledDMAEvent{event}Triggers") or "") + if channel and trigger: + if direction == "RX" and re.search(r"_RX(_TIMEOUT)?$", trigger): + return channel + if direction == "TX" and trigger.endswith("_TX"): + return channel + return _header_dma_channel(name, direction, defines) + + +def _not_generated(syscfg: Path, module: str, name: str, entry: dict, defines: dict) -> str | None: + """LibXR 驱动不支持的工作模式:I2C 只有目标模式(SysConfig 输出里没有 _BUS_SPEED_HZ), + SPI 是外设模式。返回原因的英文和中文,支持时为 None。 + A mode the LibXR driver does not support: an I2C in target mode only (no _BUS_SPEED_HZ + in the SysConfig output) and an SPI in peripheral mode. Returns the reason, or None when + the mode is supported. + """ + if module == "I2C" and f"{name}_BUS_SPEED_HZ" not in defines: + return tr( + f"{syscfg.name}: {name} is not in controller mode (no {name}_BUS_SPEED_HZ in the " + "SysConfig output); the LibXR MSPM0 I2C driver is controller-only, so no object is " + "generated and the instance is listed under Other", + f"{syscfg.name}:{name} 不是控制器模式(SysConfig 输出里没有 {name}_BUS_SPEED_HZ);" + "LibXR 的 MSPM0 I2C 驱动只支持控制器,不生成对象,实例列在 Other 中", + ) + if module == "SPI" and str(entry["params"].get("mode", "CONTROLLER")) != "CONTROLLER": + return tr( + f"{syscfg.name}: {name} is in {entry['params']['mode']} mode; the LibXR MSPM0 SPI " + "driver is controller-only, so no object is generated and the instance is listed " + "under Other", + f"{syscfg.name}:{name} 是 {entry['params']['mode']} 模式;LibXR 的 MSPM0 SPI 驱动只" + "支持控制器,不生成对象,实例列在 Other 中", + ) + return None + + +def _pwm_channels(syscfg: Path, name: str, entry: dict, defines: dict[str, str]) -> list[int]: + """PWM 实例的通道:SysConfig 输出里的 GPIO__C_IDX;输出里没有时取 .syscfg 的 + ccpPin($assign 或 $suggestSolution)。都没有时给出警告。 + The channels of a PWM instance: GPIO__C_IDX in the SysConfig output; without them + the ccpPin members of the .syscfg ($assign or $suggestSolution). A warning when neither + gives any. + """ + pattern = re.compile(rf"GPIO_{re.escape(name)}_C(\d+)_IDX") + channels = sorted({int(m.group(1)) for macro in defines if (m := pattern.fullmatch(macro))}) + if not channels: + channels = sorted( + {int(m.group(1)) for member in entry["pins"] if (m := SYSCFG_PWM_CHANNEL.match(member))} + ) + if not channels: + logging.warning( + tr( + f"{syscfg.name}: the PWM {name} has no channel (no GPIO_{name}_C_IDX in the " + "SysConfig output and no ccpPin in the .syscfg); no PWM object is generated", + f"{syscfg.name}:PWM {name} 没有通道(SysConfig 输出里没有 GPIO_{name}_C_IDX," + ".syscfg 里也没有 ccpPin);不生成 PWM 对象", + ) + ) + return channels + + +def _syscfg_of(directory: str) -> Path: + """directory 根目录唯一的 .syscfg;不是正好一个时报错退出。 + The single .syscfg in the root of directory; an error exits when there is not exactly one. + """ + syscfgs = sorted(Path(directory).glob("*.syscfg")) + if len(syscfgs) != 1: + names = ", ".join(path.name for path in syscfgs) or tr("none found", "没有找到") + _fail( + tr( + f"{directory} must hold exactly one .syscfg file in its root ({names})", + f"{directory} 的根目录必须有且只有一个 .syscfg 文件({names})", + ) + ) + return syscfgs[0] + + +def _device_of(syscfg: Path) -> tuple[str, dict[str, int]]: + """.syscfg 的器件(@v2CliArgs 的 --device,或 --board 的板子表)和器件族的外设电源域;没有 + 器件或器件未知时报错退出。 + The device of the .syscfg (--device of @v2CliArgs, or the board table for --board) and the + peripheral power domains of its family; a missing or unknown device is an error. + """ + meta = pin_project.read_syscfg(syscfg) + board = pin_project.load_boards().get(meta.board or "") + device = meta.device or (board or {}).get("device") + if device is None: + _fail( + tr( + f"{syscfg.name} does not name a device (@v2CliArgs --device or --board)", + f"{syscfg.name} 没有给出器件(@v2CliArgs 的 --device 或 --board)", + ) + ) + try: + return device, _power_domains(device) + except ValueError as error: + _fail(tr(f"{syscfg.name}: {error}", f"{syscfg.name}:{error}")) + + +def parse_project(directory: str, output: str | None = None, summary: bool = True) -> None: + """解析 directory 根目录的 .syscfg 工程并写出 YAML;summary 为真时打印摘要。 + Parse the .syscfg project in the root of directory and write the YAML; print a summary when + summary is set. + + 输出路径默认为该目录下的 .config.yaml。调用方(libxr parse)已确认目录存在且根目录含有一个 + .syscfg 文件。 + The output defaults to .config.yaml in that directory. The caller, libxr parse, has checked + that the directory exists and the root holds one .syscfg file. + """ + root = Path(directory) + syscfg = _syscfg_of(directory) + logging.info(tr(f"Processing {syscfg.name}...", f"正在处理 {syscfg.name}……")) + device, power_domains = _device_of(syscfg) + header, source = _sysconfig_output(root, syscfg) + defines = _header_defines(header) + + instances = Instances(syscfg.read_text(encoding="utf-8", errors="replace")) + peripherals: dict[str, dict] = {} + other: dict[str, dict] = {} + for module, section in GENERATED_MODULES.items(): + for name, entry in instances.peripherals(module).items(): + # .syscfg 没写外设时(求解器选的)取 SysConfig 输出的 _INST。 + # When the .syscfg names no peripheral (the solver chose it) it comes from the + # _INST of the SysConfig output. + peripheral = entry["peripheral"] or defines.get(f"{name}_INST") or None + reason = _not_generated(syscfg, module, name, entry, defines) + if reason: + logging.warning(reason) + other[name] = {"Module": module, "Peripheral": peripheral} + continue + record = {"Peripheral": peripheral} + for direction in ("TX", "RX"): + channel = _dma_channel(name, entry, direction, defines) + if channel: + record[f"DMA_{direction}"] = channel + if entry["pins"]: + record["Pins"] = dict(entry["pins"]) + if module == "PWM": + record["Channels"] = _pwm_channels(syscfg, name, entry, defines) + if module == "SPI": + # 接收 DMA 的触发(RX 或 RX_TIMEOUT):gen 只接受 RX。 + # The trigger of the receive DMA (RX or RX_TIMEOUT): gen accepts RX only. + for event in (1, 2): + trigger = str(entry["params"].get(f"enabledDMAEvent{event}Triggers") or "") + if re.search(r"_RX(_TIMEOUT)?$", trigger): + record["DMA_RX_TRIGGER"] = trigger + if module == "UART" and record.get("DMA_RX"): + # 接收 DMA 只有 UART Extend 实例能用(MSPM0_UART_EXTEND_INIT),还必须是 FULL-DMA + # 通道:gen 按这两项决定构造方式。 + # Only a UART Extend instance can use receive DMA (MSPM0_UART_EXTEND_INIT), and + # only on a FULL-DMA channel: gen decides the construction by these two. + record["Extend"] = str(peripheral) in _family(device).get("uart_extend", []) + channel_id = re.fullmatch( + r"\(?(\d+)U?\)?", defines.get(f"{record['DMA_RX']}_CHAN_ID", "") + ) + if channel_id: + record["DMA_RX_ID"] = int(channel_id.group(1)) + peripherals.setdefault(section, {})[name] = record + + for entry in instances.of_var.values(): + module, name, peripheral = entry["module"], entry["name"], entry["peripheral"] + if module in GENERATED_MODULES or module == "GPIO": + continue + if peripheral is None or peripheral.startswith("DMA_CH") or peripheral == "DEBUGSS": + continue + other[name or peripheral] = {"Module": module, "Peripheral": peripheral} + + # 每个外设实例所在的电源域(P1 的数据),gen 的时钟计算(M5)要用。 + # The power domain of every peripheral instance (the P1 data), for the clock computation of + # gen (M5). + clocks = _clock_configs(source) + for _section, records in peripherals.items(): + for name, record in records.items(): + domain = power_domains.get(str(record["Peripheral"])) + if domain is not None: + record["PowerDomain"] = domain + clock = clocks.get(name) + if clock and not _clock_is_default(clock): + record["SysConfigClock"] = clock + + data = { + "Platform": "mspm0", + "Syscfg": syscfg.name, + "Mcu": {"Family": "MSPM0", "Type": device}, + "CPUCLK": _cpuclk(defines), + "ULPCLKDivider": _ulpclk_divider(source), + "DMAFullChannels": _family(device).get("dma_full_channels"), + "GPIO": _gpio_entries(syscfg, instances, defines, _device_names(power_domains, defines)), + "Peripherals": {**peripherals, "Other": other} if other else peripherals, + } + + output_path = output or os.path.join(directory, ".config.yaml") + if not data["Peripherals"] and not data["GPIO"]: + logging.warning( + tr( + f"{syscfg.name} configures no peripheral or GPIO pin", + f"{syscfg.name} 没有配置任何外设或 GPIO 引脚", + ) + ) + if not _save_to_yaml(data, str(output_path)): + sys.exit(1) + if summary: + _print_summary(data) + + +def _family(device: str) -> dict: + """器件所属家族的 mspm0 数据(电源域、UART Extend 实例、FULL-DMA 通道数……);器件未知时 + 抛出 ValueError。 + The mspm0 data of the family of the device (power domains, UART Extend instances, the + number of FULL-DMA channels, ...); an unknown device raises ValueError. + """ + data = pin_layout.load_data("mspm0") + _, family, _ = pin_layout.mspm0_family(device, data) + return family + + +def _power_domains(device: str) -> dict[str, int]: + """器件所属家族的外设电源域(P1 的 mspm0 数据:模块名 -> 0/1);器件未知时抛出 ValueError。 + The peripheral power domains of the family of the device (the P1 mspm0 data: module -> + 0/1); an unknown device raises ValueError. + """ + return _family(device).get("power_domains", {}) diff --git a/src/libxr/pin_data/LICENSE-HPM.txt b/src/libxr/pin_data/LICENSE-HPM.txt new file mode 100644 index 0000000..fcbba7d --- /dev/null +++ b/src/libxr/pin_data/LICENSE-HPM.txt @@ -0,0 +1,31 @@ +BSD 3-Clause "New" or "Revised" License +https://spdx.org/licenses/BSD-3-Clause.html + +Copyright (c) 2021-2023, HPMicro. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/libxr/pin_data/LICENSE-ST.txt b/src/libxr/pin_data/LICENSE-ST.txt new file mode 100644 index 0000000..2c8a382 --- /dev/null +++ b/src/libxr/pin_data/LICENSE-ST.txt @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, STMicroelectronics +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/libxr/pin_data/LICENSE-TI-BOARDS.txt b/src/libxr/pin_data/LICENSE-TI-BOARDS.txt new file mode 100644 index 0000000..f98672a --- /dev/null +++ b/src/libxr/pin_data/LICENSE-TI-BOARDS.txt @@ -0,0 +1,29 @@ +Copyright (c) 2022 Texas Instruments Incorporated - http://www.ti.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of Texas Instruments Incorporated nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/libxr/pin_data/LICENSE-TI.txt b/src/libxr/pin_data/LICENSE-TI.txt new file mode 100644 index 0000000..9b7e6bd --- /dev/null +++ b/src/libxr/pin_data/LICENSE-TI.txt @@ -0,0 +1,31 @@ +Copyright � 2015 Texas Instruments Incorporated + +All rights reserved not granted herein. + +Limited License. + +Texas Instruments Incorporated grants a world-wide, royalty-free, non-exclusive license under copyrights and patents it now or hereafter owns or controls to make, have made, use, import, offer to sell and sell ("Utilize") this software subject to the terms herein. With respect to the foregoing patent license, such license is granted solely to the extent that any such patent is necessary to Utilize the software alone. The patent license shall not apply to any combinations which include this software, other than combinations with devices manufactured by or for TI (�TI Devices�). No hardware patent is licensed hereunder. + +Redistributions must preserve existing copyright notices and reproduce this license (including the above copyright notice and the disclaimer and (if applicable) source code license limitations below) in the documentation and/or other materials provided with the distribution + +Redistribution and use in binary form, without modification, are permitted provided that the following conditions are met: + +* No reverse engineering, decompilation, or disassembly of this software is permitted with respect to any software provided in binary form. + +* any redistribution and use are licensed by TI for use only with TI Devices. + +* Nothing shall obligate TI to provide you with source code for the software licensed and provided to you in object code. + +If software source code is provided to you, modification and redistribution of the source code are permitted provided that the following conditions are met: + +* any redistribution and use of the source code, including any resulting derivative works, are licensed by TI for use only with TI Devices. + +* any redistribution and use of any object code compiled from the source code and any resulting derivative works, are licensed by TI for use only with TI Devices. + +Neither the name of Texas Instruments Incorporated nor the names of its suppliers may be used to endorse or promote products derived from this software without specific prior written permission. + +DISCLAIMER. + +THIS SOFTWARE IS PROVIDED BY TI AND TI�S LICENSORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TI AND TI�S LICENSORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +November 8, 2012 + diff --git a/src/libxr/pin_data/hpm.json.gz b/src/libxr/pin_data/hpm.json.gz new file mode 100644 index 0000000..d1cbc5e Binary files /dev/null and b/src/libxr/pin_data/hpm.json.gz differ diff --git a/src/libxr/pin_data/mspm0.json.gz b/src/libxr/pin_data/mspm0.json.gz new file mode 100644 index 0000000..8c7f83a Binary files /dev/null and b/src/libxr/pin_data/mspm0.json.gz differ diff --git a/src/libxr/pin_data/mspm0_boards.json b/src/libxr/pin_data/mspm0_boards.json new file mode 100644 index 0000000..0164fb9 --- /dev/null +++ b/src/libxr/pin_data/mspm0_boards.json @@ -0,0 +1,54 @@ +{ + "boards": { + "LP_MSPM0C1104": { + "device": "MSPM0C1104", + "package": "VSSOP-20(DGS20)" + }, + "LP_MSPM0C1106": { + "device": "MSPM0C1106", + "package": "VQFN-48(RGZ)" + }, + "LP_MSPM0G3218": { + "device": "MSPM0G3218", + "package": "LQFP-64(PM)" + }, + "LP_MSPM0G3507": { + "device": "MSPM0G3507", + "package": "LQFP-64(PM)" + }, + "LP_MSPM0G3519": { + "device": "MSPM0G3519", + "package": "LQFP-100(PZ)" + }, + "LP_MSPM0G5187": { + "device": "MSPM0G5187", + "package": "LQFP-64(PM)" + }, + "LP_MSPM0H3216": { + "device": "MSPM0H3216", + "package": "LQFP-48(PT)" + }, + "LP_MSPM0L1117": { + "device": "MSPM0L1117", + "package": "LQFP-48(PT)" + }, + "LP_MSPM0L1306": { + "device": "MSPM0L1306", + "package": "VQFN-32(RHB)" + }, + "LP_MSPM0L2117": { + "device": "MSPM0L2117", + "package": "LQFP-64(PM)" + }, + "LP_MSPM0L2228": { + "device": "MSPM0L2228", + "package": "LQFP-80(PN)" + } + }, + "source": { + "commit": "20807db79aa17b49f87ab8ec87f6b6d63ee2cb32", + "dataset": "MSPM0 SDK source/ti/boards/.meta", + "license": "BSD-3-Clause", + "vendor": "Texas Instruments" + } +} diff --git a/src/libxr/pin_data/stm32-C0.json.gz b/src/libxr/pin_data/stm32-C0.json.gz new file mode 100644 index 0000000..4b50ecb Binary files /dev/null and b/src/libxr/pin_data/stm32-C0.json.gz differ diff --git a/src/libxr/pin_data/stm32-F0.json.gz b/src/libxr/pin_data/stm32-F0.json.gz new file mode 100644 index 0000000..3791154 Binary files /dev/null and b/src/libxr/pin_data/stm32-F0.json.gz differ diff --git a/src/libxr/pin_data/stm32-F1.json.gz b/src/libxr/pin_data/stm32-F1.json.gz new file mode 100644 index 0000000..8cc4544 Binary files /dev/null and b/src/libxr/pin_data/stm32-F1.json.gz differ diff --git a/src/libxr/pin_data/stm32-F2.json.gz b/src/libxr/pin_data/stm32-F2.json.gz new file mode 100644 index 0000000..b1c60e5 Binary files /dev/null and b/src/libxr/pin_data/stm32-F2.json.gz differ diff --git a/src/libxr/pin_data/stm32-F3.json.gz b/src/libxr/pin_data/stm32-F3.json.gz new file mode 100644 index 0000000..6f502f8 Binary files /dev/null and b/src/libxr/pin_data/stm32-F3.json.gz differ diff --git a/src/libxr/pin_data/stm32-F4.json.gz b/src/libxr/pin_data/stm32-F4.json.gz new file mode 100644 index 0000000..6c2cc87 Binary files /dev/null and b/src/libxr/pin_data/stm32-F4.json.gz differ diff --git a/src/libxr/pin_data/stm32-F7.json.gz b/src/libxr/pin_data/stm32-F7.json.gz new file mode 100644 index 0000000..807b460 Binary files /dev/null and b/src/libxr/pin_data/stm32-F7.json.gz differ diff --git a/src/libxr/pin_data/stm32-G0.json.gz b/src/libxr/pin_data/stm32-G0.json.gz new file mode 100644 index 0000000..0608138 Binary files /dev/null and b/src/libxr/pin_data/stm32-G0.json.gz differ diff --git a/src/libxr/pin_data/stm32-G4.json.gz b/src/libxr/pin_data/stm32-G4.json.gz new file mode 100644 index 0000000..d86cfc8 Binary files /dev/null and b/src/libxr/pin_data/stm32-G4.json.gz differ diff --git a/src/libxr/pin_data/stm32-H5.json.gz b/src/libxr/pin_data/stm32-H5.json.gz new file mode 100644 index 0000000..8bbfd69 Binary files /dev/null and b/src/libxr/pin_data/stm32-H5.json.gz differ diff --git a/src/libxr/pin_data/stm32-H7.json.gz b/src/libxr/pin_data/stm32-H7.json.gz new file mode 100644 index 0000000..46868c2 Binary files /dev/null and b/src/libxr/pin_data/stm32-H7.json.gz differ diff --git a/src/libxr/pin_data/stm32-L0.json.gz b/src/libxr/pin_data/stm32-L0.json.gz new file mode 100644 index 0000000..271ac06 Binary files /dev/null and b/src/libxr/pin_data/stm32-L0.json.gz differ diff --git a/src/libxr/pin_data/stm32-L1.json.gz b/src/libxr/pin_data/stm32-L1.json.gz new file mode 100644 index 0000000..ce020df Binary files /dev/null and b/src/libxr/pin_data/stm32-L1.json.gz differ diff --git a/src/libxr/pin_data/stm32-L4.json.gz b/src/libxr/pin_data/stm32-L4.json.gz new file mode 100644 index 0000000..f15dfd8 Binary files /dev/null and b/src/libxr/pin_data/stm32-L4.json.gz differ diff --git a/src/libxr/pin_data/stm32-L5.json.gz b/src/libxr/pin_data/stm32-L5.json.gz new file mode 100644 index 0000000..258e45e Binary files /dev/null and b/src/libxr/pin_data/stm32-L5.json.gz differ diff --git a/src/libxr/pin_data/stm32-MP1.json.gz b/src/libxr/pin_data/stm32-MP1.json.gz new file mode 100644 index 0000000..8dc909d Binary files /dev/null and b/src/libxr/pin_data/stm32-MP1.json.gz differ diff --git a/src/libxr/pin_data/stm32-MP2.json.gz b/src/libxr/pin_data/stm32-MP2.json.gz new file mode 100644 index 0000000..4df2e79 Binary files /dev/null and b/src/libxr/pin_data/stm32-MP2.json.gz differ diff --git a/src/libxr/pin_data/stm32-N6.json.gz b/src/libxr/pin_data/stm32-N6.json.gz new file mode 100644 index 0000000..1dc11af Binary files /dev/null and b/src/libxr/pin_data/stm32-N6.json.gz differ diff --git a/src/libxr/pin_data/stm32-U0.json.gz b/src/libxr/pin_data/stm32-U0.json.gz new file mode 100644 index 0000000..aa9ddd6 Binary files /dev/null and b/src/libxr/pin_data/stm32-U0.json.gz differ diff --git a/src/libxr/pin_data/stm32-U3.json.gz b/src/libxr/pin_data/stm32-U3.json.gz new file mode 100644 index 0000000..62dbb7d Binary files /dev/null and b/src/libxr/pin_data/stm32-U3.json.gz differ diff --git a/src/libxr/pin_data/stm32-U5.json.gz b/src/libxr/pin_data/stm32-U5.json.gz new file mode 100644 index 0000000..ad7c7d5 Binary files /dev/null and b/src/libxr/pin_data/stm32-U5.json.gz differ diff --git a/src/libxr/pin_data/stm32-WB0.json.gz b/src/libxr/pin_data/stm32-WB0.json.gz new file mode 100644 index 0000000..6175d9d Binary files /dev/null and b/src/libxr/pin_data/stm32-WB0.json.gz differ diff --git a/src/libxr/pin_data/stm32-WB1.json.gz b/src/libxr/pin_data/stm32-WB1.json.gz new file mode 100644 index 0000000..71d4f7a Binary files /dev/null and b/src/libxr/pin_data/stm32-WB1.json.gz differ diff --git a/src/libxr/pin_data/stm32-WB3.json.gz b/src/libxr/pin_data/stm32-WB3.json.gz new file mode 100644 index 0000000..c7c8a39 Binary files /dev/null and b/src/libxr/pin_data/stm32-WB3.json.gz differ diff --git a/src/libxr/pin_data/stm32-WB5.json.gz b/src/libxr/pin_data/stm32-WB5.json.gz new file mode 100644 index 0000000..f4351fb Binary files /dev/null and b/src/libxr/pin_data/stm32-WB5.json.gz differ diff --git a/src/libxr/pin_data/stm32-WBA2.json.gz b/src/libxr/pin_data/stm32-WBA2.json.gz new file mode 100644 index 0000000..983488d Binary files /dev/null and b/src/libxr/pin_data/stm32-WBA2.json.gz differ diff --git a/src/libxr/pin_data/stm32-WBA5.json.gz b/src/libxr/pin_data/stm32-WBA5.json.gz new file mode 100644 index 0000000..77ed192 Binary files /dev/null and b/src/libxr/pin_data/stm32-WBA5.json.gz differ diff --git a/src/libxr/pin_data/stm32-WBA6.json.gz b/src/libxr/pin_data/stm32-WBA6.json.gz new file mode 100644 index 0000000..62db71d Binary files /dev/null and b/src/libxr/pin_data/stm32-WBA6.json.gz differ diff --git a/src/libxr/pin_data/stm32-WL3.json.gz b/src/libxr/pin_data/stm32-WL3.json.gz new file mode 100644 index 0000000..1f2e2ab Binary files /dev/null and b/src/libxr/pin_data/stm32-WL3.json.gz differ diff --git a/src/libxr/pin_data/stm32-WL4.json.gz b/src/libxr/pin_data/stm32-WL4.json.gz new file mode 100644 index 0000000..5b17fc1 Binary files /dev/null and b/src/libxr/pin_data/stm32-WL4.json.gz differ diff --git a/src/libxr/pin_data/stm32-WL5.json.gz b/src/libxr/pin_data/stm32-WL5.json.gz new file mode 100644 index 0000000..cde30f4 Binary files /dev/null and b/src/libxr/pin_data/stm32-WL5.json.gz differ diff --git a/src/libxr/pin_data/stm32.json.gz b/src/libxr/pin_data/stm32.json.gz new file mode 100644 index 0000000..1353b78 Binary files /dev/null and b/src/libxr/pin_data/stm32.json.gz differ diff --git a/src/libxr/pin_layout.py b/src/libxr/pin_layout.py new file mode 100644 index 0000000..e3eb5fe --- /dev/null +++ b/src/libxr/pin_layout.py @@ -0,0 +1,541 @@ +"""libxr pins:由型号给出芯片的封装和引脚布局。 +libxr pins: the package and pin layout of a chip, from its model. + +每个引脚给出封装上的位置、名称、类型和全部可选信号。数据来自厂商的引脚数据(STM32:ST 的 +STM32_open_pin_data;MSPM0:TI SysConfig 的器件数据),由 scripts/build_pin_data.py 生成并放在 +pin_data/ 中,与许可证文本一起分发。 +Each pin gives its position on the package, its name, its type and all its selectable signals. +The data comes from the vendors' pin data (STM32: ST's STM32_open_pin_data; MSPM0: the device +data of TI SysConfig); scripts/build_pin_data.py writes it to pin_data/, which is distributed +with the license texts. +""" + +import gzip +import json +import logging +import re +import sys +from dataclasses import dataclass, field +from functools import cache +from pathlib import Path + +import yaml +from xr_syntax.i18n import tr + +DATA = Path(__file__).with_name("pin_data") + + +@dataclass +class Pin: + """封装上的一个引脚:位置、名称、类型、可选信号,以及平台给出的其他字段。 + One pin of a package: position, name, type, selectable signals, and the other fields a + platform gives. + """ + + position: str + name: str + type: str + signals: list[str] + extra: dict = field(default_factory=dict) + + +@dataclass +class PinLayout: + """一个型号的引脚布局:平台、型号、数据中的器件名、封装、数据来源和全部引脚。 + The pin layout of a model: platform, model, the part name in the data, package, data source + and all pins. + """ + + platform: str + model: str + part: str + package: str + source: dict + pins: list[Pin] + + +@cache +def load_data(platform: str) -> dict: + """读取并缓存一个平台的数据文件。 + Read and cache the data file of a platform. + """ + with gzip.open(DATA / f"{platform}.json.gz", "rt", encoding="utf-8") as file: + return json.load(file) + + +@cache +def load_stm32_shard(shard: str) -> dict: + """读取并缓存 STM32 的一个数据分片(一个系列的引脚集)。只读用到的分片,命令因此启动得快, + 占用的内存也小。 + Read and cache one data shard of the STM32 (the pin sets of a series). Only the shard in use + is read, which keeps the command quick to start and its memory small. + """ + with gzip.open(DATA / f"stm32-{shard}.json.gz", "rt", encoding="utf-8") as file: + return json.load(file) + + +def stm32_candidates(model: str) -> list[str]: + """型号在 ST 数据中可能的写法:原样,以及把温度等级数字换成 X(STM32H723VGT6 为 + STM32H723VGTX)。 + The ways a model may be written in ST's data: as it is, and with the temperature digit + replaced by X (STM32H723VGTX for STM32H723VGT6). + """ + candidates = [model] + match = re.fullmatch(r"(.*?[A-Z])[0-9]([A-Z]*)", model) + if match: + candidates.append(f"{match.group(1)}X{match.group(2)}") + return candidates + + +def layout_stm32(model: str, package: str | None) -> PinLayout: + """STM32 型号的布局。封装包含在型号中,给出的 package 必须与之相同。 + The layout of an STM32 model. The package is part of the model; a given package must be the + same. + """ + data = load_data("stm32") + by_upper = {name.upper(): name for name in data["parts"]} + for candidate in stm32_candidates(model): + if candidate in by_upper: + part = by_upper[candidate] + break + else: + near = sorted(name for name in by_upper.values() if name.upper().startswith(model[:10])) + hint = tr(f"; similar: {', '.join(near[:5])}", f";相近的有:{'、'.join(near[:5])}") + raise ValueError( + tr(f"Unknown STM32 model: {model}", f"未知的 STM32 型号:{model}") + + (hint if near else "") + ) + shard, key = data["parts"][part] + found = load_stm32_shard(shard)[key] + if package is not None and package.upper() != found["package"].upper(): + raise ValueError( + tr( + f"{model} comes in {found['package']}, not {package}; the package is part of " + "the STM32 model", + f"{model} 的封装是 {found['package']},不是 {package};STM32 的封装由型号决定", + ) + ) + pins = [ + Pin(position, name, kind, signals, {"gpio_modes": modes} if modes else {}) + for position, name, kind, signals, modes in found["pins"] + ] + return PinLayout("stm32", model, part, found["package"], data["source"], pins) + + +def mspm0_family(model: str, data: dict) -> tuple[str, dict, str]: + """model 所属的器件族:名称、数据,以及型号中器件名之后的部分(含封装代码)。取最长的前缀。 + The family a model belongs to: its name, its data, and the part of the model after the + device name (which holds the package code). The longest prefix wins. + """ + best = None + for name, family in data["families"].items(): + for prefix in family["prefixes"]: + if model.startswith(prefix) and (best is None or len(prefix) > len(best[2])): + best = (name, family, prefix) + if best is None: + raise ValueError(tr(f"Unknown MSPM0 model: {model}", f"未知的 MSPM0 型号:{model}")) + name, family, prefix = best + # 器件族名以 X 结尾时,前缀之后还有一位型号数字。 + # A family name ending in X leaves one more digit of the model after the prefix. + wildcard = name.endswith("X") + return name, family, model[len(prefix) + (1 if wildcard else 0) :] + + +def code_of(name: str) -> str: + """MSPM0 封装名括号里的代码:LQFP-64(PM) 为 PM,没有括号时为空。 + The code in parentheses of an MSPM0 package name: PM for LQFP-64(PM), empty without + parentheses. + """ + match = re.search(r"\(([^)]+)\)$", name) + return match.group(1) if match else "" + + +def mspm0_package(model: str, family: dict, rest: str, package: str | None) -> str: + """MSPM0 的封装名:先看 package(封装名、括号中的代码或不含代码的名称),没有时在型号的 + 后缀中找封装代码。 + The package name of an MSPM0: package first (the name, the code in parentheses, or the name + without the code), otherwise the package code in the suffix of the model. + """ + names = list(family["packages"]) + if package is not None: + wanted = package.upper() + for name in names: + if wanted in (name.upper(), code_of(name).upper(), name.split("(")[0].upper()): + return name + raise ValueError( + tr( + f"{model} has no package {package}; this family has {', '.join(names)}", + f"{model} 没有封装 {package};该器件族有 {'、'.join(names)}", + ) + ) + codes = [name for name in names if code_of(name) and code_of(name).upper() in rest] + if not codes: + raise ValueError( + tr( + f"Cannot tell the package of {model}; give it with --package ({', '.join(names)})", + f"无法从 {model} 判断封装;请用 --package 给出({'、'.join(names)})", + ) + ) + return max(codes, key=lambda name: len(code_of(name))) + + +def layout_mspm0(model: str, package: str | None) -> PinLayout: + """MSPM0 型号的布局。封装来自 package,或来自型号后缀中的封装代码(MSPM0G3507SPMR 的 PM)。 + The layout of an MSPM0 model. The package comes from package, or from the package code in + the model suffix (PM of MSPM0G3507SPMR). + """ + data = load_data("mspm0") + _, family, rest = mspm0_family(model, data) + chosen = mspm0_package(model, family, rest, package) + pins = [ + Pin( + position, + name, + kind, + [signal for signal, _ in signals], + {"iomux_pincm": pincm, "modes": {signal: mode for signal, mode in signals}}, + ) + for position, name, kind, pincm, signals in family["packages"][chosen] + ] + part = re.match(r"MSPM0[A-Z]\d{4}", model) + return PinLayout("mspm0", model, part.group(0) if part else model, chosen, data["source"], pins) + + +def layout_hpm(model: str, package: str | None) -> PinLayout: + """HPM SoC 的布局:型号即 SoC 名(HPM5301),封装名与数据一致(QFN48)。 + The layout of an HPM SoC: the model is the SoC name (HPM5301), and the package name matches + the data (QFN48). + """ + data = load_data("hpm") + socs = data["socs"] + soc = socs.get(model) + if soc is None: + raise ValueError( + tr( + f"Unknown HPM SoC: {model}; this data has {', '.join(socs)}", + f"未知的 HPM SoC:{model};数据里有 {'、'.join(socs)}", + ) + ) + packages = soc["packages"] + chosen = None + if package is not None: + chosen = next((name for name in packages if package.upper() == name.upper()), None) + if chosen is None: + raise ValueError( + tr( + f"{model} has no package {package}; this SoC has {', '.join(packages)}", + f"{model} 没有封装 {package};该 SoC 有 {'、'.join(packages)}", + ) + ) + elif len(packages) == 1: + chosen = next(iter(packages)) + if chosen is None: + raise ValueError( + tr( + f"Cannot tell the package of {model}; give it with --package" + f" ({', '.join(packages)})", + f"无法从 {model} 判断封装;请用 --package 给出({'、'.join(packages)})", + ) + ) + pins = [ + Pin( + position, + name, + kind, + [signal for signal, _ in signals], + {"modes": {signal: mode for signal, mode in signals}}, + ) + for position, name, kind, _, signals in packages[chosen] + ] + return PinLayout("hpm", model, model, chosen, data["source"], pins) + + +def recognize_stm32(signal: str) -> tuple[str, str, str] | None: + """识别 ST 的信号名:返回外设实例、外设类型和功能,不是外设信号(GPIO)时返回 None。 + Recognize an ST signal name: return the peripheral instance, the peripheral kind and the + function, or None when the signal is not a peripheral's (GPIO). + + 名字是 <实例>_<功能>:USART1_TX 为 USART1、USART、TX。类型是实例去掉末尾的数字(I2C2 为 + I2C)。USB_OTG_HS_DM、USB_DRD_FS_DP 和 OCTOSPIM_P1_IO2 的实例有多段,单独处理。识别只看 + 名字,不依赖 LibXR 是否有对应的外设。 + A name is _: USART1_TX is USART1, USART, TX. The kind is the instance + without its trailing digits (I2C for I2C2). The instances of USB_OTG_HS_DM, USB_DRD_FS_DP and + OCTOSPIM_P1_IO2 have several parts and are handled separately. Recognition looks at the name + only, whether or not LibXR has a matching peripheral. + """ + match = re.fullmatch(r"(USB_OTG|USB_DRD)_([A-Z]+)_(.+)", signal) + if match: + return f"{match.group(1)}_{match.group(2)}", match.group(1), match.group(3) + match = re.fullmatch(r"(OCTOSPIM)_(P\d+)_(.+)", signal) + if match: + return f"{match.group(1)}_{match.group(2)}", match.group(1), match.group(3) + match = re.fullmatch(r"([A-Za-z0-9]+?)_(.+)", signal) + if match is not None: + return match.group(1), re.sub(r"\d+$", "", match.group(1)), match.group(2) + # 没有下划线的信号(CEC、BOOT0、AUDIOCLK)自己就是实例和功能;GPIO 只是引脚的普通用法。 + # A signal without an underscore (CEC, BOOT0, AUDIOCLK) is its own instance and function; + # GPIO is only the plain use of the pin. + if signal == "GPIO": + return None + return signal, re.sub(r"\d+$", "", signal), signal + + +def recognize_mspm0(signal: str) -> tuple[str, str, str] | None: + """识别 TI 的信号名:<实例>.<功能>,UART0.TX 为 UART0、UART、TX;引脚自己的名字(PA0) + 不是外设信号,返回 None。 + Recognize a TI signal name, .: UART0.TX is UART0, UART, TX; the name of + the pin itself (PA0) is not a peripheral's, so None. + """ + match = re.fullmatch(r"([A-Za-z0-9]+)\.(.+)", signal) + if match is None: + return None + return match.group(1), re.sub(r"\d+$", "", match.group(1)), match.group(2) + + +def recognize_hpm(signal: str) -> tuple[str, str, str] | None: + """识别 HPM 的信号名(hpm_iomux.h 的宏名):UART0_TXD 为 UART0、UART、TXD;GPIO 的复用 + (GPIO_A_00)是引脚自己的普通用法,返回 None。 + Recognize an HPM signal name (the macro name of hpm_iomux.h): UART0_TXD is UART0, UART, TXD; + the GPIO mux (GPIO_A_00) is the pin's own plain use, so None. + """ + if signal.startswith("GPIO_"): + return None + match = re.fullmatch(r"([A-Za-z0-9]+)_(.+)", signal) + if match is None: + return None + return match.group(1), re.sub(r"\d+$", "", match.group(1)), match.group(2) + + +@cache +def hpm_soc_has_pwm(model: str) -> bool: + """SoC 的引脚数据里有没有 PWM 外设的信号(HPM5361 有 20 个,HPM5301 没有):有的 SoC 上 + GPTMR 只作展示,没有的才生成 HPMPWM 的 fallback 对象。 + Whether the pin data of the SoC has PWM peripheral signals (HPM5361 has 20, HPM5301 has + none): on a SoC with them a GPTMR is display-only, and only a SoC without them generates + the HPMPWM fallback objects. + """ + socs = load_data("hpm")["socs"] + soc = socs.get(model) + if soc is None: + # 不认识的 SoC 不能当作"没有 PWM":那样会给有 PWM 外设的芯片生成 GPTMR 的对象。 + # An unknown SoC must not count as "no PWM": that would generate GPTMR objects for a + # chip that has a PWM peripheral. + raise ValueError( + tr( + f"Unknown HPM SoC: {model}; this data has {', '.join(socs)}", + f"未知的 HPM SoC:{model};数据里有 {'、'.join(socs)}", + ) + ) + for package in soc["packages"].values(): + for _position, _name, _kind, _x, signals in package: + for signal, _mode in signals: + recognized = recognize_hpm(signal) + if recognized is not None and recognized[1] == "PWM": + return True + return False + + +# 各平台识别信号名的函数。 +# The function of each platform that recognizes a signal name. +RECOGNIZERS = {"stm32": recognize_stm32, "mspm0": recognize_mspm0, "hpm": recognize_hpm} + + +def natural_key(name: str) -> list: + """自然排序的键:USART2 排在 USART10 之前。 + A natural sort key: USART2 sorts before USART10. + """ + return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", name)] + + +# 定时器的哪些功能是可以输出 PWM 的通道:类型 -> 功能的正则。 +# Which functions of a timer are channels that can output PWM: kind -> regex of the function. +PWM_CHANNELS = { + "TIM": r"CH\d+N?", + "LPTIM": r"OUT|CH\d+", + "HRTIM": r"CH[A-F]\d", + "TIMA": r"CCP\d+(_CMPL)?", + "TIMG": r"CCP\d+(_CMPL)?", + "GPTMR": r"COMP_\d+", + "PWM": r"P_\d+", +} + + +def gpio_port_and_line(pin_name: str) -> tuple[str, str] | None: + """引脚名的端口字母和线号:PA9 为 (A, 9),PC2_C 为 (C, 2);不是 P<字母><数字> 形式时为 None。 + The port letter and the line number of a pin name: (A, 9) for PA9, (C, 2) for PC2_C; None + when the name is not of the form P. + """ + match = re.match(r"P([A-Z])(\d+)", pin_name) + return (match.group(1), match.group(2)) if match else None + + +def pin_has_gpio(pin: Pin) -> bool: + """引脚能当普通 GPIO:ST 的信号里有 GPIO;TI 的信号里有引脚自己的名字(PA0);HPM 的信号里 + 有它的 GPIO 复用(PA10 的 GPIO_A_10)。 + Whether the pin can be a plain GPIO: ST lists GPIO among the signals, TI lists the name of + the pin itself (PA0), and HPM lists its GPIO mux (GPIO_A_10 for PA10). + """ + if "GPIO" in pin.signals or pin.name in pin.signals: + return True + port_line = gpio_port_and_line(pin.name) + return port_line is not None and f"GPIO_{port_line[0]}_{port_line[1]}" in pin.signals + + +def peripheral_index(layout: PinLayout) -> dict: + """布局中识别出的全部外设:实例名 -> 类型和各功能可选的引脚。 + All the peripherals recognized in the layout: instance name -> kind and, per function, the + pins that can carry it. + + 除信号名中的外设外,还有:GPIOA、GPIOB……(普通 GPIO,每条线 P0、P1……);EXTI(外部中断, + 线 LINE0……可选的引脚取自 ST 数据中 GPIO 的 EXTI 模式);可输出 PWM 的定时器带 + capabilities: [pwm]。 + Besides the peripherals in the signal names there are GPIOA, GPIOB, ... (plain GPIO, one + function P0, P1, ... per line); EXTI (external interrupts, lines LINE0, ..., the pins taken + from the EXTI mode of the GPIO in ST's data); and a timer that can output PWM carries + capabilities: [pwm]. + """ + recognize = RECOGNIZERS[layout.platform] + found: dict[str, dict] = {} + + def add(instance: str, kind: str, function: str, pin: Pin) -> None: + """把 pin 记为外设 instance 的 function 可选的引脚。 + Record pin as one that can carry the function of the peripheral instance. + """ + entry = found.setdefault(instance, {"kind": kind, "signals": {}}) + entry["signals"].setdefault(function, []).append(pin.name) + + for pin in layout.pins: + for signal in pin.signals: + recognized = recognize(signal) + if recognized is not None: + add(*recognized, pin) + port_line = gpio_port_and_line(pin.name) + if port_line is not None and pin_has_gpio(pin): + # 线号去掉前导零:HPM 的 PA03 与 STM32 的 PA3 一样是 P3。 + # The line drops its leading zeros: the PA03 of an HPM is P3, like the PA3 of an STM32. + port, line = port_line[0], int(port_line[1]) + add(f"GPIO{port}", "GPIO", f"P{line}", pin) + if any(mode.startswith("EXTI") for mode in pin.extra.get("gpio_modes", [])): + add("EXTI", "EXTI", f"LINE{line}", pin) + index = {} + for instance in sorted(found, key=natural_key): + entry = found[instance] + index[instance] = { + "kind": entry["kind"], + "signals": dict(sorted(entry["signals"].items(), key=lambda s: natural_key(s[0]))), + } + pattern = PWM_CHANNELS.get(entry["kind"]) + # HPM 的 GPTMR 只在 SoC 没有 PWM 外设时由 HPMPWM 驱动(fallback 路径)。 + # An HPM GPTMR is driven by HPMPWM only when the SoC has no PWM peripheral (the + # fallback path). + if entry["kind"] == "GPTMR" and layout.platform == "hpm" and hpm_soc_has_pwm(layout.part): + pattern = None + if pattern and any(re.fullmatch(pattern, function) for function in entry["signals"]): + index[instance]["capabilities"] = ["pwm"] + return index + + +# 各平台:名称、型号前缀和布局函数。新平台在此加一行。 +# The platforms: name, model prefix and layout function. A new platform adds a line here. +PLATFORMS = ( + ("stm32", "STM32", layout_stm32), + ("mspm0", "MSPM0", layout_mspm0), + ("hpm", "HPM", layout_hpm), +) + + +def layout_pins(model: str, package: str | None = None) -> PinLayout: + """由型号的前缀选平台,返回该型号的引脚布局;型号或封装无法识别时抛出 ValueError。 + Choose the platform by the prefix of the model and return the pin layout of the model; a + model or package that is not recognized raises ValueError. + """ + model = model.strip().upper() + for _, prefix, layout in PLATFORMS: + if model.startswith(prefix): + return layout(model, package) + raise ValueError( + tr( + f"No supported platform for the model {model} (supported: " + f"{', '.join(prefix for _, prefix, _ in PLATFORMS)})", + f"型号 {model} 不属于任何支持的平台(支持:{'、'.join(p for _, p, _ in PLATFORMS)})", + ) + ) + + +def layout_to_dict(layout: PinLayout) -> dict: + """布局的字典形式,用于输出;各平台的其他字段与 position、name、type、signals 并列。 + The layout as a dict for output; the other fields of a platform sit next to position, name, + type and signals. + + pin_count 是封装上的位置数。一个位置可以有多个条目:STM32G0 的引脚可以重映射,PA9 和 PA11 + 共用一个位置,名称写成 PA11 [PA9] 和 PA9 [PA11]。 + pin_count is the number of positions on the package. A position can have several entries: + the pins of an STM32G0 can be remapped, so PA9 and PA11 share one position, written + PA11 [PA9] and PA9 [PA11]. + + peripherals 列出识别出的全部外设(USART1、ETH、FMC、GPIOA、EXTI……),每个功能可选的引脚 + 都在其中;它与 LibXR 是否有对应的外设无关。 + peripherals lists every peripheral recognized (USART1, ETH, FMC, GPIOA, EXTI, ...) with the + pins that can carry each function, whether or not LibXR has a matching peripheral. + """ + return { + "model": layout.model, + "platform": layout.platform, + "part": layout.part, + "package": layout.package, + "pin_count": len({pin.position for pin in layout.pins}), + "source": layout.source, + "peripherals": peripheral_index(layout), + "pins": [ + { + "position": pin.position, + "name": pin.name, + "type": pin.type, + "signals": pin.signals, + **pin.extra, + } + for pin in layout.pins + ], + } + + +def print_pin_layout( + model: str | None, + package: str | None, + output_format: str, + directory: str | None = None, + libxr_config: str | None = None, +) -> None: + """以 YAML 或 JSON 向标准输出打印型号的引脚布局;给出 directory 时叠加工程已选的信号。 + Print the pin layout of a model as YAML or JSON to standard output; with directory, the + signals the project has selected are overlaid. + + 型号、封装或工程无法识别时记录错误(写到标准错误)并以状态 1 退出;输出不会混入报错。 + A model, package or project that is not recognized logs an error, on standard error, and + exits with status 1; the output never mixes with the error. + """ + try: + if directory is not None: + from libxr.pin_project import layout_with_project + + info = layout_with_project(directory, model, package, libxr_config) + elif model is None: + raise ValueError( + tr( + "Give a chip model, or a project directory with -d", + "请给出芯片型号,或用 -d 给出工程目录", + ) + ) + else: + info = layout_to_dict(layout_pins(model, package)) + except ValueError as error: + logging.error(str(error)) + sys.exit(1) + if output_format == "json": + print(json.dumps(info, ensure_ascii=False, indent=2)) + else: + print( + yaml.safe_dump( + info, sort_keys=False, allow_unicode=True, default_flow_style=False + ).rstrip() + ) diff --git a/src/libxr/pin_project.py b/src/libxr/pin_project.py new file mode 100644 index 0000000..4ae557a --- /dev/null +++ b/src/libxr/pin_project.py @@ -0,0 +1,1069 @@ +"""libxr pins -d:把工程里已选的引脚信号叠加到布局上,并对应到 libxr_config.yaml 的外设设置。 +libxr pins -d: overlay the pin signals a project has selected on the layout, and tie them to the +peripheral settings of libxr_config.yaml. + +STM32 的已选信号来自 CubeMX 的 .ioc(Pxn.Signal),MSPM0 的来自根目录 SysConfig 工程 +(.syscfg)的 $assign 行(求解器选的 $suggestSolution 作兜底),HPM 的来自 boards/ 下 .hpmpc +里 main.c 调用的 pinmux 函数。信号按布局中该引脚的可选信号核对;核对不上的原样给出, +matched 为 false。 +The selected signals of an STM32 come from the CubeMX .ioc (Pxn.Signal), those of an MSPM0 from +the $assign lines of a SysConfig project (.syscfg) in the root (the solver's $suggestSolution +as the fallback), and those of an HPM from the pinmux functions of the .hpmpc under boards/ +that the root main.c calls. A signal is checked against the selectable signals of its pin in the +layout; one that does not match is given as it is, with matched false. +""" + +import json +import logging +import re +from dataclasses import dataclass +from functools import cache +from pathlib import Path + +import yaml +from xr_syntax.i18n import tr + +from libxr import pin_layout +from libxr.pin_layout import Pin, PinLayout + +# 外设类型到 libxr_config.yaml 的段:libxr gen 为这些类型生成对象。 +# Peripheral kind to the section of libxr_config.yaml: libxr gen generates objects for these. +STM32_SECTIONS = { + "USART": "USART", + "UART": "USART", + "SPI": "SPI", + "I2C": "I2C", + "ADC": "ADC", + "DAC": "DAC", + "TIM": "TIM", + "CAN": "CAN", + "FDCAN": "FDCAN", + "USB_OTG": "USB", + "USB_DRD": "USB", +} +# USB 实例在 libxr_config.yaml 中的键;USB_DRD_FS 和 USB 都叫 usb_fs。 +# The key of a USB instance in libxr_config.yaml; USB_DRD_FS and USB are both usb_fs. +USB_KEYS = {"USB_DRD_FS": "usb_fs", "USB": "usb_fs"} + + +def read_ioc(path: Path) -> tuple[str, dict[str, dict], set[str]]: + """读取 .ioc:返回芯片型号(Mcu.UserName)、引脚键 -> {signal, label} 和启用的外设(Mcu.IPn)。 + Read an .ioc: return the chip model (Mcu.UserName), pin key -> {signal, label} and the + enabled peripherals (Mcu.IPn). + + 引脚键可以带后缀,如 PH0-OSC_IN\\ (PH0);转义的空格还原为空格。 + A pin key may carry a suffix such as PH0-OSC_IN\\ (PH0); an escaped space is restored. + """ + model = "" + pins: dict[str, dict] = {} + enabled: set[str] = set() + for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + match = re.match(r"^(P[A-Z]\d+[^=]*?)\.(Signal|GPIO_Label)=(.*)$", line) + if match: + key = match.group(1).replace("\\ ", " ") + field = "signal" if match.group(2) == "Signal" else "label" + pins.setdefault(key, {})[field] = match.group(3).strip() + elif line.startswith("Mcu.UserName="): + model = line.split("=", 1)[1].strip() + elif re.match(r"Mcu\.IP\d+=", line): + enabled.add(line.split("=", 1)[1].strip()) + return model, {key: value for key, value in pins.items() if "signal" in value}, enabled + + +def find_pin(layout: PinLayout, key: str) -> Pin | None: + """布局中与 .ioc 引脚键对应的引脚:先按名字,再按 P<字母><数字> 开头(PH0-OSC_IN (PH0))。 + The pin of the layout that an .ioc pin key stands for: by name first, then by the leading + P (PH0-OSC_IN (PH0)). + """ + for pin in layout.pins: + if pin.name == key: + return pin + base = re.match(r"P[A-Z]\d+", key) + if base is None: + return None + for pin in layout.pins: + if re.match(rf"{base.group(0)}(?!\d)", pin.name): + return pin + return None + + +def resolve_stm32(pin: Pin, signal: str, enabled: set[str]) -> dict: + """一个 .ioc 信号对应的外设、类型和功能。 + The peripheral, kind and function an .ioc signal stands for. + + CubeMX 在 .ioc 里有几种写法与 ST 的信号名不同:GPIO_Output、GPIO_Input 和 GPXTIn(GPIO、 + 外部中断)、S_TIM2_CH1_ETR(共享引脚)、ADCx_INP19(不带实例的 ADC 通道)、FMC_D2_DA2( + 共用前缀的两个功能)、ETH_TXD1(省掉 MII 或 RMII)、SharedAnalog_PA4 和 COMP_DAC11_group + (模拟)。 + CubeMX writes several forms in the .ioc that differ from ST's signal names: GPIO_Output, + GPIO_Input and GPXTIn (GPIO, external interrupt), S_TIM2_CH1_ETR (shared pin), ADCx_INP19 (an + ADC channel without its instance), FMC_D2_DA2 (two functions that share a prefix), ETH_TXD1 + (MII or RMII left out), SharedAnalog_PA4 and COMP_DAC11_group (analog). + """ + port_line = pin_layout.gpio_port_and_line(pin.name) + gpio = f"GPIO{port_line[0]}" if port_line else "GPIO" + function = f"P{port_line[1]}" if port_line else "GPIO" + if re.fullmatch(r"GPIO_(Input|Output|Analog)", signal): + return {"peripheral": gpio, "kind": "GPIO", "function": function, "matched": True} + external = re.fullmatch(r"GPXTI(\d+)", signal) + if external: + return { + "peripheral": "EXTI", + "kind": "EXTI", + "function": f"LINE{external.group(1)}", + "matched": True, + } + if signal.startswith(("SharedAnalog_", "COMP_DAC")): + return {"peripheral": "ANALOG", "kind": "ANALOG", "function": signal, "matched": True} + vendor = set(pin.signals) + inner = signal[2:] if signal.startswith("S_") else signal + shared = re.fullmatch(r"([A-Z]+)(\d*)(DFSDM\d)", inner) if signal.startswith("S_") else None + if shared: + # S_DATAIN1DFSDM1 是 DFSDM1_DATIN1。 + # S_DATAIN1DFSDM1 is DFSDM1_DATIN1. + inner = f"{shared.group(3)}_{shared.group(1).replace('DATAIN', 'DATIN')}{shared.group(2)}" + adc = re.fullmatch(r"ADCx_(.+)", inner) + if adc: + instances = sorted( + { + pin_layout.recognize_stm32(s)[0] + for s in vendor + if re.fullmatch(rf"ADC\d*_{re.escape(adc.group(1))}", s) + }, + key=pin_layout.natural_key, + ) + # 有多个 ADC 可选时,取工程里启用的那个。 + # With several ADCs to choose from, take the one the project enables. + if len(instances) > 1 and len([i for i in instances if i in enabled]) == 1: + instances = [i for i in instances if i in enabled] + found = { + "peripheral": "ADC", + "kind": "ADC", + "function": adc.group(1), + "matched": bool(instances), + } + if len(instances) == 1: + found["peripheral"] = instances[0] + elif instances: + found["candidates"] = instances + return found + if inner in vendor: + peripheral, kind, function = pin_layout.recognize_stm32(inner) + return {"peripheral": peripheral, "kind": kind, "function": function, "matched": True} + for known in sorted(vendor, key=len, reverse=True): + # FMC_D2_DA2:前一个信号 FMC_D2 加上共用前缀的 FMC_DA2。 + # FMC_D2_DA2: the signal FMC_D2 plus FMC_DA2, which shares its prefix. + prefix = inner.split("_")[0] + "_" + if inner.startswith(known + "_") and prefix + inner[len(known) + 1 :] in vendor: + first = pin_layout.recognize_stm32(known) + second = pin_layout.recognize_stm32(prefix + inner[len(known) + 1 :]) + return { + "peripheral": first[0], + "kind": first[1], + "function": f"{first[2]}/{second[2]}", + "matched": True, + } + for known in vendor: + # ETH_TXD1:ST 写成 ETH_MII_TXD1 或 ETH_RMII_TXD1。 + # ETH_TXD1: ST writes ETH_MII_TXD1 or ETH_RMII_TXD1. + if known.replace("_RMII_", "_", 1).replace("_MII_", "_", 1) == inner and inner.startswith( + "ETH_" + ): + peripheral, kind, _ = pin_layout.recognize_stm32(known) + return { + "peripheral": peripheral, + "kind": kind, + "function": inner.split("_", 1)[1], + "matched": True, + } + recognized = pin_layout.recognize_stm32(inner) + if recognized is None: + return {"peripheral": gpio, "kind": "GPIO", "function": function, "matched": False} + return { + "peripheral": recognized[0], + "kind": recognized[1], + "function": recognized[2], + "matched": False, + } + + +def stm32_assignments(directory: Path, layout_for) -> tuple[str, str, PinLayout, dict[str, dict]]: + """工程里 STM32 的已选信号:型号、来源文件、布局和 引脚名 -> 信号及其识别结果。 + The selected signals of an STM32 in a project: the model, the source file, the layout and + pin name -> signal and what it was recognized as. + + layout_for 由型号给出布局,使调用者可以自行决定封装。 + layout_for gives the layout of a model, so the caller decides the package. + """ + iocs = sorted(directory.glob("*.ioc")) + model, signals, enabled = read_ioc(iocs[0]) + layout = layout_for(model) + assigned: dict[str, dict] = {} + for key, value in signals.items(): + pin = find_pin(layout, key) + if pin is None: + continue + entry = {"signal": value["signal"], **resolve_stm32(pin, value["signal"], enabled)} + if "label" in value: + entry["label"] = value["label"] + assigned[pin.name] = entry + return model, iocs[0].name, layout, assigned + + +SYSCFG_ARGS = re.compile(r"^[ \t]*(?://|\*)?[ \t]*@(v2)?[Cc]li[Aa]rgs[ \t]+(.*)$", re.M) + + +@dataclass +class Syscfg: + """一个 .syscfg 的 @cliArgs 里的器件、封装和板子;没有的为 None。 + The device, package and board in the @cliArgs of a .syscfg; None for what it does not give. + """ + + device: str | None = None + package: str | None = None + board: str | None = None + + +def cli_option(arguments: str, name: str) -> str | None: + """@cliArgs 的参数串中 --name 的值(带引号或不带)。 + The value of --name in the argument string of an @cliArgs, quoted or not. + """ + match = re.search(rf'--{name}[ \t]+(?:"([^"]*)"|(\S+))', arguments) + return (match.group(1) if match.group(1) is not None else match.group(2)) if match else None + + +def read_syscfg(path: Path) -> Syscfg: + """读 .syscfg 开头的 @cliArgs 和 @v2CliArgs(后者在后,覆盖前者)。 + Read the @cliArgs and @v2CliArgs at the top of a .syscfg (the latter comes last and overrides + the former). + """ + info = Syscfg() + text = path.read_text(encoding="utf-8", errors="replace") + for _, arguments in sorted(SYSCFG_ARGS.findall(text), key=lambda found: found[0] == "v2"): + info.device = cli_option(arguments, "device") or info.device + info.package = cli_option(arguments, "package") or info.package + board = cli_option(arguments, "board") + info.board = board.rsplit("/", 1)[-1] if board else info.board + return info + + +@cache +def load_boards() -> dict: + """MSPM0 SDK 的板子(LP_MSPM0G3507 等)到器件和封装。 + The boards of the MSPM0 SDK (LP_MSPM0G3507, ...) to their device and package. + """ + path = pin_layout.DATA / "mspm0_boards.json" + return json.loads(path.read_text(encoding="utf-8"))["boards"] + + +SYSCFG_MODULE = re.compile( + r'^const\s+(\w+)\s*=\s*scripting\.addModule\(\s*"/ti/driverlib/(\w+)"', re.M +) +SYSCFG_INSTANCE = re.compile(r"^const\s+(\w+)\s*=\s*(\w+)\.addInstance\(\)", re.M) +SYSCFG_ASSIGNMENT = re.compile(r"^(\w+)((?:\.[$\w]+|\[\d+\])+)\s*=\s*(.+?);\s*$", re.M) +SYSCFG_GPIO_PIN = re.compile(r"^\.associatedPins\[(\d+)\]\.pin\.\$assign$") +SYSCFG_GPIO_PIN_SUGGESTED = re.compile(r"^\.associatedPins\[(\d+)\]\.pin\.\$suggestSolution$") +SYSCFG_GPIO_NAME = re.compile(r"^\.associatedPins\[(\d+)\]\.\$name$") +SYSCFG_PERIPH_PIN = re.compile(r"^\.peripheral\.(\w*Pin\w*)\.\$assign$") +SYSCFG_PERIPH_PIN_SUGGESTED = re.compile(r"^\.peripheral\.(\w*Pin\w*)\.\$suggestSolution$") +SYSCFG_ADC_PIN = re.compile(r"^[Aa][Dd][Cc]Pin(\d+)$") + + +def syscfg_value(text: str): + """.syscfg 中赋值的右边:字符串、数字、布尔值和数组按其类型,表达式原样保留为字符串。 + The right side of an assignment in a .syscfg: strings, numbers, booleans and arrays as what + they are; an expression is kept as written, as a string. + """ + text = text.strip() + try: + return json.loads(text) + except ValueError: + pass + if re.fullmatch(r"0[xX][0-9a-fA-F]+", text): + return int(text, 16) + return text + + +def read_syscfg_settings(path: Path) -> dict[str, dict]: + """.syscfg 中每个外设的设置:外设实例(UART0)-> 模块、名字(UART_0)和参数。 + The settings of each peripheral in a .syscfg: the peripheral instance (UART0) -> its module, + its name (UART_0) and its parameters. + + SysConfig 的配置是脚本:UART1.targetBaudRate = 9600; 和 UART1.peripheral.$assign = "UART0";。 + 变量 UART1 是模块 UART 的一个实例,peripheral.$assign(用户指定)或 $suggestSolution(求解器 + 给出)说明它用的外设。引脚的指定和求解器内部的 $ 项不是设置,不收。 + The configuration of SysConfig is a script: UART1.targetBaudRate = 9600; and + UART1.peripheral.$assign = "UART0";. The variable UART1 is an instance of the module UART, and + peripheral.$assign (the user's) or $suggestSolution (the solver's) names the peripheral it + uses. The assignment of pins and the solver's own $ items are not settings and are left out. + """ + text = path.read_text(encoding="utf-8", errors="replace") + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + text = re.sub(r"^[ \t]*//.*$", "", text, flags=re.M) + modules = {var: module for var, module in SYSCFG_MODULE.findall(text)} + instances = { + var: modules[module] for var, module in SYSCFG_INSTANCE.findall(text) if module in modules + } + found: dict[str, dict] = {} + for var, tail, value in SYSCFG_ASSIGNMENT.findall(text): + if var not in instances: + continue + entry = found.setdefault( + var, + { + "module": instances[var], + "name": None, + "assigned": None, + "suggested": None, + "params": {}, + }, + ) + if tail == ".$name": + entry["name"] = syscfg_value(value) + elif tail == ".peripheral.$assign": + entry["assigned"] = syscfg_value(value) + elif tail == ".peripheral.$suggestSolution": + entry["suggested"] = syscfg_value(value) + elif not tail.startswith(".peripheral.") and "$" not in tail: + entry["params"][tail.lstrip(".")] = syscfg_value(value) + settings = {} + for entry in found.values(): + peripheral = entry["assigned"] or entry["suggested"] + if peripheral: + settings[peripheral] = { + "module": entry["module"], + "name": entry["name"], + "params": entry["params"], + } + return settings + + +def peripheral_entry(pin: Pin, instance: str, member: str) -> dict: + """外设引脚的叠加项:在引脚的信号表中认出 <外设>.<功能>;认不出时按成员名原样给出。 + The overlay entry of a peripheral pin: recognize . among the signals of + the pin; without a match the signal is given from the member name as it is. + + 成员名是 SysConfig 的引脚成员:rxPin、cs0Pin、OutPin、adcPin3(数字是 ADC 的通道)。 + The member is a pin member of SysConfig: rxPin, cs0Pin, OutPin, adcPin3 (the digit is the + channel of the ADC). + + TI 对 CANFD 的功能名带 CAN 前缀(CANRX、CANTX),成员名没有,按后缀核对。 + TI names the functions of a CANFD with the CAN prefix (CANRX, CANTX) that the member names + leave out, so the check also accepts the suffix. + """ + adc = SYSCFG_ADC_PIN.match(member) + target = adc.group(1) if adc else member.replace("Pin", "").upper() + candidates = [s for s in pin.signals if "." in s and s.split(".", 1)[0] == instance] + chosen = None + if len(candidates) == 1: + chosen = candidates[0] + else: + for signal in candidates: + function = signal.split(".", 1)[1] + if function.upper() == target or (target and function.upper().endswith(target)): + chosen = signal + break + return { + "signal": chosen or f"{instance}.{target}", + "peripheral": instance, + "kind": re.sub(r"\d+$", "", instance), + "function": chosen.split(".", 1)[1] if chosen else target, + "matched": chosen is not None, + } + + +SYSCFG_GPIO_ASSIGNED = re.compile(r"^\.associatedPins\[(\d+)\]\.assigned(Pin|Port)$") +IDENTIFIER = re.compile(r"[A-Za-z_]\w*") + + +def mspm0_gpio_name(group: str, name: str) -> tuple[str, bool]: + """MSPM0 GPIO 对象名(libxr parse 和 libxr pins 共用):引脚 $name 去掉 PIN_ 前缀 + (PIN_LED1 -> LED1);去掉后不是合法标识符时(SysConfig 的默认名 PIN_0 -> 0)是小写的组名 + 加引脚名(gpio_btn_pin_0)。第二项说明是否用了后一种。 + The name of an MSPM0 GPIO object (shared by libxr parse and libxr pins): the pin $name + without its PIN_ prefix (PIN_LED1 -> LED1); when that is not a valid identifier (the + SysConfig default PIN_0 -> 0), the lower-case group plus pin name (gpio_btn_pin_0). The + second item says whether the latter was used. + """ + label = name[4:] if name.startswith("PIN_") else name + if IDENTIFIER.fullmatch(label): + return label, False + return re.sub(r"\W", "_", f"{group}_{name}".lower()), True + + +def syscfg_assignments(path: Path, layout: PinLayout) -> dict[str, dict]: + """.syscfg 中 MSPM0 的已选信号:引脚名 -> 信号及其识别结果。 + The selected signals of an MSPM0 in a .syscfg: pin name -> signal and what it was recognized + as. + + 外设引脚是 <实例>.peripheral.<功能>Pin.$assign(<实例> 取 peripheral.$assign 指定的外设, + 功能按引脚的信号表核对),GPIO 是 <变量>.associatedPins[i].pin.$assign,或 TI 例程的端口 + 加引脚号写法(组的 port 或引脚的 assignedPort,加 assignedPin);标签是 libxr gen 生成的 + 对象名(mspm0_gpio_name)。求解器选的 $suggestSolution 兜底,$assign 优先;DMA 通道和 + clockTree 的晶振引脚不收。 + A peripheral pin is .peripheral.Pin.$assign (the instance is the + peripheral peripheral.$assign names, and the function is checked against the signals of the + pin), a GPIO one is .associatedPins[i].pin.$assign or the port plus pin number + form of TI's examples (the port of the group or the assignedPort of the pin, with + assignedPin); the label is the object name libxr gen generates (mspm0_gpio_name). The + solver's $suggestSolution is the fallback, and $assign wins; the DMA channels and the + oscillator pins of the clock tree are left out. + """ + text = path.read_text(encoding="utf-8", errors="replace") + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + text = re.sub(r"^[ \t]*//.*$", "", text, flags=re.M) + modules = {var: module for var, module in SYSCFG_MODULE.findall(text)} + # 单例外设(DAC12 等)没有 addInstance,直接配在模块变量上。 + # A singleton peripheral (DAC12, ...) has no addInstance; the module variable is configured + # directly. + instances = dict(modules) + instances.update( + {var: modules[module] for var, module in SYSCFG_INSTANCE.findall(text) if module in modules} + ) + peripheral_of: dict[str, str] = {} + periph_pin: dict[tuple[str, str], str] = {} + gpio_pin: dict[tuple[str, int], str] = {} + gpio_label: dict[tuple[str, int], str] = {} + # 端口加引脚号写法:(变量, 引脚序号) -> 端口(序号 -1 是组的 port)或引脚号。 + # The port plus pin number form: (variable, pin index) -> port (index -1 is the port of the + # group) or pin number. + gpio_port: dict[tuple[str, int], str] = {} + gpio_line: dict[tuple[str, int], str] = {} + for var, tail, value in SYSCFG_ASSIGNMENT.findall(text): + if var not in instances: + continue + if tail == ".port" and instances[var] == "GPIO": + gpio_port[(var, -1)] = syscfg_value(value) + continue + assigned_member = SYSCFG_GPIO_ASSIGNED.match(tail) + if assigned_member and instances[var] == "GPIO": + key = (var, int(assigned_member.group(1))) + target = gpio_line if assigned_member.group(2) == "Pin" else gpio_port + target[key] = str(syscfg_value(value)) + continue + if tail == ".peripheral.$assign": + peripheral_of[var] = syscfg_value(value) + elif tail == ".peripheral.$suggestSolution": + peripheral_of.setdefault(var, syscfg_value(value)) + elif tail == ".$name" and instances[var] == "GPIO": + # 组的 $name 是没有引脚 $name 时的标签。 + # The $name of the group is the label when a pin has no $name of its own. + gpio_label[(var, -1)] = syscfg_value(value) + continue + index = SYSCFG_GPIO_NAME.match(tail) + if index: + gpio_label[(var, int(index.group(1)))] = syscfg_value(value) + continue + member = SYSCFG_PERIPH_PIN.match(tail) or SYSCFG_PERIPH_PIN_SUGGESTED.match(tail) + if member: + key = (var, member.group(1)) + # $assign 覆盖同名的 $suggestSolution。 + # $assign overrides a $suggestSolution of the same member. + if member.re is SYSCFG_PERIPH_PIN or key not in periph_pin: + periph_pin[key] = syscfg_value(value) + continue + gpio = SYSCFG_GPIO_PIN.match(tail) or SYSCFG_GPIO_PIN_SUGGESTED.match(tail) + if gpio: + key = (var, int(gpio.group(1))) + if gpio.re is SYSCFG_GPIO_PIN or key not in gpio_pin: + gpio_pin[key] = syscfg_value(value) + for (var, index), line in gpio_line.items(): + port = re.fullmatch( + r"PORT([A-Z])", str(gpio_port.get((var, index), gpio_port.get((var, -1)))) + ) + if (var, index) not in gpio_pin and port and line.isdigit(): + gpio_pin[(var, index)] = f"P{port.group(1)}{int(line)}" + by_name = {pin.name: pin for pin in layout.pins} + assigned: dict[str, dict] = {} + for (var, member), pin_name in periph_pin.items(): + instance = peripheral_of.get(var) + pin = by_name.get(pin_name) if instance else None + if pin is not None: + assigned[pin.name] = peripheral_entry(pin, instance, member) + for (var, index), pin_name in gpio_pin.items(): + pin = by_name.get(pin_name) + if pin is None: + continue + port_line = pin_layout.gpio_port_and_line(pin.name) + entry = { + "signal": pin.name, + "peripheral": f"GPIO{port_line[0]}" if port_line else "GPIO", + "kind": "GPIO", + "function": f"P{port_line[1]}" if port_line else "GPIO", + "matched": pin.name in pin.signals, + } + name = gpio_label.get((var, index)) + if name: + entry["label"] = mspm0_gpio_name(gpio_label.get((var, -1)) or var, name)[0] + assigned[pin.name] = entry + return assigned + + +def hpm_canonical(signal: str) -> str: + """.hpmpc 的信号(UART0.A.TXD、GPIO.A.A[10])到 hpm_iomux.h 的宏名(UART0_TXD、GPIO_A_10): + 去掉中间的 IOC 通道字母,方括号换成下划线。 + The signal of a .hpmpc (UART0.A.TXD, GPIO.A.A[10]) to the macro name of hpm_iomux.h + (UART0_TXD, GPIO_A_10): the IOC channel letter in the middle goes, and brackets become + underscores. + """ + parts = signal.split(".") + if len(parts) == 3: + parts = [parts[0], parts[2]] + return "_".join(parts).replace("[", "_").replace("]", "") + + +HPM_DIRECTIVE = re.compile(r"^[ \t]*#[ \t]*(if|ifdef|ifndef|elif|else|endif)\b") +HPM_CALL = re.compile(r"\b(\w+)\s*\(\s*(?:void\s*)?\)\s*;") +HPM_MAIN = re.compile(r"\bmain\s*\([^)]*\)\s*\{") +# C 源码里的注释和字面量:块注释、行注释、字符串和字符常量(带转义)。 +# The comments and literals of C source: block comments, line comments, string and character +# constants (with escapes). +C_COMMENT_OR_LITERAL = re.compile( + r"/\*.*?\*/|//[^\n]*|\"(?:\\.|[^\"\\\n])*\"|'(?:\\.|[^'\\\n])*'", re.S +) + + +@dataclass +class HpmFunction: + """.hpmpc 的一个 pinmux 函数:选中的引脚(pad -> 信号和 pad 设置)和 gpiom 写下的 GPIO 设置。 + One pinmux function of a .hpmpc: the selected pins (pad -> signal and pad settings) and the + GPIO settings the gpiom manager wrote. + """ + + pins: dict[str, dict] + gpiom: dict[str, dict] + + +@dataclass +class HpmProject: + """一个 .hpmpc:SoC(大写,没有给出时为 None)、封装和各 pinmux 函数,按文件中的顺序。 + One .hpmpc: the SoC (upper case, None when not given), the package and the pinmux functions, + in file order. + """ + + path: Path + soc: str | None + package: str | None + functions: dict[str, HpmFunction] + + +def _mapping(value, what: str, path: Path) -> dict: + """value 本身(映射),null 为空映射;其他值抛出说明 what 的 ValueError。 + value itself when it is a mapping, an empty mapping for null; any other value raises a + ValueError that names what. + """ + if value is None: + return {} + if not isinstance(value, dict): + raise ValueError( + tr( + f"{path}: {what} is not a mapping; export the project again from the HPM Pinmux " + "Tool", + f"{path}:{what} 不是映射;请从 HPM Pinmux Tool 重新导出工程", + ) + ) + return value + + +def read_hpmpc(path: Path) -> HpmProject: + """读一个 .hpmpc(允许 UTF-8 BOM)。info 和 pinmux 缺失或类型不对时抛出 ValueError;值为 + null 的 managers、gpiom 和 selectPins 当作空。SoC 名换成大写。 + Read a .hpmpc (a UTF-8 BOM is allowed). A missing or mistyped info or pinmux raises + ValueError; a null managers, gpiom or selectPins counts as empty. The SoC name is put in + upper case. + """ + try: + document = json.loads(path.read_text(encoding="utf-8-sig")) + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error: + raise ValueError( + tr(f"{path}: not a readable .hpmpc: {error}", f"{path}:无法读取的 .hpmpc:{error}") + ) from error + content = _mapping(_mapping(document, "the file", path).get("content"), "content", path) + if "info" not in content or "pinmux" not in content: + raise ValueError( + tr( + f"{path}: no info or pinmux in content; export the project again from the HPM " + "Pinmux Tool", + f"{path}:content 中没有 info 或 pinmux;请从 HPM Pinmux Tool 重新导出工程", + ) + ) + info = _mapping(content["info"], "content.info", path) + soc = info.get("socName") + soc = soc.strip().upper() if isinstance(soc, str) and soc.strip() else None + package = info.get("packageName") + raw = _mapping( + _mapping(content["pinmux"], "content.pinmux", path).get("functions"), + "content.pinmux.functions", + path, + ) + functions: dict[str, HpmFunction] = {} + for name, function in raw.items(): + where = f"content.pinmux.functions.{name}" + function = _mapping(function, where, path) + pins = {} + for pad, selection in _mapping( + function.get("selectPins"), f"{where}.selectPins", path + ).items(): + selection = _mapping(selection, f"{where}.selectPins.{pad}", path) + signal = selection.get("signal") + if not isinstance(signal, str) or not signal: + continue + pins[str(pad)] = { + "signal": signal, + "padCtls": _mapping( + selection.get("padCtls"), f"{where}.selectPins.{pad}.padCtls", path + ), + } + managers = _mapping(function.get("managers"), f"{where}.managers", path) + gpiom = { + str(pad): _mapping(value, f"{where}.managers.gpiom.{pad}", path) + for pad, value in _mapping( + managers.get("gpiom"), f"{where}.managers.gpiom", path + ).items() + } + functions[str(name)] = HpmFunction(pins, gpiom) + return HpmProject(path, soc, package if isinstance(package, str) else None, functions) + + +def _blank(match: re.Match) -> str: + """注释或字面量换成同样多的空白,换行保留,使行号和预处理指令的位置不变。 + A comment or literal replaced by as much white space, the newlines kept, so the lines and + the places of the preprocessor directives stay. + """ + return re.sub(r"[^\n]", " ", match.group(0)) + + +def hpm_main_calls(main_c: Path, functions) -> tuple[list[str], list[str]]: + """main.c 的 main 函数体里调用的 pinmux 函数(functions 中的名字),按调用顺序:预处理 + 条件外的和条件里的(O2:后者不算,例如 rmcs 的 JTAG 共用引脚)。注释和字符串里的不算, + 辅助函数里的不算;main.c 不存在或没有 main 时都为空。 + The pinmux functions (names in functions) that the body of main in main.c calls, in call + order: those outside preprocessor conditions and those inside one (O2: the latter do not + count, such as the rmcs JTAG shared pins). Calls in comments and strings or in a helper + function do not count; both are empty when main.c or its main does not exist. + """ + if not main_c.is_file(): + return [], [] + text = C_COMMENT_OR_LITERAL.sub(_blank, main_c.read_text(encoding="utf-8", errors="replace")) + start = HPM_MAIN.search(text) + if start is None: + return [], [] + # main 的函数体:从它的 { 数到配对的 }。 + # The body of main: from its { to the matching }. + depth, end = 0, len(text) + for index in range(start.end() - 1, len(text)): + if text[index] == "{": + depth += 1 + elif text[index] == "}": + depth -= 1 + if depth == 0: + end = index + break + called: list[str] = [] + conditional: list[str] = [] + condition = 0 + offset = 0 + for line in text.splitlines(keepends=True): + line_start, offset = offset, offset + len(line) + directive = HPM_DIRECTIVE.match(line) + if directive: + if directive.group(1) in ("if", "ifdef", "ifndef"): + condition += 1 + elif directive.group(1) == "endif": + condition = max(0, condition - 1) + continue + for call in HPM_CALL.finditer(line): + position = line_start + call.start() + name = call.group(1) + if not start.end() <= position < end or name not in functions: + continue + target = conditional if condition else called + if name not in target: + target.append(name) + return called, [name for name in conditional if name not in called] + + +def hpm_called_functions(main_c: Path, functions) -> list[str]: + """main.c 的 main 在预处理条件外调用的 pinmux 函数,按调用顺序(见 hpm_main_calls)。 + The pinmux functions that main in main.c calls outside preprocessor conditions, in call + order (see hpm_main_calls). + """ + return hpm_main_calls(main_c, functions)[0] + + +def hpm_active_functions(main_c: Path, functions) -> list[str]: + """读引脚的 pinmux 函数:main.c 在预处理条件外调用的、.hpmpc 里存在的函数。main.c 不存在 + 或根本没有调用任何 pinmux 函数时退回 init_bsp_pins;调用全在预处理条件里时不退回(O2: + 条件里的函数不读,包括 init_bsp_pins)。 + The pinmux functions the pins are read from: those of the .hpmpc that the root main.c calls + outside preprocessor conditions. The fallback is init_bsp_pins when main.c does not exist or + calls no pinmux function at all; there is no fallback when every call sits inside a + condition (O2: a function in a condition is not read, init_bsp_pins included). + """ + called, conditional = hpm_main_calls(main_c, functions) + if called or conditional: + return called + return ["init_bsp_pins"] if "init_bsp_pins" in functions else [] + + +def hpm_selections( + project: HpmProject, active: list[str] +) -> tuple[list[dict], list[tuple[str, str, str, str, str]]]: + """活动函数选中的引脚,按选择顺序:[{pad, signal, padCtls, gpiom, function}];同一 pad 被 + 后一个函数再次选中时替换前面的记录,并记进冲突 (pad, 前一个函数, 信号, 后一个函数, 信号)。 + The pins the active functions select, in selection order: [{pad, signal, padCtls, gpiom, + function}]; a pad selected again by a later function replaces the earlier record and is + recorded as a conflict (pad, earlier function, signal, later function, signal). + """ + selections: dict[str, dict] = {} + conflicts = [] + for name in active: + function = project.functions[name] + for pad, selection in function.pins.items(): + previous = selections.get(pad) + if previous is not None and previous["function"] != name: + conflicts.append( + (pad, previous["function"], previous["signal"], name, selection["signal"]) + ) + # 后一个为准,记录留在原来的位置。 + # The later one wins, and the record keeps its place. + selections[pad] = { + "pad": pad, + "signal": selection["signal"], + "padCtls": selection["padCtls"], + "gpiom": function.gpiom.get(pad, {}), + "function": name, + } + return list(selections.values()), conflicts + + +def hpm_assignments(hpmpc: Path, main_c: Path, layout: PinLayout) -> dict[str, dict]: + """.hpmpc 里 main.c 调用的 pinmux 函数的已选信号:引脚名 -> 信号及其识别结果。 + The selected signals of the pinmux functions of the .hpmpc that the root main.c calls: pin name -> + signal and what it was recognized as. + + 信号是工具的三段式(UART0.A.TXD),化成 hpm_iomux.h 的宏名(UART0_TXD)后按引脚的信号表 + 核对;GPIO 的(GPIO.A.A[10])与 MSPM0 一样算普通用法。.hpmpc 不带名字,没有标签。 + A signal is the tool's three-part form (UART0.A.TXD), checked against the signals of the pin + as the macro name of hpm_iomux.h (UART0_TXD); a GPIO one (GPIO.A.A[10]) counts as the plain + use like MSPM0. The .hpmpc carries no names, so there is no label. + """ + project = read_hpmpc(hpmpc) + by_name = {pin.name: pin for pin in layout.pins} + assigned: dict[str, dict] = {} + selections, conflicts = hpm_selections(project, hpm_active_functions(main_c, project.functions)) + for pad, first, first_signal, second, second_signal in conflicts: + logging.warning( + hpm_conflict_message(hpmpc, pad, first, first_signal, second, second_signal) + ) + for selection in selections: + pin = by_name.get(selection["pad"]) + if pin is None: + continue + canonical = hpm_canonical(selection["signal"]) + if canonical.startswith("GPIO_"): + port_line = pin_layout.gpio_port_and_line(pin.name) + assigned[pin.name] = { + "signal": canonical, + "peripheral": f"GPIO{port_line[0]}" if port_line else "GPIO", + "kind": "GPIO", + "function": f"P{int(port_line[1])}" if port_line else "GPIO", + "matched": canonical in pin.signals, + } + controller = hpm_gpio_controller(selection["gpiom"]) + if controller != HPM_GPIO0_CONTROLLER: + assigned[pin.name]["gpio_controller"] = controller + continue + recognized = pin_layout.recognize_hpm(canonical) + instance, kind, pin_function = recognized or (canonical, "", "") + assigned[pin.name] = { + "signal": canonical, + "peripheral": instance, + "kind": kind, + "function": pin_function, + "matched": canonical in pin.signals, + } + return assigned + + +# .hpmpc 里 gpiom 管理器的 gpioController 值:1 是 SoC 的 GPIO0(工具为它生成 +# gpiom_soc_gpio0),HPMGPIO 只驱动这一个控制器;没写时就是它。 +# The gpioController value of the gpiom manager of a .hpmpc: 1 is the GPIO0 of the SoC (the +# tool generates gpiom_soc_gpio0 for it), the only controller HPMGPIO drives; it is the one +# when none is written. +HPM_GPIO0_CONTROLLER = "1" + + +def hpm_gpio_controller(gpiom: dict) -> str: + """gpiom 设置里引脚分给的 GPIO 控制器(工具的编号);没写时是 GPIO0。 + The GPIO controller the gpiom settings assign the pin to (the tool's number); GPIO0 when + none is written. + """ + return str(gpiom.get("gpioController") or HPM_GPIO0_CONTROLLER) + + +def hpm_conflict_message( + hpmpc: Path, pad: str, first: str, first_signal: str, second: str, second_signal: str +) -> str: + """两个活动函数选中同一个 pad 的警告。 + The warning about two active functions selecting the same pad. + """ + return tr( + f"{hpmpc}: {pad} is selected by both {first} ({first_signal}) and {second} " + f"({second_signal}); the later one wins", + f"{hpmpc}:{pad} 同时被 {first}({first_signal})和 {second}({second_signal})选中;" + "以后一个为准", + ) + + +def config_entries( + platform: str, + instance: str, + kind: str, + functions: dict, + settings: dict, + sysconfig: dict | None, + model: str | None = None, +) -> list[dict]: + """一个已选外设在 libxr_config.yaml 中的设置条目;libxr gen 不生成它的设置时为空。 + The config entries of a selected peripheral in libxr_config.yaml; empty when libxr gen does + not generate settings for it. + + 一个条目是 {section, key, present, params}。key 为 None 时条目是整段(HPM 的 GPIO 改名), + params 是该外设每个引脚的现名,文件里没有的为 None。PWM 一个实例有多个通道,每通道一个 + 条目;MSPM0 的定时器只有 SysConfig 模块是 PWM 的才生成,QEI 等没有。 + An entry is {section, key, present, params}. With a None key the entry is a whole section + (the GPIO renames of an HPM) and params holds the current name of every pin of the + peripheral, None when the file has none. An instance of PWM has a channel per entry, and an + MSPM0 timer generates only with the SysConfig module PWM, not QEI and the like. + """ + + def keyed(section: str, name: str) -> dict: + """一个有键的条目:键的大小写以文件为准,文件里没有时 present 为 False。 + A keyed entry: the case of the key is the file's, and present is False when the file has + none. + """ + values = settings.get(section) if isinstance(settings.get(section), dict) else {} + actual = next((key for key in values if str(key).lower() == name), None) + entry = {"section": section, "key": actual or name, "present": actual is not None} + if actual is not None: + entry["params"] = values[actual] + return entry + + def channels(pattern: str) -> list[int]: + """外设各引脚功能里的通道号,升序去重。 + The channel numbers in the functions of the peripheral's pins, ascending and unique. + """ + return sorted({int(match.group(1)) for f in functions if (match := re.match(pattern, f))}) + + if platform == "stm32": + section = STM32_SECTIONS.get(kind) + if section is None: + return [] + return [keyed(section, USB_KEYS.get(instance, instance.lower()))] + if platform == "mspm0": + params = (sysconfig or {}).get("params", {}) + # 与 libxr parse 一致:只有控制器模式的 I2C 和 SPI 生成对象(I2C 的 + # basicEnableController 默认关闭,SPI 的 mode 默认 CONTROLLER)。 + # As libxr parse decides: only an I2C and an SPI in controller mode generate objects + # (basicEnableController of an I2C is off by default, mode of an SPI is CONTROLLER). + if kind == "I2C" and params.get("basicEnableController") is not True: + return [] + if kind == "SPI" and str(params.get("mode", "CONTROLLER")) != "CONTROLLER": + return [] + if kind in ("UART", "I2C", "SPI"): + return [keyed(kind, instance.lower())] + if kind in ("TIMA", "TIMG") and sysconfig and sysconfig.get("module") == "PWM": + return [keyed("PWM", f"pwm_{instance.lower()}_c{n}") for n in channels(r"CCP(\d+)")] + return [] + if platform == "hpm": + # 与 libxr parse 一致:缺 SCL 或 SDA 的 I2C 不生成。 + # As libxr parse decides: an I2C without SCL or SDA is not generated. + if kind == "I2C" and {"SCL", "SDA"} <= set(functions): + return [keyed("I2C", instance.lower())] + # GPTMR 的 PWM 只在 SoC 没有 PWM 外设时生成(与解析一致)。 + # A PWM on a GPTMR is generated only when the SoC has no PWM peripheral, as parsed. + if kind == "GPTMR" and not (model and pin_layout.hpm_soc_has_pwm(model)): + return [keyed("PWM", f"pwm_{instance.lower()}_ch{n}") for n in channels(r"COMP_(\d+)")] + if kind == "GPIO": + values = settings.get("GPIO") if isinstance(settings.get("GPIO"), dict) else {} + params = {} + for pin_name in functions.values(): + port_line = pin_layout.gpio_port_and_line(pin_name) + if port_line is None: + continue + # 名字的规则与解析器的一致:p 加端口小写字母加线号(PA03 -> pa3);文件里的键 + # 不分大小写,保留文件的写法(与 libxr gen 一样)。 + # The rule of the name is the parser's: p, the lower-case port letter and the + # line (PA03 -> pa3); the key of the file matches in any case and keeps the + # file's spelling (as in libxr gen). + name = f"p{port_line[0].lower()}{int(port_line[1]):d}" + key = next((key for key in values if str(key).lower() == name), name) + params[key] = values.get(key) + if not params: + return [] + return [{"section": "GPIO", "key": None, "present": bool(values), "params": params}] + return [] + return [] + + +def project_overlay( + layout: PinLayout, + assigned: dict[str, dict], + config_path: Path | None, + sysconfig: dict[str, dict] | None = None, +) -> dict: + """工程叠加结果:每个引脚的已选信号,以及每个已选外设用到的引脚和它在 libxr_config.yaml + 中的设置。 + The project overlay: the selected signal of each pin, and for each selected peripheral the + pins it uses and its settings in libxr_config.yaml. + """ + settings = {} + if config_path is not None and config_path.is_file(): + loaded = yaml.safe_load(config_path.read_text(encoding="utf-8")) + settings = loaded if isinstance(loaded, dict) else {} + peripherals: dict[str, dict] = {} + for pin_name, entry in assigned.items(): + used = peripherals.setdefault(entry["peripheral"], {"kind": entry["kind"], "pins": {}}) + used["pins"][entry["function"]] = pin_name + if entry.get("gpio_controller"): + used.setdefault("excluded", set()).add(pin_name) + for instance, used in peripherals.items(): + # MSPM0:外设的设置在 SysConfig 工程(.syscfg)里,只读给出。 + # MSPM0: the settings of a peripheral are in the SysConfig project (.syscfg), given read-only. + if sysconfig and instance in sysconfig: + used["sysconfig"] = sysconfig[instance] + entries = ( + config_entries( + layout.platform, + instance, + used["kind"], + used["pins"], + settings, + used.get("sysconfig"), + layout.model, + ) + if not used.get("excluded") + else [] + ) + if used.get("excluded"): + # HPM:分给 GPIO0 以外控制器的引脚 gen 不生成对象,也就没有改名项。 + # HPM: gen makes no object for a pin on a controller other than GPIO0, so it has no + # rename entry. + pins = {f: p for f, p in used["pins"].items() if p not in used["excluded"]} + entries = config_entries( + layout.platform, instance, used["kind"], pins, settings, None, layout.model + ) + used.pop("excluded", None) + if entries: + used["config"] = entries + return { + "assignments": dict( + sorted(assigned.items(), key=lambda item: pin_layout.natural_key(item[0])) + ), + "peripherals": dict( + sorted(peripherals.items(), key=lambda item: pin_layout.natural_key(item[0])) + ), + } + + +def layout_with_project( + directory: str, model: str | None, package: str | None, libxr_config: str | None +) -> dict: + """工程目录中的布局和叠加:识别平台,读出已选信号,给出带 project 段的布局字典。 + The layout and overlay of a project directory: recognize the platform, read the selected + signals and return the layout dict with a project section. + + 给出 model 时以它为准,否则取自工程(STM32 的 .ioc,MSPM0 的根目录 .syscfg,HPM 的 + boards/ 下 .hpmpc 的 SoC 名)。MSPM0 的封装取自 .syscfg 的 --package 或 --board,HPM 的取 + 自 .hpmpc 的 packageName,package 给出时以它为准。 + libxr_config 默认为目录下的 User/libxr_config.yaml。 + A given model wins; otherwise it comes from the project (the .ioc of an STM32, the root + .syscfg of an MSPM0, the SoC name of the .hpmpc under boards/ of an HPM). The package of an + MSPM0 comes from the --package or --board of the .syscfg, that of an HPM from the + packageName of the .hpmpc, and a given package wins. libxr_config defaults to + User/libxr_config.yaml in the directory. + """ + root = Path(directory) + config_path = Path(libxr_config) if libxr_config else root / "User" / "libxr_config.yaml" + syscfg_file, sysconfig = None, {} + hpmpcs = sorted(root.glob("boards/*/*.hpmpc")) if (root / "app.yaml").is_file() else [] + if sorted(root.glob("*.ioc")): + chip, source, layout, assigned = stm32_assignments( + root, lambda chip: pin_layout.layout_pins(model or chip, package) + ) + elif hpmpcs: + if len(hpmpcs) > 1: + raise ValueError( + tr( + f"{root}: several .hpmpc files under boards/; keep one", + f"{root}:boards/ 下有多个 .hpmpc;请只保留一个", + ) + ) + # HPM 的工程:SoC 和封装在 .hpmpc 的 info 里,已选信号取 main.c 无条件调用的 pinmux + # 函数(O2:条件编译里的不算)。 + # An HPM project: the SoC and the package are in the info of the .hpmpc, and the selected + # signals come from the pinmux functions main.c calls without a condition (O2: a call + # inside a conditional does not count). + project = read_hpmpc(hpmpcs[0]) + if not (model or project.soc): + raise ValueError( + tr( + f"{hpmpcs[0]} does not name a SoC; give the model", + f"{hpmpcs[0]} 没有给出 SoC;请给出型号", + ) + ) + layout = pin_layout.layout_pins(model or project.soc, package or project.package) + assigned = hpm_assignments(hpmpcs[0], root / "main.c", layout) + source = hpmpcs[0].relative_to(root).as_posix() + # sysconfig_file 对 HPM 是 .hpmpc 本身:外设配置就住在里面。 + # sysconfig_file is the .hpmpc itself for an HPM: the peripheral configuration lives in + # it. + syscfg_file, sysconfig = hpmpcs[0], {} + else: + syscfgs = sorted(root.glob("*.syscfg")) + if len(syscfgs) > 1: + raise ValueError( + tr( + f"{root}: several .syscfg files in the root; keep one", + f"{root}:根目录里有多个 .syscfg;请只保留一个", + ) + ) + if not syscfgs: + raise ValueError( + tr( + f"{root}: no STM32CubeMX .ioc, HPM .hpmpc or root SysConfig .syscfg found", + f"{root}:没有找到 STM32CubeMX 的 .ioc、HPM 的 .hpmpc 或根目录的 SysConfig" + " .syscfg", + ) + ) + # SysConfig 工程在根目录:器件、封装、已选信号和设置都直接取自它,不需要跑 SysConfig。 + # A SysConfig project in the root: device, package, selected signals and settings all + # come from it directly; SysConfig does not have to run. + meta = read_syscfg(syscfgs[0]) + board = load_boards().get(meta.board or "") + # 板子型工程(--board)的器件和封装由板子表给出。 + # A board-based project (--board) takes its device and package from the board table. + device = model or meta.device or (board or {}).get("device") + if device is None: + raise ValueError( + tr( + f"{syscfgs[0]} does not name a device; give the model", + f"{syscfgs[0]} 没有给出器件;请给出型号", + ) + ) + if package is None: + package = meta.package or (board or {}).get("package") + layout = pin_layout.layout_pins(device, package) + assigned = syscfg_assignments(syscfgs[0], layout) + source = syscfgs[0].name + syscfg_file, sysconfig = syscfgs[0], read_syscfg_settings(syscfgs[0]) + info = pin_layout.layout_to_dict(layout) + info["project"] = { + "directory": str(root), + "source": source, + "libxr_config": str(config_path) if config_path.is_file() else None, + "sysconfig_file": syscfg_file.relative_to(root).as_posix() if syscfg_file else None, + **project_overlay(layout, assigned, config_path, sysconfig), + } + return info diff --git a/tests/data/hpm/hpm5301evklite.hpmpc b/tests/data/hpm/hpm5301evklite.hpmpc new file mode 100644 index 0000000..f69b8c9 --- /dev/null +++ b/tests/data/hpm/hpm5301evklite.hpmpc @@ -0,0 +1,298 @@ +{ + "clientKey": "e7ee1b8eb71cbdfffc3236f8bf62fbdf", + "secretKey": "52c36fb76cad5cb9a878bcdee4400bc0", + "content": { + "pid": "9497509b-eefd-4954-80df-5398d8aded77", + "info": { + "projectName": "tool_config_trimmed_5301", + "sdkName": "1.13.0", + "socName": "HPM5301", + "packageName": "QFN48", + "createTime": 1791298383401, + "updateTime": 1791298383401, + "vscodePid": "66927f38-37ed-42b1-8491-934af04e0eeb" + }, + "pinmux": { + "functions": { + "init_bsp_pins": { + "selectPins": { + "PA01": { + "signal": "UART0.A.RXD", + "padCtls": {} + }, + "PA00": { + "signal": "UART0.A.TXD", + "padCtls": {} + }, + "PB15": { + "signal": "UART3.B.TXD", + "padCtls": {} + }, + "PB14": { + "signal": "UART3.B.RXD", + "padCtls": {} + }, + "PB13": { + "signal": "I2C3.C.SCL", + "padCtls": { + "LOOP_BACK": "1", + "OD": "1", + "PE": "1", + "PS": "1" + } + }, + "PB12": { + "signal": "I2C3.C.SDA", + "padCtls": { + "PE": "1", + "PS": "1", + "OD": "1", + "LOOP_BACK": "1" + } + }, + "PA29": { + "signal": "SPI1.A.MOSI", + "padCtls": { + "SR": "1", + "SPD": "3" + } + }, + "PA28": { + "signal": "SPI1.A.MISO", + "padCtls": { + "SPD": "3", + "SR": "1" + } + }, + "PA26": { + "signal": "SPI1.A.CS[0]", + "padCtls": { + "SR": "1", + "SPD": "3", + "PE": "1", + "PS": "1" + } + }, + "PA27": { + "signal": "SPI1.A.SCLK", + "padCtls": { + "LOOP_BACK": "1", + "SR": "1", + "SPD": "3" + } + }, + "PB10": { + "signal": "ADC0.A.IN2", + "padCtls": {} + }, + "PB09": { + "signal": "ACMP.G.COMP[1]", + "padCtls": {} + }, + "PB11": { + "signal": "ACMP.A.CMP1_INN4", + "padCtls": {} + }, + "PA10": { + "signal": "GPIO.A.A[10]", + "padCtls": {} + }, + "PA03": { + "signal": "GPIO.A.A[03]", + "padCtls": { + "PE": "1", + "PS": "0", + "HYS": "1" + } + }, + "PA25": { + "signal": "USBPHY.A.USB_DM", + "padCtls": {} + }, + "PA24": { + "signal": "USBPHY.A.USB_DP", + "padCtls": {} + }, + "PY01": { + "signal": "USB0.C.OC", + "padCtls": {} + }, + "PY00": { + "signal": "USB0.C.ID", + "padCtls": {} + }, + "PB08": { + "signal": "GPTMR0.B.COMP[1]", + "padCtls": {} + } + }, + "managers": { + "gpiom": { + "PA10": { + "direction": "1", + "gpioController": "1", + "outputLevel": "1", + "gpioControllerHide": "[]" + }, + "PA03": { + "direction": "0", + "gpioController": "1", + "interrupt": "0", + "gpioControllerHide": "[]" + } + } + } + } + }, + "sorter": [ + "init_bsp_pins" + ] + }, + "clock": { + "functions": { + "init_acmp0_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_acmp0": 0 + } + }, + "init_adc0_bus_clock": { + "selectClocks": { + "srcClocks": [ + "osc32k", + "osc24m" + ], + "funcClocks": [ + "clk_top_adc0" + ], + "pllVcoClocks": [ + "pll0", + "pll1" + ], + "selfOnlyFuncClocks": [ + "clk_top_adc0" + ] + }, + "enableClocks": { + "clk_top_adc0": "0" + } + }, + "init_board_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_cpu0": 0, + "clk_top_ahb0": 0, + "clk_lmm0": 0, + "clk_top_mct0": 0, + "clk_rom": 0, + "clk_mot0": 0, + "clk_gpio": 0, + "clk_hdma": 0, + "clk_top_xpi0": 0 + } + }, + "init_board_clock_source": { + "selectClocks": { + "srcClocks": [ + "clk0_pll0", + "clk1_pll0", + "clk2_pll0" + ], + "funcClocks": [ + "clk_top_mct0" + ] + }, + "enableClocks": {}, + "funcClkConfig": { + "clk_top_mct0": { + "from": "osc24m", + "divFactor": 1 + } + }, + "pllClkConfig": { + "pll0": { + "params": { + "mfd": 240000000, + "mfi": 30, + "mfn": 0, + "frequency": 720000000 + } + } + } + }, + "init_gptmr0_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_tmr0": 0 + } + }, + "init_i2c3_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_i2c3": 0 + } + }, + "init_spi1_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_spi1": 0 + } + }, + "init_uart0_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_urt0": 0 + } + }, + "init_uart3_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_top_urt3": 0 + } + }, + "init_usb0_clock": { + "selectClocks": { + "srcClocks": [], + "funcClocks": [] + }, + "enableClocks": { + "clk_usb0": 0 + } + } + }, + "sorter": [ + "init_board_clock", + "init_board_clock_source", + "init_uart0_clock", + "init_uart3_clock", + "init_i2c3_clock", + "init_spi1_clock", + "init_adc0_bus_clock", + "init_gptmr0_clock", + "init_usb0_clock", + "init_acmp0_clock" + ] + } + } +} \ No newline at end of file diff --git a/tests/data/hpm/hpm5301evklite_app.yaml b/tests/data/hpm/hpm5301evklite_app.yaml new file mode 100644 index 0000000..d669fc5 --- /dev/null +++ b/tests/data/hpm/hpm5301evklite_app.yaml @@ -0,0 +1,2 @@ +dependency: + - board_gpt_pin diff --git a/tests/data/hpm/hpm5301evklite_expected_app_main.cpp b/tests/data/hpm/hpm5301evklite_expected_app_main.cpp new file mode 100644 index 0000000..f3962ac --- /dev/null +++ b/tests/data/hpm/hpm5301evklite_expected_app_main.cpp @@ -0,0 +1,60 @@ +// Generated by `libxr gen`; do not edit by hand. +// Generated from boards/hpm5301evklite/tool_config.hpmpc (HPM5301); the Pinmux Tool +// configures the pins via init_bsp_pins() in main.c and main.c stays BSP-owned. Put +// application code between the "User Code Begin" and "User Code End" markers. +#include "app_main.h" + +#include "libxr.hpp" +#include "hpm_gpio.hpp" +#include "hpm_i2c.hpp" +#include "hpm_power.hpp" +#include "hpm_pwm.hpp" +#include "hpm_soc.h" +#include "hpm_timebase.hpp" +#include "xrobot_main.hpp" + +using namespace LibXR; + +/* User Code Begin 1 */ +/* User Code End 1 */ + +extern "C" void app_main(void) +{ + /* User Code Begin 2 */ + /* User Code End 2 */ + + // Timebase and platform + static HPMTimebase timebase; + PlatformInit(); + static HPMPowerManager power_manager; + + // GPIO: init_bsp_pins() configured pa10 (PA10) as an output pin, pa3 (PA03) as an input + // pin with a pull-down. The interrupt edge comes from the pinmux and every object + // carries its port IRQ; RegisterCallback and EnableInterrupt belong to the consumer (a + // Module or the User Code). + static HPMGPIO pa10(HPM_GPIO0, GPIO_DI_GPIOA, 10, IRQn_GPIO0_A); + static HPMGPIO pa3(HPM_GPIO0, GPIO_DI_GPIOA, 3, IRQn_GPIO0_A); + + // I2C: blocking transfers; the DMA-backed background path runs on dma_mgr. + static HPMI2C i2c3(HPM_I2C3, clock_i2c3, {100000U}); + + // PWM on a GPTMR comparator: the SoC has no PWM peripheral, so HPMPWM takes its GPTMR + // fallback path; the duty comparator is fixed at 0 and SetConfig is called when + // libxr_config sets a frequency. + static HPMPWM pwm_gptmr0_ch1(reinterpret_cast(HPM_GPTMR0), + clock_gptmr0, 1, 0, HPMPWM::Polarity::NORMAL); + + // Hardware registration + XR_REGISTER(power_manager, LibXR::PowerManager); + + XR_REGISTER(pa10, LibXR::GPIO); + XR_REGISTER(pa3, LibXR::GPIO); + + XR_REGISTER(pwm_gptmr0_ch1, LibXR::PWM); + + XR_REGISTER(i2c3, LibXR::I2C); + + /* User Code Begin 3 */ + /* User Code End 3 */ + XROBOT_MAIN(); +} diff --git a/tests/data/hpm/hpm5301evklite_main.c b/tests/data/hpm/hpm5301evklite_main.c new file mode 100644 index 0000000..0e2c4c6 --- /dev/null +++ b/tests/data/hpm/hpm5301evklite_main.c @@ -0,0 +1,32 @@ +// Board bring-up of the HPM5301EVKLite BSP. +// init_bsp_pins() runs first so the console pins exist before the UART clock is +// enabled; it and the peripheral clock functions are generated by the HPM Pinmux +// Tool from boards/hpm5301evklite/tool_config.hpmpc. The LibXR objects for these +// peripherals are created in app_main(). +#include "app_main.h" +#include "board.h" +#include "clock.h" +#include "pinmux.h" + +int main(void) +{ + // Pins: UART0 PA00/PA01 (console), UART3 PB15/PB14, I2C3 PB13/PB12, SPI1 PA26-PA29, + // GPTMR0 COMP1 PB08, ADC0 IN2 PB10, ACMP COMP1 PB09 / CMP1_INN4 PB11, LED PA10, + // KEY&BOOT PA03, USB0 PA24/PA25 and PY00 (ID) / PY01 (OC) + init_bsp_pins(); + + // SDK board: PLL/clock tree, console on UART0, banner + board_init(); + + // Peripheral clocks + init_uart3_clock(); + init_i2c3_clock(); + init_spi1_clock(); + init_gptmr0_clock(); + init_adc0_bus_clock(); + init_acmp0_clock(); + init_usb0_clock(); + + app_main(); + return 0; +} diff --git a/tests/data/hpm/rmcs_slave_lite.hpmpc b/tests/data/hpm/rmcs_slave_lite.hpmpc new file mode 100644 index 0000000..0c98c26 --- /dev/null +++ b/tests/data/hpm/rmcs_slave_lite.hpmpc @@ -0,0 +1,305 @@ +{ + "clientKey": "c9bee2ba0b04fd2fe7d6a22347310040", + "secretKey": "d248f40f80b94d4d2125d03249f3888f", + "content": { + "pid": "249b3cd4-8686-48fd-8709-9da1368a6680", + "info": { + "projectName": "rmcs_slave_lite", + "sdkName": "1.13.0", + "socName": "HPM5361", + "packageName": "QFN48", + "createTime": 1791236041366, + "updateTime": 1791302904192 + }, + "pinmux": { + "functions": { + "init_bsp_jtag_shared_pins": { + "selectPins": { + "PA04": { + "signal": "MCAN1.A.RXD", + "padCtls": {} + }, + "PA05": { + "signal": "MCAN1.A.TXD", + "padCtls": {} + }, + "PA06": { + "signal": "PWM1.A.P[6]", + "padCtls": {} + }, + "PA07": { + "signal": "GPIO.A.A[07]", + "padCtls": { + "HYS": "1", + "PE": "1", + "PS": "1" + } + } + }, + "managers": { + "gpiom": { + "PA07": { + "direction": "0", + "gpioController": "1", + "interrupt": "0", + "gpioControllerHide": "[]" + } + } + } + }, + "init_bsp_pins": { + "selectPins": { + "PA01": { + "signal": "MCAN0.A.RXD", + "padCtls": {} + }, + "PA00": { + "signal": "MCAN0.A.TXD", + "padCtls": {} + }, + "PA09": { + "signal": "MCAN2.A.RXD", + "padCtls": {} + }, + "PA08": { + "signal": "MCAN2.A.TXD", + "padCtls": {} + }, + "PB14": { + "signal": "MCAN3.C.RXD", + "padCtls": {} + }, + "PB15": { + "signal": "MCAN3.C.TXD", + "padCtls": {} + }, + "PY01": { + "signal": "UART0.G.RXD", + "padCtls": { + "HYS": "1", + "PE": "1", + "PS": "1" + } + }, + "PY00": { + "signal": "UART0.G.TXD", + "padCtls": { + "PE": "1", + "PS": "1" + } + }, + "PB09": { + "signal": "UART2.B.RXD", + "padCtls": { + "HYS": "1", + "PE": "1", + "PS": "1" + } + }, + "PB08": { + "signal": "UART2.B.TXD", + "padCtls": { + "PE": "1", + "PS": "1" + } + }, + "PA30": { + "signal": "UART7.A.RXD", + "padCtls": { + "PE": "1", + "PS": "1", + "HYS": "1" + } + }, + "PA27": { + "signal": "SPI1.A.SCLK", + "padCtls": { + "LOOP_BACK": "1" + } + }, + "PA28": { + "signal": "SPI1.A.MISO", + "padCtls": {} + }, + "PA29": { + "signal": "SPI1.A.MOSI", + "padCtls": {} + }, + "PB11": { + "signal": "GPIO.A.B[11]", + "padCtls": {} + }, + "PB13": { + "signal": "GPIO.A.B[13]", + "padCtls": {} + }, + "PB12": { + "signal": "GPIO.A.B[12]", + "padCtls": {} + }, + "PB10": { + "signal": "GPIO.A.B[10]", + "padCtls": {} + }, + "PA31": { + "signal": "GPIO.A.A[31]", + "padCtls": { + "HYS": "1", + "PE": "1", + "PS": "0" + } + }, + "PA10": { + "signal": "GPTMR0.A.COMP[2]", + "padCtls": {} + }, + "PA26": { + "signal": "GPTMR2.A.COMP[2]", + "padCtls": {} + }, + "PA25": { + "signal": "USBPHY.A.USB_DM", + "padCtls": {} + }, + "PA24": { + "signal": "USBPHY.A.USB_DP", + "padCtls": {} + } + }, + "managers": { + "gpiom": { + "PB11": { + "direction": "1", + "gpioController": "1", + "gpioControllerHide": "[]", + "outputLevel": "1" + }, + "PB13": { + "direction": "1", + "gpioController": "1", + "gpioControllerHide": "[]", + "outputLevel": "1" + }, + "PB12": { + "direction": "0", + "gpioController": "1", + "interrupt": "0", + "gpioControllerHide": "[]" + }, + "PB10": { + "direction": "0", + "gpioController": "1", + "interrupt": "0", + "gpioControllerHide": "[]" + }, + "PA31": { + "direction": "0", + "gpioController": "1", + "interrupt": "0", + "gpioControllerHide": "[]" + } + } + } + } + }, + "sorter": [ + "init_bsp_pins", + "init_bsp_jtag_shared_pins" + ] + }, + "clock": { + "functions": { + "init_clocks": { + "selectClocks": { + "srcClocks": [ + "osc32k", + "osc24m", + "clk0_pll0", + "clk1_pll0", + "clk2_pll0", + "clk0_pll1", + "clk1_pll1", + "clk2_pll1", + "clk3_pll1" + ], + "funcClocks": [ + "clk_top_can0", + "clk_top_can1", + "clk_top_can2", + "clk_top_can3", + "clk_top_urt0", + "clk_top_urt2", + "clk_top_urt7", + "clk_top_spi1", + "clk_top_tmr0", + "clk_top_tmr2", + "clk_usb0" + ], + "selfOnlyFuncClocks": [], + "pllVcoClocks": [ + "pll0", + "pll1" + ] + }, + "enableClocks": { + "clk_top_can0": "0", + "clk_top_can1": "0", + "clk_top_can2": "0", + "clk_top_can3": "0", + "clk_top_tmr0": "0", + "clk_top_tmr2": "0", + "clk_top_spi1": "0", + "clk_top_urt0": "0", + "clk_top_urt2": "0", + "clk_top_urt7": "0", + "clk_usb0": "0" + }, + "pllClkConfig": { + "pll0": { + "params": { + "mfi": 40, + "mfn": 0, + "mfd": 240000000 + } + }, + "pll1": { + "params": { + "mfi": 40, + "mfn": 0, + "mfd": 240000000 + } + } + }, + "funcClkConfig": { + "clk_top_cpu0": { + "from": "clk0_pll0", + "divFactor": 2 + }, + "clk_top_mct0": { + "from": "osc24m", + "divFactor": 1 + } + }, + "srcClkConfig": { + "clk1_pll1": { + "divFactor": 3 + }, + "clk2_pll1": { + "divFactor": 7 + }, + "clk0_pll1": { + "divFactor": 1 + }, + "clk1_pll0": { + "divFactor": 3 + }, + "clk2_pll0": { + "divFactor": 7 + } + } + } + }, + "sorter": [] + } + } +} \ No newline at end of file diff --git a/tests/data/hpm/rmcs_slave_lite_app.yaml b/tests/data/hpm/rmcs_slave_lite_app.yaml new file mode 100644 index 0000000..d669fc5 --- /dev/null +++ b/tests/data/hpm/rmcs_slave_lite_app.yaml @@ -0,0 +1,2 @@ +dependency: + - board_gpt_pin diff --git a/tests/data/hpm/rmcs_slave_lite_expected_app_main.cpp b/tests/data/hpm/rmcs_slave_lite_expected_app_main.cpp new file mode 100644 index 0000000..6855d7b --- /dev/null +++ b/tests/data/hpm/rmcs_slave_lite_expected_app_main.cpp @@ -0,0 +1,52 @@ +// Generated by `libxr gen`; do not edit by hand. +// Generated from boards/rmcs_slave_lite/tool_config.hpmpc (HPM5361); the Pinmux Tool +// configures the pins via init_bsp_pins() in main.c and main.c stays BSP-owned. Put +// application code between the "User Code Begin" and "User Code End" markers. +#include "app_main.h" + +#include "libxr.hpp" +#include "hpm_gpio.hpp" +#include "hpm_power.hpp" +#include "hpm_soc.h" +#include "hpm_timebase.hpp" +#include "xrobot_main.hpp" + +using namespace LibXR; + +/* User Code Begin 1 */ +/* User Code End 1 */ + +extern "C" void app_main(void) +{ + /* User Code Begin 2 */ + /* User Code End 2 */ + + // Timebase and platform + static HPMTimebase timebase; + PlatformInit(); + static HPMPowerManager power_manager; + + // GPIO: init_bsp_pins() configured pb11 (PB11) as an output pin, pb13 (PB13) as an + // output pin, pb12 (PB12) as an input pin, pb10 (PB10) as an input pin, pa31 (PA31) as + // an input pin with a pull-down. The interrupt edge comes from the pinmux and every + // object carries its port IRQ; RegisterCallback and EnableInterrupt belong to the + // consumer (a Module or the User Code). + static HPMGPIO pb11(HPM_GPIO0, GPIO_DI_GPIOB, 11, IRQn_GPIO0_B); + static HPMGPIO pb13(HPM_GPIO0, GPIO_DI_GPIOB, 13, IRQn_GPIO0_B); + static HPMGPIO pb12(HPM_GPIO0, GPIO_DI_GPIOB, 12, IRQn_GPIO0_B); + static HPMGPIO pb10(HPM_GPIO0, GPIO_DI_GPIOB, 10, IRQn_GPIO0_B); + static HPMGPIO pa31(HPM_GPIO0, GPIO_DI_GPIOA, 31, IRQn_GPIO0_A); + + // Hardware registration + XR_REGISTER(power_manager, LibXR::PowerManager); + + XR_REGISTER(pb11, LibXR::GPIO); + XR_REGISTER(pb13, LibXR::GPIO); + XR_REGISTER(pb12, LibXR::GPIO); + XR_REGISTER(pb10, LibXR::GPIO); + XR_REGISTER(pa31, LibXR::GPIO); + + /* User Code Begin 3 */ + /* User Code End 3 */ + XROBOT_MAIN(); +} diff --git a/tests/data/hpm/rmcs_slave_lite_main.c b/tests/data/hpm/rmcs_slave_lite_main.c new file mode 100644 index 0000000..d167cea --- /dev/null +++ b/tests/data/hpm/rmcs_slave_lite_main.c @@ -0,0 +1,29 @@ +// Board bring-up of the RMCS Slave Lite BSP. +// Sets up the SDK board, then the pins and the clocks of the peripherals the board uses. +// The pins come from init_bsp_pins() and the clocks from init_clocks(); both are generated +// by the HPM Pinmux Tool from boards/rmcs_slave_lite/tool_config.hpmpc. The LibXR objects +// for these peripherals are created in app_main(). +#include "app_main.h" +#include "board.h" +#include "clock.h" +#include "pinmux.h" + +int main(void) +{ + // Pins: CAN0 PA00/PA01, CAN2 PA08/PA09, CAN3 PB15/PB14, UART0 PY00/PY01 (console), + // UART2 PB08/PB09, DBUS UART7 RX PA30, SPI1 PA27-PA29, IMU CS PB11/PB13 (high) and INT + // PB12/PB10, USB switch PA31, buzzer GPTMR0 COMP2 PA10, IMU heater GPTMR2 COMP2 PA26, + // USB0 PA24/PA25. They come first so the console pins exist when board_init() prints. + init_bsp_pins(); + + // Clocks (init_clocks(): PLLs, CPU/bus and peripheral clocks), console on UART0, PMP + board_init(); + +#if !RMCS_KEEP_JTAG + // PA04-PA07 stop being JTAG: CAN1 PA04/PA05, WS2812 PWM1 P6 PA06 and KEY PA07 + init_bsp_jtag_shared_pins(); +#endif + + app_main(); + return 0; +} diff --git a/tests/data/mspm0/g3507.syscfg b/tests/data/mspm0/g3507.syscfg new file mode 100644 index 0000000..71b7572 --- /dev/null +++ b/tests/data/mspm0/g3507.syscfg @@ -0,0 +1,230 @@ +/** + * These arguments were used when this file was generated. They will be automatically applied on subsequent loads + * via the GUI or CLI. Run CLI with '--help' for additional information on how to override these arguments. + * @cliArgs --device "MSPM0G350X" --part "Default" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @v2CliArgs --device "MSPM0G3507" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @versions {"tool":"1.28.1+4785"} + */ + +/** + * Import the modules used in this configuration. + */ +const ADC12 = scripting.addModule("/ti/driverlib/ADC12", {}, false); +const ADC121 = ADC12.addInstance(); +const ADC122 = ADC12.addInstance(); +const DAC12 = scripting.addModule("/ti/driverlib/DAC12"); +const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); +const GPIO1 = GPIO.addInstance(); +const GPIO2 = GPIO.addInstance(); +const I2C = scripting.addModule("/ti/driverlib/I2C", {}, false); +const I2C1 = I2C.addInstance(); +const I2C2 = I2C.addInstance(); +const MCAN = scripting.addModule("/ti/driverlib/MCAN", {}, false); +const MCAN1 = MCAN.addInstance(); +const PWM = scripting.addModule("/ti/driverlib/PWM", {}, false); +const PWM1 = PWM.addInstance(); +const QEI = scripting.addModule("/ti/driverlib/QEI", {}, false); +const QEI1 = QEI.addInstance(); +const SPI = scripting.addModule("/ti/driverlib/SPI", {}, false); +const SPI1 = SPI.addInstance(); +const SYSCTL = scripting.addModule("/ti/driverlib/SYSCTL"); +const SYSTICK = scripting.addModule("/ti/driverlib/SYSTICK"); +const UART = scripting.addModule("/ti/driverlib/UART", {}, false); +const UART1 = UART.addInstance(); +const UART2 = UART.addInstance(); + +/** + * Write custom configuration values to the imported modules. + */ +const divider1 = system.clockTree["PLL_CLK2X_DIV"]; +divider1.divideValue = 4; + +const divider2 = system.clockTree["PLL_CLK1_DIV"]; +divider2.divideValue = 2; + +const divider3 = system.clockTree["PLL_PDIV"]; +divider3.divideValue = 2; + +const divider4 = system.clockTree["UDIV"]; +divider4.divideValue = 2; + +const gate1 = system.clockTree["MFPCLKGATE"]; +gate1.enable = true; + +const multiplier1 = system.clockTree["PLL_QDIV"]; +multiplier1.multiplyValue = 20; + +const mux0 = system.clockTree["CANCLKMUX"]; +mux0.inputSelect = "CANCLKMUX_PLLCLK1_OUT"; + +const mux1 = system.clockTree["EXHFMUX"]; +mux1.inputSelect = "EXHFMUX_XTAL"; + +const mux2 = system.clockTree["SYSPLLMUX"]; +mux2.inputSelect = "zSYSPLLMUX_HFCLK"; + +const mux3 = system.clockTree["HSCLKMUX"]; +mux3.inputSelect = "HSCLKMUX_SYSPLL2X"; + +const mux4 = system.clockTree["EXLFMUX"]; +mux4.inputSelect = "EXLFMUX_XTAL"; + +const mux5 = system.clockTree["LFXTMUX"]; +mux5.inputSelect = "LFXTMUX_TRUE"; + +const pinFunction1 = system.clockTree["HFXT"]; +pinFunction1.enable = true; +pinFunction1.inputFreq = 16; +pinFunction1.HFXTStartup = 255; +pinFunction1.HFCLKMonitor = true; +pinFunction1.peripheral.hfxInPin.$assign = "PA5"; +pinFunction1.peripheral.hfxOutPin.$assign = "PA6"; + +const pinFunction2 = system.clockTree["LFXT"]; +pinFunction2.enable = true; +pinFunction2.inputFreq = 32.768; +pinFunction2.peripheral.lfxInPin.$assign = "PA3"; +pinFunction2.peripheral.lfxOutPin.$assign = "PA4"; + +ADC121.$name = "ADC12_0"; +ADC121.adcMem0chansel = "DL_ADC12_INPUT_CHAN_3"; +ADC121.peripheral.$assign = "ADC0"; +ADC121.peripheral.adcPin3.$assign = "PA24"; + +ADC122.$name = "ADC12_1"; +ADC122.adcMem0chansel = "DL_ADC12_INPUT_CHAN_5"; +ADC122.peripheral.$assign = "ADC1"; +ADC122.peripheral.adcPin5.$assign = "PB18"; + +DAC12.dacOutputPinEn = true; +DAC12.dacAmplifier = "ON"; +DAC12.peripheral.$assign = "DAC0"; +DAC12.peripheral.OutPin.$assign = "PA15"; + +GPIO1.$name = "GPIO_LEDS"; +GPIO1.associatedPins.create(2); +GPIO1.associatedPins[0].$name = "PIN_LED1"; +GPIO1.associatedPins[0].initialValue = "SET"; +GPIO1.associatedPins[0].pin.$assign = "PB8"; +GPIO1.associatedPins[1].$name = "PIN_LED2"; +GPIO1.associatedPins[1].initialValue = "SET"; +GPIO1.associatedPins[1].pin.$assign = "PA16"; + +GPIO2.$name = "GPIO_KEYS"; +GPIO2.associatedPins.create(2); +GPIO2.associatedPins[0].$name = "PIN_KEY1"; +GPIO2.associatedPins[0].direction = "INPUT"; +GPIO2.associatedPins[0].internalResistor = "NONE"; +GPIO2.associatedPins[0].interruptEn = true; +GPIO2.associatedPins[0].polarity = "FALL"; +GPIO2.associatedPins[0].pin.$assign = "PB24"; +GPIO2.associatedPins[1].$name = "PIN_KEY2"; +GPIO2.associatedPins[1].direction = "INPUT"; +GPIO2.associatedPins[1].internalResistor = "NONE"; +GPIO2.associatedPins[1].interruptEn = true; +GPIO2.associatedPins[1].polarity = "FALL"; +GPIO2.associatedPins[1].pin.$assign = "PB20"; + +I2C1.$name = "I2C_0"; +I2C1.basicEnableController = true; +I2C1.basicControllerBusSpeed = 100000; +I2C1.peripheral.$assign = "I2C0"; +I2C1.peripheral.sdaPin.$assign = "PA0"; +I2C1.peripheral.sclPin.$assign = "PA1"; + +I2C2.$name = "I2C_1"; +I2C2.basicEnableController = true; +I2C2.basicControllerBusSpeed = 100000; +I2C2.peripheral.$assign = "I2C1"; +I2C2.peripheral.sdaPin.$assign = "PB3"; +I2C2.peripheral.sclPin.$assign = "PB2"; + +MCAN1.$name = "MCAN_0"; +MCAN1.peripheral.$assign = "CANFD0"; +MCAN1.peripheral.rxPin.$assign = "PA13"; +MCAN1.peripheral.txPin.$assign = "PA12"; + +PWM1.$name = "PWM_TIMA1"; +PWM1.peripheral.$assign = "TIMA1"; +PWM1.peripheral.ccp0Pin.$assign = "PA28"; +PWM1.peripheral.ccp1Pin.$assign = "PA31"; + +QEI1.$name = "QEI_TIMG8"; +QEI1.enableIndexInput = true; +QEI1.peripheral.$assign = "TIMG8"; +QEI1.peripheral.ccp0Pin.$assign = "PA26"; +QEI1.peripheral.ccp1Pin.$assign = "PA22"; +QEI1.peripheral.idxPin.$assign = "PB14"; + +const Board = scripting.addModule("/ti/driverlib/Board", {}, false); +Board.peripheral.$assign = "DEBUGSS"; +Board.peripheral.swclkPin.$assign = "PA20"; +Board.peripheral.swdioPin.$assign = "PA19"; + +SPI1.$name = "SPI_1"; +SPI1.chipSelect = ["0"]; +SPI1.rxFifoThreshold = "DL_SPI_RX_FIFO_LEVEL_ONE_FRAME"; +SPI1.txFifoThreshold = "DL_SPI_TX_FIFO_LEVEL_ONE_FRAME"; +SPI1.enabledInterrupts = ["DMA_DONE_RX","DMA_DONE_TX"]; +SPI1.enabledDMAEvent1Triggers = "DL_SPI_DMA_INTERRUPT_RX"; +SPI1.enabledDMAEvent2Triggers = "DL_SPI_DMA_INTERRUPT_TX"; +SPI1.peripheral.$assign = "SPI1"; +SPI1.peripheral.sclkPin.$assign = "PB9"; +SPI1.peripheral.mosiPin.$assign = "PB15"; +SPI1.peripheral.misoPin.$assign = "PB7"; +SPI1.peripheral.cs0Pin.$assign = "PB6"; +SPI1.cs0PinConfig.enableConfig = true; +SPI1.cs0PinConfig.internalResistor = "PULL_UP"; +SPI1.DMA_CHANNEL_EVENT1.$name = "DMA_CH_SPI1_RX"; +SPI1.DMA_CHANNEL_EVENT1.addressMode = "f2b"; +SPI1.DMA_CHANNEL_EVENT1.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT1.dstLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT1.peripheral.$assign = "DMA_CH6"; +SPI1.DMA_CHANNEL_EVENT2.$name = "DMA_CH_SPI1_TX"; +SPI1.DMA_CHANNEL_EVENT2.addressMode = "b2f"; +SPI1.DMA_CHANNEL_EVENT2.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.dstLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.peripheral.$assign = "DMA_CH2"; + +SYSCTL.forceDefaultClkConfig = true; +SYSCTL.clockTreeEn = true; + +SYSTICK.periodEnable = true; +SYSTICK.period = 80000; +SYSTICK.interruptEnable = true; +SYSTICK.systickEnable = true; + +UART1.$name = "UART_0"; +UART1.targetBaudRate = 115200; +UART1.enableFIFO = true; +UART1.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY"; +UART1.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART1.enableDMATX = true; +UART1.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART1.peripheral.$assign = "UART0"; +UART1.peripheral.rxPin.$assign = "PA11"; +UART1.peripheral.txPin.$assign = "PA10"; +UART1.DMA_CHANNEL_TX.$name = "DMA_CH_UART0_TX"; +UART1.DMA_CHANNEL_TX.addressMode = "b2f"; +UART1.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART1.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART1.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART1.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH3"; + +UART2.$name = "UART_1"; +UART2.targetBaudRate = 115200; +UART2.enableFIFO = true; +UART2.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY"; +UART2.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART2.enableDMATX = true; +UART2.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART2.peripheral.$assign = "UART1"; +UART2.peripheral.rxPin.$assign = "PA9"; +UART2.peripheral.txPin.$assign = "PA8"; +UART2.DMA_CHANNEL_TX.$name = "DMA_CH_UART1_TX"; +UART2.DMA_CHANNEL_TX.addressMode = "b2f"; +UART2.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART2.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART2.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART2.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH4"; + diff --git a/tests/data/mspm0/g3507_expected_app_main.cpp b/tests/data/mspm0/g3507_expected_app_main.cpp new file mode 100644 index 0000000..37117b7 --- /dev/null +++ b/tests/data/mspm0/g3507_expected_app_main.cpp @@ -0,0 +1,144 @@ +// 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 */ +#include +#include + +// KEY interrupt counters, incremented in the GPIO interrupt callbacks. +static std::atomic key1_irq_count{0}; +static std::atomic key2_irq_count{0}; + +static void OnKeyInterrupt(bool in_isr, std::atomic* count) +{ + (void)in_isr; + count->fetch_add(1, std::memory_order_relaxed); +} +/* 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 +#define DMA_CH_UART1_TX_LIBXR_UART_IRQN UART_1_INST_INT_IRQN +#define DMA_CH_UART1_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(size_t) static uint8_t uart1_tx_buf[2 * 128]; +alignas(4) static uint8_t spi1_rx_buf[32]; +alignas(4) static uint8_t spi1_tx_buf[32]; +alignas(4) static uint8_t i2c0_buf[32]; +alignas(4) static uint8_t i2c1_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 LED2(GPIO_LEDS_PIN_LED2_PORT, GPIO_LEDS_PIN_LED2_PIN, + GPIO_LEDS_PIN_LED2_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)); + static MSPM0UART uart1(MSPM0_UART_MAIN_INIT(UART_1, DMA_CH_UART1_TX, uart1_tx_buf, + sizeof(uart1_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}); + static MSPM0I2C i2c1(MSPM0_I2C_INIT(I2C_1, PD0_BUSCLK_FREQ, i2c1_buf, sizeof(i2c1_buf), + 8), {I2C_1_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(LED2, LibXR::GPIO); + XR_REGISTER(KEY1, LibXR::GPIO); + XR_REGISTER(KEY2, LibXR::GPIO); + + XR_REGISTER(pwm_tima1_c0, LibXR::PWM); + XR_REGISTER(pwm_tima1_c1, LibXR::PWM); + + XR_REGISTER(spi1, LibXR::SPI); + + XR_REGISTER(uart0, LibXR::UART); + XR_REGISTER(uart1, LibXR::UART); + + XR_REGISTER(i2c0, LibXR::I2C); + XR_REGISTER(i2c1, LibXR::I2C); + + XR_REGISTER(ramfs, LibXR::RamFS); + + XR_REGISTER(terminal, LibXR::Terminal<32, 32, 5, 5>); + + /* User Code Begin 3 */ + // The keys pull the pin low against an external 15k pull-up: falling edge, no internal + // pull. The interrupt registration belongs to the consumer (O1: the generator creates + // the GPIO object only). + KEY1.SetConfig({GPIO::Direction::FALL_INTERRUPT, GPIO::Pull::NONE}); + KEY1.RegisterCallback(GPIO::Callback::Create(OnKeyInterrupt, &key1_irq_count)); + KEY1.EnableInterrupt(); + KEY2.SetConfig({GPIO::Direction::FALL_INTERRUPT, GPIO::Pull::NONE}); + KEY2.RegisterCallback(GPIO::Callback::Create(OnKeyInterrupt, &key2_irq_count)); + KEY2.EnableInterrupt(); + /* User Code End 3 */ + XROBOT_MAIN(); +} diff --git a/tests/data/mspm0/g3507_ti_msp_dl_config.c b/tests/data/mspm0/g3507_ti_msp_dl_config.c new file mode 100644 index 0000000..ea4ce5b --- /dev/null +++ b/tests/data/mspm0/g3507_ti_msp_dl_config.c @@ -0,0 +1,864 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.c ============= + * Configured MSPM0 DriverLib module definitions + * + * DO NOT EDIT - This file is generated for the MSPM0G350X + * by the SysConfig tool. + */ + +#include "ti_msp_dl_config.h" + +DL_TimerA_backupConfig gPWM_TIMA1Backup; +DL_TimerG_backupConfig gQEI_TIMG8Backup; +DL_SPI_backupConfig gSPI_1Backup; +DL_MCAN_backupConfig gMCAN_0Backup; + +/* + * ======== SYSCFG_DL_init ======== + * Perform any initialization needed before using any board APIs + */ +SYSCONFIG_WEAK void SYSCFG_DL_init(void) +{ + SYSCFG_DL_initPower(); + SYSCFG_DL_GPIO_init(); + /* Module-Specific Initializations*/ + SYSCFG_DL_SYSCTL_init(); + SYSCFG_DL_PWM_TIMA1_init(); + SYSCFG_DL_QEI_TIMG8_init(); + SYSCFG_DL_I2C_0_init(); + SYSCFG_DL_I2C_1_init(); + SYSCFG_DL_UART_0_init(); + SYSCFG_DL_UART_1_init(); + SYSCFG_DL_SPI_1_init(); + SYSCFG_DL_ADC12_0_init(); + SYSCFG_DL_ADC12_1_init(); + SYSCFG_DL_DMA_init(); + SYSCFG_DL_SYSTICK_init(); + SYSCFG_DL_DAC12_init(); + SYSCFG_DL_MCAN_0_init(); + /* Ensure backup structures have no valid state */ + gPWM_TIMA1Backup.backupRdy = false; + gQEI_TIMG8Backup.backupRdy = false; + + gSPI_1Backup.backupRdy = false; + gMCAN_0Backup.backupRdy = false; + +} +/* + * User should take care to save and restore register configuration in application. + * See Retention Configuration section for more details. + */ +SYSCONFIG_WEAK bool SYSCFG_DL_saveConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_TimerA_saveConfiguration(PWM_TIMA1_INST, &gPWM_TIMA1Backup); + retStatus &= DL_TimerG_saveConfiguration(QEI_TIMG8_INST, &gQEI_TIMG8Backup); + retStatus &= DL_SPI_saveConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_MCAN_saveConfiguration(MCAN_0_INST, &gMCAN_0Backup); + + return retStatus; +} + + +SYSCONFIG_WEAK bool SYSCFG_DL_restoreConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_TimerA_restoreConfiguration(PWM_TIMA1_INST, &gPWM_TIMA1Backup, false); + retStatus &= DL_TimerG_restoreConfiguration(QEI_TIMG8_INST, &gQEI_TIMG8Backup, false); + retStatus &= DL_SPI_restoreConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_MCAN_restoreConfiguration(MCAN_0_INST, &gMCAN_0Backup); + + return retStatus; +} + +SYSCONFIG_WEAK void SYSCFG_DL_initPower(void) +{ + DL_GPIO_reset(GPIOA); + DL_GPIO_reset(GPIOB); + DL_TimerA_reset(PWM_TIMA1_INST); + DL_TimerG_reset(QEI_TIMG8_INST); + DL_I2C_reset(I2C_0_INST); + DL_I2C_reset(I2C_1_INST); + DL_UART_Main_reset(UART_0_INST); + DL_UART_Main_reset(UART_1_INST); + DL_SPI_reset(SPI_1_INST); + DL_ADC12_reset(ADC12_0_INST); + DL_ADC12_reset(ADC12_1_INST); + + + DL_DAC12_reset(DAC0); + DL_MCAN_reset(MCAN_0_INST); + + DL_GPIO_enablePower(GPIOA); + DL_GPIO_enablePower(GPIOB); + DL_TimerA_enablePower(PWM_TIMA1_INST); + DL_TimerG_enablePower(QEI_TIMG8_INST); + DL_I2C_enablePower(I2C_0_INST); + DL_I2C_enablePower(I2C_1_INST); + DL_UART_Main_enablePower(UART_0_INST); + DL_UART_Main_enablePower(UART_1_INST); + DL_SPI_enablePower(SPI_1_INST); + DL_ADC12_enablePower(ADC12_0_INST); + DL_ADC12_enablePower(ADC12_1_INST); + + + DL_DAC12_enablePower(DAC0); + DL_MCAN_enablePower(MCAN_0_INST); + delay_cycles(POWER_STARTUP_DELAY); +} + +SYSCONFIG_WEAK void SYSCFG_DL_GPIO_init(void) +{ + + DL_GPIO_initPeripheralAnalogFunction(GPIO_HFXIN_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_HFXOUT_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_LFXIN_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_LFXOUT_IOMUX); + + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_TIMA1_C0_IOMUX,GPIO_PWM_TIMA1_C0_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_TIMA1_C0_PORT, GPIO_PWM_TIMA1_C0_PIN); + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_TIMA1_C1_IOMUX,GPIO_PWM_TIMA1_C1_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_TIMA1_C1_PORT, GPIO_PWM_TIMA1_C1_PIN); + + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_PHA_IOMUX,GPIO_QEI_TIMG8_PHA_IOMUX_FUNC); + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_PHB_IOMUX,GPIO_QEI_TIMG8_PHB_IOMUX_FUNC); + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_IDX_IOMUX,GPIO_QEI_TIMG8_IDX_IOMUX_FUNC); + + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SDA, + GPIO_I2C_0_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SCL, + GPIO_I2C_0_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SCL); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_1_IOMUX_SDA, + GPIO_I2C_1_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_1_IOMUX_SCL, + GPIO_I2C_1_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_1_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_1_IOMUX_SCL); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_0_IOMUX_TX, GPIO_UART_0_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_0_IOMUX_RX, GPIO_UART_0_IOMUX_RX_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_1_IOMUX_TX, GPIO_UART_1_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_1_IOMUX_RX, GPIO_UART_1_IOMUX_RX_FUNC); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_SCLK, GPIO_SPI_1_IOMUX_SCLK_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_PICO, GPIO_SPI_1_IOMUX_PICO_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_1_IOMUX_POCI, GPIO_SPI_1_IOMUX_POCI_FUNC); + + DL_GPIO_initPeripheralOutputFunctionFeatures( + GPIO_SPI_1_IOMUX_CS0, GPIO_SPI_1_IOMUX_CS0_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_DRIVE_STRENGTH_LOW, DL_GPIO_HIZ_DISABLE); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_PIN_LED1_IOMUX); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_PIN_LED2_IOMUX); + + DL_GPIO_initDigitalInputFeatures(GPIO_KEYS_PIN_KEY1_IOMUX, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_NONE, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + + DL_GPIO_initDigitalInputFeatures(GPIO_KEYS_PIN_KEY2_IOMUX, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_NONE, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + + DL_GPIO_setPins(GPIOA, GPIO_LEDS_PIN_LED2_PIN); + DL_GPIO_enableOutput(GPIOA, GPIO_LEDS_PIN_LED2_PIN); + DL_GPIO_setPins(GPIOB, GPIO_LEDS_PIN_LED1_PIN); + DL_GPIO_enableOutput(GPIOB, GPIO_LEDS_PIN_LED1_PIN); + DL_GPIO_setUpperPinsPolarity(GPIOB, DL_GPIO_PIN_24_EDGE_FALL | + DL_GPIO_PIN_20_EDGE_FALL); + DL_GPIO_clearInterruptStatus(GPIOB, GPIO_KEYS_PIN_KEY1_PIN | + GPIO_KEYS_PIN_KEY2_PIN); + DL_GPIO_enableInterrupt(GPIOB, GPIO_KEYS_PIN_KEY1_PIN | + GPIO_KEYS_PIN_KEY2_PIN); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_MCAN_0_IOMUX_CAN_TX, GPIO_MCAN_0_IOMUX_CAN_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_MCAN_0_IOMUX_CAN_RX, GPIO_MCAN_0_IOMUX_CAN_RX_FUNC); + +} + + +static const DL_SYSCTL_SYSPLLConfig gSYSPLLConfig = { + .inputFreq = DL_SYSCTL_SYSPLL_INPUT_FREQ_8_16_MHZ, + .rDivClk2x = 3, + .rDivClk1 = 0, + .rDivClk0 = 0, + .enableCLK2x = DL_SYSCTL_SYSPLL_CLK2X_ENABLE, + .enableCLK1 = DL_SYSCTL_SYSPLL_CLK1_ENABLE, + .enableCLK0 = DL_SYSCTL_SYSPLL_CLK0_DISABLE, + .sysPLLMCLK = DL_SYSCTL_SYSPLL_MCLK_CLK2X, + .sysPLLRef = DL_SYSCTL_SYSPLL_REF_HFCLK, + .qDiv = 19, + .pDiv = DL_SYSCTL_SYSPLL_PDIV_2 +}; + +SYSCONFIG_WEAK bool SYSCFG_DL_SYSCTL_SYSPLL_init(void) +{ + bool fFCCRatioStatus = false; + uint32_t fFCCSysoscCount; + uint32_t fFCCPllCount; + uint32_t fFCCRatio; + uint32_t fccTimeOutCounter; + + DL_SYSCTL_setFCCPeriods( DL_SYSCTL_FCC_TRIG_CNT_02 ); + + /* Measuring PLL. */ + DL_SYSCTL_configFCC(DL_SYSCTL_FCC_TRIG_TYPE_RISE_RISE, + DL_SYSCTL_FCC_TRIG_SOURCE_LFCLK, + DL_SYSCTL_FCC_CLOCK_SOURCE_SYSPLLCLK1); + /* Get SYSPLL frequency using FCC */ + fccTimeOutCounter = 0; + DL_SYSCTL_startFCC(); + while (DL_SYSCTL_isFCCDone() == 0) { + delay_cycles(977); /* 1x LFCLK cycle = 32MHz/32.768kHz = 977, 30.5us */ + fccTimeOutCounter++; + if(fccTimeOutCounter > 65){ + /* Timeout set to approximately 2ms (user-customizable) */ + break; + } + } + + /* get measA= SYSPLLCLK1 freq wrt LFOSC*/ + fFCCPllCount = DL_SYSCTL_readFCC(); + + /* Measuring SYSPLL Source */ + DL_SYSCTL_configFCC(DL_SYSCTL_FCC_TRIG_TYPE_RISE_RISE, + DL_SYSCTL_FCC_TRIG_SOURCE_LFCLK, + DL_SYSCTL_FCC_CLOCK_SOURCE_HFCLK); + /* Get SYSPLL frequency using FCC */ + fccTimeOutCounter = 0; + DL_SYSCTL_startFCC(); + while (DL_SYSCTL_isFCCDone() == 0) { + delay_cycles(977); /* 1x LFCLK cycle = 32MHz/32.768kHz = 977, 30.5us */ + fccTimeOutCounter++; + if(fccTimeOutCounter > 65){ + /* Timeout set to approximately 2ms (user-customizable) */ + break; + } + } + + /* get measB= SYSOSC freq wrt LFOSC*/ + fFCCSysoscCount = DL_SYSCTL_readFCC(); + + /* Get ratio of both measurements*/ + fFCCRatio = (fFCCPllCount * FLOAT_TO_INT_SCALE) / fFCCSysoscCount; + /* Check ratio is within bounds*/ + if ((FCC_LOWER_BOUND < fFCCRatio) && (fFCCRatio < FCC_UPPER_BOUND)) + { + /* ratio is good for proceeding into application code. */ + fFCCRatioStatus = true; + } + + return fFCCRatioStatus; +} +static const DL_SYSCTL_LFCLKConfig gLFCLKConfig = { + .lowCap = false, + .monitor = false, + .xt1Drive = DL_SYSCTL_LFXT_DRIVE_STRENGTH_HIGHEST, +}; +SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void) +{ + + //Low Power Mode is configured to be SLEEP0 + DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0); + DL_SYSCTL_setFlashWaitState(DL_SYSCTL_FLASH_WAIT_STATE_2); + + + DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE); + /* Set default configuration */ + DL_SYSCTL_disableHFXT(); + DL_SYSCTL_disableSYSPLL(); + DL_SYSCTL_setHFCLKSourceHFXTParams(DL_SYSCTL_HFXT_RANGE_8_16_MHZ,255, true); + DL_SYSCTL_configSYSPLL((DL_SYSCTL_SYSPLLConfig *) &gSYSPLLConfig); + + /* + * [SYSPLL_ERR_01] + * PLL Incorrect locking WA start. + * Insert after every PLL enable. + * This can lead an infinite loop if the condition persists + * and can block entry to the application code. + */ + + while (SYSCFG_DL_SYSCTL_SYSPLL_init() == false) + { + /* Toggle SYSPLL enable to re-enable SYSPLL and re-check incorrect locking */ + DL_SYSCTL_disableSYSPLL(); + DL_SYSCTL_enableSYSPLL(); + + /* Wait until SYSPLL startup is stabilized*/ + while ((DL_SYSCTL_getClockStatus() & SYSCTL_CLKSTATUS_SYSPLLGOOD_MASK) != DL_SYSCTL_CLK_STATUS_SYSPLL_GOOD){} + } + DL_SYSCTL_setULPCLKDivider(DL_SYSCTL_ULPCLK_DIV_2); + DL_SYSCTL_setLFCLKSourceLFXT((DL_SYSCTL_LFCLKConfig *) &gLFCLKConfig); + DL_SYSCTL_enableMFPCLK(); + DL_SYSCTL_setMFPCLKSource(DL_SYSCTL_MFPCLK_SOURCE_SYSOSC); + DL_SYSCTL_setMCLKSource(SYSOSC, HSCLK, DL_SYSCTL_HSCLK_SOURCE_SYSPLL); + +} + + +/* + * Timer clock configuration to be sourced by / 1 (80000000 Hz) + * timerClkFreq = (timerClkSrc / (timerClkDivRatio * (timerClkPrescale + 1))) + * 80000000 Hz = 80000000 Hz / (1 * (0 + 1)) + */ +static const DL_TimerA_ClockConfig gPWM_TIMA1ClockConfig = { + .clockSel = DL_TIMER_CLOCK_BUSCLK, + .divideRatio = DL_TIMER_CLOCK_DIVIDE_1, + .prescale = 0U +}; + +static const DL_TimerA_PWMConfig gPWM_TIMA1Config = { + .pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN, + .period = 1000, + .isTimerWithFourCC = false, + .startTimer = DL_TIMER_STOP, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_PWM_TIMA1_init(void) { + + DL_TimerA_setClockConfig( + PWM_TIMA1_INST, (DL_TimerA_ClockConfig *) &gPWM_TIMA1ClockConfig); + + DL_TimerA_initPWMMode( + PWM_TIMA1_INST, (DL_TimerA_PWMConfig *) &gPWM_TIMA1Config); + + // Set Counter control to the smallest CC index being used + DL_TimerA_setCounterControl(PWM_TIMA1_INST,DL_TIMER_CZC_CCCTL0_ZCOND,DL_TIMER_CAC_CCCTL0_ACOND,DL_TIMER_CLC_CCCTL0_LCOND); + + DL_TimerA_setCaptureCompareOutCtl(PWM_TIMA1_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERA_CAPTURE_COMPARE_0_INDEX); + + DL_TimerA_setCaptCompUpdateMethod(PWM_TIMA1_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERA_CAPTURE_COMPARE_0_INDEX); + DL_TimerA_setCaptureCompareValue(PWM_TIMA1_INST, 1000, DL_TIMER_CC_0_INDEX); + + DL_TimerA_setCaptureCompareOutCtl(PWM_TIMA1_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERA_CAPTURE_COMPARE_1_INDEX); + + DL_TimerA_setCaptCompUpdateMethod(PWM_TIMA1_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERA_CAPTURE_COMPARE_1_INDEX); + DL_TimerA_setCaptureCompareValue(PWM_TIMA1_INST, 1000, DL_TIMER_CC_1_INDEX); + + DL_TimerA_enableClock(PWM_TIMA1_INST); + + + + DL_TimerA_setCCPDirection(PWM_TIMA1_INST , DL_TIMER_CC0_OUTPUT | DL_TIMER_CC1_OUTPUT ); + + +} + + +static const DL_TimerG_ClockConfig gQEI_TIMG8ClockConfig = { + .clockSel = DL_TIMER_CLOCK_BUSCLK, + .divideRatio = DL_TIMER_CLOCK_DIVIDE_1, + .prescale = 0U +}; + + +SYSCONFIG_WEAK void SYSCFG_DL_QEI_TIMG8_init(void) { + + DL_TimerG_setClockConfig( + QEI_TIMG8_INST, (DL_TimerG_ClockConfig *) &gQEI_TIMG8ClockConfig); + + DL_TimerG_configQEI(QEI_TIMG8_INST, DL_TIMER_QEI_MODE_3_INPUT, + DL_TIMER_CC_INPUT_INV_NOINVERT, DL_TIMER_CC_0_INDEX); + DL_TimerG_configQEI(QEI_TIMG8_INST, DL_TIMER_QEI_MODE_3_INPUT, + DL_TIMER_CC_INPUT_INV_NOINVERT, DL_TIMER_CC_1_INDEX); + DL_TimerG_setLoadValue(QEI_TIMG8_INST, 65535); + DL_TimerG_enableClock(QEI_TIMG8_INST); +} + + +static const DL_I2C_ClockConfig gI2C_0ClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_0_init(void) { + + DL_I2C_setClockConfig(I2C_0_INST, + (DL_I2C_ClockConfig *) &gI2C_0ClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_0_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_0_INST); + + /* Configure Controller Mode */ + DL_I2C_resetControllerTransfer(I2C_0_INST); + /* Set frequency to 100000 Hz*/ + DL_I2C_setTimerPeriod(I2C_0_INST, 39); + DL_I2C_setControllerTXFIFOThreshold(I2C_0_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setControllerRXFIFOThreshold(I2C_0_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + DL_I2C_enableControllerClockStretching(I2C_0_INST); + + + /* Enable module */ + DL_I2C_enableController(I2C_0_INST); + + +} +static const DL_I2C_ClockConfig gI2C_1ClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_1_init(void) { + + DL_I2C_setClockConfig(I2C_1_INST, + (DL_I2C_ClockConfig *) &gI2C_1ClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_1_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_1_INST); + + /* Configure Controller Mode */ + DL_I2C_resetControllerTransfer(I2C_1_INST); + /* Set frequency to 100000 Hz*/ + DL_I2C_setTimerPeriod(I2C_1_INST, 39); + DL_I2C_setControllerTXFIFOThreshold(I2C_1_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setControllerRXFIFOThreshold(I2C_1_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + DL_I2C_enableControllerClockStretching(I2C_1_INST); + + + /* Enable module */ + DL_I2C_enableController(I2C_1_INST); + + +} + +static const DL_UART_Main_ClockConfig gUART_0ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_0Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_0_init(void) +{ + DL_UART_Main_setClockConfig(UART_0_INST, (DL_UART_Main_ClockConfig *) &gUART_0ClockConfig); + + DL_UART_Main_init(UART_0_INST, (DL_UART_Main_Config *) &gUART_0Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115190.78 + */ + DL_UART_Main_setOversampling(UART_0_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_0_INST, UART_0_IBRD_40_MHZ_115200_BAUD, UART_0_FBRD_40_MHZ_115200_BAUD); + + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_0_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_0_INST); + DL_UART_Main_setRXFIFOThreshold(UART_0_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_0_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_0_INST); +} +static const DL_UART_Main_ClockConfig gUART_1ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_1Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_1_init(void) +{ + DL_UART_Main_setClockConfig(UART_1_INST, (DL_UART_Main_ClockConfig *) &gUART_1ClockConfig); + + DL_UART_Main_init(UART_1_INST, (DL_UART_Main_Config *) &gUART_1Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115190.78 + */ + DL_UART_Main_setOversampling(UART_1_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_1_INST, UART_1_IBRD_40_MHZ_115200_BAUD, UART_1_FBRD_40_MHZ_115200_BAUD); + + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_1_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_1_INST); + DL_UART_Main_setRXFIFOThreshold(UART_1_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_1_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_1_INST); +} + +static const DL_SPI_Config gSPI_1_config = { + .mode = DL_SPI_MODE_CONTROLLER, + .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0, + .parity = DL_SPI_PARITY_NONE, + .dataSize = DL_SPI_DATA_SIZE_8, + .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, + .chipSelectPin = DL_SPI_CHIP_SELECT_0, +}; + +static const DL_SPI_ClockConfig gSPI_1_clockConfig = { + .clockSel = DL_SPI_CLOCK_BUSCLK, + .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_1 +}; + +SYSCONFIG_WEAK void SYSCFG_DL_SPI_1_init(void) { + DL_SPI_setClockConfig(SPI_1_INST, (DL_SPI_ClockConfig *) &gSPI_1_clockConfig); + + DL_SPI_init(SPI_1_INST, (DL_SPI_Config *) &gSPI_1_config); + + /* Configure Controller mode */ + /* + * Set the bit rate clock divider to generate the serial output clock + * outputBitRate = (spiInputClock) / ((1 + SCR) * 2) + * 8000000 = (80000000)/((1 + 4) * 2) + */ + DL_SPI_setBitRateSerialClockDivider(SPI_1_INST, 4); + + /* Enable SPI TX interrupt as a trigger for DMA */ + DL_SPI_enableDMATransmitEvent(SPI_1_INST); + + /* Enable SPI RX interrupt as a trigger for DMA */ + DL_SPI_enableDMAReceiveEvent(SPI_1_INST, DL_SPI_DMA_INTERRUPT_RX); + /* Set RX and TX FIFO threshold levels */ + DL_SPI_setFIFOThreshold(SPI_1_INST, DL_SPI_RX_FIFO_LEVEL_ONE_FRAME, DL_SPI_TX_FIFO_LEVEL_ONE_FRAME); + DL_SPI_enableInterrupt(SPI_1_INST, (DL_SPI_INTERRUPT_DMA_DONE_RX | + DL_SPI_INTERRUPT_DMA_DONE_TX)); + + /* Enable module */ + DL_SPI_enable(SPI_1_INST); +} + +/* ADC12_0 Initialization */ +static const DL_ADC12_ClockConfig gADC12_0ClockConfig = { + .clockSel = DL_ADC12_CLOCK_SYSOSC, + .divideRatio = DL_ADC12_CLOCK_DIVIDE_1, + .freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32, +}; +SYSCONFIG_WEAK void SYSCFG_DL_ADC12_0_init(void) +{ + DL_ADC12_setClockConfig(ADC12_0_INST, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig); + DL_ADC12_configConversionMem(ADC12_0_INST, ADC12_0_ADCMEM_0, + DL_ADC12_INPUT_CHAN_3, DL_ADC12_REFERENCE_VOLTAGE_VDDA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, DL_ADC12_AVERAGING_MODE_DISABLED, + DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); + DL_ADC12_enableConversions(ADC12_0_INST); +} +/* ADC12_1 Initialization */ +static const DL_ADC12_ClockConfig gADC12_1ClockConfig = { + .clockSel = DL_ADC12_CLOCK_SYSOSC, + .divideRatio = DL_ADC12_CLOCK_DIVIDE_1, + .freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32, +}; +SYSCONFIG_WEAK void SYSCFG_DL_ADC12_1_init(void) +{ + DL_ADC12_setClockConfig(ADC12_1_INST, (DL_ADC12_ClockConfig *) &gADC12_1ClockConfig); + DL_ADC12_configConversionMem(ADC12_1_INST, ADC12_1_ADCMEM_0, + DL_ADC12_INPUT_CHAN_5, DL_ADC12_REFERENCE_VOLTAGE_VDDA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, DL_ADC12_AVERAGING_MODE_DISABLED, + DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); + DL_ADC12_enableConversions(ADC12_1_INST); +} + +static const DL_DMA_Config gDMA_CH_SPI1_RXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_INCREMENT, + .srcIncrement = DL_DMA_ADDR_UNCHANGED, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_0, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_RX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_RX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_RXConfig); +} +static const DL_DMA_Config gDMA_CH_SPI1_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_1, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART0_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_0_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART0_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART0_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART0_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART1_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_1_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART1_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART1_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART1_TXConfig); +} +SYSCONFIG_WEAK void SYSCFG_DL_DMA_init(void){ + SYSCFG_DL_DMA_CH_SPI1_RX_init(); + SYSCFG_DL_DMA_CH_SPI1_TX_init(); + SYSCFG_DL_DMA_CH_UART0_TX_init(); + SYSCFG_DL_DMA_CH_UART1_TX_init(); +} + + +SYSCONFIG_WEAK void SYSCFG_DL_SYSTICK_init(void) +{ + /* + * Initializes the SysTick period to 1.00 ms, + * enables the interrupt, and starts the SysTick Timer + */ + DL_SYSTICK_config(80000); +} + +static const DL_DAC12_Config gDAC12Config = { + .outputEnable = DL_DAC12_OUTPUT_ENABLED, + .resolution = DL_DAC12_RESOLUTION_12BIT, + .representation = DL_DAC12_REPRESENTATION_BINARY, + .voltageReferenceSource = DL_DAC12_VREF_SOURCE_VDDA_VSSA, + .amplifierSetting = DL_DAC12_AMP_ON, + .fifoEnable = DL_DAC12_FIFO_DISABLED, + .fifoTriggerSource = DL_DAC12_FIFO_TRIGGER_SAMPLETIMER, + .dmaTriggerEnable = DL_DAC12_DMA_TRIGGER_DISABLED, + .dmaTriggerThreshold = DL_DAC12_FIFO_THRESHOLD_ONE_QTR_EMPTY, + .sampleTimeGeneratorEnable = DL_DAC12_SAMPLETIMER_DISABLE, + .sampleRate = DL_DAC12_SAMPLES_PER_SECOND_500, +}; +SYSCONFIG_WEAK void SYSCFG_DL_DAC12_init(void) +{ + DL_DAC12_init(DAC0, (DL_DAC12_Config *) &gDAC12Config); + DL_DAC12_enable(DAC0); +} + +static const DL_MCAN_ClockConfig gMCAN_0ClockConf = { + .clockSel = DL_MCAN_FCLK_SYSPLLCLK1, + .divider = DL_MCAN_FCLK_DIV_1, +}; + +static const DL_MCAN_InitParams gMCAN_0InitParams= { + +/* Initialize MCAN Init parameters. */ + .fdMode = true, + .brsEnable = true, + .txpEnable = false, + .efbi = false, + .pxhddisable = false, + .darEnable = false, + .wkupReqEnable = false, + .autoWkupEnable = false, + .emulationEnable = false, + .tdcEnable = false, + .wdcPreload = 255, + +/* Transmitter Delay Compensation parameters. */ + .tdcConfig.tdcf = 10, + .tdcConfig.tdco = 6, +}; + + +static const DL_MCAN_MsgRAMConfigParams gMCAN_0MsgRAMConfigParams ={ + + /* Standard ID Filter List Start Address. */ + .flssa = MCAN_0_INST_MCAN_STD_ID_FILT_START_ADDR, + /* List Size: Standard ID. */ + .lss = MCAN_0_INST_MCAN_STD_ID_FILTER_NUM, + /* Extended ID Filter List Start Address. */ + .flesa = MCAN_0_INST_MCAN_EXT_ID_FILT_START_ADDR, + /* List Size: Extended ID. */ + .lse = MCAN_0_INST_MCAN_EXT_ID_FILTER_NUM, + /* Tx Buffers Start Address. */ + .txStartAddr = MCAN_0_INST_MCAN_TX_BUFF_START_ADDR, + /* Number of Dedicated Transmit Buffers. */ + .txBufNum = MCAN_0_INST_MCAN_TX_BUFF_SIZE, + .txFIFOSize = 0, + /* Tx Buffer Element Size. */ + .txBufMode = 0, + .txBufElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Tx Event FIFO Start Address. */ + .txEventFIFOStartAddr = MCAN_0_INST_MCAN_TX_EVENT_START_ADDR, + /* Event FIFO Size. */ + .txEventFIFOSize = MCAN_0_INST_MCAN_TX_EVENT_SIZE, + /* Level for Tx Event FIFO watermark interrupt. */ + .txEventFIFOWaterMark = 3, + /* Rx FIFO0 Start Address. */ + .rxFIFO0startAddr = MCAN_0_INST_MCAN_FIFO_0_START_ADDR, + /* Number of Rx FIFO elements. */ + .rxFIFO0size = MCAN_0_INST_MCAN_FIFO_0_NUM, + /* Rx FIFO0 Watermark. */ + .rxFIFO0waterMark = 3, + .rxFIFO0OpMode = 0, + /* Rx FIFO1 Start Address. */ + .rxFIFO1startAddr = MCAN_0_INST_MCAN_FIFO_1_START_ADDR, + /* Number of Rx FIFO elements. */ + .rxFIFO1size = MCAN_0_INST_MCAN_FIFO_1_NUM, + /* Level for Rx FIFO 1 watermark interrupt. */ + .rxFIFO1waterMark = 3, + /* FIFO blocking mode. */ + .rxFIFO1OpMode = 0, + /* Rx Buffer Start Address. */ + .rxBufStartAddr = MCAN_0_INST_MCAN_RX_BUFF_START_ADDR, + /* Rx Buffer Element Size. */ + .rxBufElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Rx FIFO0 Element Size. */ + .rxFIFO0ElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Rx FIFO1 Element Size. */ + .rxFIFO1ElemSize = DL_MCAN_ELEM_SIZE_64BYTES, +}; + + + +static const DL_MCAN_BitTimingParams gMCAN_0BitTimes = { + /* Arbitration Baud Rate Pre-scaler. */ + .nomRatePrescalar = 3, + /* Arbitration Time segment before sample point. */ + .nomTimeSeg1 = 33, + /* Arbitration Time segment after sample point. */ + .nomTimeSeg2 = 4, + /* Arbitration (Re)Synchronization Jump Width Range. */ + .nomSynchJumpWidth = 4, + /* Data Baud Rate Pre-scaler. */ + .dataRatePrescalar = 3, + /* Data Time segment before sample point. */ + .dataTimeSeg1 = 16, + /* Data Time segment after sample point. */ + .dataTimeSeg2 = 1, + /* Data (Re)Synchronization Jump Width. */ + .dataSynchJumpWidth = 1, +}; + + +SYSCONFIG_WEAK void SYSCFG_DL_MCAN_0_init(void) { + DL_MCAN_RevisionId revid_MCAN_0; + + DL_MCAN_enableModuleClock(MCAN_0_INST); + + DL_MCAN_setClockConfig(MCAN_0_INST, (DL_MCAN_ClockConfig *) &gMCAN_0ClockConf); + + /* Get MCANSS Revision ID. */ + DL_MCAN_getRevisionId(MCAN_0_INST, &revid_MCAN_0); + + /* Wait for Memory initialization to be completed. */ + while(false == DL_MCAN_isMemInitDone(MCAN_0_INST)); + + /* Put MCAN in SW initialization mode. */ + + DL_MCAN_setOpMode(MCAN_0_INST, DL_MCAN_OPERATION_MODE_SW_INIT); + + /* Wait till MCAN is not initialized. */ + while (DL_MCAN_OPERATION_MODE_SW_INIT != DL_MCAN_getOpMode(MCAN_0_INST)); + + /* Initialize MCAN module. */ + DL_MCAN_init(MCAN_0_INST, (DL_MCAN_InitParams *) &gMCAN_0InitParams); + + + /* Configure Bit timings. */ + DL_MCAN_setBitTime(MCAN_0_INST, (DL_MCAN_BitTimingParams*) &gMCAN_0BitTimes); + + /* Configure Message RAM Sections */ + DL_MCAN_msgRAMConfig(MCAN_0_INST, (DL_MCAN_MsgRAMConfigParams*) &gMCAN_0MsgRAMConfigParams); + + + + /* Set Extended ID Mask. */ + DL_MCAN_setExtIDAndMask(MCAN_0_INST, MCAN_0_INST_MCAN_EXT_ID_AND_MASK ); + + /* Loopback mode */ + + /* Take MCAN out of the SW initialization mode */ + DL_MCAN_setOpMode(MCAN_0_INST, DL_MCAN_OPERATION_MODE_NORMAL); + + while (DL_MCAN_OPERATION_MODE_NORMAL != DL_MCAN_getOpMode(MCAN_0_INST)); + + +} + diff --git a/tests/data/mspm0/g3507_ti_msp_dl_config.h b/tests/data/mspm0/g3507_ti_msp_dl_config.h new file mode 100644 index 0000000..2cc1696 --- /dev/null +++ b/tests/data/mspm0/g3507_ti_msp_dl_config.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated - http://www.ti.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.h ============= + * Configured MSPM0 DriverLib module declarations + * + * DO NOT EDIT - This file is generated for the MSPM0G350X + * by the SysConfig tool. + */ +#ifndef ti_msp_dl_config_h +#define ti_msp_dl_config_h + +#define CONFIG_MSPM0G350X +#define CONFIG_MSPM0G3507 + +#if defined(__ti_version__) || defined(__TI_COMPILER_VERSION__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#elif defined(__IAR_SYSTEMS_ICC__) +#define SYSCONFIG_WEAK __weak +#elif defined(__GNUC__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#endif + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ======== SYSCFG_DL_init ======== + * Perform all required MSP DL initialization + * + * This function should be called once at a point before any use of + * MSP DL. + */ + + +/* clang-format off */ + +#define POWER_STARTUP_DELAY (16) + + +#define GPIO_HFXT_PORT GPIOA +#define GPIO_HFXIN_PIN DL_GPIO_PIN_5 +#define GPIO_HFXIN_IOMUX (IOMUX_PINCM10) +#define GPIO_HFXOUT_PIN DL_GPIO_PIN_6 +#define GPIO_HFXOUT_IOMUX (IOMUX_PINCM11) +#define GPIO_LFXT_PORT GPIOA +#define GPIO_LFXIN_PIN DL_GPIO_PIN_3 +#define GPIO_LFXIN_IOMUX (IOMUX_PINCM8) +#define GPIO_LFXOUT_PIN DL_GPIO_PIN_4 +#define GPIO_LFXOUT_IOMUX (IOMUX_PINCM9) +#define CPUCLK_FREQ 80000000 +/* Defines for SYSPLL_ERR_01 Workaround */ +/* Represent 1.000 as 1000 */ +#define FLOAT_TO_INT_SCALE (1000U) +#define FCC_EXPECTED_RATIO 5000 +#define FCC_UPPER_BOUND (FCC_EXPECTED_RATIO * (1 + 0.003)) +#define FCC_LOWER_BOUND (FCC_EXPECTED_RATIO * (1 - 0.003)) + +bool SYSCFG_DL_SYSCTL_SYSPLL_init(void); + + +/* Defines for PWM_TIMA1 */ +#define PWM_TIMA1_INST TIMA1 +#define PWM_TIMA1_INST_IRQHandler TIMA1_IRQHandler +#define PWM_TIMA1_INST_INT_IRQN (TIMA1_INT_IRQn) +#define PWM_TIMA1_INST_CLK_FREQ 80000000 +/* GPIO defines for channel 0 */ +#define GPIO_PWM_TIMA1_C0_PORT GPIOA +#define GPIO_PWM_TIMA1_C0_PIN DL_GPIO_PIN_28 +#define GPIO_PWM_TIMA1_C0_IOMUX (IOMUX_PINCM3) +#define GPIO_PWM_TIMA1_C0_IOMUX_FUNC IOMUX_PINCM3_PF_TIMA1_CCP0 +#define GPIO_PWM_TIMA1_C0_IDX DL_TIMER_CC_0_INDEX +/* GPIO defines for channel 1 */ +#define GPIO_PWM_TIMA1_C1_PORT GPIOA +#define GPIO_PWM_TIMA1_C1_PIN DL_GPIO_PIN_31 +#define GPIO_PWM_TIMA1_C1_IOMUX (IOMUX_PINCM6) +#define GPIO_PWM_TIMA1_C1_IOMUX_FUNC IOMUX_PINCM6_PF_TIMA1_CCP1 +#define GPIO_PWM_TIMA1_C1_IDX DL_TIMER_CC_1_INDEX + + + + +/* Defines for QEI_TIMG8 */ +#define QEI_TIMG8_INST TIMG8 +#define QEI_TIMG8_INST_IRQHandler TIMG8_IRQHandler +#define QEI_TIMG8_INST_INT_IRQN (TIMG8_INT_IRQn) +/* Pin configuration defines for QEI_TIMG8 PHA Pin */ +#define GPIO_QEI_TIMG8_PHA_PORT GPIOA +#define GPIO_QEI_TIMG8_PHA_PIN DL_GPIO_PIN_26 +#define GPIO_QEI_TIMG8_PHA_IOMUX (IOMUX_PINCM59) +#define GPIO_QEI_TIMG8_PHA_IOMUX_FUNC IOMUX_PINCM59_PF_TIMG8_CCP0 +/* Pin configuration defines for QEI_TIMG8 PHB Pin */ +#define GPIO_QEI_TIMG8_PHB_PORT GPIOA +#define GPIO_QEI_TIMG8_PHB_PIN DL_GPIO_PIN_22 +#define GPIO_QEI_TIMG8_PHB_IOMUX (IOMUX_PINCM47) +#define GPIO_QEI_TIMG8_PHB_IOMUX_FUNC IOMUX_PINCM47_PF_TIMG8_CCP1 +/* Pin configuration defines for QEI_TIMG8 IDX Pin */ +#define GPIO_QEI_TIMG8_IDX_PORT GPIOB +#define GPIO_QEI_TIMG8_IDX_PIN DL_GPIO_PIN_14 +#define GPIO_QEI_TIMG8_IDX_IOMUX (IOMUX_PINCM31) +#define GPIO_QEI_TIMG8_IDX_IOMUX_FUNC IOMUX_PINCM31_PF_TIMG8_IDX + + + +/* Defines for I2C_0 */ +#define I2C_0_INST I2C0 +#define I2C_0_INST_IRQHandler I2C0_IRQHandler +#define I2C_0_INST_INT_IRQN I2C0_INT_IRQn +#define I2C_0_BUS_SPEED_HZ 100000 +#define GPIO_I2C_0_SDA_PORT GPIOA +#define GPIO_I2C_0_SDA_PIN DL_GPIO_PIN_0 +#define GPIO_I2C_0_IOMUX_SDA (IOMUX_PINCM1) +#define GPIO_I2C_0_IOMUX_SDA_FUNC IOMUX_PINCM1_PF_I2C0_SDA +#define GPIO_I2C_0_SCL_PORT GPIOA +#define GPIO_I2C_0_SCL_PIN DL_GPIO_PIN_1 +#define GPIO_I2C_0_IOMUX_SCL (IOMUX_PINCM2) +#define GPIO_I2C_0_IOMUX_SCL_FUNC IOMUX_PINCM2_PF_I2C0_SCL + +/* Defines for I2C_1 */ +#define I2C_1_INST I2C1 +#define I2C_1_INST_IRQHandler I2C1_IRQHandler +#define I2C_1_INST_INT_IRQN I2C1_INT_IRQn +#define I2C_1_BUS_SPEED_HZ 100000 +#define GPIO_I2C_1_SDA_PORT GPIOB +#define GPIO_I2C_1_SDA_PIN DL_GPIO_PIN_3 +#define GPIO_I2C_1_IOMUX_SDA (IOMUX_PINCM16) +#define GPIO_I2C_1_IOMUX_SDA_FUNC IOMUX_PINCM16_PF_I2C1_SDA +#define GPIO_I2C_1_SCL_PORT GPIOB +#define GPIO_I2C_1_SCL_PIN DL_GPIO_PIN_2 +#define GPIO_I2C_1_IOMUX_SCL (IOMUX_PINCM15) +#define GPIO_I2C_1_IOMUX_SCL_FUNC IOMUX_PINCM15_PF_I2C1_SCL + + +/* Defines for UART_0 */ +#define UART_0_INST UART0 +#define UART_0_INST_FREQUENCY 40000000 +#define UART_0_INST_IRQHandler UART0_IRQHandler +#define UART_0_INST_INT_IRQN UART0_INT_IRQn +#define GPIO_UART_0_RX_PORT GPIOA +#define GPIO_UART_0_TX_PORT GPIOA +#define GPIO_UART_0_RX_PIN DL_GPIO_PIN_11 +#define GPIO_UART_0_TX_PIN DL_GPIO_PIN_10 +#define GPIO_UART_0_IOMUX_RX (IOMUX_PINCM22) +#define GPIO_UART_0_IOMUX_TX (IOMUX_PINCM21) +#define GPIO_UART_0_IOMUX_RX_FUNC IOMUX_PINCM22_PF_UART0_RX +#define GPIO_UART_0_IOMUX_TX_FUNC IOMUX_PINCM21_PF_UART0_TX +#define UART_0_BAUD_RATE (115200) +#define UART_0_IBRD_40_MHZ_115200_BAUD (21) +#define UART_0_FBRD_40_MHZ_115200_BAUD (45) +/* Defines for UART_1 */ +#define UART_1_INST UART1 +#define UART_1_INST_FREQUENCY 40000000 +#define UART_1_INST_IRQHandler UART1_IRQHandler +#define UART_1_INST_INT_IRQN UART1_INT_IRQn +#define GPIO_UART_1_RX_PORT GPIOA +#define GPIO_UART_1_TX_PORT GPIOA +#define GPIO_UART_1_RX_PIN DL_GPIO_PIN_9 +#define GPIO_UART_1_TX_PIN DL_GPIO_PIN_8 +#define GPIO_UART_1_IOMUX_RX (IOMUX_PINCM20) +#define GPIO_UART_1_IOMUX_TX (IOMUX_PINCM19) +#define GPIO_UART_1_IOMUX_RX_FUNC IOMUX_PINCM20_PF_UART1_RX +#define GPIO_UART_1_IOMUX_TX_FUNC IOMUX_PINCM19_PF_UART1_TX +#define UART_1_BAUD_RATE (115200) +#define UART_1_IBRD_40_MHZ_115200_BAUD (21) +#define UART_1_FBRD_40_MHZ_115200_BAUD (45) + + + + +/* Defines for SPI_1 */ +#define SPI_1_INST SPI1 +#define SPI_1_INST_IRQHandler SPI1_IRQHandler +#define SPI_1_INST_INT_IRQN SPI1_INT_IRQn +#define GPIO_SPI_1_PICO_PORT GPIOB +#define GPIO_SPI_1_PICO_PIN DL_GPIO_PIN_15 +#define GPIO_SPI_1_IOMUX_PICO (IOMUX_PINCM32) +#define GPIO_SPI_1_IOMUX_PICO_FUNC IOMUX_PINCM32_PF_SPI1_PICO +#define GPIO_SPI_1_POCI_PORT GPIOB +#define GPIO_SPI_1_POCI_PIN DL_GPIO_PIN_7 +#define GPIO_SPI_1_IOMUX_POCI (IOMUX_PINCM24) +#define GPIO_SPI_1_IOMUX_POCI_FUNC IOMUX_PINCM24_PF_SPI1_POCI +/* GPIO configuration for SPI_1 */ +#define GPIO_SPI_1_SCLK_PORT GPIOB +#define GPIO_SPI_1_SCLK_PIN DL_GPIO_PIN_9 +#define GPIO_SPI_1_IOMUX_SCLK (IOMUX_PINCM26) +#define GPIO_SPI_1_IOMUX_SCLK_FUNC IOMUX_PINCM26_PF_SPI1_SCLK +#define GPIO_SPI_1_CS0_PORT GPIOB +#define GPIO_SPI_1_CS0_PIN DL_GPIO_PIN_6 +#define GPIO_SPI_1_IOMUX_CS0 (IOMUX_PINCM23) +#define GPIO_SPI_1_IOMUX_CS0_FUNC IOMUX_PINCM23_PF_SPI1_CS0 + + + +/* Defines for ADC12_0 */ +#define ADC12_0_INST ADC0 +#define ADC12_0_INST_IRQHandler ADC0_IRQHandler +#define ADC12_0_INST_INT_IRQN (ADC0_INT_IRQn) +#define ADC12_0_ADCMEM_0 DL_ADC12_MEM_IDX_0 +#define ADC12_0_ADCMEM_0_REF DL_ADC12_REFERENCE_VOLTAGE_VDDA +#define ADC12_0_ADCMEM_0_REF_VOLTAGE_V 3.3 +#define GPIO_ADC12_0_C3_PORT GPIOA +#define GPIO_ADC12_0_C3_PIN DL_GPIO_PIN_24 +#define GPIO_ADC12_0_IOMUX_C3 (IOMUX_PINCM54) +#define GPIO_ADC12_0_IOMUX_C3_FUNC (IOMUX_PINCM54_PF_UNCONNECTED) + +/* Defines for ADC12_1 */ +#define ADC12_1_INST ADC1 +#define ADC12_1_INST_IRQHandler ADC1_IRQHandler +#define ADC12_1_INST_INT_IRQN (ADC1_INT_IRQn) +#define ADC12_1_ADCMEM_0 DL_ADC12_MEM_IDX_0 +#define ADC12_1_ADCMEM_0_REF DL_ADC12_REFERENCE_VOLTAGE_VDDA +#define ADC12_1_ADCMEM_0_REF_VOLTAGE_V 3.3 +#define GPIO_ADC12_1_C5_PORT GPIOB +#define GPIO_ADC12_1_C5_PIN DL_GPIO_PIN_18 +#define GPIO_ADC12_1_IOMUX_C5 (IOMUX_PINCM44) +#define GPIO_ADC12_1_IOMUX_C5_FUNC (IOMUX_PINCM44_PF_UNCONNECTED) + + + +/* Defines for DMA_CH_SPI1_RX */ +#define DMA_CH_SPI1_RX_CHAN_ID (6) +#define SPI_1_INST_DMA_TRIGGER_0 (DMA_SPI1_RX_TRIG) +/* Defines for DMA_CH_SPI1_TX */ +#define DMA_CH_SPI1_TX_CHAN_ID (2) +#define SPI_1_INST_DMA_TRIGGER_1 (DMA_SPI1_TX_TRIG) +/* Defines for DMA_CH_UART0_TX */ +#define DMA_CH_UART0_TX_CHAN_ID (3) +#define UART_0_INST_DMA_TRIGGER (DMA_UART0_TX_TRIG) +/* Defines for DMA_CH_UART1_TX */ +#define DMA_CH_UART1_TX_CHAN_ID (4) +#define UART_1_INST_DMA_TRIGGER (DMA_UART1_TX_TRIG) + + +/* Defines for PIN_LED1: GPIOB.8 with pinCMx 25 on package pin 22 */ +#define GPIO_LEDS_PIN_LED1_PORT (GPIOB) +#define GPIO_LEDS_PIN_LED1_PIN (DL_GPIO_PIN_8) +#define GPIO_LEDS_PIN_LED1_IOMUX (IOMUX_PINCM25) +/* Defines for PIN_LED2: GPIOA.16 with pinCMx 38 on package pin 31 */ +#define GPIO_LEDS_PIN_LED2_PORT (GPIOA) +#define GPIO_LEDS_PIN_LED2_PIN (DL_GPIO_PIN_16) +#define GPIO_LEDS_PIN_LED2_IOMUX (IOMUX_PINCM38) +/* Port definition for Pin Group GPIO_KEYS */ +#define GPIO_KEYS_PORT (GPIOB) + +/* Defines for PIN_KEY1: GPIOB.24 with pinCMx 52 on package pin 42 */ +// pins affected by this interrupt request:["PIN_KEY1","PIN_KEY2"] +#define GPIO_KEYS_INT_IRQN (GPIOB_INT_IRQn) +#define GPIO_KEYS_INT_IIDX (DL_INTERRUPT_GROUP1_IIDX_GPIOB) +#define GPIO_KEYS_PIN_KEY1_IIDX (DL_GPIO_IIDX_DIO24) +#define GPIO_KEYS_PIN_KEY1_PIN (DL_GPIO_PIN_24) +#define GPIO_KEYS_PIN_KEY1_IOMUX (IOMUX_PINCM52) +/* Defines for PIN_KEY2: GPIOB.20 with pinCMx 48 on package pin 41 */ +#define GPIO_KEYS_PIN_KEY2_IIDX (DL_GPIO_IIDX_DIO20) +#define GPIO_KEYS_PIN_KEY2_PIN (DL_GPIO_PIN_20) +#define GPIO_KEYS_PIN_KEY2_IOMUX (IOMUX_PINCM48) + + + + + +/* Defines for DAC12 */ +#define DAC12_IRQHandler DAC0_IRQHandler +#define DAC12_INT_IRQN (DAC0_INT_IRQn) +#define GPIO_DAC12_OUT_PORT GPIOA +#define GPIO_DAC12_OUT_PIN DL_GPIO_PIN_15 +#define GPIO_DAC12_IOMUX_OUT (IOMUX_PINCM37) +#define GPIO_DAC12_IOMUX_OUT_FUNC IOMUX_PINCM37_PF_UNCONNECTED + + +/* Defines for MCAN_0 */ +#define MCAN_0_INST CANFD0 +#define GPIO_MCAN_0_CAN_TX_PORT GPIOA +#define GPIO_MCAN_0_CAN_TX_PIN DL_GPIO_PIN_12 +#define GPIO_MCAN_0_IOMUX_CAN_TX (IOMUX_PINCM34) +#define GPIO_MCAN_0_IOMUX_CAN_TX_FUNC IOMUX_PINCM34_PF_CANFD0_CANTX +#define GPIO_MCAN_0_CAN_RX_PORT GPIOA +#define GPIO_MCAN_0_CAN_RX_PIN DL_GPIO_PIN_13 +#define GPIO_MCAN_0_IOMUX_CAN_RX (IOMUX_PINCM35) +#define GPIO_MCAN_0_IOMUX_CAN_RX_FUNC IOMUX_PINCM35_PF_CANFD0_CANRX + + +/* Defines for MCAN_0 MCAN RAM configuration */ +#define MCAN_0_INST_MCAN_STD_ID_FILT_START_ADDR (0) +#define MCAN_0_INST_MCAN_STD_ID_FILTER_NUM (1) +#define MCAN_0_INST_MCAN_EXT_ID_FILT_START_ADDR (48) +#define MCAN_0_INST_MCAN_EXT_ID_FILTER_NUM (1) +#define MCAN_0_INST_MCAN_TX_BUFF_START_ADDR (148) +#define MCAN_0_INST_MCAN_TX_BUFF_SIZE (2) +#define MCAN_0_INST_MCAN_FIFO_1_START_ADDR (192) +#define MCAN_0_INST_MCAN_FIFO_1_NUM (2) +#define MCAN_0_INST_MCAN_TX_EVENT_START_ADDR (164) +#define MCAN_0_INST_MCAN_TX_EVENT_SIZE (2) +#define MCAN_0_INST_MCAN_EXT_ID_AND_MASK (0x1FFFFFFFU) +#define MCAN_0_INST_MCAN_RX_BUFF_START_ADDR (208) +#define MCAN_0_INST_MCAN_FIFO_0_START_ADDR (172) +#define MCAN_0_INST_MCAN_FIFO_0_NUM (3) + + + + + +/* clang-format on */ + +void SYSCFG_DL_init(void); +void SYSCFG_DL_initPower(void); +void SYSCFG_DL_GPIO_init(void); +void SYSCFG_DL_SYSCTL_init(void); + +bool SYSCFG_DL_SYSCTL_SYSPLL_init(void); +void SYSCFG_DL_PWM_TIMA1_init(void); +void SYSCFG_DL_QEI_TIMG8_init(void); +void SYSCFG_DL_I2C_0_init(void); +void SYSCFG_DL_I2C_1_init(void); +void SYSCFG_DL_UART_0_init(void); +void SYSCFG_DL_UART_1_init(void); +void SYSCFG_DL_SPI_1_init(void); +void SYSCFG_DL_ADC12_0_init(void); +void SYSCFG_DL_ADC12_1_init(void); +void SYSCFG_DL_DMA_init(void); + +void SYSCFG_DL_SYSTICK_init(void); +void SYSCFG_DL_DAC12_init(void); +void SYSCFG_DL_MCAN_0_init(void); + +bool SYSCFG_DL_saveConfiguration(void); +bool SYSCFG_DL_restoreConfiguration(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ti_msp_dl_config_h */ diff --git a/tests/data/mspm0/g3519.syscfg b/tests/data/mspm0/g3519.syscfg new file mode 100644 index 0000000..a5183c0 --- /dev/null +++ b/tests/data/mspm0/g3519.syscfg @@ -0,0 +1,248 @@ +/** + * These arguments were used when this file was generated. They will be automatically applied on subsequent loads + * via the GUI or CLI. Run CLI with '--help' for additional information on how to override these arguments. + * @cliArgs --device "MSPM0G351X" --part "Default" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @v2CliArgs --device "MSPM0G3519" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @versions {"tool":"1.28.1+4785"} + */ + +/** + * Import the modules used in this configuration. + */ +const ADC12 = scripting.addModule("/ti/driverlib/ADC12", {}, false); +const ADC121 = ADC12.addInstance(); +const ADC122 = ADC12.addInstance(); +const DAC12 = scripting.addModule("/ti/driverlib/DAC12"); +const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); +const GPIO1 = GPIO.addInstance(); +const GPIO2 = GPIO.addInstance(); +const I2C = scripting.addModule("/ti/driverlib/I2C", {}, false); +const I2C1 = I2C.addInstance(); +const I2C2 = I2C.addInstance(); +const MCAN = scripting.addModule("/ti/driverlib/MCAN", {}, false); +const MCAN1 = MCAN.addInstance(); +const PWM = scripting.addModule("/ti/driverlib/PWM", {}, false); +const PWM1 = PWM.addInstance(); +const QEI = scripting.addModule("/ti/driverlib/QEI", {}, false); +const QEI1 = QEI.addInstance(); +const SPI = scripting.addModule("/ti/driverlib/SPI", {}, false); +const SPI1 = SPI.addInstance(); +const SYSCTL = scripting.addModule("/ti/driverlib/SYSCTL"); +const SYSTICK = scripting.addModule("/ti/driverlib/SYSTICK"); +const UART = scripting.addModule("/ti/driverlib/UART", {}, false); +const UART1 = UART.addInstance(); +const UART2 = UART.addInstance(); +const UART3 = UART.addInstance(); + +/** + * Write custom configuration values to the imported modules. + */ +const divider1 = system.clockTree["PLL_CLK2X_DIV"]; +divider1.divideValue = 4; + +const divider2 = system.clockTree["PLL_CLK1_DIV"]; +divider2.divideValue = 2; + +const divider3 = system.clockTree["PLL_PDIV"]; +divider3.divideValue = 2; + +const divider4 = system.clockTree["UDIV"]; +divider4.divideValue = 2; + +const gate1 = system.clockTree["MFPCLKGATE"]; +gate1.enable = true; + +const multiplier1 = system.clockTree["PLL_QDIV"]; +multiplier1.multiplyValue = 20; + +const mux0 = system.clockTree["CANCLKMUX"]; +mux0.inputSelect = "CANCLKMUX_PLLCLK1_OUT"; + +const mux1 = system.clockTree["EXHFMUX"]; +mux1.inputSelect = "EXHFMUX_XTAL"; + +const mux2 = system.clockTree["SYSPLLMUX"]; +mux2.inputSelect = "zSYSPLLMUX_HFCLK"; + +const mux3 = system.clockTree["HSCLKMUX"]; +mux3.inputSelect = "HSCLKMUX_SYSPLL2X"; + +const mux4 = system.clockTree["EXLFMUX"]; +mux4.inputSelect = "EXLFMUX_XTAL"; + +const mux5 = system.clockTree["LFXTMUX"]; +mux5.inputSelect = "LFXTMUX_TRUE"; + +const pinFunction1 = system.clockTree["HFXT"]; +pinFunction1.enable = true; +pinFunction1.inputFreq = 16; +pinFunction1.HFXTStartup = 255; +pinFunction1.HFCLKMonitor = true; +pinFunction1.peripheral.hfxInPin.$assign = "PA5"; +pinFunction1.peripheral.hfxOutPin.$assign = "PA6"; + +const pinFunction2 = system.clockTree["LFXT"]; +pinFunction2.enable = true; +pinFunction2.inputFreq = 32.768; +pinFunction2.peripheral.lfxInPin.$assign = "PA3"; +pinFunction2.peripheral.lfxOutPin.$assign = "PA4"; + +ADC121.$name = "ADC12_0"; +ADC121.adcMem0chansel = "DL_ADC12_INPUT_CHAN_3"; +ADC121.peripheral.$assign = "ADC0"; +ADC121.peripheral.adcPin3.$assign = "PA24"; + +ADC122.$name = "ADC12_1"; +ADC122.adcMem0chansel = "DL_ADC12_INPUT_CHAN_6"; +ADC122.peripheral.$assign = "ADC1"; +ADC122.peripheral.adcPin6.$assign = "PB19"; + +DAC12.dacOutputPinEn = true; +DAC12.dacAmplifier = "ON"; +DAC12.peripheral.$assign = "DAC0"; +DAC12.peripheral.OutPin.$assign = "PA15"; + +GPIO1.$name = "GPIO_LEDS"; +GPIO1.associatedPins.create(2); +GPIO1.associatedPins[0].$name = "PIN_LED1"; +GPIO1.associatedPins[0].initialValue = "SET"; +GPIO1.associatedPins[0].pin.$assign = "PA25"; +GPIO1.associatedPins[1].$name = "PIN_LED2"; +GPIO1.associatedPins[1].initialValue = "SET"; +GPIO1.associatedPins[1].pin.$assign = "PA16"; + +GPIO2.$name = "GPIO_KEYS"; +GPIO2.associatedPins.create(2); +GPIO2.associatedPins[0].$name = "PIN_KEY1"; +GPIO2.associatedPins[0].direction = "INPUT"; +GPIO2.associatedPins[0].internalResistor = "NONE"; +GPIO2.associatedPins[0].interruptEn = true; +GPIO2.associatedPins[0].polarity = "FALL"; +GPIO2.associatedPins[0].pin.$assign = "PB24"; +GPIO2.associatedPins[1].$name = "PIN_KEY2"; +GPIO2.associatedPins[1].direction = "INPUT"; +GPIO2.associatedPins[1].internalResistor = "NONE"; +GPIO2.associatedPins[1].interruptEn = true; +GPIO2.associatedPins[1].polarity = "FALL"; +GPIO2.associatedPins[1].pin.$assign = "PB20"; + +I2C1.$name = "I2C_0"; +I2C1.basicEnableController = true; +I2C1.basicControllerBusSpeed = 100000; +I2C1.peripheral.$assign = "I2C0"; +I2C1.peripheral.sdaPin.$assign = "PA0"; +I2C1.peripheral.sclPin.$assign = "PA1"; + +I2C2.$name = "I2C_1"; +I2C2.basicEnableController = true; +I2C2.basicControllerBusSpeed = 100000; +I2C2.peripheral.$assign = "I2C1"; +I2C2.peripheral.sdaPin.$assign = "PB3"; +I2C2.peripheral.sclPin.$assign = "PB2"; + +MCAN1.$name = "MCAN_0"; +MCAN1.peripheral.$assign = "CANFD0"; +MCAN1.peripheral.rxPin.$assign = "PA13"; +MCAN1.peripheral.txPin.$assign = "PA12"; + +PWM1.$name = "PWM_TIMA1"; +PWM1.peripheral.$assign = "TIMA1"; +PWM1.peripheral.ccp0Pin.$assign = "PA28"; +PWM1.peripheral.ccp1Pin.$assign = "PA31"; + +QEI1.$name = "QEI_TIMG8"; +QEI1.enableIndexInput = true; +QEI1.peripheral.$assign = "TIMG8"; +QEI1.peripheral.ccp0Pin.$assign = "PA26"; +QEI1.peripheral.ccp1Pin.$assign = "PA22"; +QEI1.peripheral.idxPin.$assign = "PB14"; + +const Board = scripting.addModule("/ti/driverlib/Board", {}, false); +Board.peripheral.$assign = "DEBUGSS"; +Board.peripheral.swclkPin.$assign = "PA20"; +Board.peripheral.swdioPin.$assign = "PA19"; + +SPI1.$name = "SPI_1"; +SPI1.chipSelect = ["0"]; +SPI1.rxFifoThreshold = "DL_SPI_RX_FIFO_LEVEL_ONE_FRAME"; +SPI1.txFifoThreshold = "DL_SPI_TX_FIFO_LEVEL_ONE_FRAME"; +SPI1.enabledInterrupts = ["DMA_DONE_RX","DMA_DONE_TX"]; +SPI1.enabledDMAEvent1Triggers = "DL_SPI_DMA_INTERRUPT_RX"; +SPI1.enabledDMAEvent2Triggers = "DL_SPI_DMA_INTERRUPT_TX"; +SPI1.peripheral.$assign = "SPI1"; +SPI1.peripheral.sclkPin.$assign = "PB9"; +SPI1.peripheral.mosiPin.$assign = "PB15"; +SPI1.peripheral.misoPin.$assign = "PB7"; +SPI1.peripheral.cs0Pin.$assign = "PB6"; +SPI1.cs0PinConfig.enableConfig = true; +SPI1.cs0PinConfig.internalResistor = "PULL_UP"; +SPI1.DMA_CHANNEL_EVENT1.$name = "DMA_CH_SPI1_RX"; +SPI1.DMA_CHANNEL_EVENT1.addressMode = "f2b"; +SPI1.DMA_CHANNEL_EVENT1.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT1.dstLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT1.peripheral.$assign = "DMA_CH6"; +SPI1.DMA_CHANNEL_EVENT2.$name = "DMA_CH_SPI1_TX"; +SPI1.DMA_CHANNEL_EVENT2.addressMode = "b2f"; +SPI1.DMA_CHANNEL_EVENT2.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.dstLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.peripheral.$assign = "DMA_CH2"; + +SYSCTL.forceDefaultClkConfig = true; +SYSCTL.clockTreeEn = true; + +SYSTICK.periodEnable = true; +SYSTICK.period = 80000; +SYSTICK.interruptEnable = true; +SYSTICK.systickEnable = true; + +UART1.$name = "UART_0"; +UART1.targetBaudRate = 115200; +UART1.enableFIFO = true; +UART1.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY"; +UART1.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART1.enableDMATX = true; +UART1.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART1.peripheral.$assign = "UART0"; +UART1.peripheral.rxPin.$assign = "PA11"; +UART1.peripheral.txPin.$assign = "PA10"; +UART1.DMA_CHANNEL_TX.$name = "DMA_CH_UART0_TX"; +UART1.DMA_CHANNEL_TX.addressMode = "b2f"; +UART1.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART1.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART1.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART1.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH3"; + +UART2.$name = "UART_1"; +UART2.targetBaudRate = 115200; +UART2.enableFIFO = true; +UART2.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY"; +UART2.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART2.enableDMATX = true; +UART2.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART2.peripheral.$assign = "UART1"; +UART2.peripheral.rxPin.$assign = "PA9"; +UART2.peripheral.txPin.$assign = "PA8"; +UART2.DMA_CHANNEL_TX.$name = "DMA_CH_UART1_TX"; +UART2.DMA_CHANNEL_TX.addressMode = "b2f"; +UART2.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART2.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART2.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART2.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH4"; + +UART3.$name = "UART_7"; +UART3.targetBaudRate = 115200; +UART3.enableFIFO = true; +UART3.rxFifoThreshold = "DL_UART_RX_FIFO_LEVEL_ONE_ENTRY"; +UART3.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART3.enableDMATX = true; +UART3.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART3.peripheral.$assign = "UART7"; +UART3.peripheral.rxPin.$assign = "PB18"; +UART3.peripheral.txPin.$assign = "PB17"; +UART3.DMA_CHANNEL_TX.$name = "DMA_CH_UART7_TX"; +UART3.DMA_CHANNEL_TX.addressMode = "b2f"; +UART3.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART3.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART3.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART3.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH7"; + diff --git a/tests/data/mspm0/g3519_expected_app_main.cpp b/tests/data/mspm0/g3519_expected_app_main.cpp new file mode 100644 index 0000000..7f54a69 --- /dev/null +++ b/tests/data/mspm0/g3519_expected_app_main.cpp @@ -0,0 +1,148 @@ +// Generated by `libxr gen`; do not edit by hand. +// Generated from mspm0g3519_minidb48.syscfg (MSPM0G3519); 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 */ +#include +#include + +// KEY interrupt counters, incremented in the GPIO interrupt callbacks. +static std::atomic key1_irq_count{0}; +static std::atomic key2_irq_count{0}; + +static void OnKeyInterrupt(bool in_isr, std::atomic* count) +{ + (void)in_isr; + count->fetch_add(1, std::memory_order_relaxed); +} +/* 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 +#define DMA_CH_UART1_TX_LIBXR_UART_IRQN UART_1_INST_INT_IRQN +#define DMA_CH_UART1_TX_LIBXR_UART_TX 1 +#define DMA_CH_UART7_TX_LIBXR_UART_IRQN UART_7_INST_INT_IRQN +#define DMA_CH_UART7_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(size_t) static uint8_t uart1_tx_buf[2 * 128]; +alignas(size_t) static uint8_t uart7_tx_buf[2 * 128]; +alignas(4) static uint8_t spi1_rx_buf[32]; +alignas(4) static uint8_t spi1_tx_buf[32]; +alignas(4) static uint8_t i2c0_buf[32]; +alignas(4) static uint8_t i2c1_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 (PA25), 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_PORT, GPIO_LEDS_PIN_LED1_PIN, GPIO_LEDS_PIN_LED1_IOMUX); + static MSPM0GPIO LED2(GPIO_LEDS_PORT, GPIO_LEDS_PIN_LED2_PIN, GPIO_LEDS_PIN_LED2_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)); + static MSPM0UART uart1(MSPM0_UART_MAIN_INIT(UART_1, DMA_CH_UART1_TX, uart1_tx_buf, + sizeof(uart1_tx_buf), 5, 128)); + static MSPM0UART uart7(MSPM0_UART_MAIN_INIT(UART_7, DMA_CH_UART7_TX, uart7_tx_buf, + sizeof(uart7_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}); + static MSPM0I2C i2c1(MSPM0_I2C_INIT(I2C_1, PD0_BUSCLK_FREQ, i2c1_buf, sizeof(i2c1_buf), + 8), {I2C_1_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(LED2, LibXR::GPIO); + XR_REGISTER(KEY1, LibXR::GPIO); + XR_REGISTER(KEY2, LibXR::GPIO); + + XR_REGISTER(pwm_tima1_c0, LibXR::PWM); + XR_REGISTER(pwm_tima1_c1, LibXR::PWM); + + XR_REGISTER(spi1, LibXR::SPI); + + XR_REGISTER(uart0, LibXR::UART); + XR_REGISTER(uart1, LibXR::UART); + XR_REGISTER(uart7, LibXR::UART); + + XR_REGISTER(i2c0, LibXR::I2C); + XR_REGISTER(i2c1, LibXR::I2C); + + XR_REGISTER(ramfs, LibXR::RamFS); + + XR_REGISTER(terminal, LibXR::Terminal<32, 32, 5, 5>); + + /* User Code Begin 3 */ + // The keys pull the pin low against an external 15k pull-up: falling edge, no internal + // pull. The interrupt registration belongs to the consumer (O1: the generator creates + // the GPIO object only). + KEY1.SetConfig({GPIO::Direction::FALL_INTERRUPT, GPIO::Pull::NONE}); + KEY1.RegisterCallback(GPIO::Callback::Create(OnKeyInterrupt, &key1_irq_count)); + KEY1.EnableInterrupt(); + KEY2.SetConfig({GPIO::Direction::FALL_INTERRUPT, GPIO::Pull::NONE}); + KEY2.RegisterCallback(GPIO::Callback::Create(OnKeyInterrupt, &key2_irq_count)); + KEY2.EnableInterrupt(); + /* User Code End 3 */ + XROBOT_MAIN(); +} diff --git a/tests/data/mspm0/g3519_ti_msp_dl_config.c b/tests/data/mspm0/g3519_ti_msp_dl_config.c new file mode 100644 index 0000000..1fba821 --- /dev/null +++ b/tests/data/mspm0/g3519_ti_msp_dl_config.c @@ -0,0 +1,924 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.c ============= + * Configured MSPM0 DriverLib module definitions + * + * DO NOT EDIT - This file is generated for the MSPM0G351X + * by the SysConfig tool. + */ + +#include "ti_msp_dl_config.h" + +DL_TimerA_backupConfig gPWM_TIMA1Backup; +DL_TimerG_backupConfig gQEI_TIMG8Backup; +DL_SPI_backupConfig gSPI_1Backup; +DL_MCAN_backupConfig gMCAN_0Backup; + +/* + * ======== SYSCFG_DL_init ======== + * Perform any initialization needed before using any board APIs + */ +SYSCONFIG_WEAK void SYSCFG_DL_init(void) +{ + SYSCFG_DL_initPower(); + SYSCFG_DL_GPIO_init(); + /* Module-Specific Initializations*/ + SYSCFG_DL_SYSCTL_init(); + SYSCFG_DL_PWM_TIMA1_init(); + SYSCFG_DL_QEI_TIMG8_init(); + SYSCFG_DL_I2C_0_init(); + SYSCFG_DL_I2C_1_init(); + SYSCFG_DL_UART_0_init(); + SYSCFG_DL_UART_1_init(); + SYSCFG_DL_UART_7_init(); + SYSCFG_DL_SPI_1_init(); + SYSCFG_DL_ADC12_0_init(); + SYSCFG_DL_ADC12_1_init(); + SYSCFG_DL_DMA_init(); + SYSCFG_DL_SYSTICK_init(); + SYSCFG_DL_DAC12_init(); + SYSCFG_DL_MCAN_0_init(); + /* Ensure backup structures have no valid state */ + gPWM_TIMA1Backup.backupRdy = false; + gQEI_TIMG8Backup.backupRdy = false; + + gSPI_1Backup.backupRdy = false; + gMCAN_0Backup.backupRdy = false; + +} +/* + * User should take care to save and restore register configuration in application. + * See Retention Configuration section for more details. + */ +SYSCONFIG_WEAK bool SYSCFG_DL_saveConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_TimerA_saveConfiguration(PWM_TIMA1_INST, &gPWM_TIMA1Backup); + retStatus &= DL_TimerG_saveConfiguration(QEI_TIMG8_INST, &gQEI_TIMG8Backup); + retStatus &= DL_SPI_saveConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_MCAN_saveConfiguration(MCAN_0_INST, &gMCAN_0Backup); + + return retStatus; +} + + +SYSCONFIG_WEAK bool SYSCFG_DL_restoreConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_TimerA_restoreConfiguration(PWM_TIMA1_INST, &gPWM_TIMA1Backup, false); + retStatus &= DL_TimerG_restoreConfiguration(QEI_TIMG8_INST, &gQEI_TIMG8Backup, false); + retStatus &= DL_SPI_restoreConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_MCAN_restoreConfiguration(MCAN_0_INST, &gMCAN_0Backup); + + return retStatus; +} + +SYSCONFIG_WEAK void SYSCFG_DL_initPower(void) +{ + DL_GPIO_reset(GPIOA); + DL_GPIO_reset(GPIOB); + DL_TimerA_reset(PWM_TIMA1_INST); + DL_TimerG_reset(QEI_TIMG8_INST); + DL_I2C_reset(I2C_0_INST); + DL_I2C_reset(I2C_1_INST); + DL_UART_Main_reset(UART_0_INST); + DL_UART_Main_reset(UART_1_INST); + DL_UART_Main_reset(UART_7_INST); + DL_SPI_reset(SPI_1_INST); + DL_ADC12_reset(ADC12_0_INST); + DL_ADC12_reset(ADC12_1_INST); + + + DL_DAC12_reset(DAC0); + DL_MCAN_reset(MCAN_0_INST); + + DL_GPIO_enablePower(GPIOA); + DL_GPIO_enablePower(GPIOB); + DL_TimerA_enablePower(PWM_TIMA1_INST); + DL_TimerG_enablePower(QEI_TIMG8_INST); + DL_I2C_enablePower(I2C_0_INST); + DL_I2C_enablePower(I2C_1_INST); + DL_UART_Main_enablePower(UART_0_INST); + DL_UART_Main_enablePower(UART_1_INST); + DL_UART_Main_enablePower(UART_7_INST); + DL_SPI_enablePower(SPI_1_INST); + DL_ADC12_enablePower(ADC12_0_INST); + DL_ADC12_enablePower(ADC12_1_INST); + + + DL_DAC12_enablePower(DAC0); + DL_MCAN_enablePower(MCAN_0_INST); + delay_cycles(POWER_STARTUP_DELAY); +} + +SYSCONFIG_WEAK void SYSCFG_DL_GPIO_init(void) +{ + + DL_GPIO_initPeripheralAnalogFunction(GPIO_HFXIN_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_HFXOUT_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_LFXIN_IOMUX); + DL_GPIO_initPeripheralAnalogFunction(GPIO_LFXOUT_IOMUX); + + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_TIMA1_C0_IOMUX,GPIO_PWM_TIMA1_C0_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_TIMA1_C0_PORT, GPIO_PWM_TIMA1_C0_PIN); + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_TIMA1_C1_IOMUX,GPIO_PWM_TIMA1_C1_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_TIMA1_C1_PORT, GPIO_PWM_TIMA1_C1_PIN); + + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_PHA_IOMUX,GPIO_QEI_TIMG8_PHA_IOMUX_FUNC); + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_PHB_IOMUX,GPIO_QEI_TIMG8_PHB_IOMUX_FUNC); + DL_GPIO_initPeripheralInputFunction(GPIO_QEI_TIMG8_IDX_IOMUX,GPIO_QEI_TIMG8_IDX_IOMUX_FUNC); + + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SDA, + GPIO_I2C_0_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SCL, + GPIO_I2C_0_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SCL); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_1_IOMUX_SDA, + GPIO_I2C_1_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_1_IOMUX_SCL, + GPIO_I2C_1_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_1_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_1_IOMUX_SCL); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_0_IOMUX_TX, GPIO_UART_0_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_0_IOMUX_RX, GPIO_UART_0_IOMUX_RX_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_1_IOMUX_TX, GPIO_UART_1_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_1_IOMUX_RX, GPIO_UART_1_IOMUX_RX_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_7_IOMUX_TX, GPIO_UART_7_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_7_IOMUX_RX, GPIO_UART_7_IOMUX_RX_FUNC); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_SCLK, GPIO_SPI_1_IOMUX_SCLK_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_PICO, GPIO_SPI_1_IOMUX_PICO_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_1_IOMUX_POCI, GPIO_SPI_1_IOMUX_POCI_FUNC); + + DL_GPIO_initPeripheralOutputFunctionFeatures( + GPIO_SPI_1_IOMUX_CS0, GPIO_SPI_1_IOMUX_CS0_FUNC, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, + DL_GPIO_DRIVE_STRENGTH_LOW, DL_GPIO_HIZ_DISABLE); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_PIN_LED1_IOMUX); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_PIN_LED2_IOMUX); + + DL_GPIO_initDigitalInputFeatures(GPIO_KEYS_PIN_KEY1_IOMUX, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_NONE, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + + DL_GPIO_initDigitalInputFeatures(GPIO_KEYS_PIN_KEY2_IOMUX, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_NONE, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + + DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_PIN_LED1_PIN | + GPIO_LEDS_PIN_LED2_PIN); + DL_GPIO_enableOutput(GPIO_LEDS_PORT, GPIO_LEDS_PIN_LED1_PIN | + GPIO_LEDS_PIN_LED2_PIN); + DL_GPIO_setUpperPinsPolarity(GPIO_KEYS_PORT, DL_GPIO_PIN_24_EDGE_FALL | + DL_GPIO_PIN_20_EDGE_FALL); + DL_GPIO_clearInterruptStatus(GPIO_KEYS_PORT, GPIO_KEYS_PIN_KEY1_PIN | + GPIO_KEYS_PIN_KEY2_PIN); + DL_GPIO_enableInterrupt(GPIO_KEYS_PORT, GPIO_KEYS_PIN_KEY1_PIN | + GPIO_KEYS_PIN_KEY2_PIN); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_MCAN_0_IOMUX_CAN_TX, GPIO_MCAN_0_IOMUX_CAN_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_MCAN_0_IOMUX_CAN_RX, GPIO_MCAN_0_IOMUX_CAN_RX_FUNC); + +} + + +static const DL_SYSCTL_SYSPLLConfig gSYSPLLConfig = { + .inputFreq = DL_SYSCTL_SYSPLL_INPUT_FREQ_8_16_MHZ, + .rDivClk2x = 3, + .rDivClk1 = 0, + .rDivClk0 = 0, + .enableCLK2x = DL_SYSCTL_SYSPLL_CLK2X_ENABLE, + .enableCLK1 = DL_SYSCTL_SYSPLL_CLK1_ENABLE, + .enableCLK0 = DL_SYSCTL_SYSPLL_CLK0_DISABLE, + .sysPLLMCLK = DL_SYSCTL_SYSPLL_MCLK_CLK2X, + .sysPLLRef = DL_SYSCTL_SYSPLL_REF_HFCLK, + .qDiv = 19, + .pDiv = DL_SYSCTL_SYSPLL_PDIV_2 +}; + +SYSCONFIG_WEAK bool SYSCFG_DL_SYSCTL_SYSPLL_init(void) +{ + bool fFCCRatioStatus = false; + uint32_t fFCCSysoscCount; + uint32_t fFCCPllCount; + uint32_t fFCCRatio; + uint32_t fccTimeOutCounter; + + DL_SYSCTL_setFCCPeriods( DL_SYSCTL_FCC_TRIG_CNT_02 ); + + /* Measuring PLL. */ + DL_SYSCTL_configFCC(DL_SYSCTL_FCC_TRIG_TYPE_RISE_RISE, + DL_SYSCTL_FCC_TRIG_SOURCE_LFCLK, + DL_SYSCTL_FCC_CLOCK_SOURCE_SYSPLLCLK1); + /* Get SYSPLL frequency using FCC */ + fccTimeOutCounter = 0; + DL_SYSCTL_startFCC(); + while (DL_SYSCTL_isFCCDone() == 0) { + delay_cycles(977); /* 1x LFCLK cycle = 32MHz/32.768kHz = 977, 30.5us */ + fccTimeOutCounter++; + if(fccTimeOutCounter > 65){ + /* Timeout set to approximately 2ms (user-customizable) */ + break; + } + } + + /* get measA= SYSPLLCLK1 freq wrt LFOSC*/ + fFCCPllCount = DL_SYSCTL_readFCC(); + + /* Measuring SYSPLL Source */ + DL_SYSCTL_configFCC(DL_SYSCTL_FCC_TRIG_TYPE_RISE_RISE, + DL_SYSCTL_FCC_TRIG_SOURCE_LFCLK, + DL_SYSCTL_FCC_CLOCK_SOURCE_HFCLK); + /* Get SYSPLL frequency using FCC */ + fccTimeOutCounter = 0; + DL_SYSCTL_startFCC(); + while (DL_SYSCTL_isFCCDone() == 0) { + delay_cycles(977); /* 1x LFCLK cycle = 32MHz/32.768kHz = 977, 30.5us */ + fccTimeOutCounter++; + if(fccTimeOutCounter > 65){ + /* Timeout set to approximately 2ms (user-customizable) */ + break; + } + } + + /* get measB= SYSOSC freq wrt LFOSC*/ + fFCCSysoscCount = DL_SYSCTL_readFCC(); + + /* Get ratio of both measurements*/ + fFCCRatio = (fFCCPllCount * FLOAT_TO_INT_SCALE) / fFCCSysoscCount; + /* Check ratio is within bounds*/ + if ((FCC_LOWER_BOUND < fFCCRatio) && (fFCCRatio < FCC_UPPER_BOUND)) + { + /* ratio is good for proceeding into application code. */ + fFCCRatioStatus = true; + } + + return fFCCRatioStatus; +} +static const DL_SYSCTL_LFCLKConfig gLFCLKConfig = { + .lowCap = false, + .monitor = false, + .xt1Drive = DL_SYSCTL_LFXT_DRIVE_STRENGTH_HIGHEST, +}; +SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void) +{ + + //Low Power Mode is configured to be SLEEP0 + DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0); + DL_SYSCTL_setFlashWaitState(DL_SYSCTL_FLASH_WAIT_STATE_2); + + + DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE); + /* Set default configuration */ + DL_SYSCTL_disableHFXT(); + DL_SYSCTL_disableSYSPLL(); + DL_SYSCTL_setHFCLKSourceHFXTParams(DL_SYSCTL_HFXT_RANGE_8_16_MHZ,255, true); + DL_SYSCTL_configSYSPLL((DL_SYSCTL_SYSPLLConfig *) &gSYSPLLConfig); + + /* + * [SYSPLL_ERR_01] + * PLL Incorrect locking WA start. + * Insert after every PLL enable. + * This can lead an infinite loop if the condition persists + * and can block entry to the application code. + */ + + while (SYSCFG_DL_SYSCTL_SYSPLL_init() == false) + { + /* Toggle SYSPLL enable to re-enable SYSPLL and re-check incorrect locking */ + DL_SYSCTL_disableSYSPLL(); + DL_SYSCTL_enableSYSPLL(); + + /* Wait until SYSPLL startup is stabilized*/ + while ((DL_SYSCTL_getClockStatus() & SYSCTL_CLKSTATUS_SYSPLLGOOD_MASK) != DL_SYSCTL_CLK_STATUS_SYSPLL_GOOD){} + } + DL_SYSCTL_setULPCLKDivider(DL_SYSCTL_ULPCLK_DIV_2); + DL_SYSCTL_setLFCLKSourceLFXT((DL_SYSCTL_LFCLKConfig *) &gLFCLKConfig); + DL_SYSCTL_enableMFPCLK(); + DL_SYSCTL_setMFPCLKSource(DL_SYSCTL_MFPCLK_SOURCE_SYSOSC); + DL_SYSCTL_setMCLKSource(SYSOSC, HSCLK, DL_SYSCTL_HSCLK_SOURCE_SYSPLL); + +} + + +/* + * Timer clock configuration to be sourced by / 1 (80000000 Hz) + * timerClkFreq = (timerClkSrc / (timerClkDivRatio * (timerClkPrescale + 1))) + * 80000000 Hz = 80000000 Hz / (1 * (0 + 1)) + */ +static const DL_TimerA_ClockConfig gPWM_TIMA1ClockConfig = { + .clockSel = DL_TIMER_CLOCK_BUSCLK, + .divideRatio = DL_TIMER_CLOCK_DIVIDE_1, + .prescale = 0U +}; + +static const DL_TimerA_PWMConfig gPWM_TIMA1Config = { + .pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN, + .period = 1000, + .isTimerWithFourCC = false, + .startTimer = DL_TIMER_STOP, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_PWM_TIMA1_init(void) { + + DL_TimerA_setClockConfig( + PWM_TIMA1_INST, (DL_TimerA_ClockConfig *) &gPWM_TIMA1ClockConfig); + + DL_TimerA_initPWMMode( + PWM_TIMA1_INST, (DL_TimerA_PWMConfig *) &gPWM_TIMA1Config); + + // Set Counter control to the smallest CC index being used + DL_TimerA_setCounterControl(PWM_TIMA1_INST,DL_TIMER_CZC_CCCTL0_ZCOND,DL_TIMER_CAC_CCCTL0_ACOND,DL_TIMER_CLC_CCCTL0_LCOND); + + DL_TimerA_setCaptureCompareOutCtl(PWM_TIMA1_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERA_CAPTURE_COMPARE_0_INDEX); + + DL_TimerA_setCaptCompUpdateMethod(PWM_TIMA1_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERA_CAPTURE_COMPARE_0_INDEX); + DL_TimerA_setCaptureCompareValue(PWM_TIMA1_INST, 1000, DL_TIMER_CC_0_INDEX); + + DL_TimerA_setCaptureCompareOutCtl(PWM_TIMA1_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERA_CAPTURE_COMPARE_1_INDEX); + + DL_TimerA_setCaptCompUpdateMethod(PWM_TIMA1_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERA_CAPTURE_COMPARE_1_INDEX); + DL_TimerA_setCaptureCompareValue(PWM_TIMA1_INST, 1000, DL_TIMER_CC_1_INDEX); + + DL_TimerA_enableClock(PWM_TIMA1_INST); + + + + DL_TimerA_setCCPDirection(PWM_TIMA1_INST , DL_TIMER_CC0_OUTPUT | DL_TIMER_CC1_OUTPUT ); + + +} + + +static const DL_TimerG_ClockConfig gQEI_TIMG8ClockConfig = { + .clockSel = DL_TIMER_CLOCK_BUSCLK, + .divideRatio = DL_TIMER_CLOCK_DIVIDE_1, + .prescale = 0U +}; + + +SYSCONFIG_WEAK void SYSCFG_DL_QEI_TIMG8_init(void) { + + DL_TimerG_setClockConfig( + QEI_TIMG8_INST, (DL_TimerG_ClockConfig *) &gQEI_TIMG8ClockConfig); + + DL_TimerG_configQEI(QEI_TIMG8_INST, DL_TIMER_QEI_MODE_3_INPUT, + DL_TIMER_CC_INPUT_INV_NOINVERT, DL_TIMER_CC_0_INDEX); + DL_TimerG_configQEI(QEI_TIMG8_INST, DL_TIMER_QEI_MODE_3_INPUT, + DL_TIMER_CC_INPUT_INV_NOINVERT, DL_TIMER_CC_1_INDEX); + DL_TimerG_setLoadValue(QEI_TIMG8_INST, 65535); + DL_TimerG_enableClock(QEI_TIMG8_INST); +} + + +static const DL_I2C_ClockConfig gI2C_0ClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_0_init(void) { + + DL_I2C_setClockConfig(I2C_0_INST, + (DL_I2C_ClockConfig *) &gI2C_0ClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_0_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_0_INST); + + /* Configure Controller Mode */ + DL_I2C_resetControllerTransfer(I2C_0_INST); + /* Set frequency to 100000 Hz*/ + DL_I2C_setTimerPeriod(I2C_0_INST, 39); + DL_I2C_setControllerTXFIFOThreshold(I2C_0_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setControllerRXFIFOThreshold(I2C_0_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + DL_I2C_enableControllerClockStretching(I2C_0_INST); + + + /* Enable module */ + DL_I2C_enableController(I2C_0_INST); + + +} +static const DL_I2C_ClockConfig gI2C_1ClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_1_init(void) { + + DL_I2C_setClockConfig(I2C_1_INST, + (DL_I2C_ClockConfig *) &gI2C_1ClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_1_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_1_INST); + + /* Configure Controller Mode */ + DL_I2C_resetControllerTransfer(I2C_1_INST); + /* Set frequency to 100000 Hz*/ + DL_I2C_setTimerPeriod(I2C_1_INST, 39); + DL_I2C_setControllerTXFIFOThreshold(I2C_1_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setControllerRXFIFOThreshold(I2C_1_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + DL_I2C_enableControllerClockStretching(I2C_1_INST); + + + /* Enable module */ + DL_I2C_enableController(I2C_1_INST); + + +} + +static const DL_UART_Main_ClockConfig gUART_0ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_0Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_0_init(void) +{ + DL_UART_Main_setClockConfig(UART_0_INST, (DL_UART_Main_ClockConfig *) &gUART_0ClockConfig); + + DL_UART_Main_init(UART_0_INST, (DL_UART_Main_Config *) &gUART_0Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115190.78 + */ + DL_UART_Main_setOversampling(UART_0_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_0_INST, UART_0_IBRD_40_MHZ_115200_BAUD, UART_0_FBRD_40_MHZ_115200_BAUD); + + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_0_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_0_INST); + DL_UART_Main_setRXFIFOThreshold(UART_0_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_0_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_0_INST); +} +static const DL_UART_Main_ClockConfig gUART_1ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_1Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_1_init(void) +{ + DL_UART_Main_setClockConfig(UART_1_INST, (DL_UART_Main_ClockConfig *) &gUART_1ClockConfig); + + DL_UART_Main_init(UART_1_INST, (DL_UART_Main_Config *) &gUART_1Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115190.78 + */ + DL_UART_Main_setOversampling(UART_1_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_1_INST, UART_1_IBRD_40_MHZ_115200_BAUD, UART_1_FBRD_40_MHZ_115200_BAUD); + + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_1_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_1_INST); + DL_UART_Main_setRXFIFOThreshold(UART_1_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_1_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_1_INST); +} +static const DL_UART_Main_ClockConfig gUART_7ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_7Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_7_init(void) +{ + DL_UART_Main_setClockConfig(UART_7_INST, (DL_UART_Main_ClockConfig *) &gUART_7ClockConfig); + + DL_UART_Main_init(UART_7_INST, (DL_UART_Main_Config *) &gUART_7Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115190.78 + */ + DL_UART_Main_setOversampling(UART_7_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_7_INST, UART_7_IBRD_40_MHZ_115200_BAUD, UART_7_FBRD_40_MHZ_115200_BAUD); + + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_7_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_7_INST); + DL_UART_Main_setRXFIFOThreshold(UART_7_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_7_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_7_INST); +} + +static const DL_SPI_Config gSPI_1_config = { + .mode = DL_SPI_MODE_CONTROLLER, + .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0, + .parity = DL_SPI_PARITY_NONE, + .dataSize = DL_SPI_DATA_SIZE_8, + .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, + .chipSelectPin = DL_SPI_CHIP_SELECT_0, +}; + +static const DL_SPI_ClockConfig gSPI_1_clockConfig = { + .clockSel = DL_SPI_CLOCK_BUSCLK, + .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_1 +}; + +SYSCONFIG_WEAK void SYSCFG_DL_SPI_1_init(void) { + DL_SPI_setClockConfig(SPI_1_INST, (DL_SPI_ClockConfig *) &gSPI_1_clockConfig); + + DL_SPI_init(SPI_1_INST, (DL_SPI_Config *) &gSPI_1_config); + + /* Configure Controller mode */ + /* + * Set the bit rate clock divider to generate the serial output clock + * outputBitRate = (spiInputClock) / ((1 + SCR) * 2) + * 8000000 = (80000000)/((1 + 4) * 2) + */ + DL_SPI_setBitRateSerialClockDivider(SPI_1_INST, 4); + + /* Enable SPI TX interrupt as a trigger for DMA */ + DL_SPI_enableDMATransmitEvent(SPI_1_INST); + + /* Enable SPI RX interrupt as a trigger for DMA */ + DL_SPI_enableDMAReceiveEvent(SPI_1_INST, DL_SPI_DMA_INTERRUPT_RX); + /* Set RX and TX FIFO threshold levels */ + DL_SPI_setFIFOThreshold(SPI_1_INST, DL_SPI_RX_FIFO_LEVEL_ONE_FRAME, DL_SPI_TX_FIFO_LEVEL_ONE_FRAME); + DL_SPI_enableInterrupt(SPI_1_INST, (DL_SPI_INTERRUPT_DMA_DONE_RX | + DL_SPI_INTERRUPT_DMA_DONE_TX)); + + /* Enable module */ + DL_SPI_enable(SPI_1_INST); +} + +/* ADC12_0 Initialization */ +static const DL_ADC12_ClockConfig gADC12_0ClockConfig = { + .clockSel = DL_ADC12_CLOCK_SYSOSC, + .divideRatio = DL_ADC12_CLOCK_DIVIDE_1, + .freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32, +}; +SYSCONFIG_WEAK void SYSCFG_DL_ADC12_0_init(void) +{ + DL_ADC12_setClockConfig(ADC12_0_INST, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig); + DL_ADC12_configConversionMem(ADC12_0_INST, ADC12_0_ADCMEM_0, + DL_ADC12_INPUT_CHAN_3, DL_ADC12_REFERENCE_VOLTAGE_VDDA_VSSA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, DL_ADC12_AVERAGING_MODE_DISABLED, + DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); + DL_ADC12_enableConversions(ADC12_0_INST); +} +/* ADC12_1 Initialization */ +static const DL_ADC12_ClockConfig gADC12_1ClockConfig = { + .clockSel = DL_ADC12_CLOCK_SYSOSC, + .divideRatio = DL_ADC12_CLOCK_DIVIDE_1, + .freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32, +}; +SYSCONFIG_WEAK void SYSCFG_DL_ADC12_1_init(void) +{ + DL_ADC12_setClockConfig(ADC12_1_INST, (DL_ADC12_ClockConfig *) &gADC12_1ClockConfig); + DL_ADC12_configConversionMem(ADC12_1_INST, ADC12_1_ADCMEM_0, + DL_ADC12_INPUT_CHAN_6, DL_ADC12_REFERENCE_VOLTAGE_VDDA_VSSA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP0, DL_ADC12_AVERAGING_MODE_DISABLED, + DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED); + DL_ADC12_enableConversions(ADC12_1_INST); +} + +static const DL_DMA_Config gDMA_CH_SPI1_RXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_INCREMENT, + .srcIncrement = DL_DMA_ADDR_UNCHANGED, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_0, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_RX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_RX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_RXConfig); +} +static const DL_DMA_Config gDMA_CH_SPI1_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_1, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART0_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_0_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART0_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART0_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART0_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART1_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_1_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART1_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART1_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART1_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART7_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_7_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART7_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART7_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART7_TXConfig); +} +SYSCONFIG_WEAK void SYSCFG_DL_DMA_init(void){ + SYSCFG_DL_DMA_CH_SPI1_RX_init(); + SYSCFG_DL_DMA_CH_SPI1_TX_init(); + SYSCFG_DL_DMA_CH_UART0_TX_init(); + SYSCFG_DL_DMA_CH_UART1_TX_init(); + SYSCFG_DL_DMA_CH_UART7_TX_init(); +} + + +SYSCONFIG_WEAK void SYSCFG_DL_SYSTICK_init(void) +{ + /* + * Initializes the SysTick period to 1.00 ms, + * enables the interrupt, and starts the SysTick Timer + */ + DL_SYSTICK_config(80000); +} + +static const DL_DAC12_Config gDAC12Config = { + .outputEnable = DL_DAC12_OUTPUT_ENABLED, + .resolution = DL_DAC12_RESOLUTION_12BIT, + .representation = DL_DAC12_REPRESENTATION_BINARY, + .voltageReferenceSource = DL_DAC12_VREF_SOURCE_VDDA_VSSA, + .amplifierSetting = DL_DAC12_AMP_ON, + .fifoEnable = DL_DAC12_FIFO_DISABLED, + .fifoTriggerSource = DL_DAC12_FIFO_TRIGGER_SAMPLETIMER, + .dmaTriggerEnable = DL_DAC12_DMA_TRIGGER_DISABLED, + .dmaTriggerThreshold = DL_DAC12_FIFO_THRESHOLD_ONE_QTR_EMPTY, + .sampleTimeGeneratorEnable = DL_DAC12_SAMPLETIMER_DISABLE, + .sampleRate = DL_DAC12_SAMPLES_PER_SECOND_500, +}; +SYSCONFIG_WEAK void SYSCFG_DL_DAC12_init(void) +{ + DL_DAC12_init(DAC0, (DL_DAC12_Config *) &gDAC12Config); + DL_DAC12_enable(DAC0); +} + +static const DL_MCAN_ClockConfig gMCAN_0ClockConf = { + .clockSel = DL_MCAN_FCLK_SYSPLLCLK1, + .divider = DL_MCAN_FCLK_DIV_1, +}; + +static const DL_MCAN_InitParams gMCAN_0InitParams= { + +/* Initialize MCAN Init parameters. */ + .fdMode = true, + .brsEnable = true, + .txpEnable = false, + .efbi = false, + .pxhddisable = false, + .darEnable = false, + .wkupReqEnable = false, + .autoWkupEnable = false, + .emulationEnable = false, + .tdcEnable = false, + .wdcPreload = 255, + +/* Transmitter Delay Compensation parameters. */ + .tdcConfig.tdcf = 10, + .tdcConfig.tdco = 6, +}; + + +static const DL_MCAN_MsgRAMConfigParams gMCAN_0MsgRAMConfigParams ={ + + /* Standard ID Filter List Start Address. */ + .flssa = MCAN_0_INST_MCAN_STD_ID_FILT_START_ADDR, + /* List Size: Standard ID. */ + .lss = MCAN_0_INST_MCAN_STD_ID_FILTER_NUM, + /* Extended ID Filter List Start Address. */ + .flesa = MCAN_0_INST_MCAN_EXT_ID_FILT_START_ADDR, + /* List Size: Extended ID. */ + .lse = MCAN_0_INST_MCAN_EXT_ID_FILTER_NUM, + /* Tx Buffers Start Address. */ + .txStartAddr = MCAN_0_INST_MCAN_TX_BUFF_START_ADDR, + /* Number of Dedicated Transmit Buffers. */ + .txBufNum = MCAN_0_INST_MCAN_TX_BUFF_SIZE, + .txFIFOSize = 0, + /* Tx Buffer Element Size. */ + .txBufMode = 0, + .txBufElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Tx Event FIFO Start Address. */ + .txEventFIFOStartAddr = MCAN_0_INST_MCAN_TX_EVENT_START_ADDR, + /* Event FIFO Size. */ + .txEventFIFOSize = MCAN_0_INST_MCAN_TX_EVENT_SIZE, + /* Level for Tx Event FIFO watermark interrupt. */ + .txEventFIFOWaterMark = 3, + /* Rx FIFO0 Start Address. */ + .rxFIFO0startAddr = MCAN_0_INST_MCAN_FIFO_0_START_ADDR, + /* Number of Rx FIFO elements. */ + .rxFIFO0size = MCAN_0_INST_MCAN_FIFO_0_NUM, + /* Rx FIFO0 Watermark. */ + .rxFIFO0waterMark = 3, + .rxFIFO0OpMode = 0, + /* Rx FIFO1 Start Address. */ + .rxFIFO1startAddr = MCAN_0_INST_MCAN_FIFO_1_START_ADDR, + /* Number of Rx FIFO elements. */ + .rxFIFO1size = MCAN_0_INST_MCAN_FIFO_1_NUM, + /* Level for Rx FIFO 1 watermark interrupt. */ + .rxFIFO1waterMark = 3, + /* FIFO blocking mode. */ + .rxFIFO1OpMode = 0, + /* Rx Buffer Start Address. */ + .rxBufStartAddr = MCAN_0_INST_MCAN_RX_BUFF_START_ADDR, + /* Rx Buffer Element Size. */ + .rxBufElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Rx FIFO0 Element Size. */ + .rxFIFO0ElemSize = DL_MCAN_ELEM_SIZE_64BYTES, + /* Rx FIFO1 Element Size. */ + .rxFIFO1ElemSize = DL_MCAN_ELEM_SIZE_64BYTES, +}; + + + +static const DL_MCAN_BitTimingParams gMCAN_0BitTimes = { + /* Arbitration Baud Rate Pre-scaler. */ + .nomRatePrescalar = 3, + /* Arbitration Time segment before sample point. */ + .nomTimeSeg1 = 33, + /* Arbitration Time segment after sample point. */ + .nomTimeSeg2 = 4, + /* Arbitration (Re)Synchronization Jump Width Range. */ + .nomSynchJumpWidth = 4, + /* Data Baud Rate Pre-scaler. */ + .dataRatePrescalar = 3, + /* Data Time segment before sample point. */ + .dataTimeSeg1 = 16, + /* Data Time segment after sample point. */ + .dataTimeSeg2 = 1, + /* Data (Re)Synchronization Jump Width. */ + .dataSynchJumpWidth = 1, +}; + + +SYSCONFIG_WEAK void SYSCFG_DL_MCAN_0_init(void) { + DL_MCAN_RevisionId revid_MCAN_0; + + DL_MCAN_enableModuleClock(MCAN_0_INST); + + DL_MCAN_setClockConfig(MCAN_0_INST, (DL_MCAN_ClockConfig *) &gMCAN_0ClockConf); + + /* Get MCANSS Revision ID. */ + DL_MCAN_getRevisionId(MCAN_0_INST, &revid_MCAN_0); + + /* Wait for Memory initialization to be completed. */ + while(false == DL_MCAN_isMemInitDone(MCAN_0_INST)); + + /* Put MCAN in SW initialization mode. */ + + DL_MCAN_setOpMode(MCAN_0_INST, DL_MCAN_OPERATION_MODE_SW_INIT); + + /* Wait till MCAN is not initialized. */ + while (DL_MCAN_OPERATION_MODE_SW_INIT != DL_MCAN_getOpMode(MCAN_0_INST)); + + /* Initialize MCAN module. */ + DL_MCAN_init(MCAN_0_INST, (DL_MCAN_InitParams *) &gMCAN_0InitParams); + + + /* Configure Bit timings. */ + DL_MCAN_setBitTime(MCAN_0_INST, (DL_MCAN_BitTimingParams*) &gMCAN_0BitTimes); + + /* Configure Message RAM Sections */ + DL_MCAN_msgRAMConfig(MCAN_0_INST, (DL_MCAN_MsgRAMConfigParams*) &gMCAN_0MsgRAMConfigParams); + + + + /* Set Extended ID Mask. */ + DL_MCAN_setExtIDAndMask(MCAN_0_INST, MCAN_0_INST_MCAN_EXT_ID_AND_MASK ); + + /* Loopback mode */ + + /* Take MCAN out of the SW initialization mode */ + DL_MCAN_setOpMode(MCAN_0_INST, DL_MCAN_OPERATION_MODE_NORMAL); + + while (DL_MCAN_OPERATION_MODE_NORMAL != DL_MCAN_getOpMode(MCAN_0_INST)); + + +} + diff --git a/tests/data/mspm0/g3519_ti_msp_dl_config.h b/tests/data/mspm0/g3519_ti_msp_dl_config.h new file mode 100644 index 0000000..ec28223 --- /dev/null +++ b/tests/data/mspm0/g3519_ti_msp_dl_config.h @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated - http://www.ti.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.h ============= + * Configured MSPM0 DriverLib module declarations + * + * DO NOT EDIT - This file is generated for the MSPM0G351X + * by the SysConfig tool. + */ +#ifndef ti_msp_dl_config_h +#define ti_msp_dl_config_h + +#define CONFIG_MSPM0G351X +#define CONFIG_MSPM0G3519 + +#if defined(__ti_version__) || defined(__TI_COMPILER_VERSION__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#elif defined(__IAR_SYSTEMS_ICC__) +#define SYSCONFIG_WEAK __weak +#elif defined(__GNUC__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#endif + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ======== SYSCFG_DL_init ======== + * Perform all required MSP DL initialization + * + * This function should be called once at a point before any use of + * MSP DL. + */ + + +/* clang-format off */ + +#define POWER_STARTUP_DELAY (16) + + +#define GPIO_HFXT_PORT GPIOA +#define GPIO_HFXIN_PIN DL_GPIO_PIN_5 +#define GPIO_HFXIN_IOMUX (IOMUX_PINCM10) +#define GPIO_HFXOUT_PIN DL_GPIO_PIN_6 +#define GPIO_HFXOUT_IOMUX (IOMUX_PINCM11) +#define GPIO_LFXT_PORT GPIOA +#define GPIO_LFXIN_PIN DL_GPIO_PIN_3 +#define GPIO_LFXIN_IOMUX (IOMUX_PINCM8) +#define GPIO_LFXOUT_PIN DL_GPIO_PIN_4 +#define GPIO_LFXOUT_IOMUX (IOMUX_PINCM9) +#define CPUCLK_FREQ 80000000 +/* Defines for SYSPLL_ERR_01 Workaround */ +/* Represent 1.000 as 1000 */ +#define FLOAT_TO_INT_SCALE (1000U) +#define FCC_EXPECTED_RATIO 5000 +#define FCC_UPPER_BOUND (FCC_EXPECTED_RATIO * (1 + 0.003)) +#define FCC_LOWER_BOUND (FCC_EXPECTED_RATIO * (1 - 0.003)) + +bool SYSCFG_DL_SYSCTL_SYSPLL_init(void); + + +/* Defines for PWM_TIMA1 */ +#define PWM_TIMA1_INST TIMA1 +#define PWM_TIMA1_INST_IRQHandler TIMA1_IRQHandler +#define PWM_TIMA1_INST_INT_IRQN (TIMA1_INT_IRQn) +#define PWM_TIMA1_INST_CLK_FREQ 80000000 +/* GPIO defines for channel 0 */ +#define GPIO_PWM_TIMA1_C0_PORT GPIOA +#define GPIO_PWM_TIMA1_C0_PIN DL_GPIO_PIN_28 +#define GPIO_PWM_TIMA1_C0_IOMUX (IOMUX_PINCM3) +#define GPIO_PWM_TIMA1_C0_IOMUX_FUNC IOMUX_PINCM3_PF_TIMA1_CCP0 +#define GPIO_PWM_TIMA1_C0_IDX DL_TIMER_CC_0_INDEX +/* GPIO defines for channel 1 */ +#define GPIO_PWM_TIMA1_C1_PORT GPIOA +#define GPIO_PWM_TIMA1_C1_PIN DL_GPIO_PIN_31 +#define GPIO_PWM_TIMA1_C1_IOMUX (IOMUX_PINCM6) +#define GPIO_PWM_TIMA1_C1_IOMUX_FUNC IOMUX_PINCM6_PF_TIMA1_CCP1 +#define GPIO_PWM_TIMA1_C1_IDX DL_TIMER_CC_1_INDEX + + + + +/* Defines for QEI_TIMG8 */ +#define QEI_TIMG8_INST TIMG8 +#define QEI_TIMG8_INST_IRQHandler TIMG8_IRQHandler +#define QEI_TIMG8_INST_INT_IRQN (TIMG8_INT_IRQn) +/* Pin configuration defines for QEI_TIMG8 PHA Pin */ +#define GPIO_QEI_TIMG8_PHA_PORT GPIOA +#define GPIO_QEI_TIMG8_PHA_PIN DL_GPIO_PIN_26 +#define GPIO_QEI_TIMG8_PHA_IOMUX (IOMUX_PINCM59) +#define GPIO_QEI_TIMG8_PHA_IOMUX_FUNC IOMUX_PINCM59_PF_TIMG8_CCP0 +/* Pin configuration defines for QEI_TIMG8 PHB Pin */ +#define GPIO_QEI_TIMG8_PHB_PORT GPIOA +#define GPIO_QEI_TIMG8_PHB_PIN DL_GPIO_PIN_22 +#define GPIO_QEI_TIMG8_PHB_IOMUX (IOMUX_PINCM47) +#define GPIO_QEI_TIMG8_PHB_IOMUX_FUNC IOMUX_PINCM47_PF_TIMG8_CCP1 +/* Pin configuration defines for QEI_TIMG8 IDX Pin */ +#define GPIO_QEI_TIMG8_IDX_PORT GPIOB +#define GPIO_QEI_TIMG8_IDX_PIN DL_GPIO_PIN_14 +#define GPIO_QEI_TIMG8_IDX_IOMUX (IOMUX_PINCM31) +#define GPIO_QEI_TIMG8_IDX_IOMUX_FUNC IOMUX_PINCM31_PF_TIMG8_IDX + + + +/* Defines for I2C_0 */ +#define I2C_0_INST I2C0 +#define I2C_0_INST_IRQHandler I2C0_IRQHandler +#define I2C_0_INST_INT_IRQN I2C0_INT_IRQn +#define I2C_0_BUS_SPEED_HZ 100000 +#define GPIO_I2C_0_SDA_PORT GPIOA +#define GPIO_I2C_0_SDA_PIN DL_GPIO_PIN_0 +#define GPIO_I2C_0_IOMUX_SDA (IOMUX_PINCM1) +#define GPIO_I2C_0_IOMUX_SDA_FUNC IOMUX_PINCM1_PF_I2C0_SDA +#define GPIO_I2C_0_SCL_PORT GPIOA +#define GPIO_I2C_0_SCL_PIN DL_GPIO_PIN_1 +#define GPIO_I2C_0_IOMUX_SCL (IOMUX_PINCM2) +#define GPIO_I2C_0_IOMUX_SCL_FUNC IOMUX_PINCM2_PF_I2C0_SCL + +/* Defines for I2C_1 */ +#define I2C_1_INST I2C1 +#define I2C_1_INST_IRQHandler I2C1_IRQHandler +#define I2C_1_INST_INT_IRQN I2C1_INT_IRQn +#define I2C_1_BUS_SPEED_HZ 100000 +#define GPIO_I2C_1_SDA_PORT GPIOB +#define GPIO_I2C_1_SDA_PIN DL_GPIO_PIN_3 +#define GPIO_I2C_1_IOMUX_SDA (IOMUX_PINCM16) +#define GPIO_I2C_1_IOMUX_SDA_FUNC IOMUX_PINCM16_PF_I2C1_SDA +#define GPIO_I2C_1_SCL_PORT GPIOB +#define GPIO_I2C_1_SCL_PIN DL_GPIO_PIN_2 +#define GPIO_I2C_1_IOMUX_SCL (IOMUX_PINCM15) +#define GPIO_I2C_1_IOMUX_SCL_FUNC IOMUX_PINCM15_PF_I2C1_SCL + + +/* Defines for UART_0 */ +#define UART_0_INST UART0 +#define UART_0_INST_FREQUENCY 40000000 +#define UART_0_INST_IRQHandler UART0_IRQHandler +#define UART_0_INST_INT_IRQN UART0_INT_IRQn +#define GPIO_UART_0_RX_PORT GPIOA +#define GPIO_UART_0_TX_PORT GPIOA +#define GPIO_UART_0_RX_PIN DL_GPIO_PIN_11 +#define GPIO_UART_0_TX_PIN DL_GPIO_PIN_10 +#define GPIO_UART_0_IOMUX_RX (IOMUX_PINCM22) +#define GPIO_UART_0_IOMUX_TX (IOMUX_PINCM21) +#define GPIO_UART_0_IOMUX_RX_FUNC IOMUX_PINCM22_PF_UART0_RX +#define GPIO_UART_0_IOMUX_TX_FUNC IOMUX_PINCM21_PF_UART0_TX +#define UART_0_BAUD_RATE (115200) +#define UART_0_IBRD_40_MHZ_115200_BAUD (21) +#define UART_0_FBRD_40_MHZ_115200_BAUD (45) +/* Defines for UART_1 */ +#define UART_1_INST UART1 +#define UART_1_INST_FREQUENCY 40000000 +#define UART_1_INST_IRQHandler UART1_IRQHandler +#define UART_1_INST_INT_IRQN UART1_INT_IRQn +#define GPIO_UART_1_RX_PORT GPIOA +#define GPIO_UART_1_TX_PORT GPIOA +#define GPIO_UART_1_RX_PIN DL_GPIO_PIN_9 +#define GPIO_UART_1_TX_PIN DL_GPIO_PIN_8 +#define GPIO_UART_1_IOMUX_RX (IOMUX_PINCM20) +#define GPIO_UART_1_IOMUX_TX (IOMUX_PINCM19) +#define GPIO_UART_1_IOMUX_RX_FUNC IOMUX_PINCM20_PF_UART1_RX +#define GPIO_UART_1_IOMUX_TX_FUNC IOMUX_PINCM19_PF_UART1_TX +#define UART_1_BAUD_RATE (115200) +#define UART_1_IBRD_40_MHZ_115200_BAUD (21) +#define UART_1_FBRD_40_MHZ_115200_BAUD (45) +/* Defines for UART_7 */ +#define UART_7_INST UART7 +#define UART_7_INST_FREQUENCY 40000000 +#define UART_7_INST_IRQHandler UART7_IRQHandler +#define UART_7_INST_INT_IRQN UART7_INT_IRQn +#define GPIO_UART_7_RX_PORT GPIOB +#define GPIO_UART_7_TX_PORT GPIOB +#define GPIO_UART_7_RX_PIN DL_GPIO_PIN_18 +#define GPIO_UART_7_TX_PIN DL_GPIO_PIN_17 +#define GPIO_UART_7_IOMUX_RX (IOMUX_PINCM44) +#define GPIO_UART_7_IOMUX_TX (IOMUX_PINCM43) +#define GPIO_UART_7_IOMUX_RX_FUNC IOMUX_PINCM44_PF_UART7_RX +#define GPIO_UART_7_IOMUX_TX_FUNC IOMUX_PINCM43_PF_UART7_TX +#define UART_7_BAUD_RATE (115200) +#define UART_7_IBRD_40_MHZ_115200_BAUD (21) +#define UART_7_FBRD_40_MHZ_115200_BAUD (45) + + + + +/* Defines for SPI_1 */ +#define SPI_1_INST SPI1 +#define SPI_1_INST_IRQHandler SPI1_IRQHandler +#define SPI_1_INST_INT_IRQN SPI1_INT_IRQn +#define GPIO_SPI_1_PICO_PORT GPIOB +#define GPIO_SPI_1_PICO_PIN DL_GPIO_PIN_15 +#define GPIO_SPI_1_IOMUX_PICO (IOMUX_PINCM32) +#define GPIO_SPI_1_IOMUX_PICO_FUNC IOMUX_PINCM32_PF_SPI1_PICO +#define GPIO_SPI_1_POCI_PORT GPIOB +#define GPIO_SPI_1_POCI_PIN DL_GPIO_PIN_7 +#define GPIO_SPI_1_IOMUX_POCI (IOMUX_PINCM24) +#define GPIO_SPI_1_IOMUX_POCI_FUNC IOMUX_PINCM24_PF_SPI1_POCI +/* GPIO configuration for SPI_1 */ +#define GPIO_SPI_1_SCLK_PORT GPIOB +#define GPIO_SPI_1_SCLK_PIN DL_GPIO_PIN_9 +#define GPIO_SPI_1_IOMUX_SCLK (IOMUX_PINCM26) +#define GPIO_SPI_1_IOMUX_SCLK_FUNC IOMUX_PINCM26_PF_SPI1_SCLK +#define GPIO_SPI_1_CS0_PORT GPIOB +#define GPIO_SPI_1_CS0_PIN DL_GPIO_PIN_6 +#define GPIO_SPI_1_IOMUX_CS0 (IOMUX_PINCM23) +#define GPIO_SPI_1_IOMUX_CS0_FUNC IOMUX_PINCM23_PF_SPI1_CS0 + + + +/* Defines for ADC12_0 */ +#define ADC12_0_INST ADC0 +#define ADC12_0_INST_IRQHandler ADC0_IRQHandler +#define ADC12_0_INST_INT_IRQN (ADC0_INT_IRQn) +#define ADC12_0_ADCMEM_0 DL_ADC12_MEM_IDX_0 +#define ADC12_0_ADCMEM_0_REF DL_ADC12_REFERENCE_VOLTAGE_VDDA_VSSA +#define GPIO_ADC12_0_C3_PORT GPIOA +#define GPIO_ADC12_0_C3_PIN DL_GPIO_PIN_24 +#define GPIO_ADC12_0_IOMUX_C3 (IOMUX_PINCM54) +#define GPIO_ADC12_0_IOMUX_C3_FUNC (IOMUX_PINCM54_PF_UNCONNECTED) + +/* Defines for ADC12_1 */ +#define ADC12_1_INST ADC1 +#define ADC12_1_INST_IRQHandler ADC1_IRQHandler +#define ADC12_1_INST_INT_IRQN (ADC1_INT_IRQn) +#define ADC12_1_ADCMEM_0 DL_ADC12_MEM_IDX_0 +#define ADC12_1_ADCMEM_0_REF DL_ADC12_REFERENCE_VOLTAGE_VDDA_VSSA +#define GPIO_ADC12_1_C6_PORT GPIOB +#define GPIO_ADC12_1_C6_PIN DL_GPIO_PIN_19 +#define GPIO_ADC12_1_IOMUX_C6 (IOMUX_PINCM45) +#define GPIO_ADC12_1_IOMUX_C6_FUNC (IOMUX_PINCM45_PF_UNCONNECTED) + + + +/* Defines for DMA_CH_SPI1_RX */ +#define DMA_CH_SPI1_RX_CHAN_ID (6) +#define SPI_1_INST_DMA_TRIGGER_0 (DMA_SPI1_RX_TRIG) +/* Defines for DMA_CH_SPI1_TX */ +#define DMA_CH_SPI1_TX_CHAN_ID (2) +#define SPI_1_INST_DMA_TRIGGER_1 (DMA_SPI1_TX_TRIG) +/* Defines for DMA_CH_UART0_TX */ +#define DMA_CH_UART0_TX_CHAN_ID (3) +#define UART_0_INST_DMA_TRIGGER (DMA_UART0_TX_TRIG) +/* Defines for DMA_CH_UART1_TX */ +#define DMA_CH_UART1_TX_CHAN_ID (4) +#define UART_1_INST_DMA_TRIGGER (DMA_UART1_TX_TRIG) +/* Defines for DMA_CH_UART7_TX */ +#define DMA_CH_UART7_TX_CHAN_ID (7) +#define UART_7_INST_DMA_TRIGGER (DMA_UART7_TX_TRIG) + + +/* Port definition for Pin Group GPIO_LEDS */ +#define GPIO_LEDS_PORT (GPIOA) + +/* Defines for PIN_LED1: GPIOA.25 with pinCMx 55 on package pin 45 */ +#define GPIO_LEDS_PIN_LED1_PIN (DL_GPIO_PIN_25) +#define GPIO_LEDS_PIN_LED1_IOMUX (IOMUX_PINCM55) +/* Defines for PIN_LED2: GPIOA.16 with pinCMx 38 on package pin 31 */ +#define GPIO_LEDS_PIN_LED2_PIN (DL_GPIO_PIN_16) +#define GPIO_LEDS_PIN_LED2_IOMUX (IOMUX_PINCM38) +/* Port definition for Pin Group GPIO_KEYS */ +#define GPIO_KEYS_PORT (GPIOB) + +/* Defines for PIN_KEY1: GPIOB.24 with pinCMx 52 on package pin 42 */ +// pins affected by this interrupt request:["PIN_KEY1","PIN_KEY2"] +#define GPIO_KEYS_INT_IRQN (GPIOB_INT_IRQn) +#define GPIO_KEYS_INT_IIDX (DL_INTERRUPT_GROUP1_IIDX_GPIOB) +#define GPIO_KEYS_PIN_KEY1_IIDX (DL_GPIO_IIDX_DIO24) +#define GPIO_KEYS_PIN_KEY1_PIN (DL_GPIO_PIN_24) +#define GPIO_KEYS_PIN_KEY1_IOMUX (IOMUX_PINCM52) +/* Defines for PIN_KEY2: GPIOB.20 with pinCMx 48 on package pin 41 */ +#define GPIO_KEYS_PIN_KEY2_IIDX (DL_GPIO_IIDX_DIO20) +#define GPIO_KEYS_PIN_KEY2_PIN (DL_GPIO_PIN_20) +#define GPIO_KEYS_PIN_KEY2_IOMUX (IOMUX_PINCM48) + + + + + +/* Defines for DAC12 */ +#define DAC12_IRQHandler DAC0_IRQHandler +#define DAC12_INT_IRQN (DAC0_INT_IRQn) +#define GPIO_DAC12_OUT_PORT GPIOA +#define GPIO_DAC12_OUT_PIN DL_GPIO_PIN_15 +#define GPIO_DAC12_IOMUX_OUT (IOMUX_PINCM37) +#define GPIO_DAC12_IOMUX_OUT_FUNC IOMUX_PINCM37_PF_UNCONNECTED + + +/* Defines for MCAN_0 */ +#define MCAN_0_INST CANFD0 +#define GPIO_MCAN_0_CAN_TX_PORT GPIOA +#define GPIO_MCAN_0_CAN_TX_PIN DL_GPIO_PIN_12 +#define GPIO_MCAN_0_IOMUX_CAN_TX (IOMUX_PINCM34) +#define GPIO_MCAN_0_IOMUX_CAN_TX_FUNC IOMUX_PINCM34_PF_CANFD0_CANTX +#define GPIO_MCAN_0_CAN_RX_PORT GPIOA +#define GPIO_MCAN_0_CAN_RX_PIN DL_GPIO_PIN_13 +#define GPIO_MCAN_0_IOMUX_CAN_RX (IOMUX_PINCM35) +#define GPIO_MCAN_0_IOMUX_CAN_RX_FUNC IOMUX_PINCM35_PF_CANFD0_CANRX + + +/* Defines for MCAN_0 MCAN RAM configuration */ +#define MCAN_0_INST_MCAN_STD_ID_FILT_START_ADDR (0) +#define MCAN_0_INST_MCAN_STD_ID_FILTER_NUM (1) +#define MCAN_0_INST_MCAN_EXT_ID_FILT_START_ADDR (48) +#define MCAN_0_INST_MCAN_EXT_ID_FILTER_NUM (1) +#define MCAN_0_INST_MCAN_TX_BUFF_START_ADDR (148) +#define MCAN_0_INST_MCAN_TX_BUFF_SIZE (2) +#define MCAN_0_INST_MCAN_FIFO_1_START_ADDR (192) +#define MCAN_0_INST_MCAN_FIFO_1_NUM (2) +#define MCAN_0_INST_MCAN_TX_EVENT_START_ADDR (164) +#define MCAN_0_INST_MCAN_TX_EVENT_SIZE (2) +#define MCAN_0_INST_MCAN_EXT_ID_AND_MASK (0x1FFFFFFFU) +#define MCAN_0_INST_MCAN_RX_BUFF_START_ADDR (208) +#define MCAN_0_INST_MCAN_FIFO_0_START_ADDR (172) +#define MCAN_0_INST_MCAN_FIFO_0_NUM (3) + + + + + +/* clang-format on */ + +void SYSCFG_DL_init(void); +void SYSCFG_DL_initPower(void); +void SYSCFG_DL_GPIO_init(void); +void SYSCFG_DL_SYSCTL_init(void); + +bool SYSCFG_DL_SYSCTL_SYSPLL_init(void); +void SYSCFG_DL_PWM_TIMA1_init(void); +void SYSCFG_DL_QEI_TIMG8_init(void); +void SYSCFG_DL_I2C_0_init(void); +void SYSCFG_DL_I2C_1_init(void); +void SYSCFG_DL_UART_0_init(void); +void SYSCFG_DL_UART_1_init(void); +void SYSCFG_DL_UART_7_init(void); +void SYSCFG_DL_SPI_1_init(void); +void SYSCFG_DL_ADC12_0_init(void); +void SYSCFG_DL_ADC12_1_init(void); +void SYSCFG_DL_DMA_init(void); + +void SYSCFG_DL_SYSTICK_init(void); +void SYSCFG_DL_DAC12_init(void); +void SYSCFG_DL_MCAN_0_init(void); + +bool SYSCFG_DL_saveConfiguration(void); +bool SYSCFG_DL_restoreConfiguration(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ti_msp_dl_config_h */ diff --git a/tests/data/mspm0/uart_tx_dma.syscfg b/tests/data/mspm0/uart_tx_dma.syscfg new file mode 100644 index 0000000..9470a09 --- /dev/null +++ b/tests/data/mspm0/uart_tx_dma.syscfg @@ -0,0 +1,57 @@ +/** + * These arguments were used when this file was generated. They will be automatically applied on subsequent loads + * via the GUI or CLI. Run CLI with '--help' for additional information on how to override these arguments. + */ +//@cliArgs --device "MSPM0G350X" --package "LQFP-64(PM)" --part "Default" +//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)" +// @cliArgs --board /ti/boards/LP_MSPM0G3507 --rtos nortos + +/** + * Import the modules used in this configuration. + */ +const UART = scripting.addModule("/ti/driverlib/UART", {}, false); +const UART1 = UART.addInstance(); + +const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); +const GPIO1 = GPIO.addInstance(); + +const SYSCTL = scripting.addModule("/ti/driverlib/SYSCTL"); + +/** + * Write custom configuration values to the imported modules. + */ + +UART1.$name = "UART_0"; +UART1.profile = "CONFIG_PROFILE_1"; +UART1.peripheral.$assign = "UART0"; +UART1.targetBaudRate = 9600; +UART1.enableInternalLoopback = false; +UART1.enabledInterrupts = ["DMA_DONE_TX", "EOT_DONE"]; +UART1.enableFIFO = true; +UART1.txFifoThreshold = "DL_UART_TX_FIFO_LEVEL_ONE_ENTRY"; +UART1.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART1.enableDMATX = true; + +UART1.peripheral.rxPin.$assign = "PA11"; +UART1.peripheral.txPin.$assign = "PA10"; + +UART1.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH0"; +UART1.DMA_CHANNEL_TX.addressMode = "b2f"; +UART1.DMA_CHANNEL_TX.srcIncDec = "INCREMENT"; +UART1.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART1.DMA_CHANNEL_TX.srcLength = "BYTE"; + +GPIO1.$name = "GPIO_LEDS"; +GPIO1.associatedPins.create(2); +GPIO1.associatedPins[0].$name = "USER_LED_1"; +GPIO1.associatedPins[0].assignedPort = "PORTA"; +GPIO1.associatedPins[0].initialValue = "SET"; +GPIO1.associatedPins[0].assignedPin = "0"; +GPIO1.associatedPins[0].pin.$assign = "PA0"; +GPIO1.associatedPins[1].$name = "USER_TEST"; +GPIO1.associatedPins[1].assignedPort = "PORTA"; +GPIO1.associatedPins[1].assignedPin = "15"; +GPIO1.associatedPins[1].initialValue = "SET"; + +SYSCTL.clockTreeEn = true; +SYSCTL.forceDefaultClkConfig = true; diff --git a/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.c b/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.c new file mode 100644 index 0000000..7c1aa00 --- /dev/null +++ b/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.c ============= + * Configured MSPM0 DriverLib module definitions + * + * DO NOT EDIT - This file is generated for the LP_MSPM0G3507 + * by the SysConfig tool. + */ + +#include "ti_msp_dl_config.h" + +/* + * ======== SYSCFG_DL_init ======== + * Perform any initialization needed before using any board APIs + */ +SYSCONFIG_WEAK void SYSCFG_DL_init(void) +{ + SYSCFG_DL_initPower(); + SYSCFG_DL_GPIO_init(); + /* Module-Specific Initializations*/ + SYSCFG_DL_SYSCTL_init(); + SYSCFG_DL_UART_0_init(); + SYSCFG_DL_DMA_init(); +} + + + +SYSCONFIG_WEAK void SYSCFG_DL_initPower(void) +{ + DL_GPIO_reset(GPIOA); + DL_GPIO_reset(GPIOB); + DL_UART_Main_reset(UART_0_INST); + + + DL_GPIO_enablePower(GPIOA); + DL_GPIO_enablePower(GPIOB); + DL_UART_Main_enablePower(UART_0_INST); + + delay_cycles(POWER_STARTUP_DELAY); +} + +SYSCONFIG_WEAK void SYSCFG_DL_GPIO_init(void) +{ + + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_0_IOMUX_TX, GPIO_UART_0_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_0_IOMUX_RX, GPIO_UART_0_IOMUX_RX_FUNC); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_USER_LED_1_IOMUX); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_USER_TEST_IOMUX); + + DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN | + GPIO_LEDS_USER_TEST_PIN); + DL_GPIO_enableOutput(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN | + GPIO_LEDS_USER_TEST_PIN); + +} + + + +SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void) +{ + + //Low Power Mode is configured to be SLEEP0 + DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0); + + + DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE); + /* Set default configuration */ + DL_SYSCTL_disableHFXT(); + DL_SYSCTL_disableSYSPLL(); + +} + + +static const DL_UART_Main_ClockConfig gUART_0ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_0Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_0_init(void) +{ + DL_UART_Main_setClockConfig(UART_0_INST, (DL_UART_Main_ClockConfig *) &gUART_0ClockConfig); + + DL_UART_Main_init(UART_0_INST, (DL_UART_Main_Config *) &gUART_0Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 9600 + * Actual baud rate: 9600.24 + */ + DL_UART_Main_setOversampling(UART_0_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_0_INST, UART_0_IBRD_32_MHZ_9600_BAUD, UART_0_FBRD_32_MHZ_9600_BAUD); + + + /* Configure Interrupts */ + DL_UART_Main_enableInterrupt(UART_0_INST, + DL_UART_MAIN_INTERRUPT_DMA_DONE_TX | + DL_UART_MAIN_INTERRUPT_EOT_DONE); + + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_0_INST); + /* Configure FIFOs */ + DL_UART_Main_enableFIFOs(UART_0_INST); + DL_UART_Main_setRXFIFOThreshold(UART_0_INST, DL_UART_RX_FIFO_LEVEL_ONE_ENTRY); + DL_UART_Main_setTXFIFOThreshold(UART_0_INST, DL_UART_TX_FIFO_LEVEL_ONE_ENTRY); + + DL_UART_Main_enable(UART_0_INST); +} + +static const DL_DMA_Config gDMA_CH0Config = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_0_INST_DMA_TRIGGER, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH0_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH0_CHAN_ID , (DL_DMA_Config *) &gDMA_CH0Config); +} +SYSCONFIG_WEAK void SYSCFG_DL_DMA_init(void){ + SYSCFG_DL_DMA_CH0_init(); +} + + diff --git a/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.h b/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.h new file mode 100644 index 0000000..3655e66 --- /dev/null +++ b/tests/data/mspm0/uart_tx_dma_ti_msp_dl_config.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated - http://www.ti.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.h ============= + * Configured MSPM0 DriverLib module declarations + * + * DO NOT EDIT - This file is generated for the LP_MSPM0G3507 + * by the SysConfig tool. + */ +#ifndef ti_msp_dl_config_h +#define ti_msp_dl_config_h + +#define CONFIG_LP_MSPM0G3507 +#define CONFIG_MSPM0G3507 + +#if defined(__ti_version__) || defined(__TI_COMPILER_VERSION__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#elif defined(__IAR_SYSTEMS_ICC__) +#define SYSCONFIG_WEAK __weak +#elif defined(__GNUC__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#endif + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ======== SYSCFG_DL_init ======== + * Perform all required MSP DL initialization + * + * This function should be called once at a point before any use of + * MSP DL. + */ + + +/* clang-format off */ + +#define POWER_STARTUP_DELAY (16) + + + +#define CPUCLK_FREQ 32000000 + + + +/* Defines for UART_0 */ +#define UART_0_INST UART0 +#define UART_0_INST_FREQUENCY 32000000 +#define UART_0_INST_IRQHandler UART0_IRQHandler +#define UART_0_INST_INT_IRQN UART0_INT_IRQn +#define GPIO_UART_0_RX_PORT GPIOA +#define GPIO_UART_0_TX_PORT GPIOA +#define GPIO_UART_0_RX_PIN DL_GPIO_PIN_11 +#define GPIO_UART_0_TX_PIN DL_GPIO_PIN_10 +#define GPIO_UART_0_IOMUX_RX (IOMUX_PINCM22) +#define GPIO_UART_0_IOMUX_TX (IOMUX_PINCM21) +#define GPIO_UART_0_IOMUX_RX_FUNC IOMUX_PINCM22_PF_UART0_RX +#define GPIO_UART_0_IOMUX_TX_FUNC IOMUX_PINCM21_PF_UART0_TX +#define UART_0_BAUD_RATE (9600) +#define UART_0_IBRD_32_MHZ_9600_BAUD (208) +#define UART_0_FBRD_32_MHZ_9600_BAUD (21) + + + + + +/* Defines for DMA_CH0 */ +#define DMA_CH0_CHAN_ID (0) +#define UART_0_INST_DMA_TRIGGER (DMA_UART0_TX_TRIG) + + +/* Port definition for Pin Group GPIO_LEDS */ +#define GPIO_LEDS_PORT (GPIOA) + +/* Defines for USER_LED_1: GPIOA.0 with pinCMx 1 on package pin 33 */ +#define GPIO_LEDS_USER_LED_1_PIN (DL_GPIO_PIN_0) +#define GPIO_LEDS_USER_LED_1_IOMUX (IOMUX_PINCM1) +/* Defines for USER_TEST: GPIOA.15 with pinCMx 37 on package pin 8 */ +#define GPIO_LEDS_USER_TEST_PIN (DL_GPIO_PIN_15) +#define GPIO_LEDS_USER_TEST_IOMUX (IOMUX_PINCM37) + + +/* clang-format on */ + +void SYSCFG_DL_init(void); +void SYSCFG_DL_initPower(void); +void SYSCFG_DL_GPIO_init(void); +void SYSCFG_DL_SYSCTL_init(void); +void SYSCFG_DL_UART_0_init(void); +void SYSCFG_DL_DMA_init(void); + + +bool SYSCFG_DL_saveConfiguration(void); +bool SYSCFG_DL_restoreConfiguration(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ti_msp_dl_config_h */ diff --git a/tests/data/mspm0/variants.syscfg b/tests/data/mspm0/variants.syscfg new file mode 100644 index 0000000..fe2cdc5 --- /dev/null +++ b/tests/data/mspm0/variants.syscfg @@ -0,0 +1,109 @@ +/** + * These arguments were used when this file was generated. They will be automatically applied on subsequent loads + * via the GUI or CLI. Run CLI with '--help' for additional information on how to override these arguments. + * @cliArgs --device "MSPM0G350X" --part "Default" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @v2CliArgs --device "MSPM0G3507" --package "LQFP-48(PT)" --product "mspm0_sdk@2.11.00.07" + * @versions {"tool":"1.28.1+4785"} + */ + +/** + * A test project with the SysConfig setups the two BSPs do not use: an SPI clocked from MFCLK + * with a divider, an SPI in peripheral mode, an I2C in target mode only, a PWM with the default + * instance name whose pins are only suggested, GPIO pins given as port + pin number, a pin with + * the default name, and a UART Extend with DMA on both sides (MSPM0_UART_EXTEND_INIT). + */ +const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); +const GPIO1 = GPIO.addInstance(); +const GPIO2 = GPIO.addInstance(); +const I2C = scripting.addModule("/ti/driverlib/I2C", {}, false); +const I2C1 = I2C.addInstance(); +const I2C2 = I2C.addInstance(); +const PWM = scripting.addModule("/ti/driverlib/PWM", {}, false); +const PWM1 = PWM.addInstance(); +const SPI = scripting.addModule("/ti/driverlib/SPI", {}, false); +const SPI1 = SPI.addInstance(); +const SPI2 = SPI.addInstance(); +const SYSCTL = scripting.addModule("/ti/driverlib/SYSCTL"); +const UART = scripting.addModule("/ti/driverlib/UART", {}, false); +const UART1 = UART.addInstance(); + +GPIO1.$name = "GPIO_LEDS"; +GPIO1.port = "PORTA"; +GPIO1.associatedPins.create(2); +GPIO1.associatedPins[0].$name = "USER_LED_1"; +GPIO1.associatedPins[0].assignedPin = "26"; +GPIO1.associatedPins[1].$name = "USER_LED_2"; +GPIO1.associatedPins[1].assignedPin = "27"; + +GPIO2.$name = "GPIO_BTN"; +GPIO2.associatedPins.create(1); +GPIO2.associatedPins[0].$name = "PIN_0"; +GPIO2.associatedPins[0].direction = "INPUT"; +GPIO2.associatedPins[0].pin.$assign = "PA7"; + +I2C1.$name = "I2C_TARGET"; +I2C1.basicEnableTarget = true; +I2C1.basicTargetAddress = 0x48; +I2C1.peripheral.$assign = "I2C1"; +I2C1.peripheral.sdaPin.$assign = "PB3"; +I2C1.peripheral.sclPin.$assign = "PB2"; + +I2C2.$name = "I2C_0"; +I2C2.basicEnableController = true; +I2C2.basicControllerBusSpeed = 400000; +I2C2.peripheral.$assign = "I2C0"; +I2C2.peripheral.sdaPin.$assign = "PA0"; +I2C2.peripheral.sclPin.$assign = "PA1"; + +PWM1.$name = "PWM_0"; +PWM1.peripheral.$assign = "TIMG0"; +PWM1.peripheral.ccp0Pin.$suggestSolution = "PA12"; +PWM1.peripheral.ccp1Pin.$suggestSolution = "PA13"; + +SPI1.$name = "SPI_1"; +SPI1.spiClkSrc = "MFCLK"; +SPI1.spiClkDiv = "4"; +SPI1.enabledInterrupts = ["DMA_DONE_RX","DMA_DONE_TX"]; +SPI1.enabledDMAEvent1Triggers = "DL_SPI_DMA_INTERRUPT_RX"; +SPI1.enabledDMAEvent2Triggers = "DL_SPI_DMA_INTERRUPT_TX"; +SPI1.peripheral.$assign = "SPI1"; +SPI1.peripheral.sclkPin.$assign = "PB9"; +SPI1.peripheral.mosiPin.$assign = "PB8"; +SPI1.peripheral.misoPin.$assign = "PB7"; +SPI1.peripheral.cs0Pin.$assign = "PB6"; +SPI1.DMA_CHANNEL_EVENT1.$name = "DMA_CH_SPI1_RX"; +SPI1.DMA_CHANNEL_EVENT1.addressMode = "f2b"; +SPI1.DMA_CHANNEL_EVENT1.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT1.dstLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.$name = "DMA_CH_SPI1_TX"; +SPI1.DMA_CHANNEL_EVENT2.addressMode = "b2f"; +SPI1.DMA_CHANNEL_EVENT2.srcLength = "BYTE"; +SPI1.DMA_CHANNEL_EVENT2.dstLength = "BYTE"; + +SPI2.$name = "SPI_PERIPH"; +SPI2.mode = "PERIPHERAL"; +SPI2.peripheral.$assign = "SPI0"; +SPI2.peripheral.sclkPin.$assign = "PA6"; +SPI2.peripheral.mosiPin.$assign = "PA5"; +SPI2.peripheral.misoPin.$assign = "PA4"; +SPI2.peripheral.cs0Pin.$assign = "PA2"; + +SYSCTL.MFCLKEn = true; + +UART1.$name = "UART_0"; +UART1.targetBaudRate = 115200; +UART1.enableDMATX = true; +UART1.enabledDMATXTriggers = "DL_UART_DMA_INTERRUPT_TX"; +UART1.enableDMARX = true; +UART1.enabledDMARXTriggers = "DL_UART_DMA_INTERRUPT_RX"; +UART1.peripheral.$assign = "UART0"; +UART1.peripheral.rxPin.$assign = "PA11"; +UART1.peripheral.txPin.$assign = "PA10"; +UART1.DMA_CHANNEL_TX.$name = "DMA_CH_UART0_TX"; +UART1.DMA_CHANNEL_TX.addressMode = "b2f"; +UART1.DMA_CHANNEL_TX.srcLength = "BYTE"; +UART1.DMA_CHANNEL_TX.dstLength = "BYTE"; +UART1.DMA_CHANNEL_RX.$name = "DMA_CH_UART0_RX"; +UART1.DMA_CHANNEL_RX.addressMode = "f2b"; +UART1.DMA_CHANNEL_RX.srcLength = "BYTE"; +UART1.DMA_CHANNEL_RX.dstLength = "BYTE"; diff --git a/tests/data/mspm0/variants_expected_app_main.cpp b/tests/data/mspm0/variants_expected_app_main.cpp new file mode 100644 index 0000000..1dc1da8 --- /dev/null +++ b/tests/data/mspm0/variants_expected_app_main.cpp @@ -0,0 +1,123 @@ +// Generated by `libxr gen`; do not edit by hand. +// Generated from mspm0_variants.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 (no +// ULPCLK divider). 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; +static constexpr uint32_t MFCLK_FREQ = 4000000; +// SPI_1 runs from MFCLK (4 MHz) divided by 4, as SysConfig configured it; the SPI +// driver keeps that clock configuration. +static constexpr uint32_t SPI_1_CLK_FREQ = MFCLK_FREQ / 4; + +// Ownership of the DMA channels that serve a UART; MSPM0_UART_MAIN_INIT and +// MSPM0_UART_EXTEND_INIT check 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 +#define DMA_CH_UART0_RX_LIBXR_UART_IRQN UART_0_INST_INT_IRQN +#define DMA_CH_UART0_RX_LIBXR_UART_RX 1 +#define DMA_CH_UART0_RX_LIBXR_FULL_CHANNEL 1 +#define DMA_CH_UART0_RX_LIBXR_HALF_INTERRUPT 1 +#define UART_0_LIBXR_EXTEND_CAPABLE 1 + +// DMA buffers. A UART gets 2 x N bytes for its two transmit halves; a UART Extend with +// DMA RX also gets its receive ring. 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 uart0_rx_dma_buf[128]; +alignas(4) static uint8_t spi1_rx_buf[32]; +alignas(4) static uint8_t spi1_tx_buf[32]; +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 USER_LED_1 (PA26), USER_LED_2 (PA27) as output pins and + // gpio_btn_pin_0 (PA7) as input pins. The interrupt edge comes from SysConfig; + // RegisterCallback and EnableInterrupt belong to the consumer (a Module or the User + // Code). + static MSPM0GPIO USER_LED_1(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN, + GPIO_LEDS_USER_LED_1_IOMUX); + static MSPM0GPIO USER_LED_2(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_2_PIN, + GPIO_LEDS_USER_LED_2_IOMUX); + static MSPM0GPIO gpio_btn_pin_0(GPIO_BTN_PORT, GPIO_BTN_PIN_0_PIN, + GPIO_BTN_PIN_0_IOMUX); + + // UART: TX with DMA; a UART Extend with DMA RX receives by circular DMA, the others on + // byte interrupts. + static MSPM0UART uart0(MSPM0_UART_EXTEND_INIT(UART_0, DMA_CH_UART0_TX, DMA_CH_UART0_RX, + uart0_tx_buf, sizeof(uart0_tx_buf), 5, uart0_rx_dma_buf, + sizeof(uart0_rx_dma_buf), 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, SPI_1_CLK_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_timg0_c0(MSPM0_PWM_CH(PWM_0, 0)); + static MSPM0PWM pwm_timg0_c1(MSPM0_PWM_CH(PWM_0, 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(USER_LED_1, LibXR::GPIO); + XR_REGISTER(USER_LED_2, LibXR::GPIO); + XR_REGISTER(gpio_btn_pin_0, LibXR::GPIO); + + XR_REGISTER(pwm_timg0_c0, LibXR::PWM); + XR_REGISTER(pwm_timg0_c1, 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(); +} diff --git a/tests/data/mspm0/variants_ti_msp_dl_config.c b/tests/data/mspm0/variants_ti_msp_dl_config.c new file mode 100644 index 0000000..1051b06 --- /dev/null +++ b/tests/data/mspm0/variants_ti_msp_dl_config.c @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.c ============= + * Configured MSPM0 DriverLib module definitions + * + * DO NOT EDIT - This file is generated for the MSPM0G350X + * by the SysConfig tool. + */ + +#include "ti_msp_dl_config.h" + +DL_SPI_backupConfig gSPI_1Backup; +DL_SPI_backupConfig gSPI_PERIPHBackup; + +/* + * ======== SYSCFG_DL_init ======== + * Perform any initialization needed before using any board APIs + */ +SYSCONFIG_WEAK void SYSCFG_DL_init(void) +{ + SYSCFG_DL_initPower(); + SYSCFG_DL_GPIO_init(); + /* Module-Specific Initializations*/ + SYSCFG_DL_SYSCTL_init(); + SYSCFG_DL_PWM_0_init(); + SYSCFG_DL_I2C_TARGET_init(); + SYSCFG_DL_I2C_0_init(); + SYSCFG_DL_UART_0_init(); + SYSCFG_DL_SPI_1_init(); + SYSCFG_DL_SPI_PERIPH_init(); + SYSCFG_DL_DMA_init(); + /* Ensure backup structures have no valid state */ + + + gSPI_1Backup.backupRdy = false; + gSPI_PERIPHBackup.backupRdy = false; + +} +/* + * User should take care to save and restore register configuration in application. + * See Retention Configuration section for more details. + */ +SYSCONFIG_WEAK bool SYSCFG_DL_saveConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_SPI_saveConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_SPI_saveConfiguration(SPI_PERIPH_INST, &gSPI_PERIPHBackup); + + return retStatus; +} + + +SYSCONFIG_WEAK bool SYSCFG_DL_restoreConfiguration(void) +{ + bool retStatus = true; + + retStatus &= DL_SPI_restoreConfiguration(SPI_1_INST, &gSPI_1Backup); + retStatus &= DL_SPI_restoreConfiguration(SPI_PERIPH_INST, &gSPI_PERIPHBackup); + + return retStatus; +} + +SYSCONFIG_WEAK void SYSCFG_DL_initPower(void) +{ + DL_GPIO_reset(GPIOA); + DL_GPIO_reset(GPIOB); + DL_TimerG_reset(PWM_0_INST); + DL_I2C_reset(I2C_TARGET_INST); + DL_I2C_reset(I2C_0_INST); + DL_UART_Main_reset(UART_0_INST); + DL_SPI_reset(SPI_1_INST); + DL_SPI_reset(SPI_PERIPH_INST); + + + DL_GPIO_enablePower(GPIOA); + DL_GPIO_enablePower(GPIOB); + DL_TimerG_enablePower(PWM_0_INST); + DL_I2C_enablePower(I2C_TARGET_INST); + DL_I2C_enablePower(I2C_0_INST); + DL_UART_Main_enablePower(UART_0_INST); + DL_SPI_enablePower(SPI_1_INST); + DL_SPI_enablePower(SPI_PERIPH_INST); + + delay_cycles(POWER_STARTUP_DELAY); +} + +SYSCONFIG_WEAK void SYSCFG_DL_GPIO_init(void) +{ + + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_0_C0_IOMUX,GPIO_PWM_0_C0_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_0_C0_PORT, GPIO_PWM_0_C0_PIN); + DL_GPIO_initPeripheralOutputFunction(GPIO_PWM_0_C1_IOMUX,GPIO_PWM_0_C1_IOMUX_FUNC); + DL_GPIO_enableOutput(GPIO_PWM_0_C1_PORT, GPIO_PWM_0_C1_PIN); + + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_TARGET_IOMUX_SDA, + GPIO_I2C_TARGET_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_TARGET_IOMUX_SCL, + GPIO_I2C_TARGET_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_TARGET_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_TARGET_IOMUX_SCL); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SDA, + GPIO_I2C_0_IOMUX_SDA_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_initPeripheralInputFunctionFeatures(GPIO_I2C_0_IOMUX_SCL, + GPIO_I2C_0_IOMUX_SCL_FUNC, DL_GPIO_INVERSION_DISABLE, + DL_GPIO_RESISTOR_NONE, DL_GPIO_HYSTERESIS_DISABLE, + DL_GPIO_WAKEUP_DISABLE); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SDA); + DL_GPIO_enableHiZ(GPIO_I2C_0_IOMUX_SCL); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_UART_0_IOMUX_TX, GPIO_UART_0_IOMUX_TX_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_UART_0_IOMUX_RX, GPIO_UART_0_IOMUX_RX_FUNC); + + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_SCLK, GPIO_SPI_1_IOMUX_SCLK_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_PICO, GPIO_SPI_1_IOMUX_PICO_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_1_IOMUX_POCI, GPIO_SPI_1_IOMUX_POCI_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_1_IOMUX_CS0, GPIO_SPI_1_IOMUX_CS0_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_PERIPH_IOMUX_SCLK, GPIO_SPI_PERIPH_IOMUX_SCLK_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_PERIPH_IOMUX_PICO, GPIO_SPI_PERIPH_IOMUX_PICO_FUNC); + DL_GPIO_initPeripheralOutputFunction( + GPIO_SPI_PERIPH_IOMUX_POCI, GPIO_SPI_PERIPH_IOMUX_POCI_FUNC); + DL_GPIO_initPeripheralInputFunction( + GPIO_SPI_PERIPH_IOMUX_CS, GPIO_SPI_PERIPH_IOMUX_CS_FUNC); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_USER_LED_1_IOMUX); + + DL_GPIO_initDigitalOutput(GPIO_LEDS_USER_LED_2_IOMUX); + + DL_GPIO_initDigitalInputFeatures(GPIO_BTN_PIN_0_IOMUX, + DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_NONE, + DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE); + + DL_GPIO_clearPins(GPIOA, GPIO_LEDS_USER_LED_1_PIN | + GPIO_LEDS_USER_LED_2_PIN); + DL_GPIO_enableOutput(GPIOA, GPIO_LEDS_USER_LED_1_PIN | + GPIO_LEDS_USER_LED_2_PIN); + +} + + +SYSCONFIG_WEAK void SYSCFG_DL_SYSCTL_init(void) +{ + + //Low Power Mode is configured to be SLEEP0 + DL_SYSCTL_setBORThreshold(DL_SYSCTL_BOR_THRESHOLD_LEVEL_0); + + DL_SYSCTL_setSYSOSCFreq(DL_SYSCTL_SYSOSC_FREQ_BASE); + DL_SYSCTL_enableMFCLK(); + DL_SYSCTL_setULPCLKDivider(DL_SYSCTL_ULPCLK_DIV_1); + DL_SYSCTL_setMCLKDivider(DL_SYSCTL_MCLK_DIVIDER_DISABLE); + +} + + +/* + * Timer clock configuration to be sourced by / 1 (32000000 Hz) + * timerClkFreq = (timerClkSrc / (timerClkDivRatio * (timerClkPrescale + 1))) + * 32000000 Hz = 32000000 Hz / (1 * (0 + 1)) + */ +static const DL_TimerG_ClockConfig gPWM_0ClockConfig = { + .clockSel = DL_TIMER_CLOCK_BUSCLK, + .divideRatio = DL_TIMER_CLOCK_DIVIDE_1, + .prescale = 0U +}; + +static const DL_TimerG_PWMConfig gPWM_0Config = { + .pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN, + .period = 1000, + .isTimerWithFourCC = false, + .startTimer = DL_TIMER_STOP, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_PWM_0_init(void) { + + DL_TimerG_setClockConfig( + PWM_0_INST, (DL_TimerG_ClockConfig *) &gPWM_0ClockConfig); + + DL_TimerG_initPWMMode( + PWM_0_INST, (DL_TimerG_PWMConfig *) &gPWM_0Config); + + // Set Counter control to the smallest CC index being used + DL_TimerG_setCounterControl(PWM_0_INST,DL_TIMER_CZC_CCCTL0_ZCOND,DL_TIMER_CAC_CCCTL0_ACOND,DL_TIMER_CLC_CCCTL0_LCOND); + + DL_TimerG_setCaptureCompareOutCtl(PWM_0_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERG_CAPTURE_COMPARE_0_INDEX); + + DL_TimerG_setCaptCompUpdateMethod(PWM_0_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERG_CAPTURE_COMPARE_0_INDEX); + DL_TimerG_setCaptureCompareValue(PWM_0_INST, 1000, DL_TIMER_CC_0_INDEX); + + DL_TimerG_setCaptureCompareOutCtl(PWM_0_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW, + DL_TIMER_CC_OCTL_INV_OUT_DISABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL, + DL_TIMERG_CAPTURE_COMPARE_1_INDEX); + + DL_TimerG_setCaptCompUpdateMethod(PWM_0_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERG_CAPTURE_COMPARE_1_INDEX); + DL_TimerG_setCaptureCompareValue(PWM_0_INST, 1000, DL_TIMER_CC_1_INDEX); + + DL_TimerG_enableClock(PWM_0_INST); + + + + DL_TimerG_setCCPDirection(PWM_0_INST , DL_TIMER_CC0_OUTPUT | DL_TIMER_CC1_OUTPUT ); + + +} + + +static const DL_I2C_ClockConfig gI2C_TARGETClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_TARGET_init(void) { + + DL_I2C_setClockConfig(I2C_TARGET_INST, + (DL_I2C_ClockConfig *) &gI2C_TARGETClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_TARGET_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_TARGET_INST); + + /* Configure Target Mode */ + DL_I2C_setTargetOwnAddress(I2C_TARGET_INST, I2C_TARGET_TARGET_OWN_ADDR); + DL_I2C_setTargetTXFIFOThreshold(I2C_TARGET_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setTargetRXFIFOThreshold(I2C_TARGET_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + + DL_I2C_enableTargetClockStretching(I2C_TARGET_INST); + + /* Workaround for errata I2C_ERR_04 */ + DL_I2C_disableTargetWakeup(I2C_TARGET_INST); + + /* Enable module */ + DL_I2C_enableTarget(I2C_TARGET_INST); + + +} +static const DL_I2C_ClockConfig gI2C_0ClockConfig = { + .clockSel = DL_I2C_CLOCK_BUSCLK, + .divideRatio = DL_I2C_CLOCK_DIVIDE_1, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_I2C_0_init(void) { + + DL_I2C_setClockConfig(I2C_0_INST, + (DL_I2C_ClockConfig *) &gI2C_0ClockConfig); + DL_I2C_setAnalogGlitchFilterPulseWidth(I2C_0_INST, + DL_I2C_ANALOG_GLITCH_FILTER_WIDTH_50NS); + DL_I2C_enableAnalogGlitchFilter(I2C_0_INST); + + /* Configure Controller Mode */ + DL_I2C_resetControllerTransfer(I2C_0_INST); + /* Set frequency to 400000 Hz*/ + DL_I2C_setTimerPeriod(I2C_0_INST, 7); + DL_I2C_setControllerTXFIFOThreshold(I2C_0_INST, DL_I2C_TX_FIFO_LEVEL_EMPTY); + DL_I2C_setControllerRXFIFOThreshold(I2C_0_INST, DL_I2C_RX_FIFO_LEVEL_BYTES_1); + DL_I2C_enableControllerClockStretching(I2C_0_INST); + + + /* Enable module */ + DL_I2C_enableController(I2C_0_INST); + + +} + +static const DL_UART_Main_ClockConfig gUART_0ClockConfig = { + .clockSel = DL_UART_MAIN_CLOCK_BUSCLK, + .divideRatio = DL_UART_MAIN_CLOCK_DIVIDE_RATIO_1 +}; + +static const DL_UART_Main_Config gUART_0Config = { + .mode = DL_UART_MAIN_MODE_NORMAL, + .direction = DL_UART_MAIN_DIRECTION_TX_RX, + .flowControl = DL_UART_MAIN_FLOW_CONTROL_NONE, + .parity = DL_UART_MAIN_PARITY_NONE, + .wordLength = DL_UART_MAIN_WORD_LENGTH_8_BITS, + .stopBits = DL_UART_MAIN_STOP_BITS_ONE +}; + +SYSCONFIG_WEAK void SYSCFG_DL_UART_0_init(void) +{ + DL_UART_Main_setClockConfig(UART_0_INST, (DL_UART_Main_ClockConfig *) &gUART_0ClockConfig); + + DL_UART_Main_init(UART_0_INST, (DL_UART_Main_Config *) &gUART_0Config); + /* + * Configure baud rate by setting oversampling and baud rate divisors. + * Target baud rate: 115200 + * Actual baud rate: 115211.52 + */ + DL_UART_Main_setOversampling(UART_0_INST, DL_UART_OVERSAMPLING_RATE_16X); + DL_UART_Main_setBaudRateDivisor(UART_0_INST, UART_0_IBRD_32_MHZ_115200_BAUD, UART_0_FBRD_32_MHZ_115200_BAUD); + + + /* Configure DMA Receive Event */ + DL_UART_Main_enableDMAReceiveEvent(UART_0_INST, DL_UART_DMA_INTERRUPT_RX); + /* Configure DMA Transmit Event */ + DL_UART_Main_enableDMATransmitEvent(UART_0_INST); + + DL_UART_Main_enable(UART_0_INST); +} + +static const DL_SPI_Config gSPI_1_config = { + .mode = DL_SPI_MODE_CONTROLLER, + .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0, + .parity = DL_SPI_PARITY_NONE, + .dataSize = DL_SPI_DATA_SIZE_8, + .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, + .chipSelectPin = DL_SPI_CHIP_SELECT_0, +}; + +static const DL_SPI_ClockConfig gSPI_1_clockConfig = { + .clockSel = DL_SPI_CLOCK_MFCLK, + .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_4 +}; + +SYSCONFIG_WEAK void SYSCFG_DL_SPI_1_init(void) { + DL_SPI_setClockConfig(SPI_1_INST, (DL_SPI_ClockConfig *) &gSPI_1_clockConfig); + + DL_SPI_init(SPI_1_INST, (DL_SPI_Config *) &gSPI_1_config); + + /* Configure Controller mode */ + /* + * Set the bit rate clock divider to generate the serial output clock + * outputBitRate = (spiInputClock) / ((1 + SCR) * 2) + * Infinity = (1000000)/((1 + -1) * 2) + */ + DL_SPI_setBitRateSerialClockDivider(SPI_1_INST, -1); + + /* Enable SPI TX interrupt as a trigger for DMA */ + DL_SPI_enableDMATransmitEvent(SPI_1_INST); + + /* Enable SPI RX interrupt as a trigger for DMA */ + DL_SPI_enableDMAReceiveEvent(SPI_1_INST, DL_SPI_DMA_INTERRUPT_RX); + /* Set RX and TX FIFO threshold levels */ + DL_SPI_setFIFOThreshold(SPI_1_INST, DL_SPI_RX_FIFO_LEVEL_1_2_FULL, DL_SPI_TX_FIFO_LEVEL_1_2_EMPTY); + DL_SPI_enableInterrupt(SPI_1_INST, (DL_SPI_INTERRUPT_DMA_DONE_RX | + DL_SPI_INTERRUPT_DMA_DONE_TX)); + + /* Enable module */ + DL_SPI_enable(SPI_1_INST); +} +static const DL_SPI_Config gSPI_PERIPH_config = { + .mode = DL_SPI_MODE_PERIPHERAL, + .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0, + .parity = DL_SPI_PARITY_NONE, + .dataSize = DL_SPI_DATA_SIZE_8, + .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, + .chipSelectPin = DL_SPI_CHIP_SELECT_0, +}; + +static const DL_SPI_ClockConfig gSPI_PERIPH_clockConfig = { + .clockSel = DL_SPI_CLOCK_BUSCLK, + .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_1 +}; + +SYSCONFIG_WEAK void SYSCFG_DL_SPI_PERIPH_init(void) { + DL_SPI_setClockConfig(SPI_PERIPH_INST, (DL_SPI_ClockConfig *) &gSPI_PERIPH_clockConfig); + + DL_SPI_init(SPI_PERIPH_INST, (DL_SPI_Config *) &gSPI_PERIPH_config); + + /* Configure Peripheral mode */ + /* Set RX and TX FIFO threshold levels */ + DL_SPI_setFIFOThreshold(SPI_PERIPH_INST, DL_SPI_RX_FIFO_LEVEL_1_2_FULL, DL_SPI_TX_FIFO_LEVEL_1_2_EMPTY); + + /* Enable module */ + DL_SPI_enable(SPI_PERIPH_INST); +} + +static const DL_DMA_Config gDMA_CH_SPI1_RXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_INCREMENT, + .srcIncrement = DL_DMA_ADDR_UNCHANGED, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_0, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_RX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_RX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_RXConfig); +} +static const DL_DMA_Config gDMA_CH_SPI1_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = SPI_1_INST_DMA_TRIGGER_1, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_SPI1_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_SPI1_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_SPI1_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART0_TXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_UNCHANGED, + .srcIncrement = DL_DMA_ADDR_INCREMENT, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_0_INST_DMA_TRIGGER_0, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART0_TX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART0_TX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART0_TXConfig); +} +static const DL_DMA_Config gDMA_CH_UART0_RXConfig = { + .transferMode = DL_DMA_SINGLE_TRANSFER_MODE, + .extendedMode = DL_DMA_NORMAL_MODE, + .destIncrement = DL_DMA_ADDR_INCREMENT, + .srcIncrement = DL_DMA_ADDR_UNCHANGED, + .destWidth = DL_DMA_WIDTH_BYTE, + .srcWidth = DL_DMA_WIDTH_BYTE, + .trigger = UART_0_INST_DMA_TRIGGER_1, + .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, +}; + +SYSCONFIG_WEAK void SYSCFG_DL_DMA_CH_UART0_RX_init(void) +{ + DL_DMA_initChannel(DMA, DMA_CH_UART0_RX_CHAN_ID , (DL_DMA_Config *) &gDMA_CH_UART0_RXConfig); +} +SYSCONFIG_WEAK void SYSCFG_DL_DMA_init(void){ + SYSCFG_DL_DMA_CH_SPI1_RX_init(); + SYSCFG_DL_DMA_CH_SPI1_TX_init(); + SYSCFG_DL_DMA_CH_UART0_TX_init(); + SYSCFG_DL_DMA_CH_UART0_RX_init(); +} + + diff --git a/tests/data/mspm0/variants_ti_msp_dl_config.h b/tests/data/mspm0/variants_ti_msp_dl_config.h new file mode 100644 index 0000000..5ca0da7 --- /dev/null +++ b/tests/data/mspm0/variants_ti_msp_dl_config.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2023, Texas Instruments Incorporated - http://www.ti.com + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * ============ ti_msp_dl_config.h ============= + * Configured MSPM0 DriverLib module declarations + * + * DO NOT EDIT - This file is generated for the MSPM0G350X + * by the SysConfig tool. + */ +#ifndef ti_msp_dl_config_h +#define ti_msp_dl_config_h + +#define CONFIG_MSPM0G350X +#define CONFIG_MSPM0G3507 + +#if defined(__ti_version__) || defined(__TI_COMPILER_VERSION__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#elif defined(__IAR_SYSTEMS_ICC__) +#define SYSCONFIG_WEAK __weak +#elif defined(__GNUC__) +#define SYSCONFIG_WEAK __attribute__((weak)) +#endif + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ======== SYSCFG_DL_init ======== + * Perform all required MSP DL initialization + * + * This function should be called once at a point before any use of + * MSP DL. + */ + + +/* clang-format off */ + +#define POWER_STARTUP_DELAY (16) + + +#define CPUCLK_FREQ 32000000 + + + +/* Defines for PWM_0 */ +#define PWM_0_INST TIMG0 +#define PWM_0_INST_IRQHandler TIMG0_IRQHandler +#define PWM_0_INST_INT_IRQN (TIMG0_INT_IRQn) +#define PWM_0_INST_CLK_FREQ 32000000 +/* GPIO defines for channel 0 */ +#define GPIO_PWM_0_C0_PORT GPIOA +#define GPIO_PWM_0_C0_PIN DL_GPIO_PIN_12 +#define GPIO_PWM_0_C0_IOMUX (IOMUX_PINCM34) +#define GPIO_PWM_0_C0_IOMUX_FUNC IOMUX_PINCM34_PF_TIMG0_CCP0 +#define GPIO_PWM_0_C0_IDX DL_TIMER_CC_0_INDEX +/* GPIO defines for channel 1 */ +#define GPIO_PWM_0_C1_PORT GPIOA +#define GPIO_PWM_0_C1_PIN DL_GPIO_PIN_13 +#define GPIO_PWM_0_C1_IOMUX (IOMUX_PINCM35) +#define GPIO_PWM_0_C1_IOMUX_FUNC IOMUX_PINCM35_PF_TIMG0_CCP1 +#define GPIO_PWM_0_C1_IDX DL_TIMER_CC_1_INDEX + + + + +/* Defines for I2C_TARGET */ +#define I2C_TARGET_INST I2C1 +#define I2C_TARGET_INST_IRQHandler I2C1_IRQHandler +#define I2C_TARGET_INST_INT_IRQN I2C1_INT_IRQn +#define I2C_TARGET_TARGET_OWN_ADDR 0x48 +#define GPIO_I2C_TARGET_SDA_PORT GPIOB +#define GPIO_I2C_TARGET_SDA_PIN DL_GPIO_PIN_3 +#define GPIO_I2C_TARGET_IOMUX_SDA (IOMUX_PINCM16) +#define GPIO_I2C_TARGET_IOMUX_SDA_FUNC IOMUX_PINCM16_PF_I2C1_SDA +#define GPIO_I2C_TARGET_SCL_PORT GPIOB +#define GPIO_I2C_TARGET_SCL_PIN DL_GPIO_PIN_2 +#define GPIO_I2C_TARGET_IOMUX_SCL (IOMUX_PINCM15) +#define GPIO_I2C_TARGET_IOMUX_SCL_FUNC IOMUX_PINCM15_PF_I2C1_SCL + +/* Defines for I2C_0 */ +#define I2C_0_INST I2C0 +#define I2C_0_INST_IRQHandler I2C0_IRQHandler +#define I2C_0_INST_INT_IRQN I2C0_INT_IRQn +#define I2C_0_BUS_SPEED_HZ 400000 +#define GPIO_I2C_0_SDA_PORT GPIOA +#define GPIO_I2C_0_SDA_PIN DL_GPIO_PIN_0 +#define GPIO_I2C_0_IOMUX_SDA (IOMUX_PINCM1) +#define GPIO_I2C_0_IOMUX_SDA_FUNC IOMUX_PINCM1_PF_I2C0_SDA +#define GPIO_I2C_0_SCL_PORT GPIOA +#define GPIO_I2C_0_SCL_PIN DL_GPIO_PIN_1 +#define GPIO_I2C_0_IOMUX_SCL (IOMUX_PINCM2) +#define GPIO_I2C_0_IOMUX_SCL_FUNC IOMUX_PINCM2_PF_I2C0_SCL + + +/* Defines for UART_0 */ +#define UART_0_INST UART0 +#define UART_0_INST_FREQUENCY 32000000 +#define UART_0_INST_IRQHandler UART0_IRQHandler +#define UART_0_INST_INT_IRQN UART0_INT_IRQn +#define GPIO_UART_0_RX_PORT GPIOA +#define GPIO_UART_0_TX_PORT GPIOA +#define GPIO_UART_0_RX_PIN DL_GPIO_PIN_11 +#define GPIO_UART_0_TX_PIN DL_GPIO_PIN_10 +#define GPIO_UART_0_IOMUX_RX (IOMUX_PINCM22) +#define GPIO_UART_0_IOMUX_TX (IOMUX_PINCM21) +#define GPIO_UART_0_IOMUX_RX_FUNC IOMUX_PINCM22_PF_UART0_RX +#define GPIO_UART_0_IOMUX_TX_FUNC IOMUX_PINCM21_PF_UART0_TX +#define UART_0_BAUD_RATE (115200) +#define UART_0_IBRD_32_MHZ_115200_BAUD (17) +#define UART_0_FBRD_32_MHZ_115200_BAUD (23) + + + + +/* Defines for SPI_1 */ +#define SPI_1_INST SPI1 +#define SPI_1_INST_IRQHandler SPI1_IRQHandler +#define SPI_1_INST_INT_IRQN SPI1_INT_IRQn +#define GPIO_SPI_1_PICO_PORT GPIOB +#define GPIO_SPI_1_PICO_PIN DL_GPIO_PIN_8 +#define GPIO_SPI_1_IOMUX_PICO (IOMUX_PINCM25) +#define GPIO_SPI_1_IOMUX_PICO_FUNC IOMUX_PINCM25_PF_SPI1_PICO +#define GPIO_SPI_1_POCI_PORT GPIOB +#define GPIO_SPI_1_POCI_PIN DL_GPIO_PIN_7 +#define GPIO_SPI_1_IOMUX_POCI (IOMUX_PINCM24) +#define GPIO_SPI_1_IOMUX_POCI_FUNC IOMUX_PINCM24_PF_SPI1_POCI +/* GPIO configuration for SPI_1 */ +#define GPIO_SPI_1_SCLK_PORT GPIOB +#define GPIO_SPI_1_SCLK_PIN DL_GPIO_PIN_9 +#define GPIO_SPI_1_IOMUX_SCLK (IOMUX_PINCM26) +#define GPIO_SPI_1_IOMUX_SCLK_FUNC IOMUX_PINCM26_PF_SPI1_SCLK +#define GPIO_SPI_1_CS0_PORT GPIOB +#define GPIO_SPI_1_CS0_PIN DL_GPIO_PIN_6 +#define GPIO_SPI_1_IOMUX_CS0 (IOMUX_PINCM23) +#define GPIO_SPI_1_IOMUX_CS0_FUNC IOMUX_PINCM23_PF_SPI1_CS0 +/* Defines for SPI_PERIPH */ +#define SPI_PERIPH_INST SPI0 +#define SPI_PERIPH_INST_IRQHandler SPI0_IRQHandler +#define SPI_PERIPH_INST_INT_IRQN SPI0_INT_IRQn +#define GPIO_SPI_PERIPH_PICO_PORT GPIOA +#define GPIO_SPI_PERIPH_PICO_PIN DL_GPIO_PIN_5 +#define GPIO_SPI_PERIPH_IOMUX_PICO (IOMUX_PINCM10) +#define GPIO_SPI_PERIPH_IOMUX_PICO_FUNC IOMUX_PINCM10_PF_SPI0_PICO +#define GPIO_SPI_PERIPH_POCI_PORT GPIOA +#define GPIO_SPI_PERIPH_POCI_PIN DL_GPIO_PIN_4 +#define GPIO_SPI_PERIPH_IOMUX_POCI (IOMUX_PINCM9) +#define GPIO_SPI_PERIPH_IOMUX_POCI_FUNC IOMUX_PINCM9_PF_SPI0_POCI +/* GPIO configuration for SPI_PERIPH */ +#define GPIO_SPI_PERIPH_SCLK_PORT GPIOA +#define GPIO_SPI_PERIPH_SCLK_PIN DL_GPIO_PIN_6 +#define GPIO_SPI_PERIPH_IOMUX_SCLK (IOMUX_PINCM11) +#define GPIO_SPI_PERIPH_IOMUX_SCLK_FUNC IOMUX_PINCM11_PF_SPI0_SCLK +#define GPIO_SPI_PERIPH_CS_PORT GPIOA +#define GPIO_SPI_PERIPH_CS_PIN DL_GPIO_PIN_2 +#define GPIO_SPI_PERIPH_IOMUX_CS (IOMUX_PINCM7) +#define GPIO_SPI_PERIPH_IOMUX_CS_FUNC IOMUX_PINCM7_PF_SPI0_CS0 + + + +/* Defines for DMA_CH_SPI1_RX */ +#define DMA_CH_SPI1_RX_CHAN_ID (3) +#define SPI_1_INST_DMA_TRIGGER_0 (DMA_SPI1_RX_TRIG) +/* Defines for DMA_CH_SPI1_TX */ +#define DMA_CH_SPI1_TX_CHAN_ID (2) +#define SPI_1_INST_DMA_TRIGGER_1 (DMA_SPI1_TX_TRIG) +/* Defines for DMA_CH_UART0_TX */ +#define DMA_CH_UART0_TX_CHAN_ID (1) +#define UART_0_INST_DMA_TRIGGER_0 (DMA_UART0_TX_TRIG) +/* Defines for DMA_CH_UART0_RX */ +#define DMA_CH_UART0_RX_CHAN_ID (0) +#define UART_0_INST_DMA_TRIGGER_1 (DMA_UART0_RX_TRIG) + + +/* Port definition for Pin Group GPIO_LEDS */ +#define GPIO_LEDS_PORT (GPIOA) + +/* Defines for USER_LED_1: GPIOA.26 with pinCMx 59 on package pin 46 */ +#define GPIO_LEDS_USER_LED_1_PIN (DL_GPIO_PIN_26) +#define GPIO_LEDS_USER_LED_1_IOMUX (IOMUX_PINCM59) +/* Defines for USER_LED_2: GPIOA.27 with pinCMx 60 on package pin 47 */ +#define GPIO_LEDS_USER_LED_2_PIN (DL_GPIO_PIN_27) +#define GPIO_LEDS_USER_LED_2_IOMUX (IOMUX_PINCM60) +/* Port definition for Pin Group GPIO_BTN */ +#define GPIO_BTN_PORT (GPIOA) + +/* Defines for PIN_0: GPIOA.7 with pinCMx 14 on package pin 13 */ +#define GPIO_BTN_PIN_0_PIN (DL_GPIO_PIN_7) +#define GPIO_BTN_PIN_0_IOMUX (IOMUX_PINCM14) + + +/* clang-format on */ + +void SYSCFG_DL_init(void); +void SYSCFG_DL_initPower(void); +void SYSCFG_DL_GPIO_init(void); +void SYSCFG_DL_SYSCTL_init(void); +void SYSCFG_DL_PWM_0_init(void); +void SYSCFG_DL_I2C_TARGET_init(void); +void SYSCFG_DL_I2C_0_init(void); +void SYSCFG_DL_UART_0_init(void); +void SYSCFG_DL_SPI_1_init(void); +void SYSCFG_DL_SPI_PERIPH_init(void); +void SYSCFG_DL_DMA_init(void); + + +bool SYSCFG_DL_saveConfiguration(void); +bool SYSCFG_DL_restoreConfiguration(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ti_msp_dl_config_h */ diff --git a/tests/test_cli.py b/tests/test_cli.py index a8dd492..1cba1d2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -6,6 +6,7 @@ import io import logging import os +import shutil import subprocess import sys import tempfile @@ -13,7 +14,7 @@ from pathlib import Path from unittest import mock -from fixtures import IOC, TestCase, run_libxr +from fixtures import DATA, IOC, TestCase, run_libxr from libxr import cli, update_notice @@ -42,7 +43,9 @@ def test_an_unrecognized_project_is_an_error(self): logs.output, [ f"ERROR:root:{self.root}: no supported platform recognized " - "(stm32: a directory with an STM32CubeMX .ioc file)" + "(stm32: a directory with an STM32CubeMX .ioc file; hpm: a directory with an " + "app.yaml and a .hpmpc under boards/; mspm0: a directory with a SysConfig " + ".syscfg file in its root)" ], ) @@ -95,7 +98,9 @@ def test_without_a_recorded_platform_gen_reads_the_project_directory(self): logs.output, [ "ERROR:root:.: no supported platform recognized " - "(stm32: a directory with an STM32CubeMX .ioc file)" + "(stm32: a directory with an STM32CubeMX .ioc file; hpm: a directory with an " + "app.yaml and a .hpmpc under boards/; mspm0: a directory with a SysConfig " + ".syscfg file in its root)" ], ) self.assertFalse(output.exists()) @@ -111,10 +116,39 @@ def test_an_unsupported_recorded_platform_is_an_error(self): logs.output, [ f"ERROR:root:{config}: platform 'zephyr' is not supported " - "(stm32: a directory with an STM32CubeMX .ioc file)" + "(stm32: a directory with an STM32CubeMX .ioc file; hpm: a directory with an " + "app.yaml and a .hpmpc under boards/; mspm0: a directory with a SysConfig " + ".syscfg file in its root)" ], ) + def test_an_hpm_project_is_detected(self): + project = self.root / "hpm" + (project / "boards" / "hpm5301evklite").mkdir(parents=True) + shutil.copy( + DATA / "hpm" / "hpm5301evklite.hpmpc", + project / "boards" / "hpm5301evklite" / "tool_config.hpmpc", + ) + (project / "app.yaml").write_text("dependency: []\n", encoding="utf-8") + (project / "main.c").write_text("int main(void) { init_bsp_pins(); }\n", encoding="utf-8") + self.assertEqual(run_libxr("parse", "-d", str(project))[0], 0) + self.assertEqual( + (project / ".config.yaml").read_text(encoding="utf-8").splitlines()[1], + "Platform: hpm", + ) + + def test_an_hpm_project_with_a_syscfg_is_still_hpm(self): + # 识别顺序与 libxr pins 和扩展一致:stm32、hpm、mspm0。 + # The order of recognition is that of libxr pins and the extension: stm32, hpm, mspm0. + self.test_an_hpm_project_is_detected() + project = self.root / "hpm" + (project / "board.syscfg").write_text("// a stray SysConfig file\n", encoding="utf-8") + self.assertEqual(run_libxr("parse", "-d", str(project))[0], 0) + self.assertEqual( + (project / ".config.yaml").read_text(encoding="utf-8").splitlines()[1], + "Platform: hpm", + ) + def test_a_missing_directory_is_an_error(self): config = self.root / "cubemx.yaml" config.write_text("Mcu: {}\n", encoding="utf-8") @@ -175,6 +209,8 @@ def test_every_option_reaches_its_argument(self): loud = {"verbose": True} setup = {"terminal": "", "commit": "", "git_source": "auto", "git_mirrors": ""} toolchain = {"run": "cmd_stm32_toolchain"} + pins = {"run": "cmd_pins"} + pins_default = {"package": None, "format": "yaml", "directory": None, "libxr_config": None} for argv, expected in ( (["parse"], {"directory": ".", "output": None, **quiet, "run": "cmd_parse"}), ( @@ -279,6 +315,32 @@ def test_every_option_reaches_its_argument(self): ["stm32", "flash-info", "STM32F103C8T6", "--verbose"], {"model": "STM32F103C8T6", **loud, "run": "cmd_stm32_flash_info"}, ), + ( + ["pins", "STM32H723VGT6"], + {**pins_default, "model": "STM32H723VGT6", **quiet, **pins}, + ), + ( + ["pins", "MSPM0G3507", "-p", "PM", "-f", "json", "--verbose"], + { + **pins_default, + "model": "MSPM0G3507", + "package": "PM", + "format": "json", + **loud, + **pins, + }, + ), + ( + ["pins", "-d", "p", "-c", "c.yaml"], + { + **pins_default, + "model": None, + "directory": "p", + "libxr_config": "c.yaml", + **quiet, + **pins, + }, + ), ( ["stm32", "toolchain", "gcc"], {"directory": ".", "compiler": "gcc", "std": None, **quiet, **toolchain}, @@ -295,6 +357,52 @@ def test_every_option_reaches_its_argument(self): ["stm32", "toolchain", "clang", "-p"], {"directory": ".", "compiler": "clang", "std": "picolibc", **quiet, **toolchain}, ), + ( + ["hpm", "setup"], + { + "directory": ".", + "output": "", + "xrobot": None, + "libxr_config": "", + **quiet, + "run": "cmd_hpm_setup", + }, + ), + ( + ["hpm", "setup", "-d", "p", "-o", "o.cpp", "--xrobot"] + + ["--libxr-config", "c.yaml", "--verbose"], + { + "directory": "p", + "output": "o.cpp", + "xrobot": True, + "libxr_config": "c.yaml", + **loud, + "run": "cmd_hpm_setup", + }, + ), + ( + ["mspm0", "setup"], + { + "directory": ".", + "output": "", + "xrobot": None, + "libxr_config": "", + **quiet, + "run": "cmd_mspm0_setup", + }, + ), + ( + ["mspm0", "setup", "-d", "p", "-o", "o.cpp", "--no-xrobot"] + + ["--libxr-config", "c.yaml", "--verbose"], + { + "directory": "p", + "output": "o.cpp", + "xrobot": False, + "libxr_config": "c.yaml", + **loud, + "run": "cmd_mspm0_setup", + }, + ), ): with self.subTest(argv=argv): self.assertEqual(self.parsed(*argv), expected) diff --git a/tests/test_config_cubemx_project.py b/tests/test_config_cubemx_project.py index 95fc8b5..2f46892 100644 --- a/tests/test_config_cubemx_project.py +++ b/tests/test_config_cubemx_project.py @@ -553,6 +553,29 @@ def test_the_next_steps_name_the_call_site_and_the_build(self): cubemx_cfg._report_next_steps(str(self.root)) self.assertEqual(logs.output, [build]) + def test_a_threadx_project_is_told_to_call_app_main_from_a_thread(self): + # 以前 ThreadX 工程也被告知在 main() 中调用 app_main(),而 main() 在启动调度器后不再返回。 + # A ThreadX project used to be told to call app_main() in main(), which does not + # return once it starts the scheduler. + sources = self.root / "Core" / "Src" + sources.mkdir() + (sources / "main.c").write_text("int main(void) { return 0; }\n", encoding="utf-8") + (sources / "app_threadx.c").write_text( + "UINT App_ThreadX_Init(VOID *memory_ptr) { return TX_SUCCESS; }\n", encoding="utf-8" + ) + with self.assertLogs(level="INFO") as logs: + cubemx_cfg._report_next_steps(str(self.root)) + self.assertEqual( + logs.output, + [ + "INFO:root:Next: in App_ThreadX_Init() (Core/Src/app_threadx.c), create a thread " + 'whose entry function includes "app_main.h" and calls app_main(), inside USER ' + "CODE sections, which CubeMX keeps when it regenerates the code. " + "App_ThreadX_Init() runs before the scheduler starts, so it cannot call " + "app_main() directly." + ], + ) + def test_an_xrobot_project_without_modules_gets_the_xrobot_steps(self): # 以前只给出构建命令,用户要连续失败两次才找到 xrobot init。 # Only the build command used to follow, and it took two failures to find xrobot init. @@ -571,10 +594,21 @@ def test_an_xrobot_project_without_modules_gets_the_xrobot_steps(self): with self.assertLogs(level="INFO") as logs: cubemx_cfg._report_next_steps(str(self.root), xrobot=True) self.assertEqual(logs.output, steps) + # 已有 Modules/modules.yaml:libxr 的 setup 不更新 xrobot_main.hpp,提醒运行 xrobot gen + # (用户 2026-10-07 决定,各平台统一)。 + # With Modules/modules.yaml: the libxr setup does not update xrobot_main.hpp, so it + # reminds to run xrobot gen (the user's decision of 2026-10-07, on every platform). (self.root / "Modules").mkdir() (self.root / "Modules" / "modules.yaml").write_text("modules: []\n", encoding="utf-8") - with self.assertNoLogs(level="INFO"): + with self.assertLogs(level="INFO") as logs: cubemx_cfg._report_next_steps(str(self.root), xrobot=True) + self.assertEqual( + logs.output, + [ + "INFO:root:Next: run `xrobot gen` to bring User/xrobot_main.hpp up to date; " + "libxr setup does not update it" + ], + ) (self.root / "Modules" / "modules.yaml").unlink() with self.assertNoLogs(level="INFO"): cubemx_cfg._report_next_steps(str(self.root), xrobot=False) diff --git a/tests/test_generator_code_hpm.py b/tests/test_generator_code_hpm.py new file mode 100644 index 0000000..3226c70 --- /dev/null +++ b/tests/test_generator_code_hpm.py @@ -0,0 +1,656 @@ +"""HPM 的解析与生成(libxr.peripheral_analyzer_hpm、libxr.generator_code_hpm):工程 YAML 的 +内容、生成的 app_main 源文件、重新生成时保留的用户代码,以及解析的两条提示(main.c 不调用 +pinmux 函数、两个活动函数选中同一个 pad)。 +Parsing and generating an HPM project (libxr.peripheral_analyzer_hpm, +libxr.generator_code_hpm): the project YAML, the generated app_main source, the user code kept +on regeneration, and the two parse notices (a main.c that calls no pinmux function, and two +active functions that select the same pad). + +测试固件是两个 HPM BSP 的真实工程文件:app.yaml、根目录 main.c 和 boards/ 下 Pinmux Tool 导出的 +.hpmpc。测试把固件复制进临时工程,在那里解析和生成。 +The fixtures are the real project files of the two HPM BSPs: app.yaml, the root main.c and the +.hpmpc under boards/ that the Pinmux Tool exported. A test copies the fixtures into a temporary +project and parses and generates there. +""" + +import json +import shutil +import tempfile +import textwrap +import unittest +from pathlib import Path + +import yaml +from fixtures import DATA, TestCase + +from libxr import generator_code_hpm as generator +from libxr.peripheral_analyzer_hpm import parse_project + + +def project_of(name: str) -> Path: + """把 name 的工程文件复制进一个临时工程:根目录的 app.yaml 和 main.c,boards/name/ 下的 + .hpmpc。返回工程目录,测试结束时删除。 + Copy the project files of name into a temporary project: the app.yaml and main.c in the + root and the .hpmpc under boards/name/. The directory is returned and removed at the end + of the test. + """ + root = Path(tempfile.mkdtemp(prefix="libxr-hpm-")) + shutil.copy(DATA / "hpm" / f"{name}_app.yaml", root / "app.yaml") + shutil.copy(DATA / "hpm" / f"{name}_main.c", root / "main.c") + board = root / "boards" / name + board.mkdir(parents=True) + shutil.copy(DATA / "hpm" / f"{name}.hpmpc", board / "tool_config.hpmpc") + return root + + +class Parsing(TestCase): + """libxr parse:工程 YAML 由 .hpmpc 和根目录 main.c 的调用组成。 + libxr parse: the project YAML comes from the .hpmpc and the calls of the root main.c. + """ + + def parse(self, name: str) -> dict: + """解析 name 的临时工程,返回写出的工程 YAML。 + Parse the temporary project of name and return the project YAML it wrote. + """ + root = project_of(name) + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + parse_project(str(root), summary=False) + return yaml.safe_load((root / ".config.yaml").read_text(encoding="utf-8")) + + def test_the_soc_the_package_and_the_active_functions_come_from_the_project(self): + data = self.parse("hpm5301evklite") + self.assertEqual(data["Platform"], "hpm") + self.assertEqual(data["Hpmpc"], "boards/hpm5301evklite/tool_config.hpmpc") + self.assertEqual(data["Mcu"], {"Family": "HPM", "Type": "HPM5301"}) + self.assertEqual(data["Package"], "QFN48") + self.assertEqual(data["MainFunctions"], ["init_bsp_pins"]) + + def test_gpio_pins_carry_the_pad_the_direction_and_the_pull(self): + gpio = self.parse("hpm5301evklite")["GPIO"] + self.assertEqual( + gpio["pa10"], + {"Pad": "PA10", "Port": "A", "Line": 10, "PadCtls": {}, "Direction": "OUTPUT"}, + ) + self.assertEqual(gpio["pa3"]["Direction"], "INPUT") + # KEY 引脚下拉:padCtls 使能下拉(PE=1,PS=0)。 + # The KEY pin pulls down: padCtls enables the pull-down (PE=1, PS=0). + self.assertEqual(gpio["pa3"]["PadCtls"]["PE"], "1") + self.assertEqual(gpio["pa3"]["PadCtls"]["PS"], "0") + + def test_peripherals_carry_the_i2c_the_pwm_channels_and_the_other_modules(self): + peripherals = self.parse("hpm5301evklite")["Peripherals"] + self.assertEqual(peripherals["I2C"]["I2C3"]["Pins"], {"SCL": "PB13", "SDA": "PB12"}) + # 5301 没有 PWM 外设(引脚数据里没有 PWM 信号):GPTMR 的比较器生成 PWM 对象。 + # A HPM5301 has no PWM peripheral (no PWM signal in its pin data): the GPTMR + # comparators generate PWM objects. + self.assertEqual(peripherals["PWM"]["GPTMR0"]["Channels"], [{"Index": 1, "Pad": "PB08"}]) + # UART 和 SPI 被认出但驱动还压着,只作展示。 + # UART and SPI are recognized but their drivers are held back; display only. + self.assertEqual(peripherals["Other"]["UART0"]["Module"], "UART") + self.assertEqual(peripherals["Other"]["SPI1"]["Pins"]["SCLK"], "PA27") + self.assertNotIn("GPIO", peripherals) + + def test_the_jtag_pins_of_a_conditional_function_stay_out(self): + # O2:rmcs 的 init_bsp_jtag_shared_pins 在 #if 里,PA07(KEY)不出现。5361 有 PWM + # 外设(引脚数据里有 PWM 信号),蜂鸣器和 IMU 加热器的 GPTMR 比较器归入 Other 只作 + # 展示。 + # O2: the init_bsp_jtag_shared_pins of rmcs sits in #if, so PA07 (the KEY) stays + # out. A HPM5361 has a PWM peripheral (its pin data carries PWM signals), so the + # GPTMR comparators of the buzzer and the IMU heater land in Other for display only. + data = self.parse("rmcs_slave_lite") + self.assertEqual(data["Mcu"], {"Family": "HPM", "Type": "HPM5361"}) + self.assertEqual(data["MainFunctions"], ["init_bsp_pins"]) + self.assertNotIn("pa7", data["GPIO"]) + self.assertEqual(data["GPIO"]["pb11"]["Direction"], "OUTPUT") + self.assertNotIn("PWM", data["Peripherals"]) + self.assertEqual(data["Peripherals"]["Other"]["GPTMR2"]["Pins"]["COMP_2"], "PA26") + + +class Generation(TestCase): + """libxr gen:生成的 app_main 源文件与设置,以及重新生成时保留的用户代码。 + libxr gen: the generated app_main source and settings, and the user code kept on + regeneration. + """ + + def setUp(self): + super().setUp() + generator.initialize_registry(False) + generator.reset_settings() + + def generate(self, name: str, use_xrobot: bool = True) -> tuple[Path, str]: + """解析 name 的临时工程并生成 app_main 源文件;工程里的 User/app_main.cpp 先放上审查过 + 的输出(重新生成的起点)。返回工程目录和重新生成的文本。 + Parse the temporary project of name and generate the app_main source; the reviewed + output is placed at User/app_main.cpp first, as the starting point of the + regeneration. The project directory and the regenerated text are returned. + """ + root = project_of(name) + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + parse_project(str(root), summary=False) + output = root / "User" / "app_main.cpp" + output.parent.mkdir() + reviewed = DATA / "hpm" / f"{name}_expected_app_main.cpp" + if use_xrobot: + shutil.copy(reviewed, output) + generator.generate(str(root / ".config.yaml"), str(output), use_xrobot, "") + return root, output.read_text(encoding="utf-8") + + def test_the_generated_source_matches_the_reviewed_output(self): + for name in ("hpm5301evklite", "rmcs_slave_lite"): + with self.subTest(name=name): + _root, code = self.generate(name) + expected = (DATA / "hpm" / f"{name}_expected_app_main.cpp").read_text( + encoding="utf-8" + ) + self.assertEqual(code, expected) + + def test_the_written_settings_hold_the_defaults_the_generated_code_uses(self): + root, code = self.generate("hpm5301evklite") + settings = yaml.safe_load((root / "User" / "libxr_config.yaml").read_text(encoding="utf-8")) + self.assertEqual(settings["I2C"]["i2c3"]["speed"], 100000) + self.assertIn("frequency", settings["PWM"]["pwm_gptmr0_ch1"]) + self.assertNotIn("USART", settings) + self.assertNotIn("SYSTEM", settings) + # 速率按设置写出,重新生成只写有变化的文件之外,文本保持不变。 + # The speed follows the setting; regenerating the same project writes the same text. + self.assertIn("static HPMI2C i2c3(HPM_I2C3, clock_i2c3, {100000U});", code) + + def test_a_configured_frequency_and_gpio_renames_are_applied(self): + root, _code = self.generate("hpm5301evklite") + config = root / "User" / "libxr_config.yaml" + settings = yaml.safe_load(config.read_text(encoding="utf-8")) + settings["PWM"]["pwm_gptmr0_ch1"]["frequency"] = 1000 + settings["GPIO"] = {"pa10": "LED", "pa3": "KEY"} + config.write_text(yaml.dump(settings), encoding="utf-8") + generator.generate( + str(root / ".config.yaml"), str(root / "User" / "app_main.cpp"), True, "" + ) + code = (root / "User" / "app_main.cpp").read_text(encoding="utf-8") + self.assertIn("pwm_gptmr0_ch1.SetConfig({1000});", code) + self.assertIn("static HPMGPIO LED(HPM_GPIO0, GPIO_DI_GPIOA, 10, IRQn_GPIO0_A);", code) + self.assertIn("XR_REGISTER(KEY, LibXR::GPIO);", code) + self.assertNotIn("pa10", code) + self.assertNotIn("pa3", code) + + def test_a_null_gpio_rename_keeps_the_default_name(self): + # 设置树里清空改名写成 null:对象名退回引脚名,而不是 "None"。 + # Clearing a rename in the settings tree writes null: the object name falls back to the + # pin name instead of "None". + root, _code = self.generate("hpm5301evklite") + config = root / "User" / "libxr_config.yaml" + settings = yaml.safe_load(config.read_text(encoding="utf-8")) + settings["GPIO"] = {"pa10": None, "pa3": "KEY"} + config.write_text(yaml.dump(settings), encoding="utf-8") + generator.generate( + str(root / ".config.yaml"), str(root / "User" / "app_main.cpp"), True, "" + ) + code = (root / "User" / "app_main.cpp").read_text(encoding="utf-8") + self.assertIn("static HPMGPIO pa10(HPM_GPIO0, GPIO_DI_GPIOA, 10, IRQn_GPIO0_A);", code) + self.assertIn("XR_REGISTER(pa10, LibXR::GPIO);", code) + + def test_the_gptmr_pwm_object_is_generated_and_registered(self): + # 5301 没有 PWM 外设:GPTMR 的比较器走 HPMPWM 的 fallback 路径。对象和登记都不能包 + # #if——xrobot 的生成器无法判断编译选项,guard 里的 XR_REGISTER 会被它拒绝。 + # A HPM5301 has no PWM peripheral: the GPTMR comparators take the HPMPWM fallback + # path. Neither the object nor the registration may sit inside #if — xrobot's + # generator cannot evaluate build options and rejects an XR_REGISTER there. + _root, code = self.generate("hpm5301evklite") + self.assertNotIn("#if", code) + self.assertIn("static HPMPWM pwm_gptmr0_ch1(", code) + self.assertIn("XR_REGISTER(pwm_gptmr0_ch1, LibXR::PWM);", code) + + def test_a_soc_with_a_pwm_peripheral_generates_no_gptmr_object(self): + # 5361 有 PWM 外设:蜂鸣器和加热器的 GPTMR 比较器只作展示,不生成对象也不登记。 + # A HPM5361 has a PWM peripheral: the GPTMR comparators of the buzzer and the + # heater stay display-only, no object and no registration. + _root, code = self.generate("rmcs_slave_lite") + self.assertNotIn("HPMPWM", code) + self.assertNotIn("hpm_pwm.hpp", code) + self.assertIn('#include "hpm_gpio.hpp"', code) + + def test_the_user_code_of_an_existing_file_is_kept(self): + root, _code = self.generate("hpm5301evklite") + output = root / "User" / "app_main.cpp" + code = output.read_text(encoding="utf-8") + code = code.replace( + "/* User Code Begin 1 */", + "/* User Code Begin 1 */\n// A user note that must survive regeneration.", + ) + output.write_text(code, encoding="utf-8") + generator.generate(str(root / ".config.yaml"), str(output), True, "") + self.assertIn( + "// A user note that must survive regeneration.", + output.read_text(encoding="utf-8"), + ) + + def test_without_xrobot_the_entry_sleeps_and_registers_nothing(self): + _root, code = self.generate("hpm5301evklite", use_xrobot=False) + self.assertNotIn("XR_REGISTER", code) + self.assertNotIn("xrobot_main.hpp", code) + self.assertIn("Thread::Sleep(UINT32_MAX);", code) + self.assertNotIn("XROBOT_MAIN();", code) + # 对象照旧生成。 + # The objects are still generated. + self.assertIn("static HPMPWM pwm_gptmr0_ch1(", code) + + def test_power_manager_is_generated_always_and_registered_only_with_xrobot(self): + # power_manager 对象总是生成,登记只在使用 XRobot 时;GPIO 不能改叫 power_manager。 + # The power_manager object is always generated; it is registered only with XRobot, and + # a GPIO cannot be renamed to power_manager. + root, code = self.generate("hpm5301evklite", use_xrobot=False) + self.assertIn('#include "hpm_power.hpp"', code) + self.assertIn("static HPMPowerManager power_manager;", code) + self.assertNotIn("XR_REGISTER", code) + output = root / "User" / "app_main.cpp" + generator.generate(str(root / ".config.yaml"), str(output), True, "") + self.assertIn( + "XR_REGISTER(power_manager, LibXR::PowerManager);", + output.read_text(encoding="utf-8"), + ) + (root / "User" / "libxr_config.yaml").write_text( + "GPIO:\n pa10: power_manager\n", encoding="utf-8" + ) + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + generator.generate(str(root / ".config.yaml"), str(output), True, "") + self.assertIn("a name of the HPM SDK or of the generated code", "\n".join(logs.output)) + + +class Notices(TestCase): + """解析的两条提示(P3 遗留):main.c 不调用 pinmux 函数,两个活动函数选中同一个 pad。 + The two parse notices (the P3 leftovers): a main.c that calls no pinmux function, and two + active functions that select the same pad. + """ + + HPMPC = json.dumps( + { + "content": { + "info": {"socName": "HPM5301", "packageName": "QFN48"}, + "pinmux": { + "functions": { + "init_bsp_pins": { + "selectPins": {"PA10": {"signal": "GPIO.A.A[10]", "padCtls": {}}} + }, + "init_bsp_more_pins": { + "selectPins": {"PA10": {"signal": "SPI3.A.CS[0]", "padCtls": {}}} + }, + } + }, + } + } + ) + + def parsed(self, main_c: str, hpmpc: str) -> dict: + """合成工程(给定的 main.c 和 .hpmpc)的工程 YAML。 + The project YAML of a synthetic project (the given main.c and .hpmpc). + """ + root = Path(tempfile.mkdtemp(prefix="libxr-hpm-notice-")) + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + (root / "app.yaml").write_text("dependency: []\n", encoding="utf-8") + board = root / "boards" / "board" + board.mkdir(parents=True) + (board / "tool_config.hpmpc").write_text(hpmpc, encoding="utf-8") + (root / "main.c").write_text(main_c, encoding="utf-8") + parse_project(str(root), summary=False) + return yaml.safe_load((root / ".config.yaml").read_text(encoding="utf-8")) + + def test_two_active_functions_on_one_pad_are_reported(self): + main_c = textwrap.dedent( + """\ + int main(void) + { + init_bsp_pins(); + init_bsp_more_pins(); + } + """ + ) + with self.assertLogs(level="WARNING") as logs: + data = self.parsed(main_c, self.HPMPC) + self.assertEqual(len(logs.output), 1) + # 路径是绝对临时目录,Windows 下分隔符是反斜杠;只核对路径之后的部分。 + # The path is an absolute temporary directory whose separator is a backslash on + # Windows; check the part after the path only. + self.assertTrue( + logs.output[0].endswith( + ": PA10 is selected by both init_bsp_pins (GPIO.A.A[10]) and " + "init_bsp_more_pins (SPI3.A.CS[0]); the later one wins" + ) + ) + self.assertEqual(data["MainFunctions"], ["init_bsp_pins", "init_bsp_more_pins"]) + # 后一个的信号生效:PA10 归 SPI3,不再是 GPIO。 + # The later signal wins: PA10 belongs to the SPI3 now, not to the GPIO. + self.assertNotIn("pa10", data["GPIO"]) + self.assertEqual(data["Peripherals"]["Other"]["SPI3"]["Pins"]["CS_0"], "PA10") + + def test_a_main_c_that_calls_no_pinmux_function_is_a_warning(self): + main_c = textwrap.dedent( + """\ + int main(void) + { + board_init(); + } + """ + ) + with self.assertLogs(level="WARNING") as logs: + data = self.parsed(main_c, self.HPMPC) + self.assertEqual(len(logs.output), 1) + self.assertIn("calls no pinmux function (using the init_bsp_pins fallback)", logs.output[0]) + # 退回 init_bsp_pins:引脚照读。 + # The init_bsp_pins fallback: the pins are read as usual. + self.assertEqual(data["MainFunctions"], ["init_bsp_pins"]) + self.assertEqual(data["GPIO"]["pa10"]["Pad"], "PA10") + + def test_without_init_bsp_pins_no_pin_is_read(self): + hpmpc = json.dumps( + { + "content": { + "info": {"socName": "HPM5301", "packageName": "QFN48"}, + "pinmux": { + "functions": { + "init_bsp_extra_pins": { + "selectPins": {"PA10": {"signal": "GPIO.A.A[10]", "padCtls": {}}} + } + } + }, + } + } + ) + main_c = "int main(void) { board_init(); }\n" + with self.assertLogs(level="WARNING") as logs: + data = self.parsed(main_c, hpmpc) + self.assertEqual(len(logs.output), 2) + self.assertIn("no init_bsp_pins exists, so no pin is read", logs.output[0]) + self.assertIn("select no GPIO or peripheral pin", logs.output[1]) + self.assertEqual(data["MainFunctions"], []) + self.assertEqual(data["GPIO"], {}) + + +def synthetic_project( + selections: dict, + soc: str = "HPM5301", + gpiom: dict | None = None, + main_c: str = "int main(void)\n{\n init_bsp_pins();\n}\n", +) -> Path: + """一个合成的 HPM 工程:init_bsp_pins 选中 selections(pad -> 工具的信号名),main 调用它。 + 返回工程目录。 + A synthetic HPM project: init_bsp_pins selects selections (pad -> the tool's signal name) + and main calls it. The project directory is returned. + """ + root = Path(tempfile.mkdtemp(prefix="libxr-hpm-review-")) + (root / "app.yaml").write_text("dependency: []\n", encoding="utf-8") + board = root / "boards" / "board" + board.mkdir(parents=True) + function = { + "selectPins": { + pad: {"signal": signal, "padCtls": {}} for pad, signal in selections.items() + }, + "managers": {"gpiom": gpiom or {}}, + } + hpmpc = { + "content": { + "info": {"socName": soc, "packageName": "QFN48"}, + "pinmux": {"functions": {"init_bsp_pins": function}}, + } + } + (board / "tool_config.hpmpc").write_text(json.dumps(hpmpc), encoding="utf-8") + (root / "main.c").write_text(main_c, encoding="utf-8") + return root + + +class ParseChecks(TestCase): + """解析的核对:SoC、焊盘和信号按引脚数据核对,生成前的外设一致性检查。 + The checks of the parse: the SoC, the pads and the signals against the pin data, and the + consistency of the peripherals before generation. + """ + + def parse(self, root: Path) -> dict: + """解析 root 并返回工程 YAML;测试结束时删除 root。 + Parse root and return the project YAML; root is removed at the end of the test. + """ + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + parse_project(str(root), summary=False) + return yaml.safe_load((root / ".config.yaml").read_text(encoding="utf-8")) + + def test_an_unknown_soc_is_an_error(self): + root = synthetic_project({"PA10": "GPIO.A.A[10]"}, soc="HPM9999") + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + parse_project(str(root), summary=False) + self.assertIn("Unknown HPM SoC: HPM9999", logs.output[0]) + + def test_a_lower_case_soc_is_the_same_soc(self): + data = self.parse(synthetic_project({"PA10": "GPIO.A.A[10]"}, soc="hpm5301")) + self.assertEqual(data["Mcu"]["Type"], "HPM5301") + + def test_a_pad_off_the_package_and_a_signal_the_pad_cannot_carry_are_warned_about(self): + with self.assertLogs(level="WARNING") as logs: + data = self.parse(synthetic_project({"PA12": "GPIO.A.A[12]", "PA10": "UART0.A.TXD"})) + self.assertTrue( + any("PA12 (GPIO.A.A[12]) is not a pin of HPM5301 QFN48" in line for line in logs.output) + ) + self.assertTrue(any("PA10 cannot carry UART0_TXD" in line for line in logs.output)) + self.assertNotIn("pa12", data["GPIO"]) + + def test_an_i2c_without_sda_generates_nothing(self): + with self.assertLogs(level="WARNING") as logs: + data = self.parse(synthetic_project({"PB13": "I2C3.C.SCL"})) + self.assertNotIn("I2C", data["Peripherals"]) + self.assertEqual(data["Peripherals"]["Other"]["I2C3"]["Pins"], {"SCL": "PB13"}) + self.assertTrue(any("I2C3 has no SDA pin" in line for line in logs.output)) + + def test_a_gptmr_with_capture_only_is_no_pwm(self): + data = self.parse(synthetic_project({"PB09": "GPTMR0.B.CAPT[1]"})) + self.assertNotIn("PWM", data["Peripherals"]) + self.assertEqual(data["Peripherals"]["Other"]["GPTMR0"]["Pins"], {"CAPT_1": "PB09"}) + + def test_a_gptmr_with_capture_and_compare_warns_about_the_capture(self): + with self.assertLogs(level="WARNING") as logs: + data = self.parse( + synthetic_project({"PB08": "GPTMR0.B.COMP[1]", "PB09": "GPTMR0.B.CAPT[1]"}) + ) + self.assertEqual( + data["Peripherals"]["PWM"]["GPTMR0"]["Channels"], [{"Index": 1, "Pad": "PB08"}] + ) + self.assertTrue(any("GPTMR0 also selects CAPT_1" in line for line in logs.output)) + + def test_a_pin_of_another_gpio_controller_gets_no_object(self): + with self.assertLogs(level="WARNING") as logs: + data = self.parse( + synthetic_project( + {"PA10": "GPIO.A.A[10]", "PA03": "GPIO.A.A[03]"}, + gpiom={"PA10": {"direction": "1", "gpioController": "2"}}, + ) + ) + self.assertEqual(list(data["GPIO"]), ["pa3"]) + self.assertTrue( + any("PA10 is assigned to GPIO controller 2" in line for line in logs.output) + ) + + def test_no_fallback_when_every_call_is_conditional(self): + # 审查 C2:与 libxr pins 一样,调用全在 #if 里时不退回 init_bsp_pins。 + # Review C2: as in libxr pins, no init_bsp_pins fallback when every call is in #if. + main_c = "int main(void)\n{\n#if USE_PINS\n init_bsp_pins();\n#endif\n}\n" + with self.assertLogs(level="WARNING") as logs: + data = self.parse(synthetic_project({"PA10": "GPIO.A.A[10]"}, main_c=main_c)) + self.assertEqual((data["MainFunctions"], data["GPIO"]), ([], {})) + self.assertTrue(any("only inside preprocessor conditions" in line for line in logs.output)) + self.assertFalse(any("fallback" in line for line in logs.output)) + + def test_a_pinmux_function_main_does_not_call_is_warned_about(self): + root = synthetic_project({"PA10": "GPIO.A.A[10]"}) + hpmpc = root / "boards" / "board" / "tool_config.hpmpc" + document = json.loads(hpmpc.read_text(encoding="utf-8")) + document["content"]["pinmux"]["functions"]["init_unused_pins"] = { + "selectPins": {"PA03": {"signal": "GPIO.A.A[03]", "padCtls": {}}} + } + hpmpc.write_text(json.dumps(document), encoding="utf-8") + with self.assertLogs(level="WARNING") as logs: + data = self.parse(root) + self.assertEqual(list(data["GPIO"]), ["pa10"]) + self.assertTrue( + any("init_unused_pins() is not called by main" in line for line in logs.output) + ) + + +class GenerationChecks(TestCase): + """生成的核对:设置的类型、名字和大小写,改名表和终端设置的写法。 + The checks of generation: the type, names and case of the settings, and how the rename + table and the terminal settings are written. + """ + + def setUp(self): + super().setUp() + generator.initialize_registry(False) + generator.reset_settings() + + def generate(self, config: str | None = None, root: Path | None = None) -> tuple[Path, str]: + """解析 hpm5301evklite 的临时工程(或 root),按 config(libxr_config.yaml 的内容)生成。 + 返回工程目录和生成的 app_main。 + Parse the temporary project of hpm5301evklite (or root) and generate with config (the + content of libxr_config.yaml). The project directory and the generated app_main are + returned. + """ + if root is None: + root = project_of("hpm5301evklite") + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + parse_project(str(root), summary=False) + output = root / "User" / "app_main.cpp" + output.parent.mkdir(exist_ok=True) + if config is not None: + (root / "User" / "libxr_config.yaml").write_text(config, encoding="utf-8") + generator.generate(str(root / ".config.yaml"), str(output), True, "") + return root, output.read_text(encoding="utf-8") + + def settings(self, root: Path) -> dict: + """写出的 libxr_config.yaml。 + The libxr_config.yaml that was written. + """ + return yaml.safe_load((root / "User" / "libxr_config.yaml").read_text(encoding="utf-8")) + + def assertGenerationFails(self, config: str, *messages: str): + """按 config 生成时报错退出,错误信息含 messages。 + Generation with config logs an error that holds messages and exits. + """ + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + self.generate(config) + for message in messages: + self.assertIn(message, "\n".join(logs.output)) + + def test_the_rename_table_lists_every_pin_and_no_terminal_is_written(self): + root, _code = self.generate() + settings = self.settings(root) + self.assertEqual(settings["GPIO"], {"pa10": None, "pa3": None}) + self.assertNotIn("Terminal", settings) + self.assertNotIn("terminal_source", settings) + + def test_a_stale_null_rename_goes_and_a_stale_name_stays(self): + root, _code = self.generate("GPIO:\n pa10: LED\n pb1:\n pb2: OLD\n") + self.assertEqual(self.settings(root)["GPIO"], {"pa10": "LED", "pb2": "OLD", "pa3": None}) + + def test_a_written_terminal_source_is_kept_and_warned_about(self): + with self.assertLogs(level="WARNING") as logs: + root, _code = self.generate("terminal_source: uart0\n") + self.assertEqual(self.settings(root)["terminal_source"], "uart0") + self.assertTrue( + any("terminal_source 'uart0' has no effect" in line for line in logs.output) + ) + + def test_unusable_gpio_names_are_listed_together(self): + self.assertGenerationFails( + "GPIO:\n pa10: int\n pa3: HPM_GPIO0\n", + "GPIO.pa10 'int': a C++ keyword", + "GPIO.pa3 'HPM_GPIO0': a name of the HPM SDK or of the generated code", + ) + for name, problem in ( + ("timebase", "a name of the HPM SDK or of the generated code"), + ("i2c3", "already the name of a I2C object"), + ("__led", "a reserved C++ identifier"), + ("1led", "not a valid C++ identifier"), + ): + with self.subTest(name=name): + generator.initialize_registry(False) + generator.reset_settings() + self.assertGenerationFails(f"GPIO:\n pa10: '{name}'\n", problem) + + def test_sdk_macros_and_generated_names_are_refused_and_plain_names_pass(self): + # 审查 C1:类函数宏和生成代码用到的名字(NORMAL 是 PWM 的极性)不能用;gpio_led 可以。 + # Review C1: function-like macros and names the generated code uses (NORMAL is the + # polarity of the PWM) are refused; gpio_led is fine. + self.assertGenerationFails("GPIO:\n pa10: MAX\n", "GPIO.pa10 'MAX'") + generator.initialize_registry(False) + generator.reset_settings() + self.assertGenerationFails( + "GPIO:\n pa10: NORMAL\n", + "the generated code uses the names for something else: NORMAL", + ) + generator.initialize_registry(False) + generator.reset_settings() + _root, code = self.generate("GPIO:\n pa10: gpio_led\n pa3: board_key\n") + self.assertIn("static HPMGPIO gpio_led(", code) + + def test_a_null_or_literal_speed_is_read_as_on_the_other_platforms(self): + # 审查 C3:null 用默认值,字符串按整数字面量读。 + # Review C3: null takes the default, a string is read as an integer literal. + _root, code = self.generate("I2C:\n i2c3:\n speed:\n") + self.assertIn("{100000U}", code) + generator.initialize_registry(False) + generator.reset_settings() + _root, code = self.generate("I2C:\n i2c3:\n speed: '0x61A80'\n") + self.assertIn("{400000U}", code) + + def test_a_gpio_rename_must_be_a_name(self): + self.assertGenerationFails("GPIO:\n pa10: 5\n", "GPIO.pa10 5 is not a name") + + def test_a_pwm_frequency_must_be_a_positive_integer(self): + for value in ("0", "-5", "abc", "1.5", "true"): + with self.subTest(value=value): + generator.initialize_registry(False) + generator.reset_settings() + self.assertGenerationFails( + f"PWM:\n pwm_gptmr0_ch1:\n frequency: {value}\n", + "PWM.pwm_gptmr0_ch1.frequency", + "is not a positive integer", + ) + + def test_the_key_of_a_setting_is_found_in_any_case(self): + # 文件写 I2C3、PWM_GPTMR0_CH1、PA10:生成与 libxr pins 一样读到这些段,不另写一份小写的。 + # The file writes I2C3, PWM_GPTMR0_CH1 and PA10: generation reads these entries as libxr + # pins does, and writes no second, lower-case copy. + root, code = self.generate( + "I2C:\n I2C3:\n speed: 400000\n" + "PWM:\n PWM_GPTMR0_CH1:\n frequency: 1000\n" + "GPIO:\n PA10: LED\n" + ) + self.assertIn("static HPMI2C i2c3(HPM_I2C3, clock_i2c3, {400000U});", code) + self.assertIn("pwm_gptmr0_ch1.SetConfig({1000});", code) + self.assertIn("static HPMGPIO LED(", code) + settings = self.settings(root) + self.assertEqual(list(settings["I2C"]), ["I2C3"]) + self.assertEqual(list(settings["PWM"]), ["PWM_GPTMR0_CH1"]) + self.assertEqual(settings["GPIO"], {"PA10": "LED", "pa3": None}) + + def test_an_unknown_soc_in_the_project_yaml_is_an_error(self): + root = project_of("hpm5301evklite") + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + parse_project(str(root), summary=False) + config = root / ".config.yaml" + config.write_text( + config.read_text(encoding="utf-8").replace("Type: HPM5301", "Type: HPM9999"), + encoding="utf-8", + ) + (root / "User").mkdir() + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + generator.generate(str(config), str(root / "User" / "app_main.cpp"), True, "") + self.assertIn("Unknown HPM SoC: HPM9999", logs.output[0]) + + def test_the_notice_says_when_main_calls_no_pinmux_function(self): + root = synthetic_project( + {"PA10": "GPIO.A.A[10]"}, main_c="int main(void)\n{\n board_init();\n}\n" + ) + with self.assertLogs(level="WARNING"): + _root, code = self.generate(root=root) + notice = " ".join(line.removeprefix("//").strip() for line in code.splitlines()[:6]) + self.assertIn("defines the pins in init_bsp_pins(), which main.c does not call", notice) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_generator_code_mspm0.py b/tests/test_generator_code_mspm0.py new file mode 100644 index 0000000..4964e85 --- /dev/null +++ b/tests/test_generator_code_mspm0.py @@ -0,0 +1,993 @@ +"""MSPM0 的解析与生成(libxr.peripheral_analyzer_mspm0、libxr.generator_code_mspm0):工程 +YAML 的内容、生成的 app_main 源文件,以及重新生成时保留的用户代码。 +Parsing and generating an MSPM0 project (libxr.peripheral_analyzer_mspm0, +libxr.generator_code_mspm0): the project YAML, the generated app_main source, and the user +code kept on regeneration. + +测试固件是两个 BSP 的真实工程文件:.syscfg 加上 SysConfig 生成的 ti_msp_dl_config.h/.c(CI 里 +没有 SysConfig,输出随工程一起提交),以及一个覆盖 BSP 没用到的 SysConfig 写法的 variants 工程 +(SPI 用 MFCLK 分频和 RX timeout DMA 触发、外设模式的 SPI、只有目标模式的 I2C、默认实例名且 +引脚只有建议值的 PWM、端口加引脚号的 GPIO、默认名 PIN_0 的引脚、收发都有 DMA 的 UART),它的 +输出同样由 SysConfig 1.28.1 生成,以及 TI SDK 的 uart_tx_multibyte_fifo_dma_interrupts 例程 +(一个实例只有一个 DMA 触发,输出的触发宏不带编号)。测试把固件复制进临时工程,使生成输出的 +修改时间比 .syscfg 新,解析直接复用它;测试期间不设置 SYSCONFIG_TOOL 和 MSPM0_SDK_INSTALL_DIR。 +The fixtures are the real project files of the two BSPs: the .syscfg plus the +ti_msp_dl_config.h/.c that SysConfig generated (CI has no SysConfig, so the output is committed +with the project), a variants project with the SysConfig setups the BSPs do not use (an SPI +on MFCLK with a divider and an RX timeout DMA trigger, an SPI in peripheral mode, an I2C in +target mode only, a PWM with the default instance name and only suggested pins, GPIO pins given +as port plus pin number, a pin with the default name PIN_0, a UART with DMA on both sides), +whose output SysConfig 1.28.1 generated as well, and the uart_tx_multibyte_fifo_dma_interrupts +example of the TI SDK (an instance with a single DMA trigger, so its trigger macro carries no +number). A test copies the fixtures into a temporary project, so the generated output is newer +than the .syscfg and parsing reuses it; +SYSCONFIG_TOOL and MSPM0_SDK_INSTALL_DIR are unset during the tests. +""" + +import contextlib +import io +import os +import shutil +import subprocess +import tempfile +from pathlib import Path +from unittest import mock + +import yaml +from fixtures import DATA, TestCase, run_libxr + +from libxr import generator_code_mspm0 as generator +from libxr import peripheral_analyzer_mspm0 as analyzer +from libxr.peripheral_analyzer_mspm0 import parse_project + +# 测试期间清掉的 SysConfig 环境变量:设置了它们时解析会运行 SysConfig,而不是复用固件。 +# The SysConfig environment variables cleared during the tests: with them set, parsing runs +# SysConfig instead of reusing the fixtures. +SYSCONFIG_VARIABLES = ("SYSCONFIG_TOOL", "MSPM0_SDK_INSTALL_DIR") + + +def bsp(name: str) -> str: + """g3507 这样的短名对应的固件 .syscfg 文件名。 + The fixture .syscfg file name of a short name such as g3507. + """ + return { + "g3507": "mspm0g3507_minidb48.syscfg", + "g3519": "mspm0g3519_minidb48.syscfg", + "variants": "mspm0_variants.syscfg", + "uart_tx_dma": "uart_tx_multibyte_fifo_dma_interrupts.syscfg", + }[name] + + +def project_of(name: str, edit=None, build: str = "build") -> Path: + """把 name 的固件复制进一个临时工程:根目录的 .syscfg 加上 /test/syscfg 里的 + SysConfig 输出(修改时间更新);返回工程目录。edit 给出时用它改写 .syscfg 和两个输出文件的 + 文本(edit(文件名, 文本) -> 文本)。 + Copy the fixtures of name into a temporary project: the .syscfg in the root plus the + SysConfig output in /test/syscfg (with a newer modification time); the project + directory is returned. With edit, the text of the .syscfg and of both output files is + rewritten through it (edit(file name, text) -> text). + """ + root = Path(tempfile.mkdtemp(prefix="libxr-mspm0-")) + output = root / build / "test" / "syscfg" + output.mkdir(parents=True) + copies = {f"{name}.syscfg": root / bsp(name)} + for suffix in ("ti_msp_dl_config.h", "ti_msp_dl_config.c"): + copies[f"{name}_{suffix}"] = output / suffix + # .syscfg 先写,输出后写,使输出更新。 + # The .syscfg is written first and the output after it, so the output is newer. + for source, target in copies.items(): + text = (DATA / "mspm0" / source).read_bytes().decode("utf-8") + if edit is not None: + text = edit(target.name, text) + target.write_bytes(text.encode("utf-8")) + for target in list(copies.values())[1:]: + os.utime(target) + return root + + +class MSPM0TestCase(TestCase): + """清掉 SysConfig 环境变量、并在测试结束时删除临时工程的基类。 + A base class that clears the SysConfig environment variables and removes the temporary + projects at the end of the test. + """ + + def setUp(self): + super().setUp() + patch = mock.patch.dict(os.environ) + patch.start() + self.addCleanup(patch.stop) + for variable in SYSCONFIG_VARIABLES: + os.environ.pop(variable, None) + + def project(self, name: str, edit=None, build: str = "build") -> Path: + """project_of() 的临时工程,测试结束时删除。 + The temporary project of project_of(), removed at the end of the test. + """ + root = project_of(name, edit, build) + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + return root + + def parse(self, name: str, edit=None) -> dict: + """解析 name 的临时工程,返回写出的工程 YAML。 + Parse the temporary project of name and return the project YAML it wrote. + """ + root = self.project(name, edit) + parse_project(str(root), summary=False) + return yaml.safe_load((root / ".config.yaml").read_text(encoding="utf-8")) + + +class Parsing(MSPM0TestCase): + """libxr parse:工程 YAML 由 .syscfg 和 SysConfig 输出组成。 + libxr parse: the project YAML comes from the .syscfg and the SysConfig output. + """ + + def test_the_platform_the_device_and_the_clocks_come_from_the_project(self): + data = self.parse("g3507") + self.assertEqual(data["Platform"], "mspm0") + self.assertEqual(data["Syscfg"], "mspm0g3507_minidb48.syscfg") + self.assertEqual(data["Mcu"], {"Family": "MSPM0", "Type": "MSPM0G3507"}) + self.assertEqual(data["CPUCLK"], 80000000) + self.assertEqual(data["ULPCLKDivider"], 2) + + def test_gpio_pins_carry_the_macros_the_output_actually_emits(self): + gpio = self.parse("g3507")["GPIO"] + self.assertEqual( + gpio["LED1"], + { + "Group": "GPIO_LEDS", + "Pin": "PB8", + "Port": "GPIO_LEDS_PIN_LED1_PORT", + "PinMacro": "GPIO_LEDS_PIN_LED1_PIN", + "IOMUX": "GPIO_LEDS_PIN_LED1_IOMUX", + "Direction": "OUTPUT", + "Resistor": "NONE", + }, + ) + self.assertEqual(gpio["KEY1"]["Port"], "GPIO_KEYS_PORT") + self.assertEqual(gpio["KEY1"]["Direction"], "INPUT") + self.assertEqual(gpio["KEY1"]["Interrupt"], "FALL") + + def test_peripherals_carry_the_dma_channels_the_power_domain_and_the_pwm_channels(self): + peripherals = self.parse("g3507")["Peripherals"] + self.assertEqual( + peripherals["UART"]["UART_0"], + { + "Peripheral": "UART0", + "DMA_TX": "DMA_CH_UART0_TX", + "Pins": {"rxPin": "PA11", "txPin": "PA10"}, + "PowerDomain": 0, + }, + ) + self.assertEqual(peripherals["SPI"]["SPI_1"]["PowerDomain"], 1) + self.assertEqual(peripherals["SPI"]["SPI_1"]["DMA_RX"], "DMA_CH_SPI1_RX") + self.assertEqual(peripherals["PWM"]["PWM_TIMA1"]["Channels"], [0, 1]) + self.assertNotIn("GPIO", peripherals) + + def test_ignored_peripherals_are_kept_for_display(self): + peripherals = self.parse("g3507")["Peripherals"] + self.assertEqual(peripherals["Other"]["MCAN_0"], {"Module": "MCAN", "Peripheral": "CANFD0"}) + + def test_the_second_uart_of_the_other_board_is_parsed_as_well(self): + peripherals = self.parse("g3519")["Peripherals"] + self.assertEqual(peripherals["UART"]["UART_7"]["DMA_TX"], "DMA_CH_UART7_TX") + self.assertEqual(peripherals["UART"]["UART_7"]["PowerDomain"], 0) + + def test_a_single_dma_trigger_of_the_output_names_the_channel(self): + # TI 的 uart_tx_multibyte_fifo_dma_interrupts 例程:.syscfg 只写了 + # DMA_CHANNEL_TX.peripheral.$assign,输出里的触发宏只有一个,因而不带编号 + # (UART_0_INST_DMA_TRIGGER)。 + # TI's uart_tx_multibyte_fifo_dma_interrupts example: the .syscfg names only + # DMA_CHANNEL_TX.peripheral.$assign, and the output holds a single trigger macro, + # which therefore carries no number (UART_0_INST_DMA_TRIGGER). + uart = self.parse("uart_tx_dma")["Peripherals"]["UART"]["UART_0"] + self.assertEqual(uart["DMA_TX"], "DMA_CH0") + self.assertEqual(uart["Peripheral"], "UART0") + + +class ParsingVariants(MSPM0TestCase): + """libxr parse 对 BSP 没用到的 SysConfig 写法(variants 工程,审查 2026-10-07 的 H1-H6、 + L2、L4-L6、L9 和 M4)。 + libxr parse on the SysConfig setups the BSPs do not use (the variants project; H1-H6, L2, + L4-L6, L9 and M4 of the 2026-10-07 review). + """ + + def parse_logged(self, edit=None) -> tuple[dict, list[str]]: + """解析 variants 工程,返回工程 YAML 和警告。 + Parse the variants project and return the project YAML and the warnings. + """ + with self.assertLogs(level="WARNING") as logs: + data = self.parse("variants", edit) + return data, logs.output + + def test_the_spi_clock_configuration_is_read(self): + # H1:SysConfig 把 SPI 的结构写成 gSPI_1_clockConfig,成员按列对齐。 + # H1: SysConfig writes the SPI struct as gSPI_1_clockConfig, members aligned in columns. + data, _logs = self.parse_logged() + self.assertEqual( + data["Peripherals"]["SPI"]["SPI_1"]["SysConfigClock"], + {"clockSel": "DL_SPI_CLOCK_MFCLK", "divideRatio": "DL_SPI_CLOCK_DIVIDE_RATIO_4"}, + ) + + def test_an_rx_timeout_dma_trigger_is_the_rx_channel(self): + # L2:RX timeout 触发的通道也认作 RX 通道并记下触发,gen 再说明它不能用(审查 B2)。 + # L2: a channel on the RX timeout trigger is the RX channel too and its trigger is + # recorded; gen then says it cannot be used (review B2). + data, _logs = self.parse_logged() + spi = data["Peripherals"]["SPI"]["SPI_1"] + self.assertEqual((spi["DMA_RX"], spi["DMA_TX"]), ("DMA_CH_SPI1_RX", "DMA_CH_SPI1_TX")) + self.assertEqual(spi["DMA_RX_TRIGGER"], "DL_SPI_DMA_INTERRUPT_RX") + + def timeout(name, text): + return text.replace('"DL_SPI_DMA_INTERRUPT_RX"', '"DL_SPI_DMA_INTERRUPT_RX_TIMEOUT"') + + data, _logs = self.parse_logged(timeout) + spi = data["Peripherals"]["SPI"]["SPI_1"] + self.assertEqual(spi["DMA_RX"], "DMA_CH_SPI1_RX") + self.assertEqual(spi["DMA_RX_TRIGGER"], "DL_SPI_DMA_INTERRUPT_RX_TIMEOUT") + + def test_a_uart_with_dma_rx_records_extend_and_its_channel(self): + data, _logs = self.parse_logged() + uart = data["Peripherals"]["UART"]["UART_0"] + self.assertEqual((uart["Extend"], uart["DMA_RX_ID"]), (True, 0)) + self.assertEqual(data["DMAFullChannels"], 3) + + def test_pwm_channels_come_from_the_output_when_the_pins_are_only_suggested(self): + # H3:引脚只有 $suggestSolution;去掉它们后通道仍来自 SysConfig 输出的 _IDX 宏。 + # H3: the pins are only $suggestSolution; without them the channels still come from + # the _IDX macros of the SysConfig output. + data, _logs = self.parse_logged() + pwm = data["Peripherals"]["PWM"]["PWM_0"] + self.assertEqual(pwm["Channels"], [0, 1]) + self.assertEqual(pwm["Pins"], {"ccp0Pin": "PA12", "ccp1Pin": "PA13"}) + + def drop_pins(name, text): + if name.endswith(".syscfg"): + return "\n".join(line for line in text.splitlines() if "ccp" not in line) + return text + + data, _logs = self.parse_logged(drop_pins) + self.assertEqual(data["Peripherals"]["PWM"]["PWM_0"]["Channels"], [0, 1]) + + def test_a_pwm_without_channels_is_a_warning(self): + def drop_channels(name, text): + return "\n".join( + line for line in text.splitlines() if "ccp" not in line and "_C0_" not in line + ).replace("_C1_IDX", "_C1_INDEX") + + data, logs = self.parse_logged(drop_channels) + self.assertEqual(data["Peripherals"]["PWM"]["PWM_0"]["Channels"], []) + self.assertTrue(any("the PWM PWM_0 has no channel" in line for line in logs)) + + def test_gpio_pins_given_as_port_and_pin_number_are_kept(self): + # H5:TI 例程的写法 port = "PORTA" 加 assignedPin = "26"。 + # H5: the form of TI's examples, port = "PORTA" plus assignedPin = "26". + data, _logs = self.parse_logged() + self.assertEqual(data["GPIO"]["USER_LED_1"]["Pin"], "PA26") + self.assertEqual(data["GPIO"]["USER_LED_2"]["Port"], "GPIO_LEDS_PORT") + + def test_a_skipped_gpio_pin_is_a_warning(self): + def drop_macro(name, text): + return text.replace("GPIO_LEDS_USER_LED_2_IOMUX", "GPIO_LEDS_USER_LED_2_MUX") + + data, logs = self.parse_logged(drop_macro) + self.assertNotIn("USER_LED_2", data["GPIO"]) + self.assertTrue(any("the GPIO USER_LED_2 is skipped" in line for line in logs)) + + def test_a_default_pin_name_falls_back_to_the_group_and_the_pin(self): + # M4:PIN_0 去掉前缀是 0,不是标识符。 + # M4: PIN_0 without its prefix is 0, not an identifier. + data, logs = self.parse_logged() + self.assertEqual(data["GPIO"]["gpio_btn_pin_0"]["PinMacro"], "GPIO_BTN_PIN_0_PIN") + self.assertTrue( + any("the object is named gpio_btn_pin_0. Give the pin a name" in line for line in logs) + ) + + def test_a_pin_named_like_a_device_macro_is_an_error(self): + # M4:PIN_SPI1 会成为 SPI1,与器件头文件的外设宏同名。 + # M4: PIN_SPI1 would become SPI1, the peripheral macro of the device header. + def rename(name, text): + return text.replace("PIN_0", "PIN_SPI1") + + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + self.parse("variants", rename) + self.assertIn("the GPIO object name SPI1 (pin PIN_SPI1 of GPIO_BTN)", logs.output[-1]) + + def test_an_interrupt_without_a_polarity_follows_the_sysconfig_default(self): + # L4:SysConfig 的 polarity 默认是 DISABLE。 + # L4: the polarity of SysConfig defaults to DISABLE. + def enable(name, text): + if name.endswith(".syscfg"): + return text.replace( + 'GPIO2.associatedPins[0].direction = "INPUT";', + 'GPIO2.associatedPins[0].direction = "INPUT";\n' + "GPIO2.associatedPins[0].interruptEn = true;", + ) + return text + + data, _logs = self.parse_logged(enable) + self.assertEqual(data["GPIO"]["gpio_btn_pin_0"]["Interrupt"], "DISABLE") + + def test_target_i2c_and_peripheral_spi_are_listed_but_not_generated(self): + # H6 + data, logs = self.parse_logged() + peripherals = data["Peripherals"] + self.assertNotIn("I2C_TARGET", peripherals["I2C"]) + self.assertNotIn("SPI_PERIPH", peripherals["SPI"]) + self.assertEqual( + peripherals["Other"]["I2C_TARGET"], {"Module": "I2C", "Peripheral": "I2C1"} + ) + self.assertEqual( + peripherals["Other"]["SPI_PERIPH"], {"Module": "SPI", "Peripheral": "SPI0"} + ) + self.assertTrue(any("I2C_TARGET is not in controller mode" in line for line in logs)) + self.assertTrue(any("SPI_PERIPH is in PERIPHERAL mode" in line for line in logs)) + + def test_a_dma_channel_without_a_name_in_the_syscfg_comes_from_the_output(self): + # L6:.syscfg 没写通道名时,SysConfig 输出里的 <通道>_CHAN_ID 和触发宏给出它。 + # L6: without a channel name in the .syscfg, the _CHAN_ID and the trigger + # macros of the SysConfig output give it. + def unnamed(name, text): + if name.endswith(".syscfg"): + return text.replace("UART1.DMA_CHANNEL_TX.$name", "// UART1.DMA_CHANNEL_TX.$name") + return text + + data, _logs = self.parse_logged(unnamed) + self.assertEqual(data["Peripherals"]["UART"]["UART_0"]["DMA_TX"], "DMA_CH_UART0_TX") + + def test_an_unknown_device_is_a_formatted_error(self): + # L5:没有调用栈,只有一条错误。 + # L5: no traceback, one error. + def unknown(name, text): + return text.replace('"MSPM0G3507"', '"MSPM0Z9999"') + + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + self.parse("variants", unknown) + self.assertEqual( + logs.output, + ["ERROR:root:mspm0_variants.syscfg: Unknown MSPM0 model: MSPM0Z9999"], + ) + + def test_the_summary_lines_are_translated(self): + # L9 + root = self.project("variants") + with ( + mock.patch.dict(os.environ, XR_LANG="zh"), + contextlib.redirect_stdout(io.StringIO()) as out, + self.assertLogs(level="WARNING"), + ): + parse_project(str(root)) + self.assertIn(" USER_LED_1:PA26,OUTPUT", out.getvalue().splitlines()) + + +class SysConfigRun(MSPM0TestCase): + """SysConfig 的运行和复用(M3、M7)。 + Running and reusing SysConfig (M3, M7). + """ + + def environment(self, root: Path) -> None: + """为 root 设置一个能用的 SysConfig 环境:存在的工具文件和带 product.json 的 SDK。 + Set a usable SysConfig environment for root: a tool file that exists and an SDK with + product.json. + """ + tool = root / "sysconfig_cli.bat" + tool.write_text("", encoding="utf-8") + sdk = root / "sdk" + (sdk / ".metadata").mkdir(parents=True) + (sdk / ".metadata" / "product.json").write_text("{}", encoding="utf-8") + os.environ["SYSCONFIG_TOOL"] = str(tool) + os.environ["MSPM0_SDK_INSTALL_DIR"] = str(sdk) + + def test_a_configured_sysconfig_runs_even_when_a_build_output_exists(self): + root = self.project("variants") + self.environment(root) + + def sysconfig(command, **_options): + output = Path(command[command.index("--output") + 1]) + for suffix in ("ti_msp_dl_config.h", "ti_msp_dl_config.c"): + shutil.copy(DATA / "mspm0" / f"variants_{suffix}", output / suffix) + return subprocess.CompletedProcess(command, 0, "", "") + + with ( + mock.patch("subprocess.run", side_effect=sysconfig) as run, + self.assertLogs(level="INFO") as logs, + ): + parse_project(str(root), summary=False) + self.assertEqual(run.call_count, 1) + self.assertIn("INFO:root:Running SysConfig on mspm0_variants.syscfg", logs.output) + self.assertFalse(any("Reusing" in line for line in logs.output)) + + def test_a_sysconfig_that_cannot_start_is_a_formatted_error(self): + root = self.project("variants") + self.environment(root) + with ( + mock.patch("subprocess.run", side_effect=OSError("not a program")), + self.assertLogs(level="ERROR") as logs, + self.assertRaises(SystemExit), + ): + parse_project(str(root), summary=False) + self.assertTrue(logs.output[-1].startswith("ERROR:root:Cannot run SysConfig (")) + self.assertTrue(logs.output[-1].endswith("): not a program")) + + def test_a_sysconfig_timeout_is_a_formatted_error(self): + root = self.project("variants") + self.environment(root) + with ( + mock.patch("subprocess.run", side_effect=subprocess.TimeoutExpired("x", 300)), + self.assertLogs(level="ERROR") as logs, + self.assertRaises(SystemExit), + ): + parse_project(str(root), summary=False) + self.assertIn("SysConfig did not finish on", logs.output[-1]) + + def test_a_wrong_path_is_named_instead_of_not_set(self): + root = self.project("variants") + shutil.rmtree(root / "build") + os.environ["SYSCONFIG_TOOL"] = str(root / "missing_cli.bat") + with self.assertLogs(level="ERROR") as logs, self.assertRaises(SystemExit): + parse_project(str(root), summary=False) + self.assertIn( + f"SYSCONFIG_TOOL={root / 'missing_cli.bat'} is not a file (it names " + "sysconfig_cli.bat or sysconfig_cli.sh); MSPM0_SDK_INSTALL_DIR is not set", + logs.output[-1], + ) + + def test_a_wrong_path_is_reported_when_the_build_output_is_reused(self): + root = self.project("variants") + os.environ["SYSCONFIG_TOOL"] = str(root / "missing_cli.bat") + with self.assertLogs(level="WARNING") as logs: + parse_project(str(root), summary=False) + self.assertTrue( + logs.output[0].startswith( + f"WARNING:root:SysConfig cannot run: SYSCONFIG_TOOL={root / 'missing_cli.bat'}" + ) + ) + + def test_a_cmake_build_tree_is_reused(self): + root = self.project("variants", build="cmake-build-debug") + with self.assertLogs(level="INFO") as logs: + parse_project(str(root), summary=False) + self.assertIn( + f"INFO:root:Reusing the SysConfig output in {Path('cmake-build-debug') / 'test'}", + logs.output, + ) + + +class Generation(MSPM0TestCase): + """libxr gen:生成的 app_main 源文件与设置,以及重新生成时保留的用户代码。 + libxr gen: the generated app_main source and settings, and the user code kept on + regeneration. + """ + + def setUp(self): + super().setUp() + generator.initialize_registry(False) + generator.reset_settings() + + def generate(self, name: str, use_xrobot: bool = True, edit=None) -> tuple[Path, str]: + """解析 name 的临时工程并生成 app_main 源文件;BSP 工程的 User/app_main.cpp 先放上审查 + 过的输出(重新生成的起点)。返回工程目录和重新生成的文本。 + Parse the temporary project of name and generate the app_main source; for a BSP + project the reviewed output is placed at User/app_main.cpp first, as the starting point + of the regeneration. The project directory and the regenerated text are returned. + """ + root = self.project(name, edit) + with contextlib.ExitStack() as stack: + if name == "variants": + stack.enter_context(self.assertLogs(level="WARNING")) + parse_project(str(root), summary=False) + output = root / "User" / "app_main.cpp" + output.parent.mkdir() + reviewed = DATA / "mspm0" / f"{name}_expected_app_main.cpp" + if use_xrobot and name != "variants": + shutil.copy(reviewed, output) + self.regenerate(root, use_xrobot) + return root, output.read_text(encoding="utf-8") + + def regenerate(self, root: Path, use_xrobot: bool = True) -> str: + """再生成一次 root 的 app_main 源文件并返回它的文本。 + Generate the app_main source of root once more and return its text. + """ + output = root / "User" / "app_main.cpp" + generator.generate(str(root / ".config.yaml"), str(output), use_xrobot, "") + return output.read_text(encoding="utf-8") + + def settings(self, root: Path, change=None) -> dict: + """root 的 libxr_config.yaml;给出 change 时用它修改设置并写回。 + The libxr_config.yaml of root; with change, the settings are modified through it and + written back. + """ + config = root / "User" / "libxr_config.yaml" + settings = yaml.safe_load(config.read_text(encoding="utf-8")) + if change is not None: + change(settings) + config.write_text(yaml.dump(settings), encoding="utf-8") + return settings + + def project_yaml(self, root: Path, change) -> None: + """用 change 修改 root 的工程 YAML 并写回。 + Modify the project YAML of root through change and write it back. + """ + path = root / ".config.yaml" + data = yaml.safe_load(path.read_text(encoding="utf-8")) + change(data) + path.write_text(yaml.dump(data, sort_keys=False), encoding="utf-8") + + def failure(self, root: Path) -> str: + """重新生成 root 并断言失败,返回最后一条错误。 + Regenerate root, assert that it fails and return the last error. + """ + with self.assertLogs(level="WARNING") as logs, self.assertRaises(SystemExit): + self.regenerate(root) + self.assertTrue(logs.output[-1].startswith("ERROR:")) + return logs.output[-1] + + def test_the_generated_source_matches_the_reviewed_output(self): + for name in ("g3507", "g3519", "variants"): + with self.subTest(name=name): + generator.initialize_registry(False) + generator.reset_settings() + _root, code = self.generate(name) + expected = (DATA / "mspm0" / f"{name}_expected_app_main.cpp").read_text( + encoding="utf-8" + ) + self.assertEqual(code, expected) + + def test_the_written_settings_hold_the_defaults_the_generated_code_uses(self): + root, code = self.generate("g3507") + settings = self.settings(root) + self.assertEqual(settings["terminal_source"], "uart0") + self.assertEqual(settings["UART"]["uart0"]["tx_buffer_size"], 128) + self.assertEqual(settings["I2C"]["i2c0"]["dma_enable_min_size"], 8) + self.assertEqual(settings["SPI"]["spi1"]["tx_buffer_size"], 32) + self.assertIn("frequency", settings["PWM"]["pwm_tima1_c0"]) + self.assertNotIn("USART", settings) + self.assertNotIn("SYSTEM", settings) + # 缓冲区按设置写出,重新生成只写有变化的文件之外,文本保持不变。 + # The buffers follow the settings; regenerating the same project writes the same text. + self.assertIn("static uint8_t uart0_tx_buf[2 * 128];", code) + + def test_a_configured_frequency_and_changed_sizes_are_applied(self): + root, _code = self.generate("g3507") + + def change(settings): + settings["PWM"]["pwm_tima1_c0"]["frequency"] = 1000 + settings["UART"]["uart1"]["tx_buffer_size"] = 64 + + self.settings(root, change) + code = self.regenerate(root) + self.assertIn("pwm_tima1_c0.SetConfig({1000});", code) + self.assertIn("static uint8_t uart1_tx_buf[2 * 64];", code) + + def test_the_user_code_of_an_existing_file_is_kept(self): + root, _code = self.generate("g3507") + output = root / "User" / "app_main.cpp" + code = output.read_text(encoding="utf-8") + code = code.replace( + "/* User Code Begin 1 */", + "/* User Code Begin 1 */\n// A user note that must survive regeneration.", + ) + output.write_text(code, encoding="utf-8") + self.assertIn("// A user note that must survive regeneration.", self.regenerate(root)) + + def test_without_xrobot_the_entry_sleeps_and_registers_nothing(self): + _root, code = self.generate("g3507", use_xrobot=False) + self.assertNotIn("XR_REGISTER", code) + self.assertNotIn("xrobot_main.hpp", code) + self.assertIn("Thread::Sleep(UINT32_MAX);", code) + self.assertNotIn("XROBOT_MAIN();", code) + + def test_the_spi_clock_constant_is_defined_whenever_it_is_used(self): + # H2:MFCLK 不分频和 LFCLK 时也定义 SPI_1_CLK_FREQ。 + # H2: SPI_1_CLK_FREQ is defined for MFCLK without a divider and for LFCLK as well. + root, code = self.generate("variants") + self.assertIn("static constexpr uint32_t SPI_1_CLK_FREQ = MFCLK_FREQ / 4;", code) + for sel, div, line in ( + ("MFCLK", 1, "SPI_1_CLK_FREQ = MFCLK_FREQ;"), + ("LFCLK", 2, "SPI_1_CLK_FREQ = LFCLK_FREQ / 2;"), + ("BUSCLK", 2, "SPI_1_CLK_FREQ = PD1_BUSCLK_FREQ / 2;"), + ): + with self.subTest(sel=sel, div=div): + + def change(data, sel=sel, div=div): + data["Peripherals"]["SPI"]["SPI_1"]["SysConfigClock"] = { + "clockSel": f"DL_SPI_CLOCK_{sel}", + "divideRatio": f"DL_SPI_CLOCK_DIVIDE_RATIO_{div}", + } + + self.project_yaml(root, change) + code = self.regenerate(root) + self.assertIn(f"static constexpr uint32_t {line}", code) + self.assertIn("MSPM0_SPI_INIT(SPI_1, SPI_1_CLK_FREQ,", code) + source = line.split(" = ")[1].split(" ")[0].rstrip(";") + self.assertIn(f"static constexpr uint32_t {source} = ", code) + + def test_an_unsupported_spi_clock_source_is_an_error(self): + root, _code = self.generate("variants") + + def change(data): + data["Peripherals"]["SPI"]["SPI_1"]["SysConfigClock"]["clockSel"] = "DL_SPI_CLOCK_X" + + self.project_yaml(root, change) + self.assertIn( + "SPI_1: the SysConfig clock source DL_SPI_CLOCK_X is not supported", self.failure(root) + ) + + def test_a_missing_power_domain_is_an_error_not_an_undefined_macro(self): + # L3:SysConfig 不为 I2C 定义 I2C_0_INST_FREQUENCY。 + # L3: SysConfig defines no I2C_0_INST_FREQUENCY for an I2C. + root, _code = self.generate("variants") + self.project_yaml(root, lambda data: data["Peripherals"]["I2C"]["I2C_0"].pop("PowerDomain")) + self.assertIn("I2C_0: the project YAML has no the power domain of I2C0", self.failure(root)) + + def test_a_pwm_object_is_named_after_its_timer(self): + # H4:实例名 PWM_0、定时器 TIMG0 -> pwm_timg0_c0,与 libxr pins -d 一致。 + # H4: instance PWM_0 on timer TIMG0 -> pwm_timg0_c0, as libxr pins -d names it. + root, code = self.generate("variants") + self.assertIn("static MSPM0PWM pwm_timg0_c0(MSPM0_PWM_CH(PWM_0, 0));", code) + self.assertIn("pwm_timg0_c1", self.settings(root)["PWM"]) + + def test_terminal_source_none_or_empty_stays_off(self): + # M1:与 STM32 一样,空值不生成终端;第一次生成时才选第一个 UART。 + # M1: as on STM32 an empty value generates no terminal; only the first generation + # picks the first UART. + for value in ("none", ""): + with self.subTest(value=value): + generator.initialize_registry(False) + generator.reset_settings() + root, code = self.generate("variants") + self.assertIn("// Terminal on uart0", code) + self.settings( + root, lambda settings, value=value: settings.update(terminal_source=value) + ) + for _ in range(2): + code = self.regenerate(root) + self.assertNotIn("Terminal on", code) + self.assertEqual(self.settings(root)["terminal_source"], value) + + def test_settings_are_validated(self): + # M2 + root, _code = self.generate("variants") + for section, name, key, value, message in ( + ("UART", "uart0", "tx_buffer_size", 30, "is not a multiple of 4 up to 65532"), + ("UART", "uart0", "tx_buffer_size", 65536, "is not a multiple of 4 up to 65532"), + ("UART", "uart0", "tx_queue_size", 0, "is not a positive integer"), + ("SPI", "spi1", "dma_enable_min_size", "abc", "is not a non-negative integer"), + ("I2C", "i2c0", "buffer_size", 1.5, "is not a positive integer"), + ("PWM", "pwm_timg0_c0", "frequency", "fast", "is not a positive integer"), + ("Terminal", None, "max_line_size", -1, "is not a positive integer"), + ): + with self.subTest(key=key, value=value): + + def change(settings, section=section, name=name, key=key, value=value): + (settings[section][name] if name else settings[section])[key] = value + + previous = self.settings(root) + self.settings(root, change) + error = self.failure(root) + self.assertIn(f"{key} {value!r} {message}", error) + (root / "User" / "libxr_config.yaml").write_text( + yaml.dump(previous), encoding="utf-8" + ) + + def test_a_null_setting_takes_the_default(self): + # M2:null 不再写成 2 * None。 + # M2: a null no longer becomes 2 * None. + root, _code = self.generate("variants") + self.settings(root, lambda settings: settings["UART"]["uart0"].update(tx_buffer_size=None)) + code = self.regenerate(root) + self.assertIn("static uint8_t uart0_tx_buf[2 * 128];", code) + self.assertNotIn(".SetConfig", code) + + def test_a_settings_key_in_another_case_is_used_and_kept(self): + root, _code = self.generate("variants") + + def change(settings): + settings["UART"] = {"UART0": settings["UART"].pop("uart0")} + settings["UART"]["UART0"]["tx_buffer_size"] = 64 + + self.settings(root, change) + code = self.regenerate(root) + self.assertIn("static uint8_t uart0_tx_buf[2 * 64];", code) + self.assertEqual(list(self.settings(root)["UART"]), ["UART0"]) + + def test_a_uart_extend_with_dma_rx_receives_by_dma(self): + # 用户 2026-10-07 决定:UART0(G3507 的 UART Extend)配了 FULL-DMA 通道上的 DMA RX, + # 用 MSPM0_UART_EXTEND_INIT。 + # User decision 2026-10-07: UART0 (the UART Extend of a G3507) has DMA RX on a + # FULL-DMA channel, so it takes MSPM0_UART_EXTEND_INIT. + root, code = self.generate("variants") + self.assertIn("MSPM0_UART_EXTEND_INIT(UART_0, DMA_CH_UART0_TX, DMA_CH_UART0_RX,", code) + self.assertNotIn("MSPM0_UART_MAIN_INIT(", code) + for line in ( + "#define DMA_CH_UART0_RX_LIBXR_UART_IRQN UART_0_INST_INT_IRQN", + "#define DMA_CH_UART0_RX_LIBXR_UART_RX 1", + "#define DMA_CH_UART0_RX_LIBXR_FULL_CHANNEL 1", + "#define DMA_CH_UART0_RX_LIBXR_HALF_INTERRUPT 1", + "#define UART_0_LIBXR_EXTEND_CAPABLE 1", + "alignas(4) static uint8_t uart0_rx_dma_buf[128];", + ): + self.assertIn(line, code) + self.assertEqual(self.settings(root)["UART"]["uart0"]["rx_dma_buffer_size"], 128) + + def test_a_gpio_named_like_a_generated_object_is_an_error(self): + # 审查 C1:timebase 是生成代码里的对象,GPIO 不能再叫它。 + # Review C1: timebase is an object of the generated code, so a GPIO cannot take it. + root, _code = self.generate("variants") + self.project_yaml( + root, lambda data: data["GPIO"].update(timebase=data["GPIO"].pop("USER_LED_1")) + ) + self.assertIn( + "The generated code uses these GPIO names for something else: timebase", + self.failure(root), + ) + + def test_power_manager_is_generated_always_and_registered_only_with_xrobot(self): + # power_manager 对象总是生成,登记只在使用 XRobot 时;GPIO 不能再叫 power_manager。 + # The power_manager object is always generated; it is registered only with XRobot, and + # a GPIO cannot take the name power_manager. + root, code = self.generate("g3507", use_xrobot=False) + self.assertIn('#include "mspm0_power.hpp"', code) + self.assertIn("static MSPM0PowerManager power_manager;", code) + self.assertNotIn("XR_REGISTER", code) + code = self.regenerate(root) + self.assertIn("XR_REGISTER(power_manager, LibXR::PowerManager);", code) + self.project_yaml( + root, lambda data: data["GPIO"].update(power_manager=data["GPIO"].pop("LED1")) + ) + self.assertIn( + "GPIO name power_manager is already used by the PowerManager object", + self.failure(root), + ) + + def test_dma_rx_on_a_uart_main_is_a_warning(self): + root, _code = self.generate("variants") + self.project_yaml( + root, lambda data: data["Peripherals"]["UART"]["UART_0"].update(Extend=False) + ) + with self.assertLogs(level="WARNING") as logs: + code = self.regenerate(root) + self.assertIn( + "WARNING:root:UART_0: the DMA RX channel DMA_CH_UART0_RX is not used; UART0 is a " + "UART Main instance, which receives on byte interrupts (MSPM0_UART_MAIN_INIT); DMA " + "reception needs a UART Extend instance", + logs.output, + ) + self.assertIn("MSPM0_UART_MAIN_INIT(UART_0, DMA_CH_UART0_TX,", code) + self.assertNotIn("rx_dma_buf", code) + + def test_dma_rx_on_a_channel_that_is_not_full_is_an_error(self): + root, _code = self.generate("variants") + self.project_yaml( + root, lambda data: data["Peripherals"]["UART"]["UART_0"].update(DMA_RX_ID=4) + ) + self.assertIn( + "UART_0: the DMA RX channel DMA_CH_UART0_RX is channel 4, but " + "MSPM0_UART_EXTEND_INIT receives on a FULL-DMA channel (0 to 2)", + self.failure(root), + ) + + def test_the_receive_ring_must_be_even(self): + root, _code = self.generate("variants") + self.settings( + root, lambda settings: settings["UART"]["uart0"].update(rx_dma_buffer_size=63) + ) + self.assertIn("UART.uart0.rx_dma_buffer_size", self.failure(root)) + + def test_an_spi_rx_timeout_trigger_is_an_error(self): + # 审查 B2:驱动按 SysConfig 的触发搬运每个字节,RX timeout 触发的传输等不到结束。 + # Review B2: the driver moves every byte on SysConfig's trigger, and a transfer on the + # RX timeout trigger never completes. + root, _code = self.generate("variants") + self.project_yaml( + root, + lambda data: data["Peripherals"]["SPI"]["SPI_1"].update( + DMA_RX_TRIGGER="DL_SPI_DMA_INTERRUPT_RX_TIMEOUT" + ), + ) + self.assertIn( + "SPI_1: the DMA RX channel DMA_CH_SPI1_RX is triggered by RX timeout", + self.failure(root), + ) + + def test_a_uart_without_dma_tx_names_the_sysconfig_option(self): + # L6 + root, _code = self.generate("variants") + self.project_yaml(root, lambda data: data["Peripherals"]["UART"]["UART_0"].pop("DMA_TX")) + self.assertIn( + "UART_0: no DMA TX channel in the SysConfig project (UART > DMA Configuration: " + "Enable DMA TX with the TX trigger, which also gives the _CHAN_ID and " + "UART_0_INST_DMA_TRIGGER macros, the latter numbered _0, _1 when the instance has " + "more than one trigger)", + self.failure(root), + ) + + def test_the_ti_example_with_one_dma_trigger_generates(self): + # TI 例程端到端:.syscfg 加 SysConfig 输出解析出 DMA_CH0(触发宏不带编号),生成出来的 + # UART 用它构造。 + # The TI example end to end: its .syscfg and SysConfig output parse to DMA_CH0 (the + # trigger macro carries no number), and the generated UART is constructed with it. + root = self.project("uart_tx_dma") + parse_project(str(root), summary=False) + (root / "User").mkdir() + code = self.regenerate(root) + self.assertIn("MSPM0_UART_MAIN_INIT(UART_0, DMA_CH0,", code) + self.assertIn("#define DMA_CH0_LIBXR_UART_IRQN UART_0_INST_INT_IRQN", code) + + def test_different_frequencies_of_one_timer_are_a_warning(self): + # L7 + root, _code = self.generate("variants") + + def change(settings): + settings["PWM"]["pwm_timg0_c0"]["frequency"] = 1000 + settings["PWM"]["pwm_timg0_c1"]["frequency"] = 2000 + + self.settings(root, change) + with self.assertLogs(level="WARNING") as logs: + self.regenerate(root) + self.assertIn( + "WARNING:root:libxr_config.yaml: the PWM channels of PWM_0 share one period but set " + "different frequencies (pwm_timg0_c0 1000, pwm_timg0_c1 2000); the last SetConfig " + "(pwm_timg0_c1) wins", + logs.output, + ) + + def test_a_hand_written_file_lists_the_lines_that_are_replaced(self): + # M6:已有文件没有生成说明时警告,照常生成。 + # M6: an existing file without the generated-file notice is warned about and generated + # as usual. + root, _code = self.generate("g3507") + output = root / "User" / "app_main.cpp" + output.write_text( + '#include "app_main.h"\n' + "static int hand_written = 1;\n" + "/* User Code Begin 1 */\nstatic int kept = 2;\n/* User Code End 1 */\n" + 'extern "C" void app_main(void)\n{\n' + " /* User Code Begin 2 */\n /* User Code End 2 */\n" + " KEY1.EnableInterrupt();\n" + " /* User Code Begin 3 */\n /* User Code End 3 */\n" + " XROBOT_MAIN();\n}\n", + encoding="utf-8", + ) + with self.assertLogs(level="WARNING") as logs: + code = self.regenerate(root) + warning = next(line for line in logs.output if "was not generated by libxr gen" in line) + self.assertIn("these 2 line(s) of it are replaced", warning) + self.assertTrue( + warning.endswith("\n static int hand_written = 1;\n KEY1.EnableInterrupt();") + ) + self.assertIn("static int kept = 2;", code) + # 生成过的文件不再警告。 + # A generated file is not warned about any more. + with self.assertNoLogs(level="WARNING"): + self.regenerate(root) + + +class Setup(MSPM0TestCase): + """libxr mspm0 setup 和 libxr hpm setup(M5、HPM 17):XRobot 的选择、.gitignore 和下一步。 + libxr mspm0 setup and libxr hpm setup (M5, HPM 17): the XRobot choice, .gitignore and the + next steps. + """ + + def setUp(self): + super().setUp() + generator.initialize_registry(False) + generator.reset_settings() + + def setup(self, root: Path, *options) -> tuple[int, str]: + """在 root 上运行 libxr mspm0 setup,返回退出码和生成的 app_main 源文件。 + Run libxr mspm0 setup on root and return the exit code and the generated app_main. + """ + with self.assertLogs(level="INFO") as logs: + code, _out, _err = run_libxr("mspm0", "setup", "-d", str(root), *options) + self.logs = logs.output + return code, (root / "User" / "app_main.cpp").read_text(encoding="utf-8") + + def test_a_new_project_uses_no_xrobot_and_an_existing_one_keeps_its_choice(self): + root = self.project("variants") + code, app_main = self.setup(root) + self.assertEqual(code, 0) + self.assertNotIn("xrobot_main.hpp", app_main) + self.assertIn(".config.yaml", (root / ".gitignore").read_text(encoding="utf-8")) + _code, app_main = self.setup(root, "--xrobot") + self.assertIn('#include "xrobot_main.hpp"', app_main) + _code, app_main = self.setup(root) + self.assertIn('#include "xrobot_main.hpp"', app_main) + self.assertIn( + f"INFO:root:{root / 'User' / 'app_main.cpp'} uses XRobot; generating with --xrobot " + "(--no-xrobot turns it off).", + self.logs, + ) + # HPM 17:与 stm32 setup 一样,还没有 Modules/modules.yaml 时给出 XRobot 的设置步骤, + # 其中 xrobot setup 生成 User/xrobot_main.hpp。 + # HPM 17: as stm32 setup does, the XRobot setup steps are given while there is no + # Modules/modules.yaml; xrobot setup among them generates User/xrobot_main.hpp. + self.assertIn( + "INFO:root:Next: Modules/modules.yaml does not exist yet; set up XRobot in this order:", + self.logs, + ) + # 有了 Modules/modules.yaml 之后提醒运行 xrobot gen:setup 不更新 xrobot_main.hpp。 + # Once Modules/modules.yaml exists, the reminder to run xrobot gen: setup does not + # update xrobot_main.hpp. + (root / "Modules").mkdir(exist_ok=True) + (root / "Modules" / "modules.yaml").write_text("modules: []\n", encoding="utf-8") + self.setup(root, "--xrobot") + self.assertIn( + "INFO:root:Next: run `xrobot gen` to bring User/xrobot_main.hpp up to date; libxr " + "setup does not update it", + self.logs, + ) + _code, app_main = self.setup(root, "--no-xrobot") + self.assertNotIn("xrobot_main.hpp", app_main) + self.assertFalse(any("xrobot gen" in line for line in self.logs)) + _code, app_main = self.setup(root) + self.assertNotIn("xrobot_main.hpp", app_main) + + def test_an_existing_gitignore_is_kept(self): + root = self.project("variants") + (root / ".gitignore").write_text("mine\n", encoding="utf-8") + self.setup(root) + self.assertEqual((root / ".gitignore").read_text(encoding="utf-8"), "mine\n") + + def test_hpm_setup_keeps_the_choice_of_the_output_file_as_well(self): + root = Path(tempfile.mkdtemp(prefix="libxr-hpm-setup-")) + self.addCleanup(shutil.rmtree, root, ignore_errors=True) + (root / "User").mkdir() + (root / "User" / "app_main.cpp").write_text("int x;\n", encoding="utf-8") + with ( + mock.patch("libxr.peripheral_analyzer_hpm.parse_project"), + mock.patch("libxr.generator_code_hpm.generate") as generate, + self.assertLogs(level="INFO"), + ): + self.assertEqual(run_libxr("hpm", "setup", "-d", str(root))[0], 0) + self.assertIs(generate.call_args.args[2], False) + self.assertTrue((root / ".gitignore").is_file()) + + +class Analyzer(TestCase): + """解析器的小函数。 + Small functions of the parser. + """ + + def test_the_clock_configurations_of_every_kind_are_read(self): + source = (DATA / "mspm0" / "variants_ti_msp_dl_config.c").read_text(encoding="utf-8") + clocks = analyzer._clock_configs(source) + self.assertEqual( + sorted(clocks), ["I2C_0", "I2C_TARGET", "PWM_0", "SPI_1", "SPI_PERIPH", "UART_0"] + ) + self.assertEqual(clocks["UART_0"]["clockSel"], "DL_UART_MAIN_CLOCK_BUSCLK") + + def test_a_dma_trigger_without_a_number_names_the_channel_too(self): + # SysConfig 在一个实例只有一个 DMA 触发时不写编号;通道名是触发宏前面紧挨着的 + # <通道>_CHAN_ID。 + # SysConfig writes no number while an instance has one DMA trigger; the channel is the + # _CHAN_ID directly before the trigger macro. + defines = { + "DMA_CH0_CHAN_ID": "(0)", + "UART_0_INST_DMA_TRIGGER": "(DMA_UART0_TX_TRIG)", + } + self.assertEqual(analyzer._header_dma_channel("UART_0", "TX", defines), "DMA_CH0") + self.assertIsNone(analyzer._header_dma_channel("UART_0", "RX", defines)) + + def test_an_spi_with_one_dma_event_uses_the_same_lookup(self): + # SPI 和 UART 走同一个查找:只开一个 DMA 事件时它的触发宏也不带编号。 + # An SPI uses the same lookup as a UART: with a single DMA event its trigger macro + # carries no number either. + defines = { + "DMA_CH_SPI1_RX_CHAN_ID": "(3)", + "SPI_1_INST_DMA_TRIGGER": "(DMA_SPI1_RX_TRIG)", + } + self.assertEqual(analyzer._header_dma_channel("SPI_1", "RX", defines), "DMA_CH_SPI1_RX") + self.assertIsNone(analyzer._header_dma_channel("SPI_1", "TX", defines)) + + def test_a_numbered_dma_trigger_names_the_channel_as_before(self): + # 两个及以上触发时宏带编号,两个方向各取自己前面的通道。 + # From two triggers on the macros carry numbers, and each direction takes the channel + # before its own trigger. + defines = { + "DMA_CH_UART0_TX_CHAN_ID": "(1)", + "UART_0_INST_DMA_TRIGGER_0": "(DMA_UART0_TX_TRIG)", + "DMA_CH_UART0_RX_CHAN_ID": "(0)", + "UART_0_INST_DMA_TRIGGER_1": "(DMA_UART0_RX_TRIG)", + } + self.assertEqual(analyzer._header_dma_channel("UART_0", "TX", defines), "DMA_CH_UART0_TX") + self.assertEqual(analyzer._header_dma_channel("UART_0", "RX", defines), "DMA_CH_UART0_RX") diff --git a/tests/test_generator_code_stm32.py b/tests/test_generator_code_stm32.py index 13e3c87..c2c04e0 100644 --- a/tests/test_generator_code_stm32.py +++ b/tests/test_generator_code_stm32.py @@ -476,6 +476,42 @@ def test_each_cdc_takes_its_own_queue_sizes(self): self.assertIn("USB::Endpoint::EPNumber::EP2, 64, 256, 2);", code) self.assertIn("USB::Endpoint::EPNumber::EP4, 128, 128, 7);", code) + def test_a_cdc_interface_names_its_control_and_data_interfaces(self): + # interface 接在队列容量之后,作为 CDCUart 的控制接口名和数据接口名;未设置的 CDC + # 使用 LibXR 的默认名,不写 interface 键。 + # interface follows the queue sizes as the control and data interface names of + # CDCUart; a CDC without it keeps the LibXR default names, and no interface key is + # written for it. + project = self.usb_otg_hs(cdc=[{"interface": 'Port "A"'}, {}]) + code = self.generate(project) + self.assertIn( + " static USB::CDCUart usb_otg_hs_cdc(\n" + " USB::Endpoint::EPNumber::EP1, USB::Endpoint::EPNumber::EP1,\n" + ' USB::Endpoint::EPNumber::EP2, 128, 128, 3, "Port \\"A\\"", "Port \\"A\\"");\n' + " static USB::CDCUart usb_otg_hs_cdc2(USB::Endpoint::EPNumber::EP3,\n" + " USB::Endpoint::EPNumber::EP2,\n" + " USB::Endpoint::EPNumber::EP4, 128, 128, 3);\n", + code, + ) + self.assertEqual( + generator.libxr_settings["USB"]["usb_otg_hs"]["cdc"], + [ + { + "interface": 'Port "A"', + "tx_fifo_size": 128, + "rx_fifo_size": 128, + "queue_size": 3, + }, + {"tx_fifo_size": 128, "rx_fifo_size": 128, "queue_size": 3}, + ], + ) + + def test_a_cdc_interface_must_be_a_string(self): + project = self.usb_otg_hs(cdc=[{"interface": 7}]) + with self.assertRaises(LibXRConfigError) as raised: + self.generate(project) + self.assertIn("USB.usb_otg_hs.cdc[0].interface", str(raised.exception)) + def test_on_fsdev_each_cdc_has_a_data_endpoint_and_a_notification_endpoint( self, ): diff --git a/tests/test_pin_layout.py b/tests/test_pin_layout.py new file mode 100644 index 0000000..9ad4f6c --- /dev/null +++ b/tests/test_pin_layout.py @@ -0,0 +1,457 @@ +"""libxr pins(libxr.pin_layout):由型号给出封装和引脚布局。 +libxr pins (libxr.pin_layout): the package and pin layout of a model. +""" + +import json +import re +import unittest +from pathlib import Path + +import yaml +from fixtures import TestCase, run_libxr + +from libxr import pin_layout +from libxr.pin_layout import layout_pins, layout_to_dict + + +class Stm32Models(TestCase): + """STM32 型号按 ST 的数据解析:温度等级、分组文件名和后缀都能对上。 + STM32 models resolve against ST's data: the temperature grade, grouped file names and + suffixes all match. + """ + + def test_the_temperature_grade_is_not_part_of_the_data_name(self): + layout = layout_pins("stm32h723vgt6") + self.assertEqual((layout.part, layout.package), ("STM32H723VGTx", "LQFP100")) + self.assertEqual(len(layout.pins), 100) + first = layout.pins[0] + self.assertEqual((first.position, first.name), ("1", "PE2")) + self.assertIn("USART10_RX", first.signals) + + def test_grouped_file_names_cover_every_model_of_the_group(self): + # ST 把 STM32F407IE 和 STM32F407IG 放在同一个文件 STM32F407I(E-G)Hx.xml 中。 + # ST keeps STM32F407IE and STM32F407IG in one file, STM32F407I(E-G)Hx.xml. + for model in ("STM32F407IGH6", "STM32F407IEH6"): + with self.subTest(model=model): + layout = layout_pins(model) + self.assertEqual(layout.package, "UFBGA176") + self.assertEqual(layout_to_dict(layout)["pin_count"], 201) + + def test_a_suffix_after_the_temperature_grade_is_kept(self): + self.assertEqual(layout_pins("STM32U5G9ZJT6Q").part, "STM32U5G9ZJTxQ") + self.assertEqual(layout_pins("STM32G0B1CBT6N").part, "STM32G0B1CBTxN") + + def test_remapped_pins_share_one_position(self): + # STM32G0 的 PA9 和 PA11 可以互换,同一个封装位置上有两个条目。 + # PA9 and PA11 of an STM32G0 can be swapped, so one package position has two entries. + layout = layout_pins("STM32G0B1CBT6N") + at_33 = [pin.name for pin in layout.pins if pin.position == "33"] + self.assertEqual(sorted(at_33), ["PA11 [PA9]", "PA9 [PA11]"]) + self.assertEqual(layout_to_dict(layout)["pin_count"], 48) + self.assertGreater(len(layout.pins), 48) + + def test_a_given_package_must_be_the_one_of_the_model(self): + self.assertEqual(layout_pins("STM32H723VGT6", "lqfp100").package, "LQFP100") + with self.assertRaises(ValueError) as caught: + layout_pins("STM32H723VGT6", "LQFP64") + self.assertEqual( + str(caught.exception), + "STM32H723VGT6 comes in LQFP100, not LQFP64; the package is part of the STM32 model", + ) + + def test_an_unknown_model_names_similar_ones(self): + with self.assertRaises(ValueError) as caught: + layout_pins("STM32H723VGT6X9") + self.assertTrue( + str(caught.exception).startswith("Unknown STM32 model: STM32H723VGT6X9; similar: "), + str(caught.exception), + ) + with self.assertRaises(ValueError) as caught: + layout_pins("STM32Q999RGT6") + self.assertEqual(str(caught.exception), "Unknown STM32 model: STM32Q999RGT6") + + +class HpmModels(TestCase): + """HPM 的型号即 SoC 名,封装名与 .hpmpc 的 packageName 一致;信号是 hpm_iomux.h 的宏名。 + The model of an HPM is its SoC name, the package name matches the packageName of a + .hpmpc; the signals are the macro names of hpm_iomux.h. + """ + + def test_the_soc_chooses_the_data_and_the_pin_carries_its_signals(self): + layout = layout_pins("HPM5301", "QFN48") + self.assertEqual( + (layout.platform, layout.part, layout.package), ("hpm", "HPM5301", "QFN48") + ) + self.assertEqual(layout_to_dict(layout)["pin_count"], 29) + pin = next(pin for pin in layout.pins if pin.name == "PA00") + self.assertEqual(pin.position, "12") + self.assertEqual(pin.extra["modes"]["UART0_TXD"], 2) + + def test_a_gpio_mux_is_not_a_peripheral_signal(self): + self.assertIsNone(pin_layout.recognize_hpm("GPIO_A_00")) + self.assertEqual(pin_layout.recognize_hpm("UART0_TXD"), ("UART0", "UART", "TXD")) + + def test_an_unknown_soc_or_package_is_an_error(self): + with self.assertRaises(ValueError) as caught: + layout_pins("HPM9999", "QFN48") + self.assertTrue( + str(caught.exception).startswith("Unknown HPM SoC: HPM9999"), str(caught.exception) + ) + with self.assertRaises(ValueError) as caught: + layout_pins("HPM5301", "BGA") + self.assertTrue( + str(caught.exception).startswith("HPM5301 has no package BGA"), str(caught.exception) + ) + + def test_the_packages_are_the_ordering_part_numbers(self): + # 数据手册的订购型号:HPM5301 只有 IEG1(QFN48),所以不用给封装;LQFP80 是引脚表里的一 + # 列,但没有哪个型号用它。 + # The ordering part numbers of the datasheet: HPM5301 ships as IEG1 (QFN48) only, so + # no package needs to be given; LQFP80 is a column of the pin table that no part uses. + self.assertEqual(layout_pins("HPM5301").package, "QFN48") + socs = pin_layout.load_data("hpm")["socs"] + self.assertEqual(sorted(socs["HPM5361"]["packages"]), ["LQFP100", "LQFP64", "QFN48"]) + for soc in socs.values(): + self.assertNotIn("LQFP80", soc["packages"]) + + def test_the_analog_functions_follow_the_peripherals_of_the_soc(self): + # HPM5301 只有 ADC0,没有 DAC 和运放(hpm_soc_ip.h);HPM5361 都有。 + # A HPM5301 has ADC0 only and no DAC or op-amp (hpm_soc_ip.h); a HPM5361 has them all. + pb08 = { + soc: next(p for p in layout_pins(soc, "QFN48").pins if p.name == "PB08") + for soc in ("HPM5301", "HPM5361") + } + for signal in ("ADC1_IN11", "DAC0_OUT", "OPA0_INP2"): + self.assertNotIn(signal, pb08["HPM5301"].signals) + self.assertIn(signal, pb08["HPM5361"].signals) + + def test_an_unknown_soc_has_no_answer_about_pwm(self): + # 不认识的 SoC 不能当作没有 PWM 外设,否则会给 HPM5361 这样的芯片生成 GPTMR 的 PWM。 + # An unknown SoC must not count as having no PWM peripheral; that would generate GPTMR + # PWM objects for a chip like the HPM5361. + self.assertFalse(pin_layout.hpm_soc_has_pwm("HPM5301")) + self.assertTrue(pin_layout.hpm_soc_has_pwm("HPM5361")) + with self.assertRaises(ValueError): + pin_layout.hpm_soc_has_pwm("hpm5361") + + def test_the_index_has_the_gpio_ports_and_the_pwm_capable_timers(self): + index = layout_to_dict(layout_pins("HPM5301"))["peripherals"] + self.assertEqual(index["GPIOA"]["kind"], "GPIO") + self.assertEqual(index["GPIOA"]["signals"]["P10"], ["PA10"]) + # 线号去掉前导零:PA03 是 P3。 + # The line drops its leading zero: PA03 is P3. + self.assertEqual(index["GPIOA"]["signals"]["P3"], ["PA03"]) + self.assertEqual(index["GPTMR0"]["capabilities"], ["pwm"]) + index_5361 = layout_to_dict(layout_pins("HPM5361", "QFN48"))["peripherals"] + self.assertEqual(index_5361["PWM0"]["capabilities"], ["pwm"]) + # 审查 C4:有 PWM 外设的 SoC 上 HPMPWM 不驱动 GPTMR。 + # Review C4: on a SoC with a PWM peripheral HPMPWM does not drive a GPTMR. + self.assertNotIn("capabilities", index_5361["GPTMR0"]) + + +class Mspm0Models(TestCase): + """MSPM0 型号按器件族和封装代码解析,每个引脚带 PINCM 和各信号的模式号。 + MSPM0 models resolve by family and package code; every pin carries its PINCM and the mode of + each signal. + """ + + def test_the_package_code_in_the_model_chooses_the_package(self): + layout = layout_pins("MSPM0G3507SPMR") + self.assertEqual((layout.part, layout.package), ("MSPM0G3507", "LQFP-64(PM)")) + self.assertEqual(layout_to_dict(layout)["pin_count"], 64) + pin = next(pin for pin in layout.pins if pin.position == "33") + self.assertEqual(pin.name, "PA0") + self.assertEqual(pin.extra["iomux_pincm"], 1) + self.assertEqual(pin.extra["modes"]["UART0.TX"], 2) + self.assertEqual(pin.extra["modes"]["I2C0.SDA"], 3) + + def test_a_package_can_be_given_in_three_spellings(self): + for spelling in ("LQFP-64(PM)", "LQFP-64", "pm"): + with self.subTest(package=spelling): + self.assertEqual(layout_pins("MSPM0G3507", spelling).package, "LQFP-64(PM)") + + def test_a_model_without_a_package_asks_for_one(self): + with self.assertRaises(ValueError) as caught: + layout_pins("MSPM0G3507") + self.assertEqual( + str(caught.exception), + "Cannot tell the package of MSPM0G3507; give it with --package " + "(VQFN-48(RGZ), VSSOP-32(DGS32), LQFP-48(PT), LQFP-64(PM), VSSOP-28(DGS28), VQFN-32(RHB))", + ) + with self.assertRaises(ValueError) as caught: + layout_pins("MSPM0G3507", "BGA") + self.assertTrue( + str(caught.exception).startswith("MSPM0G3507 has no package BGA; this family has "), + str(caught.exception), + ) + + def test_the_longest_family_prefix_wins(self): + # MSPM0C1105 有自己的器件族,不属于 MSPM0C110X。 + # MSPM0C1105 has a family of its own and is not part of MSPM0C110X. + layout = layout_pins("MSPM0C1105", "VQFN-32") + self.assertEqual((layout.part, layout.package), ("MSPM0C1105", "VQFN-32(RHB)")) + self.assertEqual(layout_pins("MSPM0C1103", "VSSOP-20").part, "MSPM0C1103") + + def test_an_unknown_model_is_an_error(self): + with self.assertRaises(ValueError) as caught: + layout_pins("MSPM0Z9999") + self.assertEqual(str(caught.exception), "Unknown MSPM0 model: MSPM0Z9999") + + +class Dispatch(TestCase): + """型号的前缀选平台。 + The prefix of the model chooses the platform. + """ + + def test_the_platform_follows_the_prefix(self): + self.assertEqual(layout_pins("STM32F103C8T6").platform, "stm32") + self.assertEqual(layout_pins("MSPM0G3507SPMR").platform, "mspm0") + + def test_an_unsupported_model_lists_the_platforms(self): + with self.assertRaises(ValueError) as caught: + layout_pins("CH32V203C8T6") + self.assertEqual( + str(caught.exception), + "No supported platform for the model CH32V203C8T6 (supported: STM32, MSPM0, HPM)", + ) + + def test_the_output_fields_are_the_same_for_every_platform(self): + for model in ("STM32F103C8T6", "MSPM0G3507SPMR"): + with self.subTest(model=model): + info = layout_to_dict(layout_pins(model)) + self.assertEqual( + list(info), + [ + "model", + "platform", + "part", + "package", + "pin_count", + "source", + "peripherals", + "pins", + ], + ) + self.assertLessEqual({"position", "name", "type", "signals"}, set(info["pins"][0])) + + +class Recognition(TestCase): + """外设从信号名识别,与 LibXR 是否有对应的外设无关。 + Peripherals are recognized from the signal names, whether or not LibXR has a matching + peripheral. + """ + + def test_st_signal_names(self): + for signal, expected in ( + ("USART1_TX", ("USART1", "USART", "TX")), + ("I2C2_SCL", ("I2C2", "I2C", "SCL")), + ("FDCAN1_RX", ("FDCAN1", "FDCAN", "RX")), + ("ETH_MII_TXD1", ("ETH", "ETH", "MII_TXD1")), + ("FMC_A23", ("FMC", "FMC", "A23")), + ("TIM2_CH1", ("TIM2", "TIM", "CH1")), + ("ADC1_INP19", ("ADC1", "ADC", "INP19")), + ("USB_OTG_HS_DM", ("USB_OTG_HS", "USB_OTG", "DM")), + ("USB_DRD_FS_DP", ("USB_DRD_FS", "USB_DRD", "DP")), + ("OCTOSPIM_P1_IO2", ("OCTOSPIM_P1", "OCTOSPIM", "IO2")), + ("CEC", ("CEC", "CEC", "CEC")), + ("BOOT0", ("BOOT0", "BOOT", "BOOT0")), + ): + with self.subTest(signal=signal): + self.assertEqual(pin_layout.recognize_stm32(signal), expected) + self.assertIsNone(pin_layout.recognize_stm32("GPIO")) + + def test_ti_signal_names(self): + for signal, expected in ( + ("UART0.TX", ("UART0", "UART", "TX")), + ("TIMA0.CCP0", ("TIMA0", "TIMA", "CCP0")), + ("SYSCTL.FCC_IN", ("SYSCTL", "SYSCTL", "FCC_IN")), + ("COMP2.IN0-", ("COMP2", "COMP", "IN0-")), + ): + with self.subTest(signal=signal): + self.assertEqual(pin_layout.recognize_mspm0(signal), expected) + for name in ("PA0", "PB22", "PA17/PA14"): + with self.subTest(name=name): + self.assertIsNone(pin_layout.recognize_mspm0(name)) + + def test_peripherals_without_a_libxr_object_are_recognized(self): + # LibXR 没有 ETH 和 OctoSPI 的抽象,它们仍然要被认出来,并给出可选的引脚。 + # LibXR has no abstraction for ETH or OctoSPI; they are still recognized, with their pins. + peripherals = layout_to_dict(layout_pins("STM32H723VGT6"))["peripherals"] + self.assertEqual(peripherals["ETH"]["kind"], "ETH") + self.assertEqual(peripherals["ETH"]["signals"]["CRS"], ["PA0"]) + self.assertIn("IO2", peripherals["OCTOSPIM_P1"]["signals"]) + self.assertEqual(peripherals["USB_OTG_HS"]["signals"]["DM"], ["PA11"]) + + def test_a_function_lists_the_pins_that_can_carry_it(self): + usart1 = layout_to_dict(layout_pins("STM32H723VGT6"))["peripherals"]["USART1"] + self.assertEqual(sorted(usart1["signals"]["TX"]), ["PA9", "PB14", "PB6"]) + uart0 = layout_to_dict(layout_pins("MSPM0G3507SPMR"))["peripherals"]["UART0"] + self.assertEqual(sorted(uart0["signals"]["TX"]), ["PA0", "PA10", "PA28", "PB0"]) + + def test_plain_gpio_is_listed_by_port_and_line(self): + for model, pin in (("STM32H723VGT6", "PA9"), ("MSPM0G3507SPMR", "PA9")): + with self.subTest(model=model): + gpio_a = layout_to_dict(layout_pins(model))["peripherals"]["GPIOA"] + self.assertEqual(gpio_a["kind"], "GPIO") + self.assertEqual(gpio_a["signals"]["P9"], [pin]) + + def test_external_interrupt_lines_list_the_pins_that_can_use_them(self): + # 线号等于引脚号:LINE9 可以来自任一端口的第 9 脚(ST 数据中 GPIO 带 EXTI 模式)。 + # The line number is the pin number: LINE9 can come from pin 9 of any port (the GPIO has + # the EXTI mode in ST's data). + peripherals = layout_to_dict(layout_pins("STM32H723VGT6"))["peripherals"] + self.assertEqual( + sorted(peripherals["EXTI"]["signals"]["LINE9"]), ["PA9", "PB9", "PC9", "PD9", "PE9"] + ) + info = layout_to_dict(layout_pins("STM32H723VGT6")) + modes = {pin["name"]: pin.get("gpio_modes") for pin in info["pins"]} + self.assertIn("EXTI", modes["PE2"]) + self.assertIsNone(modes["NRST"]) + + def test_the_ti_data_does_not_say_which_pins_have_interrupts(self): + # TI 的器件数据没有这一项,所以不列出 EXTI,而不是猜测。 + # TI's device data has no such field, so EXTI is not listed rather than guessed. + peripherals = layout_to_dict(layout_pins("MSPM0G3507SPMR"))["peripherals"] + self.assertNotIn("EXTI", peripherals) + + def test_timers_with_output_channels_are_marked_pwm(self): + peripherals = layout_to_dict(layout_pins("STM32H723VGT6"))["peripherals"] + self.assertEqual(peripherals["TIM1"]["capabilities"], ["pwm"]) + self.assertEqual(peripherals["LPTIM1"]["capabilities"], ["pwm"]) + self.assertNotIn("capabilities", peripherals["USART1"]) + ti = layout_to_dict(layout_pins("MSPM0G3507SPMR"))["peripherals"] + self.assertEqual(ti["TIMA0"]["capabilities"], ["pwm"]) + self.assertNotIn("capabilities", ti["UART0"]) + + def test_peripherals_sort_naturally(self): + names = list(layout_to_dict(layout_pins("STM32H723VGT6"))["peripherals"]) + self.assertLess(names.index("USART2"), names.index("USART10")) + + def test_every_signal_in_the_data_is_recognized_except_plain_gpio(self): + # ST 的 GPIO 和 TI 的引脚名(PA0、PA17/PA14)是引脚的普通用法,其余都是外设信号。 + # ST's GPIO and TI's pin names (PA0, PA17/PA14) are the plain use of the pin; the rest + # are peripheral signals. + st = pin_layout.load_data("stm32")["parts"] + for shard in {shard for shard, _ in st.values()}: + for pin_set in pin_layout.load_stm32_shard(shard).values(): + for _, _, _, signals, _ in pin_set["pins"]: + for signal in signals: + if signal != "GPIO": + self.assertIsNotNone(pin_layout.recognize_stm32(signal), signal) + for family in pin_layout.load_data("mspm0")["families"].values(): + for pins in family["packages"].values(): + for _, _, _, _, signals in pins: + for signal, _ in signals: + if not re.fullmatch(r"P[A-C]\d+(/P[A-C]\d+)*", signal): + self.assertIsNotNone(pin_layout.recognize_mspm0(signal), signal) + + +class DataFiles(TestCase): + """数据带着来源和许可证文本一起分发。 + The data is distributed with its source and the license texts. + """ + + def test_every_data_set_records_its_source_and_license(self): + for platform, vendor in (("stm32", "STMicroelectronics"), ("mspm0", "Texas Instruments")): + with self.subTest(platform=platform): + source = pin_layout.load_data(platform)["source"] + self.assertEqual(source["vendor"], vendor) + self.assertTrue(source["license"]) + self.assertRegex(pin_layout.load_data("stm32")["source"]["commit"], r"^[0-9a-f]{40}$") + self.assertRegex(pin_layout.load_data("mspm0")["source"]["version"], r"^\d+\.\d+\.\d+") + + def test_the_license_texts_are_next_to_the_data(self): + st = (pin_layout.DATA / "LICENSE-ST.txt").read_text(encoding="utf-8") + ti = (pin_layout.DATA / "LICENSE-TI.txt").read_text(encoding="utf-8") + self.assertIn("BSD 3-Clause License", st) + self.assertIn("STMicroelectronics", st) + self.assertIn("Texas Instruments Incorporated", ti) + self.assertIn("TI Devices", ti) + + def test_every_part_points_to_a_pin_set_in_an_existing_shard(self): + parts = pin_layout.load_data("stm32")["parts"] + for shard in {shard for shard, _ in parts.values()}: + self.assertTrue((pin_layout.DATA / f"stm32-{shard}.json.gz").is_file(), shard) + for name, (shard, key) in parts.items(): + with self.subTest(part=name): + self.assertIn(key, pin_layout.load_stm32_shard(shard)) + + def test_a_layout_reads_only_the_shard_of_its_series(self): + pin_layout.load_stm32_shard.cache_clear() + layout_pins("STM32H723VGT6") + self.assertEqual(pin_layout.load_stm32_shard.cache_info().currsize, 1) + + def test_the_data_files_are_declared_as_package_data(self): + # CI 在装好的包上运行测试,所以仓库的位置取自测试文件,不取自包。 + # CI runs the tests on the installed package, so the repository is found from the test + # file, not from the package. + project = (Path(__file__).resolve().parents[1] / "pyproject.toml").read_text( + encoding="utf-8" + ) + self.assertIn('"pin_data/*"', project) + + +class CommandLine(TestCase): + """布局写到标准输出,报错以错误日志给出。 + The layout goes to stdout, and errors are logged. + """ + + def run_pins(self, *argv): + """以这些参数运行 libxr pins,返回退出码、标准输出和标准错误。 + Run libxr pins with these arguments; return the exit code, stdout and stderr. + """ + return run_libxr("pins", *argv) + + def test_the_layout_is_yaml_by_default(self): + code, out, err = self.run_pins("STM32F103C8T6") + self.assertEqual((code, err), (0, "")) + info = yaml.safe_load(out) + self.assertEqual( + (info["model"], info["platform"], info["package"], info["pin_count"]), + ("STM32F103C8T6", "stm32", "LQFP48", 48), + ) + self.assertEqual(len(info["pins"]), 48) + + def test_the_layout_can_be_json(self): + code, out, err = self.run_pins("MSPM0G3507SPMR", "--format", "json") + self.assertEqual((code, err), (0, "")) + info = json.loads(out) + self.assertEqual((info["package"], info["pin_count"]), ("LQFP-64(PM)", 64)) + self.assertEqual(info["pins"][32]["name"], "PA0") + + def test_the_package_can_be_given(self): + code, out, _ = self.run_pins("MSPM0G3507", "-p", "VQFN-48") + self.assertEqual(code, 0) + self.assertEqual(yaml.safe_load(out)["package"], "VQFN-48(RGZ)") + + def test_help(self): + code, out, err = self.run_pins("--help") + self.assertEqual((code, err), (0, "")) + self.assertTrue(out.startswith("usage: libxr pins [-h] [-d DIRECTORY]"), out) + self.assertIn("libxr pins STM32H723VGT6", out) + + def test_errors_are_logged(self): + for argv, reason in ( + (("CH32V203C8T6",), pin_layout_error("CH32V203C8T6")), + (("STM32Q999RGT6",), "Unknown STM32 model: STM32Q999RGT6"), + ( + ("STM32H723VGT6", "-p", "LQFP64"), + "STM32H723VGT6 comes in LQFP100, not LQFP64; the package is part of the STM32 " + "model", + ), + ): + with self.subTest(argv=argv), self.assertLogs(level="ERROR") as logs: + code, out, _ = self.run_pins(*argv) + self.assertEqual((code, out), (1, "")) + self.assertEqual(logs.output, [f"ERROR:root:{reason}"]) + + +def pin_layout_error(model): + """没有支持的平台时的报错。 + The error for a model without a supported platform. + """ + return f"No supported platform for the model {model} (supported: STM32, MSPM0, HPM)" + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_pin_project.py b/tests/test_pin_project.py new file mode 100644 index 0000000..61fdac5 --- /dev/null +++ b/tests/test_pin_project.py @@ -0,0 +1,1062 @@ +"""libxr pins -d(libxr.pin_project):把工程已选的引脚信号叠加到布局上。 +libxr pins -d (libxr.pin_project): overlay the pin signals a project has selected on the layout. +""" + +import json +import tempfile +import textwrap +import unittest +from pathlib import Path + +import yaml +from fixtures import IOC, TestCase, run_libxr + +from libxr import pin_layout, pin_project +from libxr.pin_project import layout_with_project + +CONFIG = textwrap.dedent("""\ + USART: + usart1: + tx_buffer_size: 128 + rx_buffer_size: 64 + SPI: + spi1: + dma_section: .axi_ram +""") + + +def write(directory: Path, name: str, text: str) -> Path: + """在 directory 下写出文本文件 name(必要时建目录),返回其路径。 + Write the text file name under directory, creating the folders it needs; return its path. + """ + path = directory / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + return path + + +def ioc(model: str, enabled: list[str], pins: dict[str, str]) -> str: + """一个最小的 .ioc:型号、启用的外设和 引脚 -> 信号。 + A minimal .ioc: the model, the enabled peripherals and pin -> signal. + """ + lines = [f"Mcu.UserName={model}"] + lines += [f"Mcu.IP{index}={name}" for index, name in enumerate(enabled)] + lines += [f"{pin}.Signal={signal}" for pin, signal in pins.items()] + return "\n".join(lines) + "\n" + + +class Stm32Projects(TestCase): + """STM32 工程:已选信号取自 .ioc,设置取自 libxr_config.yaml。 + An STM32 project: the selected signals come from the .ioc, the settings from + libxr_config.yaml. + """ + + def project(self, directory, config=CONFIG, model=None, config_path=None): + """工程目录中的叠加结果(工程 .ioc 为共用的样例,config 是 User/libxr_config.yaml 的内容)。 + The overlay of the project directory (its .ioc is the shared sample, config is the + content of User/libxr_config.yaml). + """ + write(Path(directory), "Project.ioc", IOC) + if config is not None: + write(Path(directory), "User/libxr_config.yaml", config) + return layout_with_project(directory, model, None, config_path) + + def test_the_model_and_the_signals_come_from_the_ioc(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory) + self.assertEqual((info["part"], info["package"]), ("STM32F407IGHx", "UFBGA176")) + self.assertEqual(info["project"]["source"], "Project.ioc") + assignments = info["project"]["assignments"] + self.assertEqual( + assignments["PA9"], + { + "signal": "USART1_TX", + "peripheral": "USART1", + "kind": "USART", + "function": "TX", + "matched": True, + }, + ) + self.assertTrue(all(entry["matched"] for entry in assignments.values())) + + def test_gpio_labels_and_external_interrupts(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory) + assignments = info["project"]["assignments"] + # ST 给 PC13 的名字带功能后缀(PC13-ANTI_TAMP);叠加结果用布局中的名字。 + # ST names PC13 with its function (PC13-ANTI_TAMP); the overlay uses the name in the + # layout. + led = next(entry for name, entry in assignments.items() if name.startswith("PC13")) + self.assertEqual( + (led["label"], led["peripheral"], led["function"]), ("LED", "GPIOC", "P13") + ) + self.assertEqual( + (assignments["PE12"]["peripheral"], assignments["PE12"]["function"]), + ("EXTI", "LINE12"), + ) + self.assertEqual(info["project"]["peripherals"]["EXTI"]["pins"], {"LINE12": "PE12"}) + + def test_shared_pin_signals_are_resolved(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory) + tim1 = info["project"]["peripherals"]["TIM1"] + self.assertEqual((tim1["kind"], tim1["pins"]), ("TIM", {"CH1": "PE9"})) + + def test_a_peripheral_is_tied_to_its_libxr_config_settings(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory) + peripherals = info["project"]["peripherals"] + self.assertEqual( + peripherals["USART1"]["config"], + [ + { + "section": "USART", + "key": "usart1", + "present": True, + "params": {"tx_buffer_size": 128, "rx_buffer_size": 64}, + } + ], + ) + self.assertEqual( + peripherals["USART6"]["config"], + [{"section": "USART", "key": "usart6", "present": False}], + ) + self.assertEqual(peripherals["SPI1"]["config"][0]["params"], {"dma_section": ".axi_ram"}) + self.assertNotIn("config", peripherals["GPIOC"]) + + def test_the_case_of_a_key_in_the_file_is_kept(self): + # 有的 libxr_config.yaml 写 fdcan1,有的写 FDCAN1,两种都要找得到。 + # One libxr_config.yaml writes fdcan1 and another FDCAN1; both have to be found. + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "a.ioc", ioc("STM32H723VGTx", [], {"PD0": "FDCAN1_RX"})) + write( + Path(directory), "User/libxr_config.yaml", "FDCAN:\n FDCAN1:\n queue_size: 5\n" + ) + info = layout_with_project(directory, None, None, None) + self.assertEqual( + info["project"]["peripherals"]["FDCAN1"]["config"], + [{"section": "FDCAN", "key": "FDCAN1", "present": True, "params": {"queue_size": 5}}], + ) + + def test_a_missing_configuration_file_is_not_an_error(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory, config=None) + self.assertIsNone(info["project"]["libxr_config"]) + self.assertFalse(info["project"]["peripherals"]["USART1"]["config"][0]["present"]) + + def test_another_configuration_file_can_be_given(self): + with tempfile.TemporaryDirectory() as directory: + other = write( + Path(directory), "other.yaml", "SPI:\n spi1:\n dma_enable_min_size: 8\n" + ) + info = self.project(directory, config_path=str(other)) + self.assertEqual( + info["project"]["peripherals"]["SPI1"]["config"][0]["params"], + {"dma_enable_min_size": 8}, + ) + self.assertFalse(info["project"]["peripherals"]["USART1"]["config"][0]["present"]) + + def test_a_given_model_wins_over_the_ioc(self): + with tempfile.TemporaryDirectory() as directory: + info = self.project(directory, model="STM32F407IGT6") + self.assertEqual((info["part"], info["package"]), ("STM32F407IGTx", "LQFP176")) + + +class CubeMxSpellings(TestCase): + """CubeMX 在 .ioc 里的几种写法,都对应到 ST 的信号。 + The forms CubeMX writes in an .ioc, each tied to ST's signals. + """ + + def overlay(self, model, enabled, pins): + """这个 .ioc 的已选信号。 + The selected signals of this .ioc. + """ + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "a.ioc", ioc(model, enabled, pins)) + info = layout_with_project(directory, None, None, None) + return info["project"]["assignments"] + + def test_an_adc_channel_takes_the_adc_the_project_enables(self): + found = self.overlay("STM32H723VGTx", ["ADC1"], {"PA5": "ADCx_INP19"})["PA5"] + self.assertEqual((found["peripheral"], found["function"]), ("ADC1", "INP19")) + self.assertTrue(found["matched"]) + + def test_an_adc_channel_with_several_candidates_lists_them(self): + found = self.overlay("STM32H723VGTx", [], {"PA5": "ADCx_INP19"})["PA5"] + self.assertEqual(found["peripheral"], "ADC") + self.assertEqual(found["candidates"], ["ADC1", "ADC2"]) + + def test_two_functions_that_share_a_prefix(self): + found = self.overlay("STM32H563ZITx", [], {"PD0": "FMC_D2_DA2"})["PD0"] + self.assertEqual((found["peripheral"], found["function"]), ("FMC", "D2/DA2")) + self.assertTrue(found["matched"]) + + def test_the_mii_or_rmii_infix_may_be_left_out(self): + found = self.overlay("STM32H563ZITx", [], {"PB15": "ETH_TXD1"})["PB15"] + self.assertEqual((found["peripheral"], found["function"]), ("ETH", "TXD1")) + + def test_analog_pins(self): + found = self.overlay("STM32G071RBTx", [], {"PA4": "SharedAnalog_PA4"})["PA4"] + self.assertEqual((found["peripheral"], found["kind"]), ("ANALOG", "ANALOG")) + + def test_a_signal_the_pin_does_not_have_is_given_as_it_is(self): + found = self.overlay("STM32H723VGTx", [], {"PA9": "USART3_TX"})["PA9"] + self.assertEqual((found["peripheral"], found["function"]), ("USART3", "TX")) + self.assertFalse(found["matched"]) + + def test_a_pin_with_a_suffix_in_its_name(self): + # .ioc 里的键写成 PH0-OSC_IN\ (PH0)(空格转义);布局里的引脚叫 PH0-OSC_IN。 + # The .ioc key reads PH0-OSC_IN\ (PH0), the space escaped; the pin is PH0-OSC_IN in the + # layout. + found = self.overlay("STM32H723VGTx", [], {r"PH0-OSC_IN\ (PH0)": "RCC_OSC_IN"}) + self.assertEqual(list(found), ["PH0-OSC_IN"]) + self.assertEqual(found["PH0-OSC_IN"]["peripheral"], "RCC") + + +class SyscfgPackage(TestCase): + """MSPM0 的封装取自 SysConfig 工程的 .syscfg:--package,或 --board 对应的 LaunchPad。 + The package of an MSPM0 comes from the .syscfg of the SysConfig project: its --package, or the + LaunchPad its --board names. + """ + + def package(self, syscfg: str, option=None): + """给出的根目录 .syscfg 内容下,叠加结果里的封装。 + The package in the overlay for this root .syscfg content. + """ + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "a.syscfg", syscfg) + return layout_with_project(directory, None, option, None)["package"] + + def test_a_device_based_project_names_its_package(self): + for line in ( + '//@cliArgs --device "MSPM0G350X" --package "LQFP-64(PM)" --part "Default"', + ' * @cliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"', + ): + with self.subTest(line=line): + self.assertEqual(self.package(line + "\n"), "LQFP-64(PM)") + + def test_a_board_based_project_gets_the_package_of_the_board(self): + for line in ( + "// @cliArgs --board /ti/boards/LP_MSPM0G3507 --rtos nortos", + ' * @cliArgs --board "/ti/boards/LP_MSPM0G3507" --product "mspm0_sdk@2.09.00.00"', + ): + with self.subTest(line=line): + self.assertEqual(self.package(line + "\n"), "LQFP-64(PM)") + + def test_the_v2_line_comes_last_and_wins(self): + text = ( + '//@cliArgs --device "MSPM0G350X" --package "VQFN-48(RGZ)" --part "Default"\n' + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + ) + self.assertEqual(self.package(text), "LQFP-64(PM)") + + def test_a_given_package_wins_over_the_project(self): + text = '//@cliArgs --device "MSPM0G350X" --package "LQFP-64(PM)"\n' + self.assertEqual(self.package(text, option="VQFN-48"), "VQFN-48(RGZ)") + + def test_without_a_package_the_error_asks_for_one(self): + with self.assertRaises(ValueError) as caught: + self.package('//@cliArgs --device "MSPM0G3507"\n') + self.assertTrue( + str(caught.exception).startswith("Cannot tell the package of MSPM0G3507"), + str(caught.exception), + ) + + def test_a_syscfg_without_a_device_or_a_known_board_needs_the_model(self): + # 板子不在表里,器件也就无从得知。 + # The board is not in the table, so the device cannot be known either. + with self.assertRaises(ValueError) as caught: + self.package("// @cliArgs --board /ti/boards/LP_UNKNOWN\n") + self.assertTrue( + str(caught.exception).endswith("does not name a device; give the model"), + str(caught.exception), + ) + + def test_the_board_table_has_the_launchpads_with_their_devices(self): + boards = pin_project.load_boards() + self.assertEqual( + boards["LP_MSPM0G3507"], {"device": "MSPM0G3507", "package": "LQFP-64(PM)"} + ) + self.assertEqual(boards["LP_MSPM0C1106"]["device"], "MSPM0C1106") + self.assertTrue(all(board["package"] for board in boards.values())) + + def test_the_board_table_ships_with_its_license(self): + text = (pin_layout.DATA / "LICENSE-TI-BOARDS.txt").read_text(encoding="utf-8") + self.assertIn("Texas Instruments Incorporated", text) + self.assertIn("Redistribution and use in source and binary forms", text) + source = json.loads((pin_layout.DATA / "mspm0_boards.json").read_text(encoding="utf-8")) + self.assertRegex(source["source"]["commit"], r"^[0-9a-f]{40}$") + + +class SyscfgProjects(TestCase): + """MSPM0 工程:根目录的 .syscfg 直接给出器件、封装和已选信号,不需要跑 SysConfig。 + An MSPM0 project: a root .syscfg gives the device, the package and the selected signals + directly; SysConfig does not have to run. + """ + + PROJECT = textwrap.dedent("""\ + //@cliArgs --device "MSPM0G350X" --part "Default" --package "LQFP-64(PM)" + //@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)" + + const Board = scripting.addModule("/ti/driverlib/Board", {}, false); + Board.peripheral.$assign = "DEBUGSS"; + Board.peripheral.swdioPin.$assign = "PA19"; + + const pinFunction1 = system.clockTree["HFXT"]; + pinFunction1.peripheral.hfxInPin.$assign = "PA5"; + + const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); + const GPIO1 = GPIO.addInstance(); + GPIO1.$name = "GPIO_KEYS"; + GPIO1.associatedPins.create(1); + GPIO1.associatedPins[0].$name = "PIN_KEY1"; + GPIO1.associatedPins[0].pin.$assign = "PB22"; + + const UART = scripting.addModule("/ti/driverlib/UART", {}, false); + const UART1 = UART.addInstance(); + UART1.$name = "UART_0"; + UART1.peripheral.$assign = "UART0"; + UART1.peripheral.txPin.$assign = "PA0"; + UART1.peripheral.rxPin.$assign = "PA1"; + + const DAC12 = scripting.addModule("/ti/driverlib/DAC12"); + DAC12.peripheral.$assign = "DAC0"; + DAC12.peripheral.OutPin.$assign = "PA15"; + """) + + def project(self, syscfg=PROJECT, model=None): + """这个根目录 .syscfg 的叠加结果。 + The overlay of this root .syscfg. + """ + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "a.syscfg", syscfg) + return layout_with_project(directory, model, None, None) + + def test_the_device_the_package_and_the_assignments_come_from_the_syscfg(self): + info = self.project() + self.assertEqual((info["part"], info["package"]), ("MSPM0G3507", "LQFP-64(PM)")) + self.assertEqual(info["project"]["source"], "a.syscfg") + assignments = info["project"]["assignments"] + self.assertEqual(assignments["PA0"]["signal"], "UART0.TX") + self.assertTrue(assignments["PA0"]["matched"]) + self.assertEqual(assignments["PA1"]["signal"], "UART0.RX") + gpio = assignments["PB22"] + self.assertEqual( + (gpio["peripheral"], gpio["function"], gpio["label"]), + ("GPIOB", "P22", "KEY1"), + ) + + def test_settings_follow_what_gen_generates(self): + # 审查 A1:目标模式的 I2C 和外设模式的 SPI 不生成对象,也就没有 libxr_config 条目。 + # Review A1: an I2C in target mode and an SPI in peripheral mode generate no object, so + # they have no libxr_config entry. + syscfg = self.PROJECT + textwrap.dedent("""\ + const I2C = scripting.addModule("/ti/driverlib/I2C", {}, false); + const I2C1 = I2C.addInstance(); + const I2C2 = I2C.addInstance(); + I2C1.$name = "I2C_TARGET"; + I2C1.basicEnableTarget = true; + I2C1.peripheral.$assign = "I2C1"; + I2C1.peripheral.sdaPin.$assign = "PB3"; + I2C1.peripheral.sclPin.$assign = "PB2"; + I2C2.$name = "I2C_0"; + I2C2.basicEnableController = true; + I2C2.peripheral.$assign = "I2C0"; + I2C2.peripheral.sdaPin.$assign = "PA10"; + I2C2.peripheral.sclPin.$assign = "PA11"; + const SPI = scripting.addModule("/ti/driverlib/SPI", {}, false); + const SPI1 = SPI.addInstance(); + SPI1.$name = "SPI_PERIPH"; + SPI1.mode = "PERIPHERAL"; + SPI1.peripheral.$assign = "SPI0"; + SPI1.peripheral.sclkPin.$assign = "PA6"; + """) + peripherals = self.project(syscfg)["project"]["peripherals"] + self.assertNotIn("config", peripherals["I2C1"]) + self.assertNotIn("config", peripherals["SPI0"]) + self.assertEqual(peripherals["I2C0"]["config"][0]["key"], "i2c0") + + def test_gpio_pins_by_port_and_number_and_their_object_names(self): + # 审查 A3:TI 例程的端口加引脚号写法也读得到;标签是 gen 生成的对象名(PIN_0 得到 + # gpio_btn_pin_0)。 + # Review A3: the port plus pin number form of TI's examples is read as well; the label + # is the object name gen generates (PIN_0 gives gpio_btn_pin_0). + syscfg = self.PROJECT + textwrap.dedent("""\ + const GPIO2 = GPIO.addInstance(); + const GPIO3 = GPIO.addInstance(); + GPIO2.$name = "GPIO_LEDS"; + GPIO2.port = "PORTA"; + GPIO2.associatedPins.create(1); + GPIO2.associatedPins[0].$name = "USER_LED_1"; + GPIO2.associatedPins[0].assignedPin = "26"; + GPIO3.$name = "GPIO_BTN"; + GPIO3.associatedPins.create(1); + GPIO3.associatedPins[0].$name = "PIN_0"; + GPIO3.associatedPins[0].pin.$assign = "PA7"; + """) + assignments = self.project(syscfg)["project"]["assignments"] + self.assertEqual(assignments["PA26"]["label"], "USER_LED_1") + self.assertEqual(assignments["PA7"]["label"], "gpio_btn_pin_0") + + def test_a_singleton_module_is_its_own_instance(self): + # DAC12 没有 addInstance,直接配在模块变量上。 + # DAC12 has no addInstance; the module variable is configured directly. + self.assertEqual(self.project()["project"]["assignments"]["PA15"]["signal"], "DAC0.OUT") + + def test_the_swd_pins_of_the_board_are_shown(self): + self.assertEqual(self.project()["project"]["assignments"]["PA19"]["peripheral"], "DEBUGSS") + + def test_the_pins_of_the_clock_tree_are_left_out(self): + self.assertNotIn("PA5", self.project()["project"]["assignments"]) + + def test_a_canfd_member_matches_by_its_suffix(self): + # TI 的功能名是 CANRX、CANTX,成员名是 rxPin、txPin。 + # TI names the functions CANRX and CANTX; the members are rxPin and txPin. + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const MCAN = scripting.addModule("/ti/driverlib/MCAN", {}, false);\n' + "const MCAN1 = MCAN.addInstance();\n" + 'MCAN1.peripheral.$assign = "CANFD0";\n' + 'MCAN1.peripheral.rxPin.$assign = "PA13";\n' + 'MCAN1.peripheral.txPin.$assign = "PA12";\n' + ) + assignments = self.project(syscfg)["project"]["assignments"] + self.assertEqual(assignments["PA13"]["signal"], "CANFD0.CANRX") + self.assertEqual(assignments["PA12"]["signal"], "CANFD0.CANTX") + + def test_a_member_the_pin_does_not_have_is_given_as_it_is(self): + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const UART = scripting.addModule("/ti/driverlib/UART", {}, false);\n' + "const UART1 = UART.addInstance();\n" + 'UART1.peripheral.$assign = "UART9";\n' + 'UART1.peripheral.rxPin.$assign = "PA0";\n' + ) + found = self.project(syscfg)["project"]["assignments"]["PA0"] + self.assertEqual((found["signal"], found["matched"]), ("UART9.RX", False)) + + def test_the_suggested_peripheral_is_used_without_an_assignment(self): + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const UART = scripting.addModule("/ti/driverlib/UART", {}, false);\n' + "const UART1 = UART.addInstance();\n" + 'UART1.peripheral.$suggestSolution = "UART0";\n' + 'UART1.peripheral.txPin.$assign = "PA0";\n' + ) + found = self.project(syscfg)["project"]["assignments"]["PA0"] + self.assertEqual(found["peripheral"], "UART0") + + def test_dma_channels_are_not_pins(self): + syscfg = self.PROJECT + 'UART1.DMA_CHANNEL_TX.peripheral.$assign = "DMA_CH3";\n' + peripherals = self.project(syscfg)["project"]["peripherals"] + self.assertNotIn("DMA_CH3", peripherals) + + def test_two_syscfg_files_in_the_root_are_an_error(self): + with tempfile.TemporaryDirectory() as directory, self.assertRaises(ValueError) as caught: + write(Path(directory), "a.syscfg", '//@cliArgs --device "MSPM0G3507"\n') + write(Path(directory), "b.syscfg", '//@cliArgs --device "MSPM0G3507"\n') + layout_with_project(directory, None, None, None) + self.assertTrue( + str(caught.exception).endswith("several .syscfg files in the root; keep one"), + str(caught.exception), + ) + + def test_a_syscfg_without_a_device_needs_the_model(self): + with tempfile.TemporaryDirectory() as directory, self.assertRaises(ValueError) as caught: + write(Path(directory), "a.syscfg", '// @cliArgs --package "LQFP-64(PM)"\n') + layout_with_project(directory, None, None, None) + self.assertTrue( + str(caught.exception).endswith("does not name a device; give the model"), + str(caught.exception), + ) + info = self.project('//@cliArgs --package "LQFP-64(PM)"\n', model="MSPM0G3507") + self.assertEqual(info["part"], "MSPM0G3507") + + def test_an_mspm0_peripheral_is_tied_to_its_libxr_config_settings(self): + peripherals = self.project()["project"]["peripherals"] + self.assertEqual( + peripherals["UART0"]["config"], + [{"section": "UART", "key": "uart0", "present": False}], + ) + # MSPM0 的 GPIO 名字来自 .syscfg 的标签,DAC 也不生成设置段。 + # The GPIO names of an MSPM0 come from the labels of the .syscfg, and a DAC has no + # settings section either. + self.assertNotIn("config", peripherals["GPIOB"]) + self.assertNotIn("config", peripherals["DAC0"]) + + def test_a_pwm_instance_has_an_entry_per_channel(self): + # PWM 的对象名是 pwm_<定时器>_c<通道>,一个实例每个通道一个条目。 + # The object names of a PWM are pwm__c; one entry per channel of the + # instance. + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const PWM = scripting.addModule("/ti/driverlib/PWM", {}, false);\n' + "const PWM1 = PWM.addInstance();\n" + 'PWM1.$name = "PWM_TIMA1";\n' + 'PWM1.peripheral.$assign = "TIMA1";\n' + 'PWM1.peripheral.ccp0Pin.$assign = "PA28";\n' + 'PWM1.peripheral.ccp1Pin.$assign = "PA31";\n' + ) + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "a.syscfg", syscfg) + write( + Path(directory), + "User/libxr_config.yaml", + "PWM:\n pwm_tima1_c0:\n frequency: 1000\n", + ) + info = layout_with_project(directory, None, None, None) + self.assertEqual( + info["project"]["peripherals"]["TIMA1"]["config"], + [ + { + "section": "PWM", + "key": "pwm_tima1_c0", + "present": True, + "params": {"frequency": 1000}, + }, + {"section": "PWM", "key": "pwm_tima1_c1", "present": False}, + ], + ) + + def test_the_pins_the_solver_picked_are_a_fallback(self): + # 求解器选的引脚($suggestSolution)也算数;测试用的 PA0 是 UART0.TX。 + # The pins the solver picked ($suggestSolution) count; PA0 is UART0.TX in the layout. + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const UART = scripting.addModule("/ti/driverlib/UART", {}, false);\n' + "const UART1 = UART.addInstance();\n" + 'UART1.peripheral.$assign = "UART0";\n' + 'UART1.peripheral.txPin.$suggestSolution = "PA0";\n' + ) + self.assertEqual( + self.project(syscfg)["project"]["assignments"]["PA0"]["signal"], "UART0.TX" + ) + + def test_an_assignment_wins_over_the_suggestion(self): + # 同一个成员既有 $assign 又有 $suggestSolution 时,$assign 赢(PA0 被换成 PA28)。 + # When a member has both $assign and $suggestSolution, $assign wins (PA0 becomes PA28). + syscfg = ( + '//@v2CliArgs --device "MSPM0G3507" --package "LQFP-64(PM)"\n' + 'const UART = scripting.addModule("/ti/driverlib/UART", {}, false);\n' + "const UART1 = UART.addInstance();\n" + 'UART1.peripheral.$assign = "UART0";\n' + 'UART1.peripheral.txPin.$suggestSolution = "PA0";\n' + 'UART1.peripheral.txPin.$assign = "PA28";\n' + ) + assignments = self.project(syscfg)["project"]["assignments"] + self.assertEqual(assignments["PA28"]["signal"], "UART0.TX") + self.assertNotIn("PA0", assignments) + + +SYSCFG = textwrap.dedent("""\ + /** + * These arguments were used when this file was generated. + */ + // @cliArgs --board /ti/boards/LP_MSPM0G3507 --rtos nortos + const GPIO = scripting.addModule("/ti/driverlib/GPIO", {}, false); + const GPIO1 = GPIO.addInstance(); + const UART = scripting.addModule("/ti/driverlib/UART", {}, false); + const UART1 = UART.addInstance(); + const SPI = scripting.addModule("/ti/driverlib/SPI", {}, false); + const SPI1 = SPI.addInstance(); + const SYSCTL = system.modules["/ti/driverlib/SYSCTL"].$static; + + GPIO1.$name = "GPIO_GRP_0"; + GPIO1.associatedPins[0].assignedPin = "22"; + + UART1.$name = "UART_0"; + UART1.enabledInterrupts = ["RX","TX"]; + UART1.targetBaudRate = 2000000; + UART1.enableFIFO = false; + UART1.ovsRate = "3"; + UART1.txPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric0"; + UART1.peripheral.$assign = "UART0"; + UART1.peripheral.txPin.$assign = "PA0"; + + SPI1.$name = "SPI_0"; + SPI1.targetBitRate = 0x100000; + SPI1.peripheral.$suggestSolution = "SPI1"; + + SYSCTL.powerPolicy = "STANDBY0"; + // UART1.targetBaudRate = 9600; +""") + + +class HpmProjects(TestCase): + """HPM 工程:SoC 和封装取自 boards/ 下的 .hpmpc,已选信号取根目录 main.c 无条件调用的 pinmux + 函数。 + An HPM project: the SoC and the package come from the .hpmpc under boards/, and the selected + signals from the pinmux functions the root main.c calls without a condition. + """ + + HPMPC = json.dumps( + { + "content": { + "info": {"socName": "HPM5301", "packageName": "QFN48"}, + "pinmux": { + "functions": { + "init_bsp_pins": { + "selectPins": { + "PA00": {"signal": "UART0.A.TXD", "padCtls": {}}, + "PA03": {"signal": "I2C0.A.SCL", "padCtls": {}}, + "PA10": {"signal": "GPIO.A.A[10]", "padCtls": {}}, + } + }, + "init_uart0_pins": { + "selectPins": { + "PA00": {"signal": "UART0.A.TXD", "padCtls": {}}, + "PA01": {"signal": "UART0.A.RXD", "padCtls": {}}, + } + }, + "init_jtag_pins": { + "selectPins": {"PA04": {"signal": "GPIO.A.A[04]", "padCtls": {}}} + }, + } + }, + } + } + ) + MAIN = textwrap.dedent( + """\ + int main() + { + board_init(); + init_bsp_pins(); + init_uart0_pins(); + #if !RMCS_KEEP_JTAG + init_jtag_pins(); + #endif + } + """ + ) + + def project(self, files): + """给定文件的 HPM 工程的叠加结果。 + The overlay of an HPM project with these files. + """ + with tempfile.TemporaryDirectory() as directory: + for name, text in files.items(): + write(Path(directory), name, text) + return layout_with_project(directory, None, None, None) + + def project_files(self, main=MAIN): + """一个最小 HPM 工程的文件。 + The files of a minimal HPM project. + """ + return { + "app.yaml": "dependency: []\n", + "boards/board/tool_config.hpmpc": self.HPMPC, + "main.c": main, + } + + def test_the_soc_the_package_and_the_called_functions_drive_the_overlay(self): + info = self.project(self.project_files()) + self.assertEqual((info["part"], info["package"]), ("HPM5301", "QFN48")) + self.assertEqual(info["project"]["source"], "boards/board/tool_config.hpmpc") + assignments = info["project"]["assignments"] + self.assertEqual(assignments["PA00"]["signal"], "UART0_TXD") + self.assertTrue(assignments["PA00"]["matched"]) + self.assertEqual(assignments["PA01"]["signal"], "UART0_RXD") + gpio = assignments["PA10"] + self.assertEqual( + (gpio["peripheral"], gpio["kind"], gpio["function"], gpio["matched"]), + ("GPIOA", "GPIO", "P10", True), + ) + + def test_two_functions_on_one_pad_are_reported(self): + # P3 遗留:init_bsp_pins 和 init_uart0_pins 都选了 PA00;冲突要报告(以前静默后写赢)。 + # P3 leftover: init_bsp_pins and init_uart0_pins both select PA00; the conflict is + # reported (it used to be a silent last-wins). + with self.assertLogs(level="WARNING") as logs: + info = self.project(self.project_files()) + self.assertEqual(len(logs.output), 1) + # 路径是绝对临时目录,Windows 下分隔符是反斜杠;只核对路径之后的部分。 + # The path is an absolute temporary directory whose separator is a backslash on + # Windows; check the part after the path only. + self.assertTrue( + logs.output[0].endswith( + ": PA00 is selected by both init_bsp_pins (UART0.A.TXD) and " + "init_uart0_pins (UART0.A.TXD); the later one wins" + ) + ) + self.assertEqual(info["project"]["assignments"]["PA00"]["signal"], "UART0_TXD") + + def test_the_settings_follow_what_gen_generates(self): + # 审查 A1、A2:缺 SDA 的 I2C 没有条目;GPIO 改名的键不分大小写,保留文件的写法;分给 + # GPIO0 以外控制器的引脚没有改名项(gen 不为它生成对象)。 + # Review A1, A2: an I2C without SDA has no entry; the GPIO rename key matches in any + # case and keeps the file's spelling; a pin on a controller other than GPIO0 has no + # rename (gen makes no object for it). + hpmpc = json.loads(self.HPMPC) + function = hpmpc["content"]["pinmux"]["functions"]["init_bsp_pins"] + function["selectPins"]["PA09"] = {"signal": "GPIO.A.A[09]", "padCtls": {}} + function["managers"] = {"gpiom": {"PA09": {"gpioController": "2"}}} + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = json.dumps(hpmpc) + files["User/libxr_config.yaml"] = "GPIO:\n PA10: led\n" + peripherals = self.project(files)["project"]["peripherals"] + self.assertNotIn("config", peripherals["I2C0"]) + self.assertEqual( + peripherals["GPIOA"]["config"], + [{"section": "GPIO", "key": None, "present": True, "params": {"PA10": "led"}}], + ) + + def test_no_fallback_when_every_call_is_conditional(self): + # 审查 C2:init_bsp_pins 只在 #if 里调用时不读它(O2),也不退回它。 + # Review C2: init_bsp_pins called only inside #if is not read (O2), not even as the + # fallback. + main = "int main(void)\n{\n#if USE_PINS\n init_bsp_pins();\n#endif\n}\n" + self.assertEqual(self.project(self.project_files(main))["project"]["assignments"], {}) + + def test_a_gptmr_on_a_soc_with_a_pwm_peripheral_has_no_pwm_entry(self): + # 与解析一致:HPM5361 有 PWM 外设,GPTMR 只作展示,libxr_config 里没有它的 PWM 条目。 + # As parsed: a HPM5361 has a PWM peripheral, so a GPTMR is display-only and has no PWM + # entry in libxr_config. + hpmpc = json.loads(self.HPMPC) + hpmpc["content"]["info"]["socName"] = "HPM5361" + hpmpc["content"]["pinmux"]["functions"]["init_bsp_pins"]["selectPins"]["PB08"] = { + "signal": "GPTMR0.B.COMP[1]", + "padCtls": {}, + } + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = json.dumps(hpmpc) + peripherals = self.project(files)["project"]["peripherals"] + self.assertEqual(peripherals["GPTMR0"]["pins"], {"COMP_1": "PB08"}) + self.assertNotIn("config", peripherals["GPTMR0"]) + + def test_a_lower_case_soc_and_a_byte_order_mark_are_read(self): + hpmpc = json.loads(self.HPMPC) + hpmpc["content"]["info"]["socName"] = "hpm5301" + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = "" + json.dumps(hpmpc) + self.assertEqual(self.project(files)["part"], "HPM5301") + + def test_a_malformed_hpmpc_is_a_formatted_error(self): + cases = { + "no pinmux": {"content": {"info": {"socName": "HPM5301"}}}, + "a string selection": { + "content": { + "info": {"socName": "HPM5301"}, + "pinmux": {"functions": {"init_bsp_pins": {"selectPins": {"PA00": "x"}}}}, + } + }, + } + for name, document in cases.items(): + with self.subTest(name), tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.hpmpc", json.dumps(document)) + with self.assertRaises(ValueError) as caught: + pin_project.read_hpmpc(path) + self.assertIn( + "export the project again from the HPM Pinmux Tool", str(caught.exception) + ) + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.hpmpc", "{not json") + with self.assertRaises(ValueError): + pin_project.read_hpmpc(path) + + def test_null_managers_and_selections_count_as_empty(self): + document = { + "content": { + "info": {"socName": "HPM5301"}, + "pinmux": { + "functions": { + "init_bsp_pins": {"selectPins": None, "managers": None}, + "init_more_pins": { + "selectPins": {"PA10": {"signal": "GPIO.A.A[10]", "padCtls": None}}, + "managers": {"gpiom": None}, + }, + } + }, + } + } + with tempfile.TemporaryDirectory() as directory: + project = pin_project.read_hpmpc( + write(Path(directory), "a.hpmpc", json.dumps(document)) + ) + self.assertEqual(project.functions["init_bsp_pins"].pins, {}) + self.assertEqual( + project.functions["init_more_pins"].pins, + {"PA10": {"signal": "GPIO.A.A[10]", "padCtls": {}}}, + ) + + def test_a_call_inside_a_conditional_is_not_active(self): + # O2:条件编译里的函数不算,PA04 不出现。 + # O2: a function inside a conditional does not count; PA04 stays out. + self.assertNotIn("PA04", self.project(self.project_files())["project"]["assignments"]) + + def test_without_a_main_c_the_bsp_function_is_the_fallback(self): + files = {key: value for key, value in self.project_files().items() if key != "main.c"} + assignments = self.project(files)["project"]["assignments"] + self.assertEqual(assignments["PA03"]["signal"], "I2C0_SCL") + self.assertNotIn("PA01", assignments) + + def test_two_hpmpc_files_are_an_error(self): + files = self.project_files() + files["boards/other/tool_config.hpmpc"] = files["boards/board/tool_config.hpmpc"] + with self.assertRaises(ValueError) as caught: + self.project(files) + self.assertTrue( + str(caught.exception).endswith("several .hpmpc files under boards/; keep one"), + str(caught.exception), + ) + + def test_a_hpmpc_without_a_soc_needs_the_model(self): + hpmpc = json.loads(self.HPMPC) + del hpmpc["content"]["info"]["socName"] + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = json.dumps(hpmpc) + with self.assertRaises(ValueError) as caught: + self.project(files) + self.assertTrue( + str(caught.exception).endswith("does not name a SoC; give the model"), + str(caught.exception), + ) + + def test_a_peripheral_is_tied_to_its_libxr_config_settings(self): + # HPM 生成 I2C 的设置和 GPIO 的改名(pa10,PA03 -> pa3);UART 没有设置段。I2C 要有 + # SCL 和 SDA 才生成(与解析一致),这里补上 SDA。 + # An HPM generates the I2C settings and the GPIO renames (pa10, PA03 -> pa3); a UART has + # no settings section. An I2C generates only with SCL and SDA (as parsed), so SDA is + # added here. + hpmpc = json.loads(self.HPMPC) + hpmpc["content"]["pinmux"]["functions"]["init_bsp_pins"]["selectPins"]["PA02"] = { + "signal": "I2C0.A.SDA", + "padCtls": {}, + } + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = json.dumps(hpmpc) + files["User/libxr_config.yaml"] = "I2C:\n i2c0:\n speed: 100000\nGPIO:\n pa10: LED\n" + peripherals = self.project(files)["project"]["peripherals"] + self.assertEqual( + peripherals["I2C0"]["config"], + [{"section": "I2C", "key": "i2c0", "present": True, "params": {"speed": 100000}}], + ) + self.assertEqual( + peripherals["GPIOA"]["config"], + [{"section": "GPIO", "key": None, "present": True, "params": {"pa10": "LED"}}], + ) + self.assertNotIn("config", peripherals["UART0"]) + + def test_a_gptmr_channel_is_a_pwm_entry(self): + # GPTMR 的对象名是 pwm_<定时器>_ch<通道>,每通道一个条目。 + # The object names of a GPTMR are pwm__ch; one entry per channel. + hpmpc = json.loads(self.HPMPC) + hpmpc["content"]["pinmux"]["functions"]["init_bsp_pins"]["selectPins"]["PB08"] = { + "signal": "GPTMR0.B.COMP[1]", + "padCtls": {}, + } + files = self.project_files() + files["boards/board/tool_config.hpmpc"] = json.dumps(hpmpc) + peripherals = self.project(files)["project"]["peripherals"] + self.assertEqual( + peripherals["GPTMR0"]["config"], + [{"section": "PWM", "key": "pwm_gptmr0_ch1", "present": False}], + ) + + +class HpmMainCalls(TestCase): + """main.c 里哪些 pinmux 函数算被调用:只看 main 的函数体,去掉注释和字符串,条件编译里的 + 单独列出(O2)。 + Which pinmux functions main.c counts as called: only the body of main, with comments and + strings removed, and those inside a conditional listed apart (O2). + """ + + FUNCTIONS = ("init_bsp_pins", "init_uart_pins", "init_jtag_pins", "init_helper_pins") + + def calls(self, text: str) -> tuple[list[str], list[str]]: + """给定 main.c 内容时 hpm_main_calls 的结果。 + The result of hpm_main_calls for the given main.c content. + """ + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "main.c", textwrap.dedent(text)) + return pin_project.hpm_main_calls(path, self.FUNCTIONS) + + def test_the_spellings_of_a_call_are_recognized(self): + called, _ = self.calls( + """\ + int main(void) + { + init_bsp_pins(); // the console pins + init_uart_pins (void); + board_init(); init_jtag_pins ( ); + } + """ + ) + self.assertEqual(called, ["init_bsp_pins", "init_uart_pins", "init_jtag_pins"]) + + def test_calls_in_comments_and_strings_do_not_count(self): + called, conditional = self.calls( + """\ + int main(void) + { + /* init_uart_pins(); + #if 0 */ + printf("init_jtag_pins();"); + init_bsp_pins(); + } + """ + ) + self.assertEqual((called, conditional), (["init_bsp_pins"], [])) + + def test_a_call_in_a_helper_does_not_count(self): + called, _ = self.calls( + """\ + static void setup(void) + { + init_helper_pins(); + } + + int main(void) + { + setup(); + init_bsp_pins(); + } + """ + ) + self.assertEqual(called, ["init_bsp_pins"]) + + def test_a_call_inside_a_conditional_is_listed_apart(self): + called, conditional = self.calls( + """\ + int main(void) + { + init_bsp_pins(); + #ifdef KEEP_JTAG + #else + init_jtag_pins(); + #endif + } + """ + ) + self.assertEqual((called, conditional), (["init_bsp_pins"], ["init_jtag_pins"])) + + +class SysconfigSettings(TestCase): + """MSPM0 外设的设置取自 .syscfg,只读。 + The settings of an MSPM0 peripheral come from the .syscfg, read-only. + """ + + def test_the_settings_of_each_peripheral_are_read(self): + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.syscfg", SYSCFG) + settings = pin_project.read_syscfg_settings(path) + self.assertEqual( + settings["UART0"], + { + "module": "UART", + "name": "UART_0", + "params": { + "enabledInterrupts": ["RX", "TX"], + "targetBaudRate": 2000000, + "enableFIFO": False, + "ovsRate": "3", + }, + }, + ) + + def test_a_solver_suggestion_names_the_peripheral_and_a_hex_number_is_a_number(self): + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.syscfg", SYSCFG) + settings = pin_project.read_syscfg_settings(path) + self.assertEqual(settings["SPI1"]["params"], {"targetBitRate": 0x100000}) + + def test_what_is_not_a_setting_is_left_out(self): + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.syscfg", SYSCFG) + settings = pin_project.read_syscfg_settings(path) + # Pin assignments, the solver's $ items, comments, and what has no peripheral of its own + # (a GPIO group, a static module). + self.assertNotIn("txPinConfig.$name", settings["UART0"]["params"]) + self.assertEqual(sorted(settings), ["SPI1", "UART0"]) + self.assertEqual(settings["UART0"]["params"]["targetBaudRate"], 2000000) + + def test_an_expression_is_kept_as_written(self): + text = ( + 'const UART = scripting.addModule("/ti/driverlib/UART", {}, false);\n' + "const UART1 = UART.addInstance();\n" + "UART1.targetBaudRate = 2 * 1000000;\n" + 'UART1.peripheral.$assign = "UART0";\n' + ) + with tempfile.TemporaryDirectory() as directory: + path = write(Path(directory), "a.syscfg", text) + settings = pin_project.read_syscfg_settings(path) + self.assertEqual(settings["UART0"]["params"], {"targetBaudRate": "2 * 1000000"}) + + def overlay(self, files): + """给定文件的 MSPM0 工程的叠加结果。 + The overlay of an MSPM0 project with these files. + """ + with tempfile.TemporaryDirectory() as directory: + for name, text in files.items(): + write(Path(directory), name, text) + return layout_with_project(directory, None, "PM", None)["project"] + + def test_a_used_peripheral_carries_its_sysconfig_settings(self): + project = self.overlay({"a.syscfg": SYSCFG}) + self.assertEqual(project["sysconfig_file"], "a.syscfg") + uart = project["peripherals"]["UART0"]["sysconfig"] + self.assertEqual((uart["module"], uart["name"]), ("UART", "UART_0")) + self.assertEqual(uart["params"]["targetBaudRate"], 2000000) + # A peripheral without pins is not listed: SPI1 has no pin of its own here. + self.assertNotIn("SPI1", project["peripherals"]) + + +class NoProject(TestCase): + """没有可识别的工程时报错。 + A directory without a recognizable project is an error. + """ + + def test_an_empty_directory_is_an_error(self): + with tempfile.TemporaryDirectory() as directory, self.assertRaises(ValueError) as caught: + layout_with_project(directory, None, None, None) + self.assertTrue( + str(caught.exception).endswith( + ": no STM32CubeMX .ioc, HPM .hpmpc or root SysConfig .syscfg found" + ), + str(caught.exception), + ) + + +class CommandLine(TestCase): + """libxr pins 的 -d 和 -c 选项。 + The -d and -c options of libxr pins. + """ + + def test_a_project_directory_replaces_the_model(self): + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "Project.ioc", IOC) + write(Path(directory), "User/libxr_config.yaml", CONFIG) + code, out, err = run_libxr("pins", "-d", directory) + self.assertEqual((code, err), (0, "")) + info = yaml.safe_load(out) + self.assertEqual(info["part"], "STM32F407IGHx") + self.assertEqual(info["project"]["peripherals"]["USART1"]["config"][0]["present"], True) + + def test_the_configuration_file_can_be_given_with_c(self): + with tempfile.TemporaryDirectory() as directory: + write(Path(directory), "Project.ioc", IOC) + other = write(Path(directory), "x.yaml", "SPI:\n spi1:\n tx_buffer_size: 7\n") + code, out, _ = run_libxr("pins", "-d", directory, "-c", str(other), "-f", "yaml") + info = yaml.safe_load(out) + self.assertEqual(code, 0) + self.assertEqual( + info["project"]["peripherals"]["SPI1"]["config"][0]["params"], {"tx_buffer_size": 7} + ) + + def test_errors_are_logged(self): + with tempfile.TemporaryDirectory() as directory: + for argv, reason in ( + (("pins",), "Give a chip model, or a project directory with -d"), + ( + ("pins", "-d", directory), + f"{directory}: no STM32CubeMX .ioc, HPM .hpmpc or root SysConfig .syscfg found", + ), + ): + with self.subTest(argv=argv), self.assertLogs(level="ERROR") as logs: + code, out, _ = run_libxr(*argv) + self.assertEqual((code, out), (1, "")) + self.assertEqual(logs.output, [f"ERROR:root:{reason}"]) + + +if __name__ == "__main__": + unittest.main()