Доброго времени суток! Обидно мало статей посвящено адскому программированию замечательному языку программирования Ада, а уж тем более нет их по программированию на нем для микроконтроллеров. А ведь сам язык даже позиционируется как язык встроенных систем… Может, это очень сложно?

Постараюсь в этой небольшой заметке привести классический пример программы мигания светодиодом, но на Аде, с тем, чтобы все желающие могли повторить.

В качестве платы у меня используется какая-то китайская плата с процессором stm32f407vet6, у нее светодиод подключен на порту А на пин PA6. В качестве среды программирования буду использовать gnat для arm под ubuntu, для заливки подключен st-link v2 и к нему установлен пакет st-link (его надо собрать и установить из stlink.git ), но можно использовать и любой другой программатор (например SEGGER).

К счастью, в состав поддерживаемых процессоров входит и stm32f4xx, так что все, что надо сделать — это создать файл проекта с указанием версии target и runtime. Ну и написать собственно нашу программу. Для облегчения жизни я использовал программу svd2ada для автоматической генерации файлов описания периферии из svd файла для моего процессора. Svd описание можно взять из стандарного пакета, а программа svd2ada есть на GitHub.

Все исходные тексты доступны на GitHub.

Итак — собственно программа ниже:

with STM32F40x;      use STM32F40x;
with STM32F40x.RCC;  use STM32F40x.RCC;
with STM32F40x.GPIO; use STM32F40x.GPIO;
with Ada.Real_Time;  use Ada.Real_Time;

procedure main is
  Led_Pin : constant := 6;
  Port    : ODR_Field renames GPIOA_Periph.ODR.ODR;
begin
  --  Включить clock for GPIO-A
  RCC_Periph.AHB1ENR.GPIOAEN := 1;
  --  Конфигурировать PA6
  GPIOA_Periph.MODER.ARR(Led_Pin) := 2#01#;
  GPIOA_Periph.OTYPER.OT.ARR(Led_Pin) := 0;
  GPIOA_Periph.OSPEEDR.ARR(Led_Pin) := 0;
  -- Переключать раз в секунду
  loop
     Port.Arr (Led_Pin) := Port.Arr (Led_Pin) xor 1;
     delay until Clock + To_Time_Span(1.0);
  end loop;
end main;

Сначала как все это собрать и запустить…

Ниже все команды для bash в каталоге проекта, все утилиты установлены в /opt/gnat/bin

Шаг 1. Устанавливаем пути для того, чтобы был вызван нужный нам toolchain

export PATH=/opt/gnat/bin:$PATH

Шаг 2. Собираем выполнимый файл.

gprbuild -P step1.gpr

Шаг 3. Создаем загрузочный бинарник для st-flash:

arm-eabi-objcopy -O binary main main.bin

Шаг 4. Загружаем нашу прошивку через st-link2, подключенный через USB

st-flash write main.bin 0x8000000

Если все прошло успешно, то можно любоваться на мигающий светодиод…

Теперь некоторые пояснения. Для этой целевой платы уже написан весь необходимый код инициалиации, причем частота процессора устанавливается в 168 Мгц, так что кода нам писать пришлось очень мало. Строка Port: ODR_Field renames GPIOA_Periph.ODR.ODR введена для удобочитаемости кода. Фунция Clock возвращает текущее время (точность — микросекунды).

Кроме того, для Ада-программ практически не нужна операционная система (так-то она сама себе ОС) для реализации задач, средств синхронизации и т.д.

Все богатство языка Ада для этого процессора недоступно, так как определен специальный профиль для компилятора — а именно Pragma Profile( Ravenscar ). Этот профиль вводит ряд ограничений для того, чтобы ваша программа гарантированно работала в таком окружении.

Впрочем, эти ограничения можно искуственно обойти, но в том-то и состоит существенное преимущество этой возможности языка, что если вы напишите программу в рамках такого профиля, то скорее всего она будет работать…

На этом всё, пишите в комментариях, что непонятно, постараюсь ответить. В следующей статье постараюсь привести полный цикл разработки интересного приложения на Аде для того же микропроцессора начиная с описания и анализа системы на aadl и заканчивая автоматическим доказательством с помощью SPARK.

