Thursday, February 25, 2016

Atmel 8 bit GPIO 101 (a.k.a. Atmel for noobs)

I've spent the past month proactively vacuuming the internet for any and all useful Atmel programming guides.  Unsatisfied with what I found, I bought the textbook Embedded C Programming and the Atmel AVR and read it cover to cover in a day.

Throughout my edumacation, I've tried several times to actually create useful programs, but it's a long road to master programming, so I haven't progressed nearly as fast as I usually do with new engineering challenges.

My major complaint with programming is the information overload... each programming language has it's own problems and 'solutions'.  Even within a programming language, there are numerous 'dialects' , each of which takes time to wrap my head around.  Each programmer has his or her own programming style, resulting in numerous different architecture types.  In short, there's a ton to learn about how to implement each simple concept.

I'm increasingly frustrated that although I have the entire system created in state diagrams, notes, pseudocode, etc, I constantly find myself banging my head on the wall/desk/floor trying to create actual code. 

In comparison, electrical engineering is all about learning relatively few concepts, and then applying them in millions of ways.  There's no 'language' in hardware design... a MOSFET in Mexico is the same as in France... it always behaves according to the same fundamental principals, and with enough caffeine I can tell you exactly what a circuit does without scouring the internet for hours, trying to figure out what the engineer was trying to design.

software:offense :: hardware:defense 
90% of offense is memorizing numerous different plays and executing them perfectly, whereas 90% of defense is hitting the guy with the ball.  I preferred defense when I played.  Aside: I still don't regret quitting football when I did.

...

Down to business: Controlling GPIO 101 on Atmel Microcontrollers

At the lowest level, all a microcontroller does conceptually is turn pins on and off, and - usually not at the same time on a specific pin - measure if a pin is high or low.  Today, let's talk about how to program a microcontroller to read and write pins, which is otherwise known as 'GPIO' ("general purpose input output). 

I'm assuming you've used the internet to help you figure out how to install your preferred programming environment.  I don't have enough experience to 'prefer' anything, but I'm using Atmel Studio 7.0.  So far I hate it for many reasons, but I'm using it for now, until I understand it well enough to know which other IDE to use.

So you've got the software setup and have verified you can talk to the hardware using the built-in debugger tool.  Now what?  Now it's time to bit bang individual pins!

The first major concept to understand is that each pin on today's microcontrollers can do tons of different things.  Nearly every pin can be used as a low-level GPIO pin, but can also be used for dedicated hardware times, PWM outputs, serial buses, etc.  The actual additional functionality depends on your specific microcontroller.  Today we'll focus solely on GPIO.

When you're using a pin as GPIO, the code itself must manually read/write the pin each time you want to read/write data.  Compare this to using a pin as a simple counter, where the hardware measures the number of pulses received without requiring the CPU to increment a counter in software each time a pulse occurs.  Counters let the CPU do other things, whereas GPIO requires the CPU to continuously service a pin each time it wants to read/write a value.

Each time a uC is powered on, the default state of all pins is high impedance GPIO.  Once the code starts running, the pins can be configured however the programmer chooses.  Any pin that isn't specifically changed to something else will remain a high impedance GPIO.  I propose it's best to specifically program all pins, even if you're using their default behavior.

So how do we interface the hardware to the software?  If you look at your data sheet (I'm using the Atmega64M1 throughout this post), you'll find an exhausting - yet incomplete - description of the particulars.  If your head isn't spinning after the first 50 pages, check back once you get to page 1315 or so (the UC3C's hardware manual is actually this long).

So lets just pick a pin and work through programming it to do simple things:
physical hardware pin16, you're our lucky winner!
Looking at page 3, the text associated with pin16 is:
"(ADC5/INT1/ACMPN0,PCINT2) PB2"
Wow, that's a mouth full!

Each item inside the parenthesis is an alternate function besides the standard GPIO capabilities.  Thus, pin16 simplifies to:
"PB2"

...but what does "PB2" mean?
Microcontrollers don't individually address each pin.  Instead, multiple pins are placed in a "port".  Typically, the port is the same size as the actual architecture.  For example, there are typically 8 hardware pins in each port on an 8 bit uC.  Thus:
'P' is short for "port"
'B' signifies pin16 is part of port 'B'.
'2' means pin16 is the third* bit in port 'B'.
*ports are nearly always zero-indexed (i.e. the first element has index zero, the second element has index one, the third element has index two, etc).

