;=============================================================================== ; Title: Reaction Timer ; ; Author: Rob Jansen, Copyright (c) 2017 .. 2017, all rights reserved. ; ; Revisions ; --------- ; 2017-05-27 : Initial version ; ; Compiler: jalv24q6 ; ; Description: Timer to measure the reaction time. Five 7-segment displays ; are connected to the SPI interface. ; ; Sources: - ; ; ================== Constant and variable declarations ======================= ; Some compiler pragmas for optimizations Pragma warn all yes ; We want to know all compiler warnings Pragma opt variable_reduce yes ; Reduce variables. Note there seem to be a ; problem in the complier if this is set to no. include 16f1823 ; target PICmicro ; This uses the 500 kHz internal oscillator. Pragma target clock 500_000 ; Oscillator frequency 500 kHz ; Configuration memory settings (fuses). These are only a selection, sufficient ; for this program. pragma target OSC INTOSC_NOCLKOUT ; Internal Clock Pragma target PLLEN DISABLED ; PLL off Pragma target WDT DISABLED ; No Watchdog Pragma target PWRTE ENABLED ; Power up timer enabled Pragma target BROWNOUT DISABLED ; No brownout reset Pragma target FCMEN DISABLED ; No clock monitoring Pragma target IESO DISABLED ; int/ext osc. switch Pragma target LVP DISABLED ; No low voltage programming Pragma target MCLR INTERNAL ; Reset internal ; Set the internal clock frequency to 32 MHz. OSCCON_SPLLEN = 0b1 ; In fact not used since PLLEN = P4 OSCCON_IRCF = 0b1010 ; Set 500 kHz OSCCON_SCS = 0b00 ; Clock determined by FOSC ; Enable weak pull up for all unused ports since some inputs are not connected WPUA = 0b0011_1111 ; Weak pull-up for RA0 to RA5 just to be sure. WPUC = 0b0011_1111 ; Weak pull-up for RC0 to RC5 just to be sure. OPTION_REG_WPUEN = FALSE ; Enable Weak Pull-Up ; Make all pins digital I/O enable_digital_io() Alias Pushbutton is Pin_A3 ; Pin 4 Pin_A3_Direction = INPUT ; Specify connected SPI IO-pins for controlling the Shift Register that controls ; the 7-Segment displays. Alias Shift_Clock is pin_C0 ; Shift register clock, via SPIO pin 10. pin_C0_direction = output Alias Shift_Store is pin_C1 ; Shift Store, moving bits to SR output pin 9. pin_C1_direction = output Alias Shift_Data is pin_C2 ; Shift register Data, via SPIO pin 8. pin_C2_direction = output Alias Shift_ResetN is pin_C3 ; Shift reset. Active Low pin 7. pin_C3_direction = output ; 7-Segment displays have a common anode which is powered by an active high Alias Shift_CA_10000 is pin_A2 ; Common Anode 10000 Segment. Pin 11. pin_A2_direction = output Alias Shift_CA_1000 is pin_A4 ; Common Anode 1000 Segement. Pin 3. pin_A4_direction = output Alias Shift_CA_100 is Pin_A5 ; Common Anode 100 Segement. Pin 2. Pin_A5_Direction = OUTPUT Alias Shift_CA_10 is Pin_C5 ; Common Anode 10 Segement. Pin 5. Pin_C5_Direction = OUTPUT Alias Shift_CA_1 is Pin_C4 ; Common Anode 1 Segement. Pin 6. Pin_C4_Direction = OUTPUT ; Control Constants for the 7-segment Display, On is TRUE. Const Bit DISPLAY_CONTROL_ON = TRUE Const Bit DISPLAY_CONTROL_OFF = FALSE ; Constants used for multiplexing the LED displays. Const Byte MUX_10000 = 0 Const Byte MUX_1000 = 1 Const Byte MUX_100 = 2 Const Byte MUX_10 = 3 Const Byte MUX_1 = 4 ; 16 bit shift register used to generate a random number. It generates all ; numbers between 0x0001 and 0xFFFF and has to be started with a seed. ; See http://en.wikipedia.org/wiki/Linear_feedback_shift_register Var Word Random_Shift_Word = 0xACE1 -- Intialize with Seed for Random Generator ; Conversion table to translate a byte to 7-segment display. The segments ; are activated by an active high signal, assigned as follows to a byte: ; msb to lsb = Pgfe dcba where P is the decimal point. Const Byte DISPLAY_OPTIONS = 12 Const Byte DISPLAY_X = DISPLAY_OPTIONS - 1 Const Byte DISPLAY_DASH = DISPLAY_OPTIONS - 2 Const Byte DIGIT_TO_7_SEGMENT[DISPLAY_OPTIONS] = {0b_0011_1111, ; 0 = fedcba 0b_0000_0110, ; 1 = cb 0b_0101_1011, ; 2 = gedba 0b_0100_1111, ; 3 = gdcba 0b_0110_0110, ; 4 = gfcb 0b_0110_1101, ; 5 = gfdca 0b_0111_1101, ; 6 = gfedca 0b_0000_0111, ; 7 = cba 0b_0111_1111, ; 8 = gfedcba 0b_0110_1111, ; 9 = gfdcba 0b_0100_0000, ; - = g 0b_0111_0110} ; x = gfecb ; Display constant and variable declarations. Var Byte Display_State ; State machine variable for Display Var Byte Display_Segment_10000 Var Byte Display_Segment_1000 Var Byte Display_Segment_100 Var Byte Display_Segment_10 Var Byte Display_Segment_1 ; Main program constant and variable declarations. Var Word Reaction_Timer Var Word Display_Timer Var Word Delay_Timer Var Byte Reset_Timer Var Bit Key_Released ; ========================= Functions and Procedures ========================== Procedure Display_Init Is ; Initialize the SPI interface and the external Shift Registers that control Shift_ResetN = FALSE ; Clear Shift Registers. Shift_Store = FALSE ; Reset line for storing shift data . ; Initialize display to '-' Display_Segment_10000 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_1000 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_100 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_10 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_1 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] ; We will use Timer2 as input clock as to have a low interrupt frequency. ; Since this displays are multiplexed the frequency should not be too low. ; We choose a SPI clock frequencey of about 4000 Hz so an interrupt frequency ; of 500 Hz so that each 7-segment display is scanned with a frequency of ; 500 / 5 = 100 Hz. Timer 2 needs to create a frequency of 8000 Hz or 125 us. T2CON_TMR2ON = FALSE -- Timer 2 off T2CON_T2OUTPS = 0b0000 -- Postscaler is 1:1 T2CON_T2CKPS = 0b00 -- Prescaler divide by 1 ; Register PR2 holds the Timer Period using the following formula: ; Period = (PR2 + 1) * 4 * Tosc * Timer2 prescale value * postscaling ; where Tosc = 1/Fosc and Fosc = 32.000.000 Hz ; Setting the prescaler at 1 and a PR2 of 30 gives: ; (14 + 1) * 4 * 1/500.000 * 1 = 120 us Period Cycle (about 8333 Hz) PR2 = 14 -- PR2 compare value of Timer 2 TMR2 = 0 -- Reset Timer ; We do not need a Timer 2 interrupt PIE1_TMR2IE = FALSE ; Roll-over interrupt must be enabled otherwise Timer 2 does not work INTCON_PEIE = TRUE ; Start Timer 2 (and so SPI multiplexer) T2CON_TMR2ON = TRUE ; Intialize the SPI for controlling the Shift Registers. ; Set Serial Connection Control Register. SSP1CON2 and SSP1CON3 are not ; relevant since used for IIC or reading via SPI what we do not use. SSP1CON1_SSPEN = FALSE ; Disable SPI while configuring SSP1CON1_SSPM = 0b0011 ; Use Timer2 / 2 clock ; Set clock mode of SPI so that Data is clocked in SR when clock line goes up ; that is CKP = 0 and CKE = 1. SSP1CON1_CKP = FALSE SSPSTAT_CKE = TRUE ; Set global display variables. Display_State = MUX_10000 ; Start at the beginning of the state machine. ; Reset flags, release shift register and start SPI. PIR1_SSPIF = FALSE ; Clear SPI interrupt flag. Shift_ResetN = TRUE ; Enable Shift Registers, disable reset. SSP1CON1_SSPEN = TRUE ; Enable SPI. PIE1_SSP1IE = TRUE ; Enable Serial Interrupt INTCON_PEIE = TRUE ; Peripheral interrupts enabled. INTCON_GIE = TRUE ; Globale interrupt enabled SSP1BUF = 0x00 ; 0x00 is display off, generate SPI interrupt. End Procedure ; Display_Init Procedure Display_Interrupt Is Pragma interrupt ; Interrupt occured, check if SPI interrupt occured. If so then we multiplex ; the data for the three LED displays. If PIR1_SSPIF Then Case Display_State Of ; Multiplex the LED displays. Note that each state moves the data to the ; LED display of the previous LED display and prepares the data for itself. ; If the reaction Timer is active we display the timer value. ; Order is: 10000, 1000, 100, 10, 1 MUX_10000: Block ; Disable the 1 display, store the value of 10000 display and ; prepare the data for the next display. Shift_CA_1 = DISPLAY_CONTROL_OFF ; De-activate 1 display. Shift_Store = TRUE ; Output 10000 value to sisplay. Shift_CA_10000 = DISPLAY_CONTROL_ON ; Activate 10000 display. ; Send data for 1000 display to Shift Register. SSP1BUF = Display_Segment_1000 ; Write value of 1000 display. Display_State = MUX_1000 Shift_Store = FALSE End Block MUX_1000: Block ; Disable the 10000 display, store the value of 1000 display and ; prepare the data for the next display. Shift_CA_10000 = DISPLAY_CONTROL_OFF ; De-activate 10000 display. Shift_Store = TRUE ; Output 1000 value to sisplay. Shift_CA_1000 = DISPLAY_CONTROL_ON ; Activate 1000 display. ; Send data for 100 display to Shift Register. SSP1BUF = Display_Segment_100 ; Write value of 100 display. Display_State = MUX_100 Shift_Store = FALSE End Block MUX_100: Block ; Disable the 1000 display, store the value of 100 display and ; prepare the data for the next display. Shift_CA_1000 = DISPLAY_CONTROL_OFF ; De-activate 1000 display. Shift_Store = TRUE ; Output 100 value to sisplay. Shift_CA_100 = DISPLAY_CONTROL_ON ; Activate 100 display. ; Send data for 10 display to Shift Register. SSP1BUF = Display_Segment_10 ; Write value of 10 display. Display_State = MUX_10 Shift_Store = FALSE End Block MUX_10: Block ; Disable the 100 display, store the value of 10 display and ; prepare the data for the next display. Shift_CA_100 = DISPLAY_CONTROL_OFF ; De-activate 100 display. Shift_Store = TRUE ; Output 10 value to sisplay. Shift_CA_10 = DISPLAY_CONTROL_ON ; Activate 10 display. ; Send data for 1 display to Shift Register. SSP1BUF = Display_Segment_1 ; Write value of 1 display. Display_State = MUX_1 Shift_Store = FALSE End Block MUX_1: Block ; Disable the 10 display, store the value of 1 display and ; prepare the data for the next display. Shift_CA_10 = DISPLAY_CONTROL_OFF ; De-activate 10 display. Shift_Store = TRUE ; Output 1 value to sisplay. Shift_CA_1 = DISPLAY_CONTROL_ON ; Activate 1 display. ; Send data for 10000 display to Shift Register. SSP1BUF = Display_Segment_10000 ; Write value of 10000 display. Display_State = MUX_10000 Shift_Store = FALSE End Block End Case ; Clear interrupt flag PIR1_SSPIF = FALSE End If End Procedure ; Display_Interrupt Procedure Display_Error Is ; Display 'xxxxx' to indicate an error has occured. Display_Segment_10000 = DIGIT_TO_7_SEGMENT[DISPLAY_X] Display_Segment_1000 = DIGIT_TO_7_SEGMENT[DISPLAY_X] Display_Segment_100 = DIGIT_TO_7_SEGMENT[DISPLAY_X] Display_Segment_10 = DIGIT_TO_7_SEGMENT[DISPLAY_X] Display_Segment_1 = DIGIT_TO_7_SEGMENT[DISPLAY_X] End Procedure ; Display_Error Function Give_Random_Word Return Word is ; Generate a new random number using a Linear Feedback Shift Register (LFSR). Var Word Value ; Use Fibonacci LSFR x16 + x14 + x13 + x11 + 1 Value = (Random_Shift_Word ^ (Random_Shift_Word >> 2) ^ (Random_Shift_Word >> 3) ^ (Random_Shift_Word >> 5)) & 0x0001 Random_Shift_Word = (Random_Shift_Word >> 1) | (Value << 15) Return Random_Shift_Word End Function ; Give_Random_Word ; ========================= Main program starts here ========================== ; Init Display interface Display_Init ; Initialize Timer 1 to counte with Fosc/4 and a prescaler of 8 this means it ; counts with a clock of 500.000 / 4 / 8 is 15.625 Hz or 64 us. T1CON = 0b0011_0100 ; Clock is Fosc/4, prescaler 8 , Timer is disabled. T1GCON = 0x00 ; Timer 1 gate control if off. Key_Released = TRUE Reaction_Timer = 0 Forever Loop _usec_delay(10_000) ; Main Loop time of 10 ms. ; Read the random generator as to generate random number continuously. Delay_Timer = Give_Random_Word ; Handle the switch, pressed is active low. If !Pushbutton Then _usec_delay(50_000) ; Debounce 50 ms. ; We only do something if the key was released before. If Key_Released Then Key_Released = FALSE ; Show decimal points. Display_Segment_10000 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_1000 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_100 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_10 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] Display_Segment_1 = DIGIT_TO_7_SEGMENT[DISPLAY_DASH] ; Wait a Random Time. Always wait for at least 2 seconds. Delay_Timer = 2000 ; 2 seconds WHILE (Delay_Timer > 0) & !Pushbutton Loop _usec_delay(1_000) ; Wait for 1 ms. Delay_Timer = Delay_Timer - 1 End Loop If (Delay_Timer == 0) Then ; Key was still pressed. Now start the random delay. Delay_Timer = Give_Random_Word WHILE (Delay_Timer > 0) & !Pushbutton Loop _usec_delay(100) Delay_Timer = Delay_Timer - 1 End Loop If (Delay_Timer == 0) Then ; Key still pressed, show 'x' on the display and start the timer. T1CON_TMR1ON = FALSE ; Stop Timer TMR1 = 0 ; Reset Timer PIR1_TMR1IF = FALSE ; Clear Timer Overflow flag. Display_Segment_100 = DIGIT_TO_7_SEGMENT[DISPLAY_X] ; Display 'x'. T1CON_TMR1ON = TRUE ; Now start the Timer. ; Wait for key release. While !Pushbutton Loop End Loop ; Button releases, stop Timer. T1CON_TMR1ON = FALSE ; Check if timer not overflown. If !PIR1_TMR1IF Then ; Button released before timer overflow, show timer value on display. Display_Timer = TMR1 Display_Segment_10000 = DIGIT_TO_7_SEGMENT[Byte(Display_Timer / 10000)] Display_Timer = Display_Timer % 10000 Display_Segment_1000 = DIGIT_TO_7_SEGMENT[Byte(Display_Timer / 1000)] Display_Timer = Display_Timer % 1000 Display_Segment_100 = DIGIT_TO_7_SEGMENT[Byte(Display_Timer / 100)] Display_Timer = Display_Timer % 100 Display_Segment_10 = DIGIT_TO_7_SEGMENT[Byte(Display_Timer / 10)] Display_Timer = Display_Timer % 10 Display_Segment_1 = DIGIT_TO_7_SEGMENT[Byte(Display_Timer)] Else Display_Error End If ; !PIR_TIMR1F Else Display_Error End If ; Delay_Timer Else Display_Error End If; ; Delay_Timer End If ; Key_Released End If ; !Pushbutton ; Check for key release. If Pushbutton Then Key_Released = TRUE End If End Loop