Skip to main content

cmsis_pdsc_parser/
boards.rs

1//! Contains the types required to represent a [PDSC Boards](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_boards) element
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Deserializes a `u64` from either a hex string (`"0x..."`) or a decimal string.
6fn deserialize_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
7    let s = String::deserialize(d)?;
8    s.strip_prefix("0x")
9        .or_else(|| s.strip_prefix("0X"))
10        .map_or_else(
11            || s.parse::<u64>().map_err(serde::de::Error::custom),
12            |hex| u64::from_str_radix(hex, 16).map_err(serde::de::Error::custom),
13        )
14}
15
16/// Called only when the optional attribute is present; wraps the parsed value in `Some`.
17fn deserialize_opt_hex_u64<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u64>, D::Error> {
18    deserialize_hex_u64(d).map(Some)
19}
20
21#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
22/// Represents the [PDSC boards](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_boards) element
23pub struct Boards {
24    /// The list of board descriptions
25    #[serde(rename = "board")]
26    pub boards: Vec<Board>,
27}
28
29#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
30/// Represents a [PDSC board](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board) element
31pub struct Board {
32    /// Board vendor name
33    pub vendor: String,
34
35    /// Development board name
36    pub name: String,
37
38    /// Board revision suited for the BSP
39    pub revision: Option<String>,
40
41    /// 128-bit embedded debugger firmware identifier (format: 8-4-4-4-12)
42    pub uuid: Option<String>,
43
44    /// Email or webpage for sales enquiries
45    #[serde(rename = "salesContact")]
46    pub sales_contact: Option<String>,
47
48    /// Webpage for ordering the board
49    #[serde(rename = "orderForm")]
50    pub order_form: Option<String>,
51
52    /// Brief board description (max 256 characters) (0..*)
53    #[serde(rename = "description", default)]
54    pub description: Vec<String>,
55
56    /// Board features and capabilities (1..*)
57    #[serde(rename = "feature", default)]
58    pub features: Vec<Feature>,
59
60    /// Microcontroller devices mounted on the board (1..*)
61    #[serde(rename = "mountedDevice", default)]
62    pub mounted_devices: Vec<MountedDevice>,
63
64    /// Microcontroller devices compatible with the board (1..*)
65    #[serde(rename = "compatibleDevice", default)]
66    pub compatible_devices: Vec<CompatibleDevice>,
67
68    /// Non-MCU parts mounted on the board (0..*)
69    #[serde(rename = "mountedPart", default)]
70    pub mounted_parts: Vec<MountedPart>,
71
72    /// Board images (top/bottom/perspective)
73    pub image: Option<Image>,
74
75    /// On-board debug interface capabilities (0..*)
76    #[serde(rename = "debugInterface", default)]
77    pub debug_interfaces: Vec<DebugInterface>,
78
79    /// Documentation files (0..*)
80    #[serde(rename = "book", default)]
81    pub books: Vec<Book>,
82
83    /// On-board debug probe configuration
84    #[serde(rename = "debugProbe")]
85    pub debug_probe: Option<DebugProbe>,
86
87    /// Additional board memory regions (0..*)
88    #[serde(rename = "memory", default)]
89    pub memories: Vec<Memory>,
90
91    /// Flash programming algorithms for board memory (0..*)
92    #[serde(rename = "algorithm", default)]
93    pub algorithms: Vec<Algorithm>,
94
95    /// IDE-specific tool environments for this board (0..*)
96    #[serde(rename = "environment", default)]
97    pub environments: Vec<BoardEnvironment>,
98}
99
100#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
101/// Represents a [PDSC board feature](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_feature) element
102pub struct Feature {
103    /// Predefined board feature type (e.g. `LED`, `Button`, `XTAL`, `USB`, `Ethernet`); valid values: [BoardFeatureType](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html)
104    #[serde(rename = "type")]
105    pub feature_type: String,
106
107    /// Quantity or primary numeric parameter; meaning depends on `feature_type`
108    pub n: Option<String>,
109
110    /// Secondary numeric parameter; meaning depends on `feature_type`
111    pub m: Option<String>,
112
113    /// Descriptive feature name
114    pub name: Option<String>,
115}
116
117#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
118/// Represents a [PDSC mountedDevice](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_mountedDevice) element
119pub struct MountedDevice {
120    /// Device index for boards with multiple devices
121    #[serde(rename = "deviceIndex")]
122    pub device_index: Option<String>,
123
124    /// Device vendor (use `"NO_VENDOR:0"` if there is no MCU); valid values: [DeviceVendorEnum](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html)
125    #[serde(rename = "Dvendor")]
126    pub device_vendor: String,
127
128    /// Device name (use `"NO_MCU"` if there is no MCU)
129    #[serde(rename = "Dname")]
130    pub device_name: String,
131
132    /// Device family name (deprecated)
133    #[serde(rename = "Dfamily")]
134    pub device_family: Option<String>,
135
136    /// Device sub-family name (deprecated)
137    #[serde(rename = "DsubFamily")]
138    pub device_sub_family: Option<String>,
139}
140
141#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
142/// Represents a [PDSC compatibleDevice](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_compatibleDevice) element
143pub struct CompatibleDevice {
144    /// Device index for multi-device boards
145    #[serde(rename = "deviceIndex")]
146    pub device_index: Option<String>,
147
148    /// Device vendor (use `"NO_VENDOR:0"` for incompatible configurations); valid values: [DeviceVendorEnum](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html)
149    #[serde(rename = "Dvendor")]
150    pub device_vendor: Option<String>,
151
152    /// Device name or wildcard pattern
153    #[serde(rename = "Dname")]
154    pub device_name: Option<String>,
155
156    /// Device family name
157    #[serde(rename = "Dfamily")]
158    pub device_family: Option<String>,
159
160    /// Device sub-family name
161    #[serde(rename = "DsubFamily")]
162    pub device_sub_family: Option<String>,
163}
164
165#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
166/// Represents a [PDSC mountedPart](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_mountedPart) element
167pub struct MountedPart {
168    /// Quantity of parts with this name and vendor
169    pub n: String,
170
171    /// Part vendor name
172    #[serde(rename = "Hvendor")]
173    pub part_vendor: String,
174
175    /// Part name
176    #[serde(rename = "Hname")]
177    pub part_name: String,
178
179    /// Exact commercial part name (variant)
180    #[serde(rename = "Hvariant")]
181    pub part_variant: Option<String>,
182
183    /// Part revision
184    #[serde(rename = "Hrevision")]
185    pub part_revision: Option<String>,
186}
187
188#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
189/// Represents a [PDSC board image](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_image) element
190pub struct Image {
191    /// Path to the large top-side board image
192    pub large: Option<String>,
193
194    /// Path to the small top-side board image (lower resolution)
195    pub small: Option<String>,
196
197    /// Path to the bottom-side board image
198    pub bottom: Option<String>,
199
200    /// Path to a perspective-view board image
201    pub perspective: Option<String>,
202
203    /// Publishing permission; default `true`
204    pub public: Option<bool>,
205}
206
207#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
208/// Represents a [PDSC debugInterface](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_debugInterface) element
209pub struct DebugInterface {
210    /// Debug adapter type (e.g. `CMSIS-DAP`, `JTAG/SW`)
211    pub adapter: Option<String>,
212
213    /// Physical connector description
214    pub connector: Option<String>,
215}
216
217#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
218/// Represents a [PDSC board book](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_book) element
219pub struct Book {
220    /// Documentation category (e.g. `setup`, `schematic`, `manual`, `other`)
221    pub category: Option<String>,
222
223    /// Document file path or external URL
224    pub name: Option<String>,
225
226    /// Display title for the document
227    pub title: Option<String>,
228
229    /// Publishing permission; default `true`
230    pub public: Option<bool>,
231}
232
233#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
234/// Represents a [PDSC debugProbe](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_debugProbe) element
235pub struct DebugProbe {
236    /// Device index for the probe on multi-device boards
237    #[serde(rename = "deviceIndex")]
238    pub device_index: Option<String>,
239
240    /// Probe type (e.g. `CMSIS-DAP`, `DAP-Link`, `ST-Link`, `J-Link`)
241    pub name: Option<String>,
242
243    /// Probe firmware version
244    pub version: Option<String>,
245
246    /// Connection type: `jtag` or `swd`
247    #[serde(rename = "debugLink")]
248    pub debug_link: Option<String>,
249
250    /// Default debug clock speed in Hz
251    #[serde(
252        rename = "debugClock",
253        default,
254        deserialize_with = "deserialize_opt_hex_u64"
255    )]
256    pub debug_clock: Option<u64>,
257
258    /// Physical connector type (e.g. `Mini-USB`, `Micro-USB`, `USB-C`)
259    pub connector: Option<String>,
260}
261
262#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
263/// Represents a [PDSC board memory](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_memory) element
264pub struct Memory {
265    /// Processor identifier for multi-processor boards
266    #[serde(rename = "Pname")]
267    pub processor_name: Option<String>,
268
269    /// Deprecated memory region identifier
270    pub id: Option<String>,
271
272    /// Unique memory region name
273    pub name: Option<String>,
274
275    /// Access permissions (e.g. `rx`, `rw`, `rwx`)
276    pub access: Option<String>,
277
278    /// Base address of the memory region (hex or decimal)
279    #[serde(deserialize_with = "deserialize_hex_u64")]
280    pub start: u64,
281
282    /// Size of the memory region in bytes (hex or decimal)
283    #[serde(deserialize_with = "deserialize_hex_u64")]
284    pub size: u64,
285
286    /// Whether this is the general-purpose memory for the linker (default: `false`)
287    pub default: Option<bool>,
288
289    /// Whether startup code should be placed here (default: `false`)
290    pub startup: Option<bool>,
291
292    /// Whether the region should remain uninitialized (default: `false`)
293    pub uninit: Option<bool>,
294
295    /// Whether the region is initialized (deprecated, use `uninit` instead)
296    pub init: Option<bool>,
297
298    /// Name of another memory region this region aliases
299    pub alias: Option<String>,
300}
301
302#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
303/// Represents a [PDSC board algorithm](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board_algorithm) element
304pub struct Algorithm {
305    /// Processor identifier for multi-processor boards
306    #[serde(rename = "Pname")]
307    pub processor_name: Option<String>,
308
309    /// Device index for multi-device boards
310    #[serde(rename = "deviceIndex")]
311    pub device_index: Option<String>,
312
313    /// Path to the flash programming algorithm file
314    pub name: String,
315
316    /// Base address of the flash region covered by this algorithm (hex or decimal)
317    #[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
318    pub start: Option<u64>,
319
320    /// Size of the flash region covered by this algorithm in bytes (hex or decimal)
321    #[serde(default, deserialize_with = "deserialize_opt_hex_u64")]
322    pub size: Option<u64>,
323
324    /// Additional parameter passed to the algorithm
325    pub parameter: Option<String>,
326
327    /// Endianness of the target (default: `Little-endian`)
328    pub endian: Option<String>,
329
330    /// RAM execution base address for the algorithm (hex or decimal)
331    #[serde(
332        rename = "RAMstart",
333        default,
334        deserialize_with = "deserialize_opt_hex_u64"
335    )]
336    pub ram_start: Option<u64>,
337
338    /// Maximum RAM available for algorithm execution (hex or decimal)
339    #[serde(
340        rename = "RAMsize",
341        default,
342        deserialize_with = "deserialize_opt_hex_u64"
343    )]
344    pub ram_size: Option<u64>,
345
346    /// Whether this is the default algorithm for the covered region (default: `false`)
347    pub default: Option<bool>,
348
349    /// Algorithm style; defaults to `Keil`
350    pub style: Option<String>,
351}
352
353#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
354/// Represents a [board environment](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_boards_pg.html#element_board) entry
355pub struct BoardEnvironment {
356    /// IDE environment name (e.g. `uvision`, `iar`, `eclipse`)
357    pub name: String,
358
359    /// Processor name for multi-core boards; limits this environment entry to one core
360    #[serde(rename = "Pname")]
361    pub processor_name: Option<String>,
362}
363
364#[cfg(test)]
365mod tests {
366    use crate::boards::{
367        Algorithm, BoardEnvironment, Boards, Book, CompatibleDevice, DebugProbe, Feature, Image,
368        Memory, MountedDevice, MountedPart,
369    };
370
371    #[test]
372    fn parse_boards() {
373        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
374<boards>
375    <board vendor="STMicroelectronics" name="NUCLEO-F401RE" revision="Rev.C">
376        <description>STM32 Nucleo-64 development board with STM32F401RE MCU</description>
377        <feature type="XTAL" n="8" name="High-speed crystal oscillator"/>
378        <mountedDevice Dvendor="STMicroelectronics:13" Dname="STM32F401RETx"/>
379        <compatibleDevice Dvendor="STMicroelectronics:13" Dname="STM32F401*"/>
380        <image large="images/nucleo_large.png" small="images/nucleo_small.png"/>
381        <book category="setup" name="docs/setup.pdf" title="Getting Started" public="true"/>
382        <memory name="FLASH" access="rx" start="0x08000000" size="0x80000" default="true" startup="true"/>
383        <algorithm name="Flash/STM32F4xx.FLM" start="0x08000000" size="0x80000" default="true"/>
384    </board>
385</boards>"#;
386
387        let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
388        assert_eq!(boards.boards.len(), 1);
389
390        let board = &boards.boards[0];
391        assert_eq!(board.vendor, "STMicroelectronics");
392        assert_eq!(board.name, "NUCLEO-F401RE");
393        assert_eq!(board.revision, Some("Rev.C".to_string()));
394        assert_eq!(
395            board.description,
396            vec!["STM32 Nucleo-64 development board with STM32F401RE MCU".to_string()]
397        );
398        assert_eq!(
399            board.features,
400            vec![Feature {
401                feature_type: "XTAL".to_string(),
402                n: Some("8".to_string()),
403                m: None,
404                name: Some("High-speed crystal oscillator".to_string()),
405            }]
406        );
407        assert_eq!(
408            board.mounted_devices,
409            vec![MountedDevice {
410                device_index: None,
411                device_vendor: "STMicroelectronics:13".to_string(),
412                device_name: "STM32F401RETx".to_string(),
413                device_family: None,
414                device_sub_family: None,
415            }]
416        );
417        assert_eq!(
418            board.compatible_devices,
419            vec![CompatibleDevice {
420                device_index: None,
421                device_vendor: Some("STMicroelectronics:13".to_string()),
422                device_name: Some("STM32F401*".to_string()),
423                device_family: None,
424                device_sub_family: None,
425            }]
426        );
427        assert_eq!(
428            board.image,
429            Some(Image {
430                large: Some("images/nucleo_large.png".to_string()),
431                small: Some("images/nucleo_small.png".to_string()),
432                bottom: None,
433                perspective: None,
434                public: None,
435            })
436        );
437        assert_eq!(
438            board.books,
439            vec![Book {
440                category: Some("setup".to_string()),
441                name: Some("docs/setup.pdf".to_string()),
442                title: Some("Getting Started".to_string()),
443                public: Some(true),
444            }]
445        );
446        assert_eq!(
447            board.memories,
448            vec![Memory {
449                processor_name: None,
450                id: None,
451                name: Some("FLASH".to_string()),
452                access: Some("rx".to_string()),
453                start: 0x08000000,
454                size: 0x80000,
455                default: Some(true),
456                startup: Some(true),
457                uninit: None,
458                init: None,
459                alias: None,
460            }]
461        );
462        assert_eq!(
463            board.algorithms,
464            vec![Algorithm {
465                processor_name: None,
466                device_index: None,
467                name: "Flash/STM32F4xx.FLM".to_string(),
468                start: Some(0x08000000),
469                size: Some(0x80000),
470                parameter: None,
471                endian: None,
472                ram_start: None,
473                ram_size: None,
474                default: Some(true),
475                style: None,
476            }]
477        );
478        assert_eq!(board.mounted_parts, vec![]);
479        assert_eq!(board.debug_interfaces, vec![]);
480        assert_eq!(board.debug_probe, None);
481    }
482
483    #[test]
484    fn parse_board_minimal() {
485        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
486<boards>
487    <board vendor="Example" name="MyBoard">
488        <description>A minimal test board</description>
489        <feature type="LED" n="2"/>
490        <mountedDevice Dvendor="ARM:82" Dname="ARMCM0"/>
491    </board>
492</boards>"#;
493
494        let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
495        let board = &boards.boards[0];
496
497        assert_eq!(board.vendor, "Example");
498        assert_eq!(board.name, "MyBoard");
499        assert_eq!(board.revision, None);
500        assert_eq!(board.uuid, None);
501        assert_eq!(board.description, vec!["A minimal test board".to_string()]);
502        assert_eq!(
503            board.features,
504            vec![Feature {
505                feature_type: "LED".to_string(),
506                n: Some("2".to_string()),
507                m: None,
508                name: None,
509            }]
510        );
511        assert_eq!(
512            board.mounted_devices,
513            vec![MountedDevice {
514                device_index: None,
515                device_vendor: "ARM:82".to_string(),
516                device_name: "ARMCM0".to_string(),
517                device_family: None,
518                device_sub_family: None,
519            }]
520        );
521        assert_eq!(board.compatible_devices, vec![]);
522        assert_eq!(board.mounted_parts, vec![]);
523        assert_eq!(board.books, vec![]);
524        assert_eq!(board.memories, vec![]);
525        assert_eq!(board.algorithms, vec![]);
526        assert_eq!(board.image, None);
527        assert_eq!(board.debug_probe, None);
528    }
529
530    #[test]
531    fn parse_board_debug_probe() {
532        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
533<boards>
534    <board vendor="ARM" name="MPS2" salesContact="support@arm.com">
535        <description>ARM MPS2 FPGA prototyping board</description>
536        <feature type="JTAG" n="1"/>
537        <mountedDevice Dvendor="ARM:82" Dname="ARMCM3"/>
538        <mountedPart n="1" Hvendor="Xilinx" Hname="XC7A200T" Hvariant="-1FBG484C"/>
539        <debugProbe name="CMSIS-DAP" version="2.0" debugLink="swd" debugClock="10000000" connector="USB-C"/>
540    </board>
541</boards>"#;
542
543        let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
544        let board = &boards.boards[0];
545
546        assert_eq!(board.vendor, "ARM");
547        assert_eq!(board.name, "MPS2");
548        assert_eq!(board.sales_contact, Some("support@arm.com".to_string()));
549        assert_eq!(
550            board.mounted_parts,
551            vec![MountedPart {
552                n: "1".to_string(),
553                part_vendor: "Xilinx".to_string(),
554                part_name: "XC7A200T".to_string(),
555                part_variant: Some("-1FBG484C".to_string()),
556                part_revision: None,
557            }]
558        );
559        assert_eq!(
560            board.debug_probe,
561            Some(DebugProbe {
562                device_index: None,
563                name: Some("CMSIS-DAP".to_string()),
564                version: Some("2.0".to_string()),
565                debug_link: Some("swd".to_string()),
566                debug_clock: Some(10_000_000),
567                connector: Some("USB-C".to_string()),
568            })
569        );
570        assert_eq!(board.compatible_devices, vec![]);
571        assert_eq!(board.memories, vec![]);
572        assert_eq!(board.algorithms, vec![]);
573    }
574
575    #[test]
576    fn parse_board_environments() {
577        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
578<boards>
579    <board vendor="Example" name="EnvBoard">
580        <description>Board with IDE environments</description>
581        <mountedDevice Dvendor="ARM:82" Dname="ARMCM4"/>
582        <environment name="uvision"/>
583        <environment name="iar" Pname="Core0"/>
584    </board>
585</boards>"#;
586
587        let boards: Boards = serde_roxmltree::from_str(xml_str).unwrap();
588        let board = &boards.boards[0];
589
590        assert_eq!(board.vendor, "Example");
591        assert_eq!(board.name, "EnvBoard");
592        assert_eq!(board.environments.len(), 2);
593        assert_eq!(
594            board.environments[0],
595            BoardEnvironment {
596                name: "uvision".to_string(),
597                processor_name: None,
598            }
599        );
600        assert_eq!(
601            board.environments[1],
602            BoardEnvironment {
603                name: "iar".to_string(),
604                processor_name: Some("Core0".to_string()),
605            }
606        );
607    }
608}