PDA

View Full Version : Real time clock + external interrupt in one PIC possible?



Mugelpower
- 10th September 2017, 15:46
Hello people!

Its many years ago that i programmed something. Now i need to calculate the average speed of a car after pressing a button for start. I programmed a distance counter with a PIC16F88 and need now to measure the real time and divide distance by time. In my opinion that needs two interrupts.
The distance is measures using "on interrupt" counting pulses from the driveshaft. Do I need two pics or one pic and an external clock?
I could only use two types: 16F88 and 18F1320 becauce Iīm an inept programmer.
regards in advance for any helpful suggestions.

Mugelpower
- 11th September 2017, 09:27
Woahh.... I read the datasheet and was striked by the perception that the 18F1320 has three (in words: 3) different interrupts to handle around.

But.......how do I use them in PBP ? on interrupt 1? and on interrupt 2 ?

please help

mpgmike
- 11th September 2017, 17:57
Something like this:


"ON INTERRUPT GOTO Handler"

DISABLE
Handler:
IF INT0IF = 1 THEN
Counter = Counter + 1
INT0IF = 0
ELSEIF TMR1IF = 1 THEN
Speed = Counter
Counter = 0
TMR1IF = 0
TMR1H = (Preload value, HIGHBYTE)
TMR1L = (Preload value, LOWBYTE)
ENDIF
RESUME
ENABLE

Mugelpower
- 12th September 2017, 13:04
Thank you for the example,

but could you explain to me what TMR1H and TMR1L are for?

regards from an inept programmer...

mpgmike
- 12th September 2017, 18:43
Timer1 can create an Interrupt when it "rolls over" from $FFFF to $0000. This creates TMR1IF = 1. Timer1 is a 16-bit counter/timer, which means it can reach 65,535d (Hex $FFFF) before going back to zero and tripping an Interrupt Flag. Since it is a 16-bit counter, the value in the counter needs 2 bytes, which are TMR1H(IGHBYTE) and TMR1L(OWBYTE). If you are running your OSC at 4 MHz, then your Fosc/4 = (4,000,000 / 4) 1,000,000 Instruction Cycles per Second. This equates to (1 / 1,000,000) 0.000001 second (1 us) time frame per Instruction Cycle. Options for Prescaler in the Timer1 SFR goes as high as 1:8. At max 1:8 you can get 8 us (0.000008 second) clicks on your Timer1. Since Timer1 maxes out at 65,535, you can run Timer1 up to 0.52428 seconds. This isn't a very "friendly" time frame to work with. However, it is slightly over a half second. If we calculate 0.5 seconds divided by our 0.000 008 second TMR1 clicks, we get 62,500 TMR1 increments yield 1/2 second (0.5 seconds).

Back to computerese, a byte = max 255 while a word = max 65535. The architecture of the 8-bit Microchip MCUs means each address holds 8 bits, or 1 byte. In order to assign 16 bits to the Timer1 counter, it takes 2 addresses, or 2 bytes. Microchip has decided to name the 2 bytes required by Timer1 "TMR1H" for the upper byte value and "TMR1L" for the lower byte value. Sometimes it's easier to deal with our familiar decimal system. PBP lets us do that. Sometimes it's easier to deal with binary, as in setting Special Function Registers. PBP lets us do that as well. Sometimes you are forced to work with hexadecimal. Well, I find working with Hex easier with a calculator that came standard on both my MAC OS and Windows 10. It has Programmer function that easily converts any format (decimal, octal, hexadecimal, or binary) to any format (ditto).

Back to your original question, since Timer1 trips a flag when it overflows, if you want a 1 second increment, it can't happen. [You could work with some of the slower factory oscillator speeds available in ALL PIC MCUs and do the conversion math for PBP.] Best you can get at PBP's slowest OSC speed is 0.52428 seconds. If you could work with 0.5 seconds, then all we have to do is start Timer1 part way through it's counting sequence so when it flips, we got a 1/2 second.

Review, 4 MHz OSC + 1:8 Prescaler X 65,535 gives us 0.52428. If we want 0.50000 then all we have to do is take 0.5 and divide it by 0.000 008 and we get 62500. We don't want to START there, we want to END there. So we must subtract 62,500 from 65,535 and get 3035. That's bigger than 255, which means it is bigger than a byte. In order to contain such a large number, it takes 2 bytes (which max out at 65,535). I like working with decimal since that's what I grew up using. However, PIC requires that this decimal number be separated into it's upper byte and lower byte. You could convert it to binary and get %0000 1011 1101 1011, or convert it to hex and get $0B DB. If you wanted to load TMR1 with binary you would:

TMR1H = %00001011
TMR1L = %11011011

Hex is shorter and accomplishes the same thing:

TMR1H = $0B
TMR1L = $DB

Of course you could do it with decimal and:

TMR1H = 11
TMR1L = 219

Bottom line, Timer1 will start counting at 3035 through 65,535 and trip a flag at the 1/2 second mark. With that flag (TMR1IF) you can direct your code to an Interrupt Handler to do whatever you require. Though lengthy, I hope this helps.

Mugelpower
- 13th September 2017, 08:35
Thank you mpgMike,

so you convinced me to use an external real time clock. That needs an interrupt because the real time clock isnīt in sync with the pic s own clock.

I need to get 0.1 sec timing increments because 1 sec isnīt fine enough for starting time, max time is about 600 sec so I need a long variable to calculate with.

