Quick start with ARM Mbed: development on modern microcontrollers for beginners
Hi, Habr.
The traditional unique advantage of the Arduino platform was called (and even now it is sometimes called, although this is already wrong - and we will talk why) lowering the threshold for microcontroller development to the level of basic knowledge of C / C ++ and electronics on the scale of “connect an LED in the correct polarity”.
Ask about any active supporter of Arduino - and they will quickly explain to you that you can, of course, write under STM32 or nRF52, but there is no real benefit, but sleepless nights over hundreds of pages of datasheets and endless sheets of functions with long incomprehensible names are waiting for you.
The merits of Arduino in lowering the threshold of entry is really difficult to overestimate - this platform came to light in the middle of the zero years, and after 2010 it gained serious popularity among fans. There were no special alternatives at that time - the processors on the Cortex-M cores only appeared, compared to AVR, they were quite complicated even for professional developers, and the debugging boards for most vendors cost hundreds of dollars and more (and in general, the industry has a price tag for debugging on a $ 5 controller in $ 500, I was not surprised by anyone).
However, the Arduino’s big problem is that its development over the past 10+ years most resembles some AvtoVAZ models:
Since I plan a long introduction further, now, so that you can imagine what the practical part will be, I will give the full text of the program, including the initialization of the STM32 processor and the flashing of the LED. The program is written for ARM Mbed OS:
Does this look like a high input threshold? On functions with obscure names? Sleepless nights over datasheets? Not? Okay, let's not get ahead of ourselves.
The fact is that in the world of embedded development since 2010 ... a lot has happened. AVR, as well as 8-bit controllers in general, almost died - for 2017 the total share of the latter in development was 12% (data from the Aspencore developer survey), and it was divided into at least three families: AVR, PIC and STM8. In fact, their main use now is the replacement of fine logic, peripheral controllers with a minimum amount of brains, etc.
For the ATMega series, in these 12% of the space there is very little - they are redundant as ancillary and cannot compete with 32-bit Cortex-M in price / performance ratio (STM32F030 with 16-64 KB of flash and 4-8 KB of RAM is small in Russia wholesale 30-50 rubles). In fact, atmegs in some projects remained only for historical reasons.
64-bit processors are obviously older Cortex-A and intel in heavy projects. A
lot has happened in development environments. Remember, I wrote above that at the start Cortex-M frightened many developers because of its complexity? In addition to the spreading hardware initialization of hardware itself (the correct initialization of clocking alone, in order for the processor to start up at all - this is half a page of code), the main problem was the low level of compatibility of different models with each other. If one AVR changed to another, sometimes without editing the code at all, then to change the STM32F1 even to the STM32L1 you will have to fix it pretty well, and only on some Infineon or NXP ...
Within the same vendor, the problem was solved by releasing a set of libraries that abstract software from hardware - for example, STMicro currently has already made three of them: SPL, HAL and LL. However, the popularity of such libraries was not always as high as the vendors would like, and besides, they were obviously tied to a specific vendor.
This problem began to be solved with the emergence of microcontroller operating systems.
With all the words about the need to save OS resources, they turned out to be so convenient in development that now most projects are being done with their use. However, this is quite expected - when working on a large project, even if you do not use a ready-made OS, you will eventually write for yourself a set of subsystems, in fact, this OS components. Just because they are needed. (no, from the experience of people writing articles on how to do multitasking using loop () loop, I know that they don’t need anyone - but it will be a discussion that someone and a bicycle with round rubberized wheels I don’t need it, when it’s possible to drive on wooden square ones).
From the point of view of an OS programmer, this is not only a large striped flies, but also a set of out-of-box services that make life easier for him:
In general, thanks to the OS, programming of microcontrollers is getting closer to writing software for large PCs - even the API in some places is very similar to the good old POSIX.
There are already quite a few operating systems, and they have different functional sets. Well, for example:
In many cases, the OS is not tied to any development environment, and the typical arm-none-eabi-gcc is usually used as the toolchain. So, RIOT was originally made on Makefiles, and therefore you can work with it even from Visual Studio, even from the command line. ARM Mbed has its own Python assembly system ( mbed-cli ), and can also be quickly and almost automatically configured in PlatformIO , as well as exported via mbed-cli to projects for Keil uVision or the same Makefiles.
The same applies to debuggers - a word not heard in the Arduino world. As a rule, any platform allows you to use good old gdb in conjunction with any JTAG / SWD debugger you like.
And what about the input threshold, with the need for long sleepless nights to read endless datasheets on the processor registers and teach the assembler? All those unpleasant things about which you are surely mentioned?
And nothing. He is long gone. All of the above buns, services and opportunities in terms of the learning curve are obtained almost free of charge .
For example, with RIOT OS, “high input threshold” from zero familiarity with microcontrollers to launching the first program looks like this:
The whole program, if you look in main.c, looks like this:
See this scary processor initialization? Sleepless nights spent on datasheets? Infinite length calls to SPL functions? Assembler, finally?
So I do not see.
In principle, it was a difficult path. On a simpler one, you don’t need to install anything locally - ARM Mbed provides the ability to compile everything directly online.
(not that I like weather stations, I just sit at home with temperature, and besides all sorts of highly specialized things for different b2b projects, I only have Nucleo-L152RE and several boards with BME280 and OPT3001 here at hand from the generic)
1) We are registering on mbed.com .
2) We press the button “Compiler” there and get into the online development environment. In the upper right corner there is a button for selecting a board with which we will work. Click it, in the pop-up window click “Add board” and choose the version of Nucleo that we have (I have this L152RE, you may have another, and maybe not Nucleo at all, it does not matter). Returning to the project window, we select our Nucleo in the same menu as the current board.
3) Click on the top left New → New Program. Mbed offers us some sort of mountain of samples at once, but at least for the Nucleo-L152 they will all bring with them the old OS version. We want a new, fresh, released three days ago, 5.9.5, so we will choose the simplest one - “Empty program”.
For further work, we will need two things - first, to connect the mbed source itself, and second, to create and fill the main.cpp file with something. To connect sources, you must not only specify #include, but also import the mbed-os library into the project itself.
For this:
Now click on the project name with the right button → “New file” → create the main.cpp file. Fill it right there in the most trivial way:
(LED1 is already defined in the description of the board - this is actually the only user-controlled LED on Nucleo)
4) Press Ctrl-D (or the “Compile” button), wait ten to fifteen seconds, download the resulting file. We connect the Nucleo to the USB port (a powerful ambush: there are mini-USB connectors on Nucleo, for which not everyone has a lanyard anymore), we discover a new 540 KB disk with the name “Node_L152RE” on the computer. Inside it is an MBED.HTM file, unambiguously hinting why this disk is needed.
In fact, of course, any BIN- or HEX file thrown at it is automatically flashed into the controller, this is not directly connected to Mbed, just such a programming interface.
We throw there the downloaded file. Nucleo blinks the LED of the programmer, and then starts to blink at the LED on the board - thereby LED1.
5) Now we need to add a BME280 sensor, because we have a weather station or what? Fifteen seconds of the search lead us to the library with its support , on the page of which you need to click on the Import Library (libraries, of course, are different, and you should always look inside - but for now let's drop these details). When importing, we do not forget to note that this is the Library, and we need to import it into our current project (“Target path”).
Here is an example of using the library , where you can briefly see how to use it.
6) Add a reference to the library in your code, at the same time removing from it every hello world:
As legs, I, no matter what I did, directly pointed out the pins that I would be comfortable with in my Nucleo to poke an I2C sensor. Generally, because they are directly signed on the board as SDA and SCL (although in general the STM32L1 has several I2C ports, and each can be hung up on several leg variants), then most likely the I2C_SDA and I2C_SCL defains would point to them. But now it is hard for me to think about such complex matters.
7) Connect the sensor. Four wires - +3.3 V, ground, SDA, SCL. Since both the STM32 and the 3.3-volt sensor do not need to think about the coordination of levels.
8) Click “Compile” again, download the new BIN-file and throw it into Nucleo.
eight) ??????
(in fact, at this point we open our favorite terminalku - I usually use Termite, and we cling it to the virtual port corresponding to Nucleo; In a modern computer, this is likely to be the only available COM port. The speed is set at 9600 bps)
9) PROFIT !!!
Works. But here we, as good programmers, are not satisfied with the idea “if the Hindu code works, then it’s not so bad,” and we see what we want to improve.
Globally - probably two things. First, the code spinning in while (1) with a 1-second delay is a very bad concept, as the delay is not there 1 second, but 1 second plus the execution time of the code, and we will want something with other periods here. still run.
Secondly, 9600 bps is somehow sad, 2018 is in the yard, let's at least 115200.
With the port, everything is simple: you need to create your own copy of the Serial class, point it to the desired speed, and then print through it.
Serial pc (SERIAL_TX, SERIAL_RX);
pc.baud (115200);
With getting rid of while (1) also, however, there are no big problems. In Mbed there is such a thing as an event queue ( EventQueue ), events in which can be triggered after a specified period of time (call method) or permanently (call_every method), and the result of their call will be the execution of a particular function.
We make the code:
We collect, we start. It works exactly the same way as before (which is already not bad), but now we have a solution that can be scaled almost unlimitedly and without any problems. We can calmly throw other events with other periods into the eventQueue (the main thing is to make sure that we don’t live more than 50 events at one time), and until their execution time begins to overlap, we don’t have to worry about how they interact.
Why?
Because, unlike stuffing everything into one while (1) with carefully verified delays, they do not interact.
Ok, now let's add the OPT3001 here. Not that the measurement of illumination was a mandatory feature of the weather station, but since I have this sensor on the table ...
With the OPT3001, we have some problem - it is very good (and precision, and especially power consumption), but not very popular compared to the same TSL2561 sensor among home builders, so we can’t find a ready-made library for it directly in Mbed. I would say that it’s easy to write a driver for it (I wrote), but let's google into Google - and we’ll find https://github.com/ashok-rao/mbed-OPT3001 as the first link .
Again, click Import, select the subtle link “Click here to import from URL” in the title, insert the project URL on the github, do not forget to click on the “Library” item - and “Import” itself.
Not really . Astanavites
In fact, this driver is written on the knee so much that it is not worth using it at all. As the great writer once said, “having opened OPT3001.cpp, bloody tears flowed from my eyes,” so I put aside all important things for a short while and quickly sketched a more decent driver (that is, now I’ve twice been a OPT3001 driver writer).
Further along the path already known: Import → Click here to import from URL → github.com/olegart/mbed_opt3001 → Library → Target Path = our project → Import.
Add to the project the rest of the code:
Compile, save the file, throw it in the Nucleo, do not forget to switch the terminalka to 115200 ...
PROFIT !!!
All this we wrote, by and large, the left rear heel, without really thinking about anything - well, except perhaps throwing away a stupid and uncomfortable endless loop.
Among the things that we did not think about, there is such a thing as power consumption. Sometimes you have to think about it, however.
Especially for its measurement on Nucleo there is an IDD jumper, by removing which you can plug in with a multimeter and see what happens here (although generally speaking, a multimeter is a bad tool for measuring the power consumption of microcontrollers, with fast jumps of consumption it fixes mainly weather on Mars).
On my L152, the current turns out to be around 10 mA, walking a little back and forth depending on the LED1 glow, which we still pull out of habit (it consumes 2-3 mA). Somehow quite inhumane to the system, which is most of the time doing nothing, do not you? ..
In this case, we interfere with that same message queue, which we were happy recently. The fact is that it allows you to set timings with millisecond precision, and therefore works on a fast processor hardware timer, which is turned off when the latter goes to sleep - in other words, if the processor goes to sleep, our eventQueue.call_every will go to sleep, too, forever. On the STM32L151 there isn’t any special fast timer ticking in a dream — more precisely, on someprocessor models can be pulled out of normal RTC clocks (starting with L151CB-A and higher, register RTC_SSR), but in general - no.
In addition, the message queue has the dispatch_forever () method, which revolves at the end of our firmware and also does not allow to sleep - it is engaged in that, in fact, receives, processes and executes messages.
But we don’t need millisecond intervals here, do we still get data once a second? And after all, we need to process messages only when the processor wakes up, because if it didn’t wake up, where did the messages come from?
Therefore, we boldly take a thing called LowPowerTickerworking at RTC, and we are starting to rewrite the code on it - the benefit is to change only a few lines. Having started LowPowerTicker, we can say the system sleep () - and it will fall asleep in a deep sleep (or, more precisely, with what other modules allow) - there are nuances, for example, a module breaking in a dream can sleep during its work ).
But there is one problem: the ticker calls are performed in an interrupt, and in the interruption one cannot do any long things - we have very, very long sensor readings (for OPT3001, for example, 100 ms passes from request to response). With printf everything is even worse - it is also cumbersome, so the interrupt may stupidly not have enough stack. Therefore, we need to send a signal from the interrupt to the function that actually reads the sensors and prints the value so that it is executed in the usual context.
A crude method would be to set a variable in an interrupt, and in the main context, read the value of this variable in while (1), and if it suddenly cocked, call reading sensors. But we are not arduinschiki with you, so that in while (1) to push meaningful code?
Suddenly, this is the same EventQueue that we just dropped.
What did we do here? Yes, in general, nothing complicated: added Low Power Ticker, which once per second gives an interrupt. On interrupt, the controller wakes up, the interrupt handler calls eventQueue.call, which should actually pull the hard work out of the handler into the external function (the sleepy processor does not interfere with it here, since it has no delay during which the processor could fall asleep). implies immediate execution). By itself .call would not have done anything, but in the main body of the program, we again had while (1), in which there are only two lines - the dispatch method for the queue, which processes messages in it, and sleep (), which takes the processor to sleep until next interrupt.
Everything.
Fall asleep → interrupt → woke up → call → left the interruption → dispatch → took readings from the sensors → sleep () until the next click of the ticker.
PROFIT ???
PROFIT !!!
(in fact, 0.57 mA of consumption is a lot, although a couple of dozen times less than before. But in Mbed, most likely, an unpleasant nuance with STM32L1 processors is not taken into account: they have default legs in Digital In mode without braces, but it is necessary to turn on Analog In or the brace, otherwise Schmitt triggers at the input of these legs are pretty well guzzled due to spontaneous switching due to pickup. In addition, in Nucleo, the current can flow through the JTAG legs towards the programmer-debugger built into the board )
And we received all this in general without much effort. In fact, even before reaching the "real RTOS" with its processes, semaphores, messages and other goodies.
The only payment was perhaps that resource consumption - 55.1 KB of flash and 9.2 KB of RAM (you can see the Build details in the log with a message about the successful assembly of the project), but for modern controllers this is not that huge amounts Even a completely standard 2-dollar STM32F103CB has 128 KB of the first and 20 KB of the second. In principle, the minimal project on the microcontroller and OS I worked with lived on the STM32F030F4P6 with 16 KB of flash and 4 KB of RAM on RIOT OS with slightly clipped wings.
In addition, in this case, we did not save resources at all, while in a decent society, for using floats inside printf on a developer’s microcontroller, they simply silently take the door and shoot them in the corridor. They don’t say anything - everyone understands why . Even malloc is better, malloc can still be forgiven in some circumstances.
PS For the lazy - the code written here is immediately in the Mbed repository .
PPS Parts for assembly: Nucleo-L152RE (any other Nucleo / Discovery will work too, just check that it is on the board list - but everything seems to be there), BME280(attention: on Ali, these scarves are cheaper than the cost of the chip itself, not only because the Chinese are generous, but also because the cheaper BMP280, which is outwardly indistinguishable), is molded into them). Where do you get the OPT3001 closer to Ali - I don’t know, except for the native TI girl for three thousand rubles.
I don’t want to say that everything is cloudless in the world of microcontroller RTOSs, and every line of code oozes with grace - the same Mbed reached an acceptable state from my point of view somewhere in the last couple of years, they fixed it in RIOT only in release 2018.07 some of the old sores (and some have not been fixed), and so on.
However, the idea that only Arduino "provides the minimum entry threshold", and all other platforms are mandatory picking in strange libraries, hundreds of pages of datasheets and assembler, can be shared only by a person who has spent the last decade somewhere in places very remote from civilization (in the not so remote Internet, they say, and that has already been done).
Absolutely nothing difficult to start working with modern controllers in a modern development environment, no. Moreover, at the same time you get a lot of tasty, useful, and most importantly - well-implemented things, radically accelerating and simplifying development. No, I’m not talking about the “LED flashing” libraries, although they are also enough - I’m talking about timers, multitasking, messages, services and everything else that makes no sense in 2018 AD to write with your hands, because everything is already written before you and, most likely, much better than you ever write.
In fact, we literally half an hour from scratch have now done quite a decent project - not just working, but minimally normal from an architectural point of view. Scalable with little effort, working on interruptions, supporting energy saving. Not straining.
Yes, you can dodge and in a few days pile on a crooked rake-like resemblance of what users of modern platforms get out of the box in an elegant, ironed form. You can even write on this some meaningful project, which sooner or later at the cost of serious efforts spent will be able to get to work and not to fall out of the resources of a typical AVR.
But ...
Why ?!
Look at the calendar, 2018 in the yard! Don't you feel so sorry for yourself?
The traditional unique advantage of the Arduino platform was called (and even now it is sometimes called, although this is already wrong - and we will talk why) lowering the threshold for microcontroller development to the level of basic knowledge of C / C ++ and electronics on the scale of “connect an LED in the correct polarity”.
Ask about any active supporter of Arduino - and they will quickly explain to you that you can, of course, write under STM32 or nRF52, but there is no real benefit, but sleepless nights over hundreds of pages of datasheets and endless sheets of functions with long incomprehensible names are waiting for you.
The merits of Arduino in lowering the threshold of entry is really difficult to overestimate - this platform came to light in the middle of the zero years, and after 2010 it gained serious popularity among fans. There were no special alternatives at that time - the processors on the Cortex-M cores only appeared, compared to AVR, they were quite complicated even for professional developers, and the debugging boards for most vendors cost hundreds of dollars and more (and in general, the industry has a price tag for debugging on a $ 5 controller in $ 500, I was not surprised by anyone).
However, the Arduino’s big problem is that its development over the past 10+ years most resembles some AvtoVAZ models:
Since I plan a long introduction further, now, so that you can imagine what the practical part will be, I will give the full text of the program, including the initialization of the STM32 processor and the flashing of the LED. The program is written for ARM Mbed OS:
#include"mbed.h"DigitalOut myled(LED1);
intmain(){
while(1) {
myled = 1; // LED is ON
wait(0.2); // 200 ms
myled = 0; // LED is OFF
wait(1.0); // 1 sec
}
}
Does this look like a high input threshold? On functions with obscure names? Sleepless nights over datasheets? Not? Okay, let's not get ahead of ourselves.
The fact is that in the world of embedded development since 2010 ... a lot has happened. AVR, as well as 8-bit controllers in general, almost died - for 2017 the total share of the latter in development was 12% (data from the Aspencore developer survey), and it was divided into at least three families: AVR, PIC and STM8. In fact, their main use now is the replacement of fine logic, peripheral controllers with a minimum amount of brains, etc.
For the ATMega series, in these 12% of the space there is very little - they are redundant as ancillary and cannot compete with 32-bit Cortex-M in price / performance ratio (STM32F030 with 16-64 KB of flash and 4-8 KB of RAM is small in Russia wholesale 30-50 rubles). In fact, atmegs in some projects remained only for historical reasons.
64-bit processors are obviously older Cortex-A and intel in heavy projects. A
lot has happened in development environments. Remember, I wrote above that at the start Cortex-M frightened many developers because of its complexity? In addition to the spreading hardware initialization of hardware itself (the correct initialization of clocking alone, in order for the processor to start up at all - this is half a page of code), the main problem was the low level of compatibility of different models with each other. If one AVR changed to another, sometimes without editing the code at all, then to change the STM32F1 even to the STM32L1 you will have to fix it pretty well, and only on some Infineon or NXP ...
Within the same vendor, the problem was solved by releasing a set of libraries that abstract software from hardware - for example, STMicro currently has already made three of them: SPL, HAL and LL. However, the popularity of such libraries was not always as high as the vendors would like, and besides, they were obviously tied to a specific vendor.
This problem began to be solved with the emergence of microcontroller operating systems.
With all the words about the need to save OS resources, they turned out to be so convenient in development that now most projects are being done with their use. However, this is quite expected - when working on a large project, even if you do not use a ready-made OS, you will eventually write for yourself a set of subsystems, in fact, this OS components. Just because they are needed. (no, from the experience of people writing articles on how to do multitasking using loop () loop, I know that they don’t need anyone - but it will be a discussion that someone and a bicycle with round rubberized wheels I don’t need it, when it’s possible to drive on wooden square ones).
Microcontroller operating systems and what they give
From the point of view of an OS programmer, this is not only a large striped flies, but also a set of out-of-box services that make life easier for him:
- HAL - abstraction from iron. In the OS, the interaction with hardware and user code are very clearly and unambiguously divided. This allows, firstly, it is easy to transfer projects between different controllers (for example, we have a large, heavy firmware without any modifications going under STM32L072, STM32L151 and STM32L451 - just enough to indicate which board is needed now), secondly, when work on a project, several people share responsibilities between them in accordance with skills and qualifications (the logic of the application, for example, can be written by a person who has a very vague concept of working with registers in STM32, while the low-level part of the project will be developed by another man parallel, and they will not interfere with each other). In some operating systems, even access to external devices is abstracted - for example, in RIOT there is an API called SAUL, which can be used to access the sensors; in this case, the author of the top-level code will not care what kind of sensor is connected somewhere below.
- Multitasking - any serious project is a set of competing and cooperating tasks that are performed at different periods of time, periodically or on any events. Modern operating systems make it easy to allocate such tasks into separate threads that do not depend on other threads and have their own stacks, assign them a priority for execution and monitor their work.
- Timers - events are often tied to a specific time, and therefore necessarily need a clock. The controller's hardware timers for registering events in a multi-tasking OS are poorly suited due to their limited number, so the OS provides its own timers. These are subsystems that operate on the basis of one of the hardware timers, and allow you to program a virtually unlimited number of events with an accuracy equal to the discreteness of the used timer. Since the subsystem operates on the basis of hardware timer interrupts, the mutual influence of events is limited only by the intersection of interrupt handlers.
- IPC - interprocess message. Since different tasks work not in a vacuum, but communicate with each other, the OS provides the means of such communication, from simple semaphores and mutexes, which allow, for example, to slow down one thread while waiting for the other to release the necessary peripherals or receive the necessary data, to messages, in which data can be transferred and which themselves can be a trigger for switching the OS from the sending stream to the receiving flow (for example, exiting the interrupt handler: if you have a hard procedure that should start by interruption, then from interruption you simply send a message to the actual stream with this procedure, which is executed already in the normal context, without interfering with the operation of the system).
- Sets of standard libraries and functions - in addition to the API of working with the microcontroller itself, the OS can provide you with access to standard libraries, including third-party development. These can be simple, but demanded procedures such as formatting numbers between different representations, encryption procedures and calculating checksums, as well as large third-party libraries, for example, network stacks, file systems, etc., adapted to the API of this OS.
- Driver kits - many operating systems also provide out of the box driver kits for external sensors and systems relative to the controller.
In general, thanks to the OS, programming of microcontrollers is getting closer to writing software for large PCs - even the API in some places is very similar to the good old POSIX.
There are already quite a few operating systems, and they have different functional sets. Well, for example:
- FreeRTOS - strictly speaking, not the OS at all, but only the OS kernel. The body kit to it is at the discretion of the developer. The most popular at the moment microcontroller OS (but this does not mean that you need to use it)
- Contiki OS is an old universtet development that gained fame thanks to networking features, in particular, the 6LoWPAN stack. By today's standards, it is heavy, and many concepts have become outdated long ago (for example, there is no normal multitasking implementation in terms of modern concepts). Now it is being written under the name Contiki NG - various trash is thrown out of the project, but the overall concept does not change.
- RIOT OS is a young university design that claims to be a Contiki. It supports a bunch of processors of different architectures (including even older ATMega) and is booming. Lightweight and understandable. Written in C. Much in places is not enough, but easy to learn, support and refinement to fit your needs. Optimal, in my opinion, as a training for specialized students.
- ARM Mbed is a commercial (but open source, Apache 2.0) OS developed by ARM Holdings itself. It was a bit sad in version 2.0, but it was rapidly going up in the new 5.x branch. Written in C ++ and has a fairly clear and simple API. It supports a mountain of various boards, and by and large does it well. Some vendors have their own teams involved in supporting the debug boards of this vendor in Mbed.
- TI-RTOS is a proprietary development of Texas Instruments, works with just about everything TI ever released, puts it out of the box at once with support for just about everything, but the installation takes a couple of hours and reduces your disk by several gigabytes. For my taste, the API is overly heavy.
In many cases, the OS is not tied to any development environment, and the typical arm-none-eabi-gcc is usually used as the toolchain. So, RIOT was originally made on Makefiles, and therefore you can work with it even from Visual Studio, even from the command line. ARM Mbed has its own Python assembly system ( mbed-cli ), and can also be quickly and almost automatically configured in PlatformIO , as well as exported via mbed-cli to projects for Keil uVision or the same Makefiles.
The same applies to debuggers - a word not heard in the Arduino world. As a rule, any platform allows you to use good old gdb in conjunction with any JTAG / SWD debugger you like.
This scary entrance threshold
And what about the input threshold, with the need for long sleepless nights to read endless datasheets on the processor registers and teach the assembler? All those unpleasant things about which you are surely mentioned?
And nothing. He is long gone. All of the above buns, services and opportunities in terms of the learning curve are obtained almost free of charge .
For example, with RIOT OS, “high input threshold” from zero familiarity with microcontrollers to launching the first program looks like this:
- Download and install MinGW, arm-none-eabi-gcc and GNU make (for Windows 10 users, the first item is not needed, it’s enough to install a fresh Ubuntu from the Store)
- Download and unzip the latest RIOT release from GitHub.
- cd RIOT / examples / hello-world
- make BOARD = nucleo-l152re
- upload the resulting HEX file in any way on Nucleo (in newer versions of ST-Link, this can be done by simply throwing it on a virtual disk)
The whole program, if you look in main.c, looks like this:
#include<stdio.h>intmain(void){
puts("Hello World!");
printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD);
printf("This board features a(n) %s MCU.\n", RIOT_MCU);
return0;
}
See this scary processor initialization? Sleepless nights spent on datasheets? Infinite length calls to SPL functions? Assembler, finally?
So I do not see.
In principle, it was a difficult path. On a simpler one, you don’t need to install anything locally - ARM Mbed provides the ability to compile everything directly online.
Let's make a weather station
(not that I like weather stations, I just sit at home with temperature, and besides all sorts of highly specialized things for different b2b projects, I only have Nucleo-L152RE and several boards with BME280 and OPT3001 here at hand from the generic)
1) We are registering on mbed.com .
2) We press the button “Compiler” there and get into the online development environment. In the upper right corner there is a button for selecting a board with which we will work. Click it, in the pop-up window click “Add board” and choose the version of Nucleo that we have (I have this L152RE, you may have another, and maybe not Nucleo at all, it does not matter). Returning to the project window, we select our Nucleo in the same menu as the current board.
3) Click on the top left New → New Program. Mbed offers us some sort of mountain of samples at once, but at least for the Nucleo-L152 they will all bring with them the old OS version. We want a new, fresh, released three days ago, 5.9.5, so we will choose the simplest one - “Empty program”.
For further work, we will need two things - first, to connect the mbed source itself, and second, to create and fill the main.cpp file with something. To connect sources, you must not only specify #include, but also import the mbed-os library into the project itself.
For this:
- Click “Import”, in the opened section in the upper right, click the unobtrusive link “Click here to import from URL”, put this link to it (for all that terrible, this is just a link to the most recent release of Mbed, which we took from the release page )
- We poke radiobutton "Library"
- As a target project, our project has already been substituted.
- Poke "Import"
Now click on the project name with the right button → “New file” → create the main.cpp file. Fill it right there in the most trivial way:
#include"mbed.h"DigitalOut led(LED1);
intmain(){
printf("Hello World !\n");
while(1) {
wait(1); // 1 second
led = !led; // Toggle LED
}
}
(LED1 is already defined in the description of the board - this is actually the only user-controlled LED on Nucleo)
4) Press Ctrl-D (or the “Compile” button), wait ten to fifteen seconds, download the resulting file. We connect the Nucleo to the USB port (a powerful ambush: there are mini-USB connectors on Nucleo, for which not everyone has a lanyard anymore), we discover a new 540 KB disk with the name “Node_L152RE” on the computer. Inside it is an MBED.HTM file, unambiguously hinting why this disk is needed.
In fact, of course, any BIN- or HEX file thrown at it is automatically flashed into the controller, this is not directly connected to Mbed, just such a programming interface.
We throw there the downloaded file. Nucleo blinks the LED of the programmer, and then starts to blink at the LED on the board - thereby LED1.
5) Now we need to add a BME280 sensor, because we have a weather station or what? Fifteen seconds of the search lead us to the library with its support , on the page of which you need to click on the Import Library (libraries, of course, are different, and you should always look inside - but for now let's drop these details). When importing, we do not forget to note that this is the Library, and we need to import it into our current project (“Target path”).
Here is an example of using the library , where you can briefly see how to use it.
6) Add a reference to the library in your code, at the same time removing from it every hello world:
#include"mbed.h"#include"BME280.h"DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
intmain(){
while(1) {
wait(1); // 1 second
led = !led; // Toggle LEDprintf("%2.2f degC, %04.2f hPa, %2.2f %%\r\n", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
}
}
As legs, I, no matter what I did, directly pointed out the pins that I would be comfortable with in my Nucleo to poke an I2C sensor. Generally, because they are directly signed on the board as SDA and SCL (although in general the STM32L1 has several I2C ports, and each can be hung up on several leg variants), then most likely the I2C_SDA and I2C_SCL defains would point to them. But now it is hard for me to think about such complex matters.
7) Connect the sensor. Four wires - +3.3 V, ground, SDA, SCL. Since both the STM32 and the 3.3-volt sensor do not need to think about the coordination of levels.
8) Click “Compile” again, download the new BIN-file and throw it into Nucleo.
eight) ??????
(in fact, at this point we open our favorite terminalku - I usually use Termite, and we cling it to the virtual port corresponding to Nucleo; In a modern computer, this is likely to be the only available COM port. The speed is set at 9600 bps)
9) PROFIT !!!
Works. But here we, as good programmers, are not satisfied with the idea “if the Hindu code works, then it’s not so bad,” and we see what we want to improve.
Globally - probably two things. First, the code spinning in while (1) with a 1-second delay is a very bad concept, as the delay is not there 1 second, but 1 second plus the execution time of the code, and we will want something with other periods here. still run.
Secondly, 9600 bps is somehow sad, 2018 is in the yard, let's at least 115200.
With the port, everything is simple: you need to create your own copy of the Serial class, point it to the desired speed, and then print through it.
Serial pc (SERIAL_TX, SERIAL_RX);
pc.baud (115200);
With getting rid of while (1) also, however, there are no big problems. In Mbed there is such a thing as an event queue ( EventQueue ), events in which can be triggered after a specified period of time (call method) or permanently (call_every method), and the result of their call will be the execution of a particular function.
We make the code:
#include"mbed.h"#include"BME280.h"DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
EventQueue eventQueue(/* event count */50 * EVENTS_EVENT_SIZE);
voidprintTemperature(void){
printf("%2.2f degC, %04.2f hPa, %2.2f %%\r\n", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
led = !led; // Toggle LED
}
intmain(){
eventQueue.call_every(1000, printTemperature); // run every 1000 ms
eventQueue.dispatch_forever();
return0;
}
We collect, we start. It works exactly the same way as before (which is already not bad), but now we have a solution that can be scaled almost unlimitedly and without any problems. We can calmly throw other events with other periods into the eventQueue (the main thing is to make sure that we don’t live more than 50 events at one time), and until their execution time begins to overlap, we don’t have to worry about how they interact.
Why?
Because, unlike stuffing everything into one while (1) with carefully verified delays, they do not interact.
Ok, now let's add the OPT3001 here. Not that the measurement of illumination was a mandatory feature of the weather station, but since I have this sensor on the table ...
With the OPT3001, we have some problem - it is very good (and precision, and especially power consumption), but not very popular compared to the same TSL2561 sensor among home builders, so we can’t find a ready-made library for it directly in Mbed. I would say that it’s easy to write a driver for it (I wrote), but let's google into Google - and we’ll find https://github.com/ashok-rao/mbed-OPT3001 as the first link .
Again, click Import, select the subtle link “Click here to import from URL” in the title, insert the project URL on the github, do not forget to click on the “Library” item - and “Import” itself.
Not really . Astanavites
In fact, this driver is written on the knee so much that it is not worth using it at all. As the great writer once said, “having opened OPT3001.cpp, bloody tears flowed from my eyes,” so I put aside all important things for a short while and quickly sketched a more decent driver (that is, now I’ve twice been a OPT3001 driver writer).
Further along the path already known: Import → Click here to import from URL → github.com/olegart/mbed_opt3001 → Library → Target Path = our project → Import.
Add to the project the rest of the code:
#include"mbed.h"#include"BME280.h"#include"OPT3001.h"DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
OPT3001 sensor_opt(D14, D15);
EventQueue eventQueue(/* event count */50 * EVENTS_EVENT_SIZE);
Serial pc(SERIAL_TX, SERIAL_RX);
voidprintTemperature(void){
pc.printf("%2.2f degC, %04.2f hPa, %2.2f %%\r\n", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
pc.printf("%ld lux\r\n", sensor_opt.readSensor());
led = !led; // Toggle LED
}
intmain(){
pc.baud(115200);
eventQueue.call_every(1000, printTemperature); // run every 1000 ms
eventQueue.dispatch_forever();
return0;
}
Compile, save the file, throw it in the Nucleo, do not forget to switch the terminalka to 115200 ...
PROFIT !!!
Bonus: energy saving
All this we wrote, by and large, the left rear heel, without really thinking about anything - well, except perhaps throwing away a stupid and uncomfortable endless loop.
Among the things that we did not think about, there is such a thing as power consumption. Sometimes you have to think about it, however.
Especially for its measurement on Nucleo there is an IDD jumper, by removing which you can plug in with a multimeter and see what happens here (although generally speaking, a multimeter is a bad tool for measuring the power consumption of microcontrollers, with fast jumps of consumption it fixes mainly weather on Mars).
On my L152, the current turns out to be around 10 mA, walking a little back and forth depending on the LED1 glow, which we still pull out of habit (it consumes 2-3 mA). Somehow quite inhumane to the system, which is most of the time doing nothing, do not you? ..
In this case, we interfere with that same message queue, which we were happy recently. The fact is that it allows you to set timings with millisecond precision, and therefore works on a fast processor hardware timer, which is turned off when the latter goes to sleep - in other words, if the processor goes to sleep, our eventQueue.call_every will go to sleep, too, forever. On the STM32L151 there isn’t any special fast timer ticking in a dream — more precisely, on someprocessor models can be pulled out of normal RTC clocks (starting with L151CB-A and higher, register RTC_SSR), but in general - no.
In addition, the message queue has the dispatch_forever () method, which revolves at the end of our firmware and also does not allow to sleep - it is engaged in that, in fact, receives, processes and executes messages.
But we don’t need millisecond intervals here, do we still get data once a second? And after all, we need to process messages only when the processor wakes up, because if it didn’t wake up, where did the messages come from?
Therefore, we boldly take a thing called LowPowerTickerworking at RTC, and we are starting to rewrite the code on it - the benefit is to change only a few lines. Having started LowPowerTicker, we can say the system sleep () - and it will fall asleep in a deep sleep (or, more precisely, with what other modules allow) - there are nuances, for example, a module breaking in a dream can sleep during its work ).
But there is one problem: the ticker calls are performed in an interrupt, and in the interruption one cannot do any long things - we have very, very long sensor readings (for OPT3001, for example, 100 ms passes from request to response). With printf everything is even worse - it is also cumbersome, so the interrupt may stupidly not have enough stack. Therefore, we need to send a signal from the interrupt to the function that actually reads the sensors and prints the value so that it is executed in the usual context.
A crude method would be to set a variable in an interrupt, and in the main context, read the value of this variable in while (1), and if it suddenly cocked, call reading sensors. But we are not arduinschiki with you, so that in while (1) to push meaningful code?
Suddenly, this is the same EventQueue that we just dropped.
#include"mbed.h"#include"BME280.h"#include"OPT3001.h"DigitalOut led(LED1);
BME280 sensor_bme(D14, D15);
OPT3001 sensor_opt(D14, D15);
EventQueue eventQueue(/* event count */50 * EVENTS_EVENT_SIZE);
Serial pc(SERIAL_TX, SERIAL_RX);
LowPowerTicker lpTicker;
voidprintTemperature(void){
pc.printf("%2.2f degC, %04.2f hPa, %2.2f %%\r\n", sensor_bme.getTemperature(), sensor_bme.getPressure(), sensor_bme.getHumidity());
pc.printf("%ld lux\r\n", sensor_opt.readSensor());
led = !led; // Toggle LED
}
voidtickerIRQ(void){
eventQueue.call(printTemperature);
}
intmain(){
pc.baud(115200);
lpTicker.attach(tickerIRQ, 1); // every secondwhile(1)
{
eventQueue.dispatch(0);
sleep();
}
}
What did we do here? Yes, in general, nothing complicated: added Low Power Ticker, which once per second gives an interrupt. On interrupt, the controller wakes up, the interrupt handler calls eventQueue.call, which should actually pull the hard work out of the handler into the external function (the sleepy processor does not interfere with it here, since it has no delay during which the processor could fall asleep). implies immediate execution). By itself .call would not have done anything, but in the main body of the program, we again had while (1), in which there are only two lines - the dispatch method for the queue, which processes messages in it, and sleep (), which takes the processor to sleep until next interrupt.
Everything.
Fall asleep → interrupt → woke up → call → left the interruption → dispatch → took readings from the sensors → sleep () until the next click of the ticker.
PROFIT ???
PROFIT !!!
(in fact, 0.57 mA of consumption is a lot, although a couple of dozen times less than before. But in Mbed, most likely, an unpleasant nuance with STM32L1 processors is not taken into account: they have default legs in Digital In mode without braces, but it is necessary to turn on Analog In or the brace, otherwise Schmitt triggers at the input of these legs are pretty well guzzled due to spontaneous switching due to pickup. In addition, in Nucleo, the current can flow through the JTAG legs towards the programmer-debugger built into the board )
And we received all this in general without much effort. In fact, even before reaching the "real RTOS" with its processes, semaphores, messages and other goodies.
The only payment was perhaps that resource consumption - 55.1 KB of flash and 9.2 KB of RAM (you can see the Build details in the log with a message about the successful assembly of the project), but for modern controllers this is not that huge amounts Even a completely standard 2-dollar STM32F103CB has 128 KB of the first and 20 KB of the second. In principle, the minimal project on the microcontroller and OS I worked with lived on the STM32F030F4P6 with 16 KB of flash and 4 KB of RAM on RIOT OS with slightly clipped wings.
In addition, in this case, we did not save resources at all, while in a decent society, for using floats inside printf on a developer’s microcontroller, they simply silently take the door and shoot them in the corridor. They don’t say anything - everyone understands why . Even malloc is better, malloc can still be forgiven in some circumstances.
PS For the lazy - the code written here is immediately in the Mbed repository .
PPS Parts for assembly: Nucleo-L152RE (any other Nucleo / Discovery will work too, just check that it is on the board list - but everything seems to be there), BME280(attention: on Ali, these scarves are cheaper than the cost of the chip itself, not only because the Chinese are generous, but also because the cheaper BMP280, which is outwardly indistinguishable), is molded into them). Where do you get the OPT3001 closer to Ali - I don’t know, except for the native TI girl for three thousand rubles.
Conclusion
I don’t want to say that everything is cloudless in the world of microcontroller RTOSs, and every line of code oozes with grace - the same Mbed reached an acceptable state from my point of view somewhere in the last couple of years, they fixed it in RIOT only in release 2018.07 some of the old sores (and some have not been fixed), and so on.
However, the idea that only Arduino "provides the minimum entry threshold", and all other platforms are mandatory picking in strange libraries, hundreds of pages of datasheets and assembler, can be shared only by a person who has spent the last decade somewhere in places very remote from civilization (in the not so remote Internet, they say, and that has already been done).
Absolutely nothing difficult to start working with modern controllers in a modern development environment, no. Moreover, at the same time you get a lot of tasty, useful, and most importantly - well-implemented things, radically accelerating and simplifying development. No, I’m not talking about the “LED flashing” libraries, although they are also enough - I’m talking about timers, multitasking, messages, services and everything else that makes no sense in 2018 AD to write with your hands, because everything is already written before you and, most likely, much better than you ever write.
In fact, we literally half an hour from scratch have now done quite a decent project - not just working, but minimally normal from an architectural point of view. Scalable with little effort, working on interruptions, supporting energy saving. Not straining.
Yes, you can dodge and in a few days pile on a crooked rake-like resemblance of what users of modern platforms get out of the box in an elegant, ironed form. You can even write on this some meaningful project, which sooner or later at the cost of serious efforts spent will be able to get to work and not to fall out of the resources of a typical AVR.
But ...
Why ?!
Look at the calendar, 2018 in the yard! Don't you feel so sorry for yourself?