CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

PIC24 PCD compiler and MPLAB X compiler I2C?

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
allenhuffman



Joined: 17 Jun 2019
Posts: 643
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PIC24 PCD compiler and MPLAB X compiler I2C?
PostPosted: Thu Oct 02, 2025 4:01 pm     Reply with quote

I have been trying to make common source files that work with our CCS PCD projects and a new one that is using the MPLAB X compiler.

I have been unable to find examples of this "Foundation Services Library" I2C code, and neither A.I. I have tried to use has produced code that fully works.

Original:

Code:
bool AD5272WriteCommandData (unsigned int address, uint8_t command, uint16_t data)
{
    bool status = false;
    uint16_t value = (((uint16_t)command & AD5272_CMD_MASK) << 10) | (data & AD5272_DATA_MASK);

    //AD5272_PRINTF ("AD5272WriteCommandData (0x%x, 0x%x, 0x%x)\r\n", address, command, data);

#if defined(__PCD__) // CCS PCD compiler.
    unsigned int ackStatus = 0;

    // Send start condition
    i2c_start (AD5272_I2C_STREAM);
   
    // Send address with write bit (0)
    ackStatus = i2c_write (AD5272_I2C_STREAM, address << 1);
    if (ackStatus == 0)
    {
        // ACK'd, so something is there, continue writing.
        i2c_write (AD5272_I2C_STREAM, value >> 8);
        i2c_write (AD5272_I2C_STREAM, value & 0xff);
       
        status = true;
    }
   
    // Send stop condition
    i2c_stop (AD5272_I2C_STREAM);

#else // assume MPLAB X compiler.

     ...snip...

#endif

    return status;
}


Does anyone here work with both systems?

The code that was presented to me in MPLABs uses "void i2c2_waitForEvent(uint16_t*);" over there, and looks like it may require some steps to have interrupts working.

I am using a PIC24FJ1024GA606 for this project, but I am happy with a polled I2C approach like we do in CCS. This MPLAB code looks like it wants interrupts, since that wait function is looking at slave and master IRQ bits.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?

Using: 24FJ256GA106, 24EP256GP202 and 24FJ64GA002.
allenhuffman



Joined: 17 Jun 2019
Posts: 643
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Fri Oct 03, 2025 11:36 am     Reply with quote

Wow, the "Foundation Services" functions in MPLAB are basically just C wrappers to I2C register calls. I've chatted with Bing Copilot, Google Gemini, and even Claude and none of them can produce working I2C code.

I have learned you need to manually clear IRQs (polled mode) and such, so I can at least see traffic and get responses from the chip, but it leaves the bus in a weird state according to the Saleae Logic captures I have done.

"The grass is always greener on the other side."

This is a good example of how easy the CCS routines are compared to what this MPLAB stuff seems to require -- and the MPLAB stuff is still not working the same way:

