• AVR Freaks

Helpful ReplyHot!New I2C module - more difficulty - PIC18F47K42

Author
swmcl
Super Member
  • Total Posts : 255
  • Reward points : 0
  • Joined: 2014/05/10 13:54:42
  • Location: Queensland
  • Status: offline
2018/08/30 20:02:10 (permalink)
0

New I2C module - more difficulty - PIC18F47K42

Hi,
I'm moving from PIC18F47K40 to PIC18F47K42.  The I2C module in the newer chip is trying to be smarter I think which means it is less than clear to me how it works and I think it has built in limitations that weren't there before...
To start out I'm going to try to use the module in Master mode and write one single little byte to a memory device.  Nice and simple.
So.
Looking at the datasheet on pp.567.
As I understand it, I should load up various registers with bits and pieces and let it rip with a start.  My problem is understanding how to test for an ACK.
If I give it the control byte of the external chip in I2C2ADB1 and I put the upper address byte in I2C2TXB as it says to do in the datasheet, how do I know it received an ACK after sending the first byte ??
 
I also do not understand what they are saying in using the line 'with R/W = 0'.  ???
 
This simple task of writing a byte is a total of 4 bytes.  The first is the control byte to address the external chip.  The second is a high address byte.  The third is a low address byte and the last is the data byte.
 
My as yet incomplete routine follows:

bool i2c2_write_byte_to_any_address(uint8_t control_byte, uint16_t write_address, uint8_t byte_to_write)
{
    bool device_response = true; // initialise to not-present value
    uint8_t upper_write_address_byte = 0;
    uint8_t lower_write_address_byte = 0;

    lower_write_address_byte = write_address & 0x00FF;
    write_address = write_address >> 8;
    upper_write_address_byte = write_address & 0x00FF;
    
    while(I2C2STAT0.BFRE = 0) // waiting for the I2C bus to become idle (it should be so already)
    {
        
    }
    
    I2C2CON2.ABD = 0;
    I2C2CNT = 0x04;
    I2C2ADB1 = control_byte;
    I2C2TXB = upper_write_address_byte;
    I2C2CON0bits.S = 1;  // send a start
    
}

 
I have managed previously to basically bit-bash my way around I2C then I started using the module in the PIC18F47K40 and now I'm using the module in this new chip.
 
My other issue here is that because the count register can't be disabled (I believe), does that mean I can never write a sequence of more than 256bytes on the I2C bus ??
 
Thanks in advance,
Steve
#1
qhb
Superb Member
  • Total Posts : 9998
  • Reward points : 0
  • Joined: 2016/06/05 14:55:32
  • Location: One step ahead...
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/30 20:15:05 (permalink)
+1 (1)
swmcl
...My other issue here is that because the count register can't be disabled (I believe), does that mean I can never write a sequence of more than 256bytes on the I2C bus ??

I'm seeing a note on page 550 saying:

If the block size of the message is greater than 255, the I2CxCNT register can be updated mid-transfer to prvent decrement to zero.


Nearly there...
#2
swmcl
Super Member
  • Total Posts : 255
  • Reward points : 0
  • Joined: 2014/05/10 13:54:42
  • Location: Queensland
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/30 21:14:45 (permalink)
0
Ahh.. I see that little note now too.  Thanks.
 
So in the code I sent, I am trying to send the control byte and then check for an ACK.  However, if I load up I2C2ADB1 and also I2C2TXB before sending a start won't it try to send both bytes ?  I assume then my task in the software will be to load up I2C2TXB again with bytes 3 and 4 before the end of transmission.
 
#3
qhb
Superb Member
  • Total Posts : 9998
  • Reward points : 0
  • Joined: 2016/06/05 14:55:32
  • Location: One step ahead...
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/30 21:20:47 (permalink)
0
There was another note saying if the address byte is NAKed, it will immediately send a STOP (or RESTART) and finish the transaction.
 

Nearly there...
#4
swmcl
Super Member
  • Total Posts : 255
  • Reward points : 0
  • Joined: 2014/05/10 13:54:42
  • Location: Queensland
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/30 22:35:07 (permalink)
0
So I'll rely on that then (?).  I guess I can't blow anything up.
 
If I check the ACKSTAT I can see on Figure 33-19 that it becomes relevant at the 9th bit and is pulled low on a successful ACK.  In the diagram I see a change in level for TXBE that is the trigger to reload I2C2TXB I think ...
 
That means I'm waiting for TXBE to change high and low before reloading I2C2TXB.
That seems a bit ugly.
#5
mbrowning
Just a Member
  • Total Posts : 1455
  • Reward points : 0
  • Joined: 2005/03/16 14:32:56
  • Location: Melbourne, FL
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/31 07:23:36 (permalink) ☄ Helpfulby guy_g 2019/07/18 01:57:24
+2 (2)
I haven't used I2C on PICs prior to K42, so I perhaps have an advantage over those with preconceived ideas of how it should work. I followed TB3159 and found it very quick and easy to produce working code. If I recall, there were a couple of minor bugs in the documentation, but nothing difficult to figure out.
 
