I love my 2-wire LCDs and use them all the time, but since I started tinkering with the PIC10F322 chip that has only four I/O pins I decided to have a closer look at Roman Black's "Shift1" system. I could really use that one extra pin!
Shift1 uses a 74HC595 shift register with a couple RC circuits and careful code timing to allow one pin from the microcontroller to control seven I/O pins on the 74HC595.
I breadboarded the circuit and wrote some code. It wasn't terribly difficult. The orange wire is the 1-wire control line. Yellow is to the scope - you can see the red wire in the corner getting ground for the scope.
I'm still fine tuning the timing in the code. Sometimes it gets garbled as shown in the photo below. It's also possible that my interrupt service routines are messing up the Shift1 timing. It could even be that one of the ISRs is stepping on a variable that I used in the LCD code. I'll have to go through the code very carefully and see if I can get it to not be so finicky.
I do know that having the scope connected affects the thing. It definitely runs better without the scope connected. So I think probably breadboard capacitance may also be messing things up some. I think I'll draw up a PC board and get some made.
Anyway, not too bad for an evening's work.
A few days later...
The garbled display is definitely caused by the ISR routines. Shift1 has delays that are 1uS, 15uS, 30uS, 200uS and 300uS. Interrupt any of the short delays with an ISR, especially the 1uS, and bad things happen. I disabled interrupts and the problem completely went away. So that's simple enough to fix. I haven't fixed it yet in the code below, but I will post the fixed code when I do.
Next day...
Here's the code with the LED blinking interrupt fixed. But I can't get the interrupt on change for the button to work anymore. I guess the lcd_nybble subroutine has interrupts disabled so much of the time that catching a button press edge in the tiny space between is almost impossible. I'll probably have to just poll that or something:
processor 10f322
include "p10f322.inc"
__config _CP_OFF & _FOSC_INTOSC & _WDTE_OFF & _MCLRE_OFF & _LVP_OFF
cblock 0x40
i,nyb,rs,letter,count,count2,flag,temp,saves,savew,binh,binl,ascii_1,ascii_2,ascii_3,ascii_4,d1,d2,d3
endc
sig equ 0
org 0x00
goto start
;-------------------------
;interrupt service routine
org 0x04
movwf savew ;save context (w & status)
swapf STATUS,w
movwf saves
btfsc IOCAF,3 ;is RA3 IOC flag set?
goto buttisr ;yes, go to button code
blink: ;no, must be Timer0
movlw 0x04 ;toggle LED pin RA2
xorwf LATA,f
bcf INTCON,TMR0IF ;clear interrupt flag
goto done
buttisr:
bsf flag,0 ;yes, flag = 1
bcf IOCAF,3 ;clear the RA3 IOC flag
done:
swapf saves,w ;restore context
movwf STATUS
swapf savew,f
swapf savew,w
retfie
;-------------
; main program
start:
movlw b'01110000' ;16MHz clock
movwf OSCCON
clrf ANSELA ;all pins digital
clrf TRISA ;led pins outputs
movlw b'10000111' ;setup Timer0,1:256
movwf OPTION_REG
bsf INTCON,TMR0IE ;enable Timer0 interrupt
clrf flag ;zero my IOC flag
bsf IOCAN,3 ;enable IOC on RA3
bsf INTCON,IOCIE ;enable interrupt on change
bsf INTCON,GIE ;enable global interrupts
bsf LATA,sig ;set RA0 (signal) high
clrf count
clrf count2
call lcd_init
call line1
bleh:
movf count,w ;count = table pointer
call table1 ;get byte from table
xorlw 0x00 ;returned zero?
btfsc STATUS,Z
goto next ;yes, then done
movwf letter ;no, output byte to LCD
call lcd_char
incf count,f ;increment table pointer
goto bleh ;and go again
next:
clrf count
clrf count2
blah:
call delay250ms
btfsc flag,0 ;has button been pushed?
call rset ;yes, go handle it
movf count,w ;set up and go convert
movwf binh ;to ascii for display
movf count2,w
movwf binl
call bin2hex16
call line2 ;display the four hex digits
movf ascii_1,w
movwf letter
call lcd_char
movf ascii_2,w
movwf letter
call lcd_char
movf ascii_3,w
movwf letter
call lcd_char
movf ascii_4,w
movwf letter
call lcd_char
incf count2,f ;increment low byte
btfss STATUS,Z ;is zero set, meaning wrapped around?
goto blah ;no, goto blah
incf count,f ;yes, increment high byte
goto blah ;and go again
;---------------------
; table
table1 addwf PCL,f
retlw 'P'
retlw 'I'
retlw 'C'
retlw '1'
retlw '0'
retlw 'F'
retlw '3'
retlw '2'
retlw '2'
retlw ' '
retlw 'L'
retlw 'C'
retlw 'D'
retlw 0x00
;---------------------
; subroutines
rset:
clrf flag
clrf count
clrf count2
return
;---------------------
;16-bit binary passed in binh/binl
;Ascii hex values returned in ascii_1 thru ascii_4
bin2hex16:
swapf binh,w
andlw 0x0f
movwf ascii_1
movlw 0x0a
subwf ascii_1,w
movlw 0x30
btfsc STATUS,C
movlw 0x37
addwf ascii_1,f
;-----------
movf binh,w
andlw 0x0f
movwf ascii_2
movlw 0x0a
subwf ascii_2,w
movlw 0x30
btfsc STATUS,C
movlw 0x37
addwf ascii_2,f
;-----------
swapf binl,w
andlw 0x0f
movwf ascii_3
movlw 0x0a
subwf ascii_3,w
movlw 0x30
btfsc STATUS,C
movlw 0x37
addwf ascii_3,f
;-----------
movf binl,w
andlw 0x0f
movwf ascii_4
movlw 0x0a
subwf ascii_4,w
movlw 0x30
btfsc STATUS,C
movlw 0x37
addwf ascii_4,f
return
;---------------------
lcd_cmd:
movf letter,w ;do high nybble first
andlw 0xf0 ;mask out lower nybble
movwf nyb ;load nyb for lcd_nybble
bsf nyb,3 ;set E high
bcf nyb,2 ;set rs bit low
call lcd_nybble ;send first time
bcf nyb,3 ;set E low
call lcd_nybble ;send second time
swapf letter,w ;now do low nybble
andlw 0xf0 ;mask out lower nybble
movwf nyb ;load nyb for lcd_nybble
bsf nyb,3 ;set E high
bcf nyb,2 ;set rs bit low
call lcd_nybble ;send first time
bcf nyb,3 ;set E low
call lcd_nybble ;send second time
return
;---------------------
lcd_char:
movf letter,w ;do high nybble first
andlw 0xf0 ;mask out lower nybble
movwf nyb ;load nyb for lcd_nybble
bsf nyb,3 ;set E high
bsf nyb,2 ;set rs bit high
call lcd_nybble ;send first time
bcf nyb,3 ;set E low
call lcd_nybble ;send second time
swapf letter,w ;now do low nybble
andlw 0xf0 ;mask out lower nybble
movwf nyb ;load nyb for lcd_nybble
bsf nyb,3 ;set E high
bsf nyb,2 ;set rs bit high
call lcd_nybble ;send first time
bcf nyb,3 ;set E low
call lcd_nybble ;send second time
return
;---------------------
;nyb=4bits of data (first 4 shifted out)
;e = 5th shifted
;rs=command/character (6th shifted)
lcd_nybble:
movlw 7 ;set to send 7 bits
movwf i
bcf INTCON,GIE ;disable interrupts
lcdloop1:
btfsc nyb,7 ;bit 7 high?
goto one ;yes, goto one
bcf LATA,sig ;no, clock out a zero - set signal low
movlw 0x13 ;delay15us
movwf d1
Delay_0a:
decfsz d1, f
goto Delay_0a
bsf LATA,sig ;set sig high again
movlw 0x25 ;delay30us
movwf d1
Delay_0b:
decfsz d1, f
goto Delay_0b
goto increm ;done
one:
bcf LATA,sig ;clock out a one
nop ;delay1us
nop
nop
nop
bsf LATA,sig
movlw 0x12 ;delay15us
movwf d1
Delay_0c
decfsz d1, f
goto Delay_0c
nop
increm:
rlf nyb,f ;move next bit into line
decfsz i,f ;finished 7 bits?
goto lcdloop1 ;no, go again
latch:
bcf LATA,sig ;send 8th latch bit
call delay200us
bsf LATA,sig
call delay300us
rlf nyb,f ;put nyb byte back how it started
rlf nyb,f
bsf INTCON,GIE ;enable interrupts
return
;---------------------
line1:
movlw 0x80
movwf letter
call lcd_cmd
return
;---------------------
line2:
movlw 0xc0
movwf letter
call lcd_cmd
return
;---------------------
lcd_init:
call delay250ms
movlw 0x30
movwf nyb
bsf nyb,3 ;E high
bcf nyb,2 ;RS low
call lcd_nybble ;send first time
bcf nyb,3 ;E low
call lcd_nybble ;send second time
call delay5ms
bsf nyb,3 ;E high
call lcd_nybble ;send first time
bcf nyb,3 ;E low
call lcd_nybble ;send second time
call delay160us
bsf nyb,3 ;E high
call lcd_nybble ;send first time
bcf nyb,3 ;E low
call lcd_nybble ;send second time
call delay160us
movlw 0x20
movwf nyb
bsf nyb,3 ;E high
bcf nyb,2 ;RS low
call lcd_nybble ;first time
bcf nyb,3 ;E low
call lcd_nybble ;second time
call delay160us
movlw 0x28 ;set 4-bit mode and 2 lines
movwf letter
call lcd_cmd
call delay160us
movlw 0x10 ;cursor move & shift left
movwf letter
call lcd_cmd
call delay160us
movlw 0x06 ;entry mode = increment
movwf letter
call lcd_cmd
call delay160us
movlw 0x0c ;display on - cursor blink off
movwf letter
call lcd_cmd
call delay160us
movlw 0x01 ;clear display
movwf letter
call lcd_cmd
call delay250ms ;30ms is enough
return
;---------------------
delay160us:
movlw 0x33
movwf d1
Delay_10:
decfsz d1,f
goto Delay_10
goto $+1
return
;---------------------
delay200us:
movlw 0x9E
movwf d1
movlw 0x01
movwf d2
Delay_0d:
decfsz d1, f
goto $+2
decfsz d2, f
goto Delay_0d
goto $+1
return
;---------------------
delay300us:
movlw 0xEb
movwf d1
movlw 0x01
movwf d2
Delay_0e:
decfsz d1, f
goto $+2
decfsz d2, f
goto Delay_0e
goto $+1
return
;---------------------
delay5ms:
movlw 0xE6
movwf d1
movlw 0x04
movwf d2
Delay_02:
decfsz d1,f
goto $+2
decfsz d2,f
goto Delay_02
goto $+1
nop
return
;---------------------
delay250ms:
movlw 0x4E
movwf d1
movlw 0xC4
movwf d2
Delay_01:
decfsz d1,f
goto $+2
decfsz d2,f
goto Delay_01
goto $+1
nop
return
;---------------------
delay500ms:
movlw 0x03
movwf d1
movlw 0x18
movwf d2
movlw 0x02
movwf d3
Delay_20:
decfsz d1, f
goto $+2
decfsz d2, f
goto $+2
decfsz d3, f
goto Delay_20
goto $+1
return
;---------------------
delay1s:
movlw 0x07
movwf d1
movlw 0x2F
movwf d2
movlw 0x03
movwf d3
Delay_4:
decfsz d1, f
goto $+2
decfsz d2, f
goto $+2
decfsz d3, f
goto Delay_4
goto $+1
goto $+1
goto $+1
return
;---------------------
end