XMEGA32E5 TWIC (I2C) Grief

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

I am trying to use TWIC as a simple master talking to one slave device at 400kHz. My code works - sometimes. Other times it hangs, waiting for either the RIF or WIF interrupt flags (it’s a polled environment).

 

i am writing the code in assembler, and don’t want to use a library. I want to understand the hardware down to the last bit.  Has anyone written a successful TWIC driver without using a library? Could some kind soul please point me to functional source code for such a thing?

 

Thanks in advance.

 

Altazi

This topic has a solution.
Last Edited: Tue. Mar 31, 2020 - 12:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I2C hangs when the master is reading from the slave and the master does not send a NACK to the slave after the last byte that it is expecting to  receive.    The Slave may hold the SDA line asserted (low) in anticipation of more SCL pulses from the Master.  However the Master has received all the bytes that it has been expecting from the Slave.   The Master might then issue a STOP (releasing both SCL and SDA) but the Slave continues to assert (hold low) the SDA line.   In this case the Master never sees a free bus (both SCL and SDA released by all I2C devices) and can not issue a START (SDA asserted, followed by SCL asserted [brought low]) for a new I2C transfer.

 

Generally you should show us your code if you would like us to examine it for logic errors.

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

There is the TWI STATUS register in the 32E5 that your code can read to check whats happening on the bus as well.

 

Jim

 

 

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

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

Many years ago I organized the Atmel library for xmega into a single, simple master-only driver.
It is written in C, but does not use any external libraries, and is easy to rewrite to assembler.
I can provide if you wish.

My comment is my home country, so I have to change it to English.

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

kabasan wrote:

Many years ago I organized the Atmel library for xmega into a single, simple master-only driver.
It is written in C, but does not use any external libraries, and is easy to rewrite to assembler.
I can provide if you wish.

My comment is my home country, so I have to change it to English.

I thank you for this kind offer.  I would like very much to see this code, if you don't mind.  In the meantime, I will post some of mine for discussion.

 

Altazi

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

My I2C write seems to be functioning.  I am attempting to communicate with an MPL3115A2 altimeter.  A typical I2C write looks like START, SLAVE_ADDR_W, REG_ADDR, REG_DATA, STOP.  I appear to be having the most problems with the I2C read function.  I believe the I2C read for the MPL3115A2 looks like START, SLAVE_ADDR_W, REG_ADDR, START, SLAVE_ADDR_R, SLAVE_DATA, STOP.  The MPL3115A2 datasheet is weak on discussing the communication format.

 

When I try issuing the read command, the first portion (the write that sets the register address) appears to work.  I see the repeated START followed by the slave address + R packet, followed by the slave data.  The slave data is the correct value for the MPL3115A2 register I am using for testing.  I can see all of this on my scope - it all looks fine.  However, there is no 9th clock pulse after the slave data packet.  At this point, SCL remains low and SDA remains high.

 

Here is the I2C read routine .

MP_REG_RD:
	PUSH	R16					;

; Send slave address with write status

	LDI		R16,MP_SADDR		; get MPL3115A2 slave address
	LSL		R16					; shift 7-bit slave address into b7-b1
	CBR		R16,1				; b0 = 0 for write
	CALL	I2C_SADDR			; send START followed by slave address
	POP		R16					; restore reg
	BRMI	MP_REG_RD_99		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Send register address byte

	CALL	I2C_WR				; send byte & check for ACK
	BRMI	MP_REG_RD_99		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Register address sent, now switch to read to get data

	LDI		R16,$04				; send NACK - ACKACT (b4) high
;	LDI		R16,$07				; send NACK - ACKACT (b4) high and STOP
	STS		TWIC_MASTER_CTRLC,R16	; write to CTRLC

	LDI		R16,MP_SADDR		; get MPL3115A2 slave address
	LSL		R16					; shift 7-bit slave address into b7-b1
	SBR		R16,1				; b0 = 1 for read
	CALL	I2C_SADDR			; send START followed by slave address
	BRMI	MP_REG_WR_99		; skip if transaction timed out

;	BRNE	MP_REG_RD_90		; skip if no ACK received
								; this also initiates I2C read cycle
	LDS		R16,TWIC_MASTER_DATA	; get data in R16
	RJMP	MP_REG_RD_95

; No ack received, return with Z=0

MP_REG_RD_90:
	CLZ							; Z=0 for no ACK
	RJMP	MP_REG_RD_99		;

MP_REG_RD_95:
	SEZ							; Z = 1 for ACK

MP_REG_RD_99:
	CALL	I2C_STOP			; always end with STOP condition
;	POP		R16					; restore regs
;	POP		R17					;
	RET							; done

There are three subroutines called.  I2C_SADDR sends the slave address.  Upon entry, R16 contains the shifted slave address with the R/W bit set accordingly:

; Send START condition followed by slave address packet (Read)

I2C_SADDR:
	PUSH	R16					; save reg

I2C_SADDR_10:
	STS		TWIC_MASTER_ADDR,R16	; send slave address
	CALL	I2C_TIMESTART		; start transaction timeout timer

I2C_SADDR_20:
	LDS		R16,TWIC_MASTER_STATUS	; get TWIC status register
	ANDI	R16,$C0				; mask for RIF & WIF flags
	BRNE	I2C_SADDR_30		; skip out if interrupt detected

	CALL	I2C_TIMEOUT			; check for I2C timeout condition
	BREQ	I2C_SADDR_20		; keep looping

; I2C transaction timeout

	SEN							; set negative (SREG.N) on timeout
	RJMP	I2C_SADDR_99		; skip out

; I2C TX/RX interrupt detected

I2C_SADDR_30:
	LDS		R16,TWIC_MASTER_STATUS	; get TWIC status register (again)
	STS		TWIC_MASTER_STATUS,R16	; write back to clear WIF flag
	ANDI	R16,$10				; mask for RXACK bit
	TST		R16					; make SREG reflect status (Z=1, ACK received)

I2C_SADDR_99:
	POP		R16					; restore reg
	RET							; done

I2C_WR sends a byte - it seems to work OK.

I2C_WR:
	PUSH	R16					; save reg
	STS		TWIC_MASTER_DATA,R16	; send data to slave
	CALL	I2C_TIMESTART		; start transaction timeout timer

I2C_WR_20:
	LDS		R16,TWIC_MASTER_STATUS	; get TWIC status register
	ANDI	R16,$C0				; mask for RIF & WIF flags
	BRNE	I2C_WR_30			; skip out if interrupt detected

	CALL	I2C_TIMEOUT			; check for I2C timeout condition
	BREQ	I2C_WR_20			; keep looping

; I2C transaction timeout

	SEN							; set negative (SREG.N) on timeout
	RJMP	I2C_WR_99		; skip out

I2C_WR_30:
	STS		TWIC_MASTER_STATUS,R16	; write back to clear WIF flag
	ANDI	R16,$10				; mask for RXACK bit
	TST		R16					; make SREG reflect status (Z=1, ACK received)

I2C_WR_99:
	POP		R16					; restore reg
	RET							; done

