AdaCore Blog

Starting micro-controller Ada drivers in the Alire ecosystem

by Fabien Chouteau

A few days ago, some­one asked on the Ada Dri­vers Library repos­i­to­ry how to add sup­port for the SAMD21 micro-controller.

Nowa­days, I would rather rec­om­mend peo­ple to con­tribute this kind of micro-con­troller sup­port project to the Alire ecosys­tem. Like I did with the samd51_hal crate and lat­er Jere­my Gross­er with the rp2040_hal crate.

I start­ed to write a few instruc­tions on how to get start­ed, but it quick­ly became a blog-wor­thy piece of text. So here it is:

Hard­ware require­ments #

I strong­ly rec­om­mend get­ting a good debug­ger set up. Writ­ing low lev­el periph­er­al dri­vers can be hard, and with­out a prop­er debug­ger it will be most­ly impos­si­ble to know what is going wrong.

So pick a devel­op­ment board that has a debug port avail­able. For the SAMD21, the AdaFruit METRO M0 Express is a bet­ter option than the AdaFruit Feath­er M0.

For the debug­ger itself, you can either get a SEG­GER J‑Link EDU Mini that has sup­port for most Cortex‑M devices, or Black Mag­ic Probe that is open source and open hard­ware but with less devices supported.

An Alire crate for our Hard­ware Abstrac­tion Lay­er (HAL) #

Let’s start our project. To begin with, down­load and install Alire.

Then cre­ate the HAL crate for the SAMD21:

$ alr init --lib samd21_hal
$ cd samd21_hal
$ alr with gnat_arm_elf # Add a dependency on the arm-elf compiler

Add the fol­low­ing lines to the samd21_hal.gpr project file:

for Target use "arm-elf";
for Runtime ("Ada") use "light-cortex-m0p";

The run-time used is light-cortex-m0p because the SAMD21 is an ARM Cor­tex-M0 plus” device. You might have to use a dif­fer­ent run-time for your device.

I also rec­om­mend renam­ing the root pack­age to fol­low what is done in the samd51_hal and rp2040_hal:

$ mv src/samd21_hal.ads src/sam.ads
$ sed -i 's/Samd21_Hal/SAM/g' src/sam.ads

And then build:

$ alr build

This is the start­ing point for our Hard­ware Abstrac­tion Lay­er library.

Use svd2ada to gen­er­ate the low lev­el hard­ware bind­ings #

Most, if not all, ARM Cortex‑M micro-con­trollers are pro­vid­ed with an SVD file that describes the mem­o­ry mapped periph­er­al reg­is­ters. Some of the RISC‑V ones do too. Using the svd2ada tool, we can auto­mat­i­cal­ly gen­er­ate the Ada bind­ings for all the reg­is­ters, which tremen­dous­ly reduces the effort of sup­port­ing a new micro-controller.

Get the SVD file #

First we have to find the SVD file for our micro-con­troller. For the Microchip (for­mer­ly ATMEL) prod­ucts, the SVD files are found at this address: http://​packs​.down​load​.atmel​.com/

Down­load the .atpack archive cor­re­spond­ing to our micro-con­troller fam­i­ly and unzip it:

$ wget http://packs.download.atmel.com/Atmel.SAMD21_DFP.1.3.395.atpack
$ unzip Atmel.SAMD21_DFP.1.3.395.atpack -d samd21_atpack

Now one of the issues here is that there is one SVD file for each micro-con­troller of the samd21 fam­i­ly, 34 SVD files. That’s because each micro-con­troller can have a dif­fer­ent set of periph­er­als and dif­fer­ent mem­o­ry banks sizes.

Mak­ing a crate for each of the micro-con­trollers in the fam­i­ly would not scale very well (some fam­i­lies have hun­dreds of variants).

There are solu­tions to deal with that issue, but for this post I want to keep it sim­ple. So we pick the SVD file that cor­re­sponds to the spe­cif­ic micro-con­troller that we are using, and this will be a good start­ing point.

In our case, it will be the samd21_atpack/samd21a/svd/ATSAMD21G18A.svd file.

