Quadcopter Programming Part 2: Using the CMSIS Library and First Takeoff
Quadcopter Programming Part 2: Using the CMSIS Library and First Takeoff
- Tim Schumacher
In the last post of the series I explained how to get started programming an STM32 microcontroller without any library dependencies. Today we will improve the LED blinking example from last post with the CMSIS library, making it more readable and adding precise timings. Finally we will drive the rotors of the quadcopter for a somewhat assisted takeoff.
CMSIS setup #
The Cortex Microcontroller Software Interface Standard (CMSIS) library is part of the Standard Peripheral Library developed by STMicroelectronics. You can download the version for your microcontroller from their website. For the processor on my quadcopter I needed the STM32F10x version of the Standard Peripheral Library, the newest version at the time of writing was 3.5.0.
After unzipping the archive you will find the relevant library files under Libraries/CMSIS
. This is a collection of files for different processors and compilers. We will just pick the ones we need and copy them into a CMSIS
directory alongside our source files.
Before we start, recall the final code from last post:
Library files #
These hexadecimal memory addresses convey no meaning and are prone to errors. The stm32f10x.h
header file defines pointers to those registers with identifiers from the reference manual. That will make the code much more readable.
Copy Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/stm32f10x.h
or the equivalent for your processor to your CMSIS
directory. For it to compile you have to specify the processor type either by setting a compiler flag as shown later or by uncommenting one of the preprocessor definitions in the file:
Also copy the following files to your new CMSIS
folder. They are required by the stm32f10x.h
header file and provide functions to operate some of the peripherals such as the timer we will use later:
Libraries/CMSIS/CM3/CoreSupport/core_cm3.h
Libraries/CMSIS/CM3/CoreSupport/core_cm3.c
Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.h
Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.c
With these files in the CMSIS
directory you can already compile the two objects. Instead of uncommenting a line in stm32f10x.h
I passed the compiler flag -D STM32F10X_MD
for my medium density chip.
Startup and linker script #
Remember the startup assembly code and the linker script we wrote last post. The startup code was required for pointing the processor to the C entry point and the linker script was telling the linker about the memory layout of the chip. Trying to link the startup and linker script from last post with the two objects we just compiled will throw errors. CMSIS provides its own startup and linker scripts with additional pointer definitions for some memory locations.
I used the assembly startup script at CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md.s
which was compatible with gcc and needed no modifications. Here md
stands for medium density again.
The linker script is actually not part of CMSIS, but there are many scripts around that come with different IDEs and some are included in the Standard Peripheral Library distribution. I found a few linker scripts from TrueSTUDIO under the archive root at Project/STM32F10x_StdPeriph_Template/TrueSTUDIO
. None of them were for my specific chip so I compared them with diffuse.
The middle file STM3210B-EVAL/stm32_flash.ld
seemed to be the best match for my processor. I adjusted the flash size from 128 KiB to 64 KiB.
And I removed a few lines which are redundant and cause linking errors when using the file outside of TrueSTUDIO:
That’s the complete CMSIS installation. Make sure your CMSIS
directory contains all of the following files or the respective files for your processor:
Register pointers #
So let’s start by revising the blink LED script from above to make use of the CMSIS library. We begin with the hardcoded memory addresses. Last post we looked up the register memory addresses in the reference manual. But you can also find identifiers for each register there. Let’s revisit the APB2
peripheral clock enable register, it’s the first register we access in our code to be able to write to the GPIOA
registers later:
The reference manual tells us the identifier of this register in the heading.
APB2 peripheral clock enable register (RCC_APB2ENR)
CMSIS also defines variables for the different values registers can take. From the reference manual:
Bit 2 IOPAEN: IO port A clock enable
Set and cleared by software.
0: IO port A clock disabled
1: IO port A clock enabled
We can rewrite the line like this:
Much better, right? This is the code with all memory addresses replaced. Don’t forget to #include "stm32f10x.h"
.
Timings #
Another issue that needs addressing are the for-loops used for delay.
We don’t know how long this for-loop will take to run i.e. we don’t know how many clock cycles one iteration takes. This is not defined and could differ between debug and optimized builds. That’s where the timer peripheral comes into play.
A word on system clocks #
The STM32 processor on my board runs at 72 MHz. CMSIS defines the SystemCoreClock
variable with the frequency which it assumes based on the processor type we specified earlier at compile time. I find the way system clocks work really fascinating and I highly recommend two videos by Steve Mould on YouTube. In the first one he explains why quartz crystals make electricity when vibrating and in the second video he shows how this is used in quartz watches.
The quartz crystals in processors are generally vibrating at a multiple of their fundamental resonant frequency because it is challenging to manufacture crystals with a resonant frequency above 30 MHz. For example the quartz crystal in my processor vibrating at 72 MHz has a fundamental resonant frequency of 25 MHz. This is referred to as a 3rd overtone crystal.
Regular interrupts #
The timer peripheral is a circuit which can be configured for interrupts every N clock cycles. CMSIS provides the SysTick_Config
function to configure and enable the timer peripheral. Most functions are documented in Libraries/CMSIS/Documentation/CMSIS_Core.htm
if you’d like to have a look yourself. The function takes the number of clock cycles between interrupts as an argument. Looking at the code of the function you can see that there’s no magic. It writes to memory like we did before to toggle LEDs. You could write this function yourself with help of the reference manual.
We call SysTick_Config
at the beginning of the main function. We want an interrupt every millisecond for now so we divide the clock frequency by 1000 to get the number of clock cycles per millisecond.
The timer is hardwired to run a function at a specific memory location much like we saw last post where we placed the call to the C entry point at the right location in memory. The assembly startup script that comes with CMSIS already places a call to SysTick_Handler
for us at that location.
Now it’s just a matter of counting up the milliseconds and writing a delay function. The final code should be quite self-explanatory.
Inlining the Delay
function seems appropriate here because it is so small and the function overhead could potentially lead to very slight inaccuracies in the timing. The millisecond counter would overflow after about 50 days, way after the batterie runs out when the quadcopter is in the air.
Rotating lights #
Before we finally drive the rotors let’s make the LEDs on all four arms blink in a rotating fashion as a final example. We need to take another look at the quadcopter schematics to find the corresponding pins.
This time because we have pins on the GPIOB
registers we need to turn on that clock first. Then we configure all four pins as output pins and between some delay always turn the current pin on and the one before off. Straight forward, right?
Let’s have a look at the result.
But wait, one light is not working. The white LED is just the power LED, but both the bottom and the top blue LED on one arm are not working. The fact that both LEDs are not turning on made me pretty sure the LEDs are okay. Pin PB3
wasn’t powered. After digging through the reference manual for a while I noticed that the pin is configured for JTAG debugging by default. According to the reference manual you have a few options to free the pin.
To optimize the number of free GPIOs during debugging, this mapping can be configured in different ways by programming the SWJ_CFG[2:0] bits in the Alternate function remap and debug I/O configuration register (AFIO_MAPR).
The developers of the drone wired a JTAG pin to an LED, they obviously didn’t account for JTAG debugging. But there is still SWD debugging which just requires two pins and they connected those to an easily accessible debug port. So we will disable the JTAG debug port to free pin PB3
but keep SWD debugging enabled. Again CMSIS has us covered for writing to the AFIO_MAPR
register, no hardcoding memory addresses necessary.
Before we can write to the register we need to enable the correspoding clock as usual.
Voilà, ready for takeoff!
Driving the motors #
For this test we will drive all four rotors full throttle while holding the quadcopter down with a makeshift cardboard construction. I attached adhesive hook and loop tape to the batteries and drone for easy battery swapping.
Each of the four motors is connected directly to the battery via a transistor. The transitor base pins labeled PWMA
to PWMD
in this schematic correspond to GPIO pins A0
to A4
.
To regulate the speed of the rotors you would use pulse width modulation (PWM), basically switching power on and off quickly to simulate an intermediate voltage. The code for this test though just turns on the transistors for 20 seconds after 10 seconds of LED blinking for me to get my cardboard construction in place. I won’t include the code here because it is completely analogous to what we did with the LEDs but it’s here if you want to take a look.
Again all the examples including Makefiles are on GitHub.
Future plans #
The next post will be about gdb debugging on embedded systems with OpenOCD via Serial Wire Debug (SWD). Let’s see if that post takes me another half a year… To be fair I just came back to the project and finally got SWD working. This used to give me lot’s of trouble and was the main reason this post took me so long.
As always you can leave feedback and suggestions on Hacker News.
from Hacker News https://ift.tt/37UJIk3