Explaining Pure C Blink Example
Tags: programming.c, programming.arduino, programming.embedded
I’ve been playing around with an Arduino Uno this past week.
I came across this post from
balau82:
it gives a basic “blink” example program in C, and a high-level overview of how
to come up with such a program.
The high-level overview wasn’t obvious for me, so I’d complement that blogpost
with this, to explain in further detail.
e.g. When the post says:
from the Arduino Uno schematics we can find out where the pins are connected, for example we can find that the LED is connected to the PB5 pin of the Atmega328p chip
These are the pertinent details of the schematic:
- The Arduino board also has an ‘L’ beside the LED.. and the in-built LED is on ‘Pin 13’ (thus the green 13, I guess).
- PB5 is the name of the pin on the ATmega328P controller. (Datasheet here).
Looking at snippets from the code:
int main (void) {
|= _BV(DDB5); /* set Port B bit-5 to output */
DDRB
while(1) {
|= _BV(PORTB5); /* turn on LED */
PORTB ....
}
}
This left a novice like me with questions:
Where do these constants come from? What’s DDRB
? How does DDB5
refer to
PB5? Why PORTB = ...
to change the LED? What’s the difference between DDB5
and PORTB5
?
(At least I understood the magicVar |= 1 << 5
& magicVar &= ~(1 << 5)
bitmasking. – my C-Worksheet is
useful for playing around with this, also, btw).
Where do these (macro) constants come from?
Well, look at the#include <avr/io.h>
. (Probably found in/usr/lib/avr/include
), and the online reference:
it includes avr/sfr_defs.h (SFR = Special Function Registers), which has the_BV
macro as well as explaining thesfr |= _BV(bit)
replacing somesbi (sfr, bit)
instruction. – The specific file (in this case,avr/iom328p.h
) defines theDDB5
, etc. macros.What’s
DDRB
?
Reading the datasheet, Ctrl-F forDDR
leads us to Section 14.2 “Ports as General Digital I/O”.Each port pin consists of three register bits: DDxn, PORTxn, and PINxn. As shown in ”Register Description”, the DDxn bits are accessed at the DDRx I/O address, the PORTxn bits at the PORTx I/O address, and the PINxn bits at the PINx I/O address.
The DDxn bit in the DDRx Register selects the direction of this pin. If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.DDR = Data Direction Register. Ohhh.[1]
DDRB
is the byte (of the data direction of Port B),DDB5
for referring to Port B (x = B), and its 5th bit (n = 5). (The register summary in Section 35, p615 shows this).So, when the Arduino code has:
(13, OUTPUT); pinMode
it is essentially equivalent to:
|= _BV(DDB5); DDRB
….Obviously.[2]
How does
DDB5
refer to PB5?
It just does?
PB5 is the 5th bit of Port B. (Section 1.1.3).
Section 14 also has this to say:Three I/O memory address locations are allocated for each port, one each for the Data Register –
PORTx
, Data Direction Register –DDRx
, and the Port Input Pins –PINx
. The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register, will result in a toggle in the corresponding bit in the Data Register.Why
PORTB = ...
to change the LED?
Because it’s the data register. Aforementioned sections of the datasheet should have made this clear by now.[3]
There’s also compiler magic translating fromsfr |= _BV(bit)
tosbi
/cbi
etc. as explained in the avr-libc documentation. – Section 37 of the datasheet mentionssbi
= “set bit in I/O register”,cbi
= “clear bit …”.What’s the difference between
DDB5
andPORTB5
?
This should be evident from above; one is for referring to the bit of a Data-Direction register, the other for the bit of Port B.
– I think they both evaluate to5
; so unless the compiler does any magic with them, I’m guessing one could incorrectly interchange them and the program would still work. – It’s kinda tempting to just write the number5
, since the identifier has a5
in it anyway..
The commands to compile and upload it I take on faith as “just work”. (Or, rather, I didn’t need to mess with them).
Here’s a
Makefile
adapted from these commands, which will probably be useful. (make upload
will
build & upload the program to an Arduino).
I wanted to translate the Spaceship
Interface (Project 02 of
the book which comes with the Arduino Starter Kit) to C, also.
The only difference from the Blink example is that the Spaceship Interface
example reads input from a pin.
Following the aforementioned tutorial, the switch is in (Arduino) pin 2, and
the LEDs in pins 3, 4, 5.
From the schematic, this corresponds to PD2 .. PD5. (%s/B/D/
is evident, even
without the above understanding).
Reading from a pin should also be ‘easy’ from the above understanding.[4] –
And, again, the avr/sfr_defs.h
documentation mentions that PORTx = 0x33; unsigned char i = PINx
will magically get translated to the correct
instructions.
I was able to come up with this. And it worked and I was so happy.
Then I translated the example to the Atom EDSL. Read about the fun in my next blog post.
[1] Reading is useful. But, on the other hand, the datasheet is like 600 pages long.
[2] Is it fair to call this ‘hard’ (for non-engineers), in the sense that all that isn’t obvious by looking at the C code & blog? OTOH, math is hard, and that’s kindof a problem with the user, not the content.
[3] But writing lots of stupid questions is still useful for clarifying to yourself what you don’t know! – Indeed, asking questions in a programming journal is valuable even if you never get back to answering them, I’d say.
[4] Or from getting lucky by finding the example in Section 14 reading from
PINB
.