FLProg – Создание пользовательских блоков на языке С (Урок вне очереди)

e8fb1d50a6a24f57b46e3128c769f748.png

Проект FLProg в последнее время обрёл достаточно большую популярность, и мне перестало хватать времени на создание блоков для той периферии, которая необходима пользователям. В то же время среди пользователей сайта нашлось достаточное количество людей, которые неплохо разбираются в языке C и могли бы мне помочь в развитии программы. Я решил дать им соответствующий инструмент. Таким образом, в версии 1.10.3 появилась возможность создавать пользовательские блоки с интегрированным кодом на С. Это привело к довольно неожиданным результатам. Этим инструментом заинтересовались не только разбирающиеся в программировании пользователи, но и те, кто до этого ни писал не сточки кода. Они начали писать сначала простенькие блоки (например, получение логарифма — среди стандартных у меня такого блока не было), заканчивая уже серьёзными блоками с применением библиотек. Поэтому я хочу немного поломать задуманную последовательность уроков по работе с программой, и вне очереди расскажу, как создавать подобные блоки.
На уроке будет создан блок для цифрового компаса HMC5883L. За основу блока была взята эта статья. Библиотека взята отсюда.
Итак, запускаем программу и создаём новый проект. В библиотеке элементов переходим на закладку «Пользовательские» и выделяем ветку дерева библиотеки пользовательских блоков, где будет располагаться новый блок. Затем нажимаем кнопку «Создать блок»

6cdf736284fa4239b3ecf29f586785a0.PNG

Откроется окно с выбором языка программирования для блока. Доступны языки FBD, LAD и Code. Языки LAD и FBD будут рассмотрены в других уроках, а сейчас выбираем «Code».

759e27c88fdc4746a7e5029c6b689b6c.PNG

Открывается окно редактора блока пользователя. В верхней части находятся закладки параметров (1), в нижней закладки секций кода (2).

1da78d141c39412ea9e8218f19c07c75.PNG

Заполняем основные параметры.

a93ea2c107a0487e9d6fa57a88c8217a.PNG

Входы и выходы блока создаются аналогично. Входов у нашего блока не будет, поэтому попускаем эту закладку и переходим к закладке «Выходы блока». У блока будет три выхода — направление по оси X, Y, и Z. Для создания выхода нажимаем кнопку «Добавить выход».

eff7ea080670423895f4f4ec1ba6b5ba.PNG

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

431588724bd04210996cc07478e3995f.PNG

Таким же образом создаём остальные выходы.

d89371bf333c47e0a394b42f638f37f5.PNG

Теперь зададим параметр, который пользователь будет задавать при использовании блока. Это будет чувствительность датчика. В соответствии с описанием она выбирается из ряда: 0.88, 1.3, 1.9, 2.5, 4.0, 4.7, 5.6, 8.1

Переходим на закладку «Параметры пользователя» и нажимаем кнопку «Добавить параметр».

51e2f0624fde4471b63f454b87f07690.PNG

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

1909d9da91b74898b7863f1d4271039c.PNG

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

Теперь займёмся библиотекой. Перейдём на закладку «Библиотеки». Используемые в работе блока библиотеки можно загрузить непосредственно в блок. Для этого необходимо нажать кнопку «Загрузить библиотеку».

9289c80ed50a44b5990b33cb5f3cfae1.PNG

Откроется окно выбора папки с библиотекой.

e6c1f6505bb2456d87b80e520695a0b0.PNG

После выбора папки библиотеки она загрузится в блок, и будет отображена в списке загруженных библиотек. Возможна загрузка нескольких библиотек, если это необходимо для работы блока.

24648f1bf5874971a14a7eec697e99e8.PNG

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

На закладке «Описание» желательно написать, как работать с блоком, для чего он предназначен и т.д. Это описание поможет другим пользователям использовать его.

Теперь перейдём в зону кода

Первая закладка — секция «DeclareSection». На этой закладке необходимо прописать подключение необходимых библиотек, объявление переменных, массивов и структур. Заполним эту секцию, ориентируясь на пример из библиотеки.

Код секции Declare из примера.
/*
HMC5883L_Example.pde - Example sketch for integration with an HMC5883L triple axis magnetomerwe.
Copyright (C) 2011 Love Electronics (loveelectronics.co.uk)

This program is free software: you can redistribute it and/or modify
it under the terms of the version 3 GNU General Public License as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see .

*/

// Reference the I2C Library
#include 
// Reference the HMC5883L Compass Library
#include 

// Store our compass as a variable.
HMC5883L compass;
// Record any errors that may occur in the compass.
int error = 0;

// Out setup routine, here we will configure the microcontroller and compass.



При переносе в блок убраны лишние комментарии, хотя ограничений по их применению никаких отличных от ограничений самого языка С нет. Так же добавлены некоторые переменные, которые пригодятся позже в коде. В оригинальном примере они объявлены в секции Loop. Здесь они перенесены в секцию Declare.

c51879eec7b04bb0a0e3ba7c57c5646b.PNG

На сегодняшний день в разделе Declare можно использовать объявления переменных любых типов, объявление массивов и структур. Корректно обрабатывается директива #define.
Нельзя использовать объявление такого вида:

