Ada 2012 Comes to ARM Cortex M3/M4
Ada, that old dinosaur? I thought Ada was dead!
Admit it, at least a few of you had that thought, right? Well, far from being dead, the Ada language has been evolving, improving, and helping to save lives, property and money around the world for the past 30 years. And what's more, the latest version of the language, Ada 2012, will soon be coming to a two-dollar microcontroller near you.
A Personal Dream Come True
OK, maybe that's going too far - but not by too much. For a long time now, I have admired the design and the power of the Ada language. For just as long I have also wished I could program the mainstream low-to-mid-end microcontrollers I use in Ada. And all the while, that seemed like an impossible dream. Ada was available and being used, but not on any of the platforms I wanted to use it on. It was available on x86, Power PC, and some microprocessors designed specifically for military and space work, but it sure wasn't available for anything I worked with.
There were moments when AdaCore, creator of the GNAT Ada compiler, threw a tempting bone my way. One was their release for Lego Mindstorms, which used an ARM7-TDMI processor. Another was their release, sans tasking runtime, of Ada for the AVR. But I didn't want to work with the ARM7 family going forward (not to mention that the Mindstorms release was not a general ARM7 release). And I wanted to work with microcontroller families that had more horsepower than the 8-bit AVRs. OK, I'll get right to the point. I wanted to work with ARM Cortex M parts, and I wanted to program them in Ada. And there didn't seem to be any way to do that. Until now.
AdaCore has finally come to see things my way (ahem...) and is porting their Ada 2012 compiler over to ARM Cortex M3 and M4 targets. I've been working with a pre-release version for a few days now, that targets the STM32F4 board using an M4F processor. Mike, meet candy store. To say that I'm excited about this is to barely chip at the glacier.
What's So Great About Ada?
Well, if you're a C or C++ programmer who wants to produce more reliable code more quickly, and perhaps have more fun doing it, just about everything. I know that could be considered red bait for a full-on language war, and I am totally uninterested in such a melee. I'll just offer a few links, and a personal observation I made over a number of years. The personal observation involved asking, for every bug I ran across in C or C++ code that was non-trivial (let's say >1 hour to fix), would this bug have occurred with Ada, and if so, would it have been easier to find and fix with Ada? The data was depressing. So many PITA bugs that I fought with would never have made it past an Ada compiler. And so many of those few that would have escaped, would have been fingered by the Ada runtime like searchlights on a prison break. I was spending a lot of my working life dealing with bugs that didn't have to be there, and didn't have to be so hard to track down.
This isn't just my opinion either. Just to mention one data point, back in the late 1990s an article came out called "My Hariest Bug War Stories." A computer science professor analyzed the bugs in the article and found 17 that were related to software (as opposed to hardware, operating system or compiler problems, etc). Of those 17, 15 would have been caught by Ada. That's an 88% reduction in bugs, and "hairy" ones at that!
Furthermore, Ada has a wealth of what I consider very cool features. I love being able to e.g. declare an integer subtype with a range of 100 .. 250, and then declare an array that is indexed by that subtype. Another small but delightful feature is that I can write 1_234_000_000 instead of 12340000000 (quick, does that number have the correct number of zeros?) There are lots of other things in the language that make it clear that some very smart people spent a lot of time trying to get it right. Ada also has strong support for both realtime and concurrent programming, making it especially embedded-friendly.
One more thing - Ada code execution speed is in the same range as equivalent C/C++ code. But remember that a buffer overrun that executes very quickly is still a buffer overrun. Ada lets you generate code that automatically prevents or catches all kinds of nasty bugs, including buffer overruns, so you can have both speed and correctness.
There's plenty of Ada advocacy at www.adaic.org, www.adacore.com, and other locations. Two of my favorite Ada stories involve a contest to demodulate and decode a WW2 German Lorentz cipher machine, and a professor who switched his realtime model railroad programming projects from C to Ada and got impressive results from his students.
There is a phrase that I think sums up an ideal for writing software: Let the programmer write software in the problem domain, not in the solution domain. If you're dealing with widgets that weigh between 8 and 50 grams, and you want 0.1gm resolution, then being able to declare a Widget_Weight type that goes between 8 and 50 with 0.1 resolution is working in the problem space. Using an integer with rules for converting to and from grams is working in the solution space (my language doesn't understand fixed point data, but it does understand integers, so that's what I'll have to use). Everywhere you look in Ada you'll find ways to work in the problem space. This is reflected in a comment from the guy who programmed the Lorentz cipher machine (above), to the effect that programming the machine simulator in Ada was like building the actual machine out of the various mechanical parts.
What's Not So Great About Ada?
Ada seems to have attracted bad press like flies to flystrips. Some of that seems to have been because programmers didn't want to be told what language to use (even though most are, a good deal of the time). Some seems to have been because early compilers were pushing the technology and thus they were slow and they generated inefficient code. But that was 1983 and this is 2014. Does anybody still judge C++ by its first release in (coincidentally) 1983, or Java based on what was released in 1996?
Also, some bad press seems to have arisen from what I'm tempted to call an unprofessional attitude towards writing serious software. This is the charge that Ada is a straightjacket, bondage-and-domination language. Well, maybe, if you consider catching errors earlier to be an opressive burden. It's true that Ada forces you to be explicit and exact in your code, but that's just a recognition of the reality, garbage-in, garbage-out. Nobody complains "bondage and domination" regarding the various checks and checklists on their commercial aircraft, or the requirements that were applied to developing their medical devices or their microcontroller-dense new cars. It is a truism, of course, that no language will prevent all errors in your code. That doesn't mean, in my case at least, that I don't welcome all the help the language can give me, and Ada gives a whole lot of help. YMMV.
The biggest Not So Great About Ada right now, in my opinion, is that it is not ubiquitious. I wish it were as available as C, but that is far from the reality. But at least it's now about to become available for Cortex M3/M4, and that's a start.
Just a word about AdaCore software: they make their money (i.e. stay in business) selling compilers and support, along with other tools, to commercial development efforts. I just checked today and their commercial ARM tools are now listed. But they also offer free, unsupported releases of their tools at libre.adacore.com. If you want to dip your toes into the Ada waters you can download their Windows or Linux tools now to get started. Eventually their port to Cortex M3/M4 devices should be available there, but I don't have a timetable for such a release.
But Enough About That
OK, so for those of you patiently waiting for some technical content, here's an appetizer. Below is a tiny Ada program that uses some of Ada's built-in concurrency constructs to - wait for it! - read some buttons and blink some LEDs. You know you're excited now! The code executes on an STM32F4 board, which uses a Cortex M4F part from ST.
The code is simple enough, flashing the 4 LEDs on the board in either a clockwise or counterclockwise direction, with the direction being reversible with a button push. But this simple code reveals a lot of what makes Ada different and (again, IMO) better.
I will introduce the four files that comprise the program one at a time, and point out some of the unique Ada constructs in them. This will not by any stretch be an Ada programming tutorial, but it will point out some of the interesting features of the language. More Ada references will be at the end of this post. I should add that this code comes directly from an AdaCore example.
I should add that GNAT Ada deals with two kinds of source files, .ads files which contain the "spec" portion of the code, and .adb files which contain the "body" portion.
The four files in our first Ada LED-blinking program are:
File "registers.ads" -- defines and declares necessary hardware registers:
pragma Warnings (Off); with System.STM32F4; use System.STM32F4; pragma Warnings (On); package Registers is -- Bit definitions for RCC AHB1ENR register RCC_AHB1ENR_GPIOD : constant Word := 16#08#; RCC_AHB1ENR_GPIOA : constant Word := 16#01#; RCC_APB2ENR_SYSCFGEN : constant Word := 16#4000#; GPIOD_Base : constant := AHB1_Peripheral_Base + 16#0C00#; GPIOA_Base : constant := AHB1_Peripheral_Base + 16#0000#; SYSCFG_Base : constant := APB2_Peripheral_Base + 16#3800#; EXTI_Base : constant := APB2_Peripheral_Base + 16#3c00#; GPIOD : GPIO_Registers with Volatile, Address => System'To_Address (GPIOD_Base); pragma Import (Ada, GPIOD); GPIOA : GPIO_Registers with Volatile, Address => System'To_Address (GPIOA_Base); pragma Import (Ada, GPIOA); type EXTI_Registers is record IMR : Bits_32x1; EMR : Bits_32x1; RTSR : Bits_32x1; FTSR : Bits_32x1; SWIER : Bits_32x1; PR : Bits_32x1; end record; EXTI : EXTI_Registers with Volatile, Address => System'To_Address (EXTI_Base); pragma Import (Ada, EXTI); type SYSCFG_Registers is record MEMRM : Word; PMC : Word; EXTICR1 : Bits_8x4; EXTICR2 : Bits_8x4; EXTICR3 : Bits_8x4; EXTICR4 : Bits_8x4; CMPCR : Word; end record; SYSCFG : SYSCFG_Registers with Volatile, Address => System'To_Address (SYSCFG_Base); pragma Import (Ada, SYSCFG); end Registers;
Line 2: "with" brings in the named package, while "use" lets you use the short form of names in the package (e.g. GPIO_Registers instead of System.STM32F4.GPIO_Registers). It's possible to "with" a package without a corresponding "use" and sometimes that is considered good practice.
Line 5: starts definition of a package called "Registers". Packages are a fundamental element in organizing Ada code.
Line 7: Ada comments begin with "--" and go to the end of the line.
Line 8: Declares a variable as a "constant Word" with a value of 16#08#. Ada lets you represent numbers in any base from 2 to 16, using the form BB#NNNN#, where BB is the base (2 to 16) and NNNN is the digits of the number.
Line 18: Declares an instance of GPIO_Register (a record defined in the STM32F4 package), with volatile attribute and at the specified address.
Line 22: Declaring a record (like a C struct) to represent a number of associated hardware registers.
Line 45: Declaring some hardware registers to be of type Bits_8x4. What is Bits_8x4? In the System.STM32F4 .ads files, we find these declarations:
type Bits_4 is mod 2**4;
type Bits_8x4 is array (0 .. 7) of Bits_4 with Pack, Size => 32;
So Bits_8x4 is a packed array of 8, 4-bit values, stuffed into 32 bits. That's very cool, at least where I come from! It also fits perfectly with the associated hardware (the SYSCFG_EXTICRn registers), where each interrupt requires a 4-bit configuration value. Here we can just stuff the 4-bit value into the appropriate location in the array, and the compiler will do all the shifting and masking behind the scenes. This is a good example of working in the problem domain rather than in the solution domain.
File LEDS.ads -- defines and declares necessary types and subprograms related to the LEDs:
pragma Warnings (Off); package Leds is pragma Elaborate_Body; type LED_Number is mod 4; procedure On (This : LED_Number) with Inline; procedure Off (This : LED_Number) with Inline; type Directions is (Clockwise, Counterclockwise); function Current_Direction return Directions; end Leds;
Line 3: Causes the body of the package to be elaborated right after the spec. In the body, below, you'll see that the package code is just a single "Initialize" procedure. This assures that the startup code will execute in the correct order.
Line 5: Declares a variable type which is modular (i.e. unsigned), ranging from 0 to 3. We'll see more about this later.
Line 7,8: Declare two inline procedures that take an LED_number as a parameter.
Line 10: declare an enumerated type representing Clockwise and Counterclockwise.
Line 12: Declare a function that takes no parameters, and returns a value of type Directions. Both functions and procedures are what Ada calls "subprograms," with the big difference being that procedures don't return a value while functions do (and that returned value must be used).
File "LEDS.adb" -- the body of the package that deals with the LEDs (and the button - that could have been in its own package, and this happens in future examples):
with Ada.Interrupts.Names; with Ada.Real_Time; use Ada.Real_Time; with Registers; use Registers; pragma Warnings (Off); with System.STM32F4; use System.STM32F4; pragma Warnings (On); package body Leds is Masks : constant array (LED_Number) of Word := (16#1_000#, 16#2_000#, 16#4_000#, 16#8_000#); procedure On (This : LED_Number) is begin GPIOD.BSRR := Masks (This); end On; procedure Off (This : LED_Number) is begin GPIOD.BSRR := Masks (This) * 2**16; end Off; protected Button is pragma Interrupt_Priority; function Current_Direction return Directions; private procedure Interrupt_Handler; pragma Attach_Handler (Interrupt_Handler, Ada.Interrupts.Names.EXTI0_Interrupt); Direction : Directions := Counterclockwise; Last_Time : Time := Clock; end Button; Debounce_Time : constant Time_Span := Milliseconds (500); protected body Button is function Current_Direction return Directions is begin return Direction; end Current_Direction; procedure Interrupt_Handler is Now : constant Time := Clock; begin -- Clear interrupt EXTI.PR (0) := 1; -- Debouncing if Now - Last_Time >= Debounce_Time then if Direction = Counterclockwise then Direction := Clockwise; else Direction := Counterclockwise; end if; Last_Time := Now; end if; end Interrupt_Handler; end Button; function Current_Direction return Directions is begin return Button.Current_Direction; end Current_Direction; procedure Initialize is begin -- Enable clock for GPIO-D (leds) and GPIO-A (button) RCC.AHB1ENR := RCC.AHB1ENR or RCC_AHB1ENR_GPIOD or RCC_AHB1ENR_GPIOA; -- And for SYSCFGEN RCC.APB2ENR := RCC.APB2ENR or RCC_APB2ENR_SYSCFGEN; -- Configure PD12-15 (leds) and PA0 (Button) declare use GPIO; begin GPIOD.MODER (12 .. 15) := (others => Mode_OUT); GPIOD.OTYPER (12 .. 15) := (others => Type_PP); GPIOD.OSPEEDR (12 .. 15) := (others => Speed_100MHz); GPIOD.PUPDR (12 .. 15) := (others => No_Pull); GPIOA.MODER (0) := Mode_IN; GPIOA.PUPDR (0) := No_Pull; end; -- Select PA for EXTI0 SYSCFG.EXTICR1 (0) := 0; -- Interrupt on rising edge EXTI.FTSR (0) := 0; EXTI.RTSR (0) := 1; EXTI.IMR (0) := 1; end Initialize; begin Initialize; end Leds;
Line 12: Declares a constant array, indexed by type LED_Number, of 4 bitmask values for the 4 LEDs
Line 15, 20: Bodies of procedures to set one LED on or off. Notice that Ada arrays indexes are contained in parens(), not brackets[].
Line 26: A protected object named "Button" is being defined. A protected object automatically handles the sharing of its data with mutual exclusion between client tasks and/or interrupts, through access via what appear to be regular functions and procedures.
Line 28: A function that returns the state of the button.
Line 32: Declares the following procedure is private, that is, not exposed to the clients of the protected object. Since the procedure is an interrupt handler, it will never be called by software, only triggered by hardware.
Line 33: A procedure that "writes" the state of the button. In this case, the procedure is an interrupt handler, attached to the hardware button interrupt. The protected object insures mutual exclusion between calls to the Current_Direction function and the Interrupt_Handler procedure.
Line 35, 36: Declares some private data, data that will be used by the Button object but not exposed to its clients.
Lines 34, 40, 47: Uses Ada's built-in timing features to debounce the button interrupts (locking out any subsequent direction changes for 500ms after a direction change).
Line 53: A function that passes the button state from out of the protected object (so we do not have to expose the existence of the protected object, which is an implementation detail, to the client code).
Line 58: A procedure that initializes the hardware.
Line 106: The optional executable part of the body of the package, which just calls the Initialize procedure.
File "Main.adb" -- brings in the required packages and runs the main program loop:
with Leds; use Leds; with Ada.Real_Time; use Ada.Real_Time; procedure Main is Period : constant Time_Span := Milliseconds (75); -- arbitrary Next_Start : Time := Clock; Next_LED : LED_Number := 0; begin loop Off (Next_LED); if Current_Direction = Counterclockwise then Next_LED := Next_LED + 1; else Next_LED := Next_LED - 1; end if; On (Next_LED); Next_Start := Next_Start + Period; delay until Next_Start; end loop; end Main;
Line 7: 'Clock' is the current Time, in actual seconds and bits of seconds, not timer ticks that you have to convert back and forth from. Timing resolution is down to 1 CPU clock, or 5.95ns (board runs at 168 MHz).
Line 12: The main program is an endless loop which executes once every 75ms. No initialization code is explicitly called, since each package imported handles running its own initialization routines.
Line 15: Notice that Ada uses a single '=' for comparison (and ':=' for assignment).
Line 16: Remember Next_LED is a modular type. It rolls over automatically from 3 to 0, or 0 to 3. No need to check underflow/overflow and handle it manually.
Line 18: Calculate the next time point (every 75ms in this case).
Line 19: Notice how easy it is to sleep until a specified time. The main task will run every 75ms.
How the Program Works
It's probably fairly clear, but if not: First, the "Initialize" procedure gets called and initializes the uC. Then the loop in "main" begins running, turning off the current LED, checking for the direction and setting the next LED accordingly. Then the loop sleeps for 75ms and repeats. In the meantime, the button interrupt will update the direction whenever the button is pushed. Pretty simple!
One of the things you'll notice here is that there are no references to hardware timers or RTOS timers. All the timing uses language constructs. Ada understands time directly, which is pretty darned useful for a real-time language. Of course there's nothing to stop you from setting up other hardware timers and interrupts, should you need them. Every peripheral is accessible just as it would be if you were programming in C or C++.
Also notice that there are no RTOS calls to semaphores or other mutual-exclusion tools. Later when we create explicit tasks, there will likewise be no RTOS calls to create or run those. Ada understands tasking and concurrency, including mutual exclusion, directly as well.
Further Ada Resources
As I said in the beginning, this is only the briefest of introductions to Ada, and its use on ARM Cortex M3/M4. If you never considered, or even heard of, Ada, give it a look and see what you think. If you have a poor opinion of Ada from the past, consider taking a fresh look at the language as it exists today.
In the meantime, I'm designing an adaptor to be able to plug the STM32F4 board into my little experimenter motherboard, the one I've been using in the Introduction to Microcontrollers tutorials, and when that's done I'll be able to show Ada examples of some of the programs found in those tutorials.
https://www.youtube.com/user/AdaCore05
http://www.amazon.com/Programming-Ada-2005-John-Barnes/dp/0321340787
(covers Ada 2005, an Ada 2012 version is due to be out in July)
http://www.amazon.com/Building-Parallel-Embedded-Real-Time-Applications/dp/0521197163
http://www.amazon.com/Concurrent-Real-Time-Programming-Alan-Burns-ebook/dp/B001GS6TBO
- Comments
- Write a Comment Select to add a comment
Masks : constant array (LED_Number) of Word :=
(2#0001_0000_0000_0000#,
2#0010_0000_0000_0000#,
2#0100_0000_0000_0000#,
2#1000_0000_0000_0000#);
Lots of extra zeroes, but it is slightly clearer that it is a bit mask.
Sorry for the inconvenience, and for the poor english =)
AdaCore hasn't released a free version of their ARM toolset yet - I'm playing with a pre-release copy. I expect such a free version to be available within the next few months, but of course, I don't control their schedule. I'll post when it becomes available.
I downloaded latest free version of the AdaCore suit.
In that, the demo is same than yours but code is modified.
For some reason, System.STM32F4 is not used. Instead they have redefined parts of it and GPIO also locally.
If I include RUNTIME library's path into source paths, GPS or maybe gnat tries to recompile RUNTIME, which is not correct behavior. But I haven't been able to find correct setting in GPS project wizard for
using System.STM32F4.
Is this something GPS doesn't support and I should be using Makefiles or is there some simple solution
but AdaCore decided to ignore it!!!
Juha Okkonen
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: