MAX32650 and Rust

Catch Up

Well, 2020 is behind us (finally)! The second half of the year was quite a whirlwind for my family and me: we sold our condo, bought a house, and I was laid off–mostly in that order. In a weird turn of events, we were actually able to close on the house despite my not having a job, which was quite a relief. In any event, we’ve had a very busy last few months, so my side projects have taken a distant back seat.

My audio project will be on an indefinite hiatus, unfortunately. A big part of the reason I was working on that was to explore the audio space, given that I was working for an audio company. That is no longer the case! I am currently working for a medical devices company, and as such, I will shift my side projects so that I can learn more about the things I will work with here!

MAX32650

The Maxim Integrated MAX32650 is a very low power microcontroller, and one that is rather similar to offerings from ST Micro that I’ve worked with in the past. There is a nice evaluation kit available, which makes it pretty easy to get moving. That said, it’s not as accessible as some of the ST Discovery Boards. But regardless, the datasheets are very accessible and readable, and there is ARM support available via CMSIS.

Rust

I’ve heard a lot about Rust from friends and coworkers, but I’ve never really spent any time with it. As a learning exercise, I’d like to do some work to get an understanding of embedded Rust. In some ways, it seems like a good fit for a medical device: very strict compile-time analysis, “fearless concurrency”, and pretty good portability. It also seems like it integrates nicely with existing C code, which might be beneficial for working with embedded operating systems. The Rust folks are happy to point out their point of view on the matter as well.

There is a pretty good Embedded Rust tutorial available, so I followed that a little bit to get started. Aside from some syntactical things that I don’t really understand, it seems pretty easy to get started! I did the qemu bits, and so far so good. But I want to use some real hardware, and I have this MAX32650 eval kit to use.

Creating the MAX32650 Crate

A bit of Googling indicates that Rust has a nice tool that converts the ARM CMSIS packs into a Rust library (at least for Cortex-M processor based microcontrollers): svd2rust. That’s pretty nice, and saves a ton of manual work! Rust makes it pretty easy to install packages like this:

$ cargo install svd2rust

CMSIS Packs

CMSIS Packs are essentially bundles of information related to ARM Cortex microcontrollers. They are generally created by vendors of parts, and contain things like hardware descriptions, register definitions, drivers and example projects. Personally I’ve run across them in the past when fiddling with ST Microelectronics' STM32Cube programs: part of what they do is download the CMSIS packs for their parts, and set up projects that allow them to showcase their work.

The good news is: you can find these packs pretty easily directly from ARM. The list is organized by manufacturers, and sure enough, under Maxim there is an entry for MAX32650. Download that, and…. you’re left with a .pack file. Fortunately, this is really a ..zip file, so you can rename it and extract it as usual.

As the name suggests, the svd2rust tool converts the SVD file into a Rust library. What is an SVD file, you ask? Well, so did I, and apparently it is the System View Description file. It is an XML format file that describes the low level details of the system in a nice structured way that can be parsed by a machine and turned into something useful. In our case, svd2rust will convert that file into useable modules that allow us to write nice Rust code to work with registers, handle interrupts and such.

Running svd2rust

Ok, well a quick search shows two SVD files in the pack:

$ find . -name '*.svd'
./Libraries/Device/Maxim/MAX32650/Include/max32650.svd
./SVD/MAX32650/max32650.svd

They are almost identical as well:

$ diff ./SVD/MAX32650/max32650.svd ./Libraries/Device/Maxim/MAX32650/Include/max32650.svd
1503c1503
<         <description>DMA Channel 0 IRQ</description>
---
>         <description>DMA Channel 0 Interrupt</description>
1508c1508
<         <description>DMA Channel 1 IRQ</description>
---
>         <description>DMA Channel 1 Interrupt</description>
1513c1513
<         <description>DMA Channel 2 IRQ</description>
---
>         <description>DMA Channel 2 Interrupt</description>
1518c1518
<         <description>DMA Channel 3 IRQ</description>
---
>         <description>DMA Channel 3 Interrupt</description>
... snip ...

Hm, well…. I guess I’ll pick the one closest to the root directory. I create a new project folder (~/code/rust/max32650) and copy the SVD file there. From there, I’ll try to create my Rust crate!

