need TWI help

Go To Last Post
17 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am trying to get the TWI to work in master receive mode. The Start error code returned is OK (0x08).

When the SLA+R address is sent the error code returned is 0x00. The data sheet the uP I'm using (ATmega88, doc8161.pdf, page 239) indicates this code indicates a

Quote:
bus error due to an illegal START or STOP condition.

The remedy given also adds
Quote:
Only the internal hardware is affected, no STOP condition is sent on the bus. In all cases, the bus is released and TWSO is cleared.

	ldi TEMP, (1<<TWINT|1<<TWSTO)	;

	sts TWCR, TEMP			;

My question is after running the above code, to I need the resend the START or do I just try the SLA+R step again?

John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You just need to download a library, and call

result = i2c_start(SLAVE_R);

Then you examine the result value. If it is BAD, your Slave is not present or busy. If busy, you can try later. If not present, you go to the shop and buy the chip and insert on your pcb.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for the replay David. I forgot to mention that I'm using assembly code and AVR Studio.

John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to send start again, after you have re-initialized TWI.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

John3 wrote:
Thanks for the replay David. I forgot to mention that I'm using assembly code and AVR Studio.

John


Well download an ASM library then.
Regardless of the language, you write subroutines that take parameters and return GOOD or BAD status.

(Unless you are object-orientated when you just ignore errors)

The Fleury code comes with a bit-bang ASM source code.
You can always translate C code into ASM.

Most microController C is nothing more than ASM with a few macros. Most statements are setting / clearing a bit or assigning values. Compare the C and ASM snippets in your AVR data sheet.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
When the SLA+R address is sent the error code returned is 0x00.
Are you following the Asm example in the datasheet? Is STA still set when you try to send SLA+R?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

atomicdog wrote:
Quote:
When the SLA+R address is sent the error code returned is 0x00.
Are you following the Asm example in the datasheet? Is STA still set when you try to send SLA+R?

I am using the example in the datasheet, except using the address added to 0x01 (to use master receive mode). First I send Start and wait for TWI bit to be set and then check the status code return (read TWSR). It is normal 0x08 (two lowest bits masked out). Then I send SLA+R and wait for TWSI to be set. The status returned is 0x00 (two lowest bits masked out).

If I try to handle the error as recommended in the datasheet, and then begin again with a Start, the SLA+R status returned is still 0x00.

I had this all working once (but without handling errors). I think the hardware is OK.

John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What device are you trying to access?

A normal process is:

i2c_start(SLA_W)
i2c_write(data)           // normally sets some regs
...
i2c_restart(SLA_R)        //
data[0] = i2c_readAck()   // read a sequence
...
data[n-1] = i2c_readNak() // read the last byte
i2c_stop()                // release bus

You will be checking TWSR at each step: 0x08, 0x18 ..., 0x10, 0x40, 0x50 ..., 0x58.

You seem to go from SLA_W to SLA_R without setting any registers. If should be legal but no real devices work like this.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
If I try to handle the error as recommended in the datasheet, and then begin again with a Start, the SLA+R status returned is still 0x00. ...I think the hardware is OK.
The AVR is seeing an extra start or stop bit where it doesn't belong so I would triple check your hardware. What value of pullup resistors are you using? Do you have an O-scope to check that the sda/scl lines are clean?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

David, I have an I2C bus with three PCF8574A remote ports. I am just trying to read the first of the three ports in order to clear any interrupt outputs from the PCF8574A's when the program starts and before any interrupts are enabled on the uC.

This send Start code seems to work fine. The error traps are only to output to the RS232 to the PC screen.

TWI_StartManual:					;  TWI Status Codes 0x08 and 0x10
							;    TWI Start or Restart was sent

	ldi TEMP, (1<<TWINT | 1<<TWSTA | 1<<TWEN )	; 

	sts TWCR, TEMP			; 

TWI_StartManual_Wait1:

	lds TEMP, TWCR			;

	sbrs TEMP, TWINT		;

	rjmp TWI_StartManual_Wait1	;

	lds TEMP, TWSR			;

	andi TEMP, 0xF8			;

	cpi TEMP, 0x08			; start status

	brne TWIStartError		;

;	mov HEX, TEMP			;**debug**

;	rcall SendHex			;**debug**

	ret				;		
[code/]
TWI_SlaveR:					; ;Send I2C slave address for reading

	in TEMP, GPIOR0			; (values 0x0E, 0x06, 0x02 are write addrs)

