Testing C Interrupts using Breakpoints in GPSIM

Well, well, well, here we are again. I will be continuing with my new found appreciation of Small Device C Compiler (SDCC) and showing you how to handle interrupts using C-- specifically, the TMR0 interrupt. I will also show you how to simulate this using gpsim, thus learning how to set breakpoints in your code and use the stopwatch feature in gpsim. What's really great, is if you are new to PIC programming and are having a difficulty understanding the concept of the Timer module, interrupts, and prescaler values and what they mean, using GPSIM gives you a realistic visual understanding without having to build a circuit. Pretty handy dandy. I'm choosing the 16F627 because that's what I have laying around and therefore what I tend to use as my experimenting and testing PIC. If you're not already familiry with the '627, you can think of it as "The New PIC16F84". The '628 is the same thing but with 2k rather than 1k. Anyway, the chip being used is not critical as we are merely simulating the end result.

Target Audience

I've already showns you how to get started programming PICs in Linux with gputils and gpsim in my arcicle: Intro to Programming PIC Microcontrollers in Linux and I've shown you how to install, compile, and simulate a basic program using SDCC in my article: Programming PIC's in Linux using C with SDCC. Therefore, I'm assuming you have met the following requirements:
  • Have gputils installed and tested and understand what it is.
  • Have gpsim installed and tested and have used it to simulate a basic PIC ASM program.
  • Have SDCC installed and tested and have compiled and simulated a basic C PIC program.
If you are not familiar with TMR0 and are new to PIC programming, then you're going to want to read this whole article. However, if you're a seasoned PIC programmer and are just interested in learning more about using gpsim to simulate your programs, you may want to skim through the text and focus on the section Simulating and Setting Breakpoints in isr.c.


Overview of the TMR0 Interrupt

When an interrupt occurs in the PIC micro, execution of your mainline code is interrupted, the state of the registers are saved (by you when coding in ASM) and control is passed to what is called an Interrupt Service Routing, or ISR. What generates the interrupt is dependent on how you configure the PIC. For example, you could have a hardware interrupt, where the PIC generates an interrupt when someone presses a button connected to your PIC. Another commony used interrupt is the timer interrupt. TMR0 is a counter within the PIC. When you setup your PIC to use the TMR0 interrupt, the chip will generate an interrupt every time the TMR0 register overflows from 0xFF back to 0x00. Now, usually TMR0 incements every clock cycle. This means that every 256 cycles, this interrupt occurs. This gives you the ability to periodically check certain attributes of your environment or perform some other action-- somewhat like "polling". Another feature when using the TMR0, is that we can set a prescaler. This allows us to use an internal counter to create longer time increments before the 0xFF -> 0x00 over flow occurs. For example, setting a prescaler of 1:4 means that instead of TMR0 being incremented every clock cycle, it's incremented every 4 clock cycles. Now, rather than the interrupt occuring every 256 cycles, it will occur every 1024 cycles. The actual duration of the interrupt is dependent on the external clock frequency. In this example, we'll be using a PIC16F627 @ 4MHz. Another commonly used crystal for the 16F627/628 is 10MHz. The table below shows the prescaler setup and the corresponding clock cycles and time delays associated with those interrups. We'll actually simulate these delays later using gpsim.
PS2 PS1 PS0 Ratio Cycles 4MHz 10MHz
0 0 0 1:2 512 512.0 uS 204.8 uS
0 0 1 1:4 1024 1.024 mS 409.6 uS
0 1 0 1:8 2048 2.048 mS 819.2 uS
0 1 1 1:16 4096 4.096 mS 1.638 mS
1 0 0 1:32 8192 8.192 mS 3.276 mS
1 0 1 1:64 16384 16.384 mS 6.553 mS
1 1 0 1:128 32768 32.768 mS 13.107 mS
1 1 1 1:256 65536 65.536 mS 26.214 mS

The isr.c Code

