Search code examples
c++raspberry-pi3i2c

How do you write a line to an I2C device with a command and data bits for an MCP4452


I am trying to modify a piece of code originally designed for an AD5245 to use it for an MCP4452. Both are I2C devices on a Raspberry PI. I'm trying to figure out how to formulate the write code to the device based on the documentation in the image/enter image description here

enter image description here

How do I incorporate all those into something like this sample code for an AD5245?

static void writeAD5245(uint8_t i2cMCPAddress, uint8_t mcpdevice, uint8_t value) {
  beginMCP(i2cMCPAddress);
  uint8_t lsb = (uint8_t)(value >> 8);
  uint8_t msb = (uint8_t)value;
  uint16_t payload = (msb << 8) | lsb;
  i2c_smbus_write_word_data(i2cMCPHandle, MCP_WRITE, payload);
  endMCP();
}

Full datasheet: MCP4452 Datasheet

Edit to include full .h and .cpp files.

mcp4452.h

#include <sys/ioctl.h>
#include <unistd.h>
extern "C" {
    #include <linux/i2c.h>
   #include <linux/i2c-dev.h>
   #include <i2c/smbus.h>
}

#define MCP_ADDRESS     (0x2C)

#define MCP_READ            (0x01)
#define MCP_WRITE           (0x00)

#define NV_WIPER_0      (0x02)//10
#define NV_WIPER_1      (0x03)//11
#define NV_WIPER_2      (0x08)//1000
#define NV_WIPER_3      (0x09)//1001

#define MCP_CHANNEL_0   (0x00)//0
#define MCP_CHANNEL_1   (0x01)//1
#define MCP_CHANNEL_2   (0x06)//6
#define MCP_CHANNEL_3   (0x07)//7

#define MCP_CMD_WRITE   (0x00)//WRITE NEXT BYTE (SET VALUE)
#define MCP_CMD_INC     (0x01)//INCREMENT BY 1
#define MCP_CMD_DEC     (0x02)//DECREMENT BY 1
#define MCP_CMD_READ        (0x03)//READ DATA

#define MCP_RESET           (0xFF)//RESET DEVICE

class MCP4452
{
    protected:
        uint8_t   m_i2cMCPAddress;
        uint8_t   m_MCPbitShift;
        uint8_t  m_MCPChannel;
        uint8_t  m_MCPCommand;
    public:
        MCP4452(uint8_t i2cMCPAddress = MCP_ADDRESS);
        uint8_t setWPOS(uint8_t channel, uint8_t wiper_position, int command, int delay);
    private:
};

mcp4452.cpp:

#include "mcp4452.h"
#include <iostream>
#include <bitset> //CAN BE REMOVED AFTER FUNCTIONING

int i2cMCPHandle;