Build svd2ada #

Unfor­tu­nate­ly svd2ada is not avail­able in Alire yet, but we can down­load and com­pile the sources from the GitHub repos­i­to­ry: https://​github​.com/​A​d​a​C​o​r​e​/​s​v​d2ada

Gen­er­ate the bind­ings #

Using svd2ada we are now going to gen­er­ate the low lev­el hard­ware bindings:

$ svd2ada samd21_atpack/samd21a/svd/ATSAMD21G18A.svd --boolean -o src -p SAM_SVD --base-types-package HAL --gen-uint-always

We also have to add a depen­den­cy on the hal crate that is used by the bindings:

$ alr with hal

And then build again:

$ alr build

Make an exam­ple crate #

Now that we pre­pared all this code for our HAL, it is time to final­ly start pro­gram­ming the micro-con­troller for real.

To do that we are going to make anoth­er crate. For every micro-con­troller HAL crate, I rec­om­mend mak­ing an exam­ple crate for a devel­op­ment board that any­body can try. We will call the crate metro_m0_example here because it will be an exam­ple for the AdaFruit Metro M0 board:

$ cd ..
$ alr init --bin metro_m0_example
$ cd metro_m0_example
$ alr with samd21_hal --use=../samd21_hal

Some expla­na­tion is required for the last com­mand here. For this new metro_m0_example crate we declare a depen­den­cy to the samd21_hal crate using the alr with samd21_hal com­mand. Since the samd21_hal crate is not pub­lished yet (we are just start­ing to work­ing on it) the option --use=../samd21_hal tells Alire to get the sources for this crate in a local fold­er rather than try­ing to fetch it from a remote crate index.

We also have to spec­i­fy tar­get and run-time in the metro_m0_example.gpr project file:

for Target use "arm-elf";
for Runtime ("Ada") use "light-cortex-m0p";

We can now build, but there will be an issue:

$ alr build
[...]
warning: cannot find entry symbol _start; defaulting to 0000000000008000

We see this warn­ing because two things are miss­ing in our code:

  • Link­er script
  • Start­up code

There is a tool that will gen­er­ate those two for us: startup-gen

Using start­up-gen #

We already have a blog post about startup-gen here. I rec­om­mend look­ing at the blog post for more info on what we are going to do.

First we add the board spec­i­fi­ca­tions to the metro_m0_example.gpr project file. For the SAMD21G18 they are:

package Device_Configuration is
   for CPU_Name use "ARM Cortex-M0P";
   for Float_Handling use "soft";

   for Number_Of_Interrupts use "42";

   for Memories use ("RAM", "FLASH");

   --  Specify from which memory bank the program will load
   for Boot_Memory use "FLASH";

   --  Specification of the RAM
   for Mem_Kind ("RAM") use "ram";
   for Address ("RAM") use "0x20000000";
   for Size ("RAM") use "0x8000";

   --  Specification of the FLASH
   for Mem_Kind ("FLASH") use "rom";
   for Address ("FLASH") use "0x08000000";
   for Size ("FLASH") use "0x40000";
end Device_Configuration;

I extract­ed the data from the samd21_atpack/Atmel.SAMD21_DFP.pdsc file.

We then build startup-gen with Alire and use it to gen­er­ate the files:

$ cd ..
$ alr get --build startup_gen
$ cd metro_m0_example
$ eval `alr printenv`
$ ../startup_gen_21.0.0_75bdb097/startup-gen -P metro_m0_example.gpr -l src/link.ld -s src/crt0.S
CPU: ARM Cortex-M0P
Float_Handling: SOFT
Name    : RAM
Address : 0x20000000
Size    : 0x8000
Kind    : RAM
Name    : FLASH
Address : 0x08000000
Size    : 0x40000
Kind    : ROM

We now have our link­er script and start­up code, there are two more things that we have to do before com­pil­ing the example.

We have to add the fol­low­ing line in the project file (metro_m0_example.gpr) to spec­i­fy that we also want to com­pile assem­bly code:

for Languages use ("Ada", "ASM_CPP");

