Code:
'--- 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.
Bookmarks