Hope i can work things out how to get the pic to use the real time clock but I guess there are lots of examples out there.

For explanation: we (my wife and I) drive so called Oldtimer-rallyes in a 1983 Mustang convertible and were on an event last weekend where we had to drive at an average speed of 28 km/h as constant as possible AND the starting point of the road was equipped with a clock and a light gate for the starting point. The end point of the distance was kept hidden so no one knew how long the distance was and so we couldnīt calculate anything. Only possible way was to drive as constant as possible with the given speed of 28 km/h. And to make things worse, there are heroes out there driving the distance
in 0,1 sec of the calculatet time . There were two of these hidden drives , the first we were 13 seconds off (which threw us out of the reach of a cup), the second we made it in 0,5 seconds of the sheduled time.
My wife wasnīt happy about driving home without a cup (first event this year we drove without cup so shes used to getting something and shes the driver) so my task is it to get something done to increase our chances next time.

Oh yeah the other guys use GPS and mobile phone apps and such.
I hope you appreciate that the old guy (54 years) tries to solve the problem with PIC Hardware/software.......haha


regards

Miguel David

HenrikOlsson
- 13th September 2017, 09:19
Why would you use an external real time clock for this? Those are meant to keep real time as in years, days, hours, minutes - they're not meant for timing applications where you need 0.1 second resolution or better.

Instead of using interrupts to count the pulses from the driveshaft why don't you use one of the timers to count the pulses for you and use another timer as the timebase? Mike gave you an exceptional explaination of how to use a timer to generate an accurate timebase with just about any resolution. I honestly believe it's going to be a lot easier AND give you better results then trying to use an external RTC chip.

Also, timing 600 seconds with 0.1 second resolution is only 6000 "counts" so it not require a LONG. If 600 seconds is the longest time you can get 10ms resolution and still stay within the bounds of a WORD variable.

What is the longest time you'll likely want to measure? Are you getting 1 pulse per revolution of the driveshaft? What's the distance traveled per pulse?

/Henrik.

HenrikOlsson
- 13th September 2017, 10:58
By the way, if the PIC isn't supposed to do much more than count the pulses and keep time there's really no need to complicate things with interrupts etc. If you'll answer the questions in my previous post I'll get you something you can use as a startingpoint.

/Henrik.

Mugelpower
- 13th September 2017, 21:18
Hello Henrik,

thank you in advance for your input. I get one pulse per driveshaft revolution that drives the car ca. 0.6m .
I donīt expect any road mission like this to exceed 8 minutes so 600 seconds as upper limit should do.

regards and greetings to sweden (had some nice days in Malmö 4 years ago)

Mugelpower

HenrikOlsson
- 14th September 2017, 06:20
Thanks, give me some time and I'll get back to you with a starting point.
Is it likely that the distance traveled will be more than 39km?

/Henrik.

Mugelpower
- 14th September 2017, 10:06
Hello Henrik,

no, the maximum distance we have experienced were less than 3 km, so 10km will be sufficcient. Problem would be that the road mission is on public roads that are temporary closed for about 2 hours and itīs difficult here in Germany, especially in Northrhein Westfalia to find roads that long which could be blocked from public access so the Oldtimers could drive their road mission. Northrhein Westfalia is the most dense populated federal state in Germany and has 524 People per square km. For comparison Sweden has 24 People per square km.

regards
Mugelpower

mpgmike
- 14th September 2017, 15:56
Laying in bed thinking about this project, I'm guessing you want to calculate an average speed from the time you hit the go pedal. I pondered how to do that with minimal code. Essentially I came up with:

Durchschnittlich var WORD ;Speed
Geschwindigkeit var WORD ;Average
Teiler VAR WORD ;Divisor
Gesamt VAR LONG ;Total

DO
Durchschnittlich = TMR3 ;Using T3CKI with TMR1
Gesamt = Gesamt + Durchschnittlich
Teiler = Teiler + 1
Geschwindigkeit = Gesamt / Teiler
LOOP

Of course, the DO/LOOP would be paced per TMR1 Interrupt

HenrikOlsson
- 14th September 2017, 15:59
Hi,
I've written some code targeting the 18F1320, I thought I had a couple of those in stock and I just set out to build up a test circuit to verify the code but it was the 18F1330 I had and as it turns out it's a completely different beast (lacking a TMR2 which the code relies on for example).

I guess I could change the code to suit a device that I do have but then that might not be what you intend to run it on anyway and we'd still need some back and forth so I'll just post the code as is and ask you to kindly remember that it is untested. Hopefully though it serves its purpose and show you the overall idea of one way that this can be done without external RTC chips or interrupts.

There is a bit of code but I tried to make it as complete as I could for a first pass, I'm sure there are things I didn't think of though.

Feel free to pick it apart, ask questions why this or that.



'************************************************* ***************
'* Name : Speedometer.pbp *
'* Author : Henrik Olsson *
'* Notice : Copyright (c) 2017 [select VIEW...EDITOR OPTIONS] *
'* : All Rights Reserved *
'* Date : 2017-09-13 *
'* Version : 1.0 *
'* Notes : Uses TMR1 configured as a counter to measure *
'* distance traveled and TMR2 as a 2ms timebase to *
'* measure real time. *
'* An active low start/stop button is used to start *
'* and stop the measurement. *
'* *
'* : Target is 18F1320 but running on internal 8MHz *
'* oscillator but should work on any device having *
'* a TMR1 and TMR2 with or without minor changes. *
'* : *
'************************************************* ***************

'-------------------- Device configuration ----------------------
#CONFIG
CONFIG OSC = INTIO2 ; Internal RC oscillator, port function on RA6 and port function on RA7
CONFIG FSCM = ON ; Fail-Safe Clock Monitor enabled
CONFIG IESO = ON ; Internal External Switchover mode enabled
CONFIG PWRT = OFF ; PWRT disabled
CONFIG BOR = ON ; Brown-out Reset enabled
CONFIG BORV = 27 ; VBOR set to 2.7V
CONFIG WDT = ON ; WDT enabled
CONFIG WDTPS = 512 ; 1:512
CONFIG MCLRE = ON ; MCLR pin enabled, RA5 input pin disabled
CONFIG STVR = ON ; Stack full/underflow will cause Reset
CONFIG LVP = OFF ; Low-Voltage ICSP disabled
CONFIG DEBUG = OFF ; Background debugger disabled, RB6 and RB7 configured as general purpose I/O pins
CONFIG CP0 = OFF ; Block 0 (00200-000FFFh) not code-protected
CONFIG CP1 = OFF ; Block 1 (001000-001FFFh) not code-protected
CONFIG CPB = OFF ; Boot Block (000000-0001FFh) not code-protected
CONFIG CPD = OFF ; Data EEPROM not code-protected
CONFIG WRT0 = OFF ; Block 0 (00200-000FFFh) not write-protected
CONFIG WRT1 = OFF ; Block 1 (001000-001FFFh) not write-protected
CONFIG WRTC = OFF ; Configuration registers (300000-3000FFh) not write-protected
CONFIG WRTB = OFF ; Boot Block (000000-0001FFh) not write-protected
CONFIG WRTD = OFF ; Data EEPROM not write-protected
CONFIG EBTR0 = OFF ; Block 0 (00200-000FFFh) not protected from table reads executed in other blocks
CONFIG EBTR1 = OFF ; Block 1 (001000-001FFFh) not protected from table reads executed in other blocks
CONFIG EBTRB = OFF ; Boot Block (000000-0001FFh) not protected from table reads executed in other blocks
#ENDCONFIG

'---------- Variables ------------
Seconds VAR WORD ' elapsed time, seconds
ms VAR WORD ' elapsed time, milliseconds
State VAR BYTE ' State machine variable, keeps track of what we're doing
Distance VAR WORD ' Distance traveled in metres, calculated based on pulsecount
AverageSpeed VAR WORD ' Final result of measurement, in units of 0.1km/h (123=12.3km/h)
Temp var WORD ' Used during calculation
DebounceCount VAR BYTE ' Used to debounce the start/stop button
TimeOverflow VAR BIT ' Gets set if the seconds variable grows too big.

'---------- Constants -------------
Idle CON 0
Starting CON 1
Counting CON 2
Stopping CON 3
Done CON 4

'------------ Aliases --------------
StartStopButton VAR PortB.0 ' Low when pressed
ReadyIndicator VAR LATB.2 ' High when idle/ready
RunIndicator VAR LATB.3 ' High when counting/timing
ErrorIndicator VAR LATB.4 ' High on count or time overflow
TMR1ON VAR T1CON.0 ' Run/stop bit for TMR1
TMR2ON VAR T2CON.2 ' Run/stop bit for TMR2
TMR1IF VAR PIR1.0 ' TMR1 interrupt flag
TMR2IF VAR PIR1.1 ' TMR2 interrupt flag

'--------- Hardware setup -----------
ADCON1 = %11111111 ' All pins as digital
LATB = %00000000 ' All pins low
TRISB = %01000001 ' RB0=Button, RB1=TX, RB2=LED, RB3=LED, RB4=LED, RB6=Counter input
OSCCON = %01110010 ' Internal oscillator, 8MHz
T1CON = %00000010 ' External clock on RB6, timer OFF for now.
T2CON = %00000010 ' 1:16 prescaler, TMR2 off for now
PR2 = 249 ' T2 interrupt flag set every 250*16 tick

RCSTA = $90 ' Enable serial port & continuous receive
TXSTA = $20 ' Enable transmit, BRGH = 0
SPBRG = 12 ' 38400 Baud @ 8MHz, 0,16%
SPBRGH = 0
BAUDCTL.3 = 1 ' Enable 16 bit baudrate generator

PAUSE 500

Start:
HSEROUT["Program start",13]

For Temp = 0 to 9
ReadyIndicator = 1
Pause 250
ReadyIndicator = 0
Pause 250
NEXT

ReadyIndicator = 1


Main:
Select CASE State

Case Idle
If StartStopButton = 0 THEN ' If the button is pressed we
Seconds = 0
ms = 0
TMR1H = 0 ' reset the pulse count
TMR1L = 0
TMR2 = 0 ' reset the timebase
TMR1IF = 0 ' clear the overflow flag
TMR2IF = 0 ' clear the timebase tick flag
TMR1ON = 1 ' enable the counter to count pulses from the driveshaft
TMR2ON = 1 ' enable the 2ms timer tick
ReadyIndicator = 0 ' update status LEDs
RunIndicator = 1
ErrorIndicator = 0
TimeOverflow = 0 ' clear overflow flag
State = Starting ' and move to the next state.
ENDIF
'---------------------------------------------------------------------------------------------------
CASE Starting
IF TMR2IF THEN ' TMR2IF gets set every 2ms
TMR2IF = 0 ' reset flag
GOSUB UpdateTime ' update our time registers

' Wait for the button to be released before transitioning to the next state.
If StartStopButton = 1 THEN ' Button released?
DebounceCount = DebounceCount + 1
ELSE
DebounceCount = 0 ' Button must be released 10 ticks in a row.
ENDIF

IF DebounceCount = 9 THEN ' Button has been released for 10 ticks
DebounceCount = 0
State = Counting ' Transition to next state.
ENDIF
ENDIF
'---------------------------------------------------------------------------------------------------

Case Counting
IF TMR2IF THEN ' 2ms tick?
TMR2IF = 0 ' clear flag and
GOSUB UpdateTime ' update our time registers

If StartStopButton = 0 THEN ' Button pressed?
RunIndicator = 0
TMR1ON = 0 ' Disable pulse counter
TMR2ON = 0 ' Disable timer
State = Stopping
ENDIF
ENDIF

IF TMR1IF THEN ' Counter overflow
ErrorIndicator = 1
ENDIF

' Here we can do anything else that needs doing while the
' system is counting but it's very important that it does
' not take longer than 2ms or we will lose time.
'---------------------------------------------------------------------------------------------------
CASE Stopping
' Wait for the button to released before transitioning to the next state,
If StartStopButton = 1 THEN ' Is button released?
DebounceCount = DebounceCount + 1
ELSE
DebounceCount = 0
ENDIF

IF DebounceCount = 10 THEN ' Button released for duration of debounde time?
DebounceCount = 0
State = Done ' Transition to next state.
ENDIF

' The timer is no longer running so we pause here for the same duration.
PAUSE 2
'---------------------------------------------------------------------------------------------------

CASE Done
' This is where we perform the calculations and present the result.

If TMR1IF THEN ' Interrupt flag is set if counter has rolled over
HSEROUT["ERROR: Pulse counter overflow, can't compute...", 13]
Goto Abort
ENDIF

IF TimeOverflow THEN ' Flag set if seconds variable has rolled over
HSEROUT["ERROR: Seconds counter overflow, can't compute...", 13]
Goto Abort
ENDIF

' Each count from the driveshaft equals 0.6m of car movement.
' Lets say the pulse count is 12345 and the seconds count is 503.
' We've traveled 12345*0.6=7407 metres in 503 seconds, 7407/503*3.6=53.0km/h
' Using the ** operator allows us to effectively multiply by fractions of 65536 so:
'
' 12345 ** 39322 = 7407
' 7407 * 36 = 266652
' 266652 / 503 = 530 which we interpret as 53.0km/h
'
' 6045 pulses in 312 seconds,
' 6045*0.6/312*3.6=41.85km/h:
' 6045 ** 39322 = 3627
' 3627 * 36 = 130572
' 130572 / 312 = 418 which we interpret at 41.8km/h
'
' 53203 pulses in 1412 seconds,
' 53203*0.6/1412*3.6=81.39km/h:
' 53202 ** 39322 = 31922
' 31922 * 36 = 1149192
' 1149192 / 1412 = 813 which we interpret as 81.3km/h

Distance.HIGHBYTE = TMR1H
Distance.lowbyte = TMR1L
Distance = Distance ** 39322 ' Multiply by 0.6, Distance is now in metres

Temp = Distance * 36
AverageSpeed = DIV32 Seconds ' 215 for 21.5km/h

HSEROUT[DEC (TMR1H * 256 + TMR1L), " pulses",13]
HSEROUT[DEC Distance, "m", 13]
HSEROUT[DEC Seconds, ".", DEC3 ms, "s",13]
HSEROUT[DEC AverageSpeed/10,".", DEC AverageSpeed//10, "km/h",13]
Abort:
ReadyIndicator = 1
ErrorIndicator = 0
State = Idle

END SELECT

Goto Main

'-------------------------------------- Subroutines ------------------------------------------------
UpdateTime:
' Our timebase is 500Hz.
ms = ms + 2
If ms = 1000 then
ms = 0
seconds = seconds + 1

' We're using DIV32 to calculate the average speed. It does not allow the divisor
' to be larger than 32767 (which is 9 hours and some minutes so it should be fine)
' but since we're good programmers we're going to check for it anyway.
If Seconds > 32767 THEN
TimeOverflow = 1
ErrorIndicator = 1
ENDIF

ENDIF
RETURN

Mugelpower
- 15th September 2017, 07:52
Hello Henrik,

thanks a lot, Iīve never be able to program that way and so long, Iīm more the "Hello World" guy.

Is it possible to put a "LCDOUT" in betwen or does it take more than 2ms ? I need a display as interface.

Do I need a serial LCD display?

regards

Mugelpower

HenrikOlsson
- 15th September 2017, 08:35
You can use a LCD and replace the HSEROUT statements with LCDOUT if you want. When the measurement is complete it doesn't matter how long it takes because then we're not timing anymore.

If you want the display to update DURING the measurment (like showing elapsed time) it'll be a bit more complicated - you never said anything about that I don't think :-)

It's certainly doable but we'll need to think about how to best do it. But highly I suggest getting a prototype up and running FIRST.

