Tuto. 1 - Counter
PIC tutorial 1 - Counter
Fig. 1 Board 3 with 8 leds output
Description:
This is the first tutorial in the serie, which covers a few basics.
It covers basic output, delay, timers and interrupts. You will need a processor board, a power supply and the hexadecimal display or 8 leds output board on port B. On some processor boards, you could use another port. (Port A doesn't always have the 8 bits available).
The tutorial is split in 3 parts:
- basic delay routine (built-in with CCS compiler), which also covers the libraries for the processor boards.
- use of timer and interruption for the delays.
- use of a time symbol library, that can be used with any project where timing needs to be managed.
The boards can be powered by one of the PSU boards (powered from USB port), or between 2.5 to 5V.
Requirements:
Processor boardAny board (board 3 and 4 used in examples)
Extension(s)8 leds output
or hexadecimal display
Power supplyPSU 1, 2 or 3. Depends on board used.
LanguageCCS C
ProgrammerPicKIT 2
Program:
Version A
This version setup the time to wait using the CCS compiler delay routines, determined with the crystal frequency.
#define board_id 2 // Variable board_id needs to be defined with the board number used for the tutorial.
#include "../lib/Boards/mainboard.c" // Load board module
BYTE leds; // variable to store data to output on port B
void main() {
boot_up(); // Initialise the board
leds = 0; // reset counter to 0
while (true) { // main loop
output_b(leds); // output variable led on port b
leds ++; // increment it
delay_ms(250); // wait 250ms
}
}
This is a very small program. The variables are declared using types defined in a specific file, so don't look like the classic CCS types. This is to make the program easier to port to other compilers. The program loads some configuration files for the selected processor board. The variable leds hold the counter to output on port B.
The main program initialise the board with the function boot_up, reset the counter, and enter a loop that output the counter on port B, increments the counter and wait 250ms before looping again.
The function delay_ms is a built-in function of the CCS compiler that waits for a number of ms before returning. To be accurate, the compiler needs to know the clock frequency. This is defined in the mainboard file. It is easy to change the time between increments by changing the value passed to the delay_ms function. Each board having different clock speed and processors, they each have their own 'library' file. The variable board_id tells the compiler which library to use.
This program introduce the define feature of the CCS compiler. It is used to define variable, and can be used in various places in the program. When the program is compiled, the variable is replaced with its definition. The definition can be a macro for example. It is a very useful feature.
The program define the variable board_id. It tells the compiler which processor board is used to run the project. It is used to tell the program how to configure the delay functions. It is setup as 3, because I use board 3 to run the tutorial.
To use a different board, just change the board_id value and recompile. That's it.
It then loads the ../lib/Boards/mainboard.c file. This file loads the appropriate definition file for the processor board used. Depending of the board_id value, it loads a file that configure the hardware used, and loads a boot_up function that will be used later to initialise the board.
#ifndef board_id // Board ID not defined, throw error
#error "variable board_id not defined"
#else
#if (board_id == 1 ) // Board 1 library (PIC 16F819)
#include "../lib/Boards/mb16F819.c"
#elif (board_id == 2) // Board 2 library (PIC 16F628)
#include "../lib/Boards/mb16F628.c"
#elif (board_id == 3) // Board 3 library (PIC 16F873A)
#include "../lib/Boards/mb16F87x.c"
#elif (board_id == 4) // Board 4 library (PIC 18F2320)
#include "../lib/Boards/mb18F2320.c"
#elif (board_id == 5) // Board 5 library (PIC 18F26J11)
#include "../lib/Boards/mb18F26J11.c"
#elif (board_id == 6) // Board 6 library (PIC 18F26J50)
#include "../lib/Boards/mb18F2xJ50.c"
#else // Board ID unknown, throw error with board not supported
#error "Board board_id not supported"
#endif
#include "../lib/Common/TypeDefs.c"
#include "../lib/Common/PIC.h"
#endif
Mainboard.c checks that board_id is defined, else it throws an error. Depending on board_id, it loads the corresponding file for the specific processor. If the board id is not supported, it throws an error. It also loads a library that defines data types, and a library with PIC registers details. It make porting to other compiler (like Microchip C18).
The board used for this tutorial is board 3, with a PIC 16F873A processor. This is the file I will concentrate on: mb16F87x.c.
#include <16F873A.h>
#device = *=16 adc=8
#define CRYSTAL_FREQ 16000000
#if ( ( CRYSTAL_FREQ < 4000000) || ( CRYSTAL_FREQ > 20000000 ) )
#error "CRYSTAL FREQ" not defined between 4000000 and 20000000
#endif
#FUSES HS, PUT, NOWDT, BROWNOUT, NOLVP, NOCPD, NODEBUG
#ifdef PROTECT_CODE
#FUSES PROTECT
#else
#FUSES NOPROTECT // Code not protected from reading
#endif
#use delay ( clock = CRYSTAL_FREQ )
#use standard_io ( A )
#use standard_io ( B )
#use standard_io ( C )
void boot_up() {
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
}
Firstly, the program loads a file to define the device used for the project, 16F873A in this case.
It defines pointers as 16 bits variables, and the return value from analog to digital conversion to an 8 bits value.
The crystal oscillator frequency is then set, and checked that it is within acceptable values for that device (Between 4 and 20 MHz).
The fuses to be set in the processor are then defined: high speed crystal oscillator, no watch dog timer... Each processor will have its own set of fuses.
We then setup the delay functionnality: it tells the compiler the speed of the clock, to configure the built-in timing functions. This first version uses a built-in function (delay_ms) to wait between each increment. It waits a number of millisecond before returning. It is critical to define properly the clock so the timing is accurate.
We then setup the way the compiler will process inputs/outputs.
The boot_up() function is defined for every processor board. It is called in the main program to initialise it properly. It sets up various components of the processor.
The files for each processor are very similar. Using different ones allows to setup specific processor functionnality, without changing the main program.
Compile the project and program the target with a PicKIT 2 programmer for example. Connect the processor board to the 8 leds output on port B and power up the boards.
The leds will start blinking every 250ms, or 4 times a second, following a binary counter pattern.
If you use the hexadecimal display, it will display the value of the counter as an hex value.
This way works, but the processor spends a lot of time just counting... To make it more efficient, lets look at timers and interrupts in version B.
Fig. 2 Board 4 with hexa display
Version B
This version will use a timer to generate an interrupt repeatedly. I will then increment the counter and output its value on port B.
Between interrupts from the timer, the processor can go into idle mode to save energy, depending on the processor, or do other tasks.
In this version, the processor is not busy simply counting, it uses one of its hardware counter to do it, it is then free to do any other task.
For this version, I use processor board 4, with a PIC 18F2320, just to show how easy it is to compile a tutorial program on another processor board.
The most important documentation for the tutorials is the datasheet of the various processors used. Some components of a processor can be implemented a bit differently from one processor to another. The variable board_id in the program can be used to determine how to implement a component based on the processor board.
//* define CPU board used
//* 1: 16F819, 2: 16F628, 3: 16F873A, 4: 18F2320, 5: 18F26J11
#define board_id 4 // Variable board_id needs to be defined with the board number used for the tutorial.
#include "../lib/Boards/mainboard.c" // Load board module
#define inc_delay 250 // Delay in ms between each increment
#define int_count 10 // Number of interrupts between increments of counter
#define Timer1_Reload (65536-((CRYSTAL_FREQ/(4*8*1000))*inc_delay/int_count)) // (Timer 1 incremented every 1.6uS) 49910 for 25ms
BYTE leds; // variable to store data to output on port B
BYTE countInt=0; // count number of interrupt and initialise it to 0
#priority timer1 // set interrupt from timer 1 with a high priority
#int_timer1 // routine executed when an interrupt from timer 1 occurs
void Timer1Interrupt ( void ) {
set_timer1(Timer1_Reload); // reload timer 1 with predetermined value
countInt ++; // increment interrupt counter
if (countInt == int_count) { // if desired number of interrupt counted
leds ++; // increment counter to output
countInt = 0; // reset interrupt counter
}
}
void main() {
boot_up(); // Initialise the board
leds = 0; // reset counter to 0
setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_8 ); // setup timer 1 to use internal clock with prescaler to 8
set_timer1(Timer1_Reload); // setup timer 1 value with precalculated value
enable_interrupts ( INT_TIMER1 ); // enable Timer1 interrupt
enable_interrupts ( GLOBAL ); // enable all interrupts
while (true) { // main loop
output_b(leds); // output variable led on port b
#if defined(__PCH__) // if 18F family
setup_oscillator(OSC_IDLE_MODE); // set idle bit
sleep(); // go to idle mode (power save)
#endif
}
}
Timer 1 is used to generate interrupts. It is a 16 bits counter, available on all processors. Timer 1 can be setup to be incremented by the internal clock (Crystal frequency / 4) through a prescaler. It counts until 0xFFFF, and overflows to 0, which can generate an interrupt. We can preset the value of the counter for timer 1 so we know exactly how long it will take between interrupts.
The internal clock is at crystal frequency / 4, which is 5MHz with a 20MHz oscillator. If we setup the prescaler to 8 (timer 1 incremented every 8 internal clock signal), we increment timer 1 at a rate of 625KHz, or every 1.6 us.
The formula to determine the time between every increment for timer 1 is: T = (4*PRESCALER)/CRYSTAL_FREQ
The formula for another timer might be different (Timer 2 on the PIC 18F2320 has a pre and post scaler for example)
We want to increment the counter to ouput on port B every 250 ms. Timer 1 would need to count up to 250 000/1.6 = 156250. Timer 1 is a 16 bits counter, which can only count up to 65535, or 104.8576ms. We need a bit of help with the software: we will generate an interrupt every 1/10th of the required time, and increment the counter every 10 interrupts.
In the program, I defined values for the delay between the increments, and the number of interrupts to generate between them. Timer1_Reload is defined as a macro, which is the formula to calculate the value to preset timer 1 to generate the interrupt at the right time, just to show how practical the #define command can be.
Version C
This version will use a time symbol library. It is very helpfull when timing needs to be managed. Like version B, it uses a timer to generate interrupts, but also add functions to manage timing.
For this version, I use processor board 4 again, with a PIC 18F2320, but the library works with both 16 or 18 family.
The library is based on a library from Microchip, provided as part of the Microchip Application Library. It is changed to compile properly on the CCS compiler and adds support for the 16 family.
#define board_id 4 // Variable board_id needs to be defined with the board number used for the tutorial.
#include "../lib/Boards/mainboard.c" // Load board module
#include "../lib/Common/SymbolTime.c"
BYTE leds; // variable to store data to output on port B
TICK_T time1, time2;
void main() {
boot_up(); // Initialise the board
InitSymbolTimer();
enable_interrupts(global);
leds = 0; // reset counter to 0
output_b(leds);
time1 = TickGet();
while (true) { // main loop
time2 = TickGet();
if (TickGetDiff(time2, time1) > ONE_SECOND/4) {
time1 = time2;
leds ++; // increment it
output_b(leds); // output variable led on port b
}
}
}
The program is fairly short too, like version A. The heart of it is actually the library SymbolTime.c.
The library uses timer 0 to generate interrupts, and counts "ticks". The program checks the difference between to reads of ticks, and update the counter if it is the desired intervall.
SymbolTime.c implements a counter, incremented at every interrupts from timer 0. It is a 4 bytes (32 bits) value, incremented every tick (or timer 0 interrupt). The interrupt would typically occurs every 16 us, which gives the ability to measure time of up to 19 hours (16us * 2^32).
The side effect of using the library is that timer 0 is used for time management, and not available for other tasks. Global interrupt needs to be enabled.
#ifndef _SYMBOL_TIME_LIB
#define _SYMBOL_TIME_LIB
#include "../lib/Common/SymbolTime.h"
volatile BYTE timerExtension1,timerExtension2;
#if (defined(__PCM__))
volatile BYTE timerExtension3;
#endif
void InitSymbolTimer() {
#if (defined(__PCH__) || defined(__PCM__)) // 18F or 16F familly
setup_timer_0 (RTCC_INTERNAL | CLOCK_DIVIDER_SETTING);
#priority rtcc
clear_interrupt(INT_RTCC);
enable_interrupts(INT_RTCC);
timerExtension1 = 0;
timerExtension2 = 0;
#if (defined(__PCM__))
timerExtension3 = 0;
#endif
#else
#error "Symbol timer implementation required for stack usage."
#endif
}
TICK_T TickGet(void) {
TICK_T currentTime;
#if defined(__PCM__)
BYTE failureCounter;
disable_interrupts(INT_RTCC);
currentTime.bytes.b1 = 0; // copy the byte extension
currentTime.bytes.b2 = 0;
currentTime.bytes.b3 = 0;
failureCounter = 0;
do { // read the timer value
currentTime.bytes.b0 = get_rtcc();
} while( currentTime.bytes.b0 == 0xFF && failureCounter++ < 3 );
//if an interrupt occured after IE = 0, then we need to figure out if it was before or after we read TMR0L
if(TMR0IF) { //if we read TMR0 after the rollover that caused the interrupt flag then we need to increment the 2nd byte
currentTime.bytes.b1++;
if(timerExtension1 == 0xFF) {
currentTime.bytes.b2++;
}
}
currentTime.bytes.b1 += timerExtension1; // copy the byte extension
currentTime.bytes.b2 += timerExtension2;
currentTime.bytes.b3 += timerExtension3;
enable_interrupts(INT_RTCC); // enable the timer interrupts
#elif defined(__PCH__)
BYTE failureCounter;
disable_interrupts(INT_RTCC);
currentTime.bytes.b2 = 0; // copy the byte extension
currentTime.bytes.b3 = 0;
failureCounter = 0;
do { // read the timer value
currentTime.words.w0 = get_rtcc();
} while( currentTime.words.w0 == 0xFFFF && failureCounter++ < 3 );
//if an interrupt occured after IE = 0, then we need to figure out if it was before or after we read TMR0L
if(TMR0IF) { //if we read TMR0L after the rollover that caused the interrupt flag then we need to increment the 3rd byte
currentTime.bytes.b2++;
if(timerExtension1 == 0xFF) {
currentTime.bytes.b3++;
}
}
currentTime.bytes.b2 += timerExtension1; // copy the byte extension
currentTime.bytes.b3 += timerExtension2;
enable_interrupts(INT_RTCC);
#else
#error "Symbol timer implementation required for stack usage."
#endif
return currentTime;
}
#if (defined(__PCH__) || defined(__PCM__))
#int_rtcc void interruptSymbolTimer(void) {
if(TMR0IF) { // check to see if the symbol timer overflowed
clear_interrupt(INT_RTCC);
timerExtension1++;
if(timerExtension1 == 0) {
timerExtension2++;
#if (defined(__PCM__))
if(timerExtension2 == 0) {
timerExtension3++;
}
#endif
}
}
}
#endif
#endif
The library first load its header file: SymbolTime.h. This file defines various variables based on oscillator speed. It also defines the numbers of ticks (interrupts from timer 0) that would be in a set time (1 second, 1 ms, ...)
The 3 functions available are:
- InitSymbolTimer: this function initisliase timer 0, the variables and interrupts used by the library. It needs to be called before any other function can be active.
- TickGet: returns the time tick value, as the data type TICK_T, which is a 32 bits value.
- TickGetDiff: this macro returns the difference between 2 time readings.
The interrupt handler for timer 0 (or RTCC) is also defined, and increment counters.
This library makes it very easy to manage timing, and time events, or difference between them.
Files and links:
Tutorial 1 source files.Processor board 3.
Processor board 4.
8 leds output board.
Hexa decimal display board.

