I recently bought a chumby one from Woot. It’s an extremely hacker-friendly device with a 454 MHz Freescale i.MX233 ARM processor, 3.5 inch touchscreen, USB port, accelerometer, speaker, internal USB wi-fi module, and an internal microSD card. It boots from the microSD card, so it’s pretty much un-brickable as long as you keep a backup of the original SD card contents.

It’s an awesomely cool device out of the box, but all of the GUI stuff is based on Flash. Now that’s wonderful and all, but I’m just not much of a Flash guy. I really like working with Qt on embedded devices, so I got a cross compiler up and running, allowing me to design stuff on my desktop computer (running Ubuntu 10.04) and deploy it onto the chumby. I have learned during my time as a developer to document what I did when I do things like this! Two years later it’s hard to remember exactly what I did. I learned this the hard way and now I always document a procedure like this as I’m going through it. I figured while I’m documenting it, I might as well share it with the world. These instructions will walk you through creating a modern cross compiler toolchain for the i.MX233 (compatible with the chumby’s libc), using that toolchain to compile Qt/Embedded 4.7.2, and finally, creating apps on your build machine and running them on the chumby.

The basics

Start out with an Ubuntu 10.04 (“Lucid Lynx”) installation. This procedure will probably work in newer and older versions, but I’m assuming you’re using 10.04. I’m going ahead and using an amd64 install of Ubuntu, but it should work fine in an i386 install as well. Once you have that installed (however you want to do it — directly on the computer, Wubi, in a virtual machine, or whatever other crazy install method you can conjure up), we’re ready to begin.

First of all, we need to install some prerequisite packages for various purposes. In a terminal window, type:

sudo apt-get install build-essential bison flex texinfo automake libtool cvs libncurses5-dev subversion

crosstool-NG

After getting all this stuff downloaded and installed, we’re ready to start creating the cross compiler. Download crosstool-NG (I used version 1.10.0) and unpack it somewhere. crosstool-NG is absolutely amazing. It saves so much trouble while creating cross compilers. You just go through a menuconfig interface similar to the Linux kernel’s menuconfig interface, telling it exactly what you want. If you’re lucky, you just tell it to work and it grabs all the source code, patches it, and compiles it automatically, leaving you with a fully-functional cross compiler after a half-hour or so. In this case, I have verified that the configuration I’m going to give you will work. In other cases, you may have to do some trial and error because some versions of C libraries and binutils don’t work with some versions of gcc. Generally you want to use tools that came from the same time period, as this will give you a better chance of everything working together. Anyway, go into the crosstool-ng directory in a terminal, and type:

./configure --local
make

The –local option tells crosstool that we will be running it directly out of the directory we unpacked it to. Otherwise it would install itself into /usr/local or somewhere like that. I prefer it this way rather than putting it into /usr/local. This should create a script called ct-ng. We will be using this script to configure and create the toolchain.

So let’s start setting up the toolchain! I’d just give you the config file, but I’d rather walk you through setting up crosstool so you better understand how it works. Type:

./ct-ng menuconfig

After a few things finish setting up, you’ll be greeted with graphical interface in the terminal. You can move up and down with the arrow keys. The enter key will go to the selected item. Hit the Esc key twice to go back. Let’s go through the sections:

Paths and misc options

It turns out I’m going to break my own rules here. I’m going to choose newer compilers and binutils with an older glibc. It happens to work in this case, so no harm done. I’m using an older glibc because the glibc on the chumby’s root file system is version 2.8, so I’d like our version to match. If we use a newer version, some binaries will not be compatible with the older glibc, so we’d have to replace chumby’s glibc, and I’d rather not.

Highlight Use obsolete features and press the space bar to select it. This will allow us to choose an older glibc that is probably not compatible. (We’re actually going to use eglibc, which plays better with our setup than glibc)

Scroll down and you’ll notice that Prefix directory is set to “${HOME}/x-tools/${CT_TARGET}. This means it’ll create a folder called x-tools in your home directory which will store all cross toolchains you create. I left this alone, but if you’d like it elsewhere, go ahead and change it now.