Here's my generic I2C interface to peripherals. My code supports 1 byte writes (pulled from a 64entry circular buffer), and 1 or 2 byte reads, as I received read/write commands from an external controller. It's a state machine called in the main processing loop. I do check for ACKSTAT on reads, as well, since some other things besides NACK can set it. It has worked well so far.

void i2c_mach (void) {
    switch (i2c_st) {

    case i2c_next:
        if (i2c_scan_cmd) {
            i2c_st    = i2c_scan1;
        } else if (i2c_read_cmd) {
            i2c_read_cmd = 0;
            i2caddr    = (uint8_t)(i2c_rcbfr.addr << 1u);
            i2c_reg    = i2c_rcbfr.reg;
            i2c_cnt    = i2c_rcbfr.cnt;
            i2c_st    = i2c_rstart;
        } else if (i2c_hd != i2c_tl) {
            i2caddr = i2c_wcbfr[i2c_tl].addr;
            i2c_reg = i2c_wcbfr[i2c_tl].reg;
            i2c_val.bt[0] = i2c_wcbfr[i2c_tl].val;
            i2c_tl    = (i2c_tl + 1u) MOD_WCBFR_SIZE;
            i2c_st    = i2c_wstart;
        }
        break;
 
    case i2c_rstart:
        if (I2C1STAT0bits.MMA == 0) {    // if I2C not busy
            I2C1ADB1    = i2caddr;        // write operation first
            I2C1CNT        = 1;            // eeadr (0) + ptr (1)
            I2C1TXB        = i2c_reg;        // register ptr
            I2C1CON0bits.S    = 1;        // start the transfer
            CNT1IF        = 0;            // I don't think this is actually needed
            i2cfault    = 0;
            i2c_st        = i2c_rd;
        }
        break;
    case i2c_rd:
        if (I2C1STAT0bits.MMA == 0) {
            if (I2C1CON1bits.ACKSTAT == 1) {    // no ack received
                I2C1STAT1bits.CLRBF    = 1;        // clear buffer since no ack
                i2cfault= 1;
                i2c_st    = i2c_next;
            } else {
                I2C1ADB1    |= 1;            // read operation
                if (i2c_cnt == 2) {            // read 2 bytes
                    I2C1CNT        = 2;            // read 2 bytes
                    I2C1CON0bits.S    = 1;        // start the transfer
                    i2c_st        = i2c_datah;
                } else {                    // else read only 1
                    i2c_val.bt[1]    = 0;        // clear top byte
                    I2C1CNT            = 1;        // read 1 byte
                    I2C1CON0bits.S    = 1;        // start the transfer
                    i2c_st        = i2c_datal;
                }
            }
        }
        break;
    case i2c_datah:
        if (I2C1STAT1bits.RXBF) {
            if (I2C1CON1bits.ACKSTAT == 1) {    // no ack received
                I2C1STAT1bits.CLRBF    = 1;        // clear buffer since no ack
                i2cfault = 1;
                i2c_st    = i2c_error;        // error state?
            } else {
                i2c_val.bt[1]     = I2C1RXB;
                i2c_st            = i2c_datal;
            }
        }
        break;
    case i2c_datal:
        if (I2C1STAT1bits.RXBF) {
            if (I2C1CON1bits.ACKSTAT == 1) {    // no ack received
                I2C1STAT1bits.CLRBF    = 1;        // clear buffer since no ack
                i2cfault = 1;
                i2c_st    = i2c_error;        // error state?
            } else {
                i2c_val.bt[0]    = I2C1RXB;
                i2c_rdone        = 1;    
                i2c_st            = i2c_done;
            }
        }
        break;
    

    case i2c_wstart:
        if (I2C1STAT0bits.MMA == 0) {    // if I2C not busy
            I2C1ADB1    = i2caddr;        // write operation
            I2C1CNT        = 2;            // eeadr (0) + ptr (1) + data (1)
            I2C1TXB        = i2c_reg;        // register ptr
            I2C1CON0bits.S    = 1;        // start the transfer
            CNT1IF        = 0;
            i2cfault    = 0;
            i2c_st        = i2c_wr1;
        }
        break;
    case i2c_wr1:
        if (I2C1STAT1bits.TXBE) {
            if (I2C1CON1bits.ACKSTAT == 1) {    // no ack received
                I2C1CNT        = 0;
                I2C1STAT1bits.CLRBF    = 1;        // clear buffer since no ack
                i2cfault    = 1;
                i2c_st        = i2c_error;    
            } else {
                I2C1TXB        = i2c_val.bt[0];
                i2c_st        = i2c_wr2;
            }
        }
        break;
    case i2c_wr2:
        if (I2C1STAT0bits.MMA == 0) {
            if (I2C1CON1bits.ACKSTAT == 1) {    // no ack received
                I2C1CNT = 0;
                I2C1STAT1bits.CLRBF    = 1;        // clear buffer since no ack
                i2cfault    = 1;
                i2c_st    = i2c_error;    
            } else {
                i2c_st    = i2c_next;
            }
        }
        break;
        
        
    case i2c_error:
        if (i2cfault) {
            printf("i2c 0x%02x fault\n", (uint16_t)i2caddr);
            i2cfault = 0;
        }
        i2c_st = i2c_next;
        break;

    case i2c_done:
        if (i2c_rdone) {
            printf("i2c read a 0x%02x d 0x%02x\n",
                (uint16_t)i2caddr, i2c_val.wd);
            i2c_rdone = 0;
        }
        i2c_st = i2c_next;
        break;

    default:
        i2c_st    = i2c_next;
        break;
    }
}

 

