Learning I2C - circuit help

Thread Starter

Captain E

Joined Jun 16, 2015
81
Hello fellow humans!
I'm currently taking the challenge of learning about I2C, and have now learnt about this for a couple of days.

It's time for my first circuit!
I'm going to learn by making several circuits doing different things, and at the same time learn what is happening.
The first thing I'm trying to do is simple to access and configure the IODIRB chip of my MCP23018, (datasheet link) which I understand sets port side B's pins (GPB0-GPB7) as either outputs or inputs, and by reading the datasheet: (text 1.6.1, page 18)

1.6.1 I/O DIRECTION REGISTER
Controls the direction of the data I/O.
When a bit is set, the corresponding pin becomes an input. When a bit is clear, the corresponding pin becomes an output
.. I found that this is the way to do this. I'm trying to set all the GPBX as outputs, and I connected GPB0 to my Arduino UNO's analog input pin A0, so I can read it and see if the voltage is HIGH.

I understand that writing to the IODIRX SETS the IO pins low/high, and if I want to READ them I use GPIOX.

I'm using the Wire.h (link here) library, and I made this code, added below. I commented out what I understand (or think?) the code is doing, and I also put a lot of Serial prints for debugging with my computer.

Code:
#include <Wire.h>

void setup() {
  Serial.begin(9600);
  while(!Serial); //wait for Serial to start
  Serial.print("Voltage at Arduino analog A0 BEFORE: ");
  Serial.println(analogRead(A0));
  Serial.println("------------------------");
  Serial.println("WIRE: beginning");
  Wire.begin(); //join i2c bus (no parameters=join as master)
  Serial.println("WIRE: started");
  Wire.beginTransmission(0x20);  //begin transmission with MCP23018. 0x20 is the adress for the MCP23018 in hexadecimal, with ADDR connected to ground.
  Serial.println("WIRE: transmission begun");                   
  Wire.write(0x00);  //register address to talk to
                     //0x01=IODIRB register address (with help from the datasheet, page.7 TABLE 1-1)
  Serial.println("WIRE: wrote register address");
  Wire.write(0x00); //00000000=0x00 in hexadecimal
                    //=sets GPIO-B0 to PPIO-B7 as outputs?
  Serial.println("WIRE: GPIO-B5 set as output?");
  Wire.endTransmission();
  Serial.println("WIRE: transmission ended");
  Serial.println("------------------------");
  Serial.print("Voltage at Arduino analog A0 AFTER: ");
  Serial.println(analogRead(A0));
  Serial.println("------------------------");
}

void loop() {
  delay(1000); //we no need this C:
}
Would love if someone could try to help me get this working, because it isn't..

Here is the Serial outputs I get:

Code:
Voltage at Arduino analog A0 BEFORE: 1
------------------------
WIRE: beginning
WIRE: started
WIRE: transmission begun
WIRE: wrote register address
WIRE: GPIO-B5 set as output?
WIRE: transmission ended
------------------------
Voltage at Arduino analog A0 AFTER: 0
------------------------
..so I guess the code isn't THAT BAD as it seems to be a possible code for something o.o

Thank you all in advance! U guys are the best :3
 

Papabravo

Joined Feb 24, 2006
15,518
The direction register does not set the pins high or low. It only controls the direction. The value, high or low, is set by the output latch register.
Since pins default to input, the two step process, is to set the value in the output latch, then set the direction register. At this point the output will go to the value defined by the output latch. Check to see what value is in the output latch at power on/reset. If the default value is 0, that is why your example seemed to work.
 

Thread Starter

Captain E

Joined Jun 16, 2015
81
The direction register does not set the pins high or low. It only controls the direction. The value, high or low, is set by the output latch register.
Since pins default to input, the two step process, is to set the value in the output latch, then set the direction register. At this point the output will go to the value defined by the output latch. Check to see what value is in the output latch at power on/reset. If the default value is 0, that is why your example seemed to work.
ooh I see!
I missunderstood "When a bit is clear, the corresponding pin becomes an output" a bit..

I have now got a code which first sets GPB0-GPB7 pins HIGH, and then sets GPB0-GPB7 to outputs, as you said I should.

Code:
#include <Wire.h>

void setup() {
  Serial.begin(9600);
  while(!Serial); //wait for Serial to start
  Serial.print("Voltage at Arduino analog A0 BEFORE: ");
  Serial.println(analogRead(A0));
  Wire.begin(); //join i2c bus (no parameters=join as master)

  Wire.beginTransmission(0x20); //connect to MCP23018, with address 0x20 (ADDR to GND)
  Wire.write(0x13); //talk to GPIOB register
  Wire.write(0xFF); //sets all outputs HIGH
  Wire.endTransmission();

  Wire.beginTransmission(0x20);
  Wire.write(0x00);  //talk to IODIRB register
  Wire.write(0x00); //set GPB0-GPB7 as outputs
  Wire.endTransmission();

  delay(400);

  Serial.println("------------------------");
  Serial.print("Voltage at Arduino analog A0 AFTER: ");
  Serial.println(analogRead(A0));
  Serial.println("------------------------");
}

