by Marcel Birthelmer
The purpose of this project was to familiarize myself beyond the blinking-LED stage with programming.
I'm using a p16f648a to implement SPI communication with a Maxim IC Max7221 LED driver, which in turn drives 2 blocks of 2-digit 7-segment LED displays (for a total of 4 digits). The PIC also implements a (rather inefficient) decimal counter.
SPI is a rather simple bidirectional communication protocol. It was chosen because I already had the Max7221, and I also intend to use it on future projects. It consists of 4 pins on the client side and 3 pins on the host side which can be shared among all SPI clients, as well as a mechanism to drive the desired client's Chip Select (/CS) pin. The other pins are: SCLK (the clock), MISO (Master-In, Slave-Out), and MOSI (Master-out, Slave-In).
When sending a packet, the order of operations is as follows:
... and so on for all 16 (in our case) bits. Note that other devices may have a different clock polarity, as well as requiring different timing relations on the first bit. Also note that /CS must be set high before the clock goes high again after the last bit, or data loss will occur (from the MAX7221 data sheet).
The counter was simply designed for functionality, without regards to efficiency. On each counting cycle, the unit digit (CTR1) is increased by one. If it equals ten, the digit is reset to 0 and FSR is increased, and the cycle repeats. After 9999, the counter is reset back to 0. Again, this is by no means efficient, as the counter could be contained within 14 bits. However, I simply wanted a counter without working out hex->decimal conversion.
Delays are created simply by counting down from 255^n to 0, where 1<=n<=3 . Further revisions should probably use a variably scaled timer for the job.
{Ed: See the MAX7221 data sheet for connections to the LED 7-Segement displays} I'm using the p16f648a's internal 4MHz counter (occasionally switched to 48kHz mode for debugging). The pins on the pic are as follows: RA:2 is SPICLK, RB:0 is /CS, MOSI is RB:1, MISO is RB:2. Note that MISO isn't implemented since the MAX7221 doesn't really say anything.
LIST P=16F648A, R=HEX __FUSES _INTOSC_OSC_CLKOUT & _WDT_OFF & _CP_OFF & _PWRTE_ON include "p16f648a.inc" OUT1 EQU 0x20 OUT2 EQU 0x21 IN1 EQU 0x22 IN2 EQU 0x23 TMP1 EQU 0x24 TMP2 EQU 0x25 CNT EQU 0x26 ADDR EQU 0x27 CTR1 EQU 0x28 CTR2 EQU 0x29 CTR3 EQU 0x30 CTR4 EQU 0x31 CS EQU 0 MISO EQU 2 MOSI EQU 1 SPICLK EQU 2 org 0 goto init org 0x10 init: clrf PORTB ;clear port b movlw 0x04 bsf STATUS, RP0 bcf TRISA, SPICLK movwf TRISB; all output except RB2 bcf STATUS, RP0 clrf PORTB bsf PORTB, CS movlw 0x09 ; DECODE MODE : DIGITS 1-4 movwf OUT1 movlw 0x0F movwf OUT2 call xfer16 movlw 0x0A ; INADDRSITY : MAX movwf OUT1 movlw 0x0F movwf OUT2 call xfer16 movlw 0x0B ; SCAN LIMIT : DIGITS 1 - 4 movwf OUT1 movlw 0x03 movwf OUT2 call xfer16 movlw 0x0C ; SHUTDOWN MODE : NORMAL OP movwf OUT1 movlw 0x01 movwf OUT2 call xfer16 clrf CTR1 clrf CTR2 clrf CTR3 clrf CTR4 mainloop: movlw CTR1 ; load starting address movwf FSR bump: incf INDF, 1 ; increase current digit movlw 0x0F ; restrict to last four bits andwf INDF, 0 xorlw 0x0A ; check if equal to ten btfss STATUS, Z goto bump_done ; if not, done clrf INDF ; else, set to zero movf FSR, 0 ; check if this was the fourth digit xorlw CTR4 btfsc STATUS, Z ; if so, we're done goto bump_done incf FSR, 1 ; otherwise, go to next digit goto bump bump_done: clrf ADDR movlw CTR1 movwf FSR ; start at CTR1 pump: incf ADDR, 1 movf ADDR, 0 ; send address movwf OUT1 movf INDF, 0 ; send digit value movwf OUT2 call xfer16 btfsc ADDR, 3 ; finish if ADDR==4 goto finish incf FSR, 1 ; else send next goto pump finish: call l0 goto mainloop l0: movlw 0xFF movwf 0x42 l0_loop: decfsz 0x42,1 goto l0_loop return l1: movlw 0xFF movwf 0x41 l1_loop: call l0 decfsz 0x41,1 goto l1_loop return l2: movlw 0xFF movwf 0x40 l2_loop: call l1 decfsz 0x40,1 goto l2_loop return xfer16: bcf PORTB, CS ; assert /CS movf OUT1, 0 call xfer8 ; send first byte movwf IN1 movf OUT2, 0 call xfer8 ; send second byte movwf IN2 bsf PORTB, CS ; de-assert /CS call l0 ; wait return xfer8: movwf TMP1 movlw 0x8 movwf CNT xfer1: rlf TMP1, 1 ;rotate MSB of TMP1 into C bcf PORTB, MOSI ;clear output bit btfsc STATUS, C ;if rotated-out bit was high, bsf PORTB, MOSI ;output high bit nop ;keep time constant bsf PORTA, SPICLK ;bring clock high nop nop nop nop nop nop nop nop ;8x nop to make clock symmetric bcf PORTA, SPICLK ; this would implement MISO, except that we don't need it. ; bcf STATUS, C ;clear C ; btfsc PORTB, MISO ;test MISO bit ; bsf STATUS, C ;set C if MISO bit set ; nop ;keep time constant ; rlf TMP2, 1 ;rotate carry into TMP2 decfsz CNT, 1 goto xfer1 movf TMP2,0 ; 'return' TMP2 return END