I2C_STOP is supposed to send a STOP condition.  I'm not certain it is working properly, although the register write routine (not shown here) seems to work.  It is very similar to the first part of the read routine, except it terminates with a STOP instead of a repeated START.

	PUSH	R16					; save reg

	LDI		R16,$03				; command code for STOP
	STS		TWIC_MASTER_CTRLC,R16	; write to CTRLC initiates STOP

	POP		R16					; restore reg
	RET							; done

The TIMESTART and TIMESTOP routines just start TCC5 set with a 50us period to detect a timeout condition.  They do not affect any of the TWIC registers.  I included them because I got tired of having to pause the code and restart it.

 

Once I try a register read and it fails, the whole thing is hung.  At this point, the TWIC bus state is 'unknown' according to the status register bits.  Only a power cycle clears things.  I suspect this might be the slave device, which is holding the clock line low because of a botched operation. Unfortunately, I have no way to check this.

 

Any suggestions are very welcome!  Thanks!

 

Altazi

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

This is 10 years old, so it is not elegant and very embarrassing.
The latest version is a bit more sophisticated, but includes other architectures (AVR-0 and AVR-DA) and will be annoying to you.

 

I hope it helps you.

 

 

/* i2c.h for xmegaE */
#ifndef _I2C_H
#define _I2C_H

#include <avr/io.h>
#include <stdbool.h>

////////////////////////////////////////////////////////////////
// define
////////////////////////////////////////////////////////////////
/* Transaction status defines. (i2c_status)*/
#define TWIM_STATUS_READY              0
#define TWIM_STATUS_BUSY               1

/*! Transaction result enumeration. (i2c_result)*/
typedef enum TWIM_RESULT_enum {
    TWIM_RESULT_UNKNOWN          = (0x00<<0),
    TWIM_RESULT_OK               = (0x01<<0),
    TWIM_RESULT_BUFFER_OVERFLOW  = (0x02<<0),
    TWIM_RESULT_ARBITRATION_LOST = (0x03<<0),
    TWIM_RESULT_BUS_ERROR        = (0x04<<0),
    TWIM_RESULT_NACK_RECEIVED    = (0x05<<0),
    TWIM_RESULT_FAIL             = (0x06<<0),
} TWIM_RESULT_t;

////////////////////////////////////////////////////////////////
// global variable
////////////////////////////////////////////////////////////////
volatile uint8_t i2c_status;
volatile uint8_t i2c_result;

////////////////////////////////////////////////////////////////
// prototype
////////////////////////////////////////////////////////////////
void i2c_init(void);
bool i2c_trx(uint8_t dev_adr, uint8_t* buf_ptr, uint8_t byte);

////////////////////////////////////////////////////////////////
#endif

 

/* i2c.c for xmegaE */
#include <avr/interrupt.h>
#include "i2c.h"

////////////////////////////////////////////////////////////////
// USER SETTINGS
////////////////////////////////////////////////////////////////
// PORT
#define TWIX    TWIC
// VECTOR
#define TWIX_TWIM_vect      TWIC_TWIM_vect
// BAUD@32MHz
#define TWI_400kbps 35
#define TWI_100kbps 155
#define TWI_BPS TWI_400kbps

////////////////////////////////////////////////////////////////
// local variable
////////////////////////////////////////////////////////////////
static uint8_t* i2c_ptr;
static uint8_t i2c_cnt;
static uint8_t i2c_byte;

////////////////////////////////////////////////////////////////
// I2C init
////////////////////////////////////////////////////////////////
void i2c_init(void){
    TWIX.MASTER.BAUD = TWI_BPS;
    TWIX.MASTER.CTRLA = TWI_MASTER_INTLVL_MED_gc |
                        TWI_MASTER_RIEN_bm |
                        TWI_MASTER_WIEN_bm |
                        TWI_MASTER_ENABLE_bm;
    TWIX.MASTER.STATUS = TWI_MASTER_BUSSTATE_IDLE_gc;
}

////////////////////////////////////////////////////////////////
// I2C Communication start
//  i2c_adr = device_adr + (R/W)bit
////////////////////////////////////////////////////////////////
bool i2c_trx(uint8_t dev_adr, uint8_t* buf_ptr, uint8_t byte){
    if (i2c_status == TWIM_STATUS_READY) {
        i2c_ptr = buf_ptr;
        i2c_cnt = 0;
        i2c_byte = byte;
        i2c_status = TWIM_STATUS_BUSY;
        i2c_result = TWIM_RESULT_UNKNOWN;
        TWIX.MASTER.ADDR = dev_adr;
        return true;
    } else {
        return false;
    }
}

////////////////////////////////////////////////////////////////
// I2C Interrupt
////////////////////////////////////////////////////////////////
ISR(TWIX_TWIM_vect){
    uint8_t currentStatus = TWIX.MASTER.STATUS;

    // If arbitration lost
    if (currentStatus & TWI_MASTER_ARBLOST_bm) {
        TWIX.MASTER.STATUS |=   TWI_MASTER_ARBLOST_bm |
                                TWI_MASTER_BUSERR_bm |
                                TWI_MASTER_WIF_bm;
        i2c_result = TWIM_RESULT_ARBITRATION_LOST;
        i2c_status = TWIM_STATUS_READY;
    }

    // If  bus error
    else if (currentStatus & TWI_MASTER_BUSERR_bm) {
        TWIX.MASTER.STATUS |=   TWI_MASTER_ARBLOST_bm |
                                TWI_MASTER_BUSERR_bm |
                                TWI_MASTER_WIF_bm;
        i2c_result = TWIM_RESULT_BUS_ERROR;
        i2c_status = TWIM_STATUS_READY;
    }

    // If master write
    else if (currentStatus & TWI_MASTER_WIF_bm) {
        // Is NAK
        if (currentStatus & TWI_MASTER_RXACK_bm) {
            TWIX.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc;
            i2c_result = TWIM_RESULT_NACK_RECEIVED;
            i2c_status = TWIM_STATUS_READY;
        }

        // Send next
        else if (i2c_cnt < i2c_byte) {
            TWIX.MASTER.DATA = i2c_ptr[i2c_cnt++];
        }

        // Send completely
        else {
            TWIX.MASTER.CTRLC = TWI_MASTER_CMD_STOP_gc;
            i2c_result = TWIM_RESULT_OK;
            i2c_status = TWIM_STATUS_READY;
        }
    }

    // If master read
    else if (currentStatus & TWI_MASTER_RIF_bm) {
        // Read data
        i2c_ptr[i2c_cnt++] = TWIX.MASTER.DATA;

        // Receive next
        if (i2c_cnt < i2c_byte) {
            TWIX.MASTER.CTRLC = TWI_MASTER_CMD_RECVTRANS_gc;
        }

        // Receive completely
        else {
            TWIX.MASTER.CTRLC = TWI_MASTER_ACKACT_bm |
                                TWI_MASTER_CMD_STOP_gc;
            i2c_result = TWIM_RESULT_OK;
            i2c_status = TWIM_STATUS_READY;
        }
    }

    /* If unexpected state. */
    else {
        i2c_result = TWIM_RESULT_FAIL;
        i2c_status = TWIM_STATUS_READY;
    }
}

 

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

I believe that you are setting and clearing the wrong bit in R16 before the Slave Address Writes.   CBR R16,1 and SBR R16, 1   should instead be:  CBR R16,0 and SBR R16, 0.

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

Simonetta wrote:

I believe that you are setting and clearing the wrong bit in R16 before the Slave Address Writes.   CBR R16,1 and SBR R16, 1   should instead be:  CBR R16,0 and SBR R16, 0.

HAHA!!!!  YES!!!!  Excellent catch!  I always goof up on that.  Some instructions use the bit number, some use a bit mask.  Thank you!  Fix #1 on the way!

 

EDIT: Upon closer inspection, the CBR and SBR instructions use bit masks, which allow any or all bits to be cleared or set simultaneously, e.g., SBR R16,$C3.  So, the original forms were correct - even if the appearance was confusing.  Bah.  The SBRC and SBRS instructions specify the bit number, 0-7.  Back to kabasan's code and the drawing board.

 

Altazi

Last Edited: Sat. Mar 28, 2020 - 07:14 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Despite the confusion over CBR/SBR instruction format, one big improvement you can make is to refactor out the magic numbers:

	ANDI	R16,$C0				; mask for RIF & WIF flags

is one example.

 

Even in ASM you can use symbolic bit names which contribute greatly to checking faulty code.

 

 

 

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

This is what I am seeing when I attempt a read sequence:

TWIC Read Seq (Fail)

 

Everything looks fine, including the slave data packet value.  However, after the slave data packet, there is no NACK, no STOP - it just hangs this way, and a RESET from the JTAGICE3 won't clear it.  A power cycle is required to clear whatever happened.

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

Did you read the TWI status register and see what the problem is?

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

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

jgmdesign wrote:

Did you read the TWI status register and see what the problem is?

 

Jim

My TWIC initialization routine writes the bus state bits to 01 (idle).  After a successful write sequence, the bus state is returned to idle.  After attempting the read command (as shown in the scope capture), the MCU hangs and I have to halt the execution.  The bus state after halt is 10 (busy).

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

Why don't you write a proper build-able AS7.0  project?

e.g. with a proven library.

 

Then readers would assist you to debug your home-grown code.

When there is working code to compare,  it is more likely to attract help.

 

The Xmega-A is thirteen years old.   The Xmega-E is about 7 years old.

 

If there is a problem with XMEGA-E TWI we probably would have heard about it by now.

 

David.

 

 

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

david.prentice wrote:
If there is a problem with XMEGA-E TWI we probably would have heard about it by now.

+1

 

I am using the X32E5 with three items hanging if the I2C bus and its working just fine.

 

I am not using ASM though.....

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

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

There is nothing wrong with using ASM.

 

However no-one is going to read magic numbers.   Whether it is ASM or C.

 

Ok,  if someone presents working code that exhibits some "unusual behaviour" it might pique some interest.

For example,   Jim showed some "unusual branch / interrupt" behaviour on the Xmega32E5

 

David.

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

Please show us your code for reading I2C.  There is code for writing the SLA, writing data, and sending STOP, but none shown for reading data. 

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

The 32E5 automatically appends a slave read after writing the slave address with the R bit set.  Here is the routine that writes the slave address.

I2C_SADDR:
	PUSH	R16			; save reg

I2C_SADDR_10:
	STS	TWIC_MASTER_ADDR,R16	; send slave address
	CALL	I2C_TIMESTART		; start transaction timeout timer

I2C_SADDR_20:
	LDS	R16,TWIC_MASTER_STATUS	; get TWIC status register
	ANDI	R16,$C0			; mask for RIF & WIF flags
	BRNE	I2C_SADDR_30		; skip out if interrupt detected

	CALL	I2C_TIMEOUT		; check for I2C timeout condition
	BREQ	I2C_SADDR_20		; keep looping

; I2C transaction timeout

	SEN				; set negative (SREG.N) on timeout
	RJMP	I2C_SADDR_99		; skip out

; I2C TX/RX interrupt detected

I2C_SADDR_30:
	LDS	R16,TWIC_MASTER_STATUS	; get TWIC status register (again)
	STS	TWIC_MASTER_STATUS,R16	; write back to clear WIF flag
	ANDI	R16,$10			; mask for RXACK bit
	TST	R16			; make SREG reflect status (Z=1, ACK received)

I2C_SADDR_99:
	POP	R16			; restore reg
	RET				; done

The I2C_SADDR routine is called as a part of the I2C register read routine, below:

MP_REG_RD:
	PUSH	R16					; save reg address

; Send slave address with write status

	LDI	R16,MP_SADDR		; get MPL3115A2 slave address
	LSL	R16		; shift 7-bit slave address into b7-b1
	CBR	R16,$01		; b0 = 0 for write
	CALL	I2C_SADDR		; send START followed by slave address
	POP	R16			; restore reg address
	BRMI	MP_REG_RD_99		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Send register address byte

	CALL	I2C_WR			; send byte & check for ACK
	BRMI	MP_REG_RD_99		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Register address sent, now switch to read to get data

	LDI	R16,MP_SADDR		; get MPL3115A2 slave address
	LSL	R16			; shift 7-bit slave address into b7-b1
	SBR	R16,$01			; b0 = 1 for read
	CALL	I2C_SADDR		; send START followed by slave address
					; also initiates I2C read
	BRMI	MP_REG_WR_99		; skip if transaction timed out

	LDS	R16,TWIC_MASTER_DATA	; get received data in R16

	PUSH	R16			; save reg
	LDI	R16,$07			; send NACK and STOP
	STS	TWIC_MASTER_CTRLC,R16	; write to CTRLC initiates command
	POP	R16			; restore reg
	RJMP	MP_REG_RD_95

; No ack received, return with Z=0

MP_REG_RD_90:
	CLZ				; Z=0 for no ACK
	RJMP	MP_REG_RD_99		;

MP_REG_RD_95:
	SEZ				; Z = 1 for ACK

MP_REG_RD_99:
	CALL	I2C_STOP		; always end with STOP condition
	RET				; done

The first part works, but any code after the comment "; Register address sent, now switch to read to get data" is suspect.  The code was all lined up nicely, but putting in the window messed with the tab spacing.  Sorry.

 

I have made some progress.  I am now getting the ninth clock pulse on the TWIC read, but still no NACK or STOP.  This is what I see:

TWIC Read Seq (fail2)

 

After this read sequence, the bus state is BUSY.  I can set the state to IDLE, and it shows as IDLE, but the SDA level remains low.  If I attempt any other transaction after the failed read sequence, I receive a timeout error and the bus state is unknown (3).  I am not sure why I'm not getting the NACK and the STOP, since I am clearly writing to TWIC_MASTER_CTRLC with the proper settings for these.

 

Looking at the SDA signal, there appears to be a very slight 'bump' at the point where the MCU might be trying to take SDA high.  I have no way of knowing if the slave is pulling SDA low - not sure why it would, anyway.  It just looked suspicious.

 

I must say that I truly appreciate you all looking at this.  You can help me see what I am missing.  It's rough proofing your own work.  Thanks!

 

