FLProg - Creating custom blocks in C language (Lesson out of turn)
- Tutorial
The FLProg project has recently gained quite a lot of popularity, and I no longer have enough time to create blocks for the peripherals that users need. At the same time, among the users of the site there were a sufficient number of people who are well versed in the C language and could help me in developing the program. I decided to give them the appropriate tool. Thus, in version 1.10.3, it became possible to create custom blocks with integrated C code. This led to rather unexpected results. This tool was interested not only in programming-savvy users, but also those who had not written lines of code before. They started by writing simple blocks at first (for example, getting a logarithm - I didn’t have such a block among the standard ones), ending with serious blocks using libraries. Therefore, I want to break a little the conceived sequence of lessons on working with the program, and out of turn I will tell you how to create such blocks.
In the lesson, a block for the digital compass HMC5883L will be created. This article was taken as the basis of the block . The library is taken from here .
So, run the program and create a new project. In the library of elements go to the tab "Custom" and select the tree branch of the library of custom blocks where the new block will be located. Then we press the “Create Block” button.
A window opens with a choice of programming language for the block. FBD, LAD, and Code languages are available. The LAD and FBD languages will be discussed in other lessons, and now select “Code”.
The user block editor window opens. In the upper part are the tabs for the parameters (1), in the lower tabs for the code sections (2).
We fill in the main parameters.
The inputs and outputs of the block are created in the same way. Our block will not have any inputs, so we skip this tab and go to the tab “Block outputs”. The block will have three outputs - the direction along the X, Y, and Z axis. To create an output, click the "Add output" button.
The login creation window will open. It is very similar to the window for creating a variable in the main program, so I will not describe it in detail. The values for the output of the block are submitted in the Float format - therefore, we select the type of inputs the same. The name of the output is subject to restrictions that apply to the name of variables in C. since it will appear in the code. There are no restrictions on writing comments.
In the same way we create the rest of the outputs.
Now we will set the parameter that the user will set when using the block. This will be the sensitivity of the sensor. In accordance with the description, it is selected from the series: 0.88, 1.3, 1.9, 2.5, 4.0, 4.7, 5.6, 8.1
Go to the "User Settings" tab and click the "Add Parameter" button.
A window for creating a user parameter will open. Since we have a parameter of type Float, we select this type. There are no restrictions on the parameter name, since in the resulting code this name will be replaced by the parameter value. There are also no restrictions on the text of the comment.
When creating a parameter, you can set the default value. In this case, when using the block, it will not be necessary to set the parameter value. It is also possible to set the boundaries of the parameter values, and if the entered value goes beyond these boundaries, the block will be considered incorrect.
Now let's take care of the library. Let's go to the “Libraries” tab. The libraries used in the work of the block can be loaded directly into the block. To do this, click the "Download Library" button.
A window for selecting a folder with a library will open.
After selecting the library folder, it will be loaded into the block, and will be displayed in the list of loaded libraries. Several libraries can be loaded if necessary for the operation of the block.
When using a block in a project, before compiling the circuit, the program will check for the presence of libraries loaded in the block in the library directory, and if there are none, it will unload the missing ones.
On the “Description” tab, it is advisable to write how to work with the unit, what it is intended for, etc. This description will help other users to use it.
Now let's move to the code zone.
The first tab is the “DeclareSection” section. On this tab, you need to register the connection of the necessary libraries, the declaration of variables, arrays and structures. We will fill this section, guided by an example from the library.
The code for the Declare section from the example.
/*
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 <http://www.gnu.org/licenses/>.
*/// Reference the I2C Library#include<Wire.h>// Reference the HMC5883L Compass Library#include<HMC5883L.h>// 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.
When transferring to the block, unnecessary comments were removed, although there are no restrictions on their use other than those of the C language itself. Also added some variables that will come in handy later in the code. In the original example, they are declared in the Loop section. Here they are moved to the Declare section.
Today, in the Declare section you can use variable declarations of any type, declaration of arrays and structures. The #define directive is correctly processed .
You cannot use this type of declaration:
typedefstruct{
...
...}
test;
It must be redone in this form:
structtest{
...
...
};
You cannot use the extern type . This turned out to be a rather big problem, and I hope to solve it in the near future.
Now let's move on to the “SetupSection” section. Here the code is transferred from the example almost one to one.
Code from Example
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));
}
The section header and parentheses restricting the section have been removed from the example (they are inserted by the program during compilation). Also removed all diagnostic output to the comport. Here's what happened.
Note that instead of the precision parameter, the name of the parameter Precision is inserted. When compiling a block, the value entered by the user will be inserted instead of this name.
Now let's move on to the Loop section.
Original code from the sample library
voidloop(){
// 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);
}
As in the Setup section, remove the section header and bounding brackets. They will be inserted into the code automatically by the program. We also remove the declaration of variables in the Loop section. In this section, it is very undesirable to declare temporary variables, because if the block is used several times in the project, an error will occur in re-declaring the variable.
The result is this code:
At the end of the code, we call the Output function (raw, scaled, heading, headingDegrees) . In principle, you could do without it, but I decided to leave it as an example of creating a function. Therefore, go to the tab "FunctionSection".
To create a new function, click the button "Add function".
A window for entering the function header will open, where we enter its definition.
After creating the header, write the function code.
The original function code from the example
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");
}
We remove everything related to the output in the comport, and assign the values to the outputs of the block. Well, as usual, remove the header and bounding brackets. Here is the result.
Work on the block is completed.
A little bauble when working in the block editor. In any field of the code, it is possible to use the context menu to insert the name of the input, parameter output or function call template.
We finish the work by pressing the "Exit" button.
After saving the block, it can be used in the project as a regular block.
For the most patient who read to the end - a video version of this lesson.