Code:
uint16_t AD5272ReadCommandData (unsigned int address, uint8_t command, uint16_t data)
{
    uint16_t retValue = 0;
    uint16_t value = ((command & AD5272_CMD_MASK) << 10) | (data & AD5272_DATA_MASK);

    //AD5272_PRINTF ("AD5272ReadCommandData (0x%x, 0x%x, 0x%x)\r\n", address, command, data);

#if defined(__PCD__) // CCS PCD compiler.
    unsigned int ackStatus = 0;
   
    // Send start condition
    i2c_start (AD5272_I2C_STREAM);
   
    // Send address with write bit (0)
    ackStatus = i2c_write (AD5272_I2C_STREAM, address << 1);
    if (ackStatus == 0)
    {
        // ACK'd, so something is there, continue writing.
        uint8_t msb = 0;
        uint8_t lsb = 0;

        i2c_write (AD5272_I2C_STREAM, value >> 8); // MSB
        i2c_write (AD5272_I2C_STREAM, value & 0xff); // LSB

        // Send repeated start condition.
        i2c_start (AD5272_I2C_STREAM);
       
        // Send address with write bit (1)
        i2c_write (AD5272_I2C_STREAM, (address << 1) | 1);
       
        msb = i2c_read (AD5272_I2C_STREAM, 1); // ACK
        lsb = i2c_read (AD5272_I2C_STREAM, 0); // No ACK

        retValue = (msb << 8) | lsb;
    }
   
    // Send stop condition
    i2c_stop (AD5272_I2C_STREAM);

#else // assume MPLAB X compiler.

    // i2c2_driver_driver_open() is done during system startup.
    unsigned int ackStatus = 0;
    uint16_t event;

    i2c2_driver_clearBusCollision ();
    i2c2_driver_resetBus (); // Currently an empty function.
    i2c2_clearIRQ ();        // Clear the interrupt flag
   
    // Send start condition
    i2c2_driver_start ();
    i2c2_waitForEvent (&event); // Wait for the event flag
    i2c2_clearIRQ ();         // Clear the interrupt flag

    i2c2_driver_TXData (address << 1);  // Address + Write
    i2c2_waitForEvent (&event); // Wait for the event flag
    i2c2_clearIRQ ();         // Clear the interrupt flag
    if (!i2c2_driver_isNACK())
    {
        ackStatus = true;

        // ACK'd, so something is there, continue writing.
        i2c2_driver_TXData (value >> 8); // MSB
        i2c2_waitForEvent (&event); // Wait for the event flag
        i2c2_clearIRQ ();         // Clear the interrupt flag

        i2c2_driver_TXData (value & 0xFF); // LSB
        i2c2_waitForEvent (&event); // Wait for the event flag
        i2c2_clearIRQ ();        // Clear the interrupt flag

        // Send repeated start condition.
        i2c2_driver_restart ();
        i2c2_waitForEvent (&event); // Wait for the event flag
        i2c2_clearIRQ ();         // Clear the interrupt flag

        // Send address with write bit (1)
        i2c2_driver_TXData ((address << 1) | 1);
        i2c2_waitForEvent (&event); // Wait for the event flag
        i2c2_clearIRQ ();         // Clear the interrupt flag
    }
   
    // Only proceed to read if the initial write was ACK'd AND the Read address was ACK'd
    if (ackStatus && !i2c2_driver_isNACK())
    {
        uint8_t msb = 0;
        uint8_t lsb = 0;

        // Prepare to receive
        // BSET.B  I2C2CON.RCEN         ; Enable receive mode
        i2c2_driver_startRX (); // RCEN=1
        while (I2C2CONLbits.RCEN); // Wait for receive to complete
       
        // BCLR.B  I2C2CON.ACKDT        ; ...send ACK
        // BSET.B  I2C2CON.ACKEN        ; Send ACK/NACK
        i2c2_driver_sendACK (); // ACKDT=0, ACKEN=1
        // TODO: Wait for ACKDT/RCEN bits to clear?
        while(I2C2CONLbits.ACKEN);
       
        // BTSS.B  I2C2STAT.RBF         ; Wait for RBF (Receive Buffer Full)
        while (!i2c2_driver_isBuferFull ());
        // MOV     I2C2RCV,W0           ; Read received byte into W0
        msb = i2c2_driver_getRXData ();

        // Clear overflow if needed.
        if (I2C2STATbits.I2COV)
        {
            I2C2STATbits.I2COV = 0; // Clear overflow
        }

        i2c2_driver_startRX ();
        while (I2C2CONLbits.RCEN); // Wait for receive to complete

        i2c2_driver_sendNACK ();
        while(I2C2CONLbits.ACKEN); // ACKDT=1, ACKEN=0
       
        while (!i2c2_driver_isBuferFull ());
        lsb = i2c2_driver_getRXData ();
       
        if (I2C2STATbits.I2COV)
        {
            I2C2STATbits.I2COV = 0; // Clear overflow
        }
       
        retValue = (msb << 8) | lsb;
    }

    i2c2_driver_stop ();  // STOP condition
    i2c2_waitForEvent (&event); // Wait for the event flag
    i2c2_clearIRQ ();         // Clear the interrupt flag

    // Ensure the bus is fully released by polling the PEN/TRSTAT bits.
    while(I2C2CONLbits.PEN);
    while(I2C2STATbits.TRSTAT);   
#endif

    return retValue;
}