Altazi

Last Edited: Sun. Mar 29, 2020 - 07:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

NB: I haven't checked your magic numbers, it's been mentioned previously by myself and others, so that's down to you to check.

 

Otherwise the datasheet says:

 

21.5.1.4 Case M4: Address packet transmit complete - Direction bit set
If the master receives an ACK from the slave, the master proceeds to receive the next byte of data from the slave. When
the first data byte is received, the master read interrupt flag is set and the master received acknowledge flag is cleared.
The clock hold is active at this point, preventing further activity on the bus

 

 

21.9.4 STATUS – Status register

  •  Bit 7  – RIF: Read Interrupt Flag

This flag is set when a byte is successfully received in master read mode; i.e., no arbitration was lost or bus error
occurred during the operation. Writing a one to this bit location will clear RIF. When this flag is set, the master
forces the SCL line low, stretching the TWI clock period. Clearing the interrupt flags will release the SCL line.
This flag is also cleared automatically when:

  •  Writing to the ADDR register
  •  Writing to the DATA register
  •  Reading the DATA register
  •  Writing a valid command to the CMD bits in the CTRLC register

 

You prematurely both clear the status flags AND read the DATA register before setting the ACK/NACK bit you require. Therefore you should write to ACKACT before clearing the STATUS flags.

 

Interestingly the  kabasan code should also suffer from the same problem.

 

Anyway; if this is the correct answer and fixes your problem can I have your scope as my prize ?

 

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

I am always impressed by $1000s spent on a scope.   And never spend a few $0.01s on printing on paper.    Or attaching a ZIP.

 

If software is your job,   it is a wise to spend money on your "tools".   (which include software licences)

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

Oh, was there something wrong with my code?

Well, I only use LCD and RTC etc. and do not work as a slave.

The xmega32E5 has not been a problem for the last 10 years. And that code just recently worked on the AVR128DA.

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

david.prentice wrote:

I am always impressed by $1000s spent on a scope.   And never spend a few $0.01s on printing on paper.    Or attaching a ZIP.

 

If software is your job,   it is a wise to spend money on your "tools".   (which include software licences)

I am an electronic hardware engineer, specializing in analog design.  I am learning code.  I found that assembler suits me better than C, as I like to know exactly what is happening and where everything is.  I don't have chars, ints, etc. - I just have RAM, to use as I wish.

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

Even more important to start with proven libraries.

 

Once you have the hardware working,   you can think about designing your own software.   (with or without magic numbers)

 

You learn how to turn a steering wheel when you start driving a car.    You gain practical experience before designing your own engine, gearbox, wheels, ...

 

Please don't take offence.    Other Forum members have different opinions to me.     There is no right or wrong way to learn.    Just what suits the individual (and might be the same as schools, colleges have developed over 100s of years)

 

David.

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

Thank you for looking into my TWIC problems, N. Winterbottom!

21.5.1.4 Case M4: Address packet transmit complete - Direction bit set
If the master receives an ACK from the slave, the master proceeds to receive the next byte of data from the slave. When
the first data byte is received, the master read interrupt flag is set and the master received acknowledge flag is cleared.
The clock hold is active at this point, preventing further activity on the bus

In the beautiful scope capture I posted above, the master receives an ACK from the slave.  the master received the byte of data from the slave - and the data is correct for the register I specified.  The master set the Read Interrupt Flag, which is cleared in the I2C_SADDR routine.  The master data register is read promptly upon return from the I2C_SADDR routine.  The clock is held low until the ninth clock pulse occurs - this is where the NACK should be.  SCL returns to the high state, but it appears to be an ACK, and no STOP condition is sent.  As you can see, the clock line is high, as it should be.  The data line, however, is unfortunately low.

21.9.4 STATUS – Status register

  •  Bit 7  – RIF: Read Interrupt Flag

This flag is set when a byte is successfully received in master read mode; i.e., no arbitration was lost or bus error
occurred during the operation. Writing a one to this bit location will clear RIF. When this flag is set, the master
forces the SCL line low, stretching the TWI clock period. Clearing the interrupt flags will release the SCL line.
This flag is also cleared automatically when:

  •  Writing to the ADDR register
  •  Writing to the DATA register
  •  Reading the DATA register
  •  Writing a valid command to the CMD bits in the CTRLC register

The RIF is set when the data byte is received successfully - and the data is correct.  SCL is released, although which action caused this is unclear.  All that matters is that SCL is released.  What I am trying to figure out is why, after a successful slave read, when I write to TWIC_MASTER_CTRLC to send a NACK and STOP condition, I get the required ninth clock pulse, but no NACK or STOP.

 

I did discover that using the scope to capture waveforms while setting breakpoints and stepping through code is almost useless, as weird things happen with the SCL baud rate.

 

Sorry, I need my scope.  It was only moderately expensive, well under $3k as I recall.  There are some scopes north of $20k that I would love to have, though.  :-)

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

david.prentice wrote:

Even more important to start with proven libraries.

 

Once you have the hardware working,   you can think about designing your own software.   (with or without magic numbers)

 

You learn how to turn a steering wheel when you start driving a car.    You gain practical experience before designing your own engine, gearbox, wheels, ...

 

Please don't take offence.    Other Forum members have different opinions to me.     There is no right or wrong way to learn.    Just what suits the individual (and might be the same as schools, colleges have developed over 100s of years)

 

David.

 

I am sure that I could get the code to function properly if I used pretested libraries.  Then I would have learned how to use pretested libraries.  I tried finding source code for TWI libraries, but didn't have success.  In looking at many other posts from people experiencing problems, they are using libraries created by others - and if they eventually solve their problems, they still didn't gain any knowledge of the detailed operations of the the peripheral hardware and its functioning.  That is what I want to achieve.  I have incrementally worked through this monster to the point where I can actually read a byte of data from my slave device.  Unfortunately, I can only do this one time, as the bus is buggered after the read sequence.  Still, that is progress, and I am gaining insight into the operation of the TWI peripheral.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mystery (mostly) solved.  The register read routine ended with two calls for a STOP condition.  Considering they were issued microseconds apart, that must have been enough to screw up the state machine hardware.  Removing one of the STOPs was enough to make the register read routine function repeatedly and (apparently) reliably.

 

Even though I was doing something that shouldn't have been done, it seems to me (as a hardware designer) that the internal state machine was too fragile.  Even resetting the bus state and disabling and re-enabling the TWI peripheral wasn't enough to clear the error - a power cycle was required.

 

Thank you for your help!

 

Altazi

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

Have you read the NXP (formerly Philips) I2C Manual?

 

That gives you a method for recovering a "locked" bus - did you try that?

 

See Tip #5 in my signature, below:

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Altazi wrote:
I am sure that I could get the code to function properly if I used pretested libraries.  Then I would have learned how to use pretested libraries.

But then you could start looking into those libraries to see  what they do, how they do it, etc.

 

Many people find that having a working example to learn from is helpful.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When you write to the

TWIC_MASTER_CTRLC

