This page was last updated on March 13th, 2020(UTC) and it is currently March 21st, 2023(UTC).
That means this page is 3 years, 7 days, 21 hours, 7 minutes and 6 seconds old. Please keep that in mind.
20 - Sample of MCU Assembly
Hopefully, archive.org never goes away. Reason, go to page 3 at the other end of this link. What you're reading is an actual manual for an actual device meant to function as a CPU (ATMega 328P-PU, and older versions that aren't as cool). Mind you, at the time of writing this, it's less than two US dollars, while a "high end CPU" made by, say, Intel is over one thousand US dollars. Focus on the 28 PDIP (28 pins, Plastic Dual-Inline Package), since that's what I can get my hands on so cheaply. It's mean to be cheap. I thought about going right into coding here, but changed my mind, knowing that this thing probably isn't as easy to get if you're reading this in 2038. You should immediately notice that there are multiple definitions for each pin. Why? Because the programmer can change which function those pins have depending on which registers hold what values. Something like this could realstically do pretty much any task you want to hook up to a random gizmo, such as locking doors via the internet.
It has 32 kilobytes (or 32768 bytes or 262144 bits, since we use 1024 instead of 1000 for upscaling with bytes, since powers of two as much, much nicer to work with) of EEPROM to program, and it has 2 kilobytes of RAM, and 1KB of EEPROM for general memory saving. It has built in support for a few serial protocols, a few analogue to digital converters (look up what analogue vs digital is to save me the time of explaining), all at about 16 megahertz. To program the thing, you would set certain lines to certain values, then send data over serial connection and then it gets written to the EEPROM designed for programs. Usually you just use the onboard "boot loader" which is a program that many people put in the EEPROM before selling it to you, which allows for easy USB connection, instead of trying to get those pins set to what you want them to be.
So, what's the point? Well, notice there's more than one CPU with the same specs. The way these are built in reality, is that there are certain components that you're guaranteed to have. They also come with hardware that obscures the fact that there's a "pipeline." The idea of the pipeline, is that there are different steps to the process (getting info from RAM, executing instruction, storing the result, etc), and if you separate the components, you can make things alot faster by hooking them all up to the clock, having special conditions for when certain components "freeze" while the wait for the next component to finish doing what it was doing (for example, if the CPU is doing a divide operation, which usually takes longer than a single clock cycle), and just have each step doing it's own thing while other steps are doing their own things. This is made easier when you use firmware (hardcoded software) that translates the instructions. Many CPUs today don't actually directly understand the code they're supposed to. Usually, they have internal "code" that is more efficient than the code you write. While it's executing certain instructions, it's pulling in more code from wherever the program code comes from, the next stage is taking that and translating it, the next stage is storing it in a "cache" and any time code refers to itself, it might still be in the cache, which makes things execute faster.
But, you don't have to learn how it works. It more or less helps to know that it's there to help you, that it makes things faster, but you don't want to assume it can make miracles happen: you still have to write efficient code. We're constantly trying to figure out how to squeeze more out of our little chips. Thanks to abstraction, though, you can reap the benefits of the advances without having to think about them. However, if you figure out the strengths and weaknesses of the pipeline of the machine you're trying to speed up, it's helpful. It's also useful to note that a given processor is meant to react to instructions of it's "family." So, when i do x86 assembly, there's usually backwards compatibility. Some of the code i write in assembly for x86 now will still work in 2038. In fact, when I have you writing code, later, you'll find I'm relying on that idea. Below is some actual sample code for this processor. It's OK if you don't understand it, for it wasn't meant for anyone other than myself, originally.
.include "constants.inc"
;+--------------------------------------------------------------------------+
;| IVT |
;+--------------------------------------------------------------------------+
jmp init ;RESET
jmp default ;INT0
jmp default ;INT1
jmp default ;PCINT0
jmp default ;PCINT1
jmp default ;PCINT2
jmp default ;WDT
jmp default ;TIMER2 COMPA
jmp default ;TIMER2 COMPB
jmp default ;TIMER2 OVF
jmp default ;TIMER1 CAPT
jmp default ;TIMER1 COMPA
jmp default ;TIMER1 COMPB
jmp TIMER1_OVF ;TIMER1 OVF
jmp default ;TIMER0 COMPA
jmp default ;TIMER0 COMPB
jmp default ;TIMER0 OVF
jmp default ;SPI
jmp default ;USART RX Complete
jmp default ;USART empty
jmp default ;USART TX Complete
jmp default ;ADC complete
jmp default ;EE Ready
jmp default ;Analogue Comp
jmp default ;TWI
jmp default ;SPM Ready
;+--------------------------------------------------------------------------+
;| Default Handler |
;+--------------------------------------------------------------------------+
default: reti ;Return from interrupt after doing nothing
;+--------------------------------------------------------------------------+
;| init |
;+--------------------------------------------------------------------------+
init: eor r1, r1
ldi r28, 0xFF
ldi r29, 0x08
out SREG, r1 ;Clear status register
out SPL, r28 ;Initialize stack
out SPH, r29
sbi DDRB, 5 ;Enable pin toggle
call main
1: ;cli ;Disable interrupts, incase main enabled them
jmp 1b
main: ;Setup timer for interrupt
ldi r31, 0x01
sts TIMSK1, r31
ldi r31, 0x00
sts TCCR1A, r31
ldi r31, 0x02 ;How many FPS?
sts TCCR1B, r31
ldi r31, 0x01
out TIFR1, r31
sei
1: ldi r24, lo8(30)
ldi r25, hi8(30) ;Not really supported
call DELAY
in r31, PORTB
ldi r30, 0x20
eor r31, r30
out PORTB, r31
jmp 1b
ret
DELAY: call FLIP
sbiw r24, 1
brne DELAY
ret
FLIP: ldi r31, 1
out SMCR, r31
sleep
ret
TIMER1_OVF: ;Switch back to default?
reti
What's it do? Well, the first bit is an include line that creates aliases for numbers that are easier to remember (like pi so you don't have to remember 3.14159). Next is the "interrupt vector table." In other words, the first so many bytes of the code is the code that gets called when interrupts occur. These jumps then go to the itnerrupt handlers, which are almost all set to jump to "default" which basically ignores the interrupts. You'll notice a format change, but that was because these were originally two separate files: the top bit being for an abstraction layer for "boiler plate code." It sets up a "default environment" that I wanted to be able to use. Then it "calls" (basically, jumps while leaving us a way to "return") main, then when main is done, it attmepts to freeze. Main then enables the timer interrupt and the timer hardware. Then it goes into a "loop" where there's a delay, function called at the beginning, then some code to turn on the "status LED" on or off after each delay is over. The delay function subtracts a number and calls the "flip" function in a loop that ends with the r24:r25 register pair hitting 0. Flip puts the thing into sleep mode until the interrupt for the timer happens. The bottom is just me remarking that I should've just used the default handler, but I didn't ever get around to fixing it or finding a better way to do this that actually used that handler.
Now, you'll notice there's alot of code there, and most of it actually doesn't end up accomplishing any extra-ordinary goals. This is the boilerplate stuff, and this is one of the things that creates "magic box" mentality in new coders, which leads to things like "the cargo cult," where people add code they don't understand to a program because they think they need to, when they don't. Boiler plate code has a need, but often times explaining the purpose of everything is overwhelming. Look how much you went through to understand that program, and you didn't even see all the cargo. One of my goals is to help you avoid the cult of cargo, because it can also lead to some serious bugs and vulnerabilities. The challenge is maintaining a balance between the usefulness of abstraction, and the wizardry of knowing precisely what everything does, for there are obviously times where the abstraction without details is good (when trying to digest information you don't care about) and there are times this lack of information is bad (when it leads to cargo cult or improper usage or unrealistic expectations of outcome).
Get your own web kitty here!
©Copyright 2010-2023. All rights reserved.