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.