Hi, interesting article, but I would like to suggest you something. While it's OK to do bare metal just using the memory locations like you did:
*(uint32_t *)0x40021018 = 0x00000004;
after a few days, it will become tiresome to read and to remember all those addresses. I suggest you to use the header [0] that the ST provides for STM32F1, which is included in packages such as STMCubeF1 [1], STMCubeF0, etc.
For instance, instead of using the memory address directly, you could just do
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
so it's easier to remember what is what. STMCubeF1 is a big package, but you won't be using the API/drivers ST provides (such as HAL and LL), just the header. As I understand, the STM32 community kind of rejects this API as "bloated".
I have some examples here if you would like to take a look:
>but you won't be using the API/drivers ST provides (such as HAL and LL), just the header. As I understand, the STM32 community kind of rejects this API as "bloated".
If you're not making millions of something (cost sensitive), and not running into performance issues with the HAL, just pick a microcontroller with more than enough flash and use the HAL provided by ST. It's faster to develop with, relatively robust, good performance, and it will make your code more portable. Writing directly to registers is the perfect example of premature optimization.
I've spent the last ~7 years writing firmware on the NXP LPC8xx/18xx and the STM32 L0/L4/F7/H7. In that time I've only found a couple of errors in the HALs provided, and only once or twice needed to re-write some HAL code to increase performance. Using the HALs from the vendor saved me an immense amount of development time and made porting code between chips trivial.
Actually that line is not ok. But for another reason. Without "volatile" in there, the compiler may reorder this access with others, or remove it, or combine it with others. None of that is good.
That's a very important call-out. This is the kind of tear-your-hair-out stuff that's going to be very difficult to track down unless you get it exactly right. I echo that using the definitions makes the code infinitely more readable and writeable, and makes zero difference in terms of performance.
To be fair, the last paragraph of the post mentions CMSIS, which provides proper headers with register names at least.
The HAL is very complete, but a bit hard to read because it supports all the (sometimes very subtle, possibly even undocumented) variations in the STM32 feature blocks. These variations have accumulated over the years.
Edit: also, the STMCube tool is a great help for configuring the chips of that family. It can configure IO pins, function blocks, the sometimes insanely flexible clock tree, generate project skeletons, estimate power consumption, etc... it is actually quite useful.
ARGH! Seriously? The STM32F103? Crazy (or should that be Craze) hobbyists using "blue pill" boards because they are cheap (and they are, like $3 on ebay) which use this bastard chip of the ST Cortex-M line and they say "gee, its a really simple circuit and since they sell for so cheap on ebay it should be a good idea to use this chip!" BZZZZZZT!
ST Micro makes much better CPUs that would be ideal for this application, they have things like on board DSP instructions in their floating point units, and can run at internal clock rates of 180 MHz with a megabyte of Flash and 384K of RAM, some of it very high performance (core coupled memory). These CPUs are no harder to put onto a PCB than the STM32F103 and they are so much more capable. And sure, they are $10 apiece instead of $1 apiece which is fine if you're making BILLIONS of these things, but a bespoke quadcopter?
Oh but its Open Source, cool, except I can't find the PCB files anywhere, if I could I could pull out that crap chip and put in a real CPU and have OSHPark make me a few. I did find the software repo : https://github.com/Crazepony and yeah, another budget quadcopter maker with a bunch of things they want to sell and an "open source" tag to try to get people to come and look. Fair enough, it made me look. It is sort of the 'freemium' hardware company model.
Sigh. I'd love something bigger than the Crazflie[1] which is like real open source (PCB and everything) and smaller than a DJI Phantom that I could play around with.
I did one better than that, I shot my ST Micro rep an email and asked them if they would like a open source design on the web that showcased their new STM32F4 series with built in Wireless support and their new STSPIN BLDC controllers with built in Cortex-M0s. He was quite taken with the idea :-)
Quadcopter pilots have been using STM32F3 for years. Check out: https://github.com/betaflight/betaflight one of the more popular flight control projects. They've been supporting STM32F4 and STM32F7 for quite some time.
A typical quad flight controller with an STM32F4 and a decent MPU will set you back less than $30.
They kickstarted (successfully, shipping to their backers etc), now have an online store, and all of the files (both software + hardware designs) are downloadable for everyone. :)
The interesting difference here is that these things have a bunch of processors on them. So the 'pilot' process can be essentially an interrupt service routine that keeps things sane on an interrupt that cant be masked by the Python code. So during garbage collection your UAV might stop and hover, but it would be unlikely to crash in that scenario.
Further, my experience with Micropython so far has shown it to be pretty fluid. But I would be the first to tell you I haven't challenged it all that much.
Do you want to carry a payload or fly in wind? otherwise, I strongly recommend the Crazyflie, it is a good product with a vibrant community. It has an STM32F405 with FPU that is surprisingly powerful - nontrivial math in the fast control loop is no problem.
I have three of them :-) And I would like something with just a bit more payload. In particular I have been fascinated by the papers on cooperative work by multiple quad copters when driven by a supervising computer vision system.
Little bit off topic, but if anyone is on the fence about STM32, I strongly suggest trying out STM32 Black pill 3.3V version (https://wiki.stm32duino.com/images/5/52/Black_Pill_Schematic...).
It's an amazing board, Arduino-compatible, 72MHz, i/o, adc and pwm pins, uart and i2c.
You can get it cheaper and just solder the header yourself.
Even has a micro USB support.
The clock source is typically generated by a phase locked loop from the external crystal oscillator. There's a PLL multiplier and divider value that you can set obtain the desired frequency.
I think the MCU starts running at a low speed from an internal RC oscillator, and then setting up the high speed clock source happens early in the initialization code. This code can be generated by ST's STM32Cube software, which is useful as a reference implementation even if you don't use the generated code in your project.
As mentioned it uses PLL. The chip boots using the internal 8MHz oscillator, and you can then enable the external crystal using code.
The clock configuration is actually a bit complex, you can see an example from the STM32CubeMX configuration code generator tool here: https://imgur.com/S9L6U37
HS means high-speed and is used for the system clock and peripherals, while LS is low-speed and is used for the real-time clock. Peripherals have limits, like the ADC clock is shown to be 36MHz, but the maximum is 14MHz. The tool is not complaining because the ADC was not enabled for that project. Otherwise I would have to change the ADC prescaler, which take values like 2, 4, 6 etc. Thus had I enabled the ADC, I would have been forced to use a prescaler of 6, making the ADC clock 12MHz, below maximum.
To get the maximum ADC frequency of 14Mhz, I would have to lower the system clock to 56Mhz.
Other peripherals, like USB, also has some strict limits.
Pretty much all clocks in modern electronics above a few MHz are generated by PLL synthesis. The synthesizers are referenced to a slower clock crystal or something along those lines. You can go way beyond just multiplication, as well, with fractional synthesis. Check out the Si5351 for a chip that packs a couple of these synthesizers.
ST actually sells an evaluation board for learning about drone control; it has a BTLE module, four small FETs, sensors for pressure and a 9-axis IMU, a 1S battery charging circuit, and source code for some example firmware on Github. Search for 'STEVAL-FCU001V1'.
I've also seen a lot of cheap STM32-based FCU boards from the same places you'd get one of these 'blue pill' boards.
These are very useful chips to learn about, because they come in all shapes and sizes. Some focus on power efficiency, others on speed, others on connectivity or signal processing, and there aren't too many differences that you need to account for in your code.
Also, you can program and debug these using OpenOCD with a generic "ST-Link" module; they only cost a few bucks from cheap sources, or many of ST's evaluation boards have a built-in STLink probe which you can use. That lets you connect to the chip from GDB and step through your code like a normal C program, which is often a lifesaver.
The ST-Link interfaces are damned cheap for a debugging interface and pretty reliable, too. They worked better for me than then much pricier IAR probes. There are two versions of the dongle: with and without protective circuitry. The protected one is significantly more expensive (but still cheap), but you should probably go for it if you have a real risk of frying components.
On-chip debugging with OpenOCD was actually one of the next things I wanted to try out with my drone. I need to get an ST-Link dongle because the drone doesn't have one on board.
I'm not sure if JTAG will even work or if they connected a pin to some sensor or similar on the board. SWD will work for sure, I saw the two SWCLK and SWDIO connectors on the board.
Cool, good luck! If you end up wanting a more 'official' STLink probe which is still pretty cheap, the ones on their 'discovery kit' and larger 'nucleo' boards can be disconnected from the main board and used with other targets.
They're a little bulky, but if you're interested in bare-metal programming, the embedded Rust ebook[1] currently uses an STM32F3 discovery kit[2] as a target, and that board's user manual has instructions for using its ST-Link to program/debug an external application[3] like your drone.
If SWCLK and SWDIO signals are present, there are great chances that SWD will work. In any case, you can control with registers if you want to enable full JTAG, SWD, or nothing, and reuse those pins as you wish.
You can use JTAG probes that implement the CMSIS-DAP (also called DAPLink) protocol, so you get a probe that doesn't need any driver as is seen as an HID USB device. I ran through some problems when trying to use the ST-LINK/V2 with Linux, but that was some years ago and I don't know if things have changed since then.
I have no idea about the load carrying capacity of the drone, but it would be an awfully cool project to add an ultrasound sensor(s) and write a program for flying it around randomly indoors without hitting walls. A fancy feature might be to solve a real-life maze by flying through it. Would such a thing be possible?
But for bigger batteries I recommend buying a charger that can handle 4 or more separate batteries at a time like the SkyRC Q200 or the Turnigy TQ4, or just do like me, charge each battery individually.
I cannot reccomend against Kiel, iar, and atollic enough. They are garbage ide's (can only do string based auto complete, no dark theme, very poor window management), yet still cost a small fortune if you want to go above 32KB of flash usage or whatever other the free versions do. They also have their version of a linker script which is even worse than normal linker scripts, and if you are forced to use their compiler then often it doesn't even handle c++11. Or if it does have c++, the auto complete doesn't.
Go for gcc or clang with cmake (or even just make) and use qt creator or visual studio code or others. You will get cmake based auto complete, access to a proper IDE color scheme of your choice, get to use your own compiler (clang, gcc, etc), and it's much more flexible. And for debugging be able to use a much more capable ozone from segger, or even gdb enabled backend in qt creator or clion.
My work flow for example is cmake and gcc regarding tooling, qt creator or visual studio code which works with cmake to give me proper context aware auto complete, and clang-tidy as a linter running in the background. I am incredibly productive with this, and am able to make use of c++17's features to go for true zero cost abstractions, while shifting checks from runtime to the compiler via types and static asserts and whatnot.
Plus, this is far more portable. No longer having to worry about project files working across diffirent versions of keil (this bit me in the ass at my last company). It's just a simple text file.
Unfortunately, iffy IDEs are pretty common in the embedded world. I too dislike Keil's uVision IDE, but sometimes you need to take the bad with the good. At work I use Keil + MDK-Pro and now and again also use ETM trace for debugging really tricky problems. AFAIK armcc also produces higher performing binaries than arm gcc (but this probably irrelevant in a lot of use cases).
Yes, gcc and clang are overwhelmingly superior for all purposes.
Let me advise against growing dependent on IDEs. They make it easy to get started, but soon begin to hold you back. Vim, meanwhile, takes some practice, but it repays so much, year in and year out. I have never heard of anyone who learned and then abandoned it.
I can't help but wonder whether quadcopters will be where governments finally legislate control over general computation. Having access to bare metal means nobody can stop inexpensive and socially-disruptive quadcopter applications. How many years before quadcopters join rats and pigeons as the scourge of urban life, always on the prowl for unguarded power outlets or perches on power lines?
Governments and/or corporations have control of the bottom layer of cell phones and TCP/IP networking. Not so for firmware and cryptography, though not for lack of trying.
For instance, instead of using the memory address directly, you could just do
so it's easier to remember what is what. STMCubeF1 is a big package, but you won't be using the API/drivers ST provides (such as HAL and LL), just the header. As I understand, the STM32 community kind of rejects this API as "bloated".I have some examples here if you would like to take a look:
https://github.com/dbolgheroni/arm-makefile/blob/master/drv/...
Cheers,
[0] https://github.com/dbolgheroni/arm-makefile/blob/master/stm3... [1] https://www.st.com/en/embedded-software/stm32cubef1.html