_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?

Using: 24FJ256GA106, 24EP256GP202 and 24FJ64GA002.
Ttelmah



Joined: 11 Mar 2010
Posts: 19957

View user's profile Send private message

PostPosted: Sat Oct 04, 2025 11:38 am     Reply with quote

One thing might give an issue on the MPLAB stuff you show. Remember that
on a read from a device, there will be an ACK, not a NACK on the last byte
what you show does not seem to handle this. Might be why it still has
problems.
allenhuffman



Joined: 17 Jun 2019
Posts: 643
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Sat Oct 04, 2025 8:17 pm     Reply with quote

Ttelmah wrote:
One thing might give an issue on the MPLAB stuff you show. Remember that
on a read from a device, there will be an ACK, not a NACK on the last byte
what you show does not seem to handle this. Might be why it still has
problems.


I gave up on that "Foundation Services" stuff. They are just wrappers to poll or set I2C bits. And, they are missing many of the ones you still need, so you do a command, then have to manually poll a bit to make sure it has completed. Stuff like that was way too much work -- might as well write my own!

They have a second I2C thing built in that gives basically two functions. It was a brain twister to figure out, and STILL has an issue with a phantom start/stop bit before a transmission. But, it can handles writing a basic message, or reading a basic message, or queueing up transactions so it will do a repeated start between them. It is ... interesting.

The grass is MUCH greener on the CCS side of the fence when it comes to I2C. I am hoping to figure this out and write up a blog post showing how to do all of this, since no one else seems to have done it.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?

Using: 24FJ256GA106, 24EP256GP202 and 24FJ64GA002.
allenhuffman



Joined: 17 Jun 2019
Posts: 643
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Mon Oct 06, 2025 8:01 am     Reply with quote

In case anyone else goes down this path, there are two methods of I2C provided in the "MCC" tool of MPLAB. The first one is basically C wrappers to register access, and it is missing many things you need so you still have to manually poll register bits (or so it seems).

The other is more complex, and lets you write a buffer or bytes, or read a buffer of bytes. If you want to write/repeated start/read, it uses some type of buffer system where you chain them together, then it does it automatically. Sadly, I cannot really find any documentation, and there is a phantom start/stop bit seen on my analyzer which is being done inside their routines for some reason. My CCS original code does not do that.

I wanted to share a simple example, in case it helps anyone else understand how much easier the CCS tool is. This is my work-in-progress test code:

Code:
bool AD5272WriteCommandData (unsigned int address, uint8_t command, uint16_t data)
{
    bool status = false;
    uint16_t value = (((uint16_t)command & AD5272_CMD_MASK) << 10) | (data & AD5272_DATA_MASK);

#if defined(__PCD__) // CCS PCD compiler.

    unsigned int ackStatus = 0;

    // Send start condition
    i2c_start (AD5272_I2C_STREAM);
   
    // Send address with write bit (0)
    ackStatus = i2c_write (AD5272_I2C_STREAM, address << 1);
    if (ackStatus == 0)
    {
        // ACK'd, so something is there, continue writing.
        i2c_write (AD5272_I2C_STREAM, value >> 8);
        i2c_write (AD5272_I2C_STREAM, value & 0xff);
       
        status = true;
    }
   
    // Send stop condition
    i2c_stop (AD5272_I2C_STREAM);

#else // assume MPLAB X compiler.

    /*volatile*/ I2C2_MESSAGE_STATUS status_mcc; // Must be volatile
    uint8_t writePayload[2];
   
    // 1. Pack the command and data (which is in 'value') into the buffer
    writePayload[0] = (uint8_t)(value >> 8);  // Command/Data MSB
    writePayload[1] = (uint8_t)(value & 0xFF); // Command/Data LSB

    // 2. Execute the entire transaction in one blocking call.
    // This single call performs: START, Address+W, Data TX (2 bytes), STOP.
    I2C2_MasterWrite(
        writePayload,           // Pointer to data to send
        sizeof(writePayload),   // Length of data (2 bytes)
        address,                // Slave address (7-bit, the function shifts it)
        &status_mcc             // Pointer to the status variable
    );
   
    // 3. Wait for the transaction to complete
    while(status_mcc == I2C2_MESSAGE_PENDING); // Requires interrupts!

    // 4. Check the final status
    if (status_mcc == I2C2_MESSAGE_COMPLETE)
    {
        while (I2C2CONLbits.PEN);   // Wait for STOP to finish
        // This implicitly means the Address and Data were ACK'd
        status = true;
    }
    else // status is I2C2_MESSAGE_ADDRESS_NO_ACK or I2C2_MESSAGE_FAIL, etc.
    {
        I2C2CONLbits.PEN = 1;           // Force STOP
        while (I2C2CONLbits.PEN);       // Wait for STOP to complete
    }

#endif

    return status;
}


uint16_t AD5272ReadCommandData (unsigned int address, uint8_t command, uint16_t data)
{
    uint16_t retValue = 0;
    uint16_t value = ((command & AD5272_CMD_MASK) << 10) | (data & AD5272_DATA_MASK);

#if defined(__PCD__) // CCS PCD compiler.

    unsigned int ackStatus = 0;
   
    // Send start condition
    i2c_start (AD5272_I2C_STREAM);
   
    // Send address with write bit (0)
    ackStatus = i2c_write (AD5272_I2C_STREAM, address << 1);
    if (ackStatus == 0)
    {
        // ACK'd, so something is there, continue writing.
        uint8_t msb = 0;
        uint8_t lsb = 0;

        i2c_write (AD5272_I2C_STREAM, value >> 8); // MSB
        i2c_write (AD5272_I2C_STREAM, value & 0xff); // LSB

        // Send repeated start condition.
        i2c_start (AD5272_I2C_STREAM);
       
        // Send address with write bit (1)
        i2c_write (AD5272_I2C_STREAM, (address << 1) | 1);
       
        msb = i2c_read (AD5272_I2C_STREAM, 1); // ACK
        lsb = i2c_read (AD5272_I2C_STREAM, 0); // No ACK

        retValue = (msb << 8) | lsb;
    }
   
    // Send stop condition
    i2c_stop (AD5272_I2C_STREAM);

#else // assume MPLAB X compiler.

    /*volatile*/ I2C2_MESSAGE_STATUS status_mcc = I2C2_MESSAGE_PENDING;
    I2C2_TRANSACTION_REQUEST_BLOCK combinedTRB[2]; // Two TRBs needed for W/R
   
    // Data buffer for the two bytes we WRITE (Command + Data MSB/LSB)
    uint8_t writePayload[2];
   
    // Data buffer for the two bytes we READ (MSB and LSB)
    uint8_t readPayload[2] = {0, 0};

    // 1. Pack the command and data (from 'value') into the write buffer
    writePayload[0] = (uint8_t)(value >> 8);  // Command/Data MSB
    writePayload[1] = (uint8_t)(value & 0xFF); // Command/Data LSB

#if 1 // Faster way (repeated start)
    // --- Write Phase TRB (TRB [0]) ---
    // This TRB performs: START, Address+W, Data TX (2 bytes)
    I2C2_MasterWriteTRBBuild(
        &combinedTRB[0],      // TRB structure
        writePayload,         // Pointer to data to send
        sizeof(writePayload), // Length of data (2 bytes)
        address               // Slave address
    );

    // --- Read Phase TRB (TRB [1]) ---
    // This TRB performs: Repeated START, Address+R, Data RX (2 bytes), NACK, STOP
    I2C2_MasterReadTRBBuild(
        &combinedTRB[1],      // TRB structure
        readPayload,          // Pointer to data buffer to receive bytes
        sizeof(readPayload),  // Length of data (2 bytes)
        address               // Slave address
    );

    // 2. Insert the two TRBs into the queue as one sequential transaction
    I2C2_MasterTRBInsert(
        2,                    // count: 2 TRBs in the list
        combinedTRB,          // pointer to the TRB array
        &status_mcc           // pointer to the status variable
    );

    // 3. Wait for the entire transaction to complete
    while(status_mcc == I2C2_MESSAGE_PENDING);

    // 4. Check the final status and compile the result
    if (status_mcc == I2C2_MESSAGE_COMPLETE)
    {
        while (I2C2CONLbits.PEN);   // Wait for STOP to finish
       
        // Data has been successfully read into readPayload[0] (MSB) and readPayload[1] (LSB)
        retValue = (readPayload[0] << 8) | readPayload[1];
    }
    else // status is I2C2_MESSAGE_ADDRESS_NO_ACK or I2C2_MESSAGE_FAIL, etc.
    {
        I2C2CONLbits.PEN = 1;           // Force STOP
        while (I2C2CONLbits.PEN);       // Wait for STOP to complete
    }
#else // Slower way (two separate start/stop transactions)
    // Write phase
    I2C2_MasterWrite(writePayload, 2, address, &status_mcc);
    while (status_mcc == I2C2_MESSAGE_PENDING);
    if (status_mcc != I2C2_MESSAGE_COMPLETE) return 0;

    while (I2C2CONLbits.PEN);   // Wait for STOP to finish

    // Read phase
    status_mcc = I2C2_MESSAGE_PENDING;
    I2C2_MasterRead(readPayload, 2, address, &status_mcc);
    while (status_mcc == I2C2_MESSAGE_PENDING);
    if (status_mcc == I2C2_MESSAGE_COMPLETE)
    {
        while (I2C2CONLbits.PEN);   // Wait for STOP to finish

        retValue = (readPayload[0] << 8) | readPayload[1];
    }
    else // status is I2C2_MESSAGE_ADDRESS_NO_ACK or I2C2_MESSAGE_FAIL, etc.
    {
        I2C2CONLbits.PEN = 1;           // Force STOP
        while (I2C2CONLbits.PEN);       // Wait for STOP to complete
    }
#endif // fast or slow
   
#endif

    return retValue;
}