void loop() {
  delay(1000); //no need for this loop
}

Still not quite there yet, as the value of analog pin A0 is the same before and after is about 30, so LOW. (about ±5 with more attempts)

Something you can see that I'm missing?

I can make a schematics of my circuit if you would need that. General info about it:
- SCL and SDA lines are connected to the Arduino UNO's analog input pins A5 and A4, and both have got a pullup resistor at 4.7kOhms to +5V.
- MCP23018 ADDR pin is connected to GND/0V and Vss to +5V.
- GPB0, which is connected to Analog pin A0, also got a pull down resistor at 10MOhms

I'd guess there is more connections the I2C device needs to 5V/GND maybe.
________
Thanks a lot for helping me, I really appreciate it! :)
 

Papabravo

Joined Feb 24, 2006
15,518
I believe the outputs are open-drain. What this means is that the output stage has only a pull down transistor (an N-channel FET). It needs an external pullup resistor to make a satisfactory high level. The equivalent circuit in bipolar logic is called an open collector.

Here are some additional references
https://en.wikipedia.org/wiki/Open_collector
http://www.mcc-us.com/open-collectorfaq.htm
http://letsmakerobots.com/node/39125

One advantage of these outputs is that you can connect them together without damaging them, and get another level of logic. Connect two open drain outputs together and the output is high only if both of them are, and is low if either one is low. This is often done with interrupt lines:

and the DeMorganized logis is:
a LOW .OR. a LOW is a LOW
 

Thread Starter

Captain E

Joined Jun 16, 2015
81
I'm stuck a bit.. If I add a pullup resistor from my output pin (GPB0) to +5V, the analog pin A0 immediately gets HIGH. (analogRead 1023).
..and it's the same with different values of the pullup resistor.
If I remove the pullup resistor, analog pin A0 reads ~400 out of 1023.

The MCP23018 has got a PULL-UP RESISTOR CONFIGURATION REGISTER which controls the pull-up resistors for the port pins.
If a bit is set the corresponding port pin is internally pulled up with an internal resistor.
1 = Pull-up enabled. 0 = Pull-up disabled.

Is this what you mean that I need? (but internal, so I wont need this as external?)

<attempt1>
- Using the internal GPPU - GPIO PULL-UP RESISTOR REGISTER - pull-up enabled for all pins.
- Output pin directly wired to Analog input pin A0 on the Arduino UNO.

Code:

Code:
#include <Wire.h>

void setup() {

  Serial.begin(9600);
  while(!Serial); //wait for Serial to start
  Serial.print("Voltage at Arduino analog A0 BEFORE: ");
  Serial.println(analogRead(A0));
  Wire.begin(); //join i2c bus (no parameters=join as master)

  Wire.beginTransmission(0x20); //connect to MCP23018, with address 0x20 (ADDR to GND)
  Wire.write(0x0D); //talk to GPPUB register
  Wire.write(0xFF); //enable pullup on all IO-B pins (1=enabled, 0=disabled)
  Wire.endTransmission();
   

  Wire.beginTransmission(0x20);
  Wire.write(0x13); //talk tao GPIOB register
  Wire.write(0x00); //sets all outputs HIGH
  Wire.endTransmission();


  Wire.beginTransmission(0x20);
  Wire.write(0x01);  //talk to IODIRB register
  Wire.write(0x00); //set GPB0-GPB7 as outputs
  Wire.endTransmission();

  delay(400);

  Serial.println("------------------------");
  Serial.print("Voltage at Arduino analog A0 AFTER: ");
  Serial.println(analogRead(A0));
  Serial.println("------------------------");
}

void loop() {
  delay(1000);
  Serial.println(analogRead(A0));
}
Results:

Code:
Voltage at Arduino analog A0 BEFORE: 369
------------------------
Voltage at Arduino analog A0 AFTER: 348
------------------------
491
494
493
485
483
491
485
<attempt2>
-
using an external pull-up resistor on my output pin, without any internal pull-up
- output pin connected to +5V (with a resistor) and analog pin A0 on the Arduino UNO.

Code:

Code:
#include <Wire.h>