static void beginMCP(uint8_t i2cMCPAddress) {
  i2cMCPHandle = open("/dev/i2c-1", O_RDWR);
  if(i2cMCPHandle < 0)
  {
    fprintf(stderr, "Error while opening the i2c-2 device! Error: %s\n", strerror(errno));
    exit(1);
  }
  if(ioctl(i2cMCPHandle, I2C_SLAVE, i2cMCPAddress) < 0)
  {
    fprintf(stderr, "Error while configuring the slave address %d. Error: %s\n", i2cMCPAddress, strerror(errno));
    exit(1);
  }
}
static void endMCP(void) {
    close(i2cMCPHandle);
}
static void resetMCP(uint8_t i2cMCPAddress, int delay) {
    beginMCP(i2cMCPAddress);
   i2c_smbus_write_word_data(i2cMCPHandle, MCP_WRITE, MCP_RESET);
   endMCP();
}
static void setPOS(uint8_t i2cMCPAddress, uint8_t mcpchannel, uint8_t position) {
    beginMCP(i2cMCPAddress);
    uint16_t payload = (mcpchannel << 12) | (MCP_CMD_WRITE << 10) | (position);
    std::cout << "WRITE PAYLOAD: " << std::bitset<16>(payload) <<std::endl;
    i2c_smbus_write_word_data(i2cMCPHandle, MCP_WRITE, payload);
    endMCP();
}
static void incPOS(uint8_t i2cMCPAddress, uint8_t mcpchannel) {
    beginMCP(i2cMCPAddress);
    uint16_t payload = (mcpchannel << 12) | (MCP_CMD_INC << 10);
    std::cout << "INC PAYLOAD: " << std::bitset<16>(payload) <<std::endl;
    i2c_smbus_write_word_data(i2cMCPHandle, MCP_WRITE, payload);
    endMCP();
}
static void decPOS(uint8_t i2cMCPAddress, uint8_t mcpchannel) {
    beginMCP(i2cMCPAddress);
    uint16_t payload = (mcpchannel << 12) | (MCP_CMD_DEC << 10);
    std::cout << "DEC PAYLOAD: " << std::bitset<16>(payload) <<std::endl;
    i2c_smbus_write_word_data(i2cMCPHandle, MCP_WRITE, payload);
    endMCP();
}
static uint16_t readMCP(uint8_t i2cMCPAddress) {
    beginMCP(i2cMCPAddress);
    uint16_t res = i2c_smbus_read_word_data(i2cMCPHandle, MCP_READ);
    std::cout << "BYTES READ: " << std::bitset<8>(res >> 8) << std::endl;
    endMCP();
    return res;
}
MCP4452::MCP4452(uint8_t i2cMCPAddress) 
{
    m_i2cMCPAddress = i2cMCPAddress;
    m_MCPChannel = MCP_CHANNEL_0;
    m_MCPbitShift = 8;
    m_MCPCommand = MCP_CMD_WRITE;
}
uint8_t MCP4452::setWPOS(uint8_t channel, uint8_t wiper_position, int command, int delay) {
  if (wiper_position > 255 || wiper_position < 0)
  {
    std::cout << "Wiper Pos Error" <<std::endl;
    return -2;
  }
  if (channel!=0 && channel!=1 && channel!=2 && channel!=3) {
    std::cout << "Channel Error" <<std::endl;
    return -2;
  }
  switch (channel) {
    case 0:
        m_MCPChannel=MCP_CHANNEL_0;
        //m_MCPChannel= NV_WIPER_0;
        break;
    case 1:
        m_MCPChannel=MCP_CHANNEL_1;
        //m_MCPChannel= NV_WIPER_1;
        break;
    case 2:
        m_MCPChannel=MCP_CHANNEL_2;
        //m_MCPChannel= NV_WIPER_2;
        break;
    case 3:
        m_MCPChannel=MCP_CHANNEL_3;
        //m_MCPChannel= NV_WIPER_3;
        break;
  }
  std::cout << "m_i2cMCPAddress: " << std::bitset<8>(m_i2cMCPAddress) << "  ON CHANNEL: " << std::bitset<4>(m_MCPChannel) << " USING m_MCPCommand: " << std::bitset<8>(m_MCPCommand) << std::endl;
  int w = -1;
  if (command==0) {//WRITE WIPER POSITION
      setPOS(m_i2cMCPAddress, m_MCPChannel, wiper_position);
      usleep(delay);
      // Read the conversion results
      w = readMCP(m_i2cMCPAddress) >> m_MCPbitShift;
      std::cout << "FIRST READ VALUE: " << w << std::endl;
      usleep(delay);
      if (int(w)!=int(wiper_position)) {
            w = -1;
            int counter=0;
            while (int(w) != int(wiper_position) && counter<10) {
                counter++;
                w = -1;
                setPOS(m_i2cMCPAddress, m_MCPChannel, wiper_position);
                usleep(delay);
                w = readMCP(m_i2cMCPAddress) >> m_MCPbitShift;
                std::cout << "SECOND READ VALUE: " << w << std::endl;
            }
        }
    } else if (command==1) {//INCREMENT WIPER BY 1
        incPOS(m_i2cMCPAddress, m_MCPChannel);
        usleep(delay);
        w = readMCP(m_i2cMCPAddress) >> m_MCPbitShift;
       std::cout << "INC READ VALUE: " << w << std::endl;
    } else if (command==2) {//DECREMENT WIPER BY 1
        decPOS(m_i2cMCPAddress, m_MCPChannel);
        usleep(delay);
        w = readMCP(m_i2cMCPAddress) >> m_MCPbitShift;
       std::cout << "DEC READ VALUE: " << w << std::endl;
     } else if (command==3) {//RESET DEVICE
        resetMCP(m_i2cMCPAddress, delay);
        usleep(delay);
        w = readMCP(m_i2cMCPAddress) >> m_MCPbitShift;
        std::cout << "WIPER RESET " << w << std::endl;
    } else {
        std::cout << "INVALID COMMAND RECEIVED" << std::endl;
    }
  return w;
}