$ svd2rust -i max32650.svd
[ERROR svd2rust] can't convert 0 bits into a Rust integral type
[ERROR svd2rust] note: run with `RUST_BACKTRACE=1` for a backtrace

That doesn’t seem great. Not a super helpful error message either! Presumably this is an issue with the SVD file, but that is nearly 25,000 lines long, so finding it sounds hard. Why doesn’t the error message tell me what line or field the error is in?!

After a lot of fruitless Google-ing and reading of the source of svd2rust, I finally gave up and decided to see if I could figure this out by looking at the XML file. Presumably it is trying to convert a bitfield into some type of data type (e.g. if there are 8 bits in the field, use a u8). In this case, the tool was getting confused because there isn’t a correct data type to use for a 0-sized bitfield.

Looking at the SVD file, I see that the register descriptions seem to follow a pattern where the register is broken down into fields, which are defined by a bit offset and bit width. With this hypothesis, I searched and found that there was 1 register, CFG_1 that has a field SDR104 that claims to have a bitOffset of 1, and a bitWidth of 0. Removing this field allowed svd2rust to complete without an error.

diff --git a/max32650.svd b/max32650.svd
index 3cf9a47..7c37274 100644
--- a/max32650.svd
+++ b/max32650.svd
@@ -13297,13 +13297,6 @@
               <bitWidth>1</bitWidth>
               <access>read-only</access>
             </field>
-            <field>
-              <name>SDR104</name>
-              <description>SDR104 Support.</description>
-              <bitOffset>1</bitOffset>
-              <bitWidth>0</bitWidth>
-              <access>read-only</access>
-            </field>
             <field>
               <name>DDR50</name>
               <description>DDR50 Support.</description>

Output of svd2rust

After the svd2rust utility completes, there are 3 new files: device.x, build.rs and lib.rs. The first one appears to be a default mapping of the interrupt vectors, and the second one seems to be a fairly simple utility script for porting the device.x file to whereever it is needed when this crate is linked into another project. The most interesting one for now is lib.rs. It measures in at 33,807 lines, which is rather unwieldy!

Fortunately, the recommendation is to format this file and split it into neat modules. This requires that you install form via cargo install form, which takes a surprisingly long time and compiles a lot of crates! But once it’s done, it does good work.

$ form -i lib.rs -o src/ && rm lib.rs

and once it is done, we have a much more usable setup:

$ find src/
src
src/spid.rs
src/dma.rs
src/owm.rs
src/bbsir
src/bbsir/bb_sir3.rs
src/bbsir/bb_sir2.rs
src/emcc.rs
src/gpio0.rs
src/spimss.rs
... snip ...

Create the Crate

Ok, so we have a bunch of source files, now what? Well, let’s package them up so that we can utilize them in other projects. The Rust-y way of doing this seems to be creating what is called a Crate. To my n00b eyes, it seems like a Crate is a library? In any case, let’s make that. In my case, since I’ve already created some source code and set up the Git repo, I’ll use cargo-init rather than cargo-new:

$ cargo init --lib

All that this does is creates the Cargo.toml file that contains metadata about this crate. According to the svd2rust documentation, there are some specific dependencies that this crate must have:

The resulting crate must provide an opt-in "rt" feature and depend on these crates: bare-metal v0.2.x, cortex-m v0.5.x, cortex-m-rt >=v0.6.5 and vcell v0.1.x. Furthermore the "device" feature of cortex-m-rt must be enabled when the "rt" feature is enabled. 

I’m not sure what this means, exactly, but they do have an example Cargo.toml file, so I simply copy in the dependency information. At this point, I’ve created the crate!

Summary

Unfortunately, it is time for me to get to bed, so we shall have to wait to see whether I’ve accomplished anything useful. So far today, I downloaded and unpacked the CMSIS pack for the MAX32650, identified the SVD file, and used that to create a Rust crate that will allow me to access and modify various registers of the MCU! Not too shabby. Next, I will try to create a basic “Hello World” style project: in embedded projects, this generally means twiddling a GPIO so that an LED turns on. I will also explore pushing up my crate so that others will have an easier time getting started with Rust on the MAX32650! And, for good measure, there are actually similar parts (32650, 32651 and 32652 all are a part of the same family), so maybe I should create all of them?