So, the first order of business is explaining the simple program. Firstly, we have an "include statement." These are common in most "programming languages," and the objective is to take code written from another file and insert it into the including file at that location. This is a form of "pre-processing." That is to say, code that gets executed by the assembler/compiler, not the target machine. Pre-processor instructions are usually specific to the tools themselves, not the platform. So, what pre-processor commands work with avr-gcc, might not work for any other tools that compile for AVRs. Almost all pre-processing commands have a "." as a prefix, to make it easier to identify. The exceptions to this rule is when you make "macro instructions," as those are given names that you give them, thus probably won't have the prefix. We're not going to use macros, so, no worries there.

The next line with code on it is one that begins with "main:" which is a label. Labels come in many forms, but they generally represent numbers. In a way, they could be considered pre-processor commands, as well, since they're not executed on the target machine, but technically they're processed as part of the language. This type of label represents the location of the next instruction. In this case, "ret."

Also, on the same line, is a "comment." These are ignored by the tools, and exist to help people clarify things. Anything after the semi-colon is ignored.

The "ret" instruction is paired with a "call" instruction. To understand this, you must understand a "stack" structure. Imagine seein a bunch of paper plates laying on a table. You can write a number on one, then place it on a pile you call a "stack." You can write a number on a second plate, and place it on the stack. Now, when you take a plate off the stack, it will have written on it the last number you wrote, and taking off another plate would have the first number you wrote. So, for example, if you wrote 1, placed that on the stack. Then 2, then placed that on the stack. Then 3, and then placed that on the stack. Taking the plates, one by one, off the stack will yield 3, 2, and then 1.

Now, consider a "call." The call writes the location of the next instruction and places it on the stack, then it jumps to the location to the location specified in the instruction. Usually, something gets done, then a "ret" (return) instruction is issued. This takes that number off the stack and then jumps to it. This allows people to make abstractions in code (which you'll see a practical example of in a later lesson, when you open up the "serial_to_human.inc" file). So, in "boilerplate.inc," the IVT and such exists, and there's a "wrapper" that first calls "main" once, and then later repeatedly calls "LOOP." So, in this case, a section of code could be defined as beginning with "main:" and ending with "ret." In this case, then, we can essentially say that "the 'main' function does nothing." We will be putting it to use later, though.

Next we have another type of label. You define it with ".set," then give the name, then a comma separator, and then a value for that name to represent. This is to give you something to change to see things happen as a result of decisions that you make. For reasons to be explained later, "1" represents about 1/30th of a second. Therefore 2 is 2/30ths of a second. 1 second would be a value of 30. However, be aware, that setting a value of 30 would have 1 second of the LED on, followd by 1 second of it off, not it turning on every second.

The next bit we could then define as "the 'LOOP' function." The first line of which makes a call to "statusLED_toggle." It's a function that I wrote in another file that turns the status LED (the LED on B5) on if it's off, of it'll turn it off if it is already on. Simple, right? The actual method of how I do this will be explained, and it's actually only slightly more complicated.

The next order of operations is the "ldi" instruction. You use it to "load" a register with an "immediate" value. In this case, we're loading register 24 with the "low byte" of delay_frames. Then we're doing the same thing with 25. The reason? We want to "pass" this value to the following function. We can set a delay of any specified time, but we need a way of sending the length of time we want the wait to be to the DELAY function. We do this by following a common set of rules called an ABI or "Application Binary Interface." Commonly, you want to send 16 bits of data, but the AVRs registers can only hold 8 bits of data (some processors have larger registers). In order to do that, we need to send it as 1-byte halves. The "low" half is the right side. Therefore, since the default delay is 2 frames, r25 actually ends up with the value of 0. hi8 and lo8 are special preprocessor instructions to help us with separating 16bit numbers into 8bits when using decimal numbers (since hex would come with the form of 0xAABB where AA is "hi8(0xAABB)" and BB is "lo8(0xAABB)"). The maximum delay you can give, here, then would be about 36 minutes.

We then call the delay function, then return, which in turn the wrapper ends up calling LOOP again, over and over and over and over. If you remove (or "comment out") "call DELAY," you'll find that the blinks are too fast for you to ever see the LED turning off. Now, for an experiment, if you wanted a short strobe, you should be able to change it so it toggles on, for a period of time longer than it toggles off.

Get your own web kitty here!
┬ęCopyright 2010, 2011, 2012, 2013, 2014, 2017, 2018. All rights reserved.