A standard first program on an embedded platform is the blinking LED. Getting an LED to blink demonstrates that you have your toolchain set up correctly, that you are able to download your program code into the μC, and that the μC and associated circuitry (e.g. the power supply) is all working. It can even give you good evidence as to the clock rate that your microcontroller is running (something that trips up a great many people, believe it or not).
A program to blink an LED must do the following things:
Set a GPIO (general purpose input/output) pin to be an output
· Drive this output pin (connected to an LED) alternately high (1) and low (0)
· Delay a human-discernable length of time between each output pin change
CONFIGURING GPIO PINS
GPIO pins are normally configured on reset as inputs, but they can be reconfigured under program control. GPIO pin options can vary from a few simple choices (input or output) to a fairly complex set of choices. The AVR family is on the simple side of the spectrum. An AVR GPIO pin can be configured as an input or as an output. Additionally, if a pin is configured as an input, it can further be configured to enable a weak internal pullup resistor on the input. So the three choices are ouput, input, or input with internal pullup.
For the STM32, there are many more options, including both internal pullups and internal pulldowns, as well as various output configurations, and speed ratings to offer tradeoffs between output switching speed and EMI (electromagnetic interference). In addition, before using an STM32 GPIO port, that port’s clock must be enabled.
It is worth noting here that the AVR ports are 8 bits wide, while the STM32 ports are 12 bits wide.
BIT MANIPULATION
Embedded programs will typically do a fair amount of bit manipulation, both input and output. All kinds of switches, sensors, drivers, actuators, and other input and output devices are represented in the program as individual bits or collections of bits. Not only that, but a μC will have many configuration and status registers which will call for the setting or clearing of individual configuration bits, and the reading of individual status bits.
In our first example programs we will see LEDs and switches represented as individual bits, and later many other input and output devices will be added to that list. Thus it is important to understand how to manipulate bits, that is, to read individual bit states and to set or clear individual bit states.
What makes bit manipulation non-trivial is that bits do not usually exist alone, but exist within bytes (and 16-bit words and 32-bit words). Thus it becomes necessary, when writing individual bits, to avoid changing other bits within the same byte/word, and when reading individual bits, to avoid reading (or ignore) other bits within the same byte/word. The CPU logic instructions, as discussed in an earlier section, are what allow us to do these things.
It must be noted that some μC families also include instructions that directly act on individual bits, at least individual bits in certain registers or address ranges. These instructions are commonly used for reading and writing bits in configuration and status registers, and for maintaining bit flags in memory. These instructions, while nice to have, are not a necessity, and the standard logic instructions can always do the same job (with one or two caveats that we will discuss in the future). What’s more, standard C and other high-level languages will not have language constructs designed to access individual bits, even if the μC does have individual bit instructions. If you are writing in a high-level language, it will be up to your compiler as to whether it recognizes individual bit manipulations and uses specialized instructions in those cases.
It should also be pointed out that while C is good at manipulating bits, it it not as good as assembly language. That is to say, there are some bit manipulations that can only be done awkwardly, if at all, in C, that can be done easily in ASM. Which manipulations fall into this category will depend on the particular instruction set of the μC in question. A couple of general examples are, shifting or rotating data through arbitrary numbers of bytes, and using the value of the carry flag, sign flag or other condition flags directly as data bits. As I said, there may be others as well, depending on the underlying instruction set.