The MPLAB routine was generated by one of the AIs, and works, but inside the MPLAB functions something is creating a phantom start/stop bit that my analyzer sees. I do not know how robust these routines are.

It also requires interrupts and has a handler that sets some flag which you will see polled in this code.

Anyone else tried this?
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?

Using: 24FJ256GA106, 24EP256GP202 and 24FJ64GA002.
gaugeguy



Joined: 05 Apr 2011
Posts: 349

View user's profile Send private message

PostPosted: Mon Oct 06, 2025 9:20 am     Reply with quote

Software routines generated be AI are 'A' but definitely not 'I'
temtronic



Joined: 01 Jul 2010
Posts: 9585
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Mon Oct 06, 2025 2:00 pm     Reply with quote

I treat AI the same as I do 'simulators' and 'wizards'.....
Not worth wasting MY time on them...
allenhuffman



Joined: 17 Jun 2019
Posts: 643
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Mon Oct 06, 2025 2:11 pm     Reply with quote

temtronic wrote:
I treat AI the same as I do 'simulators' and 'wizards'.....
Not worth wasting MY time on them...


When they work, they are magic.

But the other 90%...

What stuns me is how I can find no documentation, no forum posts, etc. about how this stuff works -- other than posts from others also asking how they work ;-)
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?

Using: 24FJ256GA106, 24EP256GP202 and 24FJ64GA002.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group