;	ori TEMP, 0x41			; PCF8574 read

	ori TEMP, 0x71			; PCF8574A read

;	mov HEX, TEMP			; ***Debug***OK here**0x7F***

;	rcall SendHex			;  ***

	sts TWDR, TEMP			; SLA+R addr

	ldi TEMP, (1<<TWINT|1<<TWEN)	; TWEA must be clear

;	mov HEX, TEMP			; **Debug**  S/B 0x84  *OK*

;	rcall SendHex			;

	sts TWCR, TEMP			; 

TWI_SlaveR_Wait1:			;

	lds TEMP, TWCR			;

	sbrs TEMP, TWINT		; 

	rjmp TWI_SlaveR_Wait1	;

	nop						;

	lds TEMP, TWSR			; Possible responses 0x38, 0x40, 0x48, master RX

	nop						;

	mov HEX, TEMP			; ****

	rcall SendHex			; ****Output is 0x00 here***Buss Error, Illegal Start or Stop*

	andi TEMP, 0xF8			; 

	cpi TEMP, 0x40			; status code for SLA+A sent, ACK rcvd**** 

	brne TWI_SlaveR_Error	;

	mov HEX, TEMP			; ***Debug***

	rcall SendHex			;

	ret						;

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The rest of last message, which was sent prematurely:
GPIOR0 holds the write address in PCF8574 format (not PCF8574A format). It is converted to a PCF8574A read format. GPIOR0 is 0x0E to begin with.

John, I have a scope, but the trace is intermittent, so its hard to use. This hardware was working a year ago February, and I was trying to clean up the program. At that time I could successfully read all three PCF8574A ports.

John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

John,

First off, thanks for posting the code.
Second, it is up to you to lay it out attractively. e.g. remove obsolete commented lines, remove double-line spacing.

You have three PCF8574A chips with different hardware addresses. I would check that GPIOR0 really holds valid values.

As a question of style, try replacing repetitive sequences with a subroutine or macro. Your goal is clarity. Execution speed or efficiency is not relevant at all.

TWI_StartManual: ;  TWI Status Codes 0x08 and 0x10
        ;    TWI Start or Restart was sent
        ldi     TEMP,(1<<TWINT  | 1<<TWSTA | 1<<TWEN ) ;
        sts     TWCR,TEMP       ;
TWI_StartManual_Wait1:
        lds     TEMP,TWCR       ;
        sbrs    TEMP,TWINT      ;
        rjmp    TWI_StartManual_Wait1 ;
        lds     TEMP,TWSR       ;
        andi    TEMP,0xF8       ;
        cpi     TEMP,0x08       ; start status
        brne    TWIStartError   ;
        ret

TWI_SlaveR: ; ;Send I2C slave address for reading
        in      TEMP,GPIOR0     ; (values 0x0E, 0x06, 0x02 are write addrs)
        ori     TEMP,0x71       ; PCF8574A read
        sts     TWDR,TEMP       ; SLA+R addr
        ldi     TEMP,(1<<TWINT|1<<TWEN) ; TWEA must be clear
        sts     TWCR,TEMP       ;
TWI_SlaveR_Wait1: ;
        lds     TEMP,TWCR       ;
        sbrs    TEMP,TWINT      ;
        rjmp    TWI_SlaveR_Wait1 ;
        nop     ;
        lds     TEMP,TWSR       ; Possible responses 0x38, 0x40, 0x48, master RX
        nop     ;
        mov     HEX,TEMP        ; ****
        rcall   SendHex         ; ****Output is 0x00 here***Buss Error, Illegal Start or Stop*
        andi    TEMP,0xF8       ;
        cpi     TEMP,0x40       ; status code for SLA+A sent, ACK rcvd****
        brne    TWI_SlaveR_Error ;
        mov     HEX,TEMP        ; ***Debug***
        rcall   SendHex         ;
        ret     ;

I do not know how you are calling the two subroutines.
It is useful to place an explanatory comment block at the head of each function.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You may find this useful. It should be ok for all AVRs. Originally it used IN/OUT instructions for a mega32. You pass data in 'data' register. It destroys 'temp' register. Success/Failure is in CARRY flag.

#ifdef  STANDALONE      // standalone
#define F_CPU           8000000

        .include "m32def.inc"
        .def    data            = r19
        .def    temp            = r21

#endif

