forked from janroesner/sixty5o2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bootloader.asm
1160 lines (1018 loc) · 43.8 KB
/
bootloader.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;================================================================================
;
; "Sixty/5o2"
; _________
;
; v1.0
;
; Sixty/5o2 - minimal bootloader and monitor (r/o) w/ serial connection support
;
; Written by Jan Roesner <jan@roesner.it> for Ben Eater's "Project 6502"
;
; Credits:
; - Ben Eater (Project 6502)
; - Steven Wozniak (bin2hex routine)
; - Anke L. (love, patience & support)
;
;================================================================================
PORTB = $6000 ; VIA port B
PORTA = $6001 ; VIA port A
DDRB = $6002 ; Data Direction Register B
DDRA = $6003 ; Data Direction Register A
IER = $600e ; VIA Interrupt Enable Register
E = %10000000
RW = %01000000
RS = %00100000
Z0 = $00 ; General purpose zero page locations
Z1 = $01
Z2 = $02
Z3 = $03
VIDEO_RAM = $3fde ; $3fde - $3ffd - Video RAM for 32 char LCD display
POSITION_MENU = $3fdc ; initialize positions for menu and cursor in RAM
POSITION_CURSOR = $3fdd
WAIT = $3fdb
WAIT_C = $18 ; global sleep multiplicator (adjust for slower clock)
ISR_FIRST_RUN = $3fda ; used to determine first run of the ISR
PROGRAM_LOCATION = $0200 ; memory location for user programs
.org $8000
;================================================================================
;
; main - routine to initialize the bootloader
;
; Initializes the bootloader, LCD, VIA, Video Ram and prints a welcome message
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .Y, .X
; ————————————————————————————————————
;
;================================================================================
main: ; boot routine, first thing loaded
ldx #$ff ; initialize the stackpointer with 0xff
txs
jsr LCD__initialize
jsr LCD__clear_video_ram
lda #<message ; render the boot screen
ldy #>message
jsr LCD__print
ldx #$20 ; delay further progress for a bit longer
lda #$ff
.wait:
jsr LIB__sleep
dex
bne .wait
jsr MENU_main ; start the menu routine
jmp main ; should the menu ever return ...
;================================================================================
;
; MENU_main - renders a scrollable menu w/ dynamic number of entries
;
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
MENU_main:
lda #0 ; since in RAM, positions need initialization
sta POSITION_MENU
sta POSITION_CURSOR
jmp .start
.MAX_SCREEN_POS: ; define some constants in ROM
.byte $05 ; its always number of items - 2, here its 6 windows ($00-$05) in 7 items
.OFFSETS:
.byte $00, $10, $20, $30, $40, $50 ; content offsets for all 6 screen windows
.start: ; and off we go
jsr LCD__clear_video_ram
ldx POSITION_MENU
ldy .OFFSETS,X
; load first offset into Y
ldx #0 ; set X to 0
.loop:
lda menu_items,Y ; load string char for Y
sta VIDEO_RAM,X ; store in video ram at X
iny
inx
cpx #$20 ; repeat 32 times
bne .loop
.render_cursor: ; render cursor position based on current state
lda #">"
ldy POSITION_CURSOR
bne .lower_cursor
sta VIDEO_RAM
jmp .render
.lower_cursor:
sta VIDEO_RAM+$10
.render: ; and update the screen
jsr LCD__render
.wait_for_input: ; handle keyboard input
ldx #4
lda #$ff ; debounce
.wait:
jsr LIB__sleep
dex
bne .wait
lda #0
jsr VIA__read_keyboard_input
beq .wait_for_input ; no
.handle_keyboard_input:
cmp #$01
beq .move_up ; UP key pressed
cmp #$02
beq .move_down ; DOWN key pressed
cmp #$08
beq .select_option ; RIGHT key pressed
lda #0 ; explicitly setting A is a MUST here
jmp .wait_for_input ; and go around
.move_up:
lda POSITION_CURSOR ; load cursor position
beq .dec_menu_offset ; is cursor in up position? yes?
lda #0 ; no?
sta POSITION_CURSOR ; set cursor in up position
jmp .start ; re-render the whole menu
.dec_menu_offset:
lda POSITION_MENU
beq .wait_for_input ; yes, just re-render
.decrease:
dec POSITION_MENU ; decrease menu position by one
jmp .start ; and re-render
.move_down:
lda POSITION_CURSOR ; load cursor position
cmp #1 ; is cursor in lower position?
beq .inc_menu_offset ; yes?
lda #1 ; no?
sta POSITION_CURSOR ; set cursor in lower position
jmp .start ; and re-render the whole menu
.inc_menu_offset:
lda POSITION_MENU ; load current menu positions
cmp .MAX_SCREEN_POS ; are we at the bottom yet?
bne .increase ; no?
jmp .wait_for_input ; yes
.increase:
adc #1 ; increase menu position
sta POSITION_MENU
jmp .start ; and re-render
.select_option:
clc
lda #0 ; clear A
adc POSITION_MENU
adc POSITION_CURSOR ; calculate index of selected option
cmp #0 ; branch trough all options
beq .load_and_run
cmp #1
beq .load
cmp #2
beq .run
cmp #3
beq .monitor
cmp #4
beq .clear_ram
cmp #5
beq .about
cmp #6
beq .credits
jmp .end ; should we have an invalid option, restart
.load_and_run: ; load and directly run
jsr .do_load ; load first
jsr .do_run ; run immediately after
jmp .start ; should a program ever return ...
.load: ; load program and go back into menu
jsr .do_load
jmp .start
.run: ; run a program already loaded
jsr .do_run
jmp .start
.monitor: ; start up the monitor
lda #<PROGRAM_LOCATION ; have it render the start location
ldy #>PROGRAM_LOCATION ; can also be set as params during debugging
jsr MONITOR__main
jmp .start
.clear_ram: ; start the clear ram routine
jsr BOOTLOADER__clear_ram
jmp .start
.about: ; start the about routine
lda #<about
ldy #>about
ldx #3
jsr LCD__print_text
jmp .start
.credits: ; start the credits routine
lda #<credits
ldy #>credits
ldx #3
jsr LCD__print_text
jmp .start
.do_load: ; orchestration of program loading
lda #$ff ; wait a bit
jsr LIB__sleep
jsr BOOTLOADER__program_ram ; call the bootloaders programming routine
rts
.do_run: ; orchestration of running a program
jmp BOOTLOADER__execute
.end
jmp .start ; should we ever reach this point ...
;================================================================================
;
; BOOTLOADER__program_ram - writes serial data to RAM
;
; Used in conjunction w/ the ISR, orchestrates user program reading
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
; none
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
BOOTLOADER__program_ram:
CURRENT_RAM_ADDRESS_L = Z0
CURRENT_RAM_ADDRESS_H = Z1
LOADING_STATE = Z2
lda #%01111111 ; we disable all 6522 interrupts!!!
sta IER
lda #0 ; for a reason I dont get, the ISR is triggered...
sta ISR_FIRST_RUN ; one time before the first byte arrives, so we mitigate here
jsr LCD__clear_video_ram
lda #<message4 ; Rendering a message
ldy #>message4
jsr LCD__print
lda #$00 ; initializing loading state byte
sta LOADING_STATE
lda #>PROGRAM_LOCATION ; initializing RAM address counter
sta CURRENT_RAM_ADDRESS_H
lda #<PROGRAM_LOCATION
sta CURRENT_RAM_ADDRESS_L
cli ; enable interrupt handling
lda #%00000000 ; set all pins on port B to input
ldx #%11100001 ; set top 3 pins and bottom ones to on port A to output, 4 middle ones to input
jsr VIA__configure_ddrs
.wait_for_first_data:
lda LOADING_STATE ; checking loading state
cmp #$00 ; the ISR will set to $01 as soon as a byte is read
beq .wait_for_first_data
.loading_data:
lda #$02 ; assuming we're done loading, we set loading state to $02
sta LOADING_STATE
ldx #$20 ; then we wait for * cycles !!!! Increase w/ instable loading
lda #$ff
.loop:
jsr LIB__sleep
dex
bne .loop
lda LOADING_STATE ; check back loading state, which was eventually updated by the ISR
cmp #$02
bne .loading_data
; when no data came in in last * cycles, we're done loading
.done_loading:
lda #%11111111 ; Reset VIA ports for output, set all pins on port B to output
ldx #%11100000 ; set top 3 pins and bottom ones to on port A to output, 5 middle ones to input
jsr VIA__configure_ddrs
jsr LCD__clear_video_ram
lda #<message6
ldy #>message6
jsr LCD__print
ldx #$20 ; wait a moment before we return to main menu
lda #$ff
.loop_messagedisplay:
jsr LIB__sleep
dex
bne .loop_messagedisplay
rts
;================================================================================
;
; BOOTLOADER__execute - executes a user program in RAM
;
; Program needs to be loaded via serial loader or other mechanism beforehand
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .Y
; ————————————————————————————————————
;
;================================================================================
BOOTLOADER__execute:
sei ; disable interrupt handling
jsr LCD__clear_video_ram ; print a message
lda #<message7
ldy #>message7
jsr LCD__print
jmp PROGRAM_LOCATION ; and jump to program location
;================================================================================
;
; BOOTLOADER__clear_ram - clears RAM from $0200 up to $3fff
;
; Useful during debugging or when using non-volatile RAM chips
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .Y
; ————————————————————————————————————
;
;================================================================================
BOOTLOADER__clear_ram:
jsr LCD__clear_video_ram ; render message
lda #<message8
ldy #>message8
jsr LCD__print
ldy #<PROGRAM_LOCATION ; load start location into zero page
sty Z0
lda #>PROGRAM_LOCATION
sta Z1
lda #$00 ; load 0x00 cleaner byte
.loop:
sta (Z0),Y ; store it in current location
iny ; increase 16 bit address by 0x01
bne .loop
inc Z1
bit Z1 ; V is set on bit 6 (= $40)
bvs .loop
rts ; yes, return from subroutine
;================================================================================
;
; MONITOR__main - RAM/ROM Hexmonitor (r/o)
;
; Currently read only, traverses RAM and ROM locations, shows hex data contents
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
MONITOR__main:
sta Z0 ; store LSB
sty Z1 ; store MSB
.render_current_ram_location:
jsr LCD__clear_video_ram
lda #$00 ; select upper row of video ram
sta Z3 ; #TODO
jsr .transform_contents ; load and transform ram and address bytes
clc ; add offset to address
lda Z0
adc #$04
sta Z0
bcc .skip
inc Z1
.skip:
lda #$01 ; select lower row of video ram
sta Z3
jsr .transform_contents ; load and transform ram and address bytes there
jsr LCD__render
.wait_for_input: ; wait for key press
ldx #$04 ; debounce #TODO
.wait:
lda #$ff
jsr LIB__sleep
dex
bne .wait
lda #0
jsr VIA__read_keyboard_input
beq .wait_for_input ; a key was pressed? no
.handle_keyboard_input: ; determine action for key pressed
cmp #$01
beq .move_up ; UP key pressed
cmp #$02
beq .move_down ; DOWN key pressed
cmp #$04
beq .exit_monitor ; LEFT key pressed
cmp #$08
beq .fast_forward ; RIGHT key pressed
lda #0 ; explicitly setting A is a MUST here
jmp .wait_for_input
.exit_monitor:
lda #0 ; needed for whatever reason
rts
.move_down:
jmp .render_current_ram_location ; no math needed, the address is up to date already
.move_up:
sec ; decrease the 16bit RAM Pointer
lda Z0
sbc #$08
sta Z0
lda Z1
sbc #$00
sta Z1
jmp .render_current_ram_location ; and re-render
.fast_forward: ; add $0800 to current RAM location
sec
lda Z0
adc #$00
sta Z0
lda Z1
adc #$04
sta Z1
jmp .render_current_ram_location ; and re-render
.transform_contents: ; start reading address and ram contents into stack
ldy #3
.iterate_ram: ; transfer 4 ram bytes to stack
lda (Z0),Y
pha
dey
bne .iterate_ram
lda (Z0),Y
pha
lda Z0 ; transfer the matching address bytes to stack too
pha
lda Z1
pha
ldy #0
.iterate_stack: ; transform stack contents from bin to hex
cpy #6
beq .end
sty Z2 ; preserve Y #TODO
pla
jsr LIB__bin_to_hex
ldy Z2 ; restore Y
pha ; push least sign. nibble (LSN) onto stack
txa
pha ; push most sign. nibble (MSN) too
tya ; calculate nibble positions in video ram
adc MON__position_map,Y ; use the static map for that
tax
pla
jsr .store_nibble ; store MSN to video ram
inx
pla
jsr .store_nibble ; store LSN to video ram
iny
jmp .iterate_stack ; repeat for all 6 bytes on stack
.store_nibble: ; subroutine to store nibbles in two lcd rows
pha
lda Z3
beq .store_upper_line ; should we store in upper line? yes
pla ; no, store in lower line
sta VIDEO_RAM+$10,X
jmp .end_store
.store_upper_line ; upper line storage
pla
sta VIDEO_RAM,X
.end_store:
rts
.end:
lda #":" ; writing the two colons
sta VIDEO_RAM+$4
sta VIDEO_RAM+$14
rts
;================================================================================
;
; VIA__read_keyboard_input - returns 4-key keyboard inputs
;
; Input is read, normalized and returned to the caller
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: .A: (UP: $1, DOWN: $2, LEFT: $4, RIGHT: $8)
;
; Destroys: .A
; ————————————————————————————————————
;
;================================================================================
VIA__read_keyboard_input:
lda PORTA ; load current key status from VIA
ror ; normalize the input to $1, $2, $4 and $8
and #$0f ; ignore first 4 bits
eor #$0f ; deactivate / comment this line, if your keyboard
; is built with buttons tied normal low, when
; pushed turning high (in contrast to Ben's schematics)
rts
;================================================================================
;
; VIA__configure_ddrs - configures data direction registers of the VIA chip
;
; Expects one byte per register with bitwise setup input/output directions
; ————————————————————————————————————
; Preparatory Ops: .A: Byte for DDRB
; .X: Byte for DDRA
;
; Returned Values: none
;
; Destroys: none
; ————————————————————————————————————
;
;================================================================================
VIA__configure_ddrs:
sta DDRB ; configure data direction for port B from A reg.
stx DDRA ; configure data direction for port A from X reg.
rts
;================================================================================
;
; LCD__clear_video_ram - clears the Video Ram segment with 0x00 bytes
;
; Useful before rendering new contents by writing to the video ram
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: none
; ————————————————————————————————————
;
;================================================================================
LCD__clear_video_ram:
pha ; preserve A via stack
tya ; same for Y
pha
ldy #$1f ; set index to 31
lda #$20 ; set character to 'space'
.loop:
sta VIDEO_RAM,Y ; clean video ram
dey ; decrease index
bne .loop ; are we done? no, repeat
sta VIDEO_RAM ; yes, write zero'th location manually
pla ; restore Y
tay
pla ; restore A
rts
;================================================================================
;
; LCD__print - prints a string to the LCD (highlevel)
;
; String must be given as address pointer, subroutines are called
; The given string is automatically broken into the second display line and
; the render routines are called automatically
;
; Important: String MUST NOT be zero terminated
; ————————————————————————————————————
; Preparatory Ops: .A: LSN String Address
; .Y: MSN String Address
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
LCD__print:
ldx #0 ; set offset to 0 as default
jsr LCD__print_with_offset ; call printing subroutine
rts
;================================================================================
;
; LCD__print_with_offset - prints string on LCD screen at given offset
;
; String must be given as address pointer, subroutines are called
; The given string is automatically broken into the second display line and
; the render routines are called automatically
;
; Important: String MUST NOT be zero terminated
; ————————————————————————————————————
; Preparatory Ops: .A: LSN String Address
; .Y: MSN String Address
; .X: Offset Byte
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
LCD__print_with_offset:
STRING_ADDRESS_PTR = Z0
sta STRING_ADDRESS_PTR ; load t_string lsb
sty STRING_ADDRESS_PTR+1 ; load t_string msb
stx Z2 ; X can not directly be added to A, therefore we store it #TODO
ldy #0
.loop:
clc
tya
adc Z2 ; compute offset based on given offset and current cursor position
tax
lda (STRING_ADDRESS_PTR),Y ; load char from given string at position Y
beq .return ; is string terminated via 0x00? yes
sta VIDEO_RAM,X ; no - store char to video ram
iny
jmp .loop ; loop until we find 0x00
.return:
jsr LCD__render ; render video ram contents to LCD screen aka scanline
rts
;================================================================================
;
; LCD__print_text - prints a scrollable / escapeable multiline text (highlevel)
;
; The text location must be given as memory pointer, the number of pages to
; be rendered needs to be given as well
;
; Important: The text MUST be zero terminated
; ————————————————————————————————————
; Preparatory Ops: .A: LSN Text Address
; .Y: MSN Text Address
; .X: Page Number Byte
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
LCD__print_text:
sta Z0 ; store text pointer in zero page
sty Z1
dex ; reduce X by one to get cardinality of pages
stx Z2 ; store given number of pages
.CURRENT_PAGE = Z3
lda #0
sta Z3
.render_page:
jsr LCD__clear_video_ram ; clear video ram
ldy #0 ; reset character index
.render_chars:
lda (Z0),Y ; load character from given text at current character index
cmp #$00
beq .do_render ; text ended? yes then render
sta VIDEO_RAM,Y ; no, store char in video ram at current character index
iny ; increase index
bne .render_chars ; repeat with next char
.do_render:
jsr LCD__render ; render current content to screen
.wait_for_input: ; handle keyboard input
ldx #4
.wait:
lda #$ff ; debounce
jsr LIB__sleep
dex
bne .wait
lda #0
jsr VIA__read_keyboard_input
bne .handle_keyboard_input ; do we have input? yes?
jmp .wait_for_input ; no
.handle_keyboard_input:
cmp #$01
beq .move_up ; UP key pressed
cmp #$02
beq .move_down ; DOWN key pressed
cmp #$04
beq .exit ; LEFT key pressed
lda #0 ; Explicitly setting A is a MUST here
jmp .wait_for_input
.exit:
rts
.move_up:
lda .CURRENT_PAGE ; are we on the first page?
beq .wait_for_input ; yes, just ignore the keypress and wait for next one
dec .CURRENT_PAGE ; no, decrease current page by 1
sec ; decrease reading pointer by 32 bytes
lda Z0
sbc #$20
sta Z0
bcs .skipdec
dec Z1
.skipdec:
jmp .render_page ; and re-render
.move_down:
lda .CURRENT_PAGE ; load current page
cmp Z2 ; are we on last page already
beq .wait_for_input ; yes, just ignore keypress and wait for next one
inc .CURRENT_PAGE ; no, increase current page by 1
clc ; add 32 to the text pointer
lda Z0
adc #$20
sta Z0
bcc .skipinc
inc Z1
.skipinc:
jmp .render_page ; and re-render
;================================================================================
;
; LCD__initialize - initializes the LCD display
;
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: .A, .X
; ————————————————————————————————————
;
;================================================================================
LCD__initialize:
lda #%11111111 ; set all pins on port B to output
ldx #%11100000 ; set top 3 pins and bottom ones to on port A to output, 5 middle ones to input
jsr VIA__configure_ddrs
lda #%00111000 ; set 8-bit mode, 2-line display, 5x8 font
jsr LCD__send_instruction
lda #%00001110 ; display on, cursor on, blink off
jsr LCD__send_instruction
lda #%00000110 ; increment and shift cursor, don't shift display
jmp LCD__send_instruction
;================================================================================
;
; LCD__set_cursor - sets the cursor on hardware level into upper or lower row
;
; Always positions the cursor in the first column of the chosen row
; ————————————————————————————————————
; Preparatory Ops: .A: byte representing upper or lower row
;
; Returned Values: none
;
; Destroys: .A
; ————————————————————————————————————
;
;================================================================================
LCD__set_cursor:
jmp LCD__send_instruction
;================================================================================
;
; LCD__set_cursor_second_line - sets cursor to second row, first column
;
; Low level convenience function
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: none
;
; Destroys: none
; ————————————————————————————————————
;
;================================================================================
LCD__set_cursor_second_line:
pha ; preserve A
lda #%11000000 ; set cursor to line 2 hardly
jsr LCD__send_instruction
pla ; restore A
rts
;================================================================================
;
; LCD__render - transfers Video Ram contents onto the LCD display
;
; Automatically breaks text into the second row if necessary but takes the
; additional LCD memory into account
; ————————————————————————————————————
; Preparatory Ops: Content in Video Ram needs to be available
;
; Returned Values: none
;
; Destroys: .A, .X, .Y
; ————————————————————————————————————
;
;================================================================================
LCD__render:
lda #%10000000 ; force cursor to first line
jsr LCD__set_cursor
ldx #0
.write_char: ; start writing chars from video ram
lda VIDEO_RAM,X ; read video ram char at X
cpx #$10 ; are we done with the first line?
beq .next_line ; yes - move on to second line
cpx #$20 ; are we done with 32 chars?
beq .return ; yes, return from routine
jsr LCD__send_data ; no, send data to lcd
inx
jmp .write_char ; repeat with next char
.next_line:
jsr LCD__set_cursor_second_line ; set cursort into line 2
jsr LCD__send_data ; send dataa to lcd
inx
jmp .write_char ; repear with next char
.return:
rts
;================================================================================
;
; LCD__check_busy_flag - returns the LCD's busy status flag
;
; Since the LCD needs clock cycles internally to process instructions, it can
; not handle instructions at all times. Therefore it provides a busy flag,
; which when 0 signals, that the LCD is ready to accept the next instruction
; ————————————————————————————————————
; Preparatory Ops: none
;
; Returned Values: .A: LCD's busy flag (busy: $01, ready: $00)
;
; Destroys: .A
; ————————————————————————————————————
;
;================================================================================
LCD__check_busy_flag:
lda #0 ; clear port A
sta PORTA ; clear RS/RW/E bits
lda #RW ; prepare read mode
sta PORTA
bit PORTB ; read data from LCD
bpl .ready ; bit 7 not set -> ready
lda #1 ; bit 7 set, LCD is still busy, need waiting
rts
.ready:
lda #0
.return:
rts
;================================================================================
;
; LCD__send_instruction - sends a control instruction to the LCD display
;
; In contrast to data, the LCD accepts a number of control instructions as well
; This routine can be used, to send arbitrary instructions following the LCD's
; specification
; ————————————————————————————————————
; Preparatory Ops: .A: control byte (see LCD manual)
;
; Returned Values: none
;
; Destroys: .A
; ————————————————————————————————————
;
;================================================================================
LCD__send_instruction:
pha ; preserve A
.loop ; wait until LCD becomes ready
jsr LCD__check_busy_flag
bne .loop
pla ; restore A
sta PORTB ; write accumulator content into PORTB
lda #E
sta PORTA ; set E bit to send instruction
lda #0
sta PORTA ; clear RS/RW/E bits
rts
;================================================================================
;
; LCD__send_data - sends content data to the LCD controller
;
; In contrast to instructions, there seems to be no constraint, and data can
; be sent at any rate to the display (see LCD__send_instruction)
; ————————————————————————————————————
; Preparatory Ops: .A: Content Byte
;
; Returned Values: none
;
; Destroys: .A
; ————————————————————————————————————
;
;================================================================================
LCD__send_data:
sta PORTB ; write accumulator content into PORTB
lda #(RS | E)
sta PORTA ; set E bit AND register select bit to send instruction
lda #0
sta PORTA ; clear RS/RW/E bits
rts
;================================================================================
;
; LIB__bin_to_hex: CONVERT BINARY BYTE TO HEX ASCII CHARS - THX Woz!
;
; Slighty modified version - original from Steven Wozniak for Apple I
; ————————————————————————————————————
; Preparatory Ops: .A: byte to convert
;
; Returned Values: .A: LSN ASCII char
; .X: MSN ASCII char
; ————————————————————————————————————
;
;================================================================================
LIB__bin_to_hex:
ldy #$ff ; state for output switching #TODO
pha ; save A for LSD
lsr
lsr
lsr