📜 ⬆️ ⬇️

We program microcontrollers in QtCreator



For some reason, there is little documentation on qbs on the Internet, and I decided to correct this situation a little. I will try to describe in the form of a narration what needs to be done so that in QtCreator you can compile (and not only) anything for anything.

Somehow I got it so that the projects in which you need to do something just for one platform have practically disappeared. Usually it is necessary to write the 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 native development environment.

But believe me, it pretty quickly zadalbyvaet. Under Windows - MSVC, under ARM - CooCox or Keil (I offer my condolences to work under IAR), under MSP - CCS, under android - eclipse, under ios - Xcode, under peaks - MPLAB. And it would be okay, it would be possible to work with all this, but after all FIG: everywhere there are problems, subtleties and unwritten rules. All this is superimposed on the total stagnation of the so popular eclipse multiplied by the curvature additions from manufacturers.
')
Some time ago I started to reduce all my development for mobile and desktop applications under one platform. After quite a long time for Internet users, my choice was Qt. There is everything you need, if necessary, you can pick up the native code. In general, the task is closed.

But with the microcontrollers, the situation did not want to emerge categorically. Mainly due to the fact that everywhere their Wishlist and Wishlist. Well, okay, I already complained about that. I would have suffered for a long time until I suddenly came across a short description of qbs.

Those interested can rummage around on an Internet themselves, but if briefly, then this is a substitute for make and cmake, using a normal (there should be a smail) programming language. And QtCreator itself is going with her help, which means she has already got out of her pants ...

Yes, the documentation for it as usual, the cat wept, but the source has not yet been canceled, so I quickly realized that this was practically what I was looking for. Judge for yourself: you sit in the same development environment (very pleasant and fast by itself) and calmly write and edit files for several platforms. And you, as a code writer, do not care for the presence of any problems with the "native" environments.

Enough effusions, time to try. Let's create a very simple project in which we will have a desktop and microcontroller component.

Open the QtCreator, choose to create a Non-Qt Project (so long as you don’t bother much) and then choose where C and Qbs are. Pay attention to the nice 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.

Open qbs and do not understand anything. Therefore, we erase everything, arm ourselves with the Internet and begin to write. Javascript and all that.

import qbs 

So, here it seems clear. We import all qbs necessary for work.

 Project { name: "simple" } 

Save and watch the disappearance of main.c from the left panel. When you try to start a project, QtCreator will ask: why start something up? In principle, so far everything is logical.

What does the qbs terminology project consist of? Of the products. And there may be several of them, but for the time being I will make 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 see some tutorials and add dependency on cpp and indicate that this application is actually.

 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 the disk and the required output will appear in the Application Output panel



Yeah, so we're on the right track. It remains to figure out what those two magic lines are doing.

 Depends {name: "cpp"} 

I read the documentation and understand that with this command I establish the dependence of the project on some module named cpp. It became clearer? I do not.

With a simple search I find something similar in / usr / share / qtcreator / qbs / share / qbs / modules / ((If you have another operating system, then most likely the similar lies somewhere near QtCreator). In short, there is a bunch of javascript that, depending on the platform, the compiler selects for this platform. It makes no sense to repeat completely the same to me, so I leave it as it is.

 type: "application" 

From the documentation: The file tags matching the product's target artifacts . Artifact ... An Rif or Transformer . Or err ... Rule? Creates transformers for input tags. Reminds situation about Sepulcary ... Transformer? Creates files, typically from other files

I crawl on the available and I understand that approximately this is a certain set of rules that tells the build system how to build the compiled. Well, roughly speaking, you need to get an application, a library or something else at the exit. Again, though it became a little clearer, but not by much. Again, while we 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 we try to do something, we immediately get a message that there is no blink.c file at all. Well, ok, let's add a 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 msp430 microcontroller family.

 #include <msp430.h> int main(void) { WDTCTL = WDTPW + WDTHOLD; P1DIR |= 0x01; while (1) { P1OUT ^= 0x01; __delay_cycles(1000000); // 1 second @ 1MHz } return 0; } 

Being compiled and flooded, it will begin to pull the P1.0 foot at an interval of one second. And since most of the demo and developer boards have a LED on this leg, it will flash.

Now QtCreator does not swear, but nothing is poured into the microcontroller either. Strange, yes?

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 in general.

Now we will use fragments of those sacred letters that we met earlier.

For a start, I prefer in microcontroller projects to paint each functional in my file. Write each file by hand? Laziness. We peep 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 / *. C mask. For further work with them, we tag them with the letter C.

What to do with them? In qbs there are two things for this case - Rule and Transformer. In fact, they are close, but slightly different. I will try to describe a difference on fingers.

Rule can trigger on every file that falls under something. It can work once for each file (for example, to call the compiler), or maybe once for everything (linker).

Transformer is designed to trigger only on one file, with a predefined name. For example, a flash driver or some tricky script.

Ok, we add a rule that should work on all our files marked as “c”.

 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, into which javascript is thrust, which performs the necessary. In this case, it should show file passing in the Compile Output window, and output Nothing to do somewhere. Well, according to the documentation like that.

We start recompilation of everything and we look. I don’t know about you, but I don’t see anything. Why? Because qbs is painfully intelligent, and the documentation for it suffers from lacunae.

The rule does not work, because qbs believes that it does not perform any actions in the system and nothing depends on it. In principle, this corresponds to reality, but it prevents the verification.

Ok, those artifacts are responsible for this. They are the results of a Rule or Transformer activity. This is best explained with a compilation example. When we compile a .c file, then we will get an objectfile on the output. We need it for further linking, but on the other hand, we can delete it, because then we can easily generate it anew.

Again we copy the example from the documentation and slightly modernize it.

 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 activity there will be artifacts in the .obj directory (well, I added the output of which 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 necessary for us. 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 really compiles and immediately in bydlokoderkski, that is, stupidly scoring everything you need in 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; } 

Recompile everything from scratch and look in the .obj directory

 $ ls -R1 .: f27fede2220bcd32 ./f27fede2220bcd32: blink.co 

Hooray! File appeared. Now, for verification, I am doing another file with the tricky name hz.s. If I'm right, then after recompilation, another object file will appear next to it.

Appeared in the output

 compiling blink.c compiling hz.c 

and in the catalog

 ./f27fede2220bcd32: blink.co hz.co 

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? First, the multiplex flag was added, which says that this rule processes all files of this type at once in a crowd. And secondly, input disappeared in the input parameters. Appeared inputs, which is an array of files of this type. Well, I used the product name 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 file simple.elf in the build directory.

 $ file simple.elf simple.elf: ELF 32-bit LSB executable, TI msp430, version 1, statically linked, not stripped 

What we need. It can already be poured into the board and enjoy the flashing LED.

The initial goal has been achieved: we are doing everything in one development environment: both editing and compiling.

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 Moving "hard-coded" variables to a more convenient place I will leave on your conscience, for this is already a javascript tutor.

Source: https://habr.com/ru/post/258467/


All Articles