#define BUS_SPEED       100000

        .equ    TW_START        = 0x08
        .equ    TW_REP_START    = 0x10
        .equ    TW_MT_SLA_ACK   = 0x18
        .equ    TW_MT_SLA_NACK  = 0x20
        .equ    TW_MT_DATA_ACK  = 0x28
        .equ    TW_MT_DATA_NACK = 0x30

        .equ    TW_MR_SLA_ACK   = 0x40
        .equ    TW_MR_SLA_NACK  = 0x48
        .equ    TW_MR_DATA_ACK  = 0x50
        .equ    TW_MR_DATA_NACK = 0x58

;****************************
;func: void i2c_init(void) // MUST call first
;****************************
i2c_init:
        ldi     temp,(F_CPU/BUS_SPEED - 16)/2 ;100kHz @16MHz = 72, [400kHz=12]
        sts     TWBR,temp       ;100kHz @ 8Mhz = 32, [400kHz= 2]
        ret

;****************************
;func: .cs = I2C_Start(void) // .cs if free
;****************************
I2C_Start:
        ldi     temp,(1<<TWINT)|(1<<TWEN)|(1<<TWSTA)
        sts     TWCR,temp
        rcall   i2c_wait
        lds     temp,TWSR
        andi    temp,0xF8
        cpi     temp,TW_START
        breq    I2C_free
        cpi     temp,TW_REP_START
        brne    i2c_xit
I2C_free:
        sec
        ret
i2c_xit: clc
        ret

;****************************
;func: void I2C_Stop(void)
;****************************
I2C_Stop:
        ldi     temp,(1<<TWINT)|(1<<TWEN)|(1<<TWSTO)
        sts     TWCR,temp
stop_w: lds     temp,TWCR       ; wait for TWSTO to clear
        sbrc    temp,TWSTO
        rjmp    stop_w
        ret

;********************************
;func .cs = i2c_write(data) //.cs if ACK
;********************************
i2c_write:
        out     TWDR,data
        ldi     temp,(1<<TWINT)|(1<<TWEN)
        sts     TWCR,temp
        rcall   i2c_wait
        lds     temp,TWSR
        andi    temp,0xF8
        cpi     temp,TW_MT_SLA_ACK
        breq    i2c_ack
        cpi     temp,TW_MT_DATA_ACK
        breq    i2c_ack
        cpi     temp,TW_MR_SLA_ACK
        breq    i2c_ack
        cpi     temp,TW_MR_DATA_ACK
        breq    i2c_ack
        clc
        ret
i2c_ack:
        sec
        ret

;***************************************
;func: data = i2c_read(void) // always NAK
;***************************************
i2c_read:
        clc     ; comment out this for ACK if .cs
        ldi     temp,(1<<TWINT)|(1<<TWEN)
        brcc    i2c_nak
        ori     temp,(1<<TWEA)
i2c_nak:
        sts     TWCR,temp
        rcall   i2c_wait
        lds     data,TWDR
        ret

;***** internal function wait for TWI operation to complete ***
i2c_wait:
        lds     temp,TWCR       ; use register cos sfr is out of range
        sbrs    temp,TWINT
        rjmp    i2c_wait
        ret

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
You seem to go from SLA_W to SLA_R without setting any registers. If should be legal but no real devices work like this.

This one does! The PCF8574 doesn't have any registers. You write it, the data goes to the output; read it, and the input port comes back. After a (start)(read) sequence you can just keep reading it forever.

I don't know any way that you can get an 0x00 TWI error status through a coding error, unless the error is in reading the status register. It's almost certainly hardware. The TWI engine is capable of some bizarre behavior, and it's almost totally opaque to trouble-shooting. It wouldn't take long to write a manual bit-bang TWI routine and bypass the hardware - this is what I would do, as it's the only way to see what's really going on. Here's some tried and tested code.

.equ    SCL     =0
.equ    SDA     =1

; send start by bit-bang. Drive SDA, then SCL. Leave both driven.
twi_start:
        cbi     PORTD,SCL
        cbi     PORTD,SDA               ; ensure ports low
        ldi     r25,7                   ; about 3us
        sbi     DDRD,SDA                ; drive SDA (goes low)
        dec     r25
        brne    PC-1
        ldi     r25,15                  ; about 6us
        sbi     DDRD,SCL                ; drive SCL (goes low)
        dec     r25
        brne    PC-1
        ret        

