EEE502_L6(Timers_&_Interrupts_&_7_seg_displays

advertisement
Ian McCrum
School of
Engineering
Embedded Systems
Introduction to Interrupts,
Timers and the Seven Segment Display
ulster.ac.uk
Executing code
Some simple concepts
A program is just a list of machine instructions burnt into
memory. A program has a starting address, the
compiler/linker decides where to place code.
Each CPU executes lines of code sequentially, it uses an
internal register called the Program Counter to keep track
of the address in memory of what to execute.
On Power up or reset the Program Counter is initialised to
zero (in the PIC range of microprocessors). There is a
“JUMP” instruction there that jumps to an actual program.
Interrupts
Doing two things at once (apparently!)
If an “event” can force a new value into the Program
Counter this will force a “jump” to the new value. We
describe this as “interrupting the main code”
Most microprocessors allow this – and have mechanisms to
ensure a return can be made, back to wherever the code
was before it was interrupted. Any internal registers used in
the CPU must also be saved before and restored after the
execution of the interrupt code. This is done for you in XC8.
Most microprocessors have instructions to enable and
disable the interrupt mechanism, perhaps main code has to
do something which should not be interrupted.
Events – hardware interrupts
Typical sources of interrupt
It is useful to setup a timer so that when it reaches a trigger
value it generates an interrupt, this allows main code to be
doing something useful whilst waiting for a temporal event
If a external activity will take a long time, or an unknown time
then it is useful to have that activity interrupt the CPU when it
occurs.
The external event can be a change of voltage on an input
pin, you can choose rising or falling edge events. Or it can be
any change on a group of digital inputs. (see RBO/INT0 and
the Interrupt on change feature on RB4,5,6, and 7 – section
4.2 in the databook)
Events – hardware interrupts
Typical sources of interrupt
If one of the built in peripherals such as the ADC, UART,
SPI or IIC might take a bit of time to complete an data
transfer then it can be useful to interrupt the CPU when that
transfer is complete and needs CPU interaction.
Some peripherals have several possible sources of
interrupt. The UART transmit hardware can interrupt the
CPU when it has just finished transmitting, the UART
receive hardware can interrupt the CPU when an incoming
character has arrived. (it can take thousands of
microseconds to send or receive a serial character at 1200
baud for example – 10 bits at 1/1200 second for each bit)
Interrupts in the PIC16F877
Refer to the databook; chapter 14, section 11
There are 15 sources of interrupt; three tiers deep in the
hardware.
To use interrupts you must;
• Set the specific peripheral interrupt enable and also
• Set the generic interrupt enable bit.
There may be other enables needed( see PEIE) and other
PICs may allow two or more interrupts, high priority and low
priority, the CPU can almost do three things at once!
Interrupt Logic – PIC16F877
From: figure 14-10 of Microchip
PIC16F87XA databook
To use Timer 1 or
Timer 2 you must
also remember to
set PEIE
Thus a ‘1’ on TMR0IE will enable a Timer 0 interrupt,
but you also need a ‘1’ on the Global Interrupt Enable
(GIE) to fully arm the hardware.
The Interrupt will only happen when the Timer 0
Interrupt Flag (TMR0IF) goes to a ‘1’
Your software must (manually) clear TMR0IF inside
the interrupt code, to avoid an immediate re-interrupt
on leaving the Interrupt code.
Timer 0
• TMR0 is an 8-bit
timer/counter
Readable & writable
• 8-bit programmable
prescaler (2:1 …256:1)
• Internal or external
clock select
• T0IF Interrupt on
overflow of FF to 00
• Edge select for
external clock
We will use an
Internal clock with
a prescale; hence
T0CS=0, PSA=0
Timer 0 (note ZERO not “Oh”)
Details
In general timer 0 can either count input changes on RA4 or
time things, the system clock is FOSC/4 which for a 4MHz
crystal gives one microsecond resolution.
The prescaler divides by 2, 4, 8,16, 32, 64,128 or 256
depending on the 3 bit value stored in PS2, PS1 and PS0 in
the OPTION_REG.
The TMR0 interrupt is generated when the TMR0 register
overflows from 0xFF to 0x00. This overflow sets bit T0IF
(INTCON<2>). The interrupt can be masked by clearing bit
T0IE (INTCON<5>). Bit T0IF must be cleared in software
by the Timer0 module Interrupt Service Routine (ISR)
Using Timer 0
Three ways of timing things
After initialising the OPTION_REG, If you store 00 in the
TMR register it will start incrementing at the chosen period.
You can either
(a) Keep checking TMR0 until it reaches a target value
(b) Store (0xff-your target), start the timer then keep
checking TMR0IF – it gets set on a ff->00 rollover
(c) Set up as for (b) above but allow an interrupt to occur
Timer 0 Special Function Registers
SFRs which can be accessed from software
Timer 0 – setting it up
Refer to the
block diagram
of the
hardware –
then it is
obvious we
want T0CS = 0
and PSA=0
and a value in
PS2:0
The XC8 Compiler
Using it with the PIC16F
By including <xc.h> the compiler can find a number of declarations
- each hardware register is known as a SFR and each bit or group
of bits are accessible by using the structure below; these are often
the same names as the databook (XC32 is better than the XC8…)
TMR0=0x00; // an 8 bit register, clear to zero or a start value
OPTION_REGbits.T0CS=0; // use internal clock source
OPTION_REGbits.PSA=0; // use prescale unless divide by 1
OPTION_REGbits.PS=0b101; // this is the pattern for 1:64
INT_CONbits.T0IE=1; // Enable interrupts for timer zero
We will also need to do a GIE=1 to fully allow interrupts to happen.
Note that XC8 also allows other GIE and T0IE to be referred to in
code without the SFRbits. prefix
This may change in future versions of XC8
Interrupt Service Routines & vectors
You have seen how the PIC16F877 has 15 sources of interrupt.
These can cause a jump to interrupt code (if enabled correctly)
• The code is described as an Interrupt Service Routine (ISR)
• The jump is described as using an Interrupt Vector
• The vector must be setup to point to the address of the ISR
A vector is simply an address stored in memory where the CPU
can find it – the reset vector is at 0b000 and the interrupt vector is
at 0b004. The compiler places a “JUMP” instruction at both of
these, and the hardware inside the CPU jumps to the appropriate
address when reset or interrupted. Some CPUs just read the
vector table and take action themselves.
PIC18’s have two interrupt vectors, PIC32’s have hundreds!
The XC8 compiler and interrupts
There are several ways of doing this
The function qualifier interrupt (or __interrupt) can be applied to a C
function definition so that it will be executed once the interrupt occurs.
It must have void return and parameter lists – obviously since no code
will ever “call it”
The compiler will process the interrupt function differently to any other
functions, generating code to save and restore any registers used and
return using a special instruction.
Other Microchip compilers used other keywords;
__attribute__((interrupt,auto_psv,(irq(52)))) or
__ISR(_TIMER_0_VECTOR, ipl2auto) – nearly every event in
the PIC32 architecture has its own vector
So be sure to check in the compiler manual as this is non standard C
From section 5.9 of Microchips XC8
Compiler manual
Example code – 7 segment displays
See the code section of the website
If 4 separate seven segment displays are wired to parallel output ports
so that one digit at a time can be driven then a workable 4 digit display
can be created.
It is important to let each display show its digit for 5 msecs out of every
20 – the human eye’s persistence of vision will appear to see all 4
digits lit. This is known as a multiplexed display it suits a simple ISR.
We drove the
segments with
PORTD and the 4
digit select lines
(DIGITS_CTRL)
with PORTB<3:0>
Example code – 7 segment displays
The ISR and main code must communicate.
4 memory locations are set aside to hold what is to be sent to
each of 4 displays. (we use 5 locations – see later)
The 4 locations are written to by main code and read by the ISR
The write to the 4 locations must not be interrupted (literally), for
example if writing ‘1’ ‘9’ ‘4’ ‘2’ over the top of ‘2’, ‘0’, ‘1’ ,’4’ and
an interrupt occurred half way through this write sequence, it is
likely the display would read 1914 or 2042 depending on the
direction of main’s writing sequence
Thus we disable interrupts whilst altering the digit array in main.
Global Variables
For main to communicate to the ISR
Variables declared outside a function are considered to be global and
readable by any function in the declaring file.
As a matter of style I declare then as extern in a header file and then define
them (initialise them) in a file called setup.c.
Any program wanting to access them need only include “setup.h” and ensure
setup.c is compiled and included in the list of project files
Timer functions: Initialisation
This should make sense if you read 5.0 of the databook.
I would prefer the modern XC8 convention of accessing each bit on a line by itself – the
code is more self documenting and easier to read, though more typing is involved.
TMR0=156; // an 8 bit register, 256 to 156 is 100 in decimal i.e every 100 clocks
OPTION_REGbits.T0CS=0; // use internal clock source
OPTION_REGbits.PSA=0; // use prescale unless divide by 1
OPTION_REGbits.PS=0b100; // this is the pattern for 1:32 (i.e 3200 clocks)
INT_CONbits.T0IE=1; // Enable interrupts for timer zero - 3.2 milliseconds
Timer functions: ISR
If timer zero has its interrupts enabled and the interrupt has actually happened the
code inside the if statement gets executed. In practice there is only one source of
interrupt so the checks are not actually needed – but a future coder could alter the
code so it is better to be sure
It is vital to reload the TMR register and reset the T0IF flag, then we don’t get
interrupted for another 3.2 milliseconds
Seven Segment display: main code
Inside main you must setup (initialise) timer 0 so that interrupts occur
And simply drop a digit into each of the first 4 elements in the digits array
The code here drops in the ASCII code for each digit, the ISR extracts an
actual number. Note how interrupts are disabled whilst updating the array
and then re-enabled after the update is complete.
Note: sprintf acts just like printf except that the destination for the
formatted characters is a string instead of the screen. Thus
sprintf(astringname,”%04d”,1942);
Plants ‘1’ , ‘9’, ‘4’ and ‘2’ inside the string, it must already have a ‘\0’
Seven Segment display: ISR code
Working backwards, if the array digit contains “1942”, then digit[0] = ‘1’, digit[1]=‘9’ and so on.
The ascii character ‘1’ has an actual value of 0x30 and the ASCII character ‘9’ has the actual
value 0x39. Since the ASCII character ‘0’ has a value 0x30 the 2nd line
[digits[current_digit]-’0’] will have a value of the number 1,9,4 or 2 depending on whether the
variable current_digit has a value of 0,1,2 or 3.
Assuming 1, then sevseg[digits[current_digit]-’0’] will have a value of 0b1101111, the code for
sevseg[9]. Hence a 9 is displayed on the appropriate 7-seg display, all segments apart from f are
lit on the second display in from the left… for 3.2 milliseconds.
The 1st line above outputs the correct pattern 0b0100 if curreent_digit is a 1 and the 3rd and 4th
lines adjust current_digit for the next interrupt (the ‘4’ has to be dispalyred on the 3rd digit in)
Complete Code to drive 7-segments
This also shows how multiple file compilation works (there are 9 files!)
• You must add files to the project navigator in MPLAB X (the window in the
top left of the IDE)
• Normal convention is that every .c file has a .h apart from main
• .h files should not use memory, you should never #include .c files, only .h
files
• Every .h file should be guarded so its contents are only read once, see the
top 2 and last lines of my .h files
• Every .c file should compile on its own (right click in the navigator and
select compile file)
• These rules (recommendations) ease porting drivers to new projects.
I have used the software “Programmers Notepad” to display the code in the
overhead slides that follow, note the tab at the top that is highlit.
Other Interrupts and Timers
Be careful if you use 2 or more interrupts
Multiple interrupts are very tricky to code, very, very tricky to
debug and analyse in a deterministic way.
On the PIC16F there is only one interrupt vector so the one ISR
must check all xxIF flags to see who interrupted.
Such code must consider what to do if an interrupt occurs whilst
in an ISR and must consider if an interrupt might get missed (if 2
occur from the one source before the first one is serviced.)
PICs with multiple vectors are slightly easier to code – but a good
embedded systems engineer must still consider if an interrupt
might get missed, and if the priority is correct, some interrupts
are more important than others.
Other Interrupts and Timers
Different PIC chips vary
The PIC16F877 has 3 timers, timer 1 and timer 2 are both
16 bits and have extra registers that trigger wraparound –
when TMR1 reaches the value stored in PR2 it resets to
zero, it need not go up to 0xFFFF.
Each timer has special functionality, timer 2 is useful for
generating PWM signals
PIC32 chips and some PIC18F have 5 timers, on the
PIC32m a pair of timers can be used to provide a 32 bit
peripheral.
Download