; KCPSM3 Program - Control of LEDs and LCD display using switches, buttons and rotary ; controls on the Spartan-3E Starter Kit. ; ; ; Ken Chapman - Xilinx Ltd ; ; Version v1.00 - 4th January 2006 ; ; ************************************************************************************** ; Port definitions ; ************************************************************************************** ; ; adapté pour IDE et modifié par P.NOUEL ; ENSEIRB - 2006 ; ; rajout du programme de lecture des messages en ROM ; VHDL "ROM_blank_new.vhd", "prog_rom.vhd", "prog_rom" ; ; LCD interface ports ; ; The master enable signal is not used by the LCD display itself ; but may be required to confirm that LCD communication is active. ; This is required on the Spartan-3E Starter Kit if the StrataFLASH ; is used because it shares the same data pins and conflicts must be avoided. ; LCD_output_port EQU $A0 ; LCD min module output data and control LCD_E EQU 1 ; active High Enable E - bit0 LCD_RW EQU 2 ; Read=1 Write=0 RW - bit1 LCD_RS EQU 4 ; Instruction=0 Data=1 RS - bit2 LCD_drive EQU 8 ; Master enable (active High) - bit3 LCD_DB4 EQU 16 ; 4-bit Data DB4 - bit4 LCD_DB5 EQU 32 ; interface Data DB5 - bit5 LCD_DB6 EQU 64 ; Data DB6 - bit6 LCD_DB7 EQU $80 ; Data DB7 - bit7 ; ; LCD_input_port EQU $80 ; LCD character module input data LCD_read_spare0 EQU 1 ; Spare bits - bit0 LCD_read_spare1 EQU 2 ; are zero - bit1 LCD_read_spare2 EQU 4 ; - bit2 LCD_read_spare3 EQU 8 ; - bit3 LCD_read_DB4 EQU 16 ; 4-bit Data DB4 - bit4 LCD_read_DB5 EQU 32 ; interface Data DB5 - bit5 LCD_read_DB6 EQU 64 ; Data DB6 - bit6 LCD_read_DB7 EQU $80 ; Data DB7 - bit7 ; SWITCH_input_port EQU $C0 ; quatres boutons ; adresse_ROM EQU 0 ; rom contenant les messages ; ; ; ; ************************************************************************************** ; Useful data constants ; ************************************************************************************** ; ; ; The main operation of the program uses 1ms delays to set the shift rate ; of the LCD display. A 16-bit value determines how many milliseconds ; there are between shifts ; ; Tests indicate that the fastest shift rate that the LCD display supports is ; 500ms. Faster than this and the display becomes less clear to read. ; shift_delay_msb EQU 1 ; delay is 500ms (01F4 hex) shift_delay_lsb EQU $F4 ; ; ; ; ; Constant to define a software delay of 1us. This must be adjusted to reflect the ; clock applied to KCPSM3. Every instruction executes in 2 clock cycles making the ; calculation highly predictable. The '6' in the following equation even allows for ; 'CALL delay_1us' instruction in the initiating code. ; ; delay_1us_constant = (clock_rate - 6)/4 Where 'clock_rate' is in MHz ; ; Example: For a 50MHz clock the constant value is (10-6)/4 = 11 (0B Hex). ; For clock rates below 10MHz the value of 1 must be used and the operation will ; become lower than intended. ; delay_1us_constant EQU 11 ; ; ; ; ASCII table ; min_a EQU $61 min_b EQU $62 min_c EQU $63 min_d EQU $64 min_e EQU $65 min_f EQU $66 min_g EQU $67 min_h EQU $68 min_i EQU $69 min_j EQU $6A min_k EQU $6B min_l EQU $6C min_m EQU $6D min_n EQU $6E min_o EQU $6F min_p EQU $70 min_q EQU $71 min_r EQU $72 min_s EQU $73 min_t EQU $74 min_u EQU $75 min_v EQU $76 min_w EQU $77 min_x EQU $78 min_y EQU $79 min_z EQU $7A maj_A EQU $41 maj_B EQU $42 maj_C EQU $43 maj_D EQU $44 maj_E EQU $45 maj_F EQU $46 maj_G EQU $47 maj_H EQU $48 maj_I EQU $49 maj_J EQU $4A maj_K EQU $4B maj_L EQU $4C maj_M EQU $4D maj_N EQU $4E maj_O EQU $4F maj_P EQU $50 maj_Q EQU $51 maj_R EQU $52 maj_S EQU $53 maj_T EQU $54 maj_U EQU $55 maj_V EQU $56 maj_W EQU $57 maj_X EQU $58 maj_Y EQU $59 maj_Z EQU $5A character_0 EQU $30 character_1 EQU $31 character_2 EQU $32 character_3 EQU $33 character_4 EQU $34 character_5 EQU $35 character_6 EQU $36 character_7 EQU $37 character_8 EQU $38 character_9 EQU $39 character_colon EQU $3A character_stop EQU $2E character_semi_colon EQU $3B character_minus EQU $2D character_divide EQU $2F ; '/' character_plus EQU $2B character_comma EQU $2C character_less_than EQU $3C character_greater_than EQU $3E character_equals EQU $3D character_space EQU $20 character_CR EQU $0D ; carriage return character_question EQU $3F ; '?' character_dollar EQU $24 character_exclaim EQU $21 ; '!' character_BS EQU $08 ; Back Space command character parenthesis_r EQU $29 ; ')' parenthesis_l EQU $28 ; '(' ; ; ; ; ; ; ************************************************************************************** ; Initialise the system ; ************************************************************************************** ; cold_start: CALL LCD_reset ; initialise LCD display ; ; Write welcome message to LCD display ; LOAD s5, 16 ; Line 1 position 0 CALL LCD_cursor CALL disp_ENSEIRB ; Display 'ENSEIRB LCD avec ROM' LOAD s5, 32 ; Line 2 position 14 CALL LCD_cursor CALL disp_invite ; Display 'Commuter un switche (et un seul)' ; ; ; ************************************************************************************** ; Main program ; ************************************************************************************** ; ; The main program is responsible for continuously shifting the LCD display ; at 0.5 second intervals. It also polls the centre press button of the rotary ; encoder to determine which mode of LED control should be active. ; ; LCD_shift_delay: LOAD sF, shift_delay_msb ; [sF,sE]=loop delay in ms LOAD sE, shift_delay_lsb LCD_delay_loop: CALL delay_1ms ; 1ms delay SUB sE, 1 ; decrement delay counter SUBC sF, 0 JUMP NC, LCD_delay_loop CALL LCD_shift_left ; shift LCD display IN s0, SWITCH_input_port ; acquisition du numero COMP s0, 0 ; est ce 0 ? JUMP NZ, message ; sinon afficher message JUMP LCD_shift_delay ; ; ; ; ; ; ; ; ; ************************************************************************************** ; LCD text messages ; ************************************************************************************** ; ; ; Display 'ENSEIRB LCD avec ROM' on LCD at current cursor position ; ; disp_ENSEIRB: LOAD s5, maj_E CALL LCD_write_data LOAD s5, maj_N CALL LCD_write_data LOAD s5, maj_S CALL LCD_write_data LOAD s5, maj_E CALL LCD_write_data LOAD s5, maj_I CALL LCD_write_data LOAD s5, maj_R CALL LCD_write_data LOAD s5, maj_B CALL LCD_write_data CALL disp_space LOAD s5, maj_L CALL LCD_write_data LOAD s5, maj_C CALL LCD_write_data LOAD s5, maj_D CALL LCD_write_data CALL disp_space LOAD s5, min_a CALL LCD_write_data LOAD s5, min_v CALL LCD_write_data LOAD s5, min_e CALL LCD_write_data LOAD s5, min_c CALL LCD_write_data CALL disp_space LOAD s5, maj_R CALL LCD_write_data LOAD s5, maj_O CALL LCD_write_data LOAD s5, maj_M CALL LCD_write_data RET ; ; ; Display 'Commuter un switch (et un seul)' on LCD at current cursor position ; ; disp_invite: LOAD s5, maj_c CALL LCD_write_data LOAD s5, min_o CALL LCD_write_data LOAD s5, min_m CALL LCD_write_data LOAD s5, min_m CALL LCD_write_data LOAD s5, min_u CALL LCD_write_data LOAD s5, min_t CALL LCD_write_data LOAD s5, min_e CALL LCD_write_data LOAD s5, min_r CALL LCD_write_data CALL disp_space LOAD s5, min_u CALL LCD_write_data LOAD s5, min_n CALL LCD_write_data CALL disp_space LOAD s5, min_s CALL LCD_write_data LOAD s5, min_w CALL LCD_write_data LOAD s5, min_i CALL LCD_write_data LOAD s5, min_t CALL LCD_write_data LOAD s5, min_c CALL LCD_write_data LOAD s5, min_h CALL LCD_write_data CALL disp_space LOAD s5, parenthesis_l CALL LCD_write_data LOAD s5, min_e CALL LCD_write_data LOAD s5, min_t CALL LCD_write_data CALL disp_space LOAD s5, min_u CALL LCD_write_data LOAD s5, min_n CALL LCD_write_data CALL disp_space LOAD s5, min_s CALL LCD_write_data LOAD s5, min_e CALL LCD_write_data LOAD s5, min_u CALL LCD_write_data LOAD s5, min_l CALL LCD_write_data LOAD s5, parenthesis_r CALL LCD_write_data RET ; ; Display a space on LCD at current cursor position ; ; disp_space: LOAD s5, character_space CALL LCD_write_data RET ; ; ; ; ; ************************************************************************************** ; Software delay routines ; ************************************************************************************** ; ; ; ; Delay of 1us. ; ; Constant value defines reflects the clock applied to KCPSM3. Every instruction ; executes in 2 clock cycles making the calculation highly predictable. The '6' in ; the following equation even allows for 'CALL delay_1us' instruction in the initiating code. ; ; delay_1us_constant = (clock_rate - 6)/4 Where 'clock_rate' is in MHz ; ; Registers used s0 ; delay_1us: LOAD s0, delay_1us_constant wait_1us: SUB s0, 1 JUMP NZ, wait_1us RET ; ; Delay of 40us. ; ; Registers used s0, s1 ; delay_40us: LOAD s1, 40 ; 40 x 1us = 40us wait_40us: CALL delay_1us SUB s1, 1 JUMP NZ, wait_40us RET ; ; ; Delay of 1ms. ; ; Registers used s0, s1, s2 ; delay_1ms: LOAD s2, 25 ; 25 x 40us = 1ms wait_1ms: CALL delay_40us SUB s2, 1 JUMP NZ, wait_1ms RET ; ; Delay of 20ms. ; ; Delay of 20ms used during initialisation. ; ; Registers used s0, s1, s2, s3 ; delay_20ms: LOAD s3, 20 ; 20 x 1ms = 20ms wait_20ms: CALL delay_1ms SUB s3, 1 JUMP NZ, wait_20ms RET ; ; Delay of approximately 1 second. ; ; Registers used s0, s1, s2, s3, s4 ; delay_1s: LOAD s4, 50 ; 50 x 20ms = 1000ms wait_1s: CALL delay_20ms SUB s4, 1 JUMP NZ, wait_1s RET ; ; ; ; ************************************************************************************** ; LCD Character Module Routines ; ************************************************************************************** ; ; LCD module is a 16 character by 2 line display but all displays are very similar ; The 4-wire data interface will be used (DB4 to DB7). ; ; The LCD modules are relatively slow and software delay loops are used to slow down ; KCPSM3 adequately for the LCD to communicate. The delay routines are provided in ; a different section (see above in this case). ; ; ; Pulse LCD enable signal 'E' high for greater than 230ns (1us is used). ; ; Register s4 should define the current state of the LCD output port. ; ; Registers used s0, s4 ; LCD_pulse_E: XOR s4, LCD_E ; E=1 OUT s4, LCD_output_port CALL delay_1us XOR s4, LCD_E ; E=0 OUT s4, LCD_output_port RET ; ; Write 4-bit instruction to LCD display. ; ; The 4-bit instruction should be provided in the upper 4-bits of register s4. ; Note that this routine does not release the master enable but as it is only ; used during initialisation and as part of the 8-bit instruction write it ; should be acceptable. ; ; Registers used s4 ; LCD_write_inst4: AND s4, $F8 ; Enable=1 RS=0 Instruction, RW=0 Write, E=0 OUT s4, LCD_output_port ; set up RS and RW >40ns before enable pulse CALL LCD_pulse_E RET ; ; ; Write 8-bit instruction to LCD display. ; ; The 8-bit instruction should be provided in register s5. ; Instructions are written using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ; Registers used s0, s1, s4, s5 ; LCD_write_inst8: LOAD s4, s5 AND s4, $F0 ; Enable=0 RS=0 Instruction, RW=0 Write, E=0 OR s4, LCD_drive ; Enable=1 CALL LCD_write_inst4 ; write upper nibble CALL delay_1us ; wait >1us LOAD s4, s5 ; select lower nibble with SL1 s4 ; Enable=1 SL0 s4 ; RS=0 Instruction SL0 s4 ; RW=0 Write SL0 s4 ; E=0 CALL LCD_write_inst4 ; write lower nibble CALL delay_40us ; wait >40us LOAD s4, $F0 ; Enable=0 RS=0 Instruction, RW=0 Write, E=0 OUT s4, LCD_output_port ; Release master enable RET ; ; ; ; Write 8-bit data to LCD display. ; ; The 8-bit data should be provided in register s5. ; Data bytes are written using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ; Registers used s0, s1, s4, s5 ; LCD_write_data: LOAD s4, s5 AND s4, $F0 ; Enable=0 RS=0 Instruction, RW=0 Write, E=0 OR s4, 12 ; Enable=1 RS=1 Data, RW=0 Write, E=0 OUT s4, LCD_output_port ; set up RS and RW >40ns before enable pulse CALL LCD_pulse_E ; write upper nibble CALL delay_1us ; wait >1us LOAD s4, s5 ; select lower nibble with SL1 s4 ; Enable=1 SL1 s4 ; RS=1 Data SL0 s4 ; RW=0 Write SL0 s4 ; E=0 OUT s4, LCD_output_port ; set up RS and RW >40ns before enable pulse CALL LCD_pulse_E ; write lower nibble CALL delay_40us ; wait >40us LOAD s4, $F0 ; Enable=0 RS=0 Instruction, RW=0 Write, E=0 OUT s4, LCD_output_port ; Release master enable RET ; ; ; ; ; Read 8-bit data from LCD display. ; ; The 8-bit data will be read from the current LCD memory address ; and will be returned in register s5. ; It is advisable to set the LCD address (cursor position) before ; using the data read for the first time otherwise the display may ; generate invalid data on the first read. ; ; Data bytes are read using the following sequence ; Upper nibble ; wait >1us ; Lower nibble ; wait >40us ; ; Registers used s0, s1, s4, s5 ; LCD_read_data8: LOAD s4, 14 ; Enable=1 RS=1 Data, RW=1 Read, E=0 OUT s4, LCD_output_port ; set up RS and RW >40ns before enable pulse XOR s4, LCD_E ; E=1 OUT s4, LCD_output_port CALL delay_1us ; wait >260ns to access data IN s5, LCD_input_port ; read upper nibble XOR s4, LCD_E ; E=0 OUT s4, LCD_output_port CALL delay_1us ; wait >1us XOR s4, LCD_E ; E=1 OUT s4, LCD_output_port CALL delay_1us ; wait >260ns to access data IN s0, LCD_input_port ; read lower nibble XOR s4, LCD_E ; E=0 OUT s4, LCD_output_port AND s5, $F0 ; merge upper and lower nibbles SR0 s0 SR0 s0 SR0 s0 SR0 s0 OR s5, s0 LOAD s4, 4 ; Enable=0 RS=1 Data, RW=0 Write, E=0 OUT s4, LCD_output_port ; Stop reading 5V device and release master enable CALL delay_40us ; wait >40us RET ; ; ; Reset and initialise display to communicate using 4-bit data mode ; Includes routine to clear the display. ; ; Requires the 4-bit instructions 3,3,3,2 to be sent with suitable delays ; following by the 8-bit instructions to set up the display. ; ; 28 = '001' Function set, '0' 4-bit mode, '1' 2-line, '0' 5x7 dot matrix, 'xx' ; 06 = '000001' Entry mode, '1' increment, '0' no display shift ; 0C = '00001' Display control, '1' display on, '0' cursor off, '0' cursor blink off ; 01 = '00000001' Display clear ; ; Registers used s0, s1, s2, s3, s4 ; LCD_reset: CALL delay_20ms ; wait more that 15ms for display to be ready LOAD s4, 48 CALL LCD_write_inst4 ; send '3' CALL delay_20ms ; wait >4.1ms CALL LCD_write_inst4 ; send '3' CALL delay_1ms ; wait >100us CALL LCD_write_inst4 ; send '3' CALL delay_40us ; wait >40us LOAD s4, 32 CALL LCD_write_inst4 ; send '2' CALL delay_40us ; wait >40us LOAD s5, 40 ; Function set CALL LCD_write_inst8 LOAD s5, 6 ; Entry mode CALL LCD_write_inst8 LOAD s5, 12 ; Display control CALL LCD_write_inst8 LCD_clear: LOAD s5, 1 ; Display clear CALL LCD_write_inst8 CALL delay_1ms ; wait >1.64ms for display to clear CALL delay_1ms RET ; ; Position the cursor ready for characters to be written. ; The display is formed of 2 lines of 16 characters and each ; position has a corresponding address as indicated below. ; ; Character position ; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ; ; Line 1 - 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F ; Line 2 - C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF ; ; This routine will set the cursor position using the value provided ; in register s5. The upper nibble will define the line and the lower ; nibble the character position on the line. ; Example s5 = 2B will position the cursor on line 2 position 11 ; ; Registers used s0, s1, s2, s3, s4 ; LCD_cursor: TEST s5, 16 ; test for line 1 JUMP Z, set_line2 AND s5, 15 ; make address in range 80 to 8F for line 1 OR s5, $80 CALL LCD_write_inst8 ; instruction write to set cursor RET set_line2: AND s5, 15 ; make address in range C0 to CF for line 2 OR s5, $C0 CALL LCD_write_inst8 ; instruction write to set cursor RET ; ; This routine will shift the complete display one position to the left. ; The cursor position and LCD memory contents will not change. ; ; ; Registers used s0, s1, s2, s3, s4, s5 ; LCD_shift_left: LOAD s5, 24 ; shift display left CALL LCD_write_inst8 RET ; ; message : Programme en boucle qui affiche le message en fonction de son numéro ; : Lit le numero du message de 1 à n ; : parcours la mémoire pour se positionner en debut de message ; : affiche sur le lcd le message correspondant au numero ; registres utilises : s0 pour numero ; : s3 pour adresse de debut de message ( entre 0 et 7F) ; : s5 pour donnees ASCIII a transferer ; message: LOAD s3, adresse_ROM ; pointeur de debut d'adresse ROM IN s0, SWITCH_input_port ; acquisition du numero COMP s0, 0 ; est ce 0 ? JUMP Z, retour ; boucle d'attente repeat2: COMP s0, 1 ; est ce 1 ? JUMP Z, envoi ; pointeur ok, envoi possible repeat1: IN s5, s3 ; recherche du debut de message COMP s5, 0 ; fin de message ? JUMP Z , oui ; si oui ... ADD s3, 1 ; pointeur++ JUMP repeat1 oui: ADD s3, 1 ; pour depasser le 0 SUB s0, 1 ; numero-- JUMP repeat2 ; boucle deplacement du pointeur ADD s3, 1 ; incrementation du pointeur envoi: CALL LCD_clear LOAD s2, 5 ; a supprimer repeat3: IN s5, s3 ; acquisition caractere COMP s5,0 ; test du fin de chaine JUMP Z, wait ; si x"00" alors c'est fini CALL LCD_write_data ; envoi ADD s3, 1 ; caractere suivant JUMP repeat3 ; wait: IN s0, SWITCH_input_port ; acquisition du numero COMP s0, 0 ; est ce 0 ? JUMP NZ, wait ; attente tant que pas 0 retour: JUMP message