
How to flash an LED: writing ARM assembly for an STM32 microcontroller - lochsh
https://mcla.ug/blog/how-to-flash-an-led.html
======
unbalancedevh
I have years of experience in asm programming with PIC processors, and my job
is embedded software development. I got an STM32 development board a couple
years ago to get my feet wet in ARM embedded development. I got completely
bogged-down in setting up the development environment. It took me forever to
get a blinking LED, and I spent an ungodly amount of time unsuccessfully
trying to figure out how to change the rate based on a potentiometer input.

It was a very frustrating experience, but articles like this make me want to
dive in and try again!

~~~
thebruce87m
Do you mean in assembly? If you just mean C, then if you’re already in
embedded then I’d say a few years ago it was easy. These days it’s super easy
- tools like STM32CubeMX will allow you to get even custom hardware up and
running in no time.

It will even spit out multiple project types - everything from ARM
Keil/MDK/uVision to just a simple makefile.

~~~
jbott
I disagree that GUI tools like STM32CubeMX are useful. They end up being a
crutch that end up adding increasingly complex wizard driven autogeneration,
rather than just exposing the configuration in easy to consume ways. Sure, you
can get something running fast, but it’s incredibly difficult to move from
“blink a led via this template project” to “build a useful project integrating
multiple peripherals”.

I think there’s some actually interesting work being done with standardization
like SVD files, but too many vendors treat them like second class citizens
compared to their bulky code gen solutions.

~~~
thebruce87m
I think it’s decent - configuring a clock tree and initialising the
peripherals by hand might be a good way to learn the chip but in terms of
rapid prototyping I’ll take the GUI driven leg up any day.

------
dmitrygr
@author:

    
    
       1     @ Set BR8 field in GPIOA_BSRR, to clear GPIOA8
       2     movw r1, #0x0000
       3     movt r1, #0x0100
    

you can just use a mov to get that value. Any 8-bit value shifted left any
number of bits is synthesizable using one MOV

~~~
lochsh
This is true! If I was writing something to be optimal speed- and size-wise,
I'd do that. I think I just liked the consistency with the other parts of the
code where using both was necessary :)

~~~
lochsh
Oh actually I was being sleepy and hadn't understood what you'd said! I hadn't
realised you could do that actually, neat, thanks!

So you could do `mov r1, #0x01000000` right? nice.

~~~
dmitrygr
yup, some other constants are also loadable in one instr, of the form
0xXX00XX00, 0x00XX00XX, 0xXXXXXXXX where XX is any byte

~~~
saagarjha
Do you not need the usual LSL, or does the assembler take care of that?

~~~
dmitrygr
it does, yes

------
geokon
I did something similar with CMake and a lot of the details are incredibly
challenging to piece together. Everyone just blindly copies existing
linker/startup scripts. Very low level articles like this are rare and
invaluable

I spent a month and got things working with a modern toolchain
C/CMake/GCC/OpenOCD but gave up on further work bc I couldn't nail down a lot
of details. micro programming outside arduino is painful and backward. it's
stuck in the 90s