Keep scrolling down until you find Number of parallel jobs. This one will save you some time if you have a dual- or quad-core processor. While creating the toolchain, this will allow some of the files to be compiled concurrently, making better use of your multiple-core CPU. For you techies out there, it’s letting you specify the -j option passed to the make commands that will run. I generally set this to the number of cores I have available. Since my 4-core Core i7 has hyperthreading, I normally choose 8. In a VMware virtual machine where I have given the VM two cores to work with, I’d choose 2. Different people have different opinions on what’s the best value here, but I’d say stick with the number of cores you have available and you’ll be fine. If you pick too high of a value your entire system will slow to a halt, and if you pick too low of a value, you’ll be under-utilizing your CPU.

Okay, sorry about that long explanation. Now we’re ready to move to the next section. Hit Esc twice to go back to the menu. Move down to Target options and hit enter.

Target options

Highlight Target Architecture (alpha) and hit enter. Scroll down to arm and hit the space bar. We are telling crosstool-NG that we’re building a cross compiler that will target the ARM architecture, which is what the chumby uses.

chumby uses a Freescale i.MX233, which is an ARM926EJ-S. We need to tell crosstool which CPU we’re targeting, so scroll down to Emit assembly for CPU and hit enter. Type “arm926ej-s” (without the quotes) and hit enter.

Scroll down to Floating point and hit enter. Scroll down to software and hit the space bar. The i.MX233 does not have a hardware floating point unit, so we need to choose softfloat here (otherwise, the kernel emulates hardware floating point instructions, and it gets terrible performance).

We’re done here, so hit Esc twice, move down to Toolchain options, and hit enter.

Toolchain options

Our toolchain will be called “arm-chumby-linux-gnueabi”. The “chumby” part of this is called the vendor string, so we need to configure it as such. The vendor string makes no technical difference–it’s purely a cosmetic thing. Scroll down to Tuple’s vendor string, hit enter, backspace until you have erased “unknown”, type “chumby”, and press enter.

Exit this menu and go to the Operating System menu.

Operating System

Change Target OS to linux. You’ll notice that the kernel version is set at 2.6.37. It may not make a huge difference in some cases, but in this case, we need to pick a kernel version closer to the chumby’s kernel (2.6.28-chumby). Otherwise, the touchscreen library won’t work. We could probably get a tarball of the chumby’s kernel and be exact, but rather than bother with that, I set it at 2.6.27.57. Once you’ve made that change, exit this screen and go to Binary utilities.

Binary utilities

We’re not going to change anything here, but this is where you would change the version of binutils. Leave it at version 2.20.1.

Exit again and go to C compiler.

C compiler

Leave gcc at version 4.4.5. You need to scroll down under Additional supported languages to C++ and hit the space bar to make sure the toolchain is enabled for compiling C++. Now move down to C-library.

C-library

The C library is already set as eglibc, which is what we want. Change eglibc version to 2_8 (OBSOLETE). Normally this would be a bad idea to use an old version of eglibc combined with newer binutils and gcc, but we need version 2.8 in order to be compatible with the chumby’s provided glibc, and it works OK with the newer binutils and gcc. This is the sole reason we had to enable Use obsolete features when we were starting out. We also need to add an extra option here to fix a problem I encountered when I first tried to build this toolchain. Go to extra target CFLAGS and set it to “-U_FORTIFY_SOURCE”. Exit this screen and head over to Debug facilities.

Debug facilities

Go ahead and enable gdb (leaving it at version 6.8). We probably won’t use it, but it won’t hurt to have it. Leave this screen.

All done configuring

We don’t need to mess with anything else, so hit Esc twice and you will be asked if you want to save your new configuration. Hit enter, and you’re ready to build your toolchain!

Type:

./ct-ng build

crosstool-NG will update you on the status of your build as it keeps going on, but for now you can sit back and relax. If you’d like, skip ahead and start downloading some of the other stuff you will need to have as you progress further through these instructions. Depending on the speed of your computer, this compilation might take 15 minutes to an hour.

If all goes well, crosstool-NG will finish by saying “Finishing installation (may take a few seconds)…” followed by a shell prompt. At this point, your cross toolchain is complete! Assuming you left it to be installed at the default location, you can now compile programs for the chumby by using:
~/x-tools/arm-chumby-linux-gnueabi/bin/arm-chumby-linux-gnueabi-gcc

I made a quick test app:

chumbytest.c:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("It works!\n");
}

compiled it:

~/x-tools/arm-chumby-linux-gnueabi/bin/arm-chumby-linux-gnueabi-gcc chumbytest.c -o chumbytest