/Henrik.

Mugelpower
- 15th September 2017, 12:05
Hello Harry,

sorry about that . For me it was so obvious you have to drive the car with constant speed so you need to check the speedo the whole time. And I want to build a precise speedo that shows not only the actual speed but the average speed including stops, accelerations etc. Its impossible to drive a constant speed without a closed loop. Its not a straight road to drive but the roads have turns and maybe a stop. And we donīt know wheres the end of
the measured distance so we have to maintain the average speed until the end of the road mission is cleary marked.
So yes, we have to look constantly on the speedo , aybe more on the speedo than on the road.
Regards
Mugelpower

Mugelpower
- 15th September 2017, 12:08
Yes Iīm beginning to build the prototype next weekend.

Greetings to all earthlings

HenrikOlsson
- 15th September 2017, 12:45
No worries, it happens every time. Things you might think are obvious aren't to those not "into" it.

OK, so you want the display to "continously" show the average speed as you're driving? Isn't that cheating? :-)

Let me see if I got this right now:
* A press of the button resets everything and starts the measurment. Time=0, Distance=0
* While the measurement is taking place the LCD is updated periodically with the average speed in km/h.
* A second press of the button stops the measurement.
* A third press of the button resets everything and starts over.

Is that what you want? A 2x16 LCD, would that work?

I will have to build something up, unfortunately it won't be with an 18F1320 but I'll see what suitable device I can find. Haven't done any PBP in a while, this was a fun little project.

/Henrik - not Harry :-)

Mugelpower
- 15th September 2017, 17:16
Hello Henrik,

no its not cheating because everyone cheats, theres no other way to get the time right by less than a second after driving several kilometers. Some guys/dolls have up to 5 electronic devices in front of them while driving.
Most use mobile phone apps, but really,thats soooo teenagerlike and cheap......
Seeing right now:
*yes
*yes
*yes
*yes

Yes

Youre really into this , arenīt you? Makes me happy and maybe we could convince lots of people to buy an Oldtimer and put some PIC based electronics in that.....

Mugelpower

Mugelpower
- 15th September 2017, 17:20
I forgot to mention:

most success in Oldtimer rallies will occur when the woman drives and the man reads the maps and road book...... woman reading road books is surefire for lots of screaming, whining and divorces.....

HenrikOlsson
- 18th September 2017, 19:09
Hello,
I've played around with the code a bit, got it up and running on my EasyPIC7 development board using a 18F45K22 device. It counts, it times, it calculates and it displays (during and after measurement).

8465

The code I post here are what's running on my device, if you plan to use another device you must:
A) Replace the CONFIG with correct ones for your device
B) Make sure the PIC runs at 8MHz using whatever oscillator (external x-tal is probably best for timing accuracy)
C) Change the aliases to suit the hardware
D) Change the hardware setup section (TRIS, ANSEL, OSCCON, T1CON etc)



'--- Device configuration - replace with config for selected target ------------
#CONFIG
CONFIG FOSC = HSMP ; HS oscillator (medium power 4-16 MHz)
CONFIG PLLCFG = OFF ; Oscillator used directly
CONFIG PRICLKEN = OFF ; Primary clock can be disabled by software
CONFIG FCMEN = OFF ; Fail-Safe Clock Monitor disabled
CONFIG IESO = OFF ; Oscillator Switchover mode disabled
CONFIG PWRTEN = OFF ; Power up timer disabled
CONFIG BOREN = SBORDIS ; Brown-out Reset enabled in hardware only (SBOREN is disabled)
CONFIG BORV = 190 ; VBOR set to 1.90 V nominal
CONFIG WDTEN = ON ; WDT is always enabled. SWDTEN bit has no effect
CONFIG WDTPS = 32768 ; 1:32768
CONFIG CCP2MX = PORTC1 ; CCP2 input/output is multiplexed with RC1
CONFIG PBADEN = OFF ; PORTB<5:0> pins are configured as digital I/O on Reset
CONFIG CCP3MX = PORTB5 ; P3A/CCP3 input/output is multiplexed with RB5
CONFIG HFOFST = ON ; HFINTOSC output and ready status are not delayed by the oscillator stable status
CONFIG T3CMX = PORTC0 ; T3CKI is on RC0
CONFIG P2BMX = PORTD2 ; P2B is on RD2
CONFIG MCLRE = EXTMCLR ; MCLR pin enabled, RE3 input pin disabled
CONFIG STVREN = ON ; Stack full/underflow will cause Reset
CONFIG LVP = OFF ; Single-Supply ICSP disabled
CONFIG XINST = OFF ; Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
CONFIG DEBUG = OFF ; Disabled
CONFIG CP0 = OFF ; Block 0 (000800-001FFFh) not code-protected
CONFIG CP1 = OFF ; Block 1 (002000-003FFFh) not code-protected
CONFIG CP2 = OFF ; Block 2 (004000-005FFFh) not code-protected
CONFIG CP3 = OFF ; Block 3 (006000-007FFFh) not code-protected
CONFIG CPB = OFF ; Boot block (000000-0007FFh) not code-protected
CONFIG CPD = OFF ; Data EEPROM not code-protected
CONFIG WRT0 = OFF ; Block 0 (000800-001FFFh) not write-protected
CONFIG WRT1 = OFF ; Block 1 (002000-003FFFh) not write-protected
CONFIG WRT2 = OFF ; Block 2 (004000-005FFFh) not write-protected
CONFIG WRT3 = OFF ; Block 3 (006000-007FFFh) not write-protected
CONFIG WRTC = OFF ; Configuration registers (300000-3000FFh) not write-protected
CONFIG WRTB = OFF ; Boot Block (000000-0007FFh) not write-protected
CONFIG WRTD = OFF ; Data EEPROM not write-protected
CONFIG EBTR0 = OFF ; Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
CONFIG EBTR1 = OFF ; Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
CONFIG EBTR2 = OFF ; Block 2 (004000-005FFFh) not protected from table reads executed in other blocks
CONFIG EBTR3 = OFF ; Block 3 (006000-007FFFh) not protected from table reads executed in other blocks
CONFIG EBTRB = OFF ; Boot Block (000000-0007FFh) not protected from table reads executed in other blocks
#ENDCONFIG