void setup() {

  Serial.begin(9600);
  while(!Serial); //wait for Serial to start
  Serial.print("Voltage at Arduino analog A0 BEFORE: ");
  Serial.println(analogRead(A0));
  Wire.begin(); //join i2c bus (no parameters=join as master)

  Wire.beginTransmission(0x20); //connect to MCP23018, with address 0x20 (ADDR to GND)
  Wire.write(0x0D); //talk to GPPUB register
  Wire.write(0x00); //disable pullup on all IO-B pins (1=enabled, 0=disabled)
  Wire.endTransmission();
   
 
  Wire.beginTransmission(0x20);
  Wire.write(0x13); //talk tao GPIOB register
  Wire.write(0x00); //sets all outputs HIGH
  Wire.endTransmission();
 
 
  Wire.beginTransmission(0x20);
  Wire.write(0x01);  //talk to IODIRB register
  Wire.write(0x00); //set GPB0-GPB7 as outputs
  Wire.endTransmission();

  delay(400);
 
  Serial.println("------------------------");
  Serial.print("Voltage at Arduino analog A0 AFTER: ");
  Serial.println(analogRead(A0));
  Serial.println("------------------------");
}

void loop() {
  delay(1000);
  Serial.println(analogRead(A0));
}

Results:


Code:
Voltage at Arduino analog A0 BEFORE: 1023
------------------------
Voltage at Arduino analog A0 AFTER: 1023
------------------------
1023
1023
1023
1023
1023
1023
1023
1023

</attempts2>



Shouldn't both work? I have added pullup resistors both internal and external, as you said I should.

Something is wrong here :/

Maybe something is wrong with my circuit, so I made a schematics of it, just in case there is something wrong there:
(this image, with an EXTERNAL pull-up resistor)



Thanks a lot!
 

Thread Starter

Captain E

Joined Jun 16, 2015
81
~BUMPILY BUMB~

(PapaBravo has helped A LOT but I'm not quite there yet, so I'd be very happy for any help provided) :))
 

Thread Starter

Captain E

Joined Jun 16, 2015
81
Thank you! DickCappels, OBW0549 and Papabravo: thanks for wanting to help me, you are all awesome.

Still stuck with the circuit though.
Thanks again, guys!

~ Cpt. E

Moderator's note: Some text removed for clarity. Thread moved to Embedded Systems where it is hoped to receive more attention.
 
Last edited by a moderator:

shmee406

Joined Feb 21, 2020
1
I saw this thread as I was trying to troubleshoot this exact circuit. Since it looks like it didn't get fully resolved, I'll throw in my results...

My problem was that I wasn't getting any acknowledge back from the device. You can tell this if you return the value given by Wire.endTransmission(). See library documentation here. If this is the same problem that you are having, which I suspect you are, then you likely are not biasing the reset pin. The documentation on this for the MCP23018 is fuzzy. The only place it says you have to bias this pin externally is in table 1-2 which is the SPI pinout description.

Anyways, try putting a pullup resistor (1k or so) between reset and power.
 

BobaMosfet

Joined Jul 1, 2009
1,518
@shmee406 & @Captain E

Understand what a pullup/pulldown actually does. Consider a conductor like a river of water. That river is flowing at a specific rate, and with a specific quantity (volume). Voltage is the speed of the flow of water, and current is the quantity.

If you put a pull-up resistor parallel to that conductor, it's like a tributary flowing into the river. If the river suddenly stopped flowing from the source, the tributary would still drain into the river, and water would be seen down stream. Not as much, but the speed is the same, the volume is less. So voltage is the same, but current is a fraction of the original flow. Everything downstream just sees "Oh, river is still flowing". In other words, the pull-up is _influencing_ the behavior of the river as seen downstream. It isn't replacing it, and it can't override it. It only creates an influence in the absence of the river.

Conversely a pull-down resistor is like a drainage ditch splitting off from the river. While the river flows, downstream still gets a high voltage and high volume of "water". the drainage ditch is also getting a little bit. However, if the river stops flowing, but there is random rain (like capacitive or inductive build-up), that miniscule amount of water will be bled off by the drainage-ditch, and the downstream view will remain to be 'no water' coming.

That's how it works- the trick in sizing resistors is to understand what I've described above and size your resistor so that it 'influences' but doesn't override the main conductor. Higher value resistors mean less current flowing. Lower value resistors mean more current flowing.

So when you size your pull-ups, make sure you're only pulling just hard enough to do the job reliably, not too much to ruin the signal on the line. A 'signal' is a voltage level that can be measured by amplitude. Ohm's law describes the relationship between voltage, impedance, and the flow of current- each affects the other, and electronics is primarily about how to manipulate that relationship to make current flow where you want it to flow at the right intensity (voltage).

For example- it's typical to see a 4K7 (that's 4.7K Ohms) resistor being used as a pull-up or pull-down in a 5V circuit. That is because 5/4700 is 0.00106... or in other words just 1mA (a single thousandths of an Amp). It's tiny- but it's enough to influence the signal without overriding it.

BJT Transistors usually need both a resistor in series on the base to protect the transistor and limit the maximum current a GPIO pin push or pull through the base, and it also tends to have another resistor, a pull-up or pull-down in parallel with it, to influence the default value on the base (on or off).

If you want to understand why a resistor works (or in fact any component(s) you place in a circuit), here's something to remember:

Voltage loves and 'open' (circuit), but Current hates it.
 
Top