Q. Can I use inline code for functions?
A. On some compilers, adding the keyword ‘inline’ before a procedure means that each time the procedure is called, the code inside it is inserted rather than called. This reduces the overhead of jumping to the function, but makes the program larger. There is no ‘inline’ keyword for the latest version of Hi-Tech C, v7.85.
However, one can almost have inline code by using #defines. If one puts a ‘\’ character after each line, it treats it like a single large line. The only disadvantage is that there is no way to return a variable. For example, see the following code.
unsigned char x,y
#define domaths_inline(z) \
x+=z; \
y++;
void domaths_procedure(unsigned char z)
{
x+=z;
y++;
}
main()
{
domaths_inline(4); //puts instructions x+=4;y++ directly into program
domaths_procedure(4); //calls procedure which executes instructions x+=4;y++
}
Q. Whats wrong with the port initialization code below?
//these 4 lines are correct
#define INPUT 1
#define OUTPUT 0
#define CLOCK RB1
#define CLOCK_DIRECTION TRISB0
//method 1 - set direction, then port (CORRECT)
// CORRECT
CLOCK_DIRECTION=OUTPUT;
DelayUs(10); //delay 10 microseconds
CLOCK=1;
//method 2 - set port then direction (WRONG)
// WRONG - MAY LEAVE PORT VALUE AS 0
CLOCK=1;
DelayUs(10); //delay 10 microseconds
CLOCK_DIRECTION=OUTPUT;
Of the two methods above, method is 1 is correct, method 2 is wrong.
If method 2 is used, clock will be set to 1, but since clock is an input, it will change back to a 0 again if the input is a zero. When the port is set to an output, it may be a zero or a one.
Thus, if method 2 is used to set up ports, the port could end up initialized to a zero, instead of a 1.
With the correct method 1, clock is initially an input. Thus, the value of clock reflects the read value of that port.
Thus, setting any port from an input to an output wont change the value of the port. Once the value of the port is set, it can be changed to another value.
Rule of thumb: when changing ports from input -> output, always set direction first, then change value.
Worked Example
To clarify, heres a worked example of what could go wrong in action:
1. We wish to set CLOCK=OUTPUT, and CLOCK to logic level 0 (RA4=0, TRISA4=0). Compare this to step 7.
2. CLOCK=INPUT, on microcontroller (TRISA4=1). All ports are set to input as the power up default.
3. An external pull-up resistor has pulled CLOCK to logic level 1 (RA4=1).
4. Setting CLOCK to 0 tries to set clock to 0 (RA4=0).
5. But ... on the next instruction, since CLOCK is an input, it goes high again (RA4=1).
6. Next, CLOCK is set to OUTPUT (TRISA4=0).
7. Now, CLOCK=OUTPUT, and CLOCK is logic level 1 (RA4=1, and TRISA4=0). Compare this to step 1.
I hope this clarifies why its a good rule of thumb to use the correct method.
Watching the values of variables
Q. How do I watch variables in MPLab?
A. Bring up the watch window dialog by clicking on the ‘pair of glasses’ icon on the right of the menu bar.
1. Char variables are 8 bits can be displayed in a variety of formats.
2. Integer variables are 16 bits and the byte order is ‘low:hi’ in every case, as shown below.
3. Long variables are 32 bits and the byte order is ‘low:hi’ in every case, as shown below.
Of course, to display variables inside functions, the switch ‘-fakelocal‘ must be added to the linker options. For this switch to work, check that you have the latest version of Hi-Tech C, v7.86pl3 or above. This is explained in the tutorial on how to set up a project.
Q. How do I add another variable to a watch window?
A. Left click to the left of the ‘Watch_1’ title on the title bar, as below.
Q. How do I watch an array of variables in MPLab?
A. There is no built in way and in MPLab to view arrays of variables. There is a way to get around it, illustrated by the diagram below. To look at the array named ‘array’, select ‘main.array’ from the ‘add watch symbol’ box. This is address 0x21, and shows array[0]. To look at array[1] enter 0x22 as the symbol. Alternatively, to view an array of integers, increment the address by two each time.
Q. I cannot view local variables, only global variables appear in the watch window.
A. To display variables inside functions, the switch ‘-fakelocal‘ must be added to the linker options. This is explained in the tutorial on how to set up a project. For this switch to work, check that you have the latest version of Hi-Tech C, v7.85 or above.
Errors and what they mean
Q. I get the following errors or line of errors when I compile:
::Can't find 0x64 words for psect rbss_0 in segment BANK0 (error)
A. All this gibberish means is that theres not enough ram to fit the variables in. In the 16F876, there are 4 banks of 96 bytes. Move some variables to another bank, by the following method:
unsigned char array_char[79]; //goes in bank0, 96 bytes excluding overhead
bank1 unsigned int array_int[40]; //goes in bank1, 96 bytes excluding overhead
bank2 unsigned long array_long[24]; //goes in bank2, 96 bytes of 32-bit longs
bank3 float array_float[30]; //goes in bank3, 96 bytes of 24-bit floats
After this, use the variables as per normal.
However, there are some issues with passing pointers. For example, a function that accepts a pointer can only accept it from the same bank. This is illustrated by the code below.
/* the following C line wouldn’t work – have to specify bank where pointer comes from, otherwise produces error “:Fixup overflow in expression”*/
//strcpy(unsigned char *to,unsigned char *from)
//works fine
strcpy(bank2 unsigned char *to,bank1 unsigned char *from)
{
while(*to++ = *from++); //copies strings
}
bank1 unsigned char x[3];
bank2 unsigned char y[3];
main()
{
x[0]=’O’;x[1]=’K’;x[2]=0; //x contains string ‘Ok’
strcpy (&y[0],&x[0]); //now array y contains string ‘Ok’
}
The following error is produced by passing pointers in different banks, so to fix it refer to the code above.
project.obj:33:Fixup overflow in expression (loc 0xFD2 (0xFCC+6), size 1, value 0xA1) (error)
Weird quirks of the PIC micro
Q. PORTA doesn’t work when reading logic levels.
A. Set it to digital mode, by setting ADCON1=7. If its in analogue mode, it will try to do a/d operations on the port.
Q. Port RA4 doesn’t work.
A. Port RA4 requires a 10k pullup. This is because it can be the input for an external timer.
Q. My A/D doesn’t work.
A. Check out the sample files in the c:\ht-pic\samples\ directory for an example. For sample code for the PIC16F876/77, check out source code.
Q. Unexplained Operation and Stack Levels
A. The PIC16F87x has 8 stack levels. This means that function calling in C can be nested up to 8 times. If function calling is nested more than this, unexplained operation will occur, because the oldest return addresses are overwritten. Realistically, function calling can only be nested 7 times, because the interrupts use 1, if not more, stack levels. If you interrupt service routine (ISR) calls a 1 function then 2 stack levels are used just for the interrupt. Note that the ISR should never, under any circumstances, call any functions if you use Hi-Tech C. This is because the ISR has to save the function calling area, which takes lots of precious cycles.
The unexplained operation is characterised by the program jumping to functions that it shouldnt be in.
If you are unsure, always check to see how many stack levels your program uses. Unfortunately, the hardware stack on a PIC micro is not readable or accessible by the program.
The best way to check the amount of stack levels used, in Hi-Tech C, is to look at the .map file. Turn on .map files in the linking options. In MPLab, select Project..Edit Project..Node Properties..Map File On.."main.map". Manually check how many levels the function calling is nested. Allow as many stack levels for the interrupts as needed.
Another way is to manually keep a track of the stack levels with a counter. Every time a function is called, increment the counter. Every time a function is returned from, decrement the counter. Keep a track of the maximum number this counter gets to, ie:
stack_level++;if (stack_level>stack_level_max) stack_level_max=stack_level;
However, this method is not recommended. If your program is big enough to warrant checking for stack levels, it will be a royal pain to add all the calls. A #define makes it easier, also used to switch on/off the debug code, but even still, examining the .map file is much more reliable, quicker, and doesnt make the program larger.
Interesting quirks of Hi-Tech C
Generally, Hi-Tech C is a very stable, bug free compiler. In two years of using it, I have never encountered any trouble with it. Currently I have an 8k, 5000 line C program that works beautifully. Make sure that you have the latest version, v7.86pl2, as some earlier versions have bugs. For example, v7.84 without the patch level 1 would sometimes branch the wrong way in an ‘if..else’ statement if the variables were in different banks.
Q. Whats wrong with the following program? It gives errors.
#define DOMATHS \ <- invisible space or tabs after ‘\’ gives error
x++; \
y++
#include
unsigned char x,y;
main()
{
DOMATHS;
}
A. An insidious problem. Check that theres no invisible spaces after any of the ‘\’ characters. Do this by moving the cursor to every line with a ‘\’, and pressing the key.
Symptoms? It will generate multiple errors, with the one below as the last one.
c:\pic\main.c: 12: illegal character (0134) (error)
Serial Port with PIC
Q. I’m using a 16F876 PIC micro to communicate to a PC with a serial port. It wont work. How do I fix it?
A. Here is PIC Hi-Tech C code, schematic picture and protel 99 files, plus VB 6 example code. Download.
A. This means you are ignoring framing and overrun error bits. If too many characters are received before they are recorded in software, the overrun bit, OERR, gets set. This shuts off all further transmissions. It a wrong stop bit is received, the framing error bit, FERR, gets set. This shuts off all further transmissions.
See the project for serial comms in the sample projects section.
Q. I want a routine to do a serial port in software, because I’m using a low-end PIC.
Go to directory ‘c:\ht-pic\samples’ and look at files ‘serial.c’ and ‘iserial.c’. The second file receives characters into a buffer in the background, using interrupts. Its almost like the hardware serial port on a high-end PIC.
Interrupts
Q. How do I use interrupts?
A. Interrupts are very useful. When a certain event happens – such as the logic level on a port changing, a timer overflowing or a serial character arriving – a flag is set. For example, if the logic level on port RB0 changes, instantly the flag INTF will get set.
If the particular interrupt is enabled, the current state of the processor is saved, and execution branches to the interrupt routine.
For example, if INTE is enabled, then it will jump to the interrupt routine as soon as INTF is set. When it is finished, the state of the processor is retrieved and execution continues where it left off.
Here is sample code to read the data line if the clock line goes low
#include
main()
{
//set up capture port interrupt
CCP1CON=0B00000100; //every falling edge of clock interrupts it
CCP1IE=1; //enable capture port interrupt on data line
PEIE=1;
GIE=1; //global interrupt enable
while(1); //whiz around doing nothing until interrupted, it jumps to isr
}
/*note the keyword ‘interrupt’. Hi-Tech C handles the code to save and restore the state of the micro, and the calculating the address to hook into the interrupt*/
interrupt isr()
{
//when clock line goes low, falling edge, it reads data line
if (CCP1IF) //clock line falling edge on CCP1IF, RC2, pin 13 on micro
{
CCP1IF=0;
//read data line in here
}
}
Q. My interrupt routine is not working.
A. Try the following tips:
1. If you put a breakpoint in the interrupt routine, and it doesn’t get there, check that every variable in the interrupt chain is enabled. The diagram below has been reproduced from the PIC16F876 manual, from the section on interrupts. For example, to enable the interrupt on the 16-bit timer 1 overflow, TMR1IE, PEIE and GIE must all be enabled for the interrupt to ‘interrupt to CPU’. If PEIE or GIE is disabled, it will never jump to the interrupt routine.
2. You must clear the interrupt flag, and in some cases read the port involved with the interrupt before exiting the interrupt routine. Otherwise, it will keep going back into the interrupt routine continuously for ever.
3. Important: the rule of thumb involving volatile variables:
If a variable is not declared volatile, problems will arise if it is changed. This is because the optimiser makes the program store a temporary copy of the variable in a register. If the interrupt comes along and changes it, even though it is changed in ram, it is not changed in the register.
Making a variable volatile forces the program to load a fresh copy of the variable every time it wants to check it. It also slows the program down slightly.
Here is some sample code to illustrate when to make a variable volatile:
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
//WRONG METHOD – ‘x’ IS REFERENCED IN BOTH MAIN() AND INTERRUPT
//unsigned char x;
//correct method
volatile unsigned char x;
#define FALSE 0
#define TRUE 1
main()
{
INTE=1;
GIE=1;
x=FALSE;
while(x==FALSE)
{
//idling loop waiting for interrupt to change ‘x’
//if x is not declared volatile, it will check its value from a temporary
//register. When the interrupt changes ‘x’ in ram, it will never know.
}
//wait for interrupt to change x to true, reaching this portion of code
}
interrupt isr()
{
//interrupt on port RB0 change
if (RBIF)
{
RBIF=0;
x=TRUE;
}
}
Q. Pins RB4 to RB7 all generate a common interrupt on change, RBIF. How do I tell what pin the interrupt came from?
A. Keep a record of the previous state of the port, and use XOR to work out what pin changed, thus:
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
main()
{
RBIE=1;
GIE=1;
while(1); //wait doing nothing
}
unsigned char prev_portb=0;
interrupt isr()
{
//work out which pin portb changed to produce this interrupt
if (RBIF)
{
RBIF=0;
if ((prev_portb ^ PORTB) == 0B00010000)
{
//pin RB4 changed
if ((PORTB & 0B00010000) == 0)
{
//now its 0, so its a falling edge
//execute code here (only on falling edge)
}
}
if ((prev_portb ^ PORTB) == 0B00100000)
{
//pin RB5 changed
//execute code here (on pin change)
}
prev_portb=PORTB;
}
}
Q. How do I execute some code precisely every 800us at 4MHz?
A. Use the built in timer. Set it up so when it rolls over it triggers an interrupt. When the interrupt is triggered, it sets flag T0IF high and executes some code.
For this example, we will use the 8-bit timer 0, available on PIC micros.
If the micro is running at 4Mhz, it is executing instructions at clk/4 speed, or 1MIPS. This is 1 instruction every 1us. For 800us, we need 800 timer ticks before it rolls over.
The timer rolls over at 0xFF, or 255. 255 is smaller than 800, so use 4 lots of 200 ticks using a 1:4 prescaler.
If we want 200 ticks, and the timer counts up and rolls over at 255, we need to set the timer to 55 each time, so it will count up to 255. Note that the prescaler is not rewritten each time – this needs to be only set once initially. The manual seems to indicate that whenever tmr0 is rewritten, it rewrites the prescaler. However, it means that it zeros the internal counter for the prescaler, not the actual prescaler itself.
This timing method is used in the simple multitasking technique for a PIC. In the meantime, here are some code examples.
Method 1: execute code every 800us by using polling to check the bit in main()
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
#define POLLING_PERIOD 200 //with 4Mhz processor, 200us
#define TMR0_PRESCALER 1 //gives 1:4 prescaler
//the -3 factor is to make up for overhead
//the 0xff- factor is because the timer counts *up* to 0xff
//do not change this
#define TMR0_SETTING (0xff - (POLLING_PERIOD-3))
main()
{
OPTION&=0B11000000; //turn off bottom 6 bits to configure tmr0
OPTION|=TMR0_PRESCALER; //set prescaler to 1:4
while(1)
{
TMR0=TMR0_SETTING;
T0IF=0;
while(T0IF==0); //wait 800us for flag to go high
//OK, tmr0 has overflowed, flag T0IF has gone high
//this code right here is executed every 800us
}
}
Method 2: generate an interrupt to execute code every 800us in background
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
#define POLLING_PERIOD 200 //with 4Mhz processor, 200us
#define TMR0_PRESCALER 1 //gives 1:4, 800us
//the -5 factor is to make up for overhead
//the 0xff- factor is because the timer counts up to 0xff
#define TMR0_SETTING (0xff - (POLLING_PERIOD-5))
main()
{
OPTION&=0B11000000; //turn off bottom 6 bits to configure tmr0
OPTION|=TMR0_PRESCALER; //set prescaler to 1:4
//work out which interrupt enable bits to set by referring to diagram
T0IE=1;
GIE=1;
while(1)
{
//idle, using interrupt to execute code
}
}
void interrupt isr(void)
{
if (T0IF)
{
TMR0=TMR0_SETTING;
T0IF=0;
//code right here is executed every 800us
}
}
A. On some compilers, adding the keyword ‘inline’ before a procedure means that each time the procedure is called, the code inside it is inserted rather than called. This reduces the overhead of jumping to the function, but makes the program larger. There is no ‘inline’ keyword for the latest version of Hi-Tech C, v7.85.
However, one can almost have inline code by using #defines. If one puts a ‘\’ character after each line, it treats it like a single large line. The only disadvantage is that there is no way to return a variable. For example, see the following code.
unsigned char x,y
#define domaths_inline(z) \
x+=z; \
y++;
void domaths_procedure(unsigned char z)
{
x+=z;
y++;
}
main()
{
domaths_inline(4); //puts instructions x+=4;y++ directly into program
domaths_procedure(4); //calls procedure which executes instructions x+=4;y++
}
Q. Whats wrong with the port initialization code below?
//these 4 lines are correct
#define INPUT 1
#define OUTPUT 0
#define CLOCK RB1
#define CLOCK_DIRECTION TRISB0
//method 1 - set direction, then port (CORRECT)
// CORRECT
CLOCK_DIRECTION=OUTPUT;
DelayUs(10); //delay 10 microseconds
CLOCK=1;
//method 2 - set port then direction (WRONG)
// WRONG - MAY LEAVE PORT VALUE AS 0
CLOCK=1;
DelayUs(10); //delay 10 microseconds
CLOCK_DIRECTION=OUTPUT;
Of the two methods above, method is 1 is correct, method 2 is wrong.
If method 2 is used, clock will be set to 1, but since clock is an input, it will change back to a 0 again if the input is a zero. When the port is set to an output, it may be a zero or a one.
Thus, if method 2 is used to set up ports, the port could end up initialized to a zero, instead of a 1.
With the correct method 1, clock is initially an input. Thus, the value of clock reflects the read value of that port.
Thus, setting any port from an input to an output wont change the value of the port. Once the value of the port is set, it can be changed to another value.
Rule of thumb: when changing ports from input -> output, always set direction first, then change value.
Worked Example
To clarify, heres a worked example of what could go wrong in action:
1. We wish to set CLOCK=OUTPUT, and CLOCK to logic level 0 (RA4=0, TRISA4=0). Compare this to step 7.
2. CLOCK=INPUT, on microcontroller (TRISA4=1). All ports are set to input as the power up default.
3. An external pull-up resistor has pulled CLOCK to logic level 1 (RA4=1).
4. Setting CLOCK to 0 tries to set clock to 0 (RA4=0).
5. But ... on the next instruction, since CLOCK is an input, it goes high again (RA4=1).
6. Next, CLOCK is set to OUTPUT (TRISA4=0).
7. Now, CLOCK=OUTPUT, and CLOCK is logic level 1 (RA4=1, and TRISA4=0). Compare this to step 1.
I hope this clarifies why its a good rule of thumb to use the correct method.
Watching the values of variables
Q. How do I watch variables in MPLab?
A. Bring up the watch window dialog by clicking on the ‘pair of glasses’ icon on the right of the menu bar.
1. Char variables are 8 bits can be displayed in a variety of formats.
2. Integer variables are 16 bits and the byte order is ‘low:hi’ in every case, as shown below.
3. Long variables are 32 bits and the byte order is ‘low:hi’ in every case, as shown below.
Of course, to display variables inside functions, the switch ‘-fakelocal‘ must be added to the linker options. For this switch to work, check that you have the latest version of Hi-Tech C, v7.86pl3 or above. This is explained in the tutorial on how to set up a project.
Q. How do I add another variable to a watch window?
A. Left click to the left of the ‘Watch_1’ title on the title bar, as below.
Q. How do I watch an array of variables in MPLab?
A. There is no built in way and in MPLab to view arrays of variables. There is a way to get around it, illustrated by the diagram below. To look at the array named ‘array’, select ‘main.array’ from the ‘add watch symbol’ box. This is address 0x21, and shows array[0]. To look at array[1] enter 0x22 as the symbol. Alternatively, to view an array of integers, increment the address by two each time.
Q. I cannot view local variables, only global variables appear in the watch window.
A. To display variables inside functions, the switch ‘-fakelocal‘ must be added to the linker options. This is explained in the tutorial on how to set up a project. For this switch to work, check that you have the latest version of Hi-Tech C, v7.85 or above.
Errors and what they mean
Q. I get the following errors or line of errors when I compile:
::Can't find 0x64 words for psect rbss_0 in segment BANK0 (error)
A. All this gibberish means is that theres not enough ram to fit the variables in. In the 16F876, there are 4 banks of 96 bytes. Move some variables to another bank, by the following method:
unsigned char array_char[79]; //goes in bank0, 96 bytes excluding overhead
bank1 unsigned int array_int[40]; //goes in bank1, 96 bytes excluding overhead
bank2 unsigned long array_long[24]; //goes in bank2, 96 bytes of 32-bit longs
bank3 float array_float[30]; //goes in bank3, 96 bytes of 24-bit floats
After this, use the variables as per normal.
However, there are some issues with passing pointers. For example, a function that accepts a pointer can only accept it from the same bank. This is illustrated by the code below.
/* the following C line wouldn’t work – have to specify bank where pointer comes from, otherwise produces error “:Fixup overflow in expression”*/
//strcpy(unsigned char *to,unsigned char *from)
//works fine
strcpy(bank2 unsigned char *to,bank1 unsigned char *from)
{
while(*to++ = *from++); //copies strings
}
bank1 unsigned char x[3];
bank2 unsigned char y[3];
main()
{
x[0]=’O’;x[1]=’K’;x[2]=0; //x contains string ‘Ok’
strcpy (&y[0],&x[0]); //now array y contains string ‘Ok’
}
The following error is produced by passing pointers in different banks, so to fix it refer to the code above.
project.obj:33:Fixup overflow in expression (loc 0xFD2 (0xFCC+6), size 1, value 0xA1) (error)
Weird quirks of the PIC micro
Q. PORTA doesn’t work when reading logic levels.
A. Set it to digital mode, by setting ADCON1=7. If its in analogue mode, it will try to do a/d operations on the port.
Q. Port RA4 doesn’t work.
A. Port RA4 requires a 10k pullup. This is because it can be the input for an external timer.
Q. My A/D doesn’t work.
A. Check out the sample files in the c:\ht-pic\samples\ directory for an example. For sample code for the PIC16F876/77, check out source code.
Q. Unexplained Operation and Stack Levels
A. The PIC16F87x has 8 stack levels. This means that function calling in C can be nested up to 8 times. If function calling is nested more than this, unexplained operation will occur, because the oldest return addresses are overwritten. Realistically, function calling can only be nested 7 times, because the interrupts use 1, if not more, stack levels. If you interrupt service routine (ISR) calls a 1 function then 2 stack levels are used just for the interrupt. Note that the ISR should never, under any circumstances, call any functions if you use Hi-Tech C. This is because the ISR has to save the function calling area, which takes lots of precious cycles.
The unexplained operation is characterised by the program jumping to functions that it shouldnt be in.
If you are unsure, always check to see how many stack levels your program uses. Unfortunately, the hardware stack on a PIC micro is not readable or accessible by the program.
The best way to check the amount of stack levels used, in Hi-Tech C, is to look at the .map file. Turn on .map files in the linking options. In MPLab, select Project..Edit Project..Node Properties..Map File On.."main.map". Manually check how many levels the function calling is nested. Allow as many stack levels for the interrupts as needed.
Another way is to manually keep a track of the stack levels with a counter. Every time a function is called, increment the counter. Every time a function is returned from, decrement the counter. Keep a track of the maximum number this counter gets to, ie:
stack_level++;if (stack_level>stack_level_max) stack_level_max=stack_level;
However, this method is not recommended. If your program is big enough to warrant checking for stack levels, it will be a royal pain to add all the calls. A #define makes it easier, also used to switch on/off the debug code, but even still, examining the .map file is much more reliable, quicker, and doesnt make the program larger.
Interesting quirks of Hi-Tech C
Generally, Hi-Tech C is a very stable, bug free compiler. In two years of using it, I have never encountered any trouble with it. Currently I have an 8k, 5000 line C program that works beautifully. Make sure that you have the latest version, v7.86pl2, as some earlier versions have bugs. For example, v7.84 without the patch level 1 would sometimes branch the wrong way in an ‘if..else’ statement if the variables were in different banks.
Q. Whats wrong with the following program? It gives errors.
#define DOMATHS \ <- invisible space or tabs after ‘\’ gives error
x++; \
y++
#include
unsigned char x,y;
main()
{
DOMATHS;
}
A. An insidious problem. Check that theres no invisible spaces after any of the ‘\’ characters. Do this by moving the cursor to every line with a ‘\’, and pressing the
Symptoms? It will generate multiple errors, with the one below as the last one.
c:\pic\main.c: 12: illegal character (0134) (error)
Serial Port with PIC
Q. I’m using a 16F876 PIC micro to communicate to a PC with a serial port. It wont work. How do I fix it?
A. Here is PIC Hi-Tech C code, schematic picture and protel 99 files, plus VB 6 example code. Download.
- Do you have Hyperterminal set to the correct COM port, “N,8,1” with no flow control?
- Get an oscilloscope, and put it in pin 3 of the serial port. Type some characters, and you should see it coming up on the oscilloscope.
- Connect pins 2 and 3 on the serial cable. This makes a loopback. Everything you type in Hyperterm will be displayed instead of lost.
- The PIC sends out 5V logic levels. The serial line operates with –13V/+13V logic levels. Some sort of interface is needed, usually using a MAX232, MAX3222 or SIPEX232 chip. Look up the datasheet and check that you have got it right. Check the capacitor values. The capacitors need to be a minimum size, anything above this will do. 33uF electrolytic caps are fine, if a bit of an overkill.
- Make sure that the order of the lines into the MAX232 chip is correct, as above.
- Check it from the micro side of things. Run the program, downloaded above, and verify that 5V logic levels are coming out of the transmit (TX) pin.
- Check that the crystal speed matches the value used to calculate SPBRG and BRGH. If you are using the ICEPIC 2000, check that the crystal speed, set in the menu options, matches the ones used to calculate SPBRG and BRGH. Send out 0xAA, which is 10101010 in binary. Using the oscilloscope, measure the time between the pulses and verify that they match the baud rate.
- If all else fails, change computer and try it on another.
A. This means you are ignoring framing and overrun error bits. If too many characters are received before they are recorded in software, the overrun bit, OERR, gets set. This shuts off all further transmissions. It a wrong stop bit is received, the framing error bit, FERR, gets set. This shuts off all further transmissions.
See the project for serial comms in the sample projects section.
Q. I want a routine to do a serial port in software, because I’m using a low-end PIC.
Go to directory ‘c:\ht-pic\samples’ and look at files ‘serial.c’ and ‘iserial.c’. The second file receives characters into a buffer in the background, using interrupts. Its almost like the hardware serial port on a high-end PIC.
Interrupts
Q. How do I use interrupts?
A. Interrupts are very useful. When a certain event happens – such as the logic level on a port changing, a timer overflowing or a serial character arriving – a flag is set. For example, if the logic level on port RB0 changes, instantly the flag INTF will get set.
If the particular interrupt is enabled, the current state of the processor is saved, and execution branches to the interrupt routine.
For example, if INTE is enabled, then it will jump to the interrupt routine as soon as INTF is set. When it is finished, the state of the processor is retrieved and execution continues where it left off.
Here is sample code to read the data line if the clock line goes low
#include
main()
{
//set up capture port interrupt
CCP1CON=0B00000100; //every falling edge of clock interrupts it
CCP1IE=1; //enable capture port interrupt on data line
PEIE=1;
GIE=1; //global interrupt enable
while(1); //whiz around doing nothing until interrupted, it jumps to isr
}
/*note the keyword ‘interrupt’. Hi-Tech C handles the code to save and restore the state of the micro, and the calculating the address to hook into the interrupt*/
interrupt isr()
{
//when clock line goes low, falling edge, it reads data line
if (CCP1IF) //clock line falling edge on CCP1IF, RC2, pin 13 on micro
{
CCP1IF=0;
//read data line in here
}
}
Q. My interrupt routine is not working.
A. Try the following tips:
1. If you put a breakpoint in the interrupt routine, and it doesn’t get there, check that every variable in the interrupt chain is enabled. The diagram below has been reproduced from the PIC16F876 manual, from the section on interrupts. For example, to enable the interrupt on the 16-bit timer 1 overflow, TMR1IE, PEIE and GIE must all be enabled for the interrupt to ‘interrupt to CPU’. If PEIE or GIE is disabled, it will never jump to the interrupt routine.
2. You must clear the interrupt flag, and in some cases read the port involved with the interrupt before exiting the interrupt routine. Otherwise, it will keep going back into the interrupt routine continuously for ever.
3. Important: the rule of thumb involving volatile variables:
Every variable that is referred to in ‘main()’ and ‘interrupt’ must be declared volatile
Making a variable volatile forces the program to load a fresh copy of the variable every time it wants to check it. It also slows the program down slightly.
Here is some sample code to illustrate when to make a variable volatile:
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
//WRONG METHOD – ‘x’ IS REFERENCED IN BOTH MAIN() AND INTERRUPT
//unsigned char x;
//correct method
volatile unsigned char x;
#define FALSE 0
#define TRUE 1
main()
{
INTE=1;
GIE=1;
x=FALSE;
while(x==FALSE)
{
//idling loop waiting for interrupt to change ‘x’
//if x is not declared volatile, it will check its value from a temporary
//register. When the interrupt changes ‘x’ in ram, it will never know.
}
//wait for interrupt to change x to true, reaching this portion of code
}
interrupt isr()
{
//interrupt on port RB0 change
if (RBIF)
{
RBIF=0;
x=TRUE;
}
}
Q. Pins RB4 to RB7 all generate a common interrupt on change, RBIF. How do I tell what pin the interrupt came from?
A. Keep a record of the previous state of the port, and use XOR to work out what pin changed, thus:
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
main()
{
RBIE=1;
GIE=1;
while(1); //wait doing nothing
}
unsigned char prev_portb=0;
interrupt isr()
{
//work out which pin portb changed to produce this interrupt
if (RBIF)
{
RBIF=0;
if ((prev_portb ^ PORTB) == 0B00010000)
{
//pin RB4 changed
if ((PORTB & 0B00010000) == 0)
{
//now its 0, so its a falling edge
//execute code here (only on falling edge)
}
}
if ((prev_portb ^ PORTB) == 0B00100000)
{
//pin RB5 changed
//execute code here (on pin change)
}
prev_portb=PORTB;
}
}
Q. How do I execute some code precisely every 800us at 4MHz?
A. Use the built in timer. Set it up so when it rolls over it triggers an interrupt. When the interrupt is triggered, it sets flag T0IF high and executes some code.
For this example, we will use the 8-bit timer 0, available on PIC micros.
If the micro is running at 4Mhz, it is executing instructions at clk/4 speed, or 1MIPS. This is 1 instruction every 1us. For 800us, we need 800 timer ticks before it rolls over.
The timer rolls over at 0xFF, or 255. 255 is smaller than 800, so use 4 lots of 200 ticks using a 1:4 prescaler.
If we want 200 ticks, and the timer counts up and rolls over at 255, we need to set the timer to 55 each time, so it will count up to 255. Note that the prescaler is not rewritten each time – this needs to be only set once initially. The manual seems to indicate that whenever tmr0 is rewritten, it rewrites the prescaler. However, it means that it zeros the internal counter for the prescaler, not the actual prescaler itself.
This timing method is used in the simple multitasking technique for a PIC. In the meantime, here are some code examples.
Method 1: execute code every 800us by using polling to check the bit in main()
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
#define POLLING_PERIOD 200 //with 4Mhz processor, 200us
#define TMR0_PRESCALER 1 //gives 1:4 prescaler
//the -3 factor is to make up for overhead
//the 0xff- factor is because the timer counts *up* to 0xff
//do not change this
#define TMR0_SETTING (0xff - (POLLING_PERIOD-3))
main()
{
OPTION&=0B11000000; //turn off bottom 6 bits to configure tmr0
OPTION|=TMR0_PRESCALER; //set prescaler to 1:4
while(1)
{
TMR0=TMR0_SETTING;
T0IF=0;
while(T0IF==0); //wait 800us for flag to go high
//OK, tmr0 has overflowed, flag T0IF has gone high
//this code right here is executed every 800us
}
}
Method 2: generate an interrupt to execute code every 800us in background
//(c)Shane Tolmie, http://www.microchipc.com/, distribute freely for non commercial use on the condition that you include this web link somewhere in your document.
#include
#define POLLING_PERIOD 200 //with 4Mhz processor, 200us
#define TMR0_PRESCALER 1 //gives 1:4, 800us
//the -5 factor is to make up for overhead
//the 0xff- factor is because the timer counts up to 0xff
#define TMR0_SETTING (0xff - (POLLING_PERIOD-5))
main()
{
OPTION&=0B11000000; //turn off bottom 6 bits to configure tmr0
OPTION|=TMR0_PRESCALER; //set prescaler to 1:4
//work out which interrupt enable bits to set by referring to diagram
T0IE=1;
GIE=1;
while(1)
{
//idle, using interrupt to execute code
}
}
void interrupt isr(void)
{
if (T0IF)
{
TMR0=TMR0_SETTING;
T0IF=0;
//code right here is executed every 800us
}
}
No comments:
Post a Comment