And we have to add the fol­low­ing lines in the project file (metro_m0_example.gpr) to spec­i­fy the link­er script to use:

package Linker is
   for Switches ("Ada") use ("-T", Project'Project_dir & "/src/link.ld",
                             "-Wl,--print-memory-usage",
                             "-Wl,--gc-sections");
end Linker;

We can now final­ly build our first appli­ca­tion for the SAMD21:

$ alr build
[...]
Memory region         Used Size  Region Size  %age Used
           FLASH:         756 B       256 KB      0.29%
             RAM:        4120 B        32 KB     12.57%

The default Alire init project is just a null state­ment, so don’t for­get to add an end-less loop in src/metro_m0_example.adb procedure.

Run­ning on the board #

Now that we have a suc­cess­ful build, we can run our infi­nite loop on the board.

This part is not going to be very detailed because the process can be dif­fer­ent depend­ing on the micro-con­troller/board­/de­bug­ger.

The basic steps with JLink are:

  • Cre­ate a GDB serv­er for the debug­ger. For instance $ JLinkGDBServerCLExe -device ATSAMD21G18 -if swd -vd -nogui
  • Start GDB with the appli­ca­tion bina­ry: arm-eabi-gdb bin/metro_m0_example
  • From GDB:
    • Con­nect to the debug­ger: (gdb) target remote :2331
    • Reset the micro-con­troller: (gdb) monitor reset
    • Load the appli­ca­tion: (gdb) load
    • Put a break point on the main pro­ce­dure: (gdb) b <_ada_metro_m0_example>
    • Start the exe­cu­tion: (gdb) continue

The debug­ger should stop after reach­ing the break­point. That’s it, we made our first pro­gram for the SAMD21.

Build high­er lev­el abstrac­tions #

From here we can start devel­op­ing our example/​application with just the SVD gen­er­at­ed bind­ings, but it is best to build a high­er abstrac­tion above to help the users of our HAL crate.

This will also allow us to have com­mon inter­faces to use exist­ing dri­vers for I2C or SPI for instance. It’s also good to pro­vide use­ful pack­ages to con­fig­ure and set­up the micro-con­troller, like the clock gen­er­a­tors for instance.

This is where the hard part begins and where we are get­ting out­side the scope of this blog post ^^

The use­ful resources will be:

  • micro-con­troller documentation
  • Ven­dor pro­vid­ed dri­vers (in C)
  • Ada dri­vers for oth­er micro-controllers

Make a BSP crate #

There is one last thing I want to men­tion here. So far we made two crates: the samd21_hal and metro_m0_example. But there is anoth­er lev­el of abstrac­tion that we can add.

Most of the time the peo­ple will start pro­gram­ming a micro-con­troller using a devel­op­ment board, either from the ven­dor or from a third par­ty like AdaFruit, Spark­Fun or pimoroni. This is what we did in this blog post.

These devel­op­ment boards come with some fea­tures that are spe­cif­ic to them. For instance, LEDs, but­tons, sen­sors, etc.

It is bet­ter to make a Board Sup­port Pack­age (BSP) crate for those fea­tures, so that our­selves and oth­ers can re-use that code in the future.

So let’s make a new crate:

$ cd ..
$ alr init --lib metro_m0_bsp
$ cd metro_m0_bsp
$ alr with samd21_hal --use=../samd21_hal

And in the exam­ple crate, depend on this BSP:

$ cd ../metro_m0_example/ 
$ alr with metro_m0_bsp --use=../metro_m0_bsp
$ alr build

Con­clu­sion #

I realise that this pro­ce­dure is a bit long, but on the oth­er hand it is made short­er and eas­i­er by the use of tools such as Alire, svd2ada and startup-gen. There are short­cuts that you can take if you want, but this will set you in the right direc­tion for the long term main­te­nance of your HAL and BSP code.

Posted in

About Fabien Chouteau

Fabien Chouteau

Fabien joined AdaCore in 2010 after his engineering degree at the EPITA (Paris). He is involved in real-time, embedded and hardware simulation technology. Maker/DIYer in his spare time, his projects include electronics, music and woodworking.