We program microcontrollers in QtCreator
- Tutorial
For some reason, there is little documentation about qbs on the Internet, and I decided to fix this situation a bit. I’ll try to describe in the form of a narrative what needs to be done so that QtCreator can compile (and not only) anything for anything.
Somehow it turned out so that projects in which you need to do something only for one platform have practically disappeared. Usually you need to write firmware for the microcontroller and the control program for a smartphone or desktop. You can do everything the old fashioned way: write code for each device in its development environment.
But believe me, this is pretty fast shutting up. Under Windows - MSVC, under ARM - CooCox or Keil (I offer my condolences to IAR), under MSP - CCS, under android - eclipse, under ios - Xcode, under peaks - MPLAB. And it’s okay, you could work with all this, but figs: everywhere there are troubles, subtleties and unwritten rules. All this is superimposed on the general retardation of so popular eclipse multiplied by krivoruky additions from manufacturers.
Some time ago, I began to reduce all my developments for mobile and desktop applications under one platform. After a pretty long chat on the internet, my choice was Qt. There is everything you need, if necessary, you can pick up the native code. In general, the task was closed.
But with microcontrollers, the situation did not want to develop categorically. Mainly due to the fact that everywhere there are their Wishlist and Wishlist. Well, I already complained about it. I would have been tormented for a long time until I suddenly came across a short description of qbs.
Those who wish can rummage through the Internet themselves, but in short, this is a substitute for all make and cmake, using a normal programming language. And QtCreator himself is going to use it, which means she has already climbed out of her panties ...
Yes, the documentation on it as usual, the cat cried, but no one has canceled the source yet, so pretty quickly I realized that it was practically what I was looking for. Judge for yourself: you are sitting in the same development environment (in itself very pleasant and fast) and quietly write and edit files for several platforms. And you, as a codewriter, do not care for the presence of all kinds of troubles there with "native" environments.
Enough of outpouring, time to try. Let's create a very simple project in which we will have a desktop and microcontroller component.
Open QtCreator, choose to create a Non-Qt Project (so as not to bother much yet), and then select where C and Qbs are present. Pay attention to the pleasant looking words Platform independent
As a result, we get one main.c and qbs. You can already click "build" and get the output of Hello World.
We open qbs and do not understand anything. Therefore, we erase everything, arm ourselves with the Internet and start writing. Javascript and all that.
import qbs
So, it seems to be clear here. We import everything necessary for qbs to work.
Project {
name: "simple"
}
We save and observe the disappearance of main.c from the left panel. When you try to start a project, QtCreator will ask: why start something? In principle, everything is logical so far.
What does the project consist of in qbs terminology? From the products. And there may be several, but for now I will do one.
Project {
name: "simple"
Product {
name: "desktop"
}
}
Now for our "desktop" indicate the source.
Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
}
}
When you try to compile nothing will change. We’ll take a look at some tutorials and add a dependency on cpp and point out that this is actually an application.
Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
}
And now, when trying to build an application, QtCreator will rustle a little with a disk and the desired
Aha will appear in the Application Output panel , so we are on the right track. It remains to figure out what the very two magic lines do.
Depends {name: "cpp"}
I read the documentation and understand that with this command I set the project to depend on some module named cpp. It became clearer? I don’t.
By a simple search, I find something similar in / usr / share / qtcreator / qbs / share / qbs / modules / (If you have a different OS, then most likely the same lies somewhere near QtCreator). In short, there is a bunch of javascript, which, depending on the platform, selects the compiler for this platform. It does not make sense to repeat completely like that, so I leave it as it is.
type: "application"
From the documentation: The file tags matching the product's target artifacts . Artifact ... An Artifact represents a single file produced by a Rule or Transformer . ... ep ... Rule? Creates transformers for input tags. Reminds a situation about sepulkariy ... Transformer? Creates files, typically from other files I
crawl through the accessible and understand that this is approximately a certain set of rules that tells the build system how to compile the compiled one. Well, roughly speaking, at the output you need to get an application, a library or something else in general. Again, although it has become a little clearer, but not by much. Again, let’s take for granted.
But back to our project. Let's add another product, just for the microcontroller
Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
Product {
name: "micro"
files: "blink.c"
}
}
When you try to do something, we will immediately get a message that there is actually no blink.c file. Well, ok, add the blink.c file to the project. As the name implies, this is the same HelloWorld, only for microcontrollers. I took from the examples for the microcontroller family msp430.
#include<msp430.h>intmain(void){
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= 0x01;
while (1) {
P1OUT ^= 0x01;
__delay_cycles(1000000); // 1 second @ 1MHz
}
return0;
}
Once compiled and flooded, it will start tugging at P1.0 with an interval of one second. And since most of the demo and development boards have an LED on this leg, it will flash.
Now QtCreator does not swear, but nothing is poured into the microcontroller either. Strange, huh?
It makes no sense to add Depends {name: "cpp"}, because the native gcc installed in the system is not aware of the existence of such a platform, and it will be useful in the future, for example, for peak controllers, where everything is different.
Now we will use fragments of those sacred letters that we met before.
To begin with, I prefer to paint each functional in my file in microcontroller projects. Record each file with your hands? Laziness. We look at the solution and rewrite the block
Product {
name: "micro"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
}
Here we create a group of files that we call “msp430 sources” and stupidly include in it all the files that fit the src / * mask. C. For further work with them we tag them with the letter S.
What to do with them? Qbs has two things in this case - Rule and Transformer. In fact, they are close, but slightly different. I'll try to describe the difference on my fingers.
Rule can fire on every file that falls under something. It can be triggered once for each file (for example, to call the compiler), and maybe once for everything (linker).
Transformer is designed to operate on only one file with a predefined name. For example, a flasher or some tricky script.
Ok, add a rule that should work on all our files marked as “s”.
Product {
name: "micro"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "file passing"
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}
}
In principle, everything is clear from the syntax. There are inputs, there is prepare, in which a javascript is thrust, which performs the necessary. In this case, it should show file passing in the Compile Output window, and display Nothing to do somewhere. Well, the documentation seems to be like this.
We start the recompilation of everything and look. I don’t know about you, but I don’t see anything. Why? Because qbs is painfully smart, and the documentation for it suffers from gaps.
The rule does not work, because qbs believes that it does not perform any actions in the system and that nothing depends on it. In principle, this corresponds to reality, but it makes it difficult to conduct verification.
Ok, those same artifacts are responsible for this. These are the results of the Rule or Transformer. This is best explained by compilation. When we compile the .s file, the output will be an object file. We need it for further linking, but on the other hand, we can delete it, since then we can calmly regenerate it again.
Again, copy the example from the documentation and slightly upgrade.
Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "Compiling "+ input.fileName
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}
Now we say that after our work there will be artifacts in the .obj directory (well, I added the output of what file we are working on now). We start. Again, nothing in return. Why? The answer is the same - nobody needs files with the 'obj' tag.
Well, for verification we will make it so that we need them. And in general, our application is one continuous obj.
Product {
name: "micro"
type: "obj"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var cmd = new JavaScriptCommand();
cmd.description = "Compiling "+ input.fileName
cmd.silent = false;
cmd.highlight = "compiler";
cmd.sourceCode = function() {
print("Nothing to do");
};
return cmd;
}
}
}
We try, and good luck! The coveted “Compiling blink.c” appeared in the window. Now let's add that it would really compile and immediately in a quick-by-coder manner, that is, stupidly hammering everything you need into one pile.
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
args.push("-g")
args.push("-Os")
args.push("-Wall")
args.push("-Wunused")
args.push('-c');
args.push(input.filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"var cmd = new Command(compilerPath, args);
cmd.description = 'compiling ' + input.fileName;
cmd.highlight = 'compiler';
return cmd;
}
We recompile everything from scratch and look in the .obj directory
$ ls -R1
.:
f27fede2220bcd32
./f27fede2220bcd32:
blink.c.o
Hooray! The file has appeared. Now, for verification, I am making another file with the tricky name hz.s. If I am right, then after recompilation, another object file will appear next to it.
The output appeared
compiling blink.c
compiling hz.c
and in the catalog
./f27fede2220bcd32:
blink.c.o
hz.c.o
Everything seems ok. Now you need to link this whole thing. So again, the rule, only now for linking.
Rule {
multiplex: true
inputs: ['obj']
Artifact {
fileTags: ['elf']
filePath: project.name + '.elf'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
for (i in inputs["obj"])
args.push(inputs["obj"][i].filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"var cmd = new Command(compilerPath, args);
cmd.description = 'linking ' + project.name;
cmd.highlight = 'linker';
return cmd;
}
}
Where are the differences? Firstly, the multiplex flag was added, which indicates that this rule processes all files of a given type at once in bulk. And secondly, input disappeared in the input parameters. Inputs appeared, which is an array of files of this type. Well, I used the name of the product to take the name for the final firmware.
We put the type of application elf and try to build. After some time, we will find the simple.elf file in the assembly directory
$ file simple.elf
simple.elf: ELF 32-bit LSB executable, TI msp430, version 1, statically linked, not stripped
That is what we need. You can already fill it in the board and enjoy the blinking LED.
The initial goal has been achieved: we are doing everything in one development environment: both editing and compilation.
Just in case, the final qbs
import qbs
Project {
name: "simple"
Product {
name: "desktop"
files: "main.c"
Depends {name: "cpp"}
type: "application"
}
Product {
name: "micro"
type: "elf"
Group {
name: "msp430 sources"
files: 'src/*.c'
fileTags: ['c']
}
Rule {
inputs: ["c"]
Artifact {
fileTags: ['obj']
filePath: '.obj/' + qbs.getHash(input.baseDir) + '/' + input.fileName + '.o'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
args.push("-g")
args.push("-Os")
args.push("-Wall")
args.push("-Wunused")
args.push('-c');
args.push(input.filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"var cmd = new Command(compilerPath, args);
cmd.description = 'compiling ' + input.fileName;
cmd.highlight = 'compiler';
return cmd;
}
}
Rule {
multiplex: true
inputs: ['obj']
Artifact {
fileTags: ['elf']
filePath: project.name + '.elf'
}
prepare: {
var args = [];
args.push("-mmcu=cc430f5137")
for (i in inputs["obj"])
args.push(inputs["obj"][i].filePath);
args.push('-o');
args.push(output.filePath);
var compilerPath = "/usr/bin/msp430-elf-gcc"var cmd = new Command(compilerPath, args);
cmd.description = 'linking ' + project.name;
cmd.highlight = 'linker';
return cmd;
}
}
}
}
PS I will leave the removal of "hardcoded" variables to a more convenient place on your conscience, for this is already a javascript tutorial.