Adventures in Assembly -- Blinking LED Part 1

Assembly is fun...if you do it right.

There are a lot of benefits when writing assembly: smaller, faster code, direct access to hardware, constructs that are impossible to create in C, and others.

Assembly can also be hard. It is an "unstructured" language. The assembler has no idea what it is you are trying to do, and will not run interference for you. It will not tell you when your accessing a data type wrong, or if your accessing a portion of memory you're not supposed to. It'll just blindly assemble your code, mistakes and all, and generate a .hex file that may, or may not, work. Code testing and maintenance can become next to impossible -- not just for others, but even for yourself as the code grows over time.

Just like in any other language, code can be written badly. It's just easier in .asm to write bad code. But it is not hard to write good, manageable, maintainable code, either. As an .asm programmer for over 40 years, I've developed techniques making .asm authorship and maintenance easy.

I've always wanted to teach some of my techniques, and I have on the AAC threads to a limited extent, but much of what I do cannot be taught except through a "total project" approach. So, without further ado, I present my first .asm tutorial: Blinking LED Part 1.

The goal of this project is two-fold: First, to present the basic framework that I use for assembly programming, and second, to blink a couple of LEDs. This seems like where many new .asm programmers like to start.

The code is built around a PIC18F65K22 processor -- for no other reason than that is a chip I currently use and I have hardware available. I'll try to keep this -- and future code -- processor agnostic (within the PIC18 series) so you can use whatever hardware you have available. Or, in the case of no hardware, learn to use the simulator built into MPLAB. If using a different MCU, just modify the and files.

Speaking of MPLAB, the code presented will be built on MPLAB IDE 8.92, the final and aging release of MPLAB. I've yet to make the jump to MPLAB X for reasons I've discussed in the past.

I've included a .zip file with the entire project, including the MPLAB workspace files. This way you can hit the ground running. I am not going to include a schematic: the hardware for this project is simple: two LEDs -- one anode tied to RD2 and the other anode to RD3, and each cathode tied to ground through appropriate current limiting resistors.

When you unzip the files, you'll notice there are 16 (!) .inc and .asm files. Overkill for just blinking 2 LEDs, right? Well, yes, if that is all our code ever aspires to be. But I expect my code to grow, and I will use those files -- and add more -- to keep things organized and maintainable.

Here are the files:
  • project.asm -- this is the main file to be included in the project. It loads up all the other files during build and handles the reset vector to start program execution.
  • -- defines the processor, oscillator frequency, and configuration settings.
  • -- defines data types I use in the code. This helps me remember how to access them properly.
  • -- a standard set of macro I use to make PIC .asm code easier to write and understand.
  • -- this defines the port bits used by the application, as well as there initial power-on states.
  • -- TMR0 system timer definitions.
  • -- literal constants used in various locations of the program.
  • -- file registers used by the program.
  • -- definitions for various analog aspects of the MCU.
  • interrupts.asm -- handles the hi and low interrupt vectors as well as managing interrupt context save and restore as necessary.
  • gensub.asm -- generic subroutines used in the program
  • systim.asm -- TMR0 system timer.
  • init.asm -- program initialization code.
  • analog.asm -- analog hardware management code.
  • main.asm -- the main program loop
  • led.asm -- the LED "driver". This is the code that makes our LEDs blink.
I'm not going to detail all of the files -- they are mostly commented enough to be self-explanatory. Most importantly, I wish to address the system timer and the LED "driver".

System Timer

The system timer is a basic building block upon which I build all my applications -- without exception. It provides the "cooperative" multitasking ability of my code -- allowing many different devices to operate "simultaneously" and for hardware drivers to be written in a "non-blocking" fashion. Non-blocking means that the code never waits for a chunk of hardware to complete its job. Other tasks are run in the mean time. You will never see something like DelayMS(xx) in my code. Ever.

TMR0 is dedicated to the system timer. Always. I never use it for anything else. And I've never needed to. It is set to simply count up and rollover from the time the program starts to when the plug is pulled. Each rollover, the _timer register is incremented (via interrupt) and any bits that changed as a result are recorded in the _tmrchg register (The underscore indicates the the register is likely to change via interrupt asynchronously to the main program. This is a reminder that the value needs to be synced to the main loop before it is useful).

Each bit of the _timer register represents a period of time twice as long as the bit preceding it. In this application, the TMR0 prescaler is configured so as to rollover every 256µS with an 8 Mhz clock. Therefore, bit 0 of _timer changes state each 256µS. The timings can be seen in

The very first call from the main loop must be POLLTIM. POLLTIM synchronizes the asynchronous timer information into register timer and tmrchg. It also adds an extra byte to the timer chain allowing bit time periods up to 17 seconds.

The timer bits are always active, representing the count in _timer. The tmrchg bits are active for only one main loop after the change has actually occured. Therefore, tmrchg can be used to activate processes based on time via a simple bit test. Which leads into:

LED Driver

The file led.asm represents the LED "driver" code. This is the "heart" of our current application. It consists of 4 instructions:

    btfsc    tc131ms        ;toggle LED1 every 131ms
    btg    led1

    btfsc    tc524ms        ;toggle LED2 every 524ms
    btg    led2
That's it! All that is required to blink two LEDs, one with a period of about 1/4 second and the other with a period of about 1 second.

In part 2, we will discover how to blink the LEDs with an arbitrary period.

Comments and questions welcome!

Blog entry information

Last update


More entries in General

Share this entry