diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..400a800a Binary files /dev/null and b/.DS_Store differ diff --git a/Calculations.s b/Calculations.s new file mode 100644 index 00000000..96b2966a --- /dev/null +++ b/Calculations.s @@ -0,0 +1,295 @@ +#include + +global Divide_By_20, Divide_By_Ten, Load_HRZ_Table, Divide_By_Hundred, Determine_HRZ, IIR_Filter +global measured_heart_rate_zone_address, heart_rate_zone_address +; this includes subroutines for calculations: e.g. max heart rate calculation, boundary calculations +psect udata_acs +myDenominator_low:ds 1 +myNumerator:ds 1 +myQuotient:ds 1 +myRemainder:ds 1 +myDiff:ds 1 +STATUS_CHECK:ds 1 +HR_max:ds 1 +Zone_Value:ds 1 +HR_Measured:ds 1 ; reserve one byte for measured HR value from sensor + +x1:ds 1 +x2:ds 1 +x3:ds 1 +x1x2H:ds 1 +x1x2L:ds 1 +x1x2x3H:ds 1 +x1x2x3L:ds 1 +myDen_low:ds 1 +myQuo:ds 1 +myRem:ds 1 + + +psect calculations_code,class=CODE + +Divide_By_20: ; divide the number stored in WREG by 20 + ; Ensure myDenominator is not zero + ; MOVWF myNumerator + + MOVLW 19 ; Think this needs to be n - 1, where n is the denominator?? + MOVWF myDenominator_low ; divide by 20 + + MOVLW 0 ; Move 0 into WREG to check if denominator is zero + CPFSEQ myDenominator_low + GOTO Clear ; Check the MSB of myDenominator + GOTO DivisionError ; If zero, handle division by zero +Clear: ; Perform division algorithm + CLRF myQuotient ; Clear the quotient register + CLRF myRemainder ; Clear the remainder register + +Division_Loop: + MOVFF myDenominator_low, WREG + CPFSLT PRODL ; if lower byte is smaller than denominator: need to borrow + bra Subtract + bra Borrow_or_Done +Borrow_or_Done: + MOVLW 0 + CPFSGT PRODH ; Check if done, i.e. if the upper byte is zero. + bra Division_Done + DECF PRODH, 1 ; Borrow from higher byte + ;sMOVFF PRODH, PORTB +Subtract: + INCF myQuotient, 1 ; Increment quotient + MOVFF myQuotient, PORTD + MOVFF myDenominator_low, WREG + SUBWFB PRODL, 1 ; myNumerator -= myDenominator + ;MOVFF PRODL, PORTC + bra Division_Loop +Division_Done: + ;MOVFF myQuotient, PORTC + MOVFF myQuotient, WREG ; return with the quotient in the WREG + RETURN +DivisionError: + RETURN + + +Load_HRZ_Table: ; call with HR_max in WREG + MOVWF HR_max + + movlw heart_rate_zone_address + movwf FSR0 + + CLRF EEADR ; start at address 0 + BCF EECON1, 6 ; set for memory, bit 6 = CFGS + BCF EECON1, 7 ; set for data EEPROM, bit 7 = EEPGD + ;BSF EECON1, 2 ; write enable, bit 2 = WREN + ;BCF INTCON, 7 ; disable interrupts, bit 7 = GIE + +Loop: + ;MOVFF EEADR, PORTB + BSF EECON1, 0 ; read current address, bit 0 = RD + nop ; need to have delay after read instruction for reading to complete + MOVFF EEDATA, WREG ; W = multiplier + ;MOVFF EEDATA, PORTC + MULWF HR_max + + CALL Divide_By_20 ; (HR_max*multiplier)/20, return with quotient in WREG + + MOVWF INDF0 + INCF FSR0 + +; MOVWF EEDATA ; move data to EE +; +; MOVLW 0x55 +; MOVWF EECON2 +; MOVLW 0xAA +; MOVWF EECON2 +; +; BSF EECON1, 1 ; to write data, bit 1 = WR +; BTFSC EECON1, 1 +; bra $-2 ; wait for write to complete + INCF EEADR, 1 ; Increment address and save back to EEADR + + MOVFF EEADR, WREG ; Routine to check if the end has been reached + SUBLW 6 + MOVWF STATUS_CHECK + MOVLW 0 + CPFSEQ STATUS_CHECK ; comparison to see if the end of the table has been reached + bra Loop + bra End_Write +End_Write: + ; Continue on with the rest of the code + ;BCF EECON1, 2 ; disenable writing function + MOVLW 0xFF + MOVWF PORTD + RETURN + +Determine_HRZ: ; enter with measured HR stored in WREG + movwf HR_Measured + + movlw heart_rate_zone_address + movwf FSR2 + + MOVLW 6 + MOVWF Zone_Value ; initialise at 6, highest possible zone value is 5 + +Table_Compare_Loop: + MOVF POSTINC2, W, A ; move heart rate to WREG + CPFSLT HR_Measured ; skip if f < W + bra Output_Zone_Value + decfsz Zone_Value + bra Table_Compare_Loop +Output_Zone_Value: + MOVFF Zone_Value, WREG + return + +IIR_Filter: + MOVWF x3 ; newest measurement WREG -> x3 + + MOVFF x1, WREG + ADDWF x2, 0 ; 200 + 256 = 456 = 1C8 + MOVWF x1x2L + + MOVLW 0x00 + ADDWFC x1x2H, 1 ; carry to higher byte + INCF x1x2L, 1 + + ; give newest measurement a higher weighting, multiply it by 2 + MOVFF x3, WREG ; newest measuremeng in WREG + MULLW 2 ; double the weighting than the other two measurements + ; results stored in PRODH:PRODL 2*HR_newest + MOVFF PRODH, WREG + MOVFF PRODL, WREG + ADDWF x1x2L, 0 ; 456 + 200 = 656 = 290 + MOVWF x1x2x3L ; contains lower byte of sum + + MOVFF PRODH, WREG + ADDWFC x1x2H, 0 + MOVWF x1x2x3H ; contains higher byte of sum + + ; divide by 4 + + MOVLW 3 ; Think this needs to be n - 1, where n is the denominator?? + MOVWF myDen_low ; divide by 4 to find the average + + MOVLW 0 ; Move 0 into WREG to check if denominator is zero + CPFSEQ myDen_low + GOTO Clear_1 ; Check the MSB of myDenominator + GOTO DivisionError_1 ; If zero, handle division by zero +Clear_1: ; Perform division algorithm + CLRF myQuo ; Clear the quotient register + CLRF myRem ; Clear the remainder register +Division_Loop_1: + MOVFF myDen_low, WREG + CPFSLT x1x2x3L ; if lower byte is smaller than denominator: need to borrow + bra Check_Equal + bra Borrow_or_Done_1 +Borrow_or_Done_1: + MOVLW 0 + CPFSGT x1x2x3H ; Check if done, i.e. if the upper byte is zero. + bra Division_Done_1 + DECF x1x2x3H, 1 ; Borrow from higher byte + ;MOVFF x1x2x3H, PORTB + bra Subtract_1 +Check_Equal: + MOVFF myDen_low, WREG + CPFSEQ x1x2x3L + bra Subtract_1 + bra Borrow_or_Done_1 +Subtract_1: + INCF myQuo, 1 ; Increment quotient + MOVFF myQuo, PORTD + MOVFF myDen_low, WREG + SUBWFB x1x2x3L, 1 ; myNumerator -= myDenominator + ;MOVFF x1x2x3L, PORTC + bra Division_Loop_1 +Division_Done_1: + bra Update_Vals + MOVFF myQuo, WREG ; 656/4 = 164 ... + RETURN +DivisionError_1: + RETURN +Update_Vals: + MOVFF x2, WREG + MOVWF x1 ; update x1 with value in x2 + MOVFF myQuo, WREG + MOVWF x2 ; update x2 with newest measurement + return + +Divide_By_Ten: + ; Ensure myDenominator is not zero + MOVWF myNumerator +; Think this needs to be n - 1, where n is the denominator?? + MOVLW 9 + MOVWF myDenominator_low ; divide by 20 + + MOVLW 0 + CPFSEQ myDenominator_low + GOTO Clear_Ten ; Check the MSB of myDenominator + GOTO DivisionError_Ten ; If zero, handle division by zero +Clear_Ten: ; Perform division algorithm?? + CLRF myQuotient ; Clear the quotient register + CLRF myRemainder ; Clear the remainder register + +Division_Loop_Ten: + MOVFF myDenominator_low, WREG + CPFSLT myNumerator ; if lower byte is smaller than denominator: need to borrow + BRA Subtract_Ten + BRA Division_Done_Ten +;Borrow_or_Done_Ten: +; MOVLW 0 +; CPFSGT PRODH ; Check if done, i.e. if the upper byte is zero. +; BRA Division_Done_Ten +; DECF PRODH, 1 +; ;MOVFF PRODH, PORTB +Subtract_Ten: + INCF myQuotient, 1 + ;MOVFF myQuotient, PORTD + MOVFF myDenominator_low, WREG + SUBWFB myNumerator, 1 + ;MOVFF PRODL, PORTC + BRA Division_Loop_Ten +Division_Done_Ten: + ;MOVFF myQuotient, PORTC + MOVFF myQuotient, WREG ; return with the quotient in the WREG + RETURN +DivisionError_Ten: + RETURN + +Divide_By_Hundred: + ; Ensure myDenominator is not zero + MOVWF myNumerator ; enter routine with numerator in WREG +; Think this needs to be n - 1, where n is the denominator?? + MOVLW 99 + MOVWF myDenominator_low ; divide by 20 + + MOVLW 0 + CPFSEQ myDenominator_low + GOTO Clear_Hundred ; Check the MSB of myDenominator + GOTO DivisionError_Hundred ; If zero, handle division by zero +Clear_Hundred: ; Perform division algorithm?? + CLRF myQuotient ; Clear the quotient register + CLRF myRemainder ; Clear the remainder register + +Division_Loop_Hundred: + MOVFF myDenominator_low, WREG + CPFSLT myNumerator ; if lower byte is smaller than denominator: need to borrow + BRA Subtract_Hundred + BRA Division_Done_Hundred +;Borrow_or_Done_Hundred: +; MOVLW 0 +; CPFSGT PRODH ; Check if done, i.e. if the upper byte is zero. +; BRA Division_Done_Hundred +; DECF PRODH, 1 +; ;MOVFF PRODH, PORTB +Subtract_Hundred: + INCF myQuotient, 1 + ;MOVFF myQuotient, PORTD + MOVFF myDenominator_low, WREG + SUBWF myNumerator, 1 + ;MOVFF PRODL, PORTC + BRA Division_Loop_Hundred +Division_Done_Hundred: + ;MOVFF myQuotient, PORTC + MOVFF myQuotient, WREG ; return with the quotient in the WREG + RETURN +DivisionError_Hundred: + RETURN + + \ No newline at end of file diff --git a/Digit_Reader.s b/Digit_Reader.s new file mode 100644 index 00000000..1f90fd24 --- /dev/null +++ b/Digit_Reader.s @@ -0,0 +1,292 @@ +#include + +; This includes the subroutine to decode the input from the keypad for the two-digit age input + +global Decode_First_Digit, Decode_Second_Digit, Read_Age_Input_Find_HR_Max +extrn Keypad_READ +extrn delay_ms +extrn LCD_Write_Message +extrn age_address_1, age_address_2 + +psect udata_acs ; reserve data space in access ram +first_digit: ds 1 +second_digit: ds 1 +digit_input_counter: ds 1 ; counter for checking how many digits of the age has been inputted +age_first: ds 1 ; first digit of age input +age_second: ds 1 ; second digit of age input +age: ds 1 ; age, after combining the two digits +maximum_heart_rate: ds 1 ; value for maximum heart rate is stored here + +psect digit_reader_code,class=CODE + +Age_Read_1: + movlw age_address_1 + movwf FSR0 + movff digit_input_counter, PORTJ ; output digit counter to PORTJ to visualise how many digits are left to be inputted + + call Keypad_READ ; keypad read subroutine, value stored in W + call Decode_First_Digit ; decode first digit, return with 10s value in WREG + movwf age_first ; save value in variable age_first + movwf PORTD ; output to PORTD to visualise the number just inputted + + movlw 0xFF + call delay_ms + + movlw 0xFF ; value of error message + cpfslt age_first ; if no valid input, branch to Age_Read_1 to read from Keypad again; + bra Age_Read_1 + decf digit_input_counter,1 ; if there has been a valid input, decrement the digit counter and return + + movlw age_address_1 + movwf FSR2 + movlw 1 ; assume 1 digit + call LCD_Write_Message + + return + +Age_Read_2: + movlw age_address_2 + movwf FSR0 + movff digit_input_counter, PORTJ ; output digit counter to PORTJ to visualise how many digits are left to be inputted + + call Keypad_READ ; keypad read subroutine, value stored in W + call Decode_Second_Digit ; decode first digit, return with 10s value in WREG + movwf age_second ; save value in variable age_first + movwf PORTD ; output to PORTD to visualise the number just inputted + + movlw 0xFF + call delay_ms + + movlw 0xFF ; value of error message + cpfslt age_second ; if no valid input, branch to Age_Read_1 to read from Keypad again; + bra Age_Read_2 + decf digit_input_counter, 1 ; if there has been a valid input, decrement the digit counter and return + movff digit_input_counter, PORTJ ; output digit counter to PORTJ to visualise how many digits are left to be inputted + + movlw age_address_2 + movwf FSR2 + movlw 1 ; assume 1 digit + call LCD_Write_Message + + return + +Read_Age_Input_Find_HR_Max: + ; read in age input from keypad + + movlw 2 + movwf digit_input_counter + + movlw 2 ; set WREG to 2, to deduce value in digit coubnter + cpfslt digit_input_counter ; skip if smaller than two, otherwise read first digit + call Age_Read_1 + movlw 1 + cpfslt digit_input_counter ; skip if smaller than 1, otherwise read second digit + call Age_Read_2 + + ; add the two digits of age together + movff age_first, WREG ; move first digit of age to WREG + addwf age_second, W ; add second digit to WRED (age_first) and store result in WREG + movwf age ; store age in memory + ;movff age, PORTD + + movlw 0xFF + movwf PORTJ + + movlw 0xFF + call delay_ms + + movlw 0xFF + call delay_ms + + movlw 0xFF + call delay_ms + + movlw 0xFF + call delay_ms ; delay to see on board + + ; find maximum heart rate + movff age, WREG ; put age in WREG for use in subroutine + sublw 220 + return ; return with max HR in WREGs + +Decode_First_Digit: ; Read input from keypad, interpret as the 10s value, return as literal + movwf first_digit, A +Test_none_1: ; no key pressed + movlw 0xFF + cpfseq first_digit, A + bra Test_0_1 ; this is the ?no? result + retlw 0xFF ; this is the ?yes? result +Test_0_1: + movlw 0xBE + cpfseq first_digit, A + bra Test_1_1 + movlw '0' + movwf INDF0 + incf FSR0 + retlw 0 +Test_1_1: + movlw 0x77 + cpfseq first_digit, A + bra Test_2_1 + movlw '1' + movwf INDF0 + incf FSR0 + retlw 10 +Test_2_1: + movlw 0xB7 + cpfseq first_digit, A + bra Test_3_1 + movlw '2' + movwf INDF0 + incf FSR0 + retlw 20 +Test_3_1: + movlw 0xD7 + cpfseq first_digit, A + bra Test_4_1 + movlw '3' + movwf INDF0 + incf FSR0 + retlw 30 +Test_4_1: + movlw 0x7B + cpfseq first_digit, A + bra Test_5_1 + movlw '4' + movwf INDF0 + incf FSR0 + retlw 40 +Test_5_1: + movlw 0xBB + cpfseq first_digit, A + bra Test_6_1 + movlw '5' + movwf INDF0 + incf FSR0 + retlw 50 +Test_6_1: + movlw 0xDB + cpfseq first_digit, A + bra Test_7_1 + movlw '6' + movwf INDF0 + incf FSR0 + retlw 60 +Test_7_1: + movlw 0x7D + cpfseq first_digit, A + bra Test_8_1 + movlw '7' + movwf INDF0 + incf FSR0 + retlw 70 +Test_8_1: + movlw 0xBD + cpfseq first_digit, A + bra Test_9_1 + movlw '8' + movwf INDF0 + incf FSR0 + retlw 80 +Test_9_1: + movlw 0xDD + cpfseq first_digit, A + retlw 0xFF ; error message: when a letter or an invalid input has been detected + movlw '9' + movwf INDF0 + incf FSR0 + retlw 90 + + +Decode_Second_Digit: ; Read input from keypad, interpret as the 10s value, return as literal + movwf second_digit, A +Test_none_2: ; no key pressed + movlw 0xFF + cpfseq second_digit, A + bra Test_0_2 ; this is the ?no? result + retlw 0xFF ; this is the ?yes? result +Test_0_2: + movlw 0xBE + cpfseq second_digit, A + bra Test_1_2 + movlw '0' + movwf INDF0 + incf FSR0 + retlw 0 +Test_1_2: + movlw 0x77 + cpfseq second_digit, A + bra Test_2_2 + movlw '1' + movwf INDF0 + incf FSR0 + retlw 1 +Test_2_2: + movlw 0xB7 + cpfseq second_digit, A + bra Test_3_2 + movlw '2' + movwf INDF0 + incf FSR0 + retlw 2 +Test_3_2: + movlw 0xD7 + cpfseq second_digit, A + bra Test_4_2 + movlw '3' + movwf INDF0 + incf FSR0 + retlw 3 +Test_4_2: + movlw 0x7B + cpfseq second_digit, A + bra Test_5_2 + movlw '4' + movwf INDF0 + incf FSR0 + retlw 4 +Test_5_2: + movlw 0xBB + cpfseq second_digit, A + bra Test_6_2 + movlw '5' + movwf INDF0 + incf FSR0 + retlw 5 +Test_6_2: + movlw 0xDB + cpfseq second_digit, A + bra Test_7_2 + movlw '6' + movwf INDF0 + incf FSR0 + retlw 6 +Test_7_2: + movlw 0x7D + cpfseq second_digit, A + bra Test_8_2 + movlw '7' + movwf INDF0 + incf FSR0 + retlw 7 +Test_8_2: + movlw 0xBD + cpfseq second_digit, A + bra Test_9_2 + movlw '8' + movwf INDF0 + incf FSR0 + retlw 8 +Test_9_2: + movlw 0xDD + cpfseq second_digit, A + retlw 0xFF ; error message: when a letter or an invalid input has been detected + movlw '9' + movwf INDF0 + incf FSR0 + retlw 9 + + + + + diff --git a/Keypad.s b/Keypad.s new file mode 100644 index 00000000..08b4aba2 --- /dev/null +++ b/Keypad.s @@ -0,0 +1,183 @@ +#include + +global Keypad_INIT, Keypad_READ, delay_ms + +psect udata_acs ; reserve data space in access ram +Keypad_counter: ds 1 ; reserve 1 byte for variable UART_counter +row: ds 1 +col: ds 1 +keyval: ds 1 +cnt_ms: ds 1 ; reserve 1 byte for ms counter +cnt_l: ds 1 ; reserve 1 byte for variable cnt_l +cnt_h: ds 1 ; reserve 1 byte for variable cnt_h + +psect uart_code,class=CODE + +Keypad_INIT: + + banksel PADCFG1 + bsf REPU ;PADCFG1, REPU, 1 Pulling up resistors + clrf LATE, A + banksel 0 + + movlw 0x0F + movwf TRISE, A + + movlw 1 + call delay_ms + + return + + +Keypad_READ: + ; Drive output bits low all at once + movlw 0x00 + movwf PORTE, A + + movff PORTE, col ; read in column values + + movlw 0xF0 + movwf TRISE, A ; changing TRI state + + movlw 1 + call delay_ms ;delay + + ; Drive output bits low all at once + movlw 0x00 + movwf PORTE, A + + movff PORTE, row ; read in row values + movff row, WREG + + iorwf col, 0, 0 ; inclusive or logic for row and column + movwf keyval ; + + movlw 0x0F + movwf TRISE, A + + movlw 1 + call delay_ms + + movff keyval, WREG ; move keyvalue into WREG for decoder to work + + ;call Test_none ; decode results, returns with result in working directory + + return + +Test_none: ; no key pressed + movlw 0xFF + cpfseq keyval, A + bra Test_0 ; this is the ?no? result + retlw 0xFF ; this is the ?yes? result +Test_0: + movlw 0xBE + cpfseq keyval, A + bra Test_1 + retlw '0' +Test_1: + movlw 0x77 + cpfseq keyval, A + bra Test_2 + retlw '1' +Test_2: + movlw 0xB7 + cpfseq keyval, A + bra Test_3 + retlw '2' +Test_3: + movlw 0xD7 + cpfseq keyval, A + bra Test_4 + retlw '3' +Test_4: + movlw 0x7B + cpfseq keyval, A + bra Test_5 + retlw '4' +Test_5: + movlw 0xBB + cpfseq keyval, A + bra Test_6 + retlw '5' +Test_6: + movlw 0xDB + cpfseq keyval, A + bra Test_7 + retlw '6' +Test_7: + movlw 0x7D + cpfseq keyval, A + bra Test_8 + retlw '7' +Test_8: + movlw 0xBD + cpfseq keyval, A + bra Test_9 + retlw '8' +Test_9: + movlw 0xDD + cpfseq keyval, A + bra Test_A + retlw '9' +Test_A: + movlw 0x7E + cpfseq keyval, A + bra Test_B + retlw 'A' +Test_B: + movlw 0xDE + cpfseq keyval, A + bra Test_C + retlw 'B' +Test_C: + movlw 0xEE + cpfseq keyval, A + bra Test_D + retlw 'C' +Test_D: + movlw 0xED + cpfseq keyval, A + bra Test_E + retlw 'D' +Test_E: + movlw 0xEB + cpfseq keyval, A + bra Test_F + retlw 'E' +Test_F: + movlw 0xE7 + cpfseq keyval, A + retlw 0xFF ; error message + retlw 'F' + + +delay_ms: ; delay given in ms in W + movwf cnt_ms, A +lp2: movlw 250 + call delay_x4us + decfsz cnt_ms, A + bra lp2 + return + +delay_x4us: ; delay given in chunks of 4 microsecond in W + movwf cnt_l, A ; now need to multiply by 16 + swapf cnt_l, F, A ; swap nibbles + movlw 0x0f + andwf cnt_l, W, A ; move low nibble to W + movwf cnt_h, A ; then to cnt_h + movlw 0xf0 + andwf cnt_l, F, A ; keep high nibble in cnt_l + call delay + return + +delay: ; delay routine 4 instruction loop == 250ns + movlw 0x00 ; W=0 +lp1: decf cnt_l, F, A ; no carry when 0x00 -> 0xff + subwfb cnt_h, F, A ; no carry when 0x00 -> 0xff + bc lp1 ; carry, then loop again + return ; carry reset so return + + + + + diff --git a/LCD.s b/LCD.s index ab1f2218..1a1c001b 100644 --- a/LCD.s +++ b/LCD.s @@ -1,17 +1,29 @@ #include -global LCD_Setup, LCD_Write_Message - +global LCD_Setup, Write_Welcome, SetTwoLines, LCD_Write_Message, LCD_Write_Hex +global Clear_LCD, LCD_Send_Byte_HR, LCD_Send_Byte_HRZ, LCD_clear, LCD_shift psect udata_acs ; named variables in access ram LCD_cnt_l: ds 1 ; reserve 1 byte for variable LCD_cnt_l LCD_cnt_h: ds 1 ; reserve 1 byte for variable LCD_cnt_h LCD_cnt_ms: ds 1 ; reserve 1 byte for ms counter LCD_tmp: ds 1 ; reserve 1 byte for temporary use LCD_counter: ds 1 ; reserve 1 byte for counting through nessage +LCD_hex_tmp: ds 1 +TwoLineCounter: ds 1 +counter:ds 1 +myArray:ds 1 LCD_E EQU 5 ; LCD enable bit LCD_RS EQU 4 ; LCD register select bit +psect data + ; ******* Input Message, data in programme memory, and its length ***** +InputMessage: + db 'P','l','e','a','s','e',' ','I','n','p','u','t',' ','A','g','e',0x0a + ; message, plus carriage return + myTable_l EQU 17 ; length of data + align 2 + psect lcd_code,class=CODE LCD_Setup: @@ -46,7 +58,23 @@ LCD_Setup: call LCD_delay_x4us return -LCD_Write_Message: ; Message stored at FSR2, length stored in W +LCD_Write_Hex: ; Writes byte stored in W as hex + movwf LCD_hex_tmp + swapf LCD_hex_tmp,W ; high nibble first + call LCD_Hex_Nib + movf LCD_hex_tmp,W ; then low nibble +LCD_Hex_Nib: ; writes low nibble as hex character + andlw 0x0F + movwf LCD_tmp + movlw 0x0A + cpfslt LCD_tmp + addlw 0x07 ; number is greater than 9 + addlw 0x26 + addwf LCD_tmp,W + call LCD_Send_Byte_D ; write out ascii + return + +LCD_Write_Message: ; Message address stored at FSR2, length stored in W movwf LCD_counter, A LCD_Loop_message: movf POSTINC2, W, A @@ -54,7 +82,13 @@ LCD_Loop_message: decfsz LCD_counter, A bra LCD_Loop_message return - + +LCD_shift: + movlw 0011000000B ; entry mode incr by 1 no shift + call LCD_Send_Byte_I + movlw 10 ; wait 40us + call LCD_delay_x4us + return LCD_Send_Byte_I: ; Transmits byte stored in W to instruction reg movwf LCD_tmp, A swapf LCD_tmp, W, A ; swap nibbles, high nibble goes first @@ -69,6 +103,63 @@ LCD_Send_Byte_I: ; Transmits byte stored in W to instruction reg call LCD_Enable ; Pulse enable Bit return +Clear_LCD: + movlw 00000001B ; display clear + call LCD_Send_Byte_I + return + +LCD_Send_Byte_HR: ; Transmits byte stored in W to data reg + movwf LCD_tmp, A + swapf LCD_tmp, W, A ; swap nibbles, high nibble goes first + andlw 0x0f ; select just low nibble + movwf LATB, A ; output data bits to LCD + bsf LATB, LCD_RS, A ; Data write set RS bit + call LCD_Enable ; Pulse enable Bit + movf LCD_tmp, W, A ; swap nibbles, now do low nibble + andlw 0x0f ; select just low nibble + movwf LATB, A ; output data bits to LCD + bsf LATB, LCD_RS, A ; Data write set RS bit + call LCD_Enable ; Pulse enable Bit + movlw 10 ; delay 40us + call LCD_delay_x4us + return + +LCD_Send_Byte_HRZ: ; Transmits byte stored in W to data reg + movwf LCD_tmp, A + swapf LCD_tmp, W, A ; swap nibbles, high nibble goes first + andlw 0x0f ; select just low nibble + movwf LATB, A ; output data bits to LCD + bsf LATB, LCD_RS, A ; Data write set RS bit + call LCD_Enable ; Pulse enable Bit + movf LCD_tmp, W, A ; swap nibbles, now do low nibble + andlw 0x0f ; select just low nibble + movwf LATB, A ; output data bits to LCD + bsf LATB, LCD_RS, A ; Data write set RS bit + call LCD_Enable ; Pulse enable Bit + movlw 10 ; delay 40us + call LCD_delay_x4us + return + +Write_Welcome: + lfsr 0, myArray ; Load FSR0 with address in RAM + movlw low highword(InputMessage) ; address of data in PM + movwf TBLPTRU, A ; load upper bits to TBLPTRU + movlw high(InputMessage) ; address of data in PM + movwf TBLPTRH, A ; load high byte to TBLPTRH + movlw low(InputMessage) ; address of data in PM + movwf TBLPTRL, A ; load low byte to TBLPTRL + movlw myTable_l ; bytes to read + movwf counter, A ; our counter register +loop: tblrd*+ ; one byte from PM to TABLAT, increment TBLPRT + movff TABLAT, POSTINC0; move data from TABLAT to (FSR0), inc FSR0 + decfsz counter, A ; count down to zero + bra loop ; keep going until finished + + movlw myTable_l ; output message to LCD + addlw 0xff ; don't send the final carriage return to LCD + lfsr 2, myArray + call LCD_Write_Message + LCD_Send_Byte_D: ; Transmits byte stored in W to data reg movwf LCD_tmp, A swapf LCD_tmp, W, A ; swap nibbles, high nibble goes first @@ -85,6 +176,18 @@ LCD_Send_Byte_D: ; Transmits byte stored in W to data reg call LCD_delay_x4us return +LCD_clear: + movlw 00000001B ; display clear + call LCD_Send_Byte_I + movlw 2 ; wait 2ms + call LCD_delay_ms + return + +SetTwoLines: + movlw 11000000B + call LCD_Send_Byte_I + return + LCD_Enable: ; pulse enable bit LCD_E for 500ns nop nop @@ -135,4 +238,3 @@ lcdlp1: decf LCD_cnt_l, F, A ; no carry when 0x00 -> 0xff end - diff --git a/Message.s b/Message.s new file mode 100644 index 00000000..acee9843 --- /dev/null +++ b/Message.s @@ -0,0 +1,105 @@ +#include + +global Heart_Rate_Zone_Msg, Heart_Rate_Msg, Welcome_Msg + +extrn hr_msg, hrz_msg, welcome_msg + +psect Messages, class = CODE + +Heart_Rate_Msg: + movlw hr_msg + movwf FSR0 + + movlw 'H' + movwf INDF0 + incf FSR0 + movlw 'e' + movwf INDF0 + incf FSR0 + movlw 'a' + movwf INDF0 + incf FSR0 + movlw 'r' + movwf INDF0 + incf FSR0 + movlw 't' + movwf INDF0 + incf FSR0 + movlw ' ' + movwf INDF0 + incf FSR0 + movlw 'R' + movwf INDF0 + incf FSR0 + movlw 'a' + movwf INDF0 + incf FSR0 + movlw 't' + movwf INDF0 + incf FSR0 + movlw 'e' + movwf INDF0 + incf FSR0 + movlw ':' + movwf INDF0 + incf FSR0 + return + +Heart_Rate_Zone_Msg: + movlw hrz_msg + movwf FSR0 + + movlw 'Z' + movwf INDF0 + incf FSR0 + movlw 'o' + movwf INDF0 + incf FSR0 + movlw 'n' + movwf INDF0 + incf FSR0 + movlw 'e' + movwf INDF0 + incf FSR0 + movlw ':' + movwf INDF0 + incf FSR0 + return + +Welcome_Msg: + movlw welcome_msg + movwf FSR0 + + movlw 'I' + movwf INDF0 + incf FSR0 + movlw 'n' + movwf INDF0 + incf FSR0 + movlw 'p' + movwf INDF0 + incf FSR0 + movlw 'u' + movwf INDF0 + incf FSR0 + movlw 't' + movwf INDF0 + incf FSR0 + movlw ' ' + movwf INDF0 + incf FSR0 + movlw 'A' + movwf INDF0 + incf FSR0 + movlw 'g' + movwf INDF0 + incf FSR0 + movlw 'e' + movwf INDF0 + incf FSR0 + movlw ':' + movwf INDF0 + incf FSR0 + return + + diff --git a/RRInterval.s b/RRInterval.s new file mode 100644 index 00000000..cf4f8e57 --- /dev/null +++ b/RRInterval.s @@ -0,0 +1,177 @@ +#include + +global no_overflow, overflow, Sixteen_Division + +psect udata_acs + +prevtimeH: ds 1 +prevtimeL: ds 1 + +maxH:ds 1 +maxL:ds 1 +Num_H:ds 1 +Num_L:ds 1 +Den_H:ds 1 +Den_L:ds 1 +Heart_Rate:ds 1 + +periodH: ds 1 +periodL: ds 1 +Numerator: ds 1 ;for working out bpm 60/RR interval + +psect External_timer, class = CODE + +;RR_Setup: +; ;movlw 60 +; ;movwf Numerator ;for working out bpm 60/RR interval +; bsf TRISA, 5, A +; movlw 0x05 +; movwf CCP5CON, A +; ;bsf CCP5CON, 1 +; ;bsf CCP5CON, 2 ; sets up CCP5 as capture on every rising edge (00000110) +; movlw 0x00 +; banksel CCPTMRS1 +; movwf CCPTMRS1,B ;sets CCP5 to use timer1 for capture +; movlw 00111111B ;enables Timer1 to synchronise with external clock at RA5 and perform read/write in 1 16bit operation +; movwf T1CON +; bsf PIE4, 2 ;enables CCP5 interrupt +; bcf PIE1, 0 ;enables timer1 overflow interrupt +; bsf IPR4, 2 ;timer 1 +; ;bsf ODCON2, 2 +; movlw 0x00 +; movwf TRISF +; movwf PIR1 +; movwf PIR4 +; return +; +;RR_int: +; movlw 0xff +; movwf LATF, A +; bcf PIR4, 2 ;Checks to see if timer1 overflowed +; retfie f +; bra no_overflow +; bra overflow + +no_overflow: + ;subtract values stored in prevtimeH and prevtimeL from CCPR5H and CCPR5L store result in period + ;period returned in units of 1.024 ms + ;Do subtraction + MOVFF prevtimeL, WREG + CPFSLT CCPR5L + bra Subtract_no + bra Borrow_no +Borrow_no: + DECF CCPR5H, 1 +Subtract_no: + MOVFF prevtimeL, WREG + SUBWF CCPR5L, 0 ; subtract prevtimeL from CCPR5L, store result in WREG + MOVWF periodL ; move value into periodL + MOVFF prevtimeH, WREG + SUBWF CCPR5H, 0 ; subtract prevtimeH from CCPR5H, store result in WREG + MOVWF periodH + ;store value in periodH and periodL + + + movff CCPR5H, WREG ;update previous time + movwf prevtimeH + movff CCPR5L, WREG + movwf prevtimeL + bcf PIR4, 2 ;clear CCP interrupt flag + ;movff periodL, PORTF + retfie f + +overflow: + ;subtract prevtimeH and prevtimeL from 11111111B as this will be max value + ;add this result to values stored in CCPR5H and CCPR5L to calculate period + ;period returned in units of 1.024 ms (as this is the period of external clock) + ;Do subtraction + MOVLW 0xFF + MOVWF maxL + MOVWF maxH + + CPFSEQ prevtimeL ; prevtimeL = 0xFF, need to borrow + bra Subtract_o + bra Borrow_o +Borrow_o: + DECF maxH, 1 +Subtract_o: + MOVFF prevtimeL, WREG + SUBWF maxL, 0 ; subtract prevtimeL from CCPR5L, store result in WREG + MOVWF periodL ; move value into periodL + MOVFF prevtimeH, WREG + SUBWF maxH, 0 ; subtract prevtimeH from CCPR5H, store result in WREG + MOVWF periodH + + ;Addition periodL/H + CCPR5L/H +Addition: + + MOVFF CCPR5L, WREG + ADDWF periodL, 1 ; place value back into periodL + MOVFF periodL, WREG + + MOVFF CCPR5H, WREG + ADDWFC periodH, 1 + MOVFF periodH, WREG + + ;store final value in periodH and periodL + + movff CCPR5H, WREG ;update previous time + movwf prevtimeH + movff CCPR5L, WREG + movwf prevtimeL + bcf PIR4, 2 ;clear CCP interrupt flag + bcf PIR1, 2 + ;movff periodL, PORTF + retfie f + + + +Sixteen_Division: + MOVLW 0xEA + MOVWF Num_H + MOVLW 0x60 + MOVWF Num_L ; initiate numerator to 60000 ms +; MOVLW 0x02 +; MOVWF Den_H +; MOVLW 0x58 +; MOVWF Den_L + MOVLW 0 + MOVWF Heart_Rate ; initialise quotient +Check_for_zero: + MOVLW 0 + CPFSEQ PRODL + bra High_byte_check + CPFSEQ PRODH + bra High_byte_check + bra End_Sixteen_Division +High_byte_check: + MOVFF PRODH, WREG + CPFSGT Num_H + bra End_Sixteen_Division ; when high byte of denominator is greater than numerator + bra Low_byte_check +Low_byte_check: + MOVFF PRODL, WREG + CPFSGT Num_L + bra Sixteen_Borrow + bra Sixteen_Subtraction +Sixteen_Subtraction: + MOVFF PRODL, WREG + SUBWF Num_L, 1 + MOVFF PRODH, WREG + SUBWF Num_H, 1 + INCF Heart_Rate, 1 + ;MOVFF Heart_Rate, PORTB + bra High_byte_check +Sixteen_Borrow: + DECF Num_H, 1 ; borrow from Num_H + MOVFF PRODH, WREG + CPFSGT Num_H + bra End_Sixteen_Division + bra Sixteen_Subtraction +End_Sixteen_Division: + ;MOVFF Heart_Rate, PORTC + MOVFF Heart_Rate, WREG + ; move results into results register pair + return ; return with Heart Rate in WREG + + \ No newline at end of file diff --git a/Timer.s b/Timer.s new file mode 100644 index 00000000..8679d768 --- /dev/null +++ b/Timer.s @@ -0,0 +1,33 @@ +#include + +global Timer_Setup +psect External_timer, class = CODE + +;Timer_int_hi: +; btfss INTCON, 2 ;check this is a timer 0 interrupt, bit 5 = TMR0IF +; retfie f ;if not then return +; btfss PORTA, 4 ;check if bit 4 is set (skips next instruction if set) +; bra Turn_on +; bra Turn_off +; +;Turn_off: +; bcf PORTA, 4 +; bcf INTCON, 2 +; retfie f +; +;Turn_on: +; bsf PORTA, 4 +; bcf INTCON, 2 +; retfie f + +Timer_Setup: + movlw 10000011B ; Fcyc/256 = 62.5 KHz + movwf T0CON, A + bsf GIE ;enable all interrupts 7=GIE + bsf INTCON, 6 + bsf INTCON, 5 ;TMR0IE + return + + + + \ No newline at end of file diff --git a/UART.s b/UART.s index 32bf8df9..19b80024 100644 --- a/UART.s +++ b/UART.s @@ -1,6 +1,6 @@ #include -global UART_Setup, UART_Transmit_Message +global UART_Setup, UART_Transmit_Message, UART_Transmit_Byte psect udata_acs ; reserve data space in access ram UART_counter: ds 1 ; reserve 1 byte for variable UART_counter diff --git a/main.s b/main.s index dba4af8d..50be2a79 100644 --- a/main.s +++ b/main.s @@ -1,63 +1,280 @@ #include -extrn UART_Setup, UART_Transmit_Message ; external subroutines -extrn LCD_Setup, LCD_Write_Message +extrn UART_Setup, UART_Transmit_Message, UART_Transmit_Byte ; external subroutines +extrn LCD_Setup, Clear_LCD, LCD_Send_Byte_HR, LCD_Send_Byte_HRZ, LCD_Write_Message, LCD_Write_Hex, LCD_clear, LCD_shift +extrn Keypad_INIT, Keypad_READ, delay_ms +extrn Decode_First_Digit, Decode_Second_Digit, Read_Age_Input_Find_HR_Max +extrn Divide_By_20, Divide_By_Ten, Load_HRZ_Table, Determine_HRZ, IIR_Filter +extrn Timer_Setup, Divide_By_Hundred +extrn no_overflow, overflow, Sixteen_Division +extrn Heart_Rate_Zone_Msg, Heart_Rate_Msg, Welcome_Msg +global hr_msg, hrz_msg, welcome_msg, age_address_1, age_address_2, heart_rate_zone_address, measured_heart_rate_zone_address psect udata_acs ; reserve data space in access ram -counter: ds 1 ; reserve one byte for a counter variable -delay_count:ds 1 ; reserve one byte for counter in the delay routine - +counter: ds 1 ; reserve one byte for a counter variable +delay_count:ds 1 ; reserve one byte for counter in the delay routine +Measured_Zone:ds 1 +Time_Counter:ds 1 +OverflowCounter_1:ds 1 +OverflowCounter_2:ds 1 +Count:ds 1 +hundred_digit:ds 1 +ten_digit:ds 1 +single_digit:ds 1 +HR_Zone:ds 1 +HR_Measured:ds 1 ; reserve one byte for measured HR value from sensor +HR_max: ds 1 ; the maximum heart rate calculated froma ge +HR_max_20: ds 1 ; the quotient of HR_max divided by 20 +LOOP_COUNTER:ds 1 ; loop counter for HRZ boundary value calculations +TABLE_INDEX_DIFF:ds 1 ; variable used to check end of loop condition +STATUS_CHECK:ds 1 ; use this in loop to check if the end of loop as been reached + TABLE_START_ADDRESS EQU 0xA0 ; table start address for HRZ boundary values + TABLE_SIZE EQU 8 ; this value needs to be n+1, where n is how many times you want to read/write the table +hr_msg EQU 0xE0 +hrz_msg EQU 0xF0 +measured_heart_rate_address EQU 0xD0 +measured_heart_rate_zone_address EQU 0xC0 +welcome_msg EQU 0xB0 +age_address_1 EQU 0xA0 +age_address_2 EQU 0xA1 +heart_rate_zone_address EQU 0xC2 + psect udata_bank4 ; reserve data anywhere in RAM (here at 0x400) myArray: ds 0x80 ; reserve 128 bytes for message data -psect data +psect edata ; store data in EEPROM, so can read and write +;ORG 0x1000 ; ******* myTable, data in programme memory, and its length ***** -myTable: - db 'H','e','l','l','o',' ','W','o','r','l','d','!',0x0a +Database: + DB 20, 18, 17, 15, 13, 11 + align 2 + +psect data +HRMessage: + db 'H','R','=',0x0a ; message, plus carriage return - myTable_l EQU 13 ; length of data + myTable_l EQU 4 ; length of data align 2 - + psect code, abs rst: org 0x0 goto setup +Timer_Interrupt:org 0x0008 + btfss TMR0IF + retfie f + call Increase_Interrupt + bcf TMR0IF + movlw 10000011B ; Fcyc/128 = 125 KHz + movwf T0CON, A + retfie f + ; ******* Programme FLASH read Setup Code *********************** setup: bcf CFGS ; point to Flash program memory bsf EEPGD ; access Flash program memory call UART_Setup ; setup UART - call LCD_Setup ; setup UART + call Keypad_INIT ; setup keypad + call LCD_Setup + call Timer_Setup + + ; load messages into database + call Heart_Rate_Msg + call Heart_Rate_Zone_Msg + call Welcome_Msg + + movlw 0x00 + movwf OverflowCounter_1 ; Initialise Time_Counter + movwf OverflowCounter_2 + + movlw 0x00 + movwf TRISH + + movlw 0x00 + movwf TRISF + +; movlw 0x00 +; movwf TRISC + + movlw 0xFF + movwf TRISD + + movlw 0x00 + movwf TRISJ + + ;movlw 0 + ;movwf kb_pressed, A ; initialise this as 0, to indicate o key has been pressed + goto start ; ******* Main programme **************************************** -start: lfsr 0, myArray ; Load FSR0 with address in RAM - movlw low highword(myTable) ; address of data in PM - movwf TBLPTRU, A ; load upper bits to TBLPTRU - movlw high(myTable) ; address of data in PM - movwf TBLPTRH, A ; load high byte to TBLPTRH - movlw low(myTable) ; address of data in PM - movwf TBLPTRL, A ; load low byte to TBLPTRL - movlw myTable_l ; bytes to read - movwf counter, A ; our counter register -loop: tblrd*+ ; one byte from PM to TABLAT, increment TBLPRT - movff TABLAT, POSTINC0; move data from TABLAT to (FSR0), inc FSR0 - decfsz counter, A ; count down to zero - bra loop ; keep going until finished - - movlw myTable_l ; output message to UART - lfsr 2, myArray - call UART_Transmit_Message - movlw myTable_l ; output message to LCD - addlw 0xff ; don't send the final carriage return to LCD - lfsr 2, myArray +start: + call LCD_clear + movlw welcome_msg + movwf FSR2 + movlw 10 ; because there are 11 letters + call LCD_Write_Message ; write welcome messgae, prompt age input + call LCD_shift + + call Read_Age_Input_Find_HR_Max ; return with W = HRmax + movwf HR_max + movlw 121 + call Load_HRZ_Table + + call Timer_Setup ; this needs to happen after loading HRZ table, because interrupts interfere with eeprom + + movlw 0x00 + movwf PORTJ, A ; clear checking port +Detection_Loop: + + movlw 0x00 + CPFSGT PORTD ; skip if pulse signal is high + bra Update_and_Branch + CPFSGT PORTJ ; skip if previous pulse was also high + call Signal_Detected + bra Update_and_Branch +Update_and_Branch: + MOVFF PORTD, PORTJ ; update LATJ with current value + MOVLW 0x00 + MOVWF PORTH + bra Detection_Loop +Signal_Detected: + MOVFF PORTD, PORTJ ; update LATJ with current value + MOVLW 0xFF + MOVWF PORTH + + ;still need to calculate heart rate from here + movff OverflowCounter_1, WREG + mullw 66 + call Sixteen_Division + MOVWF HR_Measured + + ;MOVFF PRODL, HR_Measured ; move timer count to WREG, OverflowCounter increments 1 every 4.08ms + ;MOVFF HR_Measured, WREG + call IIR_Filter ; Output_HR = average of past 3 measurements + ; write to LCD + + call Load_Measured_Heart_Rate ; load heart rate into database + call LCD_clear + + movlw hr_msg + movwf FSR2 + movlw 11 ; because there are 11 letters call LCD_Write_Message + + ; write heart rate to LCD + movlw measured_heart_rate_address + movwf FSR2 + movlw 3 ; assume 3 digits + call LCD_Write_Message ; Display the number + call LCD_shift + + ; write heart rate to UART + movlw measured_heart_rate_address + movwf FSR2 + movlw 3 + call UART_Transmit_Message + + CLRF OverflowCounter_1, A ; reset time_counter + + movlw ',' + call UART_Transmit_Byte + + MOVFF HR_Measured, WREG + call Determine_HRZ ; return with zone number in WREG + call Load_Measured_Heart_Rate_Zone + + ; write hr zone prompt + movlw hrz_msg + movwf FSR2 + movlw 5 ; because there are 5 letters + call LCD_Write_Message + + ; write zone information + movlw measured_heart_rate_zone_address + movwf FSR2 + movlw 1 + call LCD_Write_Message ; Display the number + call LCD_shift + + movlw measured_heart_rate_zone_address + movwf FSR2 + movlw 1 + call UART_Transmit_Message + + movlw 0x0A + call UART_Transmit_Byte + + + bra Detection_Loop - goto $ ; goto current line in code - ; a delay subroutine if you need one, times around loop in delay_count -delay: decfsz delay_count, A ; decrement until zero - bra delay +Increase_Interrupt: + INCF OverflowCounter_1, 1 + MOVFF OverflowCounter_1, WREG + MOVFF OverflowCounter_1, LATH + BC Increment_OFC2 ; Branch if carry return - - end rst \ No newline at end of file +Increment_OFC2: + INCF OverflowCounter_2, 1 + ;MOVFF OverflowCounter_2, WREG + return + +Find_HR_from_Overflow: + MOVFF Count, WREG ; move count to W for multiplication + MULLW 8 ; multiply counter with period of timer0, result stored in PRODH:PRODL + call Sixteen_Division; denominator stored in PRODH, PRODL + return + +Load_Measured_Heart_Rate_Zone: + movwf HR_Zone + + movlw measured_heart_rate_zone_address + movwf FSR0 + + movff HR_Zone, WREG + addlw '0' + call Write_to_FSR + return + +Load_Measured_Heart_Rate: ; enter with measured heart rate in WREG + movwf Count + + movlw measured_heart_rate_address + movwf FSR0 + + movff Count, WREG + call Divide_By_Hundred ; return with quotient in WREG + movwf hundred_digit + movff hundred_digit, WREG + addlw '0' + call Write_to_FSR + movff hundred_digit, WREG + mullw 100 ; subtract hundred digit + movff PRODL, WREG + subwf Count, 1 ; Count - PRODL (the hundred digit), store in Count + + movff Count, WREG + call Divide_By_Ten + movwf ten_digit + movff ten_digit, WREG + addlw '0' + call Write_to_FSR + movff ten_digit, WREG + mullw 10 + movff PRODL, WREG + subwf Count, 1 + + movff Count, WREG + addlw '0' + call Write_to_FSR + return + +Write_to_FSR: + movwf INDF0 + incf FSR0 + return + + + end rst + \ No newline at end of file diff --git a/nbproject/configurations.xml b/nbproject/configurations.xml index 06bd9f31..77fa66aa 100755 --- a/nbproject/configurations.xml +++ b/nbproject/configurations.xml @@ -16,6 +16,12 @@ main.s UART.s LCD.s + Keypad.s + Digit_Reader.s + Calculations.s + RRInterval.s + Timer.s + Message.s PIC18F87K22 - ICD3PlatformTool + Simulator pic-as - 2.30 + 2.45 4 - + @@ -58,6 +64,7 @@ false + false false @@ -72,8 +79,13 @@ + + + + + @@ -108,6 +120,11 @@ + + + + + @@ -132,6 +149,13 @@ value="toolpack.updateoptions.uselatestoolpack"/> + + + + + + + @@ -261,6 +285,8 @@ value="report"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -576,6 +1115,7 @@ + @@ -596,6 +1136,7 @@ +