register after sending START and SLA, you are not indicating that the Master should send a NACK to the Slave after receiving the data byte from the Slave.  So the Slave is waiting for more SCL pulses from the Master.   With each I2C data-read operation, you have to be able to tell the I2C controller whether to send ACK (more read data expected) or NACK (this is the last byte, so release the bus).

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

awneil wrote:

Have you read the NXP (formerly Philips) I2C Manual?

 

That gives you a method for recovering a "locked" bus - did you try that?

 

See Tip #5 in my signature, below:

Yes, I reviewed the manual.  It didn't offer anything relevant on this subject, except that it recommended a power cycle if the other steps failed.  I didn't find any methods that would clear a stuck master.  Disabling and re-enabling the TWI peripheral didn't even work.  Neither did an actual MCU reset.  The power cycle, inconvenient as it is, is 100% effective.

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

awneil wrote:

Altazi wrote:
I am sure that I could get the code to function properly if I used pretested libraries.  Then I would have learned how to use pretested libraries.

But then you could start looking into those libraries to see  what they do, how they do it, etc.

 

Many people find that having a working example to learn from is helpful.

Finding a working example of the low-level driver code would be helpful.  No so easy to find.  No joy.  Kabasan was kind enough to share his code, at least.

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

This is the code that works for reading a single register from my slave device.  I2C_SADDR is called by the overall register read routine, MP_REG_RD:

; Upon Entry:	Shifted slave address in R16, R/W bit set/cleared
; Upon Exit:	Z=0 NACK, Z=1 ACK
;
; Send START condition (followed by slave address packet for Read)

I2C_SADDR:
	PUSH	R16			; save reg

I2C_SADDR_10:
	STS	TWIC_MASTER_ADDR,R16	; send slave address
	CALL	I2C_TIMESTART		; start transaction timeout timer

I2C_SADDR_20:
	LDS	R16,TWIC_MASTER_STATUS	; get TWIC status register
	ANDI	R16,$C0			; mask for RIF & WIF flags
	BRNE	I2C_SADDR_30		; skip out if interrupt detected

	CALL	I2C_TIMEOUT		; check for I2C timeout condition
	BREQ	I2C_SADDR_20		; keep looping

; I2C transaction timeout

	SEN				; set negative (SREG.N) on timeout
	RJMP	I2C_SADDR_99		; skip out

; I2C TX/RX interrupt detected

I2C_SADDR_30:
	LDS	R16,TWIC_MASTER_STATUS	; get TWIC status register (again)
	STS	TWIC_MASTER_STATUS,R16	; write back to clear WIF flag
	ANDI	R16,$10			; mask for RXACK bit
	TST	R16			; make SREG reflect status (Z=1, ACK received)

I2C_SADDR_99:
	POP	R16			; restore reg
	RET				; done

Here is the overall register read routine:

; Upon Entry:	R16 contains register address
;
; Upon Exit:	R16 contains returned data
;				Z=0 NACK, Z=1 ACK

MP_REG_RD:
	PUSH	R16			; save reg address

; Send slave address with write status

	LDI	R16,MP_SADDR		; get MPL3115A2 slave address
	LSL	R16			; shift 7-bit slave address into b7-b1
	CBR	R16,$01		; b0 = 0 for write
	CALL	I2C_SADDR		; send START followed by slave address
	POP	R16			; restore reg address
	BRMI	MP_REG_RD_97		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Send register address byte

	CALL	I2C_WR			; send byte & check for ACK
	BRMI	MP_REG_RD_97		; skip if transaction timed out

	BRNE	MP_REG_RD_90		; skip if no ACK received

; Register address sent, now switch to read to get data

	LDI	R16,MP_SADDR		; get MPL3115A2 slave address
	LSL	R16			; shift 7-bit slave address into b7-b1
	SBR	R16,$01			; b0 = 1 for read
	CALL	I2C_SADDR		; send START followed by slave address
					; also initiates I2C read
	BRMI	MP_REG_RD_97		; skip if transaction timed out

	LDS	R16,TWIC_MASTER_DATA	; get received data in R16

	PUSH	R16			; save reg
	LDI	R16,$07			; send NACK and STOP
	STS	TWIC_MASTER_CTRLC,R16	; write to CTRLC initiates command
	POP	R16			; restore reg
	RJMP	MP_REG_RD_99        ; do not send STOP twice!

; No ack received, return with Z=0

MP_REG_RD_90:
	CLZ				; Z=0 for no ACK
	RJMP	MP_REG_RD_99		;

MP_REG_RD_95:
	SEZ				; Z = 1 for ACK

MP_REG_RD_97:
	CALL	I2C_STOP		; always end with STOP condition

MP_REG_RD_99:
	RET				; done

And finally,

I2C_STOP:
	PUSH	R16			; save reg

	LDI	R16,$03			; command code for STOP
	STS	TWIC_MASTER_CTRLC,R16	; write to CTRLC initiates STOP

	POP	R16			; restore reg
	RET				; done

All of this appears to be working repeatedly and reliably.

 

Now, however, I am trying to get a multiple read sequence working, where I can do a sequence of reads followed by ACKs, and a final read followed by a NACK and STOP.  I am having trouble getting the ninth clock and ACK after the automatic byte read I get when I write to the TWIC_MASTER_SLAVE register with the READ bit set.  It's not happening, and that is buggering the rest of it.  As before, anything after the comment

; Register address sent, now switch to read to get data

is suspect.

Altazi

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

To be succinct, the slave single-register read sequence shown below works just fine:

 

Master                              Slave

START

SLAVE_ADDR + WR (0)

                                        ACK

DATA (Register number)

                                        ACK

DATA (Register value)

                                        ACK

REPEATED_START

SLAVE_ADDR + RD (1)

                                        DATA

NACK

STOP

 

What I would like to do is to create a multiple read sequence, something like that which is shown below.  The slave automatically increments the register address, so sequential reads should return values from reg0, reg1, . . ., etc.:

 

Master                              Slave

START

SLAVE_ADDR + WR (0)

                                        ACK

DATA (Register number)

                                        ACK

DATA (Register value)

                                        ACK

REPEATED_START

SLAVE_ADDR + RD (1)

                                        DATA1

ACK <------------------------------------------ This is what I do not know how to get

                                        DATA2

ACK

                                        DATA3

ACK

                                        DATA4

ACK

                                        DATA5

NACK

STOP

 

When the TWIC_MASTER_ADDR register is written with b0 = 1 (read), the next data byte read occurs automatically as a part of the sequence.  After that first data byte is received, I do not get a ninth clock pulse for the ACK

 

Writing to TWI_MASTER_CRTLC with just the ACK/NACK bit set or cleared prior to writing the TWIC_MASTER_ADDR doesn't initiate a ninth clock or an ACK/NACK.

 

Writing to TWI_MASTER_CTRLC with an ACK/NACK and a command code for byte receive initiates a byte read sequence followed by an ACK/NACK.

 

What must I do to get the TWI master to send the ninth clock with an ACK after that first automatic byte read when the slave address is written?

 

Altazi

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

I have written a TWI section that, among other things, will read sequential device registers. The style might not be optimum, but it shows the sequence.

 

Oh yes, it works.

 

Let me know if I've failed to document any constant or macro definitions. I've looked at this code so often I can't see it any more.

 

;
; twi.asm
;
; Created: 14Mar2020
; Updated: 16May2020
; Author : JSRagman
;

; Description:
;     XMEGA E5 TWI functions - Single Master.
;
; Notes:
;     1. These functions are written for a single-master TWI bus.
;        There is only one TWI master - you.
;     2. SLA+W means Slave TWI address plus Write bit (0).
;     3. Error Handling - The Status Register T bit (SREG_T) is used to
;        indicate whether a function has completed normally (SREG_T = 0)
;        or has encountered an error (SREG_T = 1).

; Abbreviations:
;     DS       Data Stack
;     EE       EEPROM
;     SR       SRAM
;     SREG_T   Status Register T bit
;     TwiRd    TWI Read
;     TWiWr    TWI Write

; Parameter Usage:
;     r20  -  SLA+W/R
;     r21  -  device register address
;     r22  -  byte count
;             Data Stack byte count
;             register value
;     r23  -  SRAM byte count
;       X  -  SRAM destination pointer
;             SRAM source pointer
;             EEPROM source pointer
;       Y  -  Data Stack

; Return Values:
;     r22  -  register value
;  SREG_T  -  status flag: success (0) or error (1)


; Function List:
;     Twi_LockParms     - Gains control over sr_twiparms
;     Twi_OnReset       - Initializes the TWI module following power-on reset
;     TwiRd_Register    - Retrieves the contents of one device register
;     TwiRd_Regs        - Reads one or more consecutive device registers
;                         and writes the data to SRAM
;     TwiRd_Wait        - Waits for MSTATUS.RIF, .ARBLOST, or .BUSERR
;     TwiWr_FromDS      - Transmits one or more bytes from the Data Stack
;     TwiWr_FromEE      - Transmits a block of data from EEPROM
;     TwiWr_FromSR      - Transmits a block of data from SRAM and/or
;                         the Data Stack
;     TwiWr_Register    - Writes to a single device register
;     TwiWr_Wait        - Waits for MSTATUS.WIF




; Twi_LockParms                                                       21May2020
; -----------------------------------------------------------------------------
; Description:
;     Gains control over sr_twiparms by setting OPST_TWIPARMS_bp.
;
;     If the bit is already set, this function waits for it to clear and then
;     sets it before returning.
; Parameters:
;     None
; Address Labels:
;     sr_twiparms
; I/O Registers Affected:
;     GP_OPSTAT (GPIO_GPIOR1)
; Returns:
;     Nothing
; Note:
;     Functions that consume sr_twiparms content will clear OPST_TWIPARMS
;     on exit (or else).
Twi_LockParms:

    sbic   GP_OPSTAT,  OPST_TWIPARMS_bp
    rjmp   Twi_LockParms

    sbi    GP_OPSTAT,  OPST_TWIPARMS_bp

    ret


; Twi_OnReset                                                         16May2020
; -----------------------------------------------------------------------------
; Description:
;     Initializes the ATxmega32E5 TWI module. Baud rate values assume
;     a 32 MHz system clock.
;
;     1. Set a 300ns SDA hold time
;     2. Set the Master BAUD value for a 400 kHz SCL
;     3. Set the Master CTRLA Enable bit
;     4. Set TWI bus state = Idle
; Parameters:
;     None
; I/O Registers Affected:
;     TWIC_CTRL          (0x0480) - TWI Common Control Register
;     TWIC_MASTER_BAUD   (0x0485) - TWI Master Baud Rate Register
;     TWIC_MASTER_CTRLA  (0x0481) - TWI Master Control Register A
;     TWIC_MASTER_STATUS (0x0484) - TWI Master Status Register
; Constants:
;     TWIM_BAUD_400khz_c  - Master BAUD value for a 400 kHz SCL
; Macros Used:
;     stsi - Writes an immediate value to an 8-bit I/O register
Twi_OnReset:
    push   r16

    stsi   TWIC_CTRL,             TWI_SDAHOLD_300NS_gc
    stsi   TWIC_MASTER_BAUD,      TWIM_BAUD_400khz_c
    stsi   TWIC_MASTER_CTRLA,     (1<<TWI_MASTER_ENABLE_bp)
    stsi   TWIC_MASTER_STATUS,    TWI_MASTER_BUSSTATE_IDLE_gc

    pop    r16
    ret


; TwiRd_Register                                                      16May2020
; -----------------------------------------------------------------------------
; Description:
;     Retrieves the value of one device register into r22.
;
; Parameters:
;     r20          - device SLA+W
;     r21          - register address
;
; General-Purpose Registers:
;     Parameters   - r20, r21
;     Modified     - r22
;
; Constants:
;     ACKACT_NACK_c   - (0x04) ACK action = NACK
;
; Functions Called:
;     SREG_T = TwiRd_Wait ( )
;
; Macros Used:
;     stsi - Writes an immediate value to an 8-bit I/O register
;
; Returns:
;     SREG_T - success (0) or error (1)
;     r22    - register value
;
TwiRd_Register:
    push   r16

   .def    slarw   = r20                    ; param: SLA+W
   .def    regaddr = r21                    ; param: register address

;   Transmit SLA+W
    sts    TWIC_MASTER_ADDR,   slarw        ; ADDR   = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiRd_Register_exit              ; if (SREG_T == 1)
                                            ;     goto exit
;   Transmit register address
    sts    TWIC_MASTER_DATA,   regaddr      ; DATA   = regaddr
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiRd_Register_exit              ; if (SREG_T == 1)
                                            ;     goto exit
;   Generate repeated START
;   Transmit SLA+R
    sbr    slarw,              1            ; slarw  = Read
    sts    TWIC_MASTER_ADDR,   slarw        ; ADDR   = SLA+R
    rcall  TwiRd_Wait                       ; SREG_T = TwiRd_Wait()
    brts   TwiRd_Register_exit              ; if (SREG_T == 1)
                                            ;     goto exit
;   Read data byte into r22
    lds    r22,                TWIC_MASTER_DATA

TwiRd_Register_exit:
;   Generate STOP condition
    stsi   TWIC_MASTER_CTRLC,  (TWI_MASTER_CMD_STOP_gc | ACKACT_NACK_c)

   .undef  slarw
   .undef  regaddr

    pop    r16
    ret


; TwiRd_Regs                                                          16May2020
; -----------------------------------------------------------------------------
; Description:
;     Reads one or more registers from a TWI-connected device and writes the
;     data to SRAM.
;
; Parameters:
;     r20            - device SLA+W
;     r21            - device register address
;     r22            - byte count
;     X              - SRAM destination pointer
;
; General-Purpose Registers:
;     Parameters     - r20, r21, r22, X
;     Modified       - 
;
; I/O Registers:
;     TWIC_MASTER_ADDR
;     TWIC_MASTER_CTRLC
;     TWIC_MASTER_DATA
;
; Constants:
;     ACKACT_NACK_c  - (0x04) ACK action = NACK
;
; Functions Called:
;     SREG_T = TwiRd_Wait ( )
;
; Returns:
;     SREG_T         - success (0) or error (1)
;
TwiRd_Regs:
    push   r16
    push   r19
    push   r22
    push   XL
    push   XH

   .def    command = r19                    ; CTRLC register command
   .def    slarw   = r20                    ; param: SLA+W
   .def    regaddr = r21                    ; param: register address
   .def    count   = r22                    ; param: byte count