DEFINE OSC 8 ' We're running at 8MHz. The code is based on that fact.

DEFINE LCD_DREG PORTB ' Set LCD Data port
DEFINE LCD_DBIT 0 ' Set starting Data bit (0 or 4) if 4-bit bus
DEFINE LCD_RSREG PORTB ' Set LCD Register Select port
DEFINE LCD_RSBIT 4 ' Set LCD Register Select bit
DEFINE LCD_EREG PORTB ' Set LCD Enable port
DEFINE LCD_EBIT 5 ' Set LCD Enable bit
DEFINE LCD_BITS 4 ' Set LCD bus size (4 or 8 bits)
DEFINE LCD_LINES 2 ' Set number of lines on LCD
DEFINE LCD_COMMANDUS 1500 ' Set command delay time in us
DEFINE LCD_DATAUS 40 ' Set data delay time in us


'---------- Variables ------------
Hours VAR BYTE ' Elapsed time, hours
Minutes VAR BYTE ' Elapsed time, minutes
Seconds VAR WORD ' Elapsed time, seconds
ms VAR WORD ' Elapsed time, milliseconds
State VAR BYTE ' State machine variable, keeps track of what we're doing
Distance VAR WORD ' Distance traveled in metres, calculated based on pulsecount
AverageSpeed VAR WORD ' Final result of measurement, in units of 0.1km/h (123=12.3km/h)
Temp var WORD ' Used during calculation
DebounceCount VAR BYTE ' Used to debounce the start/stop button
LCDSequence VAR BYTE ' Used to update the display step-by-step
TimeOverflow VAR BIT ' Gets set if the seconds variable grows too big.
UpdateDisplay VAR BIT ' Flag that is set when it't time to update the LCD

'----------------- Constants ----------------------
Idle CON 0
Starting CON 1
Counting CON 2
Stopping CON 3
Done CON 4
Error CON 5

'------------------- Aliases - change to suit hardware -------------------------
StartStopButton VAR PORTB.6 ' Low when pressed
ReadyIndicator VAR LATA.0 ' High when idle/ready
RunIndicator VAR LATA.1 ' High when counting/timing
ErrorIndicator VAR LATA.2 ' High on count or time overflow
TMR1ON VAR T1CON.0 ' Run/stop bit for TMR1
TMR2ON VAR T2CON.2 ' Run/stop bit for TMR2
TMR1IF VAR PIR1.0 ' TMR1 interrupt flag
TMR2IF VAR PIR1.1 ' TMR2 interrupt flag

'--------------- Target setup - change to suit target device -------------------
ANSELA = 0 ' All pins as digital
ANSELB = 0
ANSELC = 0
ANSELD = 0
ANSELE = 0

TRISA = %11111000 ' Status LEDs on RC0-RC2
LATA = %00000000

TRISB = %01000000 ' LCD on RB0-RB5, Start/Stop on RB6
LATB = %00000000 ' All pins low


'OSCCON = %01110010 ' For 18F1320, Internal oscillator, 8MHz
'T1CON = %00000010 ' For 18F1320, External clock on RB6, timer OFF for now.
T1CON = %10000000 ' For 18F45K22
T2CON = %00000010 ' 1:16 prescaler, TMR2 off for now
PR2 = 249 ' T2 interrupt flag set every 250*16 tick

PAUSE 500

Start:
State = Idle
ReadyIndicator = 1
RunIndicator = 0
ErrorIndicator = 0
LCDOUT $FE, 1, " Ready"
LCDOUT $FE, $C0, "Press start/stop"

Main:
Select CASE State

Case Idle
If StartStopButton = 0 THEN ' If the button is pressed we
'LCDOUT $FE, 1 ' Clear the screen
Hours = 0
Minutes = 0
Seconds = 0
ms = 0
TMR1H = 0 ' reset the pulse count
TMR1L = 0
TMR2 = 0 ' reset the timebase
TMR1IF = 0 ' clear the overflow flag
TMR2IF = 0 ' clear the timebase tick flag
TMR1ON = 1 ' enable the counter to count pulses from the driveshaft
TMR2ON = 1 ' enable the 2ms timer tick
ReadyIndicator = 0 ' update status LEDs
RunIndicator = 1
ErrorIndicator = 0
TimeOverflow = 0 ' clear overflow flag
State = Starting ' and move to the next state.
ENDIF
'---------------------------------------------------------------------------------------------------
CASE Starting
IF TMR2IF THEN ' TMR2IF gets set every 2ms
TMR2IF = 0 ' reset flag
GOSUB Update ' update our time registers