Download isr.c.tar.gz and extract the file. If you would like, open it up in a text editor so you can follow along. If you are already familiar with the code, you may want to skip ahead.
/* Define processor and include header file. */
#define __16f627
These lines tell the compiler what PIC we are using and what header file to use. The header file will contain all the defenitions we are using. For example, when use PS0 = 2, PS0 is defined in the header file. When we get down to simulating in gpsim, check out the 16F627.inc file in the Source Browser window.
/* Setup chip configuration */
typedef unsigned int config;
config at 0x2007 __CONFIG = _CP_OFF &
This is known as the configuration word. There is detailed information in The PIC16F627 Datasheet so I'm not going to discuss each feature, but this is how we configure the various options of the chip. Notice I'm using the defines from the header file rather than just plugging in some hex value. This makes the code easy to change and very readable.
static void isr(void) interrupt 0 { 

 T0IF = 0; /* Clear timer interrupt flag */
 PORTB++; /* increment PORTB */
This is a function using the SDCC 'interrupt' keyword. (I've removed the comments from the function here). From the SDCC manual: "For the interrupt function, use the keyword 'interrupt' with level number of 0 (PIC14 only has 1 interrupt so this number is only there to avoid a syntax error - it ought to be fixed)." This is our interrupt service routing. This function executes every time an interrupt occurs in the PIC. This code will be put at address 0x04 by the compiler, just as we would do if writing in assembly using org 0x004. The compiler will also create the code to save the state of the registers for us. We'll see this later when simulating the code with gpsim. What we're doing here, is clearing the TMR0 Interrupt Flag which was set as a result of the interrupt, and then incrementing PORTB. It's clear to see then, that PORTB will increment by a value of one every time this interrupt routine is called.
void main(void) {

    CMCON = 0x07;           /* disable comparators */
    T0CS = 0;               /* clear to enable timer mode */
    PSA = 0;                /* clear to assign prescaller to TMRO */

    TRISB = 0xF0;           /* PORTB.0-3 Output, 4-7 Input */
    PS2 = 0;                /* 001 @ 4Mhz = 1.024 mS */
PS1 = 0;
PS0 = 1;

INTCON = 0; /* clear interrupt flag bits */
GIE = 1; /* global interrupt enable */
T0IE = 1; /* TMR0 overflow interrupt enable */

TMR0 = 0; /* clear the value in TMR0 */
Now the first part of the main function, up to the endless loop, is where I'm configuring the options in the PIC. For detailed information on each of these bits that I'm setting or clearing, refer to the datasheet. The comments are as much detail as I'm going to describe here. As you can see by how I've set the PS2:PS0 bits, we're going to be using a 1:4 prescaler resutling in an interrupt every 1.024 mS. This is sometimes called a 1mS interrupt, but not that it's not actually 1mS.
while(1) { /* Loop forever */

 We aren't doing anything in the main loop... we're just sittin'
 here like a jackass waiting for that TMR0 interrupt to occur.
Now, in this case our mainline code does absolutely nothing. This is because this is a minimal program to illustrate the interrupts. So the code is going to run in a continuous loop doing nothing until it gets the interrupt, at which point the isr() function will run and then back to this endless loop we go.

Compiling isr.c

If you have not already installed and tested sdcc, see my article: Programming PIC's in Linux using C with SDCC. To compile the code, you need to open a terminal and navigate tot he folder where you have the isr.c file. Issue the command below:
sdcc --debug -mpic14 -p16f627 isr.c

Simulating and Setting Breakpoints in isr.c

Now we're getting somewhere. Hopefull you've read my article: Intro to Programming PIC Microcontrollers in Linux and therefore are familiar with using gpsim a little bit. However, now we're going to expand our knowledge a bit. So, launch gpsim using the command:
gpsim -pp16f627 -s isr.cod isr.asm
When gpsim opens up, make sure you have the following windows open (use the 'Windows' menu to hide/show windows): Source Browser, Breadboard, and StopWatch.a

gpsim window layout with stopwatch

Now, before we do it right, just click the 'Run' button in the main window and see what happens. At first, is seems that nothing is happening. But look at the stop watch window. It's going nuts. What's happening, is the GUI is trying to update for every cycle, resulting in a very slow simulation. On my system, it was a full 3 seconds before the first pin turned red at which point the stopwatch indicated that roughly 1.6 mS had occurred in simulation time. Now, it may be desireable to have a slow simulation, but not for us. The version of gpsim I'm running is a little bit buggy. Therefore, before starting over, I'm going to quit gpsim and launch it again . (just press the up arrow in your terminal window to get the last command issued).

Once back in gpsim, before clicking 'Run', change the 'Simulation mode' in the main window to '1000 cycles/GUI update'. Now click 'Run'. You should notice that the pins in the Breadboard window are changing states faster than you can keep track of. How do we test the timer to make sure it's actually ocurring every 1024 cycles/1.024 mS like we intended? The answer is breakpoints.

Now, for what I want to do, let's again quit gpsim and re-launch it so that everything is nice and fresh. In the Source Browser, click on the 'isr.asm' tab to view the assembly. Scroll down to our interrupt service routine, which is indicated with comment lines that read:

; interrupt and initialization code
Now there's a bit of assembly generated here for. Much of it is to save the states of our registers before going in to the ISR so that we can resume the mainline code when the ISR is complete. We want to set a breakpoint at the beginning of the ISR, so that the simulation will pause or 'break' every time we enter the ISR. So, highlight the line that reads 'MOVWF WSAVE' which is right after the line that reads ' ; static void isr(void) interrupt 0 { ' and right click to get the popup menu. Now select 'Breakpoint here' to set the breakpoint. You will get a little red circle to indicate the breakpoint. Your source browser should now look like this:

setting a breakpoint in gpsim

Back in the gpsim Main window, make sure the simulation mode is '1000 cycles/GUI update' and click run. Pay attention to the stopwatch window. You'll notice our simulation stopped at 1050 cycles (1.050 mS). What happened to 1024 cycles like we had setup?

gpsim stopwatch window Well, when the program first starts we are still setting up the time and prescaler and other configuration options. Therefore, the first interrupt doesn't occur at precicely 1024 cycles from startup. Let's call this initializing. However, now that the code is up and running (although it's paused at the moment because of our breakpoint), we should see a more acurate time delay this next run. Click 'Zero' on the StopWatch window to 0 out the cycles and time. Notice how that creates a cycle offset so we still know how many cycles since the startup. Now, if you click run again, the code runs and stops as soon as it enters our ISR routine. This time, B0 goes high. But look at the StopWatch window... interesting. Looks like we setup our prescaler and timer exactly as we had calculated.

gpsim stopwatch window

gpsim breadboard window

Yay! Good for us. Click run again, and again, and notice each time our simulation stops after 1024 cycles (1.024 mS) and with each time, the lower 4 bits of PORTB increment, counting up in binary.

So, now you can edit isr.c, modifying the lines...

    PS2 = 0;                /* 001 @ 4Mhz = 1.024 mS */
PS1 = 0;
PS0 = 1;
... to experiment with difference prescaller values to change the interval of the TMR0 interrupt. Good Luck!
Did you enjoy Testing C Interrupts using Breakpoints in GPSIM? If you would like to help support my work, A donation of a buck or two would be very much appreciated.
blog comments powered by Disqus
Linux Servers on the Cloud IN MINUTES