#rem ########################################################## # # # PICAXE REV COUNTER # # # # Code Version :1U # # Date :December 2006 # # PICAXE Type :18X # # Editor Software :5.07 # # # ########################################################## NOTE1: CYLINDER SELECT VALUES ============================= R5 R4 R3 Combined ADC Range Representing (Ohms) (Ohms) (Ohms) Resistance VALUE Limits Cylinders (SW1) (SW2) (SW3) (Ohms) (Ideal) ---------------------------------------------------------------------- 0 0 0 0 0 0 to 112 1 0 0 5K1 5K1 225 113 to 295 2 0 10K 0 10K 365 296 to 415 Not used 0 10K 5K1 15K1 466 416 to 502 4 15K 0 0 15K 465 416 to 502 Not used 15K 0 5K1 20K1 539 503 to 566 6 15K 10K 0 25K 594 567 to 617 Not used 15K 10K 5K1 30K1 640 618 to 1023 8 So, in summary: SW1 SW2 Sw3 CYLINDERS ========================== ON ON ON 1 ON ON OFF 2 ON OFF OFF 4 OFF ON OFF 6 OFF OFF OFF 8 #endrem '######################################################### '# EEPROM # '######################################################### EEPROM 0,(4) 'This is the Brightness setting. '######################################################### '# VARIABLES # '######################################################### 'Word0 (b0 and b1) Symbol Max7219Register = b0 Symbol Cylinders = b0 Symbol Max7219Data = b1 Symbol TimedOutFlag = b0 'Word1 (b2 and b3) Symbol AvValue = w1 Symbol DisplayValueW = w1 'Word2 (b4 and b5) Symbol ADCValue = w2 Symbol PulseWidth = w2 Symbol WRemainder = w2 Symbol MSW = w2 Symbol Address = b4 Symbol TimeSinceRefresh = w2 'Word3 (b6 and b7) Symbol OldestValue = w3 Symbol LSW = w3 Symbol OldestValueLSB = b6 Symbol OldestValueMSB = b7 Symbol Digit = b7 'Word4 (b8 and b9) Symbol NewValue = w4 Symbol NewValueLSB = b8 Symbol BitCount = b8 Symbol NewValueMSB = b9 Symbol DisplayStarted = b9 'Word5 (b10 and b11) Symbol MovAvSum = w5 Symbol Divisor = w5 Symbol LoopCount = b10 Symbol Char_7219 = b11 Symbol SampleDuration = w5 Symbol Timer1ValueW = w5 'Word6 (b12 and b13) Symbol TempW = w6 Symbol Index = b12 Symbol Brightness = b13 '######################################################### '# CONSTANTS # '######################################################### 'Miscellaneous Symbol Samples = 6 'The RPM samples that are stored and averaged. Don't exceed '6 otherwise there is likely to be overflow in CalcMovingAverage. Symbol StoreBytes = Samples * 2 Symbol DisplayRefreshRate = 250 'ms Symbol TargetSampleInterval = 120 'ms Symbol Yes = 1 Symbol No = 0 'Sensor Thresholds Symbol RPMHigh = 3000 'RPM Symbol RPMLow = 2800 'RPM Symbol ShiftHigh = 5700 'RPM Symbol ShiftLow = 5500 'RPM 'Input Pin Assignments Symbol CylinderSelectPin = 0 Symbol BrightnessInput = Input2 Symbol FlipFlopPin = 7 'Output Pin Assignments Symbol ShiftLampPin = 1 Symbol SPIMotorPin = 3 Symbol DIN_7219 = 5 'to MAX7219 Leg 1 (DIN) Symbol LOAD_7219 = 6 'to MAX7219 Leg 12 (LOAD(CS bar)) Symbol CLK_7219 = 7 'to MAX7219 Leg 13 (CLK) 'RAM constants '(Useable RAM on an 18X is from 80 to 127 and 192 to 239) 'The RAM block 80 to 127 is used to store 6 * RPM Word values Symbol RAM_ValuesStoreStart = 80 Symbol RAM_Cylinders = 192 Symbol RAM_MovAvSum = 193 'and 194 Symbol RAM_Index = 195 Symbol RAM_TimedOutFlag = 196 Symbol RAM_TimeSinceRefresh = 197 'and 198 'EEPROM Constants Symbol EEPROM_Brightness = 0 'MAX7219 register values Symbol DECODE_MODE = $09 Symbol INTENSITY = $0A Symbol SCAN_LIMIT = $0B Symbol SHUTDOWN = $0C Symbol DIGIT_TEST = $0F Symbol DIGIT_0 = $01 Symbol DIGIT_1 = $02 Symbol DIGIT_2 = $03 Symbol DIGIT_3 = $04 Symbol DIGIT_4 = $05 Symbol DIGIT_5 = $06 Symbol DIGIT_6 = $07 Symbol DIGIT_7 = $08 'MAX7219 data byte values Symbol DIGIT_TEST_OFF = $00 Symbol DIGIT_TEST_ON = $01 Symbol DISP_Blank = $ff Symbol DISP_H = $fc Symbol DISP_Dash = $fa 'Execution times (determined by separate tests using Timer1): ' - GetRPM (excluding the Pulsin command) = 10ms ' - CalcMovingAverage = 7ms ' - SetRPMIndicators = 4ms Symbol EXEC_SampleExPulsin = 21 'ms. '######################################################### '# INITIALISE # '######################################################### SetFreq M8 'This maximises the resolution and is what the RPM maths is based on. Pause 4000 'Wait 2 seconds for the circuit to settle down. Gosub FlashShiftLight 'Confirm that the shift light is functional Gosub GetCylinderSetting Gosub Initialise_7219 Gosub InitialiseTimer1 '######################################################### '# MAIN PROGRAM # '######################################################### Main: Gosub ResetAndStartTimer1 Gosub GetRPM Gosub CalcMovingAverage Gosub SetRPMIndicators 'If the elasped duration since resetting Timer1 (SampleDuration) is less than 'TargetSampleInterval then pause for the difference. This ensures that 'the sample interval is constant. Gosub GetTime If SampleDuration < TargetSampleInterval Then TempW = TargetSampleInterval - SampleDuration * 2 '*2 because 8MHz clock Pause TempW SampleDuration = TargetSampleInterval Endif 'Update TimeSinceRefreshed Peek RAM_TimeSinceRefresh, Word TimeSinceRefresh TimeSinceRefresh = TimeSinceRefresh + SampleDuration Poke RAM_TimeSinceRefresh, Word TimeSinceRefresh 'If the display refresh time has been missed then refresh the Display If TimeSinceRefresh >= DisplayRefreshRate Then RefreshDisplay 'Predict if the next Sample will delay the display refresh. 'The predition is based on adding SampleDuration onto 'the current TimeSinceRefresh. TempW = TimeSinceRefresh + SampleDuration If TempW < DisplayRefreshRate Then CheckBrightnessButton 'The prediction is that the next Sample will delay the display refresh. 'Pause and then display, rather than take another sample (Getting the 'display refreshed at a constant rate is more important than sampling). TempW = DisplayRefreshRate - TimeSinceRefresh * 2 Pause TempW RefreshDisplay: Poke RAM_TimeSinceRefresh,0,0 'Set TimeSinceRefreshed to 0 Peek RAM_TimedOutFlag,TimedOutFlag If TimedOutFlag = Yes Then Char_7219 = DISP_Dash '---- Meaning no signal Gosub DisplaySameChars ElseIf AvValue > 9999 Then Char_7219 = DISP_H 'HHHH Meaning RPM too High Gosub DisplaySameChars Else Gosub DisplayValue Endif CheckBrightnessButton: If BrightnessInput = 0 Then Gosub ChangeBrightness Goto Main '######################################################### '# SUBROUTINES # '######################################################### Initialise_7219: 'Initialises the MAX 7219 Max7219Register = SCAN_LIMIT Max7219Data = $03 'Display digits 0 to 3 Gosub Send_data_7219 Max7219Register = DECODE_MODE Max7219Data = $FF 'Code B decode for all digits Gosub Send_data_7219 Max7219Register = INTENSITY Read EEPROM_Brightness, Max7219Data 'Get the stored value from EEPROM Gosub Send_data_7219 Char_7219 = DISP_Blank Gosub DisplaySameChars 'Ensure all digits are blank Max7219Register = SHUTDOWN Max7219Data = $01 'Brings into normal operation, from initial shutdown Gosub Send_data_7219 'Set the MAX 7219 into test digit mode Max7219Register = DIGIT_TEST Max7219Data = DIGIT_TEST_ON Gosub Send_data_7219 Pause 4000 'Wait 2 seconds (8MHz clock) 'Turn off the MAX 7219 test digit mode Max7219Register = DIGIT_TEST Max7219Data = DIGIT_TEST_OFF Gosub Send_data_7219 Return Send_data_7219: 'Sends data to the MAX 7219 'ON ENTRY: b0 contains the register to send data to ' b1 contains the data byte to send 'ON EXIT : Both b0 and b1 are 0 'Send the data Low LOAD_7219 For BitCount = 0 To 7 OutPin5 = Bit7 'OutPin5 = DIN_7219 Pulsout CLK_7219, 1 b0 = b0 * 2 Next For BitCount = 0 To 7 OutPin5 = Bit15 'OutPin5 = DIN_7219 Pulsout CLK_7219, 1 b1 = b1 * 2 Next High LOAD_7219 Return GetCylinderSetting: 'Get the ADC value associated with the DIP switch settings and 'from it determine the number of cylinders. 'ON EXIT: The number of cylinders is stored in RAM_Cylinders. ReadADC10 CylinderSelectPin,ADCValue If ADCValue >=618 Then Cylinders = 8 Elseif ADCValue >= 503 Then Cylinders = 6 Elseif ADCValue >= 416 Then Cylinders = 4 Elseif ADCValue >= 113 Then Cylinders = 2 Else Cylinders = 1 Endif Poke RAM_Cylinders, Cylinders Return GetRPM: 'Get the new RPM value. 'Read the ignition pulse-width from the Flip-flop and calculate the 'RPM value using the formula RPM = 24,000,000 / (Cylinders * PulseWidth). '(Refer to separate article for more details of the calculations). Pulsin FlipFlopPin,0,PulseWidth 'Measure the pulse width If PulseWidth > 0 Then Poke RAM_TimedOutFlag, No Else 'Pulsin command has timed out. Poke RAM_TimedOutFlag, Yes PulseWidth = 65535 Endif 'Calculate the RPM. Peek RAM_Cylinders,Cylinders TempW = 65535 / Cylinders If PulseWidth > TempW Then 'RPM <367 MSW = Cylinders ** PulseWidth LSW = Cylinders * PulseWidth TempW = LSW / 368 TempW = MSW * 178 + TempW NewValue = 65217 / TempW Else 'RPM >=367 TempW = Cylinders * PulseWidth NewValue = 65217 / TempW * 368 WRemainder = 65217 // TempW TempW = TempW / 46 NewValue = 2 * WRemainder / TempW * 4 + NewValue WRemainder = 2 * WRemainder // TempW NewValue = WRemainder * 4 / TempW + NewValue Endif 'Put a cap of 9999 on the new RPM value to ensure that MovAvSum doesn't 'overflow. NewValue = NewValue Max 9999 Return DisplaySameChars: 'Displays the same character for each of the four digits. 'ON ENTRY: Char_7219 holds the character to display. For LoopCount = DIGIT_0 To DIGIT_3 Max7219Register = LoopCount Max7219Data = Char_7219 Gosub Send_data_7219 Next Return DisplayValue: 'Display a value on the MAX7219 digits. 'Blanks out all leading zeroes. 'ON ENTRY: DisplayValueW (or AVValue) is the value to display. 'Note AvValue uses the same underlying variable as DispValueW. 'Initialise display variables Divisor =1000 DisplayStarted = No WRemainder = DisplayValueW 'Select each digit and display the appropriate value. For Digit = DIGIT_3 to DIGIT_0 Step -1 Max7219Register = Digit 'Calculate the value to display Max7219Data = WRemainder / Divisor WRemainder = WRemainder // Divisor 'Ensure any leading zeroes are blanked off. If Max7219Data = 0 And DisplayStarted = No Then Max7219Data = DISP_Blank 'Blank digit Else DisplayStarted = Yes End if 'Display the value on the selected digit. Gosub Send_data_7219 Divisor = Divisor / 10 Next Return CalcMovingAverage: 'Calculate the new moving average value. 'ON ENTRY: NewValue holds the new value that should be added to the store. 'ON EXIT: AvValue holds the new calculated average. 'Work out the address of the currently indexed word in the Store. This 'address holds the oldest value. Peek RAM_Index,Index Address = Index * 2 + RAM_ValuesStoreStart 'Extract the oldest RPM Value from the store, and 'overwrite it with the New Value. Peek Address,OldestValueLSB Poke Address,NewValueLSB Inc Address Peek Address,OldestValueMSB Poke Address,NewValueMSB 'Increment the index and set it to 0 if it = Samples Index = Index + 1 //Samples 'Save the new index Poke RAM_Index,Index 'Update the moving average sum. Peek RAM_MovAvSum,Word MovAvSum MovAvSum = MovAvSum + NewValue - OldestValue Poke RAM_MovAvSum,Word MovAvSum 'Calculate the new moving average AvValue = MovAvSum / Samples Return ChangeBrightness: 'Change the brightness of the MAX7219 digits. 'Get the current brightness value (1 to 8) Read EEPROM_Brightness, Brightness Brighter: 'Increment the current Brightness value, but set to 1 if > 8 Brightness = Brightness + 1 //9 MIN 1 'Update the display with the new brightness value Max7219Data = Brightness * 2 - 1 Max7219Register = INTENSITY Gosub Send_data_7219 'Display the current Brightness value on all digits DisplayValueW = Brightness * 1000 DisplayValueW = Brightness * 100 + DisplayValueW DisplayValueW = Brightness * 10 + DisplayValueW DisplayValueW = Brightness + DisplayValueW Gosub DisplayValue 'Pause for one second (8MHz clock) to give user time to review 'the brightness and decide if it's ok. Pause 2000 If BrightnessInput = 0 Then Brighter 'If user has released the button, wait approximately 5 seconds to see 'if they are going to press it again. LoopCount = 0 Do Pause 39 '39 * 255 = 9945 = 4.9seconds (8MHz clock) If BrightnessInput = 0 Then Brighter Inc LoopCount Loop Until LoopCount = 255 'Save the brightness value Write EEPROM_Brightness, Brightness Return FlashShiftLight: 'Flash Shift light 3 times to confirm it's functioning. Low ShiftLampPin 'Not strictly necessary if just calling this routine 'from power up. For LoopCount = 1 To 6 Toggle ShiftLampPin Pause 1000 'half a second (8MHz clock) Next Return SetRPMIndicators: 'Sets the SPIMotor signal and the ShiftLamp, depending on the 'Av RPM value. 'ON ENTRY: AvValue holds the new Average RPM value 'If the new Average RPM is greater than the RPMHigh threshold then output 'a PWM signal. If it's below the RPMLow threshold then turn the PWM signal 'off. This gives it some hysterisis and stops the motor oscillating on and 'off when the value is hovering around the threshold. If AvValue > RPMHigh Then PWMout SPIMotorPin,249,200 Elseif AvValue < RPMLow Then PWMout SPIMotorPin,0,0 Endif 'If the new Average RPM is greater than the ShiftHigh threshold then turn 'the Shift Lamp on. If it's below the ShiftLow threshold then turn the 'Shift Lamp off. This gives some hysterisis and will stop the lamp flickering 'when the value is hovering around the threshold. If AvValue > ShiftHigh Then High ShiftLampPin Elseif AvValue < ShiftLow Then Low ShiftLampPin Endif Return #Rem ######################################################### # TIMER1 'MODULE'. J.Leach December 2006 # ######################################################### This module enables you to time events in milliseconds using Timer1. At 8MHz clock the maximum time that can be meaured is 524ms These routines have been written following discovery of the detail by the Happy Hippy. #endrem 'RAM Addresses: Symbol RAM_PIR1 = $0C Symbol RAM_T1CON = $10 Symbol RAM_TMR1 = $0E 'PIR1: Symbol ADIF_BIT = bit6 Symbol RCIF_BIT = bit5 Symbol TXIF_BIT = bit4 Symbol SSPIF_BIT = bit3 Symbol CCP1IF_BIT = bit2 Symbol TMR2IF_BIT = bit1 Symbol TMR1IF_BIT = bit0 'T1CON: Symbol T1RUN_BIT = bit6 Symbol T1CKPS1_BIT = bit5 Symbol T1CKPS0_BIT = bit4 Symbol T1OSCEN_BIT = bit3 Symbol NOT_T1SYNC_BIT = bit2 Symbol T1INSYNC_BIT = bit2 Symbol TMR1CS_BIT = bit1 Symbol TMR1ON_BIT = bit0 'General Timer1 variable 'Symbol Timer1ValueW = w5 'Set it to any suitable word variable, but not w0. Symbol T1CON = b0 Symbol PIR1 = b0 InitialiseTimer1: 'Sets the T1CON control register. Only needs to be called once, before 'using Timer1. T1CKPS1_BIT = 1'Divide by 8 Pre-Scaler T1CKPS0_BIT = 1'Divide by 8 Pre-Scaler T1OSCEN_BIT = 0'External LP Oscillator Not Enabled NOT_T1SYNC_BIT = 0'Ignored if Internal Clock TMR1CS_BIT = 0'Internal Clock TMR1ON_BIT = 0'Timer 1 Disabled Poke RAM_T1CON,T1CON 'Set T1CON Return ResetAndStartTimer1: 'Clear Timer1 and the interrupt(overflow) flag. Start Timer1. 'Set both LSB and MSB of Timer1 to 0 Poke RAM_TMR1,0,0 'Clear Timer1 Interrupt Peek RAM_PIR1,PIR1 TMR1IF_BIT = 0 Poke RAM_PIR1,PIR1 'Start Timer1 Peek RAM_T1CON,T1CON TMR1ON_BIT = 1 Poke RAM_T1CON,T1CON Return GetTime: 'Stop Timer1, Load the result into Timer1ValueW and convert it 'to milliseconds (Assumes an 8MHz clock) 'Stop Timer1 Peek RAM_T1CON,T1CON TMR1ON_BIT = 0 Poke RAM_T1CON,T1CON 'Load result Peek RAM_TMR1,Word Timer1ValueW 'Convert result to milliseconds. 'With a 8MHz clock, and a pre-scaler of 1:1, Timer1 increments every 0.5uS. 'With a pre-scaler of 1:8 it increments every 4uS. So the time in uS = '4 * Timer1ValueW. So the time in mS = 4 * Time1ValueW / 1000 = 'Timer1ValueW / 250. Timer1ValueW = Timer1ValueW / 250 'Check for Timer1 overflow and if it has overflowed add on another 262ms 'onto Timer1ValueW Peek RAM_PIR1,PIR1 If TMR1IF_BIT = 1 Then Timer1ValueW = Timer1ValueW + 262 Endif Return