So how do we pull pin16 low?  The manual explains GPIO in great detail (from page 51 to page 58).  To summarize, first we need to configure pin16 as an output:
 DDRB |= ( 1 << DDB2 );
Holy crap, did I lose you?  I was lost too, as there are a TON of concepts in that single line.  Let's break it down into the following sections.  But first, some background:

'x' refers to a specific port (pin16 is in port 'B', as discussed above).

'n' refers to a specific pin location inside a port (pin16 is at index '2' in port 'B', as discussed above).

'DDRx' is the "data direction register", which tells the uC whether each pin in the port is an input or an output.  DDRx contains 8 bits, one for each pin in port x.
'DDxn' is simply a hardware abstraction of a pin's location in the port.  Specifically, DDxn is simply a number.  For example, when the code sees 'DDB2', it searches to where 'DDB2' was defined, and ultimately replaces 'DDB2' with the number '2'.  It's okay to be a bit confused still.

'<<' is the left shift operator.  At a high level, the binary representation of the number on the left is shifted left by the number on the right.  Since 'DDB2' is on the right, and we know it's value is '2', the uC is going to shift '0b00000001' left two spaces.  Thus, "( 1 << DDB2 )" simply becomes "0b00000100".  FYI: "0b" in front of a number simply means the number is represented in binary.
So what is the '<<' doing?  It's creating a "bit mask," which is then used with some logical operator ('|=' in our example) to do something else.  It's okay to be more confused... this tacos gonna roll back around over on itself until we get all the fillings inside.

Okay,  so we've evaluated the right side ('0b00000100), which is simply 4 in our standard decimal system.  Now what's this '|=' symbol all about?  Programmers like shortcuts, as they make writing code faster, but unfortunately they confuse the hell out of new programmers (like myself).
'A |= B' is shorthand for:
"OR each bit in 'A' with each bit in 'B' and then store the value in 'A'". Mathematically, it's equivalent to:
A = A | B
Which is completely different from, and not to be confused with:
A = A || B

'|' means OR each individual bit on the left with each individual bit on the right.  If 'A' and 'B' have the following values:
A: "0b00010001"
B: "0b01010000"

Then after executing A=A|B, A will have the value:
A: "0b01010001"

On the other hand, if we use '||', we're telling the uC to OR the entire left side with the entire right side.  In computer programming, any value besides zero is considered 'TRUE'.  Thus, the result of:
A || B
is simply '1', because '||' is simply a logical OR, and at least one side of said OR is TRUE (in fact, both sides are).  Thus, the result of "A||B" is simply 'TRUE'.  So now the calculation becomes:
A = TRUE
which doesn't make a whole lot of sense, since 'A' is an 8 bit register.

...

So let's put it all together:
 DDRB |= ( 1 << DDB2 );
 -finds the index of pin16 (DDB2), which is '2'.
-shifts the number 1 (i.e. 0b00000001) '2' places to the left, resulting in the mask '0b00000100'
-bitwise ORs the port DDRB with the mask '0b00000100' (note that the other 7 pin values remain uncharged, hence why we created the mask in the first place).
-stores the result in DDRB

At the end of the day, that one line of code changes pin16 to an output.  Nothing more, nothing less.  See if you can fill up that taco a bit more before continuing.

...

Yum, tacos!

...

So now we've completed the conceptually simple task of changing pin16 to an output.  Now we need to set the output value.  First, let's make pin16 output a logic 1 (a.k.a 'HIGH', a.k.a. 5 volts on my board):
PORTB |= (1 << PORTB2)
Far fewer new concepts this time.  In fact, we're doing the exact same thing as the previous example, except that we're reading from a different variable and writing to a different hardware registers.  New concepts:
'PORTxn' is similar to 'DDxn' in the previous example.  On the 64M1, the two values are interchangeable, although this is bad practice, as larger uCs might not assign the same index to multiple hardware registers linked to the same hardware pin.  As before, 'PORTB2' is simply defined elsewhere in the hardware abstraction layer as '2'.  Humorously, Atmel confuses the two in their example program.  Great way to start someone out ;).