and transferred it to the chumby with scp to make sure it worked before advancing any further (assuming ssh has been enabled on the chumby):

scp chumbytest root@MyChumbysName.local:/tmp

to run it on the chumby over ssh:

ssh root@MyChumbysName.local
/tmp/chumbytest
exit

As long as it worked as expected, you’re ready to start working on getting Qt compiled. Well, kind of.

tslib

We need to do one prerequisite in order to get the touchscreen working, and it’s a library called tslib. Download it and extract it.

Start out inside the tslib directory and type:

./autogen.sh

Let’s go ahead and add our cross compiler to our path:

export PATH=~/x-tools/arm-chumby-linux-gnueabi/bin:$PATH

Now we’re ready to configure tslib to be cross compiled, and we’re going to tell it to install itself into a temporary directory inside our current directory. There are two hacks we have to do here–the first is we define a variable ac_cv_func_malloc_0_nonnull to be yes to avoid a compile error, and we also add -U_FORTIFY_SOURCE to the CFLAGS to avoid other errors, the same way we had to during the compilation of eglibc. Here’s the complete command:

ac_cv_func_malloc_0_nonnull=yes ./configure --prefix=$PWD/tslib --host=arm-chumby-linux-gnueabi CFLAGS=-U_FORTIFY_SOURCE

Compile it:

make -j 2 (or 4 or 8 or whatever, depending on how many cores you have)

Install it:

make install

Now there should be a tslib subdirectory inside the tslib directory, containing the compiled library, plugins, and a config file in etc. We really need to put the tslib library and include files into the appropriate place in our cross compiler’s sysroot so we can use it when compiling. Type:

sudo cp -R tslib/lib/* ~/x-tools/arm-chumby-linux-gnueabi/arm-chumby-linux-gnueabi/sysroot/usr/lib

to copy all the libraries into the cross compiler. We should also copy the include files in there:

sudo cp tslib/include/tslib.h ~/x-tools/arm-chumby-linux-gnueabi/arm-chumby-linux-gnueabi/sysroot/usr/include

This will simply make it easier in the future when compiling, so we don’t have to specify where the libraries are located. Now, let’s get the touchscreen working on the chumby before we compile Qt. Transfer the compiled tslib subdirectory to the chumby’s storage:

tar -cf - tslib | ssh root@MyChumbysName.local tar -xf - -C /mnt/storage

Now we need to get a shell into the chumby, quit the chumby control panel, and set up a few environment variables so we can calibrate the touchscreen:

ssh root@MyChumbysName.local
/usr/chumby/scripts/stop_control_panel
export LD_LIBRARY_PATH=/mnt/storage/tslib/lib:$LD_LIBRARY_PATH
export TSLIB_TSDEVICE=/dev/input/by-id/soc-noserial-event-ts
export TSLIB_CALIBFILE=/mnt/storage/tslib/etc/pointercal
export TSLIB_CONFFILE=/mnt/storage/tslib/etc/ts.conf
export TSLIB_PLUGINDIR=/mnt/storage/tslib/lib/ts

Edit /mnt/storage/tslib/etc/ts.conf and uncomment the “module_raw input” line.

You’re now ready to calibrate the touchscreen! Type:

/mnt/storage/tslib/bin/ts_calibrate

If all goes well, you should see “TSLIB calibration utility” and “Touch crosshair to calibrate” appear on the screen. Touch the crosshair for each corner and then the center, and the app will exit (the screen will stay the same, though). Now you’re calibrated! Test it by typing:

/mnt/storage/tslib/bin/ts_test

You should be able to drag the crosshair around on the screen. Hit ctrl-C to exit.

OK, so your touchscreen setup is now complete. You know all of those export commands you just typed before calibrating? You may want to put them into a file somewhere so they can be sourced whenever you need to run them. Whenever you want to use the touchscreen, those environment variables need to be set that way. We are now finally ready to compile Qt.

Qt

Here we go! The exciting part has arrived. We are ready to compile Qt. First, you need to download the latest source code for Qt. Pick the LGPL version unless you have a commercial license. Note that these instructions are based on version 4.7.2. Once you’ve downloaded the tarball, extract it and enter its directory. We now need to set up a Qt spec for the chumby.

Spec

Inside the mkspecs directory is a list of other directories containing specs for many different architectures. There is also a directory called “qws”. We want to use a qws spec, because our Qt will be drawing directly to the frame buffer device without having to have a window server like X11 installed. Instead, Qt will be its own window server. In the qws directory, there is a spec called linux-arm-gnueabi-g++. This one is very similar to the toolchain we are using, so let’s go ahead and duplicate it:

cd mkspecs/qws
cp -R linux-arm-gnueabi-g++ linux-arm-chumby-gnueabi-g++

Now, you need to edit the qmake.conf file inside of our new linux-arm-chumby-gnueabi-g++ directory. You will see several references to tools prefixed with “arm-none-linux-gnueabi-“. You should replace this prefix with the prefix for your toolchain. In fact, I would put the full path to each of these tools. For example, change arm-none-linux-gnueabi-gcc to /home/yourname/x-tools/arm-chumby-linux-gnueabi-gcc, and so on. It may seem stupid because you could always add the directory to your path, but when we’re executing the cross compiler from inside of Qt Creator, it will be easier if the full path is already there. Once you’re done with this, save the file and we’re ready to go!

Compiling

Here is the complete command, along with descriptions of what every option does below it:

./configure -opensource -embedded arm -xplatform qws/linux-arm-chumby-gnueabi-g++ -no-feature-cursor -qt-mouse-tslib -nomake examples -nomake demos

-opensource: specify that we’re using the LGPL. If you have a commercial license you should change this.
-embedded arm: specify that we’re compiling Qt/Embedded for ARM.
-xplatform qws/linux-arm-chumby-gnueabi-g++: specify the make spec that will be used for compiling. This tells Qt where our cross compiler is, and info about the cross compiled system.
-no-feature-cursor: hides the cursor, since it doesn’t make much sense to have one with a touchscreen. If you want a cursor, you can remove this option.
-qt-mouse-tslib: tells Qt to use tslib for its mouse driver
-nomake examples: Tells Qt to skip the examples. I already know how to use Qt so I don’t need a bunch of examples compiled. If you want them, keep it there.
-nomake demos: Same as above.

There are other options you can use to turn off specific features. If you don’t envision ever using WebKit, you can turn it off (-no-webkit). Anyway, type the command above, and you’ll have to agree to the open source license. Once that’s done, it’ll take a few minutes to configure the build. When the configure is complete, type:

make -j 2 (or whatever number of cores you have available)

Once that’s done (and it’ll be a while, trust me on this one!) you can install the libraries by typing:

sudo make install

The reason you need sudo is that Qt defaults to installing in /usr/local/Trolltech. You can change this with the -prefix option on the configure script, but I don’t mind Qt residing there. Once that’s done, you should have a functional Qt. Send the Qt libraries over to the chumby:

cd /usr/local/Trolltech/QtEmbedded-4.7.2-arm/
tar -cf - lib | ssh root@MyChumbysName.local tar -xf - -C /mnt/storage

And now Qt should be on the chumby’s SD card, with its libraries stored in /mnt/storage/lib. We can’t test it until we make a quick Qt app, though, so let’s install the Qt SDK.

Qt SDK

The Qt SDK is a massive download, currently in beta, but it works just fine. We’re going to install it and use it to create a test app to make sure Qt works. First, download the SDK from the Qt download site. I downloaded the Qt SDK 1.1 Beta online installer for Linux 64-bit from here (since my development machine is 64-bit). Make it executable and run it:

chmod +x Qt_SDK_Lin64_online_v1_1_beta_en.run
./Qt_SDK_Lin64_online_v1_1_beta_en.run

At this point, you’ll need to wait for it to finish “Retrieving information from remote installation sources…”. Once that’s done, accept the defaults, allow it to install in ~/QtSDK or wherever else you want it, and eventually you’ll have to wait for it to finish downloading and installing the SDK. Once it’s done, go ahead and allow it to Launch Qt Creator, but uncheck Open Qt SDK ReadMe.

After a few moments, Qt Creator will pop up. We now need to add our cross toolchain so that Qt Creator knows about it.

Adding the cross toolchain

Click on Tools, and choose Options. On the left, click Qt4. You should see a list of available Qt versions. We are going to add a new one, so click the blue + on the right side of the window. Where it says <specify a name>, type “Chumby Qt”, and where it says <specify a qmake location>, click Browse… and navigate to your cross compiled Qt’s qmake binary (/usr/local/Trolltech/QtEmbedded-4.7.2-arm/bin/qmake). Below, it should say “Found Qt version 4.7.2, using mkspec qws/linux-arm-chumby-gnueabi-g++ (Desktop)”. It’s inaccurate in claiming that this is a desktop target, but it’s ok — it still works. Click OK to save your new Qt version.

Creating a test app

I didn’t want to turn this into a tutorial on how to use Qt, but I at least want to walk you through creating a basic app, cross compiling it, getting it onto the chumby, and running it. Click on File and choose New File or Project…. Leave Qt Gui Application highlighted and click Choose…. Name your project TestChumbyQt and save it wherever you’d like. When it asks you to set up targets, uncheck all the targets except for the Chumby Qt target we created earlier. Click Next and leave the QMainWindow class name alone (MainWindow is fine for this example). Accept all the default choices.

Now we are ready to create the test app. Expand the Forms folder on the left and open mainwindow.ui. Click on the background of the newly-created window. Next, on the right side, there should be a “geometry” property. Click the + to expand it. Change width to 200 and height to 150. This should shrink the window a bit. On the left, drag a push button onto the main window. You can resize it to make it easier to press if you’d like.

Compiling the test app

Ok, we’re ready to compile the app. Go to Build and choose Build Project “TestChumbyQt”. If it asks you to save, click Save All. You should see a progress bar on the left side and eventually it will turn green, meaning the compile succeeded.

We’re ready to go. I put my test app in ~/Documents, so your command may be slightly different from mine:

scp ~/Documents/TestChumbyQt-build-desktop/TestChumbyQt root@MyChumbysName.local:/mnt/storage

Environment variables

Now, we need to define a few environment variables in order for Qt to work. You will need to do all of the exports we did above when we were testing out tslib, so if you still have that shell open you can skip doing those again. I’m listing them again just in case, though, plus some extras you need to do regardless.

export LD_LIBRARY_PATH=/mnt/storage/tslib/lib:$LD_LIBRARY_PATH
export TSLIB_TSDEVICE=/dev/input/by-id/soc-noserial-event-ts
export TSLIB_CALIBFILE=/mnt/storage/tslib/etc/pointercal
export TSLIB_CONFFILE=/mnt/storage/tslib/etc/ts.conf
export TSLIB_PLUGINDIR=/mnt/storage/tslib/lib/ts

And the ones you need to do:

export QWS_DISPLAY="LinuxFb:mmWidth80:mmHeight52"
export QT_QWS_FONTDIR=/mnt/storage/lib/fonts
export QWS_MOUSE_PROTO=tslib

The QWS_DISPLAY variable tells Qt about the physical dimensions of the chumby’s screen in millimeters. I did a very crude measurement with a ruler. If you don’t specify it, all the text will appear tiny because we’re working on a small screen. QT_QWS_FONTDIR tells Qt where to look for its fonts. Because it thinks it’s stored in /usr/local/Trolltech, we need to specify this so it knows where it should actually look. Finally, QWS_MOUSE_PROTO tells Qt to use tslib rather than the generic Linux input system.

As I said earlier, you will want to make a file you can source that includes all of these environment variable definitions — they are necessary in order to start a Qt app. Notice I didn’t add /mnt/storage/lib (where we put the Qt libraries) to LD_LIBRARY_PATH — the reason I didn’t is because the chumby already has that directory in its pre-supplied LD_LIBRARY_PATH, so I didn’t want to put it in there twice.

Running the test app

We’re ready to go now. In an SSH session to the chumby, type:

/mnt/storage/TestChumbyQt -qws

(-qws tells TestChumbyQt to be a window server. If you run other apps while it’s running, you don’t want to pass -qws to them–only one app should be the window server.) You may see an error about being unable to open /etc/pointercal, but that’s okay–our environment variable tells it to look elsewhere anyway. If all goes well, your window should appear on the chumby’s screen. Try pressing the button and dragging the window around. Everything should work! You can quit the app by pressing control-C in the terminal, or tapping the X in the upper right corner of the window.

Congratulations, you now have Qt working on the chumby. There are ways to get a Qt app to run at bootup now rather than the flash-based GUI, but that’s beyond the scope of this document. All that’s important is now you know how to run Qt apps.

All done

Unfortunately, I just don’t have the bandwidth necessary to host any binaries or source code for this. However, I have (hopefully) provided you with all the info you’ll need to create the libraries yourself. I have not yet figured out how to make a Windows-based toolchain for this — I know how to create the ARM cross compiler for Windows, but I just don’t know how to get Qt to compile inside of Windows. If I ever discover how, I’ll make another post about it. So for now, you will need a Linux environment for your development.

I know some of what I did was a little crude — I should have specified the ultimate destination directory for tslib when I configured it with –prefix, and I also probably could have specified in the configure script some of the paths that I had to redefine with environment variables, but this works for now. There’s nothing special about /mnt/storage, by the way — I just picked it because it was easy to put stuff there. It might be better suited for a special directory in /usr or something along those lines.

If you run into any problems, feel free to leave a comment and I’ll try to figure out what’s wrong. Enjoy!

Wow! It has been a while. I finally decided to write another one of these. I promised a long time ago to write about timers. Well, almost 4 months later, here we go!

Timers are extremely useful peripherals often built into microcontrollers. Are you going to be doing anything that involves timing or delays in your microcontroller program? Are you going to:

  • Increment a variable every second?
  • Measure the length of time a button is pressed down?
  • Wait for 20 milliseconds?
  • Blink a light every half second?

If you are doing any of the above, or anything even remotely related, you are probably going to be using a timer. A lot of desktop frameworks have a similar kind of setup — Qt, for example, has the QTimer class which allows you to schedule something to happen in the future. .NET has a Timer class that does the same thing. And as you can guess, Mac OS X’s Foundation framework has the NSTimer class. These tend to use lower-level operating system routines, which in turn use hardware timers, and they will run a handler inside your event loop when they fire. You don’t have to know how to use the hardware timers though–the operating system handles it for you. Well, in a microcontroller, you will directly use a timer peripheral to do similar tasks.

Timers can generally be put into several different modes. Some microcontrollers have more complex and powerful timers than others. I’m only going to focus on basic timer concepts here. The first thing you need to know is that timers are usually memory-mapped peripherals built into the microcontroller. They have various configuration registers you can access. The most important register is the counter register. As you can guess, it counts. It holds the timer’s current value, and while the timer is running, it is constantly being incremented or decremented, based on what direction the timer counts. Some timers count up, some timers count down. Some even allow you to set which direction it counts. It doesn’t really matter what direction a timer counts, because you can do all the same things with it either way.

Anyway, so I said this counter register counts up or down, depending on the timer. Let’s pretend that it counts up, for the sake of explaining how this timer works. So the counter starts at zero, and then after a certain amount of time, it will be incremented to 1, and then 2, and so on. How often is it incremented? This is the key to how timers work! Generally the timer runs at a fraction of the system clock rate. So if your microcontroller is running at 100 MHz, the timer might run at 25 MHz. In many microcontrollers, this is configurable. You might be able to set the timer to also run at 100 MHz, or 50 MHz, or 12.5 MHz. Let’s assume that our microcontroller is running at 100 MHz and its timer runs at 25 MHz. This means that in one second, it will be incremented 25 million times. That’s a lot! Alternatively, we could say the counter will be incremented every 1/25,000,000 seconds, or every 40 nanoseconds.

My example here was a little crazy. It’s very doubtful that you need resolution that high. Generally timers, particularly fast ones such as the 25 MHz one we are using here as an example, also have a divider. The divider does exactly what it sounds like — it divides. It does it like this — it has a divider counter register which counts up every time the timer would count. After the divider counter gets high enough, the main timer counter register is finally incremented and the divider counter register is reset to zero. What this does in effect is makes the timer count slower. In this case, let’s say our divider is 25. This means that every 25 ticks of the 25 MHz timer clock, our final counter register is incremented, effectively making it into a 1 MHz clock instead of a 25 MHz clock. It’s just a nice way to slow down the counting. The value the divider will count up to is a value configurable in a register.

So let’s say we’ve configured the timer as follows: the CPU is at 100 MHz, the timer is at 25 MHz, and the timer’s divider is set to 25. Thus, the timer’s counter register is incremented at a rate of 1 MHz, or 1 million times per second. I like making my timers count at nice, even intervals like this because it makes it easier to do the math in your head to figure out how many counts it will take for a certain amount of time to pass. You can do it however you’d like, though!

The only other thing I really need to cover before we jump into the basics is: how to tell the timer to start counting. Timers generally have a configuration register of some kind, and one of the bits in it tells the timer to start counting. For this timer peripheral, let’s say if bit 0 of the timer config register is a 1, it is counting, and if it’s a zero, it’s not counting.

If the timer is configured this way, you now can do some very basic timing. Let’s say all your program does is turn on an LED for a second, then turn it off for a second, and repeat the process. So for our example, we have an LED connected to PORT A, pin 0. Here’s some sample code (keep in mind that TIMER_CONFIG, TIMER_DIVISOR, TIMER_DIVISOR_COUNTER, and TIMER_COUNTER are the registers for the timer):

// Set port A, pin 0 as an output.
DDRA |= 0x01;
// Turn it off.
PORTA &= ~0x01;

// Make sure the timer is turned off
TIMER_CONFIG &= ~0x01;

// Configure the timer to have a divisor of 25, and reset all the counters.
TIMER_DIVISOR = 25;
TIMER_DIVISOR_COUNTER = 0;
TIMER_COUNTER = 0;

// Start the timer (bit 0 of the timer flag register turns it on, in our example)
TIMER_CONFIG |= 0x01;

// Initialize this variable -- it will contain the time we last did something.
uint32_t lastTimeValue = TIMER_COUNTER;

// Do this forever...
while (1)
{
    // Look at the counter's value now.
    uint32_t nowTimeValue = TIMER_COUNTER;

    // Has it been 1 million ticks since the last time we toggled the LED?
    // (1 million ticks = 1 second)
    if ((nowTimeValue - lastTimeValue) >= 1000000)
    {
        // Toggle pin 0 of port A (^ is the XOR operator,
        // think about how it works!)
        PORTA ^= 0x01;

        // Update our "last time" variable to contain this time.
        lastTimeValue = nowTimeValue;
    }
}

Okay, did you follow along? This program will do nothing except toggle that LED on and off. It will toggle it every second. A couple of notes: the data type uint32_t is brought in by including the header file stdint.h. It’s just a portable way of specifying that it’s a 32-bit unsigned value. I could have used unsigned long instead, but I like using uint8_t, uint16_t and uint32_t (and their signed equivalents — int8_t, int16_t, and int32_t) because they make it obvious how big each variable is.

Also, the XOR operator (^) will toggle bits. Remember that PORTA ^= 0x01 is equivalent to: PORTA = PORTA ^ 0x01. This will turn bit 0 on if it’s off, and off if it’s on.

So this works and is fine and dandy, but it takes up the entire main loop of the program! Well, you can put other things to do in the main loop, but if you do it that way, you may not toggle the LED after exactly 1 second, especially depending on what other CPU-intensive things you’re doing in the main loop. This may not matter for something as simple as a blinking light, but for other tasks it could be very important. If you need more precise timing, you need to set the timer to use interrupts instead.

Most timers support a mode where it’s constantly checking the counter to see if it matches another value you specify, and once the counter matches it, it interrupts. This would be the mechanism you would use to do a precise timing. In this case, you have another register called the match value register. You would set your match value register to the value you want the counter to match — 1000000 in this case. What happens after this may depend on the timer. Some timers may have an option to automatically reset the counter back down to zero (or another specified value) after it reaches the match value. In other timers, you might have to adjust the match value instead. I’ll cover both methods so you see what I mean. Assume that the timers and GPIO have been configured as I had them before.

Program 1 (assuming the timer automatically resets the counter back to zero whenever a match occurs):

int main(void)
{
    // All the previous configuration stuff goes here,
    // setting up the GPIO and timer (but don't start the timer yet...)

    // Set the timer match value to 1000000, so it interrupts every 1 second.
    TIMER_MATCH = 1000000;

    // Start the timer
    TIMER_CONFIG |= 0x01;

    // Now our main loop literally does nothing!
    while (1)
    {
    }
}

// This is where the interrupt will jump to -- may need to specify
// to your compiler that it is an interrupt handler.

void TIMER_INT_HANDLER(void)
{
   PORTA ^= 0x01;
}

I may have to explain TIMER_INT_HANDLER to you. This is heavily microcontroller-dependent, but basically you need a way of specifying to the microcontroller what to do when an interrupt occurs. You do this by creating an interrupt handler, and then the address of it gets put into a table of addresses to jump to when a specific type of interrupt occurs (called a vector table). For now, just pretend that TIMER_INT_HANDLER has been properly set up as an interrupt handler for the timer peripheral. I’ll cover that later.

This is a lot simpler! You can put all kinds of other stuff in the main loop, and that interrupt will occur at almost precisely every second. It may vary just a little based on if you have disabled interrupts anywhere in your main loop for interrupt safety (as I mentioned in my last post on interrupts).

Now, here’s another code sample for if your microcontroller does not automatically reset your timer’s counter back to zero after a match occurs:

Program 2 (assuming the timer does *not* reset the counter back to zero when a match occurs):

int main(void)
{
    // All the previous configuration stuff goes here,
    // setting up the GPIO and timer (but don't start the timer yet...)

    // Set the timer match value to 1000000, so it interrupts every 1 second.
    TIMER_MATCH = 1000000;

    // Start the timer
    TIMER_CONFIG |= 0x01;

    // Now our main loop literally does nothing!
    while (1)
    {
    }
}

// This is where the interrupt will jump to -- may need to specify
// to your compiler that it is an interrupt handler.

void TIMER_INT_HANDLER(void)
{
    TIMER_MATCH += 1000000;
    PORTA ^= 0x01;
}

The difference is in TIMER_INT_HANDLER. I added 1,000,000 to the match value, so now it will match again 1,000,000 ticks (1 second) after the last match occurred. In this case, the counter register continues counting past 1,000,000, so by moving the match value up another 1,000,000, it will catch the counter when it gets to 2,000,000. Get it? It’s nothing really difficult, but the first option I showed earlier is easier if your timer peripheral automatically resets the counter back down to zero after a match. So if you have to do it this way, this is how you do it–otherwise, I’d recommend the first approach. You may be asking me: in this method, why didn’t you just set TIMER_COUNTER to zero instead of add 1,000,000 to TIMER_MATCH? Wouldn’t that have the same effect? You may think that it would, but it actually does not. Here’s why:

Between when the timer signals that a match occurred and we actually get to execute the interrupt handler, some time passes by. If the timer itself resets the counter back to zero, this is no problem, because it can reset the counter instantly as soon as the match happens. But if we try to do it ourselves manually, we will only reset it back to zero after so much time has already passed. In other words, the counter might actually be up to 1,000,002 or so before we tell it to go back to zero. In effect, this causes us to toggle the LED every 1 second PLUS however long on average it takes to get to that first instruction that resets the counter. By adding 1,000,000 to the TIMER_MATCH register instead, it guarantees that the next match will occur exactly 1 second after the last match occurred. This may not matter for something as simple as toggling an LED, but if you add up that extra delay over time in a very time-sensitive algorithm, it would cause inaccuracy.

Note that at one point we will overflow the 32-bit TIMER_MATCH register. When the match register gets up to 4294000000, the next time we add 1000000 to it, we will get 4295000000, which is larger than the maximum 32-bit integer (4294967295). That’s okay — it will wrap around and work correctly. The C standard guarantees that unsigned integers will wrap around correctly — e.g. if you add 1 to 0xFFFFFFFF, you will get 0. So in this case, when we add 1000000 to 4294000000, we will end up with a match value of 32704 (4295000000 % 4294967296), and all will be well with the world (the timer counter itself will also overflow correctly back up to 0, and match when it reaches 32704, exactly 1000000 higher than 4294000000 after wrapping).

These are the two simplest ways to use timers. Timers also have all kinds of other crazy features; I described the most common way they are used. I hope I didn’t make that too confusing. It was a lot to digest, but basically, here’s a summary:

  1. Timers are memory-mapped peripherals, and you know what their clock rate is based on what you read in the microcontroller’s data sheet and how you configure it.
  2. Timers have divisors, so you know exactly how long it takes between increments (or decrements) of the timer’s counter.
  3. You can use that information alone to do time-based stuff, but if you want maximum accuracy you should use an interrupt.
  4. To use an interrupt, you set a match value and the timer will interrupt whenever it reaches the match value.
  5. Some microcontrollers will allow you to automatically reset the counter whenever the match value is reached, which is handy for periodic tasks.
  6. If your microcontroller doesn’t automatically reset the counter after a match, you should update the match value instead of resetting the counter value on your own, or your interval between interrupts will be slightly longer than it should be.

Ok, that’s enough for today. I’ll talk more about interrupts and interrupt handlers next time. Have fun!