; send stop by bit-bang. Float SCL, then float SDA. Leave both
; SCL and SDA floating.
twi_stop:
        ldi     r25,7                   ; about 3us
        cbi     DDRD,SCL                ; float SCL (goes high)
        dec     r25
        brne    PC-1
        ldi     r25,7                   ; about 3us
        cbi     DDRD,SDA                ; float SDA (goes high)
        dec     r25
        brne    PC-1
        ret        

; read byte to r24 by bit-banging, msb first, then send ACK
; if T flag set, else NAK
; Idle state (on entry) is both lines driven low.
twi_rdat:
        ldi     r25,8                   ; 8 bits
        cbi     DDRD,SDA                ; float SDA
        ldi     r24,4                   ; about 2us
        dec     r24
        brne    PC-1
twi_rbit:
        lsl     r24                     ; shift bits left
        push    r25
        cbi     DDRD,SCL                ; float SCL (goes high)
        ldi     r25,15                  ; about 6us
        dec     r25
        brne    PC-1
        sbic    PIND,SDA                ; sample SDA
        sbr     r24,0x01                ; if high, set bit
        ldi     r25,12                  ; about 6us
        sbi     DDRD,SCL                ; drive SCL (goes low)
        dec     r25
        brne    PC-1
        pop     r25
        dec     r25
        brne    twi_rbit
; see if we should ACK
        brtc    PC+2                    ; if T flag set
        sbi     DDRD,SDA                ; assert SDA (ACK)
        ldi     r25,15                  ; about 6us
        cbi     DDRD,SCL                ; float SCL (goes high)
        dec     r25
        brne    PC-1
        sbi     DDRD,SCL                ; drive SCL low
        ldi     r25,10                  ; about 6us
        dec     r25
        brne    PC-1
        ret        

; send byte in r24 to twi by bit-banging, msb first, then
; check ACK. Idle state (on entry) is both lines driven low.
twi_wdat:
        ldi     r25,8                   ; 8 bits
twi_wbit:
        sbrc    r24,7                   ; bit 7 0?
        cbi     DDRD,SDA                ; no, float SDA
        sbrs    r24,7                   ; bit 7 1?
        sbi     DDRD,SDA                ; no, hold SDA low
        push    r25
        ldi     r25,15                  ; about 6us
        cbi     DDRD,SCL                ; float SCL (goes high)
        dec     r25
        brne    PC-1
        ldi     r25,12                  ; about 6us
        sbi     DDRD,SCL                ; drive SCL (goes low)
        dec     r25
        brne    PC-1
        lsl     r24                     ; next bit
        pop     r25
        dec     r25
        brne    twi_wbit
; sample the ACK bit
        cbi     DDRD,SDA                ; float SDA
        ldi     r24,0x30                ; 'NAK' status
        ldi     r25,9                   ; about 6us
        cbi     DDRD,SCL                ; float SCL (goes high)
        sbis    PIND,SDA                ; sample SDA
        ldi     r24,0x28                ; 'ACK' status if low
        dec     r25
        brne    PC-3
        sbi     DDRD,SCL                ; drive SCL low
        ldi     r25,10                  ; about 6us
        dec     r25
        brne    PC-1
        ret        
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can read and write any i2c device ad infinitum.
With am PCF8574A, you would commonly do:

   i2c_start(0x70);   // SLA_W
   i2c_write(data);   // 
   i2c_start(0x71);   // SLA_R
   data = i2c_readNak();
   i2c_start(0x70);   // SLA_W
   ...

Although perfectly legal to miss out the i2c_write() step, there seems little point in doing a SLA_W if you are NOT writing anything.

Likewise, you can do as many writes or reads as you like. The only constraint being that the last read is with a NAK before a restart or stop.

I would guess that John is not doing his calls in the conventional order. The neater the code, the easier it is to follow the program logic.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for your help guys. I will try and make the oscilliscope work enough to look at the SDa and Clk signals.

I'm pretty sure I am correctly reading the status register. This project sat for about 18 months, so it is possible something was zapped by ESD.

John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The problem was caused by hardware. One of the PCF8574As had a half level on the SDa line. The others were OK. It just happened to be the chip I was trying to read first.

The SDa trace to that chip was open under the chip body. A jumper wire fixed it. I also think I know why it was open. Someone bought some nice soldering flux that was for electronic circuits. I used it, but cleaned off what I could get to with alcohol to make it look 'nice'.

Anyhow, the label says is for wave solder machines and it directs the flux be washed off in a water wash process. The parts I used a very small and I used a lot of flux. Some probably wicked under the part.

I've been a radio ham for 50 years and never knowlingly used acid core solder (or flux).