Oh well - there's always next year
#6
swmcl
Super Member
  • Total Posts : 255
  • Reward points : 0
  • Joined: 2014/05/10 13:54:42
  • Location: Queensland
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2018/08/31 14:23:05 (permalink)
0
Thanks Mark,
I just checked in to the forum thinking here goes another day of headbanging ...
I like your signature too BTW.  Tis true that yesterday is forgotten... !  Each day is a reset and go it seems.
Age does that.
 
Anyways, I'll try to adapt what you have sent for this chip and see if I get anything.
Cheers,
Steve
#7
guy_g
New Member
  • Total Posts : 3
  • Reward points : 0
  • Joined: 2011/02/10 07:19:13
  • Location: 0
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/07/18 02:32:48 (permalink)
0
Thanks Mark,
Your various posts helped me get an i2c master working. For others, keeping ACKCNT=1 so recieves are terminated correctly, and clearing PCIF before each byte sent seems to fix the MCC-generated code.
 
I noticed you have a state 'i2c_scan1' - have you got a slave address scan to work?
 
I'm unable to get the module to repeatedly:
   - transmit an address
   - wait for ACK
   - send a Stop
It's happy to do it once; depending on how I set it up either sends NACK immediatedly after the address (because ACKCNT=1 and I2C1CNT has reached 0), or waits forever (because I don't know how to tell the module to quit waiting for ACK and send a Stop).
Any ideas how to force a Stop while waiting for ACK?
 
Cheers
Guy
#8
ric
Super Member
  • Total Posts : 23163
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/07/18 02:54:29 (permalink)
-1 (1)
You don't "wait for an ACK".
During the ninth clock pulse, the slave either drives SDA low (ACK) or it doesn't (NAK).
 
The only time you would "wait for an ACK" is when you are talking to an EEPROM, and after doing a write operation you can poll for "write completion" by sending zero length packets over and over. IUt will NAK them all until the write is complete.
I haven't used your PIC device, but the underlying structure of how I2C works doesn't change.

I also post at: PicForum
Links to useful PIC information: http://picforum.ric323.co...opic.php?f=59&t=15
NEW USERS: Posting images, links and code - workaround for restrictions.
To get a useful answer, always state which PIC you are using!
#9
mbrowning
Just a Member
  • Total Posts : 1455
  • Reward points : 0
  • Joined: 2005/03/16 14:32:56
  • Location: Melbourne, FL
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/07/18 03:25:52 (permalink) ☄ Helpfulby guy_g 2019/07/19 01:00:51
0
I do have a couple states I didn’t include above that scan all addresses. Basically like the first read or write state with CNT=0 and the next state reporting good addresses and incrementing the address

I’ve also added a receive command buffer and receive data returned to a pointer for arbitrary length reads.

One design continuously scans a bunch of IOexpanders and cross-point switches for status changes. All the IOx and switches require initialization. It’s nice to stack up a slew of commands and let the i2C state machines (both ports) do their thing in the background while other work gets done. Poor man’s multitasking.

Oh well - there's always next year
#10
guy_g
New Member
  • Total Posts : 3
  • Reward points : 0
  • Joined: 2011/02/10 07:19:13
  • Location: 0
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/07/18 09:15:51 (permalink)
0
Thanks Matt,
Got it working, I must have been doing something daft.
For anyone curious to probe one slave address, my blocking function inserted into MCC-generated i2c1.c:

uint8_t i2c1_probe(uint8_t address) {
    uint8_t timeOut = I2C1_TIMEOUT_US;
    if (i2c1_error)        i2c1_init();
    if (address < 0x08 || address > 0x77)    return 0;    // restricted addresses
    I2C1ADB1 = (uint8_t)(address << 1);    // Write to address
    I2C1CNT = 0;                        // 0 bytes to send; I2C1TXB is empty
    I2C1PIRbits.PCIF = 0;                // always clear flag before Start
    I2C1CON0bits.S = 1;
    wait4Stop();                        // finished.
    return !(I2C1CON1bits.ACKSTAT);    // true if slave ACKed
}

 
cheers
Guy
#11
Les
Super Member
  • Total Posts : 237
  • Reward points : 0
  • Joined: 2011/02/23 04:27:28
  • Location: UK
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/07/21 14:48:49 (permalink)
#12
Jump to:
© 2019 APG vNext Commercial Version 4.5