typedef struct{
...
...}
test;

Его необходимо переделать в такой вид:

struct test{
...

...
};

Нельзя использовать тип extern. Это оказалась достаточно большой проблемой, и я надеюсь в ближайшее время её решить.

Теперь перейдём к секции «SetupSection». Здесь код переносится из примера практически один к одному.

Код из примера
void setup()
{
  // Initialize the serial port.
  Serial.begin(9600);

  Serial.println("Starting the I2C interface.");
  Wire.begin(); // Start the I2C interface.

  Serial.println("Constructing new HMC5883L");
  compass = HMC5883L(); // Construct a new HMC5883 compass.
    
  Serial.println("Setting scale to +/- 1.3 Ga");
  error = compass.SetScale(1.3); // Set the scale of the compass.
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.GetErrorText(error));
  
  Serial.println("Setting measurement mode to continous.");
  error = compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous
  if(error != 0) // If there is an error, print it out.
    Serial.println(compass.GetErrorText(error));
}



Из примера убраны заголовок секции и ограничивающие секцию скобки (при компиляции они вставляются программой). Так же убран весь диагностический вывод в компорт. Вот что получилось.

e46ece2c15084cca89e04e3ab50f024d.PNG

Обратите внимание, что вместо параметра точности вставлено имя параметра «Точность». При компиляции блока вместо этого имени будет вставлено значение, которое ввёл пользователь.

Теперь перейдём к секции Loop.

Оригинальный код из примера библиотеки
void loop()
{
  // Retrive the raw values from the compass (not scaled).
  MagnetometerRaw raw = compass.ReadRawAxis();
  // Retrived the scaled values from the compass (scaled to the configured scale).
  MagnetometerScaled scaled = compass.ReadScaledAxis();
  
  // Values are accessed like so:
  int MilliGauss_OnThe_XAxis = scaled.XAxis;// (or YAxis, or ZAxis)

  // Calculate heading when the magnetometer is level, then correct for signs of axis.
  float heading = atan2(scaled.YAxis, scaled.XAxis);
  
  // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location.
  // Find yours here: http://www.magnetic-declination.com/
  // Mine is: 2” 37' W, which is 2.617 Degrees, or (which we need) 0.0456752665 radians, I will use 0.0457
  // If you cannot find your Declination, comment out these two lines, your compass will be slightly off.
  float declinationAngle = 0.0457;
  heading += declinationAngle;
  
  // Correct for when signs are reversed.
  if(heading < 0)
    heading += 2*PI;
    
  // Check for wrap due to addition of declination.
  if(heading > 2*PI)
    heading -= 2*PI;
   
  // Convert radians to degrees for readability.
  float headingDegrees = heading * 180/M_PI; 

  // Output the data via the serial port.
  Output(raw, scaled, heading, headingDegrees);

  // Normally we would delay the application by 66ms to allow the loop
  // to run at 15Hz (default bandwidth for the HMC5883L).
  // However since we have a long serial out (104ms at 9600) we will let
  // it run at its natural speed.
  // delay(66);
}



Так же как в секции Setup убираем заголовок секции и ограничивающие скобки. Они буду вставлены в код автоматически программой. Так же убираем объявление переменных в секции Loop. В этой секции очень не желательно объявлять временные переменные, поскольку если блок будет использоваться в проекте несколько раз, то произойдёт ошибка повторного декларирования переменной.
В результате получается такой код:

83a58c544c9f4772b6619838a45fa4ad.PNG

В конце кода мы вызываем функцию Output (raw, scaled, heading, headingDegrees). В принципе можно было обойтись без неё, но я решил оставить её для примера создания функции. Поэтому переходим на закладку «FunctionSection»
Для создания новой функции нажимаем кнопку «Добавить функцию».

86cd3c69952440649ccf62515ec0ef40.PNG

Откроется окно ввода заголовка функции, куда вводим её определение.

c25121c6c0064e39a80b5d6a9d0a51fa.PNG

После создания заголовка пишем код функции.

Оригинальный код функции из примера
void Output(MagnetometerRaw raw, MagnetometerScaled scaled, float heading, float headingDegrees)
{
   Serial.print("Raw:\t");
   Serial.print(raw.XAxis);
   Serial.print("   ");   
   Serial.print(raw.YAxis);
   Serial.print("   ");   
   Serial.print(raw.ZAxis);
   Serial.print("   \tScaled:\t");
   
   Serial.print(scaled.XAxis);
   Serial.print("   ");   
   Serial.print(scaled.YAxis);
   Serial.print("   ");   
   Serial.print(scaled.ZAxis);

   Serial.print("   \tHeading:\t");
   Serial.print(heading);
   Serial.print(" Radians   \t");
   Serial.print(headingDegrees);
   Serial.println(" Degrees   \t");
}



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

f6f939aac28145948f7f07692f9c5bcb.PNG

Работа над блоком закончена.

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

5a2825c9f73b4db9b0e4675aa7cda823.png

Завершаем работу нажатием кнопки «Выход».

58bfd6f03cf8492cb2c1e8ac857c70d2.PNG

После сохранения блока его можно использовать в проекте как обычный блок.

Для самых терпеливых которые дочитали до конца — видео версия данного урока.

© Geektimes