From 2e976575940eebe7db1ccdb8810f197a477438e2 Mon Sep 17 00:00:00 2001 From: stefan-b-jakobsson <70063525+stefan-b-jakobsson@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:01:07 +0300 Subject: [PATCH] [KERNAL] New I2C batch commands (#158) * Initial code * More work on initial implementation * Bugfixes, and test implementation for interruptable batch commands * Test: disable joystick scan, disable IRQ while sending I2C start and stop conditions * Disable joystick scan while I2C batch command runs, fix I2C batch write * Fix: Prevent joystick scan from driving I2C pins * Fix bug introduced in joystick scan method * Improve joystick scan performance * Further joy scan speed optimization * Added vectors to kernsup.inc --------- Co-authored-by: mooinglemur --- cfg/kernal-x16.cfgtpl | 1 + cfg/x16.cfginc | 3 +- inc/kernal.inc | 2 + kernal/cbm/init.s | 3 +- kernal/cbm/irq.s | 1 - kernal/cbm/nmi.s | 4 + kernal/drivers/x16/i2c.s | 298 +++++++++++++++++++++++++++++++++- kernal/drivers/x16/joystick.s | 90 +++++++--- kernal/vectors.s | 6 +- kernsup/kernsup.inc | 4 +- 10 files changed, 370 insertions(+), 42 deletions(-) diff --git a/cfg/kernal-x16.cfgtpl b/cfg/kernal-x16.cfgtpl index 26e62c24..98b21438 100644 --- a/cfg/kernal-x16.cfgtpl +++ b/cfg/kernal-x16.cfgtpl @@ -15,6 +15,7 @@ SEGMENTS { KVAR: load = KVAR, type = bss; VARCHANNEL: load = KVAR, type = bss; VARFONTS: load = KVAR, type = bss; + I2CMUTEX: load = I2CMUTEX, type = bss; KVAR2: load = KVAR2, type = bss; GDRVVEC: load = GDRVVEC, type = bss; diff --git a/cfg/x16.cfginc b/cfg/x16.cfginc index 49959ffe..bb4d3f11 100644 --- a/cfg/x16.cfginc +++ b/cfg/x16.cfginc @@ -22,7 +22,8 @@ ZPBASIC: start = $00D4, size = $002B; # BASIC (last byte used: $FE) /* $0200-$02FF: always-available variables and RAM code */ KVAR: start = $0200, size = $00BB; # KERNAL -/* start = $02BB, size = $0006; # reserved for KERNAL growth */ +/* start = $02BB, size = $0005; # reserved for KERNAL growth */ +I2CMUTEX: start = $02C0, size = $0001; # I2C MUTEX FLAG GRAPHVAR: start = $02C1, size = $0003; # GRAPH BANK VARS KERNRAM: start = $02C4, size = $0020; # KERNAL RAM code GDRVVEC: start = $02E4, size = $001C; # framebuffer driver vectors diff --git a/inc/kernal.inc b/inc/kernal.inc index 816ea14a..9b8156b4 100644 --- a/inc/kernal.inc +++ b/inc/kernal.inc @@ -132,6 +132,8 @@ entropy_get = $fecf i2c_read_byte = $fec6 i2c_write_byte = $fec9 +i2c_batch_read = $feb4 +i2c_batch_write = $feb7 kbdbuf_peek = $febd kbdbuf_get_modifiers = $fec0 diff --git a/kernal/cbm/init.s b/kernal/cbm/init.s index 02a6b6bd..0c31dcc9 100644 --- a/kernal/cbm/init.s +++ b/kernal/cbm/init.s @@ -6,7 +6,7 @@ .feature labels_without_colons -.import cint, ramtas, ioinit, enter_basic, restor, vera_wait_ready, call_audio_init, boot_cartridge +.import cint, ramtas, ioinit, enter_basic, restor, vera_wait_ready, call_audio_init, boot_cartridge, i2c_restore .export start @@ -20,6 +20,7 @@ start ldx #$ff jsr ioinit ;go initilize i/o devices jsr ramtas ;go ram test and set jsr restor ;go set up os vectors + jsr i2c_restore ;release I2C pins and clear mutex flag ; jsr cint ;go initilize screen jsr call_audio_init ;initialize audio API and HW. diff --git a/kernal/cbm/irq.s b/kernal/cbm/irq.s index 681113a3..61b2341f 100644 --- a/kernal/cbm/irq.s +++ b/kernal/cbm/irq.s @@ -7,7 +7,6 @@ .feature labels_without_colons .import dfltn, dflto, kbd_scan, clock_update, cinv, cbinv - .export key .segment "IRQ" diff --git a/kernal/cbm/nmi.s b/kernal/cbm/nmi.s index 3ddb1bd5..c1f9e191 100644 --- a/kernal/cbm/nmi.s +++ b/kernal/cbm/nmi.s @@ -11,6 +11,7 @@ monitor = $fecc clrch = $ffcc .import enter_basic, cint, ioinit, restor, nminv .import call_audio_init +.import i2c_restore .import jsrfar .export nnmi, timb, dbgbrk @@ -22,6 +23,7 @@ clrch = $ffcc ; warm reset, ctrl+alt+restore, default value for (nminv) nnmi jsr ioinit ;go initilize i/o devices jsr restor ;go set up os vectors + jsr i2c_restore ;release I2C pins and clear mutex flag ; jsr cint ;go initilize screen jsr call_audio_init ;initialize audio API and HW. @@ -33,6 +35,7 @@ nnmi jsr ioinit ;go initilize i/o devices ; timb - where system goes on a brk instruction ; timb jsr restor ;restore system indirects + jsr i2c_restore ;release I2C pins and clear mutex flag jsr ioinit ;restore i/o for basic jsr cint ;restore screen for basic jsr call_audio_init ;initialize audio API and HW. @@ -47,4 +50,5 @@ monen rti dbgbrk jsr clrch + jsr i2c_restore ;release I2C pins and clear mutex flag bra monen diff --git a/kernal/drivers/x16/i2c.s b/kernal/drivers/x16/i2c.s index 0a73434a..21b09b60 100644 --- a/kernal/drivers/x16/i2c.s +++ b/kernal/drivers/x16/i2c.s @@ -5,17 +5,22 @@ ; License: 2-clause BSD .include "io.inc" +.include "regs.inc" pr = d1pra ddr = d1ddra SDA = (1 << 0) SCL = (1 << 1) +.segment "I2CMUTEX" +i2c_mutex: .res 1 + .segment "I2C" -.export i2c_read_byte, i2c_write_byte +.export i2c_read_byte, i2c_write_byte, i2c_batch_read, i2c_batch_write .export i2c_read_first_byte, i2c_read_next_byte, i2c_read_stop .export i2c_write_first_byte, i2c_write_next_byte, i2c_write_stop +.export i2c_restore, i2c_mutex __I2C_USE_INLINE_FUNCTIONS__=1 @@ -254,6 +259,9 @@ i2c_read_byte: phx phy + lda i2c_mutex + bne @err + jsr i2c_read_first_byte bcs @err pha @@ -292,7 +300,12 @@ i2c_read_byte: ; c 1 on error (NAK) ;--------------------------------------------------------------- i2c_read_first_byte: - jsr i2c_init + lda i2c_mutex + beq @1 + sec + rts + +@1: jsr i2c_init jsr i2c_start ; SDA -> LOW, (wait 5 us), SCL -> LOW, (no wait) txa ; device asl @@ -332,7 +345,12 @@ i2c_read_first_byte: ; Return: a value ;--------------------------------------------------------------- i2c_read_next_byte: - i2c_ack + lda i2c_mutex + beq :+ + lda #$ee + rts + +: i2c_ack i2c_read_next_byte_after_ack: jsr i2c_read @@ -351,7 +369,10 @@ i2c_read_next_byte_after_ack: ; Return: Nothing ;--------------------------------------------------------------- i2c_read_stop: - i2c_nack + lda i2c_mutex + beq :+ + rts +: i2c_nack jmp i2c_stop ;--------------------------------------------------------------- @@ -373,6 +394,12 @@ i2c_write_byte: phx phy + ldy i2c_mutex + bne @error + + ply + phy + jsr i2c_write_first_byte bcs @error jsr i2c_write_stop @@ -406,6 +433,9 @@ i2c_write_byte: ;--------------------------------------------------------------- i2c_write_first_byte: pha ; value + lda i2c_mutex + bne @error + jsr i2c_init jsr i2c_start txa ; device @@ -440,7 +470,10 @@ i2c_write_first_byte: ; Return: Nothing ;--------------------------------------------------------------- i2c_write_next_byte: - jmp i2c_write + ldx i2c_mutex + beq :+ + rts +: jmp i2c_write ;--------------------------------------------------------------- ; i2c_write_stop @@ -453,7 +486,10 @@ i2c_write_next_byte: ; Return: Nothing ;--------------------------------------------------------------- i2c_write_stop: - jmp i2c_stop + lda i2c_mutex + beq :+ + rts +: jmp i2c_stop ;--------------------------------------------------------------- @@ -652,4 +688,252 @@ i2c_brief_delay: pla pha pla - rts \ No newline at end of file + rts + +;--------------------------------------------------------------- +; i2c_batch_read +; +; Function: +; Read a batch of data into a RAM buffer +; +; Pass: x 7-bit device address +; r0 pointer to start of data buffer +; r1 number of bytes to read +; c 0: Advance buffer pointer on each byte +; 1: Buffer pointer not advanced +; +; Return: x device (preserved) +; r0 pointer to start of data buffer (preserved) +; r1 number of bytes to read (preserved) +; c 1 on error (NAK) +;--------------------------------------------------------------- +i2c_batch_read: + ; Preserve input values on stack + lda r0 + pha + lda r0+1 + pha + lda r1 + pha + lda r1+1 + pha + + phx + php + + ; Exit if number of bytes to read is 0 + lda r1 + ora r1+1 + bne @br1 + + plp + plx + clc + jmp i2c_batch_read_restore + +@br1: ; Init I2C transmission + lda #1 + sta i2c_mutex + + sei + jsr i2c_init + jsr i2c_start + + plp + pla ; 7 bit address + pha + php + + asl ; device * 2 + ina ; set read bit + jsr i2c_write + bcc i2c_batch_read_loop + +@err: sei + jsr i2c_stop + plp + plx + sec + bra i2c_batch_read_restore + +i2c_batch_read_loop: + ; Read one byte and store it in the buffer + jsr i2c_read + sta (r0) + + ; Advance buffer pointer, if so requested (function called with C=0) + plp + php + bcs @br3 + + inc r0 + bne @br2 + inc r0+1 + + ; Rewind buffer pointer and increment RAM bank if address >= $C000 +@br2: lda r0+1 + cmp #$c0 + bcc @br3 + lda #$a0 + sta r0+1 + inc ram_bank + + ; Decrement byte request counter +@br3: lda r1 + bne @br4 + dec r1+1 +@br4: dec r1 + + ; Check if requested number of bytes have been read + lda r1 + ora r1+1 + beq i2c_batch_read_exit + + ; Acknowledge, and fetch next byte + i2c_ack + bra i2c_batch_read_loop + +i2c_batch_read_exit: + i2c_nack + sei + jsr i2c_stop + + plp + plx + clc + +i2c_batch_read_restore: + pla + sta r1+1 + pla + sta r1 + pla + sta r0+1 + pla + sta r0 + stz i2c_mutex + rts + +;--------------------------------------------------------------- +; i2c_batch_write +; +; Function: +; Write a batch of data from a RAM buffer +; +; Pass: x 7-bit device address +; r0 pointer to start of data buffer +; r1 number of bytes to write +; +; Return: x device (preserved) +; r0 pointer to start of data buffer (preserved) +; r1 number of bytes to write (preserved) +; r2 number of bytes written +; c 1 on error (NAK) +;--------------------------------------------------------------- +i2c_batch_write: + ; Preserve input on stack + lda r0 + pha + lda r0+1 + pha + lda r1 + pha + lda r1+1 + pha + phx + php + + ; Reset counter + stz r2 + stz r2+1 + + ; Exit if number of bytes to write is 0 + lda r1 + ora r1+1 + bne @1 + plp + clc + bra @restore + +@1: ; Init I2C transmission + lda #1 + sta i2c_mutex + + sei + jsr i2c_init + jsr i2c_start + plp + pla ; 7 bit device address + pha + php + asl ; device * 2 + jsr i2c_write + bcs @err + +@loop: ; Write one byte + lda (r0) + jsr i2c_write + bcs @err + + ; Increment byte count written + inc r2 + bne @2 + inc r2+1 + +@2: ; Advance buffer pointer + inc r0 + bne @3 + inc r0+1 + +@3: ; Rewind buffer pointer if address >= $C000 + lda r0+1 + cmp #$c0 + bcc @4 + lda #$a0 + sta r0+1 + inc ram_bank + +@4: ; Decrement bytes to write counter + lda r1 + bne @5 + dec r1+1 +@5: dec r1 + + ; Check if all requested bytes have been written + lda r1 + ora r1+1 + bne @loop + +@exit: sei + jsr i2c_stop + plp + clc + bra @restore + +@err: sei + jsr i2c_stop + plp + sec + +@restore: + plx + pla + sta r1+1 + pla + sta r1 + pla + sta r0+1 + pla + sta r0 + stz i2c_mutex + rts + +;--------------------------------------------------------------- +; i2c_restore +; +; Function: +; Restore i2c driver +;--------------------------------------------------------------- +i2c_restore: + stz i2c_mutex + jmp i2c_stop \ No newline at end of file diff --git a/kernal/drivers/x16/joystick.s b/kernal/drivers/x16/joystick.s index b0f3c231..14bf3290 100644 --- a/kernal/drivers/x16/joystick.s +++ b/kernal/drivers/x16/joystick.s @@ -39,22 +39,26 @@ joy4: .res 3 ; joystick 4 status ; ; Function: Scan all joysticks ; +; Duration before changes: 1,787 clock cycles => 223 us +; Duration after changes: 1,627 clock cycles => 203 us +; No loop: 1306 => 163,25 us ;--------------------------------------------------------------- joystick_scan: KVARS_START_TRASH_A_NZ + ; Set latch and clock as outputs, and data1..4 as inputs, leave I2C pins (0..1) unchanged lda nes_ddr and #$ff-bit_data1-bit_data2-bit_data3-bit_data4 ora #bit_latch+bit_jclk sta nes_ddr - lda #bit_latch - trb nes_data + + ; Set latch=low and clock=high lda #bit_jclk - tsb nes_data + sta nes_data - ; pulse latch - lda #bit_latch - tsb nes_data + ; pulse latch while clk=high + lda #bit_latch+bit_jclk + sta nes_data pha pla pha @@ -63,58 +67,90 @@ joystick_scan: pla pha pla - trb nes_data + lda #bit_jclk + sta nes_data ; read 3x 8 bits - ldx #0 -l2: ldy #8 -l1: lda #bit_jclk - trb nes_data ; Drive NES clock low (NES controller doesn't change when low) + ldx #bit_jclk + ldy #8 +l1: stz nes_data ; Drive NES clock low (NES controller doesn't change when low) lda nes_data ; Read all controller bits - pha - lda #bit_jclk - tsb nes_data ; Drive NES clock high - pla + stx nes_data ; Drive NES clock high ; process while NES clock is high (bits change) rol ; Move bit 7 into C - rol joy1,x ; Roll C into joy1 + rol joy1 ; Roll C into joy1 rol ; Move bit 6 into C - rol joy2,x ; Roll C into joy2 + rol joy2 ; Roll C into joy2 rol ; Roll bit 5 into C - rol joy3,x ; Roll C into joy3 + rol joy3 ; Roll C into joy3 rol ; Roll bit 4 into C - rol joy4,x ; Roll C into joy4 + rol joy4 ; Roll C into joy4 dey bne l1 - inx - cpx #3 + + ldy #8 +l2: stz nes_data ; Drive NES clock low (NES controller doesn't change when low) + + lda nes_data ; Read all controller bits + stx nes_data ; Drive NES clock high + + ; process while NES clock is high (bits change) + rol ; Move bit 7 into C + rol joy1+1 ; Roll C into joy1 + rol ; Move bit 6 into C + rol joy2+1 ; Roll C into joy2 + rol ; Roll bit 5 into C + rol joy3+1 ; Roll C into joy3 + rol ; Roll bit 4 into C + rol joy4+1 ; Roll C into joy4 + + dey bne l2 + ldy #8 +l3: stz nes_data ; Drive NES clock low (NES controller doesn't change when low) + + lda nes_data ; Read all controller bits + stx nes_data ; Drive NES clock high + + ; process while NES clock is high (bits change) + rol ; Move bit 7 into C + rol joy1+2 ; Roll C into joy1 + rol ; Move bit 6 into C + rol joy2+2 ; Roll C into joy2 + rol ; Roll bit 5 into C + rol joy3+2 ; Roll C into joy3 + rol ; Roll bit 4 into C + rol joy4+2 ; Roll C into joy4 + + dey + bne l3 + ; force present if controller ID (bits 8-11) is not 15 - ; ldy #0 + lda joy1+1 and #%00001111 cmp #15 beq :+ - sty joy1+2 + stz joy1+2 : lda joy2+1 and #%00001111 cmp #15 beq :+ - sty joy2+2 + stz joy2+2 : lda joy3+1 and #%00001111 cmp #15 beq :+ - sty joy3+2 + stz joy3+2 : lda joy4+1 and #%00001111 cmp #15 beq :+ - sty joy4+2 + stz joy4+2 : KVARS_END_TRASH_A_NZ @@ -303,4 +339,4 @@ intab0_len = *-intab0 intab1: .byte KEYCODE_C, KEYCODE_D, KEYCODE_S, KEYCODE_X .byte KEYCODE_LCTRL -intab1_len = *-intab1 +intab1_len = *-intab1 \ No newline at end of file diff --git a/kernal/vectors.s b/kernal/vectors.s index 2260f0d6..2ba427f5 100644 --- a/kernal/vectors.s +++ b/kernal/vectors.s @@ -14,7 +14,7 @@ .import mouse_config; [mouse] .import joystick_scan, joystick_get; [joystick] .import clock_update, clock_get_timer, clock_set_timer, clock_get_date_time, clock_set_date_time; [time] -.import i2c_read_byte, i2c_write_byte +.import i2c_read_byte, i2c_write_byte, i2c_batch_read, i2c_batch_write .import GRAPH_init, GRAPH_clear, GRAPH_set_window, GRAPH_set_colors, GRAPH_draw_line, GRAPH_draw_rect, GRAPH_move_rect, GRAPH_draw_oval, GRAPH_draw_image, GRAPH_set_font, GRAPH_get_char_size, GRAPH_put_char @@ -43,8 +43,8 @@ .byte 0,0,0 ; $FEAB .byte 0,0,0 ; $FEAE jmp mciout ; $FEB1 - .byte 0,0,0 ; $FEB4 - .byte 0,0,0 ; $FEB7 + jmp i2c_batch_read ; $FEB4 + jmp i2c_batch_write ; $FEB7 jmp savehl ; $FEBA jmp kbdbuf_peek ; $FEBD diff --git a/kernsup/kernsup.inc b/kernsup/kernsup.inc index 86470927..adf62709 100644 --- a/kernsup/kernsup.inc +++ b/kernsup/kernsup.inc @@ -2,8 +2,8 @@ .byte 0,0,0 ; $FEAB .byte 0,0,0 ; $FEAE bridge mciout ; $FEB1 -.byte 0,0,0 ; $FEB4 -.byte 0,0,0 ; $FEB7 +bridge i2c_batch_read ; $FEB4 +bridge i2c_batch_write ; $FEB7 bridge bsave ; $FEBA bridge kbdbuf_peek ; $FEBD