' Wait for the button to be released before transitioning to the next state.
If StartStopButton = 1 THEN ' Button released?
DebounceCount = DebounceCount + 1
ELSE
DebounceCount = 0 ' Button must be released 10 ticks in a row.
ENDIF

IF DebounceCount = 9 THEN ' Button has been released for 10 ticks
DebounceCount = 0
State = Counting ' Transition to next state.
ENDIF
ENDIF
'---------------------------------------------------------------------------------------------------

Case Counting
IF TMR2IF THEN ' 2ms tick?
TMR2IF = 0 ' clear flag and
GOSUB Update ' update our time registers

If StartStopButton = 0 THEN ' Button pressed?
RunIndicator = 0
TMR1ON = 0 ' Disable pulse counter
TMR2ON = 0 ' Disable timer
State = Stopping
ENDIF
ENDIF

IF TMR1IF THEN ' Pulse counter overflow
TMR1ON = 0 ' Disable pulse counter
TMR2ON = 0 ' Disable timebase
RunIndicator = 0
ErrorIndicator = 1
State = Error
ENDIF

' Here we can do anything else that needs doing while the
' system is counting but it's very important that it does
' not take longer than 2ms or we will lose time.
'---------------------------------------------------------------------------------------------------
CASE Stopping
' Wait for the button to released before transitioning to the next state,
If StartStopButton = 1 THEN ' Is button released?
DebounceCount = DebounceCount + 1
ELSE
DebounceCount = 0
ENDIF

IF DebounceCount = 10 THEN ' Button released for duration of debounde time?
DebounceCount = 0
State = Done ' Transition to next state.
ENDIF

' The timer is no longer running so we pause here for the same duration.
PAUSE 2
'---------------------------------------------------------------------------------------------------

CASE Done
' This is where we perform the calculations and present the result.

' Each count from the driveshaft equals 0.6m of car movement.
' Lets say the pulse count is 12345 and the seconds count is 503.
' We've traveled 12345*0.6=7407 metres in 503 seconds, 7407/503*3.6=53.0km/h
' Using the ** operator allows us to effectively multiply by fractions of 65535 so:
'
' 12345 ** 39322 = 7407
' 7407 * 36 = 266652
' 266652 / 503 = 530 which we interpret as 53.0km/h
'
' 53203 pulses in 1412 seconds,
' 53203*0.6/1412*3.6=81.39km/h:
' 53202 ** 39322 = 31922
' 31922 * 36 = 1149192
' 1149192 / 1412 = 813 which we interpret as 81.3km/h

Distance.HIGHBYTE = TMR1H
Distance.lowbyte = TMR1L
Distance = Distance ** 39322 ' Multiply by 0.6, Distance is now in metres

Temp = Distance * 36
AverageSpeed = DIV32 Seconds ' 215 for 21.5km/h

LCDOUT $FE, 1, DEC Distance/1000, ".", DEC3 Distance//1000, "km in ", DEC2 Minutes, ":", DEC2 Seconds
LCDOUT $FE, $C0, "Speed: ", DEC AverageSpeed/10,".", DEC AverageSpeed//10, "km/h"

ReadyIndicator = 1
State = Idle


'------------------------------------------------------------------------------
CASE Error
If TMR1IF THEN ' Interrupt flag is set if counter has rolled over
LCDOUT $FE, 1, "ERROR Pulse cnt"
ENDIF

IF TimeOverflow THEN ' Flag set if seconds variable has rolled over
LCDOUT $FE, 1, "ERROR Timer"
ENDIF

LCDOUT $FE, $C0, "Restart to clear"

State = Idle
END SELECT


Goto Main

'-------------------------------------- Subroutines ------------------------------------------------
Update:
' Our timebase is 500Hz.
ms = ms + 2
If ms = 1000 then
ms = 0
seconds = seconds + 1

IF Seconds // 60 = 0 THEN
Minutes = Minutes + 1
IF Minutes = 60 THEN
Minutes = 0
Hours = Hours + 1
ENDIF
ENDIF

UpdateDisplay = 1 ' We're triggering a display update every second

Distance.HIGHBYTE = TMR1H
Distance.lowbyte = TMR1L
Distance = Distance ** 39322 ' Multiply by 0.6, Distance is now in metres

Temp = Distance * 36
AverageSpeed = DIV32 Seconds ' 215 for 21.5km/h

' We're using DIV32 to calculate the average speed. It does not allow the divisor
' to be larger than 32767 (which is 9 hours and some minutes so it should be fine)
' but since we're good programmers we're going to check for it anyway.
If Seconds > 32767 THEN
TimeOverflow = 1
RunIndicator = 0
ErrorIndicator = 1
State = Error
ENDIF

ENDIF

' Since we have a 2ms time tick and we're polling the that tick with software
' we can not allow ourselves to spend too much time doing anything else.
' For this reason we've divided the while display update process in sections
' so we do it little by little while still going back checking the timer tick
' so that we don't lose track of time.
IF UpdateDisplay THEN
Select Case LCDSequence
Case 1
LCDOUT $FE, 128 ' Cursor at first character, first line
CASE 2
LCDOUT DEC2 Minutes, ":"
CASE 3
LCDOUT DEC2 Seconds//60
CASE 4
LCDOUT " "
IF Distance < 10000 THEN
LCDOUT " "
ENDIF
CASE 5
LCDOUT DEC Distance/1000, ".", DEC3 Distance//1000
CASE 6
LCDOUT "km "
CASE 7
LCDOUT $FE, $C0 ' Cursor at second line
CASE 8
LCDOUT DEC AverageSpeed/10,".", DEC AverageSpeed//10, "km/h "
CASE 9
LCDOUT " "
END SELECT

