Close

4-Bit LC Display

A project log for W6502SBC

a 6502 sbc with the option to be used with a backplane as a module.

wilfried-klaasWilfried Klaas 08/28/2022 at 17:120 Comments

Bei dem bisherigen Versuchen habe ich das LCD immer mit 8-Bit angesprochen. D.h. der komplette Port B vom VIA und noch 3 Steuerleitungen vom Port A wurden für das LCD belegt. Nun haben die LCDs aber auch einen 4-Bit Modus. Dieser wird auch im Arduino Umfeld gerne benutzt, da man so Leitungen sparen kann. Der Geschwindigkeitsnachteil durch die Verwendung des 4-Bit Interfaces ist vernachlässigbar. Also habe ich das LCD auch bei meinen SBC auf 4-Bit umgestellt. Hardwaretechnisch war das ganz einfach. Die Datenleitungen D0..3 vom Display werden fest mit Masse verbunden, währen die Leitung D4..7 auf die Port Pins PB0..3 kommen. PB 4 bleibt leer. PB5..7 sind jetzt die Steuerleitungen RS, R/W und E. (In dieser Reihenfolge)

Nun geht's an die Software und das ist gar nicht mal so einfach. Zwar gibt es viele Beispiele auch für den 6502 mit 6522, aber von den Beispielen habe ich keines zum Laufen bekommen. Also musste ich selbst forschen. Zum Umschalten des Displays in den 4-Bit Modus muss ein gewisse Muster eingehalten werden. Und nebenbei machte dann auch das Display bei erneutem Reset Druck plötzlich mucken. Also muss das Display auch per Software einen Reset ausführen. Ließt man dazu das Kapitel im Datenblatt, erhält man eine genaue Reihenfolge wie welche Befehle mit welchen Zeitabständen erfolgen müssen. Dazu braucht man dann auch eine zuverlässige Delay Routine. Gleichzeitig habe ich nun auch ein paar zusätzliche Methoden zur Ausgabe ins ROM integriert.

Nebenbei habe ich auch den ROM Sockel mit einem ZIF Aufsatz versehen. Das macht das Programmieren erheblich einfacher und schont die Beine der ICs und den Sockel. Evtl. muss ich auch mal über eine In-Circut-Programmiermöglichkeit nachdenken. Achja, die zusätzlichen Kabel, die vom Display nach recht weggehen, hängen an meinem Logic Analyser. Der hat mir dismal bei der Analyse und Programmierung sehr gute Dienste geleistet.

Hier die LCD-Routinen aus meinem BIOS.

; constants for LCD
LCD_E  .equ %10000000
LCD_RW .equ %01000000
LCD_RS .equ %00100000

;----- macros -----
.macro msg_out(msg)
  lda #>msg
  ldx #<msg
  jsr do_strout 
.endmacro
;----- bios start code -----
do_reset: ; bios reset routine 
  sei
  ldx #$ff ; set the stack pointer 
  txs 

  jsr do_ioinit  ; initialise port A an timer of VIA
  jsr do_scinit
  
  ;jsr lcd_clear
  msg_out(message_w6502sbc)
...
; ---- Display routines ----
do_scinit:    ; initialise LC-Display on port B
  ; D4..D7 on Port pins PB0..3
  ; RS; R/W and E on Port pins PB5, PB6, PB7
  lda #$ff   ; Set all pins on port B to output
  sta VIA_DDRB
  lda #0    ; all pins low
  sta VIA_ORB

  ; reset the display, wait at least 15ms
  lda #$58
  jsr do_delay

  ; send 3 times the reset...
  lda #(%00000011 | LCD_E) ; 1. RESET
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  lda #$1f
  jsr do_delay

  lda #(%00000011 | LCD_E) ; 2. RESET
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  lda #$01
  jsr do_delay

  lda #(%00000011 | LCD_E) ; 3. RESET
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  lda #$01
  jsr do_delay

  lda #(%00000010 | LCD_E) ; Set 4-bit mode; 
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  lda #$01
  jsr do_delay

  ; after this command we can use the 4-Bit mode and we could use busy flag for former sync
  lda #%00101000 ; 2-line display; 5x8 font
  jsr lcd_instruction

  lda #%00001110 ; Display on; cursor on; blink off
  jsr lcd_instruction

  lda #%00000110 ; Increment and shift cursor; don't shift display
  jsr lcd_instruction

  lda #%00000010 ; Return home
  jsr lcd_instruction

  lda #%00000001 ; Clear display
  jsr lcd_instruction
  rts