;   Transmit SLA+W
    sts    TWIC_MASTER_ADDR,   slarw        ; ADDR   = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiRd_Regs_exit                  ; if (SREG_T == 1)
                                            ;     goto exit
;   Transmit register address
    sts    TWIC_MASTER_DATA,   regaddr      ; DATA   = regaddr
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiRd_Regs_exit                  ; if (SREG_T == 1)
                                            ;     goto exit
;   Generate repeated START
;   Transmit SLA+R
    sbr    slarw,              1            ; slarw  = Read
    sts    TWIC_MASTER_ADDR,   slarw        ; ADDR   = SLA+R
    ldi    command,            TWI_MASTER_CMD_RECVTRANS_gc

TwiRd_Regs_loop:
    rcall  TwiRd_Wait                       ; SREG_T = TwiRd_Wait()
    brts   TwiRd_Regs_exit                  ; if (SREG_T == 1)
                                            ;     goto _exit
    lds    r16,    TWIC_MASTER_DATA         ; r16       = DATA
    st     X+,     r16                      ; SRAM[X++] = r16
    dec    count                            ; if (--count == 0)
    breq   TwiRd_Regs_exit                  ;     goto _exit
    sts    TWIC_MASTER_CTRLC,  command      ; CTRLC = command
    rjmp   TwiRd_Regs_loop                  ; goto _loop

TwiRd_Regs_exit:
;   Generate STOP condition
    ldi    command,            (TWI_MASTER_CMD_STOP_gc | ACKACT_NACK_c)
    sts    TWIC_MASTER_CTRLC,  command

   .undef  command
   .undef  slarw
   .undef  regaddr
   .undef  count

    pop    XH
    pop    XL
    pop    r22
    pop    r19
    pop    r16
    ret



; TwiRd_Wait                                                          16May2020
; -----------------------------------------------------------------------------
; Description:
;     Waits for a RIF, ARBLOST, or BUSERR status flag to be set.
;     Returns the SREG T flag to indicate success (0) or error (1).
;
;     Success is defined as:
;         - MSTATUS.RIF is set
;
; Parameters:
;     None
;
; Constants:
;     TWIM_RDFLAGS_bm  - (0b_1000_1100)  RIF, ARBLOST, and BUSERR flags
;
; Returns:
;     SREG_T - success (0) or error (1)
;
TwiRd_Wait:
    push   r16

    clt                                     ; SREG_T = 0
TwiRd_Wait_wait:
    lds    r16,    TWIC_MASTER_STATUS       ; r16 = STATUS
    andi   r16,    TWIM_RDFLAGS_bm          ; if (RDFLAGS == 0)
    breq   TwiRd_Wait_wait                  ;     goto TwiRd_Wait_wait

    sbrs   r16,    TWI_MASTER_RIF_bp        ; if (RIF == 0)
    set                                     ;     error: SREG_T = 1

    pop    r16
    ret


; TwiWr_FromDS                                                        16May2020
; -----------------------------------------------------------------------------
; Description:
;     Transmits one or more bytes from the Data Stack to a
;     TWI-connected device.
;
; Parameters:
;     r20          - device SLA+W
;     r22          - byte count (n)
;     Data Stack   - n bytes of data
;
; General-Purpose Registers:
;     Parameters   - r20, r22, Y
;     Modified     - Y
;
; Data Stack:
;     Initial      - n bytes of data
;     Final        - empty
;
; I/O Registers:
;     TWIC_MASTER_ADDR
;     TWIC_MASTER_CTRLC
;     TWIC_MASTER_DATA
;
; Functions Called:
;     SREG_T = TwiWr_Wait ( )
;
; Macros Used:
;     popd         - Pops the top byte from the data stack into a register
;     stsi         - Writes an immediate value to an 8-bit I/O register
;
; Returns:
;     SREG_T       - success (0) or error (1)
;
TwiWr_FromDS:
    push   r16
    push   r20
    push   r22

   .def    slaw    = r20                    ; param: SLA+W
   .def    count   = r22                    ; param: byte count

    sts    TWIC_MASTER_ADDR,   slaw         ; ADDR   = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiWr_FromDS_error

    tst    count                            ; compare(count, zero)
TwiWr_FromDS_loop:
    breq   TwiWr_FromDS_exit                ; if (count == 0)
                                            ;     goto exit
    popd   r16                              ; r16    = DS[Y++]
    sts    TWIC_MASTER_DATA,   r16          ; DATA   = r16
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    dec    count                            ; count--
    brtc   TwiWr_FromDS_loop                ; if (SREG_T == 0)
                                            ;     goto TwiWr_FromDS_loop
TwiWr_FromDS_error:
    tst    count                            ; compare(count, zero)
TwiWr_FromDS_popall:
    breq   TwiWr_FromDS_exit                ; if (count == 0) goto exit
    popd   r16                              ; r16 = DS[Y++]
    dec    count                            ; count--
    rjmp   TwiWr_FromDS_popall              ; goto  TwiWr_FromDS_popall

TwiWr_FromDS_exit:
;   Generate STOP condition
    stsi   TWIC_MASTER_CTRLC,  TWI_MASTER_CMD_STOP_gc

   .undef  count
   .undef  slaw

    pop    r22
    pop    r20
    pop    r16
    ret


; TwiWr_FromEE                                                        16May2020
; -----------------------------------------------------------------------------
; Description:
;     Transmits a block of data from EEPROM to a TWI-connected device.
;
; Parameters:
;     r20          -  target device SLA+W
;     r22          -  byte count
;     X            -  Points to EEPROM source data
;
; General-Purpose Registers:
;     Parameters   - r20, r22, X
;     Modified     - 
;
; I/O Registers:
;     TWIC_MASTER_ADDR
;     TWIC_MASTER_CTRLC
;     TWIC_MASTER_DATA
;
; Functions Called:
;     SREG_T = TwiWr_Wait ( )
;
; Macros Used:
;     stsi         - Writes an immediate value to an 8-bit I/O register
;
; Returns:
;     SREG_T - success (0) or error (1)
;
TwiWr_FromEE:
    push   r16
    push   r22
    push   XL
    push   XH

   .def    slaw    = r20                    ; param: SLA+W
   .def    count   = r22                    ; param: byte count

    sts    TWIC_MASTER_ADDR,   slaw         ; _ADDR  = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiWr_FromEE_exit

    tst    count                            ; compare(count, zero)
TwiWr_FromEE_loop:
    breq   TwiWr_FromEE_exit                ; if (count == 0)
                                            ;     goto exit
    ld     r16,                X+           ; r16    = EEPROM[X++]
    sts    TWIC_MASTER_DATA,   r16          ; _DATA  = r16
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    dec    count                            ; count--
    brtc   TwiWr_FromEE_loop                ; if (SREG_T == 0)
                                            ;     goto TwiWr_FromEE_loop
