As you may have guessed, this is not really about counting marbles. It is about counting pulses that may represent any number of things you are interested in counting. These pulses may come slow or fast, regularly or randomly, or in some other way, and we need to know how to handle them in all situations. We use the counters for counting the events. The marbles just happen to be an interesting medium for us to play with: They are easily obtained, are easy to handle, and are inexpensive (and they do roll!). See Figure 17.1.
You need to know a few things about this problem before we start, however. They are
This is not trivial by a long shot!
There is a limit to how fast the marbles can go by in the real world, and the programs we create have to be faster than that.
There is a considerable difference between counting marbles that go by one at a time in an orderly fashion and those that go by one next to the other, helter skelter, fast. In this project, we will count them one by one, and then in the later part of the project we will count them going by much faster.
Signal bounce or signal jitter is a problem in any “real-world” situation. Especially so with these shiny glass marbles.
As can be expected, there are many ways to get a job like this one done—meaning that not everything will have been explored when we are done with this chapter.
We have asked for and been given the following assurances from Mr. Marbles the owner and manager of the local Marbles Inc. plant. These assurances are designed to make the specifications of the project more complete and to make our task easier. (In other words, we have been given the parameters for what we are to do.)
Figure 17.1 Counting marbles, an escapement. (Note the sensor between the two gates.)
All the marbles are perfectly round.
All marbles are the same size.
All marbles have the same density.
All marbles are opaque to infrared (IR) and light frequencies. (Here we are making the assumption that the IR light does not bounce around and cause us problems. This is not really a valid assumption!)
All marbles are uniformly colored.
We do not have to count more than 1000 marbles at a time.
The maximum rate of marble flow is 900 marbles a minute (15 per second).
The marbles will be given to us loose in a cardboard box and are to be returned the same way.
First, let’s design a system that counts marbles by allowing them to go one by one only. In order for the marbles to be presented to the counter one by one, we need an escapement that will release them one at a time. The following is a schematic for a simple two-gate/lever escapement that we can make. It uses two model aircraft R/C servos to control the two gates that will let the marbles go by one at a time. This schematic is followed by photographs of a simple escapement that I made in my wood shop in less than an hour. A similar escapement could also be made out of cardboard or hobby materials glued together with hot glue if more comprehensive shop facilities are not readily available.
It is very important that you actually make the physical components needed for this project. They provide an important part of the “learning by doing” experience because they expose you to all sorts of things no one ever expects. It is what the real world is all about and it can all be done quite easily with some pieces of cardboard, a mat knife, and a hot glue gun. The idea is to build it. It does not have to be perfect. What you need to make is shown in Figure 17.2.
Figure 17.2 Schematic and photograph of a simple escapement. (Single sensor escapement for marbles using two servos. Sensor not shown. See Figure 17.1 for sensor.)
This simple escapement/marble chute will be used for all the counting projects with simple modifications and changes in sensor positions. Provisions should be made for a bin above and below the escapement to accommodate about a pint of marbles (around 100). The design should allow the marbles to be returned to the top bin between runs with ease so that experimental counting can proceed run after run without delay.
For the first escapement-based counting program, the escapement will be programmed to work as follows:
1. At startup, the lower gate is closed and the upper gate is open. The system ensures this by starting in this position. On reset, go to this position.
2. The counter is set to 0.
3. Load the marbles.
4. If there are any marbles in the system, the first marble comes to rest against the lower gate.
5. The optical detector confirms this.
6. If there is a marble above the lower gate, the upper gate closes.
7. Both gates are closed now.
8. There is a marble between the gates.
9. The lower gate opens and releases the marble.
10. The counter increments by one at this point.
11. The lower gate waits 0.01 seconds and closes again. This makes sure the marble has dropped away.
12. As soon as the lower gate has closed, the upper gate opens and stays open till a marble comes to rest against the lower gate. The optical detector will confirm this.
13. The cycle repeats as long as more marbles come down the chute.
This is the basic escapement model for almost all escapements. See Figure 17.3 for a simple implementation.
Notice that the system is designed in a way that does not leave any marbles behind in the counter at the end of the operations. This is an important consideration.
The system could be designed with only one servo, but we will use two servos, one for each gate so we can have complete and flexible control of the action. (Mind experiment: Think about how you would do this with only one servo.)
As mentioned earlier, we need a sensor to tell us if there is a marble resting on the lower gate. We will use an inexpensive IR transmitter/detector pair across the marble to provide this function.
We also need sensors to tell us when the two gates are fully open and fully closed, but since we are using servos, we can use the commands to the servos combined with suitable delays to know what the status of the gates is at any time. (We are assuming that the servos are always able to follow the commands given to them. If we were designing a much faster and more industrial pneumatic system, we might use micro switches, light beams, or proximity switches to confirm the in and out positions of the gates.)
Figure 17.3 Close-up view showing how the simple gates are made.
The connections to the system will be as follows:
Servo 1 |
J7 on the LAB-X1 |
Servo 2 |
J8 on the LAB-X1 |
Phototransistor |
PORTB.0 on the LAB-X1 |
Infrared LED |
PORTD.1 on the LAB-X1 |
SOME SPECIAL REQUIREMENTS FOR SERVOS
We need a way to pulse the servos on a continuing and regular basis. We know from the servo literature that the servos have to be reminded of their position about 60 times a second in order to maintain proper operation. We will need to set up an interrupt to come ON about 60 times a second to do this. The interrupt routine refreshes the counters in the servo every time it is called. The POT0, POT1 numbers in the routine for each servo represent the IN and OUT position of each of the servos.
FINDING THE SERVO POSITIONS
Note Certain aspects of working with model aircraft servos concern the length and frequency of the signals they need. If you are not familiar with these, go to the Internet and read up on the relevant part on aircraft servomotor control.
Before we can go any further, we need to determine the exact IN and OUT position of each gate so we can set these parameters to the appropriate values for each servo in the program.
Program 17.1 lets you move both servos under computer control from POT0 and watch the setting on the LCD. With this program, we can find the values of TOP_GATE and BOT_GATE for each of the servos with ease whenever we need to. The program uses the two lower potentiometers on the LAB-X1 to control the position of the servos in real time. Adjust the potentiometers to get the useable IN and OUT position of the servos and write the values down for each servo.
Program 17.1 This is a stand-alone program for finding the exact servo settings to determine the open and close positions of the gates
CLEAR ; clear the variables
DEFINE OSC 4 ; define the oscillator
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; 4 bit protocol
DEFINE LCD_RSREG PORTE ; register select byte
DEFINE LCD_RSBIT 0 ; register select it
DEFINE LCD_EREG PORTE ; enable port
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; leave low for write
ADCON1=%00000111 ; set A-to-D control register
PAUSE 500 ; pause for .5 sec for LCD
; startup
;
TRISC = %00000000 ; PORTC is all outputs for servos
PORTC = %00000000 ; set to 0
PORTD = %00000000 ; PORTD is all outputs for LCD
PORTE = %00000000 ; PORTE is all outputs for LCD
LCDOUT $FE, 1, “CLEAR” ; clear display and show CLEAR
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set internal clock source
; (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in us
POT0 VAR BYTE ; create adval to store result
POT1 VAR BYTE ; create adval to store result
;
LOOP: ; main loop
LCDOUT $FE, $80, DEC3 POT0,” ”, DEC3 POT1,” ” ; display data
ADCIN 0, POT0 ; read PORTA.0
ADCIN 1, POT1 ; read port a1
POT0=POT0 + 23 ; so actual pulse is displayed
; (arbitrary value)
PULSOUT PORTC.1, POT0 ; pulse port c.1
PAUSE POT1 ; pause 1/60 seconds (approx
; value is 24)
PULSOUT PORTC.0, POT0 ; pulse port c.2
PAUSE POT1 ; pause 1/60 seconds, (approx
; value is 24)
GOTO LOOP ; do it again
END ; we always end with end
Noted for the record: in FUTABA systems, the servo center is defined as a 1.52 milliseconds wide pulse delivered about 60 times a second. The positional range is about 0.75 milliseconds on either side of that. Other manufacturers specify other values around 1.5 milliseconds, so it is worth it to check exactly what your servos need. Develop the habit of working to accurate parameters and looking things up for yourself.
In the preceding program, the pulses to both connectors J7 and J8 use the same value, POT0. Doing it this way allows us to hook a servo up to either connector, and the operation will be the same. Or both servos can be connected at the same time and you can find the end positions for both with this program. Both servos will not have the same value for the open and close positions for their respective gates unless you can make the linkages absolutely identical. POT1 controls the delay between the pulses. We want to find the shortest and longest delays that give us smooth servo operation, and then use the average.
Let’s develop the “escapement-based” counting program step by step, segment by segment, as we were just discussing. At the start of the program, we need to wait for a signal to start the counting process. At this point, the top gate must be open and the lower gate must be closed. When the first marble arrives on top of the bottom gate, we will be ready to start counting. We assume the other marbles are in position, but this—as we will see—may not necessarily be the case, meaning that our software must accommodate all possibilities.
The hardware we will respond to and control consists of the following items connected as indicated:
Infrared LED power on PORTD.1
Infrared LED ground to ground
Phototransistor power on PORTB.1
Phototransistor ground to ground
Phototransistor signal to PORTB.0
Top servo on PORTC.1 to the servo pins
Bottom servo on PORTC.2 to the servo pins
We will use switch 1 connected between line PORTB.4 and ground to start the counting process. PORTB.4 will be pulled up internally with OPTION_REG.7=0
The wiring schematic for the preceding description is shown in Figure 17.4.
Figure 17.4 The two ways of doing schematic wiring diagrams for IR-LED phototransistor pairs.
Program 17.2 Program to count marbles with an escapement
CLEAR ; always start with clear variables
DEFINE OSC 4 ; always define osc speed
; As always we will define the code for the LCD operation first.
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; and bits
DEFINE LCD_RSREG PORTE ; select register
DEFINE LCD_RSBIT 0 ; select bit
DEFINE LCD_EREG PORTE ; enable register
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; low mean we will write only
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set internal clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in us
;
; Next we set all the ports to the required directions
; and set their bits high and low
;
TRISB=%11110000 ; set PORTB direction
PORTB=%00001110 ; set PORTB bits
TRISC=%00000000 ; set PORTC direction
PORTC =%00000000 ; set PORTC bits
TRISD=%00000000 ; set PORTD direction
PORTD =%00000000 ; set PORTD bits
PORTE =%00000000 ; set PORTE direction
; Then we set the registers to make the system digital
; Set the interrupt controls for the timer
; And the options register for the pullups and the prescalar
;
ADCON1 =%00000111 ; set A, E registers to digital
INTCON=%10100000 ; set interrupt control reg bits
OPTION_REG=%00000101 ; set option reg bits
;
; We need for the program to pause for half a second
; To allow the LCD to come up to speed and clear the screen
;
PAUSE 500 ; pause for LCD startup
LCDOUT $FE, $01 ; clear the LCD
;
Assign variables for the servos, and counters and set their values
;
POT0 VAR BYTE ; create adval to store result
POT1 VAR BYTE ; create adval to store result
POT2 VAR BYTE ; create adval to store result
ALPHA VAR BYTE ; counter variable
COUNTER VAR BYTE ; counter variable
COUNTER=0 ; set counter
;
; Tell the program where to go on an interrupt
;
ON INTERRUPT GOTO INT_ROUTINE ; set interrupt target
;
; Set the initial conditions for the two gates as was determined
POT0=160 ; close top gate
POT1=100 ; close bottom gate
;
; Create a loop that the program can wait in till we press
; Switch 1
;
WAITLOOP: ; this is the loop for waiting to start
IF PORTB.4=1 THEN ; switch is connected to
GOTO WAITLOOP ; line B4
ELSE ;
ENDIF ;
RETURN ;
; Since we will be reading the photo-transistor on port B0 we
; have to set PORTB
; so that B0 is an input.
; We are going to use the interrupt feature of B0 in later
; programs so we want to keep the sensor on B0.
; We could have use any line
; at this stage in this program
;
TRISB = %11111111 ; we have to reassign this here for
; B0 input
;
LOOP: ; main loop
PORTD.3=PORTB.0 ; so you can see the signal on D3
POT0=120 ; open top gate
LCDOUT $FE, $80, DEC3 POT2, “ ”,”PRESS RESET” ;
LCDOUT $FE, $C0, DEC3 COUNTER,” ” ;
IF PORTB.0=0 THEN LOOP ; if no marble then go back to loop
ADCIN 0, POT2 ; read POT0 for servo wait pause
POT0=158 ; open top gate
POT1=160 ; close bottom gate
GOSUB PAUSER ; pause between moves
POT0=125 ; close top gate
POT1=120 ; open bottom gate
GOSUB PAUSER ; pause between moves
COUNTER=COUNTER + 1 ; count the marble
GOTO LOOP ; go back to top
;
DISABLE ; disable interrupts
INT_ROUTINE: ; the interrupt routine is called
; every 1/60
PULSOUT PORTC.1, POT0 ; of a second and pulses both servos
; to
PULSOUT PORTC.0, POT1 ; their set values. Then it
INTCON.2=0 ; resets the interrupt flag for Timer0
RESUME ; resume program
ENABLE ; enable interrupts
;
PAUSER: ; this routine is read from POT0 and
FOR ALPHA =1 TO POT2 ; determines how long it is between
PAUSE 2 ; instructions to the servos.
NEXT ALPHA ; adjust it with POT0
RETURN ; do not make it to short
END ; All programs end in END.
The program starts out with both gates closed.
The code in Program 17.2 will stay in the “waitloop” until switch 1 is pressed.
That opens the top gate and the system is ready to count.
As soon as a marble stops against the lower gate, the marble is detected, the top gate closes and the bottom gate opens.
One is added to the counter (at this point and not any sooner or later).
The bottom gate closes.
The top gate opens and the cycle is repeated.
When there are no more marbles, the system stops.
Press reset to start over.
If we were designing for a real-world situation, we would add some type of detectors to make sure the gates actually went in and out as intended. Proximity detectors of some sort are the detectors of choice in that they have no moving parts. We would probably use pneumatic actuators and run the operation from a programmable logic controller (PLC), though as we saw in this exercise, a PIC would be much cheaper and would do the job just fine. The system could also, of course, be designed to use two solenoids with appropriate IN/OUT detectors.
The preceding is the simplest way to count the marbles, but it is painfully slow. Slow enough to be counted with an expensive PLC!
When the marbles are coming fast and furious, we cannot stop them one at a time and count them as we did in the previous program. We need a faster way of doing the job. One way is to have each marble cause an interrupt and then in the interrupt routine increment the variable that has the marble count in it. (Keeping in mind that the largest variable in PBP is 16 bits or 65,536, unless we make arrangements to count to a larger number by keeping track of the overflows at 65,536.)
The program keeps counting till the marbles run out. Reset takes you back up to the top. This process will be fast enough for most counting, but there are instances when it will not—a topic we will discuss later in this chapter.
The front end of Program 17.3 is very similar to the top of the preceding escapement program. The changes are in how we do the counting.
Program 17.3 This program is not executable (Program not for use in real counting as it is now; for discussion and examination only)
The top of the program now looks like this:
CLEAR ; always start with clear variables
DEFINE OSC 4 ; always define osc speed
DEFINE LCD_DREG PORTD ; define LCD connections
DEFINE LCD_DBIT 4 ; and bits
DEFINE LCD_RSREG PORTE ;
DEFINE LCD_RSBIT 0 ;
DEFINE LCD_EREG PORTE ;
DEFINE LCD_EBIT 1 ;
LOW PORTE.2 ; low mean we will write only
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
DEFINE ADC_BITS 8 ; set number of bits in result
DEFINE ADC_CLOCK 3 ; set internal clock source (3=rc)
DEFINE ADC_SAMPLEUS 50 ; set sampling time in us
;
; Next we set all the ports to the required directions
; And set their bits high and low
;
TRISB=%11110000 ; set port directions and values
PORTB=%00001110 ;
TRISC=%00000000 ;
PORTC =%00000000 ;
TRISD=%00000000 ;
PORTD=%00000001 ;
;
; Then we set the registers to make the system digital
; Set the interrupt controls for the timer
; And the options register for the pullups and the prescalar ;
ADCON1=%00000111 ; set registers to digital
INTCON=%10100000 ; set interrupt control reg bits
OPTION_REG=%00000101 ; set option reg bits
; We need for the program to pause for half a second
; To allow the LCD to come up to speed and clear the screen
;
PAUSE 500 ; pause for LCD startup
LCDOUT $FE, $01 ; clear the LCD
;
; Assign variables for the servos and counters and set the values
;
POT0 VAR BYTE ; create adval to store result
POT1 VAR BYTE ; create adval to store result
POT2 VAR BYTE ; create adval to store result
ALPHA VAR BYTE ; counter
;
; Tell the program where to go on an interrupt
ON INTERRUPT GOTO INT_ROUTINE ; set interrupt target
;
; Set the initial conditions for the two gates as top open,
; bottom closed
POT0=125 ; open top gate
POT1=100 ; close bottom gate
;
; Create a loop that the program can wait in till we press Switch 1
WAITLOOP: ; this is the loop for
PORTD.3=PORTB.4 ; the start switch to be pressed
IF PORTB.4=1 THEN ; switch is connected to
GOTO WAITLOOP ; line B4
ELSE ;
ENDIF ;
;
; Since we will be reading the photo-transistor on port B0 we
; have to reset
; TRISB for PORTB so that B0 is an input. We are going to use
; the interrupt feature of
; BO in later programs so we want to keep the sensor on B0. We
; could have
; used any line at this stage in this program. We need to make
; B0 an input so
; that it can receive the signal from the IR – Phototransistor
; pair and enable
; the interrupt capability for B (later).
;
TRISB =%00001111 ; we have to reassign this here
; for B0 input
OPTION_REG =%01111111 ; set bits
INTCON=%10010000 ; set bits
TMR1L =0 ; clear low byte
TMR1H =0 ; clear high byte
;
LOOP: ; we do the counting work here in
; this loop
; this is an empty loop for now
GOTO LOOP ; end of loop
;
INT_ROUTINE: ; show contents of loop here
LCDOUT $FE, $80, DEC3 TMR1H, “ ”, DEC3 TMR1L,” ” ;
RESUME ;
END ;
This is an acceptable way of counting marbles, but we can do the job even faster if we use one of the timers in the counter mode. We will consider both Timer0 and Timer1. (Remember: Timer2 cannot be used as a counter because it has no input pin.)
Using Timer0 (8 bit) to learn about counting to counters.
Timer0 is not suitable for counting the marbles, so we will write a short program to demonstrate its use as a counter. We will set up a situation that will let us count how many times we press button 1 on the LAB-X1 keypad, starting with 250 in the counter. Doing this lets us see the counter overflow after a few button presses. We will also monitor the contents of INTCON on the LCD to see what happens as the button is pressed again and again. You need to master techniques like these to look at what is happening in the PIC or other processors your using, whenever necessary.
Set up the counter for Timer0 as follows:
OPTION_REG=%01101000 ; No prescalar and external clocking
; on TOCK1 (bit 5), rising edge (bit 4)
INTCON=%00100000 ; Enable interrupts with bit5
TOCK1, which is pin 6 of the PIC, will be used as an input for the counter (PORTA.4). Provide a jumper from D.1 to A.4 so the result of the key press, which appears on D.1, will be routed to the counter.
The following is what you should see on the LCD:
Every time you press button 1 on the keypad, the counter in TMR0 will increment by 1. After it gets to 255, the next key press will show bit 2 on INTCON go to 1 for 0.25 seconds. Next the counter will reset to 250, and the bit at INTCON.2 will be cleared. INTCON.0 is the “PORTB change” interrupt flag. It stays at 1 because we keep pressing a key that affects PORTB.
The program that reflects that above is shown in Program 17.4.
Program 17.4 Demonstration of the use of TMR0 as a Counter
CLEAR ;
DEFINE OSC 4 ; osc frequency
DEFINE LCD_DREG PORTD ; define the LCD connections
DEFINE LCD_DBIT 4 ;
DEFINE LCD_RSREG PORTE ;
DEFINE LCD_RSBIT 0 ;
DEFINE LCD_EREG PORTE ;
DEFINE LCD_EBIT 1 ;
LOW PORTE.2 ;
;
TRISA = %00010000 ; set PORTA
TRISB = %11110000 ; set PORTB
TRISD = %00000000 ; set PORTD
TRISE = %00000000 ; set PORTE
ADCON1= %00000111 ; don't forget to set ADCON1
OPTION_REG=%01101000 ; sets interrupt enable for TMR0
; bit3=1 sets prescalar to 1:1 or no scaling
; bit4=0 sets high to low transition on count
; bit5=1 sets counter mode using A.4 as input
; bit6=0 sets interrupt edge select
; bit7=0 sets pull ups on PortB
PAUSE 500 ; pause .500 second for LCD to start up
LCDOUT $FE, 1 ; clear LCD, go to first line
TMR0=250 ; put 250 into counter
INTCON=%00100000 ; clear all of interrupt register
; except bit 5
;
MAIN: ; main loop
LCDOUT $FE, $80, BIN8 TMR0, “=”, DEC3 TMR0,”=TMR0” ;
LCDOUT $FE, $C0, BIN8 INTCON ,”=”,”INTCON” ;
IF INTCON.2=1 THEN ; If the interrupt flag is set
LCDOUT $FE, $80, BIN8 TMR0, “=”, DEC3 TMR0 ; show changes to
LCDOUT $FE, $C0, BIN8 INTCON ,”=”,”INTCON bits” ; the two
; registers
TMR0=250 ; reset count in TMR0
PAUSE 750 ; pause so you can see what happens
INTCON=0 ; reset INTCON register
ELSE ;
ENDIF ;
GOTO MAIN ; do it again
;
END ; end program.
In Program 17.4, we press SW1 again and again and see what happens in the LCD. For each key press, you need to both release and hold the button down for a good part of a second to actually observe the key-down and key-up effect so that the 750 pause that allows us to look at the LCD can be discounted.
We will use Timer1 as the counter for the marbles because it is the only timer that can count to more than 255 without having to count the interrupts at every overflow of the counter and make the related calculations. It will count to the 1000 marbles designated in Mr. Marbles’ specification.
Actually, the microprocessor is much faster than the marbles coming out the chute, but we will pretend that we need to really hurry in our counting process for some other process we might encounter in the future.
If the marbles are coming so fast we do not have time to update the registers, we have to set up an internal counter and feed the signals that the interrupt provides directly into that counter. The signals will have to be debounced before they can be connected to the internal counter because these counters are extremely fast and will pick up every little jitter as a countable event.
THE BASICS OF COUNTING DIRECTLY INTO A TIMER BEING USED AS A COUNTER
In order to use a timer as a counter, we must set up the timer as usual and then change the input to the timer from the internal clock to the external signal and set the appropriate parameters for the incoming signal (rising edge, and so on).
For Timer1, the 16-bit counter, this is done as follows:
Setting Timer1 up as a counter.
T1CON.4 and T1CON 5 = 0 ; for no pre-scale
PIE1.0=1 ; to enable interrupt
INTCON.6=1 ; also needed to enable interrupt
T1CON.0=1 ; to start the counter. We have to
; connect the
; incoming signal to PORTC.0
T1CON.1=1 ; selects PORTC.0 for input
T1CON.3=3 ; also needed to select PORTC.0 for input
Note 1 The interrupt flag at PIR1.0 will be set when the counter overflows (and would have to be cleared in the interrupt service routine—our count will never get that far, but we still need to be aware of this!). We are not interested in the counter overflow in this particular application because we will never count past 65635, but if we were to count past that, the code to reset the interrupt flag and keep track of the overflows would have to be added to the program at the appropriate location. It might even be necessary to close one of the gates (meaning: stop a very fast process) before we got to the overflow to stop everything while we got this done.
Study the wiring diagram for the marble counter (Figure 17.5) so you fully understand how the hardware is wired on the controller.
In this program, each marble interrupts the IR beam, which feeds into B.0, the interrupt receiving pin. Any change in B.0 causes an interrupt and de-bounces the incoming
Figure 17.5 Wiring diagram for counting marbles into counter TMR1. (The four PORTD and the three PORTE lines, marked with circles in the figure, go to the LCD.)
signal because an interrupt can only be set once. The interrupt routine toggles pin B.4 up and down, and since pin B.4 is connected to pin C.0, the signal is fed to C.0. We are using pin C.0 because it is the only pin that can be used as an input for the Timer1 register pair as indicated in the data sheet.
THE WORKING PROGRAM
Program 17.5 demonstrates fundamental TMR1 operation as a counter for the marbles. As written it counts the number of times PORTB.0 is grounded. Displays show us the following things
; Value of X
; INTCON register (too fast to see, add pauses if you want to
; see it)
; B register (too fast to see, add pauses if you want
; to see it)
; TMR1L and TMR1H registers
;
; Working connections are
; Connect B1 to C0 to feed Timer1 counter
; Ground B0 to increment the counter.
The above ideas are implemented in Program 17.5.
Program 17.5 Basic marble counting routine to count directly to the Timer1 counter register pair (The wiring diagram for this program is in Figure 17.5)
CLEAR ; always start with clear. Good habit.
DEFINE OSC 4 ; always define the osc speed. Good habit
DEFINE LCD_DREG PORTD ; define LCD connections data PORTB
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; register select port
DEFINE LCD_RSBIT 0 ; register select bit
DEFINE LCD_EREG PORTE ; enable port
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; set low to write only
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
ADCON1=%00000111 ; set ADCON1 for digital operation
;
TRISA=%00000000 ; set PORTA
TRISB=%11110001 ; set port I/O
TRISC=%00001111 ; set PORTC
TRISE=%00000000 ; set PORTE
X VAR WORD ;
;
T1CON=%00000011 ; set bits to control Timer1. Page 56
; bit7=0 Not used
; bit6=0 Not used
; bit5=0 Prescalar=1
; bit4=0 Prescalar=
; bit3=0 Osc enable bit. Osc off
; bit2=0 Synchronize to external clock
; bit1=1 Use external clock
; bit0=1 Start Timer1. This bit turns in ON
OPTION_REG =%01111111 ; set bits. Page 21 of the datasheet
; bit7=0 Pulls up all of PORTB
; bit6=1 Interrupt edge select. Rising
; bit5=1 Clock source. RA4
; bit4=1 Increment on high to low
; bit3=1 Prescalar assigned to WDT therefore prescale=1
; bit2=1 Prescalar selection
; bit1=1 Prescalar selection
; bit0=1 Prescalar selection
INTCON =%10010000 ; set bits for interrupt control.
; Page 22
; bit7=1 Global interrupt enable
; bit6=0 enables any peripheral to set interrupt
; bit5=0 enables flag for TMR0 overflow. Goes with bit2
; bit4=1 Enables interrupt for bit 0 on PORTB. Goes with bit1
; bit3=0 enables interrupt any bit on PORTB. Goes with bit0
; bit2=0 TMR0 overflow flag or bit. TMR0 is always on.
; bit1=0 flag for bit0 on PORTB. See bit4
; bit0=0 flag for any bit on PORTB. See bit3
;
PAUSE 500 ; pause for start up
LCDOUT $FE, 1, “CLEARS THE LCD. ”; clear the display.
PAUSE 500 ; this is for seeing a reset button
; response
TMR1L=0 ; clear low byte
TMR1H=0 ; clear high byte
ON INTERRUPT GOTO INT_ROUTINE ; interrupt target ;
LOOP: ; main loop
LCDOUT $FE, $80, “X=”, DEC5 X,” ” ;
LCDOUT $FE, $8A, “I=” ,BIN8 INTCON ;
LCDOUT $FE, $C0, “B=“,BIN8 PORTB,” “,DEC3 TMR1H,” “,DEC3 TMR1L
GOTO LOOP ; end of loop
;
DISABLE ; disable interrupts
INT_ROUTINE: ; interrupt routine
TOGGLE PORTB.1 ;
TOGGLE PORTB.1 ;
X=256*TMR1H + TMR1L ;
INTCON.1=0 ; reset the interrupt flag for B0
PAUSE 25 ;
RESUME ; resume program
ENABLE ; enable interrupts
;
END ; end of program
The count will be displayed as 2 three-digit single byte registers and will flow from the low byte on the right to the high byte on the left every time the count in the low byte spills over 255.
The counter increments every time line C.0 goes from 0 to 1. With the first marble resting on top of the lower gate, the count is 0. As soon as the marble drops away, the count goes to 1. It will stay at 1 till the second marble drops by. It will then go to 2 and so on.
The program needs to hold the lower gate closed and the upper gate open till you press SW1 to start the counting process. Once counting starts,the gates stay open till you reset the system at the end of the count.
The entire program with the “wait for SW1” added and the bit identification and some other comments removed is shown in Program 17.6.
Program 17.6 Program for counting to the TMR1 counter (See wiring diagram)
CLEAR ; always start with clear.
; Good habit.
DEFINE OSC 4 ; always define the osc speed.
; Good habit
DEFINE LCD_DREG PORTD ; define LCD connections data
; PORTB
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; register select port
DEFINE LCD_RSBIT 0 ; register select bit
DEFINE LCD_EREG PORTE ; enable port
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; set low to write only
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
ADCON1=%00000111 ; set ADCON1 for digital
; operation
;
TRISB=%11110001 ; Set port I/O
TRISC=%00001111 ; set Port
X VAR WORD ;
;
T1CON=%00000011 ; set bits to control Timer1
OPTION_REG =%01111111 ; set bits
INTCON=%10010000 ; set bits for interrupt control.
;
PAUSE 500 ; pause for startup
LCDOUT $FE, 1, “CLEARS THE LCD. ” ; clear the display.
PAUSE 250 ; This is for seeing a reset
; button response
LCDOUT $FE, 1 ; clear again.
TMR1L=0 ; clear low byte
TMR1H=0 ; clear high byte
ON INTERRUPT GOTO INT_ROUTINE ; interrupt target
;
LOOP: ; main loop
LCDOUT $FE, $80,” TMR1= “,DEC3 TMR1H,” “,DEC3 TMR1L ;
LCDOUT $FE, $C0, “COUNT=“,DEC5 X ;
GOTO LOOP ; end of loop
;
DISABLE ; disable interrupts
INT_ROUTINE: ; interrupt routine
TOGGLE PORTB.1 ;
TOGGLE PORTB.1 ;
X=256*TMR1H + TMR1L ;
INTCON.1=0 ; reset the interrupt flag for B0
PAUSE 20 ;
RESUME ; resume program
ENABLE ; enable interrupts
;
END ; end of program
The wiring needed to implement the above program is shown in Figure 17.5.
RB0 will be high when the IR floods the phototransistor (which is the same as the dormant B.0 status, and this is what we want).
The wiring to test the operation of this program can be attached to the LAB-X1 and is shown in Figures 17.6 and 17.7.
The circuit can be mounted to the LAB-X1 and tested for proper operation before mounting to the marble counter. Play with a marble between the two devices to see how they react. The response to the edge of a marble is interesting, as is the point where you can induce the most jitter in the signal. See what happens when you change the value of the resistor in line with the phototransistor. High values are needed.
Adding the necessary startup sequence and switch 5 (SW5) to start the system, we get Program 17.7.
Program 17.7 Final program for counting marbles into the TMR1 counter (Connect the servo to J7)
CLEAR ; always start with clear.
; Good habit.
DEFINE OSC 4 ; always define the osc speed.
; Good habit
DEFINE LCD_DREG PORTD ; define LCD connections data PORTB
DEFINE LCD_DBIT 4 ; 4 bit path
DEFINE LCD_RSREG PORTE ; register select port
DEFINE LCD_RSBIT 0 ; register select bit
DEFINE LCD_EREG PORTE ; enable port
DEFINE LCD_EBIT 1 ; enable bit
LOW PORTE.2 ; set low to write only
DEFINE LCD_COMMANDUS 2000 ; delay in micro seconds
DEFINE LCD_DATAUS 50 ; delay in micro seconds
DEFINE CCP1_REG PORTC ;
DEFINE CCP1_BIT 2 ;
ADCON1=%00000111 ; set ADCON1 for digital operation
;
TRISB =%11110001 ; set port I/O
PORTB=0 ;
TRISC =%00001111 ; set Port
X VAR WORD ;
Y VAR BYTE ;
N VAR BYTE ;
M VAR BYTE ;
T1CON =%00000011 ; set bits to control Timer1
OPTION_REG =%01111111 ; set bits.
INTCON=%10010000 ; set bits for interrupt control.
;
PAUSE 500 ; pause for startup
LCDOUT $FE, 1, “CLEARS THE LCD. ” ; clear the display.
PAUSE 250 ; This is for seeing a reset
; button response
LCDOUT $FE, 1 ; clear again.
TMR1L =0 ; clear low byte
TMR1H =0 ; clear high byte
ON INTERRUPT GOTO INT_ROUTINE ; interrupt target
N=60 ; Iteration count
M=16 ; Pause between servo pulses
;
LCDOUT $FE, $80, “HOLDING” ;
FOR Y=1 TO N ;
GOSUB GATE_IN ;
NEXT Y ;
LCDOUT $FE, $80, “PRESS SW5 TO START” ;
;
WAITROUTINE: ;
IF PORTB.4=1 THEN WAITROUTINE ;
N=35 ;
LCDOUT $FE, $80, “OPENING GATE” ;
PAUSE 500 ;
FOR Y=1 TO N ;
GOSUB GATE_OUT ;
NEXT Y ;
;
LOOP: ; main loop
LCDOUT $FE, $80, “TMR1= “,DEC3 TMR1H,” “,DEC3 TMR1L,””-
LCDOUT $FE, $C0, “COUNT=“,DEC5 X ;
GOTO LOOP ; end of loop
;
DISABLE ; disable interrupts
INT_ROUTINE: ; interrupt routine
TOGGLE PORTB.1 ;
TOGGLE PORTB.1 ;
X=256*TMR1H + TMR1L ;
INTCON.1=0 ; reset the interrupt flag for B0
RESUME ; resume program
ENABLE ; enable interrupts
;
GATE_IN: ;
HIGH PORTC.1 ;
PAUSEUS 2300 ; CW
LOW PORTC.1 ;
PAUSE M ;
RETURN ;
;
GATE_OUT: ;
HIGH PORTC.1 ;
PAUSEUS 750 ; CCW
LOW PORTC.1 ;
PAUSE M ;
RETURN ;
END ; end of program
Figure 17.6 Diagram of the wiring needed to test the counter. (The high “normally open” condition here matches the weak high pullup on B.0 as it should.)
Figure 17.7 Wiring setup to test the sensitivity of the phototransistor response.
Some of the succeeding paragraphs are from the datasheet.
TIMER1 OPERATION IN ASYNCHRONOUS COUNTER MODE
The following is what we use for our marble counting exercise.
If control bit T1SYNC (T1CON<2>) is set, the external clock input is not synchronized. The timer continues to increment asynchronous to the internal phase clocks. The timer will continue to run during sleep and can generate an interrupt on overflow, which will wake up the processor. However, special precautions in software are needed to read/write the timer (Chapter 6.4.1 of the datasheet). In asynchronous counter mode, Timer1 cannot be used as a time-base for capture or compare operations.
READING AND WRITING TIMER1 IN ASYNCHRONOUS COUNTER MODE
Reading TMR1H or TMR1L while the timer is running from an external asynchronous clock will guarantee a valid read (taken care of in hardware). However, the user should keep in mind that reading the 16-bit timer in two 8-bit values itself poses certain problems, since the timer or one of the registers that form the timer may overflow between the reads.
For writes, it is recommended that the user simply stop the timer and write the desired values. A write contention can occur by writing to the timer registers, while the register is incrementing. This can produce an unpredictable value in the timer register. Reading the 16-bit value requires some care. Examples 12-2 and 12-3 in the PICmicro Mid-Range MCU Family Reference Manual (DS33023) show how to read and write Timer1 when it is running in asynchronous mode.
THE TIMER1 OSCILLATOR
A crystal oscillator circuit is built in between pins T1OSI (input) and T1OSO (amplifier output). It is enabled by setting control bit T1OSCEN (T1CON<3>). The oscillator is a low-power oscillator rated up to 200 kHz. It will continue to run during sleeps. It is primarily intended for use with a 32 kHz crystal. The data sheet shows the capacitor selection for the Timer1 oscillator.
The Timer1 oscillator is identical to the LP oscillator. The user must provide a software time delay to ensure proper oscillator startup.
RESETTING TIMER1 USING A CCP TRIGGER OUTPUT
If the CCP1 or CCP2 module is configured in compare mode to generate a “special event trigger” (CCP1M3:CCP1M0 = 1011), this signal will reset Timer1. Timer1 must be configured for either timer or synchronized counter mode to take advantage of this feature. If Timer1 is running in asynchronous counter mode, this reset operation may not work. In the event that a write to Timer1 coincides with a special event trigger from CCP1 or CCP2, the write will take precedence. In this mode of operation, the CCPRxH:CCPRxL register pair effectively becomes the period register for Timer1.
RESETTING OF TIMER1 REGISTER PAIR (TMR1H, TMR1L)
TMR1H and TMR1L registers are not reset to 00h on a POR or any other reset except by the CCP1 and CCP2 special event triggers.
T1CON register is reset to 00h on a power-on reset or a brown-out reset, which shuts off the timer and leaves a 1:1 prescale. In all other resets, the register is unaffected.
It is recommended practice that you stop TMR1 before reading or writing the two registers. This avoids the problem of the low-byte register overflowing and changing the high-byte register while you are in the middle of setting the two registers. There is no way to set them both simultaneously. (This is possible in some other PICs.)
THE TIMER1 PRESCALAR
The prescalar counter is cleared on writes to the TMR1H or TMR1L registers so it has to be reselected after a write to the timer registers.
If you watch INTCON on the LCD while interrupts are set and cleared, you will notice that the GIE (global interrupt enable) bit, bit7 of INTCON, gets cleared automatically when an interrupt is set, and is reset automatically when you clear the interrupt flag (if you had set it to 1 in the first place). This means that a second interrupt cannot occur before the first one that was set is cleared. Since PBP allows the use of more than one ON INTERRUPT GOTO command in a program, strategies to make sure all interrupts are handled properly can be formulated. It is best to do one interrupt at a time to keep it simple while you are a beginner.
TIMER2
The third timer is Timer2. This 8-bit timer cannot be used as a counter because it does not have the ability to respond to an external clock of any description. There is no line on this timer internal or external) that we can connect an external signal to.