Introduction: Timer Interrupts on the DP32
Timers! I'm excited to finally be covering timers on my favorite board, the DP32!
Remember way back when I wrote about external interrupts? If you're not familiar with interrupts and how they work, I recommend checking that tutorial out first. External interrupts are useful if you want an event outside the board (like a button press or a communication signal) to trigger some special code that needs to execute quickly, but what if you want that special code to run repeatedly after a very precise time?
Well where time is concerned, timers reign supreme!
In this tutorial, I'll show you how to set up a timer, and connect an interrupt to it so it'll run over and over again after a very specific amount of time.
Let's get started!
For more Instructables on building cheap robots, please check out the For Cheap Robots collection!
For more things that I've done, you can check out my profile page!
For more info from Digilent or the Digilent Makerspace, check out the Digilent blog!
While you're there, consider signing up for the Digilent newsletter!
Step 1: What You'll Need
For this project all you'll need is a DP32, or one of Digilent's other boards, like the WF32 or uC32.
I'll be using a DP32, but I'll point out what changes need to be made for the other boards as we go along, so keep an eye out for those.
Step 2: Time Has Come Today
So what are timers and how do they work?
The best analogy I have is to something called a "souzu" or "boar-scarer" from Japan. You might be familiar with their use in Japanese gardens, for scaring off animals that might tear up the garden or eat the plants*. It's a beautifully simple machine that operates using a reservoir, a weight, and a water source, and each of these parts is a perfect analogy for each of the components of a timer**.
*One was featured in Kill Bill, during The Bride's fight with O-Ren. I'm sorely tempted to post a link to that clip here, but it's a bit graphic so I won't. You can google it if you're curious.
** For those in the know, I couldn't manipulate the peripheral bus divider, PBDIV, through Arduino (likely because it's set by the bootloader), so I'm not going to cover any peripheral bus operations.
Step 3: Operation of a Souzu
A souzu operates by slowly filling an internal reservoir with a constant flow of water. Usually this is a trickle, but in our case we'll assume it's a steady drip.
As the water fills the reservoir, it eventually overcomes the weight of the other end (like in the second picture), and tips over (like in the third). This makes a clacking noise, and empties the reservoir so the process can start over.
Step 4: Timer Analogy
But how does this relate to a timer?
Inside your microcontroller, each timer receives a timer signal from what is known as the Peripheral Bus Clock (or PBCLK). This signal is essentially a steady ticking, like a metronome or, in our analogy, the constant dripping of water.
The rate of our clock signal (how fast it ticks) can be controlled through something called a "prescaler". This is like the valve on a faucet. By closing the valve, we can slow down the rate of the drips, and by increasing our prescaler we can slow down the rate at which the PBCLK ticks come.
As water drips into the souzu, it fills up the reservoir inside. Similarly, as the ticks arrive to our timer, they are counted in a Timer Register. This register slowly counts higher and higher, in the same way that the water in the reservoir slowly rises.
Eventually something has to give. Once the reservoir exceeds the weight on the other end, it tips over and empties out. Inside our timer, this limit is set by the Period Reset Register. Once the value in the Timer Register equals the Period Reset Register, it is reset to zero, and the process starts all over again.
It is at that moment, that the Timer Register equals the Period Reset Register, that our Timer Interrupt is activated. You can think of this as the "clack" that you hear as the souzu hits the wood.*
* Yes I'm aware that the clack does not happen when the souzu empties, but when it falls back into position. For this example it's simpler if we don't worry about that.
Step 5: Setting Up the Timer
So now that you understand the parts of a timer, you can start to set up your own.
In the code that you'll be using later, is a function called start_timer_3(). This function takes all the steps needed to properly configure timer 3.
The only argument that the function takes is a target frequency in Hz. This is the desired frequency of the interrupt. In other words, if you choose 400 Hz, this function will configure things so your timer interrupt will execute 400 times a second.
Before I begin explaining this function step by step, you should know that I'll be making reference to a bunch of constants that are set at the very beginning of the sketch in the sections titled "Define some constants" and "User Defined Variables". If you want to use these constants in your own code, you'll have to remember to include their definitions.
Step 6: Setting Up the Timer: Period Calculation
The first thing this function does is calculate the Period Reset. If you recall from the previous analogy, the Period Reset controls how high the Timer Register has to count before it resets and calls the interrupt. The formula for our Period Reset is:
period = [PB Clock frequency] / ([Prescaler] * [target frequency])
This formula is needed because the period will change based on our Prescaler value and the target frequency. It's worth noting that the formula used in the actual program is:
period = CLOCK_FREQ / (1 << PRESCALE * frequency)
The "1 << PRESCALE" is essentially 2^(PRESCALE). I'll explain what the PRESCALE value means later.
Step 7: Setting Up the Timer: T3CONCLR and Turning the Timer Off
T3CON is the Timer 3 Control register. You can think of it like a big switchboard, where each of the switches is a bit that controls some aspect of the timer 3's operation. This is a very important register, so you can imagine that it would be really bad to accidentally mess it up.
That's where T3CONSET and T3CONCLR come in. These registers are used to change T3CON in very specific ways.
When any bit in T3CONSET is set high (i.e. set to a one), it will immediately set those corresponding bits to a 1 in T3CON, and then reset its own bits to zero.
T3CONCLR will do the opposite. When any bit in T3CONCLR is set high, it will set those bits in T3CON low.
In line 77 we use T3CONCLR to set the Timer 3 enable bit to 0. This turns the timer off, so it stops counting up and we can start manipulating its settings without worrying about messing something up.
Later in line 84, the last line in the function, we use T3CONSET to set the enable bit back to 1, which turns the timer on again. We do this last because we don't want the timer running until we've gotten everything set up just right.
Step 8: Setting Up the Timer: Setting the Prescaler
Immediately after we turn the timer off in line 77, we use T3CONCLR again to clear out our previous prescaler.
The next several lines use another technique for manipulating T3CON that doesn't involve T3CONCLR or T3CONSET.
It works by pulling T3CON's current value into a variable. It's kind of like taking a snapshot of what T3CON looks like at that moment.
This variable can then be changed so it contains our desired prescaler, and re-loaded into T3CON.
But what is the prescaler?
Way back in our souzu analogy, I said the prescaler acted like a sort of valve that could slow down the PB Clock signal. The way it does that is by dividing the ticks.
Let's say you have a prescaler of 1:4. That means that for every 4 clicks from PB Clock, your timer only sees one. That means it takes four times as long to count, and the frequency is four times smaller.
The actual prescaler value used in T3CON is a code the tells your board what prescaler ratio to use. In our case, we can choose a ratio of 1:1, 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 1:256. If the prescaler code inside T3CON is set to 0, that corresponds to a prescaler of 1:1. A code of 1 is 1:2, 2 gives 1:4, and so on.
Notice how there isn't an option for a 1:128 prescaler? Keep that in mind, because a code of 7 will give you a prescaler of 1:256, and the prescaler cannot get any higher than that.
Step 9: Setting Up the Timer: a Few Last Steps
The rest of the steps are simple and quick, so we'll cover them all in one fell swoop.
On line 82, we clear our Timer Register. If you recall from our analogy, the Timer Register is where our ticks are counted. It's analogous to the water reservoir in our souzu, and clearing it is like emptying up this resevoir before starting the flow of water again. We do this to make sure our count starts from 0 after we've got all our settings finished.
After that, we set our period. Remember three steps back when we calculated that? This is where we use it!
Finally, we use T3CONSET to set our timer 3 enable bit, which turns our timer on and starts it running!
Step 10: Set Up the Interrupt
After all of that, we still need to set our timer interrupt. Inside the microcontroller, this is exactly the same as setting up an external interrupt. However, there isn't a handy function like attachInterrupt() that we can use to simplify it (like we did back in my external interrupts tutorial). That means that we have to take each of those steps manually, which gives us a neat insight into how interrupts work behind the scenes!
I don't want this tutorial to go too much longer, so I'm going to leave that section unexplained. However, if you look in your code, I've included some comments that can help to explain each step.
Step 11: Interrupt Service Routine
This section should be pretty clear, but I'll give it a quick once-over.
In this code, our ISR only needs to increment a count value and set a flag high. That flag tells the loop code that the interrupt has been run, and it should react accordingly.
The last line is very important though. Because we're not using attachInterrupt(), we need to make sure the interrupt flag (the one in the hardware, not the one that our loop code uses) is lowered. If we don't do this, the interrupt will just loop forever, and the rest of our code won't be able to do anything!
Step 12: Loop Code
The loop code is a little bit weird so let me break it down for you. It starts with an if-statement that checks for the flag variable we set in our interrupt code. If the interrupt has been run, then we run the code inside the loop.
The first thing our code does, then, is set the first LED high. If the interrupt hasn't been run recently, we set that LED low (which you can see down at the bottom next to the else-statement. This lets us get a sense of how fast our interrupt frequency really is.
The next three blocks of code set our other LEDs high or low based off their respective bits. Instead of giving each LED its own variable, they are each tied to a bit inside our count variable. This acts as its own sort of post-scaler, slowing down our interrupt so we can see it more clearly. LED 2 blinks half as fast as our interrupt LED (LED 1). LED 3 blinks half as fast as LED 2, and LED 4 blinks half as fast as LED 4.
What's more, we can increase the bit shift to slow down our LEDs even more. I'll show you how to do that in the next step.
The final stop in our code is simply to set the interrupt flag to low. That way, the next time our code loops, it doesn't re-execute the code until the interrupt has been run again.
Step 13: Customizing Your Own Code
Download the sketch I've included in this step.
Up at the top is a section titled "User Defined Variables". This section includes everything you need to change for your timer, so you don't need to worry about finding it in the code.
Here we set our prescale value. It should be set to a 1:2 value already, but if you want a 1:4, or a 1:32, just change it accordingly. This way, you can set the desired prescaler, and don't have to remember which code gives which prescaler.
The clock frequency here refers to the frequency of the PB Clock. On the DP32, it's set to 40 MHz. On the uC32, and WF32, it'll be 80 MHz, so set it according to which board you're using.
This is the target frequency for our interrupt. It should be set to 400 Hz when you download the code, but you can change this to whatever frequency you want your timer to operate at... almost.
In truth, because the Timer Register is only so large, it has limits on how long it can delay the interrupt. That means, that faster clocks (higher prescale values) won't be able to achieve the lower frequencies, and slower clocks won't be able to achieve higher frequencies. Here's a table of what frequencies each prescale value can achieve on the DP32*:
Prescaler Max frequency Min frequency 1:1 40 MHz 611 Hz 1:2 20 MHz 306 Hz 1:4 10 MHz 153 Hz 1:8 5 MHz 77 Hz 1:16 2.5 MHz 39 Hz 1:32 1.25 MHz 20 Hz 1:64 625 kHz 10 Hz 1:256 312 kHz 5 Hz
* To get these values for the uC32 and WF32, simply multiply each of the frequencies by 2.
Both the DP32 and WF32 have four onboard LEDs which are referenced using the PIN_LED# constants. This makes using the uC32 a little trickier, as you'll need to swap out any reference to PIN_LED# to LED_#. That way you can set which external pins are connected to LEDs yourself.
You will also, of course, have to set up some external LEDs.
In the previous step I explained how each LED is mapped to a bit in our counting variable. The bit offset allows you to change what part of that variable each LED is mapped to. Increasing the bit offset effectively slows down how fast the LEDs blink.
Step 14: Timer Conflict
The last thing you need to know about timers involves something I'll call timer conflict.
The DP32, uC32, and WF32 all have five timers. This code uses timer 3, but you can change it to refer to any of the other timers if you want, with one caveat.
According to its reference manual, the DP32 uses timer 1 and timer 2 for serial communication. That includes communication along the USB port used for programming. That means you shouldn't use these timers for your own programs because it could mess up the serial communication.
I'm honestly not sure if the WF32 or uC32 use their timers for serial communication, because their reference manuals don't say.
Step 15: Behavior
Once you've customized your code for your board, you can upload it to your microcontroller and watch it go!
You should see LED1 glowing dimly, as it is only lit immediately after the interrupt is handled. If your interrupt frequency is higher, it will glow more brightly, and it will glow more dimly if it's lower.
LED2 should blink very rapidly, likely it will be too rapid to actually see with the naked eye. LEDs 3 and 4 will each blink more slowly, but again it's very easy for these to be too fast to see as well.
In order to make your LED blink more slowly, you can either set the frequency lower (keeping in mind which clock prescalers will be able to achieve what frequencies), or you can set the bit offset higher.
Anyway, play around with different prescaler, frequency, and bit offset settings, and explore what your timers can do!
I hope you found this tutorial useful!