;;================================================= ;; MCP_4067.asm ;; ;; part of the GPL'ed DIY 744 MCP ;; ;; This is the 16x 7-Segment Display controller ;; that consists of a PIC, 16 7-Segment displays, ;; 8 Resistors and a MOS4067 1-of-16 analog (De-)Multiplexer ;; ;; Copyright (c) 2003 by Manuel Bessler ;; ;; The full text of the legal notices is contained in the file called ;; COPYING, included with this distribution. ;; ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License ;; as published by the Free Software Foundation; either version 2 ;; of the License, or (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;; ;;================================================= ;;================================================= ;; This is how I have my seven segment displays (common cathode) connected ;; 7 a ;; ===== ===== ;; | | | | ;; 1 | | 6 f | | b For reference: the standard 'letter' ;; | 0 | | g | segment assignments ;; ===== ===== ;; | | | | ;; 2 | | 5 e | | c ;; | | | | ;; ===== o 4 ===== o dp ;; 3 d ;; ;; numbers next to segments correnspond to pins on PortB ;;================================================= ;; Things added: ;; ;; display updating via timer interrupt... WORKS ;; ;; PIC-TO-PIC communication ;; ;; update to 16 diplays ;; ;; initialization sequence on displays ;;================================================= LIST P=16F628, R=DEC ; use a PIC16F628, use decimal system as standard #include "p16f628.inc" ; include appropriate processor definitions ; set config bits: __config _INTRC_OSC_NOCLKOUT & _LVP_OFF & _WDT_OFF & _PWRTE_ON & _BODEN_ON ERRORLEVEL -302 ; disable 302 assembler warning messages ;; ============== 7 Segment specials for lookup/jump table OFF equ 10 DASH equ 11 MINUS equ 11 PLUS equ 12 SEGa equ 13 SEGb equ 14 SEGc equ 15 SEGd equ 16 SEGe equ 17 SEGf equ 18 SEGg equ 19 SEGdp equ 20 ;; ======================== ; define SRAM data areas/variables/file registers CBLOCK 0x20 ssdigit0,ssdigit1,ssdigit2,ssdigit3,ssdigit4 ssdigit5,ssdigit6,ssdigit7,ssdigit8,ssdigit9,ssdigit10 ssdigit11,ssdigit12,ssdigit13,ssdigit14,ssdigit15 actdispl,indirector delaycounter_inner,delaycounter_outer p2pdata,p2pbitcounter dcp_stage,dcp_display,dcp_digit tmp1,tmp2 ENDC CBLOCK 0x70 ; available in every bank && it always accesses 0x70 :-) saveW,saveSTATUS,savePCLATH,saveFSR ENDC ;;------------------------------- ;; program start point ;;------------------------------- ORG 0x000 ; program start point at 0x000 GOTO init ;;------------------------------- ;; interrupt vector ;;------------------------------- ORG 0x0004 ; interrupt vector interrupt: ; global interrupts automatically disabled on entry! ; first, save w and status so we don't really ; mess up the system! MOVWF saveW ; make a backup of W MOVF STATUS,w ; status -> W CLRF STATUS MOVWF saveSTATUS ; save STATUS MOVF PCLATH,w MOVWF savePCLATH ; save PCLATH ;!!! SUBTLE GOTCHA !!! This one doesn't ;bite you until you have code that ;crosses page boundaries. How insidious! CLRF PCLATH ;explicitly select Page 0 MOVF FSR,w MOVWF saveFSR ; make backup of FSR, just in case BTFSC INTCON,T0IF GOTO timer0int ; so this was a timer0 interrupt ; other interrupt checks go here GOTO end_interrupt ; some other not-catched int: jump to end of interrupt timer0int: CALL update_display ; do your thing BCF INTCON,T0IF ; clear the Timer0 Interrupt Flag manually (has to be done in software) GOTO end_interrupt end_interrupt: MOVF saveFSR,w MOVWF FSR ;restore fsr MOVF savePCLATH,w MOVWF PCLATH ;restore pclath. (Page=original) MOVF saveSTATUS,w MOVWF STATUS ;restore status! (bank=original) SWAPF saveW,f ;restore w from *original* bank! SWAPF saveW,w ;swapf does not affect any flags! RETFIE ;;------------------------------- ;; generic initialization ;;------------------------------- init: CLRF PORTA ; set portA pins low CLRF PORTB ; set portB pins low MOVLW 0x07 MOVWF CMCON ; turn off comparators, all pins digital BCF STATUS,RP1 BSF STATUS,RP0 ; sel bank 1 CLRF TRISA ; set portA direction registers to all input CLRF TRISB ; set portB direction registers to all input BCF STATUS,RP0 ; sel bank 0 ;;------------------------------- ;; application specific initialization ;;------------------------------- BSF STATUS,RP0 ; sel bank 1 BSF TRISA,7 ; actually... use RA7 as input BSF TRISA,6 ; and also: use RA6 as input BCF STATUS,RP0 ; sel bank 0 CALL delay250ms CALL delay250ms CALL delay250ms CALL delay250ms ; init course display digits MOVLW DASH MOVWF ssdigit0 MOVWF ssdigit1 MOVWF ssdigit2 MOVWF ssdigit3 MOVWF ssdigit4 MOVWF ssdigit5 MOVWF ssdigit6 MOVWF ssdigit7 MOVWF ssdigit8 MOVWF ssdigit9 MOVWF ssdigit10 MOVWF ssdigit11 MOVWF ssdigit12 MOVWF ssdigit13 MOVWF ssdigit14 MOVWF ssdigit15 MOVLW 0x20 MOVWF indirector ; indirector is backup for FSR, set up indirect addressing to ssdigit?? MOVLW 0x00 MOVWF actdispl ; initialize display 0 MOVLW 0x00 MOVWF dcp_stage ; initialize display_control_protocol stage to 0 ;interrupt stuff CLRF INTCON ; all interrupt stuff off CLRF TMR0 ; reset Timer0 BSF STATUS,RP0 ; sel bank 1 BCF OPTION_REG,T0CS ; use Timer Mode on Timer0 (==> BANK 1) BCF STATUS,RP0 ; sel bank 0 BSF INTCON,T0IE ; enable Timer0 Interrupt BSF INTCON,GIE ; allow interrupts ;;------------------------------- ;; display_test routines ;;------------------------------- ;; this displays some patterns on the 7-segment ;; displays to test them and for the 'coolness' factor :-) ;; * first, all displays (simultaneously) will sequence segments (one-by-one) a-g&dp ;; * then it will do the same just for every display independently ;; * and finally: turn on all segments on all displays at the same time ;; and then return to all zeros ;;------------------------------- display_test: MOVLW SEGa CALL copy_digits CALL delay250ms MOVLW SEGb CALL copy_digits CALL delay250ms MOVLW SEGc CALL copy_digits CALL delay250ms MOVLW SEGd CALL copy_digits CALL delay250ms MOVLW SEGe CALL copy_digits CALL delay250ms MOVLW SEGf CALL copy_digits CALL delay250ms MOVLW SEGg CALL copy_digits CALL delay250ms MOVLW SEGdp CALL copy_digits CALL delay250ms MOVLW OFF CALL copy_digits CALL delay250ms CALL test_one_by_one MOVLW 8 & SEGdp CALL copy_digits CALL delay250ms CALL delay250ms CALL delay250ms CALL delay250ms ; init display digits ;;;;;;;; sample startup values on display MOVLW 2 MOVWF ssdigit0 MOVLW 5 MOVWF ssdigit1 MOVLW 0 MOVWF ssdigit2 MOVLW 0x00 MOVWF ssdigit3 MOVWF ssdigit4 MOVWF ssdigit5 MOVLW PLUS MOVWF ssdigit6 MOVLW 2 MOVWF ssdigit7 MOVLW 0 MOVWF ssdigit8 MOVWF ssdigit9 MOVWF ssdigit10 MOVLW 0 MOVWF ssdigit11 MOVLW 5 MOVWF ssdigit12 MOVLW 0 MOVWF ssdigit13 MOVWF ssdigit14 MOVWF ssdigit15 ;;------------------------------- ;; main loop ;;------------------------------- Main: GOTO p2p_recv GOTO Main ;================================================ ;;------------------------------- ;; display test routine 1 ;;------------------------------- ;; copy W to ssdigit* ;;------------------------------- copy_digits: ; uses content of W as digitcode to be displayed MOVWF tmp1 ; digit to display -> tmp1 MOVLW 16 ; number of displays to test MOVWF tmp2 ; to tmp2, will be used as counter MOVLW 0x20 MOVWF FSR cpy_next: MOVF tmp1,w ; load segment code MOVWF INDF ; put into ssdigit[0..15] INCF FSR,f ; inc pointer to next DECFSZ tmp2,f ; all displays done ? GOTO cpy_next RETURN ;;------------------------------- ;; display test routine 2 ;;------------------------------- ;; flash each segment on each display (one-by-one) for a short time ;;------------------------------- test_one_by_one: MOVLW 16 ; number of displays to test MOVWF tmp2 ; to tmp2, will be used as counter MOVLW 0x20 ; offset of ssdigit0 variable in RAM MOVWF FSR ; address into FSR next_1by1: MOVLW SEGa ; flash segment a MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGb ; flash segment b MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGc ; flash segment c MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGd ; flash segment d MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGe ; flash segment e MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGf ; flash segment f MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGg ; flash segment g MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms MOVLW SEGdp ; flash segment dp MOVWF INDF ; put into ssdigit[0..15] CALL delay50ms INCF FSR,f ; inc pointer to next DECFSZ tmp2,f ; all displays done ? GOTO next_1by1 RETURN ;;------------------------------- ;; display update routine ;; this gets called by the ISR ;;------------------------------- update_display: MOVLW 16 ; number of displays --> currently 16 displays connected SUBWF actdispl,w BTFSS STATUS,C ; Carry clear if result is negative GOTO no_display_scan_restart display_scan_restart: MOVLW 0x20 MOVWF indirector MOVLW 0x00 MOVWF actdispl no_display_scan_restart: MOVF indirector,w MOVWF FSR MOVF INDF,w ; indirect addressing load 7seg code -> W CALL binto7seg MOVWF PORTB ; new code to 7segment display MOVF actdispl,w ; actdispl -> W MOVWF PORTA ; address for 4067 out to portA INCF actdispl,f ; next active display INCF indirector,f ; next memory location for 7seg display code RETURN ;;------------------------------- ;; up/down routines ;;------------------------------- ;; up/down increment/decrement displayed number ;;------------------------------- up_hundreds: ; ... so W is actually 6 ... CLRF ssdigit1 ; then zero ssdigit2 and carry INCF ssdigit0,W ; increment next display MOVWF ssdigit0 ; we also need it saved XORLW 4 ; xor the hundreds digit w/ 4 (a circle has 3_6_0 degrees, so wrap after 359) BTFSC STATUS,Z ; finished if not Zero Flag set, return CLRF ssdigit0 ; else (==4) clear digit to 0 (all three displays should show "000") RETURN ; final return up_tens: ; ... so W is actually 10 ... CLRF ssdigit2 ; then zero ssdigit2 and carry INCF ssdigit1,W ; increment next display MOVWF ssdigit1 ; we also need it saved XORLW 6 ; when digit0==3: xor with tens digit w/ 6 (a circle has 3_6_0 degrees, so wrap after 359) ;;; XORLW 10 ; else: xor with tens digit w/ 6 (a circle has 3_6_0 degrees, so wrap after 359) BTFSC STATUS,Z ; finished if not Zero Flag set, return GOTO up_hundreds ; else (==6) continue at up_hundreds RETURN up: INCF ssdigit2,W ; increment least signifcant display MOVWF ssdigit2 ; we also need it saved XORLW 10 ; xor with ten, to see if > 9 BTFSC STATUS,Z ; finished if not Zero Flag set, return GOTO up_tens ; else (==10) continue at up_tens RETURN ; down stuff down: MOVLW 2 MOVWF ssdigit2 RETURN ;;------------------------------- ;; binary to 7-segment conversion routine ;;------------------------------- ;; implemented as a simple jump table ;; (BEWARE: this does not use std 7seg pin assignments) ;;------------------------------- binto7seg: ADDWF PCL,F RETLW 0xEE ; zero RETLW 0x60 ; one RETLW 0xCD ; two RETLW 0xE9 ; three RETLW 0x63 ; four RETLW 0xAB ; five RETLW 0x2F ; six RETLW 0xE0 ; seven RETLW 0xEF ; eight RETLW 0xE3 ; nine RETLW 0x00 ; OFF RETLW 0x01 ; dash / minus RETLW 0x07 ; 'plus' (with one part missing) RETLW b'10000000' ; only segment a RETLW b'01000000' ; only segment b RETLW b'00100000' ; only segment c RETLW b'00001000' ; only segment d RETLW b'00000100' ; only segment e RETLW b'00000010' ; only segment f RETLW b'00000001' ; only segment g RETLW b'00010000' ; only dot/ segment dp ;;------------------------------- ;; PIC-TO-PIC comm receiver (bit-banging type) routines ;;------------------------------- ;; Pins used: ;; RA6: Clock ;; RA7: Data ;; ;; protocol: >=3ms pause between bytes ;; data bit has to be stable before rising edge of clock ;; gets saved into p2pdata ;;------------------------------- p2p_recv: wait_pause: ; first look for 3ms low on clock line BTFSC PORTA,6 GOTO wait_pause ; clock line was high: start over waiting for pause CALL delay1ms BTFSC PORTA,6 GOTO wait_pause ; after 1ms: clock line was high: start over waiting for pause CALL delay1ms BTFSC PORTA,6 GOTO wait_pause ; after 2ms: clock line was high: start over waiting for pause CLRF p2pdata ; reset p2pdata for next byte MOVLW 0x08 ; ???? 8 or 7 ???? protocol wants 8 bits per byte MOVWF p2pbitcounter ; this register keeps track of the number of received bits wait_start_clock: BTFSS PORTA,6 GOTO wait_start_clock BCF STATUS,C BTFSC PORTA,7 ; clock is high, save first bit (MSB) on Data Line in W BSF STATUS,C ; prepare Carry for rotate right RLF p2pdata,f DECF p2pbitcounter,f BTFSS STATUS,Z ; p2pbitcounter == 0 ? then GOTO next_bit byte_done: ; ; do something with received byte ; CALL display_control_protocol GOTO p2p_recv ; wait for next byte next_bit: CALL delay1ms GOTO wait_start_clock GOTO p2p_recv ;; endless receive loop !!!!!! ;;------------------------------- ;; display control protocol routines ;;------------------------------- ;; BNF for the display control protocol: ;; MSG := 'D' displayno digit '*' ;; displayno := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' ;; digit := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-' | ' ' | '.' ;;------------------------------- display_control_protocol: gie0: BCF INTCON,GIE ; disable interrupts BTFSC INTCON,GIE ; make sure it IS cleared, recommended by Microchip GOTO gie0 MOVLW 0x00 XORWF dcp_stage,w ; what stage of protocol are we in ? BTFSC STATUS,Z GOTO stage0 MOVLW 0x01 XORWF dcp_stage,w BTFSC STATUS,Z GOTO stage1 MOVLW 0x02 XORWF dcp_stage,w BTFSC STATUS,Z GOTO stage2 MOVLW 0x03 XORWF dcp_stage,w BTFSC STATUS,Z GOTO stage3 GOTO dcp_fallback ; fallback, reset to stage0 stage0: ; check for 'D' MOVLW 0x44 ; ASCII for 'D' is 0x44 XORWF p2pdata,w ; compare BTFSS STATUS,Z GOTO dcp_fallback ; no 'D' found: reset to stage0 MOVLW 0x01 ; found 'D': stage1 is next MOVWF dcp_stage GOTO end_dcp stage1: ; find out what display is wanted MOVLW 0x30 ; to convert ascii to binary: subtract 0x30 SUBWF p2pdata,w ; do the subtraction, leave result in W MOVWF dcp_display ; save into displayno MOVLW 0x02 MOVWF dcp_stage GOTO end_dcp stage2; MOVLW 0x30 ; to convert ascii to binary: subtract 0x30 SUBWF p2pdata,w ; do the subtraction, leave result in W MOVWF dcp_digit ; save digit for display on successful protocol completion MOVLW 0x03 MOVWF dcp_stage GOTO end_dcp stage3: ; look for terminating char '*' (ASCII 0x2A) MOVLW 0x2A XORWF p2pdata,w BTFSS STATUS,Z ; compare p2pdata with '*' GOTO dcp_fallback ; no '*' found: reset to stage0 MOVF dcp_display,w ; load display no for DCP MSG packet ADDLW 0x20 ; for indirect addressing, add offset of ssdigit0 MOVWF FSR ; put resulting address into file select register MOVF dcp_digit,w ; load digit from DCP MSG packet MOVWF INDF ; store it to address pointed to by FSR ; no GOTO, since dcp_stage needs to be reset anyways dcp_fallback: CLRF dcp_stage ; reset to stage0 end_dcp: BSF INTCON,GIE ; enable interrupts RETURN ;;------------------------------- ;; delay routines ;;------------------------------- delay1ms: MOVLW 1 MOVWF delaycounter_outer ; takes value in W for delay lenght in msec delay1_outer: MOVLW 200 ; 200*5usec = 1msec MOVWF delaycounter_inner delay1_inner: ; inner loop takes 5usec @4MHz NOP NOP DECFSZ delaycounter_inner,F GOTO delay1_inner DECFSZ delaycounter_outer,F GOTO delay1_outer RETURN ;;------------------------------- delay50ms: MOVLW 50 ; 50 * 1msec = 50msec MOVWF delaycounter_outer delay50_outer: MOVLW 200 ; 200*5usec = 1msec MOVWF delaycounter_inner delay50_inner: ; inner loop takes 5usec @4MHz NOP NOP DECFSZ delaycounter_inner,F GOTO delay50_inner DECFSZ delaycounter_outer,F GOTO delay50_outer RETURN ;;------------------------------- delay250ms: MOVLW 250 ; 250 * 1msec = 250msec MOVWF delaycounter_outer delay250_outer: MOVLW 200 ; 200*5usec = 1msec MOVWF delaycounter_inner delay250_inner: ; inner loop takes 5usec @4MHz NOP NOP DECFSZ delaycounter_inner,F GOTO delay250_inner DECFSZ delaycounter_outer,F GOTO delay250_outer RETURN ;;------------------------------- delayXms: MOVWF delaycounter_outer ; takes value in W for delay lenght in msec delay_outer: MOVLW 200 ; 200*5usec = 1msec MOVWF delaycounter_inner delay_inner: ; inner loop takes 5usec @4MHz NOP NOP DECFSZ delaycounter_inner,F GOTO delay_inner DECFSZ delaycounter_outer,F GOTO delay_outer RETURN ;;================================================= ;; End of program ;;================================================= END