Комментарии (12)


  1. el_gato
    13.03.2018 12:24

    Можно еще добавить что AdaCore держит на гитхабе достаточно часто обновляемую библиотеку драйверов для STM32 на Ada с примерами использования, а так же ежегодно проводит челленджи «Make with Ada» для разработчиков встраиваемых систем, с целью популяризации языка.


  1. UA3MQJ
    13.03.2018 14:56

    Только я начал читать… и статья закончилась.
    Появилась ли поддержка народно любимого STM32F103?
    Спасибо! Пишите, пожалуйста, еще про Ада и STM.


    1. oam2oam Автор
      13.03.2018 15:37

      Для STM32F107 я специально сделал версию BSP. Думаю, что не сложно сделать и для STM32F103. Вот только ОЗУ у них маловато… Что, впрочем решается использованием controlled типов.


  1. Shamrel
    13.03.2018 14:59

    Писать для МК на Ada можно. Но зачем? (сарказм off)
    Это определенной целевая аудитория или специфические задачи? Или просто «по приколу»?


    1. oam2oam Автор
      13.03.2018 15:43

      Одной из целей было привлечь внимание к использованию языка Ада. Сам я 30 лет пишу на С (и еще многих других языках), но Ада — это совсем другой подход…
      А вот МК выбран осознанно — именно там обычно очень велика цена ошибки, особенно неотловленной в процесса отладки. Именно для МК, для написания критических приложений, мне кажется и стоит использовать Аду.


  1. Shamrel
    13.03.2018 16:25

    Тогда жду статьи об использовании этого языка!
    Предлагаю для начала темы:

    1. Небольшой ликбез по языку (применительно к теме МК).
    2. «Сама себе ОС» — особенности окружения.



  1. Whuthering
    13.03.2018 20:25

    Хотелось бы увидеть более подробный рассказ в стиле «А зачем?»: какие именно преимущества есть у языка в целом и для встраиваемых систем (с примерами), каких проблем его использование позволяет избежать (тоже с примерами), какие пока что есть ограничения и как они могут быть решены, и т.д.


    1. el_gato
      13.03.2018 23:30

      Мне в Аде нравится злая система типов. Синтетическая ситуация — нужно написать функцию для вычисления площади прямоугольника.
      и чтобы вот длина могла быть от 1 до 10, ширина от 1 до 5 и никак иначе, ну и площадь соответственно. На Аде будет выглядеть как-то так.

      with Ada.Text_IO;
      procedure Main is
         type Rectangle_Width is  digits 5 range 1.0..10.0;
         type Rectangle_Length is digits 5 range 1.0..5.0;
         type Rectangle_Square is digits 5 range 1.0 .. 50.0;
      
         function "*"(A: Rectangle_Width; B: Rectangle_Length) return Rectangle_Square is
            Result: Rectangle_Square;
         begin
            Result := Rectangle_Square(A) * Rectangle_Square(B);
            return Result;
         end "*";
      
         Width: constant Rectangle_Width := 5.0;
         Height: constant Rectangle_Length := 5.0;
         Area: Rectangle_Square;
      begin
         Area := Width * Height;
         Ada.Text_IO.Put_Line(Area'Image);
      end Main;
      


      Если какой-то из параметров выйдет за пределы допустимых значений — бросает исключение, а если значения известны на этапе компиляции, то даже и не скомпилируется.

      Нужно посчитать то же самое, но вот чтобы размеры указывались с точностью 1/100 и чтобы никаких там 1.124568 меняем floating point на fixed point

         type Rectangle_Width is  delta  0.01 range 1.0..10.0;
         type Rectangle_Length is delta  0.01 range 1.0..5.0;
         type Rectangle_Square is delta  0.01 range 1.0 .. 50.0;
      


      Но что мне нравится больше всего, так это то что можно говорить при случае — «Я знаю язык программирования ада».


      1. oam2oam Автор
        14.03.2018 07:46

        Вы правы, но для МК это имеет еще и важное следствие, вот пример:

        type CFGR_PPRE_Field_Array is array (1 .. 2) of CFGR_PPRE_Element
             with Component_Size => 3, Size => 6;
        

        тут определен массив из двух элементов по 3 бита общей длины 6 бит. Любой программист для МК встречал необходимость описывать и использовать битовые поля регистров. Ну и конечно, можно создать свой тип практически любого вида и описать его область значений…
        Не буду даже говорить, насколько это спасает от ошибок времени компиляции и разработки!


      1. Tsvetik
        14.03.2018 11:03

        Поддержу. Давно пишу на C для embedded и все время поглядываю в сторону Ada. Очень нравится строгая типизация и отлов большинства ошибок на этапе компиляции.


      1. burdakovd
        14.03.2018 13:26

        Result := Rectangle_Square(A) * Rectangle_Square(B);

        Но в этом случае мы получается конвертируем длины в Square ещё до умножения, что несколько странно.


        Т.е. если бы условия были скажем такими:


           type Rectangle_Width is  digits 5 range 0.1..0.5;
           type Rectangle_Length is digits 5 range 0.1..0.5;
           type Rectangle_Square is digits 5 range 0.01 .. 0.25;

        тогда выражение Rectangle_Square(A) могло бы выбросить исключение если A > 0.25?


        1. el_gato
          14.03.2018 15:00

          Оно и выбросит исключение при таких условиях. А типы привести надо так как мы не можем использовать операцию умножения между двумя разными типами в той же самой функции которой мы собственно эту операцию и определяем. Безконечная рекурсия получится.
          (Можно обойтись и без этой функции просто перемножив предварительно приведя к одному типу

          Area := Rectangle_Square(Width) * Rectangle_Square(Height);
          )
          В Aде нельзя производить какие либо операции с различными типами данных без предварительного приведения.