[https://geokon-gh.github.io/bluepill/](https://geokon-gh.github.io/bluepill/)

------
bloopernova
This was a really interesting article, and I learned a lot. I'm very much
lacking in chip/assembly level knowledge, and this helped me to understand a
little bit more. Thank you, Lochsh, for writing it!

~~~
lochsh
This is lovely to hear, I'm so glad it was helpful!

~~~
Optimal_Persona
Not too familiar with the content of your post, but...I love your site design
- great use of space, color & serif fonts!

~~~
lochsh
I love getting compliments on my site design, thanks very much.

------
digitaltom
Fantastic article, thanks for taking the time to write it up! I'm trying to
learn about embedded systems at the moment and it feela like a super steep
learning curve, really appreciate your post breaking everything down.

A little typo I think I spotted: ' So, to turn on our LED we want to set the
BR8 field, and to turn it off, we want to set the BS8 field.'

Should this be: ' So, to turn on our LED we want to _clear_ the BR8 field, and
to turn it off, we want to set the BS8 field. '

~~~
lochsh
Thank you for the compliment ^_^ I'm glad you found the post helpful.

This isn't a typo, actually. If you look at the documentation of the BSRR
register (which is screenshotted in the blog post), it says of BRy:

> These bits are write-only [...] [Setting to 1] Resets the corresponding ODRx
> bit

So setting BR8 in the BSRR clears the ORD8 bit in the output data register.
Because our LED is active low, this turns the LED on.

The indirection can make this a little confusing, I hope this cleans it up!

~~~
digitaltom
Ahh I see now, thank you for teaching me. My mistake!

Do you happen to know of any great learning resources, basically more posts
like you've written that go into lots of details and explain why things are
done?

Thanks again!

~~~
lochsh
I'm not sure I do have suggestions for such resources sadly :( If anyone has
any please drop them here!

------
amelius
Anybody got USB communication (CDC class) working on this platform without the
proprietary libs?

~~~
dmitrygr
no, i spent months on this, and the answer is no

They use IP from synopsis, but <rumor>synopsis refuses to allow their docs to
be published so every manufacturer has to read them and regurgitate them into
their own docs in their own words</rumor>. In any case official STM docs are
incomplete. See their driver source - it accesses undocumented registers and
sets undocumented bits in documented regs. Without them the usb core will not
start or run.

Your options are to carefully rewrite STM's libs (which are based on real
synopsis docs and DO work) or use them as is. Both solutions IMHO leave you
subject to STM's EULA and such

this is the #1 issue in stm32

a close #2 is the their so-smart-it-is-useless i2c controller. I know of no
project that uses it. Everyone bitbangs i2c master on stm32. The hardware unit
is very easy to wedge to a point where only a power cycle unwedges it.

~~~
rabryan
Avoid ST at all costs in my experience. Their documentation is terrible. They
never acknowledge hardware errata even after clear evidence of it. They’re not
a company with a solid engineering culture imo

~~~
__init
I'm really surprised to hear that. ST has always been one of my favorite
manufacturers. I find their datasheets to be clear and well laid out, though I
will note that I'm usually more interested in the hardware side than the
software side. Their chips and boards are also high quality and convenient,
e.g. GPIOs are often five-volt tolerant, Nucleo boards come with well-made
breakout headers and several peripherals (at seemingly impossibly low cost),
chips have wide supply voltages, etc. I can't say I know anything about their
errata (I've never run into any), but even if the situation is as bad as you
say, that's quite a harsh condemnation, especially considering how widely-used
and successful they are in industry. Do you have other complaints?

~~~
rabryan
I’ve used their accelerometer (LIS2D). Performance and specs are great - it
was just a pain to develop the embedded interface with it because the
documentation had a bunch of mistatements and was missing critical information
(a weak pullup on a certain line caused a bunch of current consumption - took
days to figure out). Also they had an off by one bug in their LIFO queue that
took a long time to figure out. Once you getting it working its great. Last
time I checked they still hadnt updated their documentation with what I
reported to them...

------
MuffinFlavored
Is there a better way to sleep/delay other than what basically seems like 100%
CPU usage in a subtraction loop?

~~~
rdc12
The other approach to delay's (keep in mind, that there is no other process to
yield the CPU too) is to use a timer (a peripheral of the micro controller).

This would take a fair bit more code to configure the timer, and setup an
interrupt to handle the timer.

~~~
MuffinFlavored
How do multiple processes usually 'share' a timer?

~~~
rdc12
Microcontrollers (assuming no RTOS in use), typically don't have multiple
processes, they have the main thread of execution (which in the article is the
entire program) and have interrupt routines that a run in response to an
external event (in this case, the timer has expired).

Using the interrupt driven approach, can led to better performance both in CPU
time (async communication with slower periphials etc) and battery life (sleep
states).