EDIT2: TEST PAYLOAD DATA AND RESULTS

0000 0000 0000 0000<<CHANNEL 1 SET TO 0--WORKS
0000 0000 1111 1111<<CHANNEL 1 SET TO 255--WORKS
0000 0100 0000 0000<<CHANNEL 1 INCREMENT--DOES NOT WORK
0000 1000 0000 0000<<CHANNEL 1 DECREMENT--DOES NOT WORK

0111 0000 0000 0000<<CHANNEL 4 SET TO 0--DOES NOT WORK
0111 0000 1111 1111<<CHANNEL 4 SET TO 255--DOES NOT WORK
0111 0100 0000 0000<<CHANNEL 4 INCREMENT--WORKS
0111 1000 0000 0000<<CHANNEL 4 DECREMENT--WORKS

Solution

  • Your code is not implementing the I²C write correctly.

    As seen in the datasheet and also other tutorials for working with an I²C bus it is important that for a write you send a third byte containing the data (applicable to a single byte write) during a write command.

    Thus,

    • Byte 1 - Control byte includes the device address and the R/W bit
    • Byte 2 - Command byte includes the data address and the command
    • Byte 3 - Data byte expected during a write command.

    From what I understand is that the <i2c/smbus.h> driver will sort the formatting out for you and you only need to provide the command byte and the data byte when writing.

    Thus, your method setPOS becomes:

    static void setPOS(uint8_t i2cMCPAddress, uint8_t mcpchannel, uint8_t position) {
        beginMCP(i2cMCPAddress);
        uint8_t command = mcpchannel << 4;          // Set the data address
        command = command | (MCP_CMD_WRITE) << 2;   // Set the command type
        
        std::cout << "WRITE COMMAND: " << std::bitset<8>(command) <<std::endl;
        std::cout << "WRITE DATA: " << std::bitset<8>(position) <<std::endl;
    
        // Changed from write *word* to write *byte* data
        i2c_smbus_write_byte_data(i2cMCPHandle, command, position);
        endMCP();
    }
    

    From the following test calls:

    setPOS(MCP_ADDRESS, MCP_CHANNEL_0, 0xFF);
    setPOS(MCP_ADDRESS, MCP_CHANNEL_1, 0xFF);
    

    Gives the following output:

    WRITE COMMAND: 00000000
    WRITE DATA: 11111111
    WRITE COMMAND: 00010000
    WRITE DATA: 11111111
    

    See the above example here.

    I have a good idea that this is the reason for your problem

    Odd thing is that now I am able to write a value (0-255) to the devices first wiper and I can increment/decrement (+1/-1) wipers 2-4, but I cannot write a value to wipers 2-4 and I cannot increment/decrement wiper 1.

    Just a note, from the datasheet I do see the MSB is set on p53:

    ‘1000 00d’b - Write Next Byte (Third Byte) to Volatile Wiper 0 Register.

    In the above example I did not set it. You should verify if it should be set / not.