Язык Embeddecy

Назначение

Язык Embeddecy является высокоуровневым языком программирования микроконтроллеров, представляет собой надстройку над языком С. На языке Embeddecy описываются программы для программируемых устройств в системе MCU Blocks.

Модульность

Программа на языке Embeddecy описывается в файлах *.embс, которые превращаются транслятором в обычные С-файлы. Язык Embeddecy совмещен с языком С следующим образом:

  1. Синтаксис языка Embeddecy основан на синтаксисе языка С с добавлением дополнительных конструкций;
  2. Программа на языке Embeddecy описывается в файлах *.embc;
  3. Имеется возможность обращаться из программы на языке Embeddecy к модулям на других языках программирования (например, на языке С).

Элементы языка Embeddecy описываются в пространствах имен, задаваемых с помощью слова namespace. Элементами языка Embeddecy являются все элементы языка С, а также такие новые элементы как:

  • модули (задачи - task, пакеты - package)
  • пользовательские типы (объявленные через typedef, перечисленческие enum и структурные struct типы);
  • анонимные функции;
  • типы делегатов и переменные-делегаты (delegate);
  • события (event);
  • шаблоны модулей (template, объявления параметров);
  • интерфейсы (interface);
  • директивы препроцессора (#define_module, #main).

Задачи, в отличие от пакетов, содержат поток управления и исполняются параллельно другим задачам. Задачи обмениваются друг с другом сообщениями, сигнатуры которых описываются в теле задачи. Функции задач не видны извне - их можно вызывать только изнутри (из других функций этой же задачи) и поэтому можно сказать, что весь код задачи исполняется всегда только в ее потоке управления. Код функций пакетов исполняется в том потоке управления, из которого происходит вызов этих функций. Пакеты и задачи не являются объектами в терминологии объектно-ориентированных языков программирования, т. е. их нельзя создавать динамически: существeт только по одному экземпляру каждой описанной в коде задачи и на них невозможно в коде получить ссылку.

Интересной особенностью языка, позволяющей удобно оформлять библиотечные решения, является возможность описывать шаблоны модулей с параметрами. Для того, чтобы шаблон модуля превратился непосредственно в модуль, необходимо его инстанцировать, т. е. задать формальным параметрам шаблона фактические значения.

В одном файле может быть описано несколько модулей, каждый может находиться в своем пространстве имен. Нет никаких ограничений на имя файла: имя файла никак не привязано к содержимому файла. В системе существует понятие библиотеки как набора исходных файлов Embeddecy в файловой структуре, расположенных внутри определенной папки (и вложенных в нее папках). В бинарном или объектном виде Embeddecy файлы не распространяются в отличие от статических библиотек .lib и объектных файлов .obj для языка С.

В IDE файлы на языке Embeddecy и С разбиваются по устройствам, к которым эти файлы относятся. Точкой входа в программу устройства является функция, помеченная директивой #main (директивой также может быть помечено тело задачи body). Все задачи должны быть запущены пользователем самостоятельно (обычно в начале программы). Однако если точкой входа является функция body (т.е. она помечена директивой #main) одной из задач, то эта задача будет запущена автоматически, пользователю необходимо запустить остальные задачи самостоятельно. Если в файлах устройства нет функций, помеченных директивой #main, то ищется стандартная точка входа языка С (функция main).

Области видимости

Области видимости в языке Embeddecy определяют список доступных из некоторой области программы элементов из другой области программы. Под доступностью элементов обычно понимается возможность обращения к ним через их прямые короткие названия без указания областей, в которых они находятся. Т.к. модули описывают в файлах, а сущность “файл” никак не представлена в языке, то области видимости в системе задаются не только с помощью средств языка, но и с помощью средств инструментальной среды: в среде для проекта указываются ссылки на библиотеки как наборы файлов, среди которых необходимо искать нужные модули.

В языке Embeddecy области видимости задаются на двух уровнях:

  1. видимость элементов пространств имен;
  2. видимость элементов модулей.

Видимость элементов пространств имен. Видимость элементов пространств имен представляет собой возможность из некоторой области программы обращаться к элементам некоторого пространства имен непосредственно через их короткое название. Элементы, описанные в одном пространстве имен видны друг другу. Элементы, описанные вне пространств имен, видны отовсюду. Для того чтобы включить в область видимости одного пространства имен элементы из другого, необходимо импортировать данные элементы при помощи директивы import. При импортировании можно указать как отдельный элемент (с указанием пространства имен, в котором он находится), а также можно импортировать все элементы из пространства имен при использовании символа *. Также можно импортировать элементы файлов на языке С, для чего используется ключевое слово importc.

namespace MyNameSpace
 {
 importc "headerfile.h";
 import MCUBlocks.atmel.atmega1280.UART1;// импорт шаблона м.у.а.б. UART1
 import MCUBlocks.atmel.atmega1280.*; // импорт всех элементов
 // пространства имен MCU Blocks.atmel.atmega1280
 #init_module UART1 MY_UART1 // инициализация модуля MY_UART1 по
                                // шаблону UART1
 {
     ...
 }
 ...
 }

Без области видимости обращение к элементу другого пространства имени возможно только через имя этого пространства. Например, если в предыдущем примере не импортировать элементы пространства имен MCUBlocks.atmel.atmega1280, то инициализировать модуль по шаблону придется так:

#init_module MCUBlocks.atmel.atmega1280.UART1 MY_UART1
 // инициализация модуля MY_UART1 по шаблону UART1
 {
     ...
 }

Пространства имен могут быть вложенными друг в друга. В таком случае именем дочернего пространства имен становится имя, составленное из имени, в котором находится данное пространство имен, символа ”.” и короткого имени самого дочернего пространства имен. Ниже приведен пример, показывающий различные аспекты задания областей видимости за счет использования пространств имен.

int z1;
namespace MCUBlocks
{
    namespace atmel // полное название = MCUBlocks.atmel
    {
            const int a = 1;
            int a2;
            void func1()
            {
                    // поскольку не было import приходится указывать
                    // название пространства имен
                    int c = MCUBlocks.b;
            }
            namespace atmega1280
            {
                    import MCUBlocks.*;
                    import MCUBlocks.atmel.a2;
                    // можно обращаться по короткому названию, т.к.
                    // было произведено импортирование
                    int d = b;
                    // поскольку не было импортирования, то
                    // приходится указывать пространство имен
                    d = MCUBlocks.atmel.a;
                    // можно обращаться по короткому названию, т.к.
                    // было произведено импортирование
                    d = a2;
                    d = z1; // т.к. вне пространств имен
            }
    }
    // поскольку не было импортирования, то
    // приходится указывать пространство имен
    int b = MCU Blocks.atmel.a;
}

Видимость элементов модулей. Второй уровень областей видимости позволяет задать видимость отдельных элементов видимых пакетов. Существует два модификатора видимости элемента пакета: private - невидимый извне и public - видимый извне данного модуля. Однако есть ограничение для задач: у задачи видимыми могут быть только сообщения и события (только с ними можно использовать модификатор видимости public - все остальные элементы задач всегда невидимы и с ними нельзя использовать модификатор public а модификатор private в таком случае не нужен (хотя может быть использован для наглядности). Что касается пакетов, для их элементом по умолчанию задается модификатор доступа private (если модификатор опущен). Модификатор доступа идет первым при описании элемента пакета.

Существуют ограничения на области видимости, которые задаются принадлежностью кода файлам для разных устройств (подробнее в разделе MCU Remoting).

Делегаты и анонимные функции

Анонимные функции - функции, у которых описан только код функции без ее сигнатуры. Использование анонимных функций является удобным способом использовать параметризируемый код без отдельного описания самой функции как элемента модуля и без задания ей названия.

Формат описания анонимной функции таков:

([<типа параметра 1>] <название параметра1>, …, [<типа параметра n>] <название параметраn>) -> {<блок кода>},

Например

(int a, float b) -> {return a+b;}

В область видимости кода в анонимной функции входит область видимости кода в точке использования согласно общим правилам, а также список параметров. Если типы параметров анонимной фунцкции не указаны, они берутся из сигнатуры события, на которое осуществляется подписка или из сигнатуры делегата, которому выполняется присваивание. Тип возвращаемого значения определяется по выражению return в коде функции как супертип возможнных возвращемых функцией значени; если выражения return не имеется, то предполагается тип возвращаемого значения как void Анонимные функции могут использоваться:

  1. для подписки на события;
  2. для присвоения переменной-делегату (или передачу делегату как значение параметра).

Делегаты - переменные, представляющие собой ссылку на явную или анонимную функцию и введенные в языке Embeddecy как синтаксический сахар вместо использования указателей на функцию. Для описания делегата, сначала необходимо задать тип делегата, после чего уже можно объявлять переменную-делегат заданного типа. При описании типа делегата указывается ключевое слово delegate название вводимого типа-делегата и сигнатура функции.

delegate void mydelegate(int, int); // описание типа mydelegate как
// делегата на функцию с двумя целочисл. пар-ми
delegate void mydelegate(int a, int b); // использование имен переменных
// возможно, но имеет смысл только для документации
// и прозрачности кода

После этого тип делегата можно использовать как обычный тип в языке С, т.е. использовать переменные-делегаты с данным типом. В коде переменной-делегату можно присвоить значение - либо существующую функцию с аналогичной сигнатурой, либо анонимную функцию. Можно также передавать делегат как параметр.

Примеры использования делегатов:

void someFunc1(int a, int b);
void someFunc2(int a, int b);
delegate void mydelegate(int a, int b); // описание типа делегата
delegate float mydelegate2(int a, int b); // описание типа делегата
mydelegate d = somefunc1; // инициализация делегата somefunc1
mydelegate2 d2;
<...>
{
    d = somefunc2; // с этого момента делегат ссылается
   // на функцию somefunc2
    d = (a, b)->{send task1.mes1(a);}; // с этого момента делегат
//ссылается на анонимную функцию которая отсылает сообщение,
                    //передавая параметр “a” из вызывающего делегат кода
            // более полная запись делегирования, приведенного выше
            d = (int a,int b)->{send task1.mes1(a);};
    d = (a, b)->
{ // с этого момента делегат ссылается
  // на анонимную функцию
int c = 1;
c = 2;
};
d2 = {return a==b? a+5 : b+6.0;};
}

Параллельное программирование

Основное отличие задачи от пакета заключается в том, что задачи исполняются параллельно, поскольку каждая содержит свой поток управления. Взаимодействие между задачами осуществляется посредством отсылки и принятия сообщений (часть из них отсылаются при возникновении событий). Сообщения могут иметь параметры и их можно отправлять синхронно и асинхронно. Отправить сообщение пакету невозможно - у него возможно только вызвать функцию. Задачам функции нужны только для внутреннего пользования.

Синхронный способ взаимодействия между задачами предполагает взаимодействие через механизм “рандеву”: задача-инициатор посылает другой задаче запрос на рандеву и ждет согласия, после согласия принимающей задачи рандеву считается произведенным и вызывается код по обработке сообщения принимающей задачи, после чего исполнение кода в каждой задаче продолжается. Также синхронно в задаче вызывается событие как последовательный синхронный вызов обработчиков функций (анонимных или тех, на которые ссылаются подписанные делегаты).

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

Для управления пакетами и задачами в язык внедрены конструкции, представленные в таблице 1.

Таблица 1. Конструкции для работы с задачами и для взаимодействия между модулями
Описание команды Пример
Описание задачи task<имя задачи> {<описание функций> body{<тело задачи>}}
Запустить планировщик startSheduler();
Остановить задачу и удалить ее (внутри задачи) deleteTask();
Остановить задачу и удалить ее (извне с указанием имени удаляемой задачи) <имя задачи>.deleteTask();
Выдержка паузы в данной задаче в течение заданного количества миллисекунд <имя задачи>.delay_ms(ms_count)
Синхронная отправка сообщения <имя задачи>.<имя сообщения>(<факт. пар-ры>);
Асинхронная отправка сообщения send <имя задачи>.<имя сообщ>(< факт. пар-ры >);
Проверить, нет ли непринятого сообщения hasmessage(<имя сообщ.>);
Прием сообщения accept <имя сообщ.>;

Для целей синхронизации внутри функций пакетов возможно использовать семафоры (бинарные и счетные), а также предлагается использовать конструкцию critical { <критическая секция> } для обозначения кода критической секции. Критической секцией считается участок кода, который может исполняться только в одной задаче и не может быть прерван другим или параллельно исполнен другим (для этого при входе в секцию отключается прерывания и планировщик Использование данной конструкции предполагает автоматическую синхронизацию по бинарному семафору и отключение прерываний для микроконтроллера при входе в секцию и восстановление при выходе из нее.

События

В языке Embeddecy события используются для удобства централизованного вызова кода делегатов и анонимных функций. Событие имеет параметры (наподобие функции) и описывается следующим образом:

event myEvent(<формальные параметры>);

Делегаты и анонимные функции, которые необходимо вызывать при возникновении события, необходимо подписывать на это событие. На событие можно подписывать только делегаты и анонимные функции, не возвращающие значения (т.е. тип возвращаемого значения void). Преимуществом использования делегатов является возможность впоследствии отписать делегат от события, в то время как анонимную функцию напрямую можно только подписать на событие. Подписка делегатов и анонимных функций на событие производится при помощи оператора +=, удаление подписки - -=. Для подписки на событие, находящееся в другом устройстве, используется оператор via.

Синтаксис подписки выглядит следующим образом:

<Название задачи>.<Название события> += <делегат>/<анонимная функция>;

<Название задачи>.<Название события> -= <делегат>;

Примеры:

task1.myEvent += (a, b)->{task2.MyMessage(a, b);};
task1.myEvent += (a, b)->{send task2.MyMessage(a, b);};
task1.myEvent += (a, b)->
{
send task2.MyMessage(a, b);
int i = 1;
};
delegate void td(int, int);
td deleg = (int a, int b)->{int c = a + b; return;};
task1.myEvent += deleg;
task1.myEvent -= deleg;

Вызов события может быть произведен только из модуля, в котором оно объявлено следующим образом:

myEvent(5, 6);

Удобная работа с битами

Для удобства работы c битами переменных каждая переменая целочисленных типов представляется как массив, элементами которого являются биты (тип bin). Обращения к битам целочисленного типа синтаксически производится как обращение к элементам массива с использованием номера бита в операторе обращения к элементу массива [].

Пример:

unsigned char c = 2;
bin c2;
c2 = c[1]); // c2 == 1
PORTA[7] = 0; // PORTA &= ~(1 << 7)

Перечисления

В языке С, как известно, в коде элементы перечисления используются как константы, без использования имени перечисления, что делает невозможным использование одноименных элементов разных перечислений. Например, если в перечислении Colors описан элемент RED, то второго такого элемента не может быть ни в каком другом перечислении (имя RED резервируется как если бы RED была простой константой). В языках C# и Java такого ограничения нет, поскольку обращение к элементу перечисления идет через имя перечисления, например, Colors.RED. В языке Embeddecy реализована поддержка обоих подходов:

1. Существует возможность использовать в коде элемент перечисления как константу без указания названия перечисления. Данный подход является устаревшим подходом языка С и не рекомендуется к использованию и поэтому элементы перечислений не появляются в автодополнении если не указано имя перечисления. Данный подход применяется к каждому отдельно взятому элементу перечисления следующим образом: если в коде было использвано имя элемента перечисления, то данный элемент считется использующимся как константа и в этом случае включается правило “не может существовать элемента с другим таким именем в другом перечислении” (в противном случае будет выдаваться ошибка компиляции).

2. Наиболее приемлемым считается подход, при котором обращение к элементам перечисления идет через имя перечисления, например, Colors.RED. В этом случае элемент RED может существовать в любом другом перечислении.

enum Colors
{
     Red = 0, Green = 1, Blue
}

MCU Remoting и поддержка распределенных программ

Технология MCU Remoting предназначена для упрощения описания программ для разных автоматизирующих устройств, взаимодействующих друг с другом. Суть ее заключается в следующем: абстрагироваться от уровня аппаратных интерфейсов и протоколов, построенных поверх них, до уровня модулей в разных устройствах, которые обмениваются друг с другом параметризируемыми сообщениями. В случае обмена сообщениями между модулями разных устройств пользователю предлагается выбрать интерфейс, по которому должно отправляться сообщение. Фактически указание интерфейса, по которому необходимо передавать сообщение или производить синхронный вызов, прозводится через имя модуля управления блоком, который управляет необходимым интерфейсным блоком. Указание названия интерфейсного модуля управления идет через слово via после сигнатуры отсылаемого сообщения. Например, асинхронный вызов сообщения message1 задачи task3 через модуль управления интерфейсным блоком SPI1:

send task3.message1(<параметры>) via SPI1;

Удаленная подписка:

task3.event1 += send task3.message1(<параметры>) via SPI1;

Следует отметить ограничения поддержки написания распределенных программ: среди элементов программ, находящихся в разных устройствах, взаимодействовать могут только модули в конструкциях с оператором via. Причем взаимодействие это возможно только через отсылку сообщений задачам, вызова функций пакетов, вызова событий и делегатов. Для фрагментов программ, находящимися в разных устройствах:

  • возможно Только с помощью оператора via
    • вызывать функции пакетов,
    • отсылать сообщения,
    • вызывать события,
    • вызывать делегаты пакета.
  • нельзя напрямую обращаться к переменным (вне пространства имени, внутри него или к переменным пакета)

  • одно пространство имен может распространяться только на одно устройство

  • предупреждение, не являющееся ошибкой: при удаленном вызове или передаче сообщения не желательно передавать значения параметров, содержащих адреса (указатели). Ошибкой является передача параметра-делегата.

  • подписываться на события возможно только в пространстве имен, где это событие объявлено со ссылкой на функцию или делегат, содержащихся в фрагменте программы для этого же устройства.

  • делегату можно присваивать функцию только из фрагмента программы этого же устройства, в котором он объявлен.

В качестве модуля, через который можно передавать сообщение (т.е. название модуля после оператора via) может быть только интерфейсный модуль управления. В данном случае для выяснения, является ли модуль интерфейсным, применяется утиная типизация: модуль является интерфейсным, если содержит присущий интерфейсным модулям набор функций.

Рассмотрим пример, в котором имеются два фрагмента кода, находящихся в разных устройствах, при этом из кода устройства 1 невозможно обращаться к переменным устройства 2:

Device1:

int a1;
namespace ns1
{
     int b1;
     void do1_2()
     {
     }
     package p1
     {
             int c1;
             void do1()
             {
             a2 = 5; // ошибка компиляции: необъявленный ID
             ns2.b2 = 5; // ошибка: попытка доступа к переменным
             // другого устройства
             ns2.p2.c2 = 5; // ошибка: попытка доступа к переменным
                     // другого устройства
             ns2.p2.do2(); // ошибка: попытка доступа к элементу
                             // модуля другого устройства
                             // без использования интерфейса (via)
             ns2.event2(5,6) via <interface module>; // возможно
             ns2.event2 +=  <...>; // запрещено
             int a = deleg2(5,6) via <interface module>; // возможно
             int a = deleg2(5,6) via <non-interface module>; // нельзя
             deleg2 = <...>; // запрещено
             };
     }
}

Device2:

int a2;
namespace ns2
{
     event event2(int, int);
     delegate int tdeleg(int, int);
     tdeleg deleg2;
     int b2;
     package p2{
             int c2;
             void do2(){
             // все аналогично p1.do1();
             };
       }
     }

Шаблоны модулей

Шаблоны модулей позволяют описать модули (пакеты и задачи) и их параметры, для которых затем при инстанциации будут заданы конкретные значения. Параметры модулей описываются с помощью специальных директив.

Существует три вида параметров шаблонов модулей управления:

  1. макро-параметры;
  2. параметры-типы;
  3. параметры-пины;
  4. параметры-значения;
  5. параметры-модули.

Формат описания шаблона и его параметров:

template <module_type> <name>

<macro <param_macro_name>,

type <param_type_name> is <type1> | <type2> | … | <typen>,

pin <param_pin_name>,

value <expr_type_name> <param_expr_name>,

module <interface_name> <param_module_name>>

{<...> // тело шаблона модуля}

где module_type – тип модуля: package / task, param_macro_name – название макропараметра, param_pin_name – название параметра-пина, param_expr_name – название параметра-выражения, expr_type_name – название типа параметра-выражения, param_type_name – название параметра-типа, param_module_name – название параметра-модуля, interface_name – название интерфейса, который должен реализовывать модуль.

Макро-параметры представляют собой строковые параметры (значением может быть любая строка символов: число или даже блок кода, не содержащие символ “;”).

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

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

Особым параметром, относящимся ко всему шаблону, является язык реализации шаблона, задающийся параметром в квадратных скобках: [lang=<название языка>]. Язык шаблона означает язык, на котором будут реализовываться функции шаблона. Сигнатуры при этом все так же описываются на языке Embeddecy, но все, что находится внутри фигурных скобок (тела функции) описывается на целевом языке, указанном в параметре.

Пример описания шаблона модуля

template interface ADC_GENERAL // обобщенный интерфейс модуля управления АЦП
< MCU_GeneralConfig generalConfig,
type ValueType is int | unsigned byte,
type BitsResolution_enum is ADC_AVR.BitsResolution_enum | ADC_STM.BitsResolution_enum | ADC_PIC.BitsResolution_enum, ...>
{
     void Init();
     <...>
     ValueType ConvertGetSample (Channels_enum channel)
     <...>
}
template package ADC_STM
< MCU_GeneralConfig generalConfig,
type ValueType is int | unsigned char> implements ADC_GENERAL < MCU_GeneralConfig generalConfig,
type ValueType is int | unsigned char, BitsResolution_enum = ADC_STM.BitsResolution_enum, ...>
{
public typedef enum BitsResolution {<...>};
<...>
<...> // реализация функций ADC_GENERAL
     // ниже идут некроссплатформенные функции, которых нет в интерфейсе
public bool getOverrunInterruptEnabled();
<...>
}
<...>
// После этого можем инстанцировать пакет для работы с АЦП STM
// с указанием модулям
#define_module stm8l152c6cfg = STM8l152c6_GeneralConfig<...>
#define_module adc_hardware = ADC_STM <stm8l152c6cfg,unsigned char> ;
#define_module adc_crossplatform = (ADC_GENERAL < generalConfig, ValueTYpe, BitsResolution_enum = ADC_STM.BitsResolution_enum>, ...) adc_hardware;
<...>
adc_crossplatform.Init(); // кроссплатформенная инструкция
adc_hardware.getOverrunInterruptEnabled();// некроссплатформенная инструкция

Инстанциация шаблонов

При инстанциации пина задается некая строка в формате [особый идентификатор порта, номер бита в порте <режим>], где режим = input, inputPullUp, output. В коде возможны следующие обращения к пину:

Пример инстанциации пинов:

// импорт шаблона модуля, чтобы его можно было использовать
import MCUBlocks.PIRControl
// Модуль управления PIRC1 из шаблона PIRControl
// Пин МК PA2, подключаем к пину блока State
// Пин МК PA0, подключенному к пину блока Power
// Пин МК PA1, подключенному к пину блока Value
#init_module PIRControl PIRC1    // определение блока
{
// здесь идет задание статических параметров блока
// Задать параметр шаблона State блока PIRC1 = PA2 на вход
#define_pin State [A,2,input]
// Задать параметр шаблона Power блока PIRC1 = PA0
#define_pin Power [A,0]
// Задать параметр шаблона Value блока PIRC1 = PA1 на выход
#define_pin Value [A,1,output]
// Задать параметр шаблона PWM как блок PWM1
#define_module PWM PWM1
// Установка макропараметра
#define_macro freq 45
}
void func() // пользовательская функция
{
PIRC1.duration=48; // установка переменной модуля
PIRC1.setFreq(50); // вызов С-функции, возможно inline
PIRC1.State = 1; // записали единицу в PA2
}
LED_PIN = 1; // взвести, т.к. значение
LED_PIN.high(); // взвести
LED_PIN = 0; // сбросить
LED_PIN.low(); // сбросить
LED_PIN.invert(); // инвертировать
<...> = LED_PIN; // использовать текущее значение
LED_PIN.modeOutput(); // установить на выход (если режим при инстанциации
    // не был задан или был задан на выход)
LED_PIN.modeInputPullUp(); // установить на вход с подтяжкой (если режим
 // не был задан или задан на вход)
LED_PIN.modeInput(); // установить на вход без подтяжки (если режим не
   // был задан или на вход)

Тракт трансляции

Транслятору Embeddecy на вход подается список файлов (.с2_0, и .h файлов проекта), в которых он находит модули и их элементы (также может быть указан архив файлов). Embeddecy-файлы преобразуются в С-файлы следующим образом:

  1. каждый Embeddecy-файл превращается в С-файл, все конструкции языка Embeddecy раскрываются в конструкции языка С;
  2. пространства имен языка Embeddecy раскрываются так, что к называниям всех элементов, объявленных в некотором пространстве имен, добавляется название этого пространства имен; к элементам модулей добавляется название модуля, в котором они объявлены;
  3. файлы и .h проверяются транслятором Си 2.0 на наличие ошибок, как с точки зрения языка Си, так и с точки зрения взаимодействия конструкций Embeddecy и C;
  4. все файлы устройства собираются и компилируются в бинарный файл.
_images/image2.png

Пример описания модуля управления виртуальным блоком, управляющим светодиодом

[lang=c]
template package LEDControl
#external_import
{
     #include <avr/io.h>
}
{
     #param_macro PWM;
     #param_pin LED_PIN;
     private bool LEDstate = 0;
     // Включить светодиод
 void On()
 {
     LED_PIN = 1;
 }
 // Выключить светодиод
 void Off()
 {
     LED_PIN = 0;
 }
     // Включить светодиод с определенной яркостью (с использованием ШИМ)
 void OnWithBrightness(int percents)
 {
     // перечисляются параметры для иниц. ШИМа
     // настроим ШИМ на генерацию прерывания,
             // и установим туда свой обработчик, который будет Toggl’ить LED
             PWM.Enabled = false; // стадартно: на всякий случай
             PWM.Freq = MCU.freq / block.prescailer; // на частоте в 2 раза
             // меньше MCU. Использовали свойство блока “prescailer”
             PWM.Duty = percents; // скважность
             PWM.PulsesToSend = 0; // бесконечное количество импульсов слать
     PWM.InterruptEnabled = true; // разрешаем прерывание
     PWM.OnTick += PWM1_Tick_handler; // устанавливаем обработчик
     PWM.Enabled = true; // включаем ШИМ
 }
 // Фактически данный обработчик будет вызываться из прерывания ШИМа,
 // а лучше - встраиваться в него inline’ом
 private void PWM1_Tick_handler()
 {
     if (!LEDstate)
             On();
     else    Off();
     LEDstate = ! LEDstate;
 }
 }

Пример кода, включающий различные конструкции (без программного смысла)

namespace myns
{
import MCU Blocks.atmel.atmega1280.UART1;
import MCU Blocks.atmel.atmega1280.SPI1;
import MCU Blocks.atmel.atmega1280.RadioModuleControl;
import myns2;


#init_module MCU Blocks.atmel.atmega1280.UART1 UART1
// модуль UART1 из шаблона UART1
{
    baud_rate = 19200;
    transmit = true;
    receive = true;
    word_size = UART.WORS_SIZES.bit8;
    stop_bits = UART.STOP_BITS.TWO
}

#init_module MCU Blocks.atmel.atmega1280.SPI1 SPI1
{
     mode = master;
}

#init_module MCU Blocks.atmel.atmega1280.RadioModuleControl
{
     SPI = SPI1;
}

package package1
{
     enum MyEventEnum
    {
     val1, val2, val3
    }

     event myevent(MyEventEnum e, int x); // определили событие

void func1(int x)
{
     send UART1.sendString("abc");
     PORTA = x;
     bit b = x[7]; // берем последний бит
     if (b == 0)   // == 0b == 0x0
     {
         PORTA[7] = 0;
         myevent(MyEventEnum.val2, 5);
     }
     else myevent(MyEventEnum.val3, 6);
}

float farray[] = {123.0, 34, 32434};
float farray2[5];
void func2()
{
     if (sizeof(farray2) == 3)
         myevent(MyEventEnum.val1, 7);
}
}

task task1
{
     event myevent(int x, int y);
     int a, b = 3;
     void func1() {}
     void main()
     {
      myevent(x, y) += task2.mes1(x);
      while (true)
            {
                 task2.mes2();
                 delay_ms(1000);
                 send task3.mes4();
                 task3.mes1(5, 7.0);
                 myevent(5, 6);
            }
            myevent -= task2.func1;
     }
}
task task2
{
 message mes1(int x);
void func1(int x, int y)
{
 PORTA = x;
 PORTB = y;
 PORTB[0] = 0;
     }

      message mes2(int x);
      void func2(void){}

      void main()
      {
            while (true)
            {
                 accept mes1(y) do func1(0, y);
                 accept mes2() do func2();
            }
      }
}

task task3
{
     message mes1(int x, float y);
     message mes4();
     void func4(){}
     void main()
     {
     for (;;)
     {
          accept mes1(x, y) do
          {
              x++; y -= 1;
              send task4.mes1(x, y) via RadioModuleControl;
          }

          accept mes4() do func4();
     }
   }
}

} // end namespace
namespace myns2 //другое устройство
{
task task4
{
     message mes1(int x, float y);
     body
     {
     for (;;)
       {
          accept mes1(x, y);
       // зачем-то просто принятие без чтения параметров
       }
     }
}
}