• AVR Freaks

Helpful ReplyHot!New I2C module - more difficulty - PIC18F47K42

Author
swmcl
Super Member
  • Total Posts : 271
  • 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 : 9999
  • 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 : 271
  • 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 : 9999
  • 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 : 271
  • 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
USNA79
  • Total Posts : 1551
  • 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;
    }
}

 

Go Navy! Beat Army!
#6
swmcl
Super Member
  • Total Posts : 271
  • 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 : 24150
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
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
USNA79
  • Total Posts : 1551
  • 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.

Go Navy! Beat Army!
#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 : 239
  • 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
MarPICnus
New Member
  • Total Posts : 25
  • Reward points : 0
  • Joined: 2018/08/16 11:13:00
  • Location: 0
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/10/16 03:43:00 (permalink)
0
Hello People
 
I'm using the same PIC18F47k42 for I2C, also using MCC functions.
My problem is that my ACKSTAT bit never goes high, so there is no way to check when it falls low after a SLAVE ACK on its address.
 
I'm in Master mode
 
Is there some setting to force ACKSTAT high? 
 
   ////////////// My I2C INIT /////////////////
void I2C1_Initialize(void)
{
if(!I2C1CON0bits.EN || lastError != I2C1_GOOD)
{
lastError = I2C1_GOOD;
// CSTR Enable clocking; S Cleared by hardware after Start; MODE 7-bit address; EN disabled; RSEN disabled;
I2C1CON0 = 0x04;
// TXU 0; CSD Clock Stretching enabled; ACKT 0; RXO 0; ACKDT Acknowledge; ACKSTAT ACK received; ACKCNT Not Acknowledge;
I2C1CON1 = 0x80;
// ABD enabled; GCEN disabled; ACNT disabled; SDAHT 300 ns hold time; BFRET 8 I2C Clock pulses; FME = 1;
I2C1CON2 = 0x24;
// CLK TMR6 post scaled output;
I2C1CLK = 0x08;
I2C1PIR = 0;// ;Clear all the error flags
I2C1ERR = 0;
// Enable I2C module
I2C1CON0bits.EN = 1;
}
}
 
//////////////////The ACK I want to test//////////////
 
wait4BusFree();
I2C1ADB1 = MMIRdAddr;                  
I2C1CNT = 1;                                 
I2C1CON0bits.RSEN = 0;                
I2C1CON0bits.S = 1;                       // Start
wait4Start();

if (!I2C1CON1bits.ACKSTAT)             // Here I want to test for the ACK from slave to determine if slave is connected                                                          or not, because user can unplug slave device. 
{
CommsArr[0] = SlaveRdAddr;
CommsArr[1] = receiveByte();
/* Settings Adjust */
if (CommsArr[1] == 0x33)
{
.......rest of code
 
Any guidance will be great
Marinus
 
#13
ric
Super Member
  • Total Posts : 24150
  • Reward points : 0
  • Joined: 2003/11/07 12:41:26
  • Location: Australia, Melbourne
  • Status: online
Re: New I2C module - more difficulty - PIC18F47K42 2019/10/16 05:32:06 (permalink)
+1 (1)
Did you read post#4 ?
 

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!
#14
mbrowning
USNA79
  • Total Posts : 1551
  • Reward points : 0
  • Joined: 2005/03/16 14:32:56
  • Location: Melbourne, FL
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/10/16 07:35:12 (permalink)
+1 (1)
MarPICnus
My problem is that my ACKSTAT bit never goes high, so there is no way to check when it falls low after a SLAVE ACK on its address.
I2C1CON0bits.S = 1;                       // Start
wait4Start();
if (!I2C1CON1bits.ACKSTAT)             // Here I want to test for the ACK from slave to determine if slave is connected 

You need to read the datasheet not just blindly try stuff. ACKSTAT is meaningless and can be any value until the address byte is shifted out and the ACK bit has been shifted in. Then the ACKSTAT bit will reflect what was received from the slave.
 
You need to wait until TXBE is high, or some other clue that ACKSTAT has meaning.
 
The beauty of the K42 I2C peripheral is that you can set up an entire 1 to 3 byte write, set the S bit, and wait until it's all done when MMA goes low and then check ACKSTAT to see if acknowledge was received. If any byte is not ACK'd, the peripheral ends the transmission at that point.
 
 

Go Navy! Beat Army!
#15
MarPICnus
New Member
  • Total Posts : 25
  • Reward points : 0
  • Joined: 2018/08/16 11:13:00
  • Location: 0
  • Status: offline
Re: New I2C module - more difficulty - PIC18F47K42 2019/10/16 08:08:31 (permalink)
0
Thank you for the input, I will implement it tomorrow.
#16
Jump to:
© 2019 APG vNext Commercial Version 4.5