; ; 2x16 Chars LCD and some keys as a module, which is accessed using ; a (slightly modified, slow) I2C-bus. ; ; Copyright (C) 2007 DK1RM ; ; This code may be used by radio amateurs and hobbyists for private ; purposes. Commercial usage (even of parts of this code) without ; my explicit allowance is prohibited. ; ; 13-Dec-2007 DK1RM ; Mod/Bugfix: To reduce amount of databytes to be transfered to write ; some text, command LCD_KEY_WRITE_LCD_DATA does no more set flag ; ACTION_AWAIT_CMD after writing the databyte received. So any data ; received after this command will be written to the display until the ; I2C-functions signal "end of transfer" (by clearing "I2C_state"-bit ; I2C_SBIT_SELECTED). ; ; 06-Oct-2007 DK1RM ; Bugfix: Commands, which need data, have to set ACTION_AWAIT_CMD ; after processing. ; ; ; -- Definition of used PIC list p=16f84a include "p16f84a.inc" ; -- PIC config: external xtal osc, watchdog off, startup timer on __config _CP_ON & _HS_OSC & _WDT_OFF & _PWRTE_ON ; -- Value definitions ; for timer PRESCALE equ 0x01 ; devide Fosc/4 by 4 -> 4us clock to timer ;;OPTION_INIT equ 0xd0 + PRESCALE ; used in simulation (seems "gpsim" cant handle internal pullups correct) OPTION_INIT equ 0x50 + PRESCALE ; value to initialize OPTION_REG: ; RBPU = 0 -> activate pullups at port B ; INTEDG, T0SE = 1 (don't using them) ; T0CS = 0 -> use Fosc/4 ; PSA = 0 -> pass Fosc/4 through prescaler TIMER_VAL equ 0x05 ; timer-value to get interrupt in abt. 1ms ; values needed by included LCD-code LCD_D_PORT equ 0 ; using PORTA for databits LCD_D_PORT_LOW equ 0 ; using PA0-3 to drive LCD-databits 4-7 LCD_RS_PORT equ 1 ; using PORTB for signal "RS" LCD_RS_BIT equ 6 ; using PB6 for signal "RS" LCD_RW_PORT equ 1 ; using PORTB for signal "RW" LCD_RW_BIT equ 7 ; using PB7 for signal "RW" LCD_E_PORT equ 1 ; using PORTB for signal "E" LCD_E_BIT equ 3 ; using PB3 for signal "E" LCD_CLOCKSPEED equ 4 ; clock of PIC is 4MHz ;;LCD_MINFUNC equ 1 ; not defined, need full implementation here ; values needed by included I2C-code ;;I2C_MASTER not defined, this should be a slave-device I2C_PORT equ 1 ; using PORTB for this I2C_SCL_BIT equ 4 ; use PB4 for SCL I2C_SDA_BIT equ 5 ; use PB5 for SDA ; values needed to scan keys KEY_SELECT_PORT equ 1 ; using PORTB to select keys KEY_SELECT_BIT0 equ 2 ; PB2 is driving the LSB KEY_SELECT_BIT1 equ 1 ; PB1 KEY_SELECT_BIT2 equ 0 ; PB0 is driving the MSB KEY_SCAN_PORT equ 0 ; using PORTA to read keys KEY_SCAN_BIT equ 4 ; PB4 is used for reading a key ; bits in "action" ACTION_POLL_KEYS equ 0 ; Timer overflow, time to poll keys ACTION_AWAIT_CMD equ 1 ; Awaiting command from I2C-bus ACTION_SEND_LCD_ADR equ 2 ; If data is requested, send LCD-address ACTION_SEND_LCD_DAT equ 3 ; If data is requested, send LCD-data ; include commandcodes for this program here (also used by I2C_master) include "lcd_key_mod_cmds.inc" ; -- Variables (RAM) definitions cblock 0x0c ; used by interrupt-function wsav ; saved W during interrupt-handling statsav ; saved STATUS during interrupt-handling ; needed by included LCD-code LCD_tmp1 ; used by included LCD-code LCD_tmp2 LCD_tmp3 LCD_data ; needed by included I2C-code I2C_state ; Current state of sending/receiving data. I2C_action ; Additional bits used for bus-protocol. I2C_saddr ; Slave-address (7 bits), set by initialization. I2C_lport ; Contains last state of portbits. I2C_buffer ; Used to assemble serial data. I2C_count ; Bitcounter. I2C_rxbuffer ; Buffer for received byte I2C_txbuffer ; Buffer for byte to send ;; I2C_tmp ; Temporary data (not needed for a slave-device). ; used by application action ; some flagbits signaling what to do key_index ; index for scanning keys keybits ; current state of keys newkeybits ; bits for assembling current state of keys key0 ; for debouncing key 0 key1 ; ... these have to be consecutive key2 key3 key4 key5 key6 key7 ; for debouncing key 7 ;; state ; some flagbits tmp command ; the command got databyte ; the databyte got ;; buffer ; using rest as buffer endc ; -- Programm code ; startup org 0 goto startup ; interrupt-handling org 4 irq: movwf wsav ; save W (does not affect STATUS-flags) swapf STATUS, w ; get content of STATUS movwf statsav ; save (nibble-swaped) STATUS btfss INTCON, RBIF ; change on PB4-7 ? goto check_timer_irq ; no, try next bcf INTCON, RBIF ; clear it (before! calling function) call I2C__Changed ; yes, handle it check_timer_irq: btfss INTCON, T0IF ; irq from timer 0 ? goto irq_exit ; no, ignore ; handle timer-interrupt bcf STATUS, RP0 ; select bank 0 bcf INTCON, T0IF ; clear interrupt-bit movlw TIMER_VAL ; value to trigger next irq movwf TMR0 bsf action, ACTION_POLL_KEYS ; signal timer overflow ; interrupt return irq_exit: swapf statsav, w ; get back saved STATUS movwf STATUS ; restore STATUS without changing flags swapf wsav, f ; swap nibbles of saved W swapf wsav, w ; restore W without changing flags retfie ; return (and set GIE again) ; ; The following two functions have to be coded within ; a 8-bit-address-space ; ; process a command without data. The function called ; depends on the (checked!) value in W. ; process_command: addwf PCL, f nop ; unused goto set_datasource_keys ; LCD_KEY_DATASOURCE_KEYS goto set_datasource_lcdaddr ; LCD_KEY_DATASOURCE_LCDAADDR goto set_datasource_lcddata ; LCD_KEY_DATASOURCE_LCDDATA goto LCD__Clear ; LCD_KEY_CLEAR_LCD goto LCD__Home ; LCD_KEY_HOME goto LCD__SetCursorForward ; LCD_KEY_CURSORDIR_FWD goto LCD__SetCursorBackward ; LCD_KEY_CURSORDIR_BACK goto LCD__SetDisplayShiftOn ; LCD_KEY_LCDSHIFT_ON goto LCD__SetDisplayShiftOff ; LCD_KEY_LCDSHIFT_OFF goto LCD__DisplayOn ; LCD_KEY_DISPLAY_ON goto LCD__DisplayOff ; LCD_KEY_DISPLAY_OFF goto LCD__CursorOn ; LCD_KEY_CURSOR_ON goto LCD__CursorOff ; LCD_KEY_CURSOR_OFF goto LCD__BlinkingOn ; LCD_KEY_BLINK_ON goto LCD__BlinkingOff ; LCD_KEY_BLINK_OFF goto LCD__ShiftDisplayLeft ; LCD_KEY_LCDSHIFT_LEFT goto LCD__ShiftDisplayRight ; LCD_KEY_LCDSHIFT_RIGHT goto LCD__MoveCursorLeft ; LCD_KEY_CURSOR_LEFT goto LCD__MoveCursorRight ; LCD_KEY_CURSOR_RIGHT ; ; process a command with data. The function called ; depends on the (checked!) value in W. ; process_command_data: addwf PCL, f goto cmd_LCD__SetDispDataAddr ; LCD_KEY_SET_DISPLAY_ADDR goto cmd_LCD__SetCharGenAddr ; LCD_KEY_SET_CHARGEN_ADDR goto cmd_LCD__WriteData ; LCD_KEY_WRITE_LCD_DATA ; ; cmd_LCD__SetDispDataAddr: movf databyte, w ; get data call LCD__SetDispDataAddr bsf action, ACTION_AWAIT_CMD ; ready for next command return ; cmd_LCD__SetCharGenAddr: movf databyte, w ; get data call LCD__SetCharGenAddr bsf action, ACTION_AWAIT_CMD ; ready for next command return ; cmd_LCD__WriteData: movf databyte, w ; get data call LCD__WriteData ;; bsf action, ACTION_AWAIT_CMD ; ready for next command ; bugfix/mod 13-Dec-2007: keep this command until i2c-funcs signal "disconnected" return ; ; include LCD-code here include "lcd162c.inc" ; include (modified, slow) I2C-bus-code here include "i2c.inc" ifdef DEBUG ; ; write digit (lsb of W) to display write_digit: andlw 0x0f iorlw '0' ; make ASCII of it goto LCD__WriteData ; will return to caller of this ; ; write two digits (content of W) to display write_2digits: movwf tmp swapf tmp, w call write_digit ; write MSB movf tmp, w call write_digit ; write LSB return ; endif ; ; ; Prepare fetching data from key-bits set_datasource_keys: bcf action, ACTION_SEND_LCD_ADR ; no more from address of LCD bcf action, ACTION_SEND_LCD_DAT ; no more from data of LCD return ; ; Prepare fetching data from LCD-address set_datasource_lcdaddr: bsf action, ACTION_SEND_LCD_ADR ; get data from address of LCD bcf action, ACTION_SEND_LCD_DAT ; no more from data of LCD return ; ; Prepare fetching data from LCD-data set_datasource_lcddata: bsf action, ACTION_SEND_LCD_DAT ; get data from data of LCD bcf action, ACTION_SEND_LCD_ADR ; no more from address of LCD return ; ; Poll the next key ; ; Each time, this is called, one of the eight keys (depending ; on the content of "key_index") is polled and the resulting ; bit is shifted into the corresponding "keyN"-variable. If ; all bits of a "keyN"-variable are the same (zero or one), ; the bit corresponding to the key in "newkeybits" is set ; or cleared, otherwise the previous state is unchanged. ; After all eight keys are scanned, the content of "newkeybits" ; is copied to "keybits". ; poll_key: bcf STATUS, RP0 ; bank 0 ; select key bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT0 btfsc key_index, 0 bsf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT0 bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT1 btfsc key_index, 1 bsf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT1 bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT2 btfsc key_index, 2 bsf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT2 ; setup pointer to current key movlw key0 addwf key_index, w movwf FSR ; sample and debounce key bcf STATUS, C ; assume released key btfss PORTA + + KEY_SCAN_PORT, KEY_SCAN_BIT ; key pressed ? bsf STATUS, C ; yes, got zerobit rlf INDF, f ; shift bit into "debouncer" movf INDF, w ; get last 8 states of this key btfsc STATUS, Z ; pressed for 8 cycles ? bsf newkeybits, 0 ; yes, set state "pressed" xorlw 0xff btfsc STATUS, Z ; released for 8 cycles ? bcf newkeybits, 0 ; yes, set state "released" rrf newkeybits, f ; prepare for next keybit bcf newkeybits, 7 ; assume LSB was zero btfsc STATUS, C ; LSB was set before shift ? bsf newkeybits, 7 ; yes, move it to MSB incf key_index, f ; next key to scan btfss key_index, 3 ; all eight keys scanned ? return ; no, try again next time ; all keys sampled, setup new result movf newkeybits, w ; get assembled keybits movwf keybits ; set new keybits clrf key_index ; prepare next round ;; call I2C__Changed ; avoid interference with I2C-stuff return ; ; Fetch a databyte. The datasource depends on some flags ; of "action": If ACTION_SEND_LCD_ADR is set, the databyte ; will be the address fetched from the LCD, if ; ACTION_SEND_LCD_DAT is set, this function will return a ; databyte fetched from LCD-data. If both bit are cleared, ; the current keybits will be returned. ; get_requested_data: btfsc action, ACTION_SEND_LCD_ADR ; address from LCD requested ? goto LCD__GetAddr ; yes, fetch it and return btfsc action, ACTION_SEND_LCD_DAT ; data from LCD requested ? goto LCD__ReadData ; yes, fetch it and return movf keybits, w ; no, get key state bits return ; ; ; start of processing startup: bsf STATUS, RP0 ; select bank 1 (adr 0x80-0x100) movlw OPTION_INIT ; initialize options (prescaler) movwf ( OPTION_REG & 0x7f ) ; set signals for key-selection to "output" bcf (TRISA & 0x7f) + KEY_SELECT_PORT, KEY_SELECT_BIT0 bcf (TRISA & 0x7f) + KEY_SELECT_PORT, KEY_SELECT_BIT1 bcf (TRISA & 0x7f) + KEY_SELECT_PORT, KEY_SELECT_BIT2 ; set signals for key-scan to "input" bsf (TRISA & 0x7f) + KEY_SCAN_PORT, KEY_SCAN_BIT bcf STATUS, RP0 ; back to bank 0 ; select key 0 bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT0 bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT1 bcf PORTA + KEY_SELECT_PORT, KEY_SELECT_BIT2 clrf action ; initialize action-bits clrf command ; current command clrf keybits ; current state of keys movlw 0x08 movwf key_index ; index for scanning keys movlw key0 movwf FSR ; setup pointer to "key0" key_init: clrf INDF ; clear state of key incf FSR, f ; next key decfsz key_index, f ; all done ? goto key_init ; no, once again movlw LCD_KEY_I2C_ADDRESS ; address of (this) I2C-slave call I2C__Init call I2C__EnableTxAck ; always able to send data movlw LCD_FLAG_2LINE ; want to use 2-line display call LCD__Init ; initialize LCD with given options call LCD__DisplayOn movlw 0x00 ; position to begin of line 1 call LCD__SetDispDataAddr clrf TMR0 ; first timer-interrupt in abt. 1ms bsf INTCON, T0IE ; enable timer-interrupt bsf INTCON, RBIE ; enable interrupt on change at port B bsf INTCON, GIE ; enable any interrupts ; ; ; main loop mloop: ; part 1, look for data request from I2C-bus btfss I2C_state, I2C_SBIT_DATA_REQ ; I2C-master requests data ? goto part2 ; no, look for other things to do call get_requested_data ; yes, get the databyte to W call I2C__SetTxData ; ...and deliver to the I2C-stuff goto part3 ; look for other things to do ; part2: ; part 2, look for command from bus btfss I2C_state, I2C_SBIT_SELECTED ; addressed from I2C-bus ? bsf action, ACTION_AWAIT_CMD ; no, first byte received should be a command btfss I2C_state, I2C_SBIT_RXDATA ; something received from I2C ? goto part3 ; no, look for other things to do call I2C__GetRxData ; yes, get the byte btfss action, ACTION_AWAIT_CMD ; Awaiting command from I2C-bus ? goto data_received ; no, byte seems to be data ; got command, try to process it bcf action, ACTION_AWAIT_CMD ; now have a command movwf command ; save command, maybe needed later btfss command, LCD_KEY_BIT_NEED_DATA ; command needs data ? goto decode_command ; no, decode and process it bcf command, LCD_KEY_BIT_NEED_DATA ; yes, remove bit for later decoding goto part3 ; go on with other things ; decode_command: ; decode and process command without data sublw LCD_KEY_MAXCMD_NODATA ; maximum command code btfss STATUS, C ; command-code in range ? goto part3 ; no, ignore movf command, w ; yes, get code again call process_command ; ... and process it bsf action, ACTION_AWAIT_CMD ; ready for next command goto mloop ; done ; data_received: ; received data, action depends on command movwf databyte ; save data got movf command, w ; get command sublw LCD_KEY_MAXCMD_DATA ; maximum command code for this kind of command btfss STATUS, C ; command-code in range ? goto part3 ; no, ignore movf command, w ; yes, get code again call process_command_data ; ... and process it goto mloop ; done ; part3: ; part 3, poll keys, if it is time for this btfss action, ACTION_POLL_KEYS ; timer overflow ? goto part4 ; no, go on ; ; poll the next key bcf action, ACTION_POLL_KEYS ; signal seen call poll_key ; part4: goto mloop ; -- End of programm code ; -- EEPROM data org 0x2100 ; "official" address for dataspace in hexfile Data_start: ; -- End of data end