TwiWr_FromEE_exit:
;   Generate STOP condition
    stsi   TWIC_MASTER_CTRLC,  TWI_MASTER_CMD_STOP_gc

   .undef  slaw
   .undef  count

    pop    XH
    pop    XL
    pop    r22
    pop    r16
    ret


; TwiWr_FromSR                                                        16May2020
; -----------------------------------------------------------------------------
; Description:
;     Transmits a block of data from SRAM to a TWI-connected device.
;     Data can be sourced from the Data Stack, an SRAM address, or both.
;     The Data Stack is transmitted first, followed by SRAM data.
;
;     This is useful if you have some nice clean data in SRAM but need to
;     preface it with a command or two before sending it off.
;
; Parameters:
;     r20          - device SLA+W
;     r22          - (n) data stack byte count
;     r23          - (p) SRAM data byte count
;     X            - points to p bytes of SRAM data
;     DS           - n bytes of data
;
; General-Purpose Registers:
;     Parameters   - r20, r22, r23, X, Y
;     Modified     - Y
;
; Data Stack:
;     Initial      - n bytes of data (n can be zero)
;     Final        - empty
;
; Functions Called:
;     SREG_T = TwiWr_Wait ( )
;
; Macros Used:
;     popd - Pops the top byte from the data stack into a register
;
; Returns:
;     SREG_T - success (0) or error (1)
;
TwiWr_FromSR:
    push   r16
    push   r19
    push   r22
    push   r23
    push   XL
    push   XH

   .def    slaw    = r20                    ; param: device SLA+W
   .def    dscount = r22                    ; param: Data Stack byte count
   .def    srcount = r23                    ; param: SRAM data byte count

; Connect with device
    sts    TWIC_MASTER_ADDR,   slaw         ; ADDR   = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiWr_FromSR_error

; Data Stack Section
TwiWr_FromSR_dstack:
    tst    dscount                          ; compare(dscount, zero)
TwiWr_FromSR_dstack_loop:
    breq   TwiWr_FromSR_addr                ; if (dscount == 0)
                                            ;     goto SRAM section
    popd   r16                              ; r16    = DS[Y++]
    sts    TWIC_MASTER_DATA,   r16          ; DATA   = r16
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    dec    dscount                          ; dscount--
    brtc   TwiWr_FromSR_dstack_loop         ; if (SREG_T == 0)
                                            ;     goto TwiWr_FromSR_dstack_loop
    rjmp   TwiWr_FromSR_error               ; goto TwiWr_FromSR_error

; SRAM Section
TwiWr_FromSR_addr:
    tst    srcount                          ; compare(srcount, zero)
TwiWr_FromSR_addr_loop:
    breq   TwiWr_FromSR_exit                ; if (srcount == 0)
                                            ;     goto exit
    ld     r16,                X+           ; r16    = SRAM[X++]
    sts    TWIC_MASTER_DATA,   r16          ; DATA   = r16
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    dec    srcount                          ; srcount--
    brtc   TwiWr_FromSR_addr_loop           ; if (SREG_T == 0)
                                            ;     goto TwiWr_FromSR_addr_loop
    rjmp   TwiWr_FromSR_exit                ; goto exit

; Error Section - Ensures the data stack is empty before returning
TwiWr_FromSR_error:
    tst    dscount                          ; compare(dscount, zero)
TwiWr_FromSR_popall:
    breq   TwiWr_FromSR_exit                ; if (dscount == 0)
                                            ;     goto exit
    popd   r16                              ; r16 = DS[Y++]
    dec    dscount                          ; dscount--
    rjmp   TwiWr_FromSR_popall              ; goto TwiWr_FromSR_popall

TwiWr_FromSR_exit:
;   Generate STOP condition
    stsi   TWIC_MASTER_CTRLC,  TWI_MASTER_CMD_STOP_gc

   .undef  slaw
   .undef  dscount
   .undef  srcount

    pop    XH
    pop    XL
    pop    r23
    pop    r22
    pop    r19
    pop    r16
    ret


; TwiWr_Register                                                      21May2020
; -----------------------------------------------------------------------------
; Description:
;     Writes to a single TWI-connected device register.
;
;     Establishes a connection with the device, transmits the register address,
;     then the register value.
; Parameters:
;     r20          - device SLA+W
;     r21          - register address
;     r22          - register value
; General-Purpose Registers:
;     Parameters   - r20, r21, r22
;     Modified     - 
; I/O Registers Affected:
;     TWIC_MASTER_ADDR
;     TWIC_MASTER_CTRLC
;     TWIC_MASTER_DATA
; Functions Called:
;     SREG_T = TwiWr_Wait ( )
; Returns:
;     SREG_T - success (0) or error (1)
TwiWr_Register:
    push   r18

   .def    command = r18                    ; CTRLC register command
   .def    slaw    = r20                    ; param: SLA+W
   .def    regaddr = r21                    ; param: register address
   .def    regval  = r22                    ; param: register value

    sts    TWIC_MASTER_ADDR,   slaw         ; ADDR   = SLA+W
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiWr_Register_exit

    sts    TWIC_MASTER_DATA,   regaddr      ; DATA   = regaddr
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()
    brts   TwiWr_Register_exit

    sts    TWIC_MASTER_DATA,   regval       ; DATA   = regval
    rcall  TwiWr_Wait                       ; SREG_T = TwiWr_Wait()

TwiWr_Register_exit:
;   Generate STOP condition
    ldi    command,     TWI_MASTER_CMD_STOP_gc
    sts    TWIC_MASTER_CTRLC,  command

   .undef  command
   .undef  slaw
   .undef  regaddr
   .undef  regval

    pop    r18

    ret



; TwiWr_Wait                                                          21May2020
; -----------------------------------------------------------------------------
; Description:
;     Waits for STATUS.WIF to be set. Checks ARBLOST, BUSERR, and RXACK, and
;     then returns SREG_T to indicate success (0) or error (1).
;
;     Success is defined as:
;         - WIF     = 1, and
;         - ARBLOST = 0, and
;         - BUSERR  = 0, and
;         - RXACK   = 0 (ACK)
; Parameters:
;     None.
; General-Purpose Registers:
;     Parameters - 
;     Modified   - 
; Constants:
;     TWIM_WRFLAGS_bm    -  (0b_0001_1100)  RXACK, ARBLOST, and BUSERR flags
; Returns:
;     SREG_T - success (0) or error (1)
; Note:
;     If STATUS.WIF is never set, this function never returns.
;     Just so you know.
TwiWr_Wait:
    push   r16

TwiWr_Wait_wait:
    lds    r16,    TWIC_MASTER_STATUS       ; r16 = STATUS
    sbrs   r16,    TWI_MASTER_WIF_bp        ; if (WIF == 0)
    rjmp   TwiWr_Wait_wait                  ;     goto _wait

    andi   r16,    TWIM_WRFLAGS_bm          ; if (WRFLAGS == 0)
    breq   TwiWr_Wait_exit                  ;     success: goto _exit
                                            ; else
    set                                     ;     error: SREG_T = 1

TwiWr_Wait_exit:

    pop    r16
    ret