3
\$\begingroup\$

I have a code where I don't understand parts of the I2C protocol.

The code below is a part of the code to read a bit from the bus

  if (SDA==1)
  {
     i_byte = (i_byte << 1) | 0x01; 
  }
  else
  {
     i_byte = i_byte << 1;
  }

Why is the ORing with 0x01 done ?

The code below is a part of the code to write a bit from the bus

  if(o_byte&0x80)
  {
     i2c_high_sda();
  }
  else
  {
     i2c_low_sda();
  }

Again ANDing with 0x80 is done for what ??

The passed parameters for writing are 0x00, 0x90 and 0x91. I understand that 1001 is the allocated address group for the ADC PCF8591F but what is 0x00 and 0x91 for ??

I have searched the complete the THE I2C-BUS SPECIFICATION VERSION 2.1 but I find no reference to these hex numbers. Am I missing some other resource that I need to read ?

\$\endgroup\$
1
  • \$\begingroup\$ Its probably something device specific that you will find the answer to in the datasheet for the device itself. \$\endgroup\$ Apr 10 '12 at 18:15
5
\$\begingroup\$

The code in your example is "clocking in" or "shifting in" a byte using a serial protocol. On every pulse of the clock line, your program reads in the next bit from a data line.

Here's another way to write your code which may be clearer, assuming that your program is the master (ie. in charge of generating the clock line). The purpose of this code is to clock out a square wave on CLK and to sample SDA at the centre of each high CLK pulse. Each of the samples are shifted into incoming_byte a bit at a time.

The diagram below shows the byte 0x47 as it might appear on the SDA line. Notice how the bit pattern is built up by shifting and ORing the data into a byte variable, assume that SDA can only be 0 or 1 for the illustration.

A signal

uint8_t incoming_byte = 0x00;

for (i=0; i<8; i++)
{
  delay(1);
  CLK=1;
  delay(1);
  incoming_byte <<= 1;
  incoming_byte |= SDA;
  delay(1);
  CLK=0;
  delay(1);
}

printf("%02X", incoming_byte);
\$\endgroup\$
3
\$\begingroup\$

These snippets looks like they have been taken out of receive/send loops that iterate 8 times for each byte. The first loop shifts received bits left (I2C transfers data MSB first) and sets the newly arrived bit to 1 if SDA is high and to 0 if it's low. The left shift operator is defined to set vacated bits to zero so you don't need do do anything if you need your newly arrived bit to be zero. If you need it to be 1 you OR the result with 1 (binary 00000001) because x | 0 = x and x | 1 = 1 so you only change the least significant bit.

The second case is transmission. If the most significant bit is 1 you set SDA high and if it's 0 you set it low. Again, x & 1 = x and x & 0 = 0, and 0x80 is 10000000 binary so you only test the most significant bit of o_byte. What that code is missing is o_byte = o_byte << 1; at the end of the loop body.

Google bitwise operations in C to understand bit manipulation, you'll need it.

(The actual bytes transmitted over the bus are slave-specific except the first byte in each transaction which is the slave address with the LSB speicfying whether it's a write or read request, in the most common case of 7-bit addresses. If I understood your code right it doesn't matter which exact values it is passed, it will process them all in the same way.)

\$\endgroup\$
0
2
\$\begingroup\$

You really should provide more data like what system you're using, to which pins are the I^2C lines connected and so on.

Since I have to guess, here's what it looks to me:

  if (SDA==1)
  {
     i_byte = (i_byte << 1) | 0x01; 
  }
  else
  {
     i_byte = i_byte << 1;
  }

if (SDA==1) I'd say that this tests the data pin to check if it's high or not. The whole procedure looks like it's meant to be used in a loop with i_byte being the read byte.

i_byte = (i_byte << 1) | 0x01; Here we shift the value of i_byte by one location to the left. So if it was 0000 0001, now it would be 0000 0010. In addition to that we use logical OR to add 0000 0001 to the end of the byte, so we'd get 0000 0011. As you can see, once we shifted the whole byte to the left, the zero we got is new data that wasn't there before. Since this happens when the SDA equals one, I'd say that we're adding the bit we just read to the end of the byte.

i_byte = i_byte << 1; This line seems to confirm my suspicion. If the SDA is not one, that is to say it is zero, we just move the last bit one place to the left and the zero we've got remains.

Next:

  if(o_byte&0x80)
  {
     i2c_high_sda();
  }
  else
  {
     i2c_low_sda();
  }

if(o_byte&0x80) This looks like a part of instruction series to me. The 0x80 tests if the most significant bit of the o_byte (which I guess is output byte) is one or zero. It's probably used with a series of ifs for each bit of the byte, so expect to find 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 too somewhere there.

So if the first bit is one, we do i2c_high_sda();. I'd say that if the bit is one, we set the output high.

Next we have: i2c_low_sda();. It happens if the condition isn't true, that is to say that the first bit of o_byte is zero. It would seems that it drives the SDA bit low and provides logical zero at the output.

\$\endgroup\$
2
\$\begingroup\$

Others have explained the code snippets. I'll address the 0x00, 0x90 and 0x91.

The datasheet (http://astro.temple.edu/~cvecchio/PCF8591_6.pdf) page 5 figure 4 shows how the address byte is built: the 1001 bits go in the upper nibble, the lowest bit selects read/write, the remaining 3 bits are determined by pins of the chip. If these pins are wired low this results in 0x91 for reading and 0x90 for writing.

I am not sure where you found the 0x00, but it might be the next byte written to the chip, which would be the control byte.

\$\endgroup\$
0
\$\begingroup\$

Before you do any more programming of a microcontroller, you'll find it extremely helpful to learn about the following concepts:

  • Binary counting
  • Hexadecimal counting
  • The AND operation
  • The OR operation

These four things are tragically misunderstood, even by very experienced software developers who have not had any exposure to embedded systems or device drivers. These concepts are vitally important, and you will come across them time and again. But the good news is that they are actually very simple, and you can understand them all in an afternoon. But it may take lots of practice before they come naturally.

When you understand them, you will be able to answer without thinking: "What is 0x80 in binary?".

\$\endgroup\$
7
  • 2
    \$\begingroup\$ This is a very good point, but doesn't actually answer the question \$\endgroup\$ Apr 10 '12 at 22:19
  • \$\begingroup\$ @JobyTaffey That is correct. Sometimes I feel that it actually does the OP a disservice to simply answer the question, frustrating as that may be. From the questions he's asking, it seems that he needs much more than just an answer. If he can't understand that simple code, he's not going to get much further before needing to ask another similar question. \$\endgroup\$ Apr 10 '12 at 23:34
  • 1
    \$\begingroup\$ A response which doesn't answer the question might be better as a comment \$\endgroup\$ Apr 11 '12 at 9:03
  • \$\begingroup\$ I nearly did it as a comment, but it got so long I decided to put it as an answer. Is it a big problem ? \$\endgroup\$ Apr 11 '12 at 9:49
  • \$\begingroup\$ No, it's just hard to vote your answer up or down given that it doesn't answer the question. \$\endgroup\$ Apr 11 '12 at 9:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.