'PORTx' is an 8 bit register whose individual bits each represent one hardware pin.  When a given pin is configured as an output (as we did in the previous example), writing the corresponding pin HIGH causes the actual output voltage on that pin to go HIGH (i.e. 5 v on my board).  Likewise, writing the corresponding pin LOW causes the actual output voltage on that pin to go LOW.  On the other hand, if the pin is configured as an input, writing a '0' to a specific hardware pin enables an internal pullup resistor*, whereas a '1' disables said pullup, causing the pin voltage to float (i.e. tristate).

*The 64M1 has a single bit "PUD" in the MCUCR register that must also be set low to allow any pin to enable it's internal pullup.  I'm glossing over this for now.

So now we've configured pin16 as an output and made the output HIGH.  It should be simple to make pin16's output LOW, right?  Let's see:
PORTB &= ~(1<< PORTB2)

Hmm... the names are the same, but now we need to understand a few more Boolean algebra concepts:
(1<
<PORTB2) is the same as before: '0b00000100'
'~' bitwise negates the above into '0b111110100'

'&=' is similar to '|=', except that now we're bitwise ANDing PORTB with '0b11111011'
Thus, '0b11111011' is simply a mask that only changes the value of pin16.  Specifically:
-The goal was to make pin16 output low
-pin16 is at index 2 (i.e. the third bit from the right, because the first bit is zero indexed).
-index 2 is '0' in the mask (0b11111011)
-when we AND index 2 ('0') with the previous output value ('PORTB'), the result is always zero.
-when we AND each other index ('1') with the previous output value ('PORTB'), the result is always the previous value.
Thus, we've successfully changed pin16's output to LOW, without changing any other pin on the port.

...

Phew!

Now there's one last concept before we call it a day.  What if you want to change the state of two pins in the same port to LOW in the same operation?  Let's say we want to change both pin16 and pin23 to LOW.

Previously in our code, we must have defined these pins as outputs (remember: the default value is always input until reassigned).  We already know how to do this with pin16:
DDRB |= (1<
<DDB2);
Then we go back to the data sheet to find out pin23 is "PB3".  Thus, we could have individually assigned pin23 as an output in a similar fashion:
DDRB |= (1<< DDB3);

Perfectly valid to do them one at a time, but it takes fewer cycles to lump them all together and write the port register once:
DDRB |= ( 1<
< DDB2 | << DDB3);
Thus, our mask becomes '0b00001100'.  Then we bitwise OR it with the existing values and BOTH pin16 & pin23 become outputs in the same instruction.  Boom, it's that simple!

To complete our example, we just need to make both pin outputs LOW:
PORTB &= ~( 1<< DDB2 | << DDB3);

As before, we create a mask (i.e. everything inside the parenthesis): '0b00001100'
Then we negate the mask with '~': '0b11110011'
Then we bitwise AND the mask with the previous PORTB values, and presto(ish), we've set both pin16 and pin23 LOW, and it only took THIS ENTIRE POST to learn how to do it.

Isn't programming fun?

...

So obviously these are very low level tasks, and people can abstract them into higher level functions.  So why not use those?  It turns out Atmel's software abstraction layer is REALLY horrible, particularly because the software documentation is - shall we say - sparse.  Worse still, Atmel's Software Framework (ASF) attempts to unify every single product they sell into a single programming paradigm, which means abstractions are placed on top of abstractions, which are placed on top of abstractions, etc.  By the time we get to these so-called "easy to use" functions, they're so abstracted that it's really hard to understand what's going on at the hardware level.  I waded through hundreds of files, found a few bugs, wrote some horrible code, and then finally gave up, threw a virtual match in Atmel's ASF, and started from scratch.

I did keep their lowest-level abstraction layer, which tells the compiler which specific address belongs to "PORTB".  I then created a single-layer hardware abstraction that contains the concepts discussed in this post (and many more).  And while it took a little more time to figure it out, now I understand it, and the code base is simpler.  I've given up 'portability' - which is the concept that I could move an ASF project to a different processor with relative ease - but I don't plan on doing that.

That's all for now.