Help understanding I2C register addresses

Hi fellow tinkerers!

I'm stuck in this situation: I have this chip (https://www.mouser.com/datasheet/2/302/mc44cc373-1188207.pdf) and I have to change bits SFD1 and SFD0 (to 0 and 1 respectively) using I2C, but I have little experience with this kind of operation (and I only have access to one of this chips, so I really can't screw up!). I know that I need the address of the device itself (according to the datasheet, it should be 0xCA, correct me if I'm wrong, and in that case, if you can tell me why, it would be really helpful for learning), but I think I also need an address for the register itself, as to not erroneously write over a different set of bits.
Is this address not provided on the datasheet or am I missing something?

Thanks in advance for your time! :smile:

I2C addresses can be 7 or 8 bits. The lowest bit indicates a read or write operation. The Arduino library uses 7 bit addressing with the low bit set by the read or write operation. To get the 7 bit address shift the 8 bit address in the data sheet right one bit. In this case you would use 0x65.

1 Like

So, using Wire library it would be like this?:

#include <Wire.h>

void setup() {
  Wire.begin();

  uint8_t newBits = (B00000100);

  Wire.beginTransmission(0x65);
  Wire.write(newBits);
  Wire.endTransmission();
}

The address of the device itself, shifted, is what points to that specific register? That's the part that's still not clear for me, how do I know that I'm just writing to those specific bits?

Aah, so I have to specify the address of the device itself (in my case, seems to be 0xCA), and then using "Wire.write(B..." send the fifteen bits?

I make it that it should be using the address 0x65 or 0x67 depending on the single address line A1 being high or low when you communicate with it.

You see there are two ways of specifying an I2C address, as an 8 bit address which includes the read/write line as the least significant bit or as a 7 bit address which has separate read/write instructions. The Arduino uses this latter convention, and the data sheet uses the former convention.

However there is not an address select pin on the actual device so as page 9 of the data sheet says:-

Since the I2C bus is a two-wire bus that does not have a separate chip-select line, each IC on the bus has a unique address. This address must be sent each time an IC is communicated with. The address is the first seven bits that are sent to the IC as shown in Table 9. The eighth bit sent is the R/W bit, it determines whether the master will read from or write to the IC.

So that address is used each time you talk to the chip, so it becomes the preamble to any communications with any of the internal register. That is you start with that address defining value and follow it up with the register you want to communicate with. This is the STA value that is mentioned in the examples.

Page 11 of the data sheet clearly shows what each of the internal registers do.

However, there is a lot more you need to do with the hardware. First off you need to set the I2C to a high speed mode which is 800KHz. Then what sort of Arduino are you using? Is it 3V3 or 5V? What are you doing with pin 3? What I2C pull up resistor values do you have, and what voltage are they pulling to?

Edit:- I started answering this before there were any replies. When I posted I saw the other 8 posts.

1 Like

I'm using an Arduino Uno, 5V. I still haven't connected anything yet, as I thought it would be better for me to understand the code part better before even going to the physical wiring. I would be connecting the chip to the Arduino just to change those bits (so all the other pins are free, there's nothing connected, except for the 5V power and GND).

#include <Wire.h>

byte deviceAddress = 0x65; //Is this ok?
byte size = 16;

void setup() {
  Wire.begin();
  Wire.setClock(800000); //Set clock to 800KHz
  Wire.requestFrom(deviceAddress, size);

  Serial.begin(9600); // Initialize the serial communication
}

void loop() {
  // Check if data is available from the I2C device
  if (Wire.available()) {
    while (Wire.available()) {
      // Read and print each byte received
      char receivedByte = Wire.read();
      Serial.print(receivedByte, HEX); // Print in hexadecimal format
      Serial.print(" "); // Add a space between bytes
    }
    Serial.println(); // Print a newline after all bytes are printed
  }
}

This code should allow me to read the bits and show them on the Serial monitor, as to give me an idea of what I'm going to do next, right?

On first reading of the datasheet, here's my impressions:

Depends. It's correct for MC44CC373CA, MC44CC374CA and MC44CC374T1A. For MC44CC373CAS it'd be 0x67.

No. In read mode, you only ever get 1 byte back, the status byte. So it would be byte size = 1;.

And I'd set the clock to 100KHz. 800KHz is the maximum the chip supports. No sense pushing things for a single byte.

1 Like

So, how would I read the whole register to the serial monitor? This would be for learning purposes, and to get the bits already there so that later I can change SFD1 and SFD0 only (leaving the rest untouched).

You can't read any of the programming bits, only the status bits. You'll have to figure out what the C1 and C0 bytes need to be and program all the bits in them. There is no mechanism to read a programming byte (what an awkward term!), change some bits, and write the modified value back. Look at Table 11, example 1 for what you have to do. It'll be something like:

    byte data[2];
    data[0] = 0x55; // C1 value - just an example
    data[1] = 0xAA; // C0 value - just an example
    Wire.beginTransmission(deviceAddress);  // Transmit to device
    Wire.write(val, sizeof val);             // Sends C1 and C0 bytes
    Wire.endTransmission(true);      // Stop transmitting

I tried to write the code for just changing those two bits (SFD1 to 0 and SFD0 to 1), but I just don't know how to keep bits that are already there intact. How would the actual code be?

#include <Wire.h>

byte deviceAddress = 0x65;

void setup() {
byte data[4]; //Do I need to write all the bytes up to the one that includes the "SFD" bits?
data[0] = 0xXX; //How do I keep the data that was already there?
data[1] = 0xXX; //How do I keep the data that was already there?
data[2] = 0xXX; //How do I keep the data that was already there?
data[3] = 0x68; //If I'm not mistaken, this should set both SFD bits as expected (but it would be nice to don't even touch the other bits here)
Wire.beginTransmission(deviceAddress);
Wire.write(val, sizeof val); //This should be "data"?
Wire.endTransmission(true);      // Stop transmitting
}

You can't keep or read the values that were already there. You have to program all the bits in both C1 and C0. So you have to figure out what they need to be.

Repeat that to yourself as many times as you need to. That's the situation you're stuck with.

Go back and read what @Delta_G wrote. They're not registers, so don't think of them like that. They're commands. The MSb of the first byte, and the number of bytes, determine which command format is used. You have 2 byte commands, and 4 byte commands. And you have to set or reset each data bit in each byte of the command appropriately.

On the bright side, since you're only really interested in setting/resetting SFD1 and SFD0, you really only have to figure out 4 more bits, assuming you want what the datasheet calls "normal operation": PS, SYSL, PWC and SREF. Everything else will be 0, with the exception of the MSb of C1, which will be 1.

1 Like

But he later said that they are registers, so I thought there was a way of reading them, I still find it confusing.

The status byte could be considered a register. But since it's the only thing you can read, you could just as easily call it a status byte.

One thing that may not be obvious at a first glance to Table 11 is that STA, CA and STO are not bytes to be sent. STA is the start condition, and is handled by the hardware. STO is the stop condition, also handled by the hardware. CA is the chip address, which is taken care of in the beginTransmission call. The only data you need to send are the two bytes for C1 and C0.

And you can dig in your heels as much as you like, but it's not going to change the facts on the ground. This is the situation you're stuck with. So, I've said what I've had to say, and now I'm done. Good luck!

1 Like

So, I think I'm more confused than before :grin:, how would I use the Wire library to set every bit in the two 16bit registers? (as to include bits SFD1 and SFD0), it seems that I can't read the values already there, but I should be able to at least overwrite them with new ones.

They are write only registers, this is not common, but not totally unheard of.

note on page 10 it says:-
I2C Read Mode Format

To read back the status data, the read address shown in Table 10 is sent by the master. The modulator then responds with an ACK followed by a byte containing status information on the RF oscillator out-of-frequency range.

Sorry but wrong.

Data is only sent from an I2C device after you have written to it. So there is no need to read anything from it, because it will not send you data spontaneously. Your Arduino is the Master device, your chip is the slave device. A slave device will only send data when a master has commanded it. Look at the I2C examples in the IDE.

So look at table 11 and it gives you examples of the chain of writes needed to change your controlling registers.

Have you used I2C before? It seems a lot of confusion is being caused by you not knowing how to communicate with an I2C device.

here's code i use to write to a specific chip and register

// ---------------------------------------------------------
void i2cWrite (
    byte    chip,
    byte    port,
    byte    val )
{
    chip += chip < Chip20 ? Chip20 : 0;

    if (8 & debug)
        printf ("   %s: c 0x%02x, p %2d, v 0x%02x\n",
            __func__, chip, port, val);

    Wire.beginTransmission (chip);
    Wire.write (port);
    Wire.write (val);
    Wire.endTransmission ();
}

and read a register

byte
i2cRead (
    byte    chip,
    byte    port )
{
    if (8 & debug)
        return 0xFF;

    chip += chip < Chip20 ? Chip20 : 0;

    Wire.beginTransmission (chip);
    Wire.write (port);
    Wire.endTransmission ();

    Wire.requestFrom ((int)chip, 1); //get 1 byte
    byte val =  Wire.read ();

    if (16 & debug)  {
        Serial.print (F ("  i2cRead: chip "));
        Serial.print (chip, HEX);
        Serial.print (", port ");
        Serial.print (port, HEX);
        Serial.print (", val ");
        Serial.println (val, HEX);
    }

    return val;
}

The Wire library i2c_scanner isn't finding the chip, why could that be?

400kHz is the maximum an Uno supports (and AFAIK defaults to when entering a higher number).
Leo..

But shouldn't a lower clock speed work? (Already tried but no success either)

You shouldn't change the default I2C clock frequency (100kHz) until you actually need to.
So remove/comment that Wire.setclock(); line.
Leo..