lcd_wait: ; wait until the LCD is not busy
  pha
  lda #%11110000 ;set PORTB pins 0 - 3 as input
  sta VIA_DDRB
@lcdbusy:
  lda #LCD_RW
  sta VIA_ORB
  ora #LCD_E
  sta VIA_ORB
  ; loding high nibble with busy flag
  lda VIA_ORB
  sta HNIBBLE
  lda #LCD_RW
  sta VIA_ORB
  ora #LCD_E
  sta VIA_ORB
  ; getting the low nibble, address counter
  lda VIA_ORB
  sta LNIBBLE
  lda #LCD_RW
  sta VIA_ORB
  lda HNIBBLE
  and #%00001000 ; mask the busy flag
  bne @lcdbusy
  lda #$FF ; setting port to output again
  sta VIA_DDRB
  pla
  rts

lcd_instruction: ; sending A as an instruction to LCD
  pha
  pha
  lsr
  lsr
  lsr
  lsr
  ora #LCD_E
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  pla
  and #$0f
  ora #LCD_E
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  pla
  rts

lcd_secondrow: ; move cursor to second row
  pha
  ;jsr lcd_wait
  lda #%10000000 + $40
  jsr lcd_instruction
  pla
  rts
lcd_home:; move cursor to first row
  pha
  ;jsr lcd_wait
  lda #%10000000 + $00
  jsr lcd_instruction
  pla
  rts
lcd_clear: ; clear entire LCD
  pha
  ;jsr lcd_wait
  lda #$00000001 ; Clear display
  jsr lcd_instruction
  pla
  rts

do_strout: ; output string, address of text hi: A, lo: X
  phy
  stx TEMP_VEC
  sta TEMP_VEC+1
  ldy #0
strprint:
  lda (TEMP_VEC),y
  beq strreturn
  jsr do_chrout
  iny
  jmp strprint
strreturn:
  ply
  rts

do_chrout: ; output a single char to LCD, char in A
  jsr lcd_wait
  pha
  ; sending high nibble
  lsr
  lsr
  lsr
  lsr
  ora #(LCD_RS | LCD_E)
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB

  pla 
  and #$0F
  ora #(LCD_RS | LCD_E)
  sta VIA_ORB
  eor #LCD_E
  sta VIA_ORB
  rts

;------------------------------------------------------------------------------
; The count of outloops will be used from A.
; for 1MHz we had a cycle with 1us. if A = 1 we had 20 + 20 clks, which means a minimum of 200us,
; but the reality is somtime different. To get the 200us on my sbc there must be 32 inner loops.
; $01 = 200uS, $02= 360us, $04= 700uS, $08= 1,4ms, $10= 2,7ms, $20= 5,3ms, $40= 10,6ms, $80= 21,3ms, $FF=42,3ms
do_delay:
  phy     ; 3 clk
@outer:   
  ldy  #$20  ; 2 clk, this gives an inner loop of 5 cycles x 20 =  100uS     
@inner:
  dey     ; 2 clk
  bne @inner  ; 2 + 1 clk (for the jump back)
  sbc #$01   ; 2 clk
  bne @outer  ; 2 + 1 clk exit when COUNTER is less than 0
  ply     ; 4 clk
  rts     ; 6 clk 

Discussions