LCDSequence = LCDSequence + 1
IF LCDSequence = 10 THEN
LCDSequence = 0
UpdateDisplay = 0
ENDIF
ENDIF

RETURN

/Henrik.

Mugelpower
- 19th September 2017, 11:00
hello Henrik,

wow, you are really fast! I had no time to build anything we drove another Oldtimer-rallye made Place 9 in our class and cups were down to #8, haha so no cup either.

1 Questions:

1. I need to calibrate the distanc efactor. You wrote

Distance = Distance ** 39322 ' Multiply by 0.6, Distance is now in metres

so I have to change this 39322 am I right?

I ordered the parts (the PIC has 40 pins and is large) and will fix it on a veroboard.

Regards

Mugelpower

HenrikOlsson
- 19th September 2017, 14:14
Hi,
You don't have to use the 40-pin 18F45K22. You can use the 28-pin 18F25K22 with very minor changes to the code (CONFIG) or you can use another PIC - as long as it has a TMR1 & TMR2. But, if you want the code to run as-is then 45K22 it is.

If the distance per pulse is not 0.6m then you need to change the 39322 constant. For 0.61m it would be 39977 and for 0.59m it would be 38666 so as I think you can see you should be able to calibrate very accuratly. The final result will always suffer from truncation in the math routines so the display can be short as much as one count (0.6m or whatever distance one pulse equals) - which I don't think matters much.

/Henrik.

Mugelpower
- 19th September 2017, 15:21
Hello Henrik,

I guess Iīm blind. In the first program you had RB6 as counter input. In the second program RB6 is the start-stop button. Where did the counter input go?

regards
Mugelpower

HenrikOlsson
- 19th September 2017, 16:18
On the 18F1320 (for which I tried to write the original program) the T1CKI is on RB6. On the 18F45K22 the T1CKI is on RC0.

/Henrik.

HenrikOlsson
- 18th October 2017, 20:28
Did you ever get it running on your end or did you loose interest?

I'm cleaning up my desk, can I put the dev board away?
It's best to ask because if I don't Murphy tells me there will be a new question, bug report or feature request tomorrow :-)

/Henrik.

Mugelpower
- 31st October 2017, 12:26
Hello Henrik,
no Iīm not out of interest. I bought some 18F45K22 and 16Mhz crystal generators and then realized my PICKIT2 doesnīt support this device. I really appreciate your help and Iīm astonishied about your speed. But I need some more time to get
into the hardware. Iīll realize that with a 18F1320 because i am already working on a project : a water pump, fuel pump and fan controller. Iīm shure the configs for the speedo project will drive me crazy.

HenrikOlsson
- 31st October 2017, 16:22
If you look at this thread: http://www.microchip.com/forums/m540021.aspx I Think you'll find that you can get your PICKit2 working with the 45K22. If you don't wan't to mess around just get a PICKit3 from Digikey, Mouser or Microchip Direct and be done with it :-)

The timer code, as written, expects an 8MHz Clock, you'll need to change some things if you're using 16MHz, the CONFIG isn't one of them though. It should run fine with the CONFIG as posted.

/Henrik.

Mugelpower
- 1st November 2017, 08:35
Hello Henrik,

Iīm stupid. Looked at the cristals and they were the right 8 MHz ones. Ordered the PICkit 3 yesterday so i can move along.

Demon
- 10th November 2017, 19:45
POST PICTURES!!!

I get a kick out of looking at people's projects. It gives me ideas on how to do stuff.

Art's slacking off with posting youtube videos, so someone else has to PIC up the slack. :D

Robert

Mugelpower
- 19th November 2017, 19:25
Hello Demon,

i will post pictures. my computer in the basement that i use for PIC programming runs on XP and has no internet connection. I have to learn how to speed up posting mobile phone pics to my email account and then to here.....no digital native I am.

Mugelpower
- 8th December 2017, 03:24
8528
This is my wife and the car that needs an electronic update.
Bought PBP3 gold yesterday.
will try Henriks code with PBP3.

Demon
- 12th December 2017, 19:43
Now you have to post a video. I want to hear those side pipes growl. :D

Mugelpower
- 13th December 2017, 11:24
Hello Demon,
Iīm not shure if I have one. But I can link to a video of her other Mustang. Yes my wife has two of them and yes both are red but the older one 1979 is a coupe and the 1983 with the picture above is a convertible. The convertible has only about 250 hp and only one side pipe.This is just for driving around. The coupe had two side pipes and about 450 hp. 1/4 mile best was 11.39.This car is too loud for everyday use an needs too much fuel. In the video the sound is bad because there were other cars narby while filming so you have to sort out the V8 sound.
The coupe undergoes a little wrenching around because I built a rollcage in it and set another engine and transmission into that should have 650 to 700 hp. My wife will drive low 11s to high 10s on the 1/4 mile.
Heres a link to a video with our favorite opponent:

https://www.youtube.com/watch?v=nNG5cItno3w

regards:

Mugelpower