From 423a3abc1c96fad4e9eb9a020708074e9cc73ee2 Mon Sep 17 00:00:00 2001 From: William Toohey Date: Mon, 28 Dec 2015 00:21:05 +1000 Subject: [PATCH] I2C - 400KHz mode, wait for slave on clock, fixes Hori and other 3rd party controllers --- Firmware/Keyboard/Keyboard.c | 2 + Firmware/Keyboard/i2cmaster.S | 166 +++++++++++++++++++++++----------- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/Firmware/Keyboard/Keyboard.c b/Firmware/Keyboard/Keyboard.c index c125012..53787c6 100644 --- a/Firmware/Keyboard/Keyboard.c +++ b/Firmware/Keyboard/Keyboard.c @@ -145,11 +145,13 @@ void Nunchuck_Init(void) { i2c_write(0xF0); i2c_write(0x55); i2c_stop(); + _delay_ms(25); i2c_start(NUNCHUCK_ADDR | I2C_WRITE); i2c_write(0xFB); i2c_write(0x00); i2c_stop(); + _delay_ms(25); Nunchuck_back(); } else { Nunchuck_gone(); diff --git a/Firmware/Keyboard/i2cmaster.S b/Firmware/Keyboard/i2cmaster.S index 8277757..9c67c58 100644 --- a/Firmware/Keyboard/i2cmaster.S +++ b/Firmware/Keyboard/i2cmaster.S @@ -13,8 +13,11 @@ ; ; Based on the Atmel Application Note AVR300, corrected and adapted ; to GNU assembler and AVR-GCC C call interface -; Replaced the incorrect quarter period delays found in AVR300 with -; half period delays. +; Replaced the incorrect quarter period delays found in AVR300 with +; half period delays. + +; Tweaked by monty for true 400KHz operation at 8MHz system clock on an +; atmega16u2. Duty cycle adjusted for correct 400KHz spec adherence. ; ; USAGE ; These routines can be called from C, refere to file i2cmaster.h. @@ -42,15 +45,15 @@ ; ; V1 had different I2C pinouts #ifdef V1_BUILD - #define SDA 1 // SDA Port D, Pin 4 - #define SCL 0 // SCL Port D, Pin 5 - #define SDA_PORT PORTD // SDA Port D - #define SCL_PORT PORTD // SCL Port D + #define SDA 1 ; SDA Port D, Pin 4 + #define SCL 0 ; SCL Port D, Pin 5 + #define SDA_PORT PORTD + #define SCL_PORT PORTD #else - #define SDA 4 // SDA Port D, Pin 4 - #define SCL 3 // SCL Port D, Pin 3 - #define SDA_PORT PORTD // SDA Port D - #define SCL_PORT PORTD // SCL Port D + #define SDA 4 ; SDA Port D, Pin 4 + #define SCL 3 ; SCL Port D, Pin 3 + #define SDA_PORT PORTD + #define SCL_PORT PORTD #endif ;****** @@ -78,17 +81,28 @@ ;************************************************************************* .stabs "",100,0,0,i2c_delay_T2 .stabs "i2cmaster.S",100,0,0,i2c_delay_T2 - .func i2c_delay_T2 ; delay 1.25 microsec with 8 Mhz crystal + .func i2c_delay_T2 ; delay 1.25 microsec with 8 Mhz crystal i2c_delay_T2: ; 4 cycles - ;rjmp 1f ; 2 " -;1: nop ; 1 " - ret ; 3 " - .endfunc ; total 10 cyles = 1.25 microsec with 8 Mhz crystal + ret ; 5 " + .endfunc ; total 9 cyles = 1.25 microsec with 8 Mhz crystal, IO op happens on the 10th + +;************************************************************************* +; delay 40 microseconds, to give the slave time to do stuff +;************************************************************************* + .func i2c_delay_40us +i2c_delay_40us: ; 4 cycles + ldi r16, 103 ; 1 +delay: + dec r16 ; 1 + brne delay ; 2 + ; 1 + ret ; 5 " + .endfunc ; total 320 cyles = 40 microsec with 8 Mhz crystal ;************************************************************************* ; Initialization of the I2C bus interface. Need to be called only once -; +; ; extern void i2c_init(void) ;************************************************************************* .global i2c_init @@ -186,8 +200,13 @@ i2c_stop: rcall i2c_delay_T2 ;delay T/2 cbi SCL_DDR,SCL ;release SCL rcall i2c_delay_T2 ;delay T/2 +i2c_stop_wait: + ;wait SCL high (in case wait states are inserted) + sbis SCL_IN,SCL ; 2 + rjmp i2c_stop_wait cbi SDA_DDR,SDA ;release SDA rcall i2c_delay_T2 ;delay T/2 + rcall i2c_delay_40us ; delay 40us to let slave catch its breath ret .endfunc @@ -202,39 +221,59 @@ i2c_stop: .global i2c_write .func i2c_write i2c_write: - sec ;set carry flag - rol r24 ;shift in carry and out bit one - rjmp i2c_write_first + sec ;set carry flag ; + rol r24 ;shift in carry and out bit one ; + rjmp i2c_write_first ; i2c_write_bit: - lsl r24 ;if transmit register empty + lsl r24 ;if transmit register empty ; 1 + breq i2c_get_ack ; 1 + nop ; 1 i2c_write_first: - breq i2c_get_ack - sbi SCL_DDR,SCL ;force SCL low - brcc i2c_write_low - nop - cbi SDA_DDR,SDA ;release SDA - rjmp i2c_write_high -i2c_write_low: - sbi SDA_DDR,SDA ;force SDA low - rjmp i2c_write_high + ; 7 since the last cbi + sbi SCL_DDR,SCL ;force SCL low ; 2 + brcc i2c_write_low ; 1 + nop ; 1 + cbi SDA_DDR,SDA ;release SDA ; 2 + rjmp i2c_write_high ; 2 +i2c_write_low: ; 1 + sbi SDA_DDR,SDA ;force SDA low ; 2 + rjmp i2c_write_high ; 2 i2c_write_high: - rcall i2c_delay_T2 ;delay T/2 - cbi SCL_DDR,SCL ;release SCL - rcall i2c_delay_T2 ;delay T/2 - rjmp i2c_write_bit - -i2c_get_ack: + nop + nop + nop + nop + nop + ; 13 since the last + cbi SCL_DDR,SCL ;release SCL ; 2 +i2c_write_wait: + ;wait SCL high (in case wait states are inserted) + sbis SCL_IN,SCL ; 2 + rjmp i2c_write_wait + + rjmp i2c_write_bit ; 2 + +i2c_get_ack: ; 1 sbi SCL_DDR,SCL ;force SCL low cbi SDA_DDR,SDA ;release SDA - rcall i2c_delay_T2 ;delay T/2 + nop + nop + nop + nop + nop + nop + nop + nop + nop cbi SCL_DDR,SCL ;release SCL i2c_ack_wait: sbis SCL_IN,SCL ;wait SCL high (in case wait states are inserted) rjmp i2c_ack_wait - + clr r24 ;return 0 sbic SDA_IN,SDA ;if SDA high -> return 1 ldi r24,1 + sbi SCL_DDR,SCL ;force SCL low rcall i2c_delay_T2 ;delay T/2 clr r25 ret @@ -265,41 +304,58 @@ i2c_readAck: i2c_read: ldi r23,0x01 ;data = 0x01 i2c_read_bit: - sbi SCL_DDR,SCL ;force SCL low - cbi SDA_DDR,SDA ;release SDA (from previous ACK) - rcall i2c_delay_T2 ;delay T/2 - - cbi SCL_DDR,SCL ;release SCL - rcall i2c_delay_T2 ;delay T/2 - + sbi SCL_DDR,SCL ;force SCL low ; 2 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + nop ; 1 + + cbi SCL_DDR,SCL ;release SCL ; 2 + i2c_read_stretch: - sbis SCL_IN, SCL ;loop until SCL is high (allow slave to stretch SCL) + ;loop until SCL is high (allow slave to stretch SCL) + sbis SCL_IN, SCL ; 2 rjmp i2c_read_stretch - - clc ;clear carry flag - sbic SDA_IN,SDA ;if SDA is high + ; + clc ;clear carry flag ; 1 + sbic SDA_IN,SDA ;if SDA is high ; 2 sec ; set carry flag - - rol r23 ;store bit - brcc i2c_read_bit ;while receive register not full - + ; + rol r23 ;store bit ; 1 + ;while receive register not full + brcc i2c_read_bit ; 2 + i2c_put_ack: - sbi SCL_DDR,SCL ;force SCL low + sbi SCL_DDR,SCL ;force SCL low cpi r24,1 breq i2c_put_ack_low ;if (ack=0) cbi SDA_DDR,SDA ; release SDA rjmp i2c_put_ack_high i2c_put_ack_low: ;else sbi SDA_DDR,SDA ; force SDA low + nop i2c_put_ack_high: - rcall i2c_delay_T2 ;delay T/2 + nop + nop + nop + nop + nop cbi SCL_DDR,SCL ;release SCL i2c_put_ack_wait: sbis SCL_IN,SCL ;wait SCL high rjmp i2c_put_ack_wait - rcall i2c_delay_T2 ;delay T/2 + nop mov r24,r23 clr r25 + sbi SCL_DDR,SCL ;force SCL low + cbi SDA_DDR,SDA ;release SDA ret .endfunc