Category: Raspberry PI

I2C and MCP23017, part 2

I2C and MCP23017, part 2

After being able to set basic outputs and inputs in the previous post, the next step could be to be understand the inputs, internal pull ups and the triggers INTA and INTB.

Let’s start with internal pull ups. The MCP23017 has the ability to software define internal pull ups. This means that you do not have to build these outside the chip. The registers GPPUA (0x0C) and GPPUB (0x0D) are used to set or remove this internal pull up. As with the other registers, 8 bits can be set. 0 means do not use internal pull up while a 1 means use an internal pullup. For the remainder of this article, we will assume a range of buttons or other inputs on bank A.

Let’s tell the MCP23017 that all of bank A needs to have the internal pull up set:
(again pseudo code. The code will be different for the Raspberry Pi and the NodeMCU)

write_to_i2c_bus(device_address,GPPUA,0xFF)

Now our inputs only need to connect to ground or float to send a signal. Next let’s tell the chip that we want any change on an input in bank A to send a signal to INTA. We do this by setting the individual bits on GPINTENA (0x04) high. (The corresponding GPINTENB can be found on 0x05).

write_to_i2c_bus(device_address,GPINTENA,0xFF)

All set, now if a change happens on any port on bank A, INTA is triggered. On your device (Raspberry PI or NodeMCU) you can define a trigger to react on this.

VERY IMPORTANT!! After each trigger on INTA (or INTB) you will NOT receive another trigger until you clear this one. You do this by either reading from GPIOA (or GPIOB) or by reading INTCAPA (0x10) (or INTCAPB at 0x11). Since you don’t know whether or trigger has happened during the initial boot up of your program, I suggest doing a read of GPIOA and/or GPIOB after you define your trigger to make sure the trigger is cleared.

The values you read from INTCAPA (or INTCAPB) show (in bit order) which ports set off the trigger.

If you are short on input ports on your device, it is possible to OR both INT ports such that you only have one trigger to alert you that there is a change on EITHER bank A or B. To do this, you set bit 6 (0x40) of either IOCONA (0x0A) or IOCONB (oxoB). To do this, you read the value first and than set or unset bit 6 and write it back.

current_value = read_from_i2c_bus(device_address,IOCONA)
new_value = current_value OR 0x40 (to set the 6th bit, chose one line \/)
new_value = current_value AND 0xBF (to unset the 6th bit, chose one line /\)
write_to_i2c_bus(device_address,IOCONA,new_value)

Depending on whether you want to set or unset the bit, you chose one of the 2 lines in the example above.

I2C and the MCP23017

I2C and the MCP23017

Sometimes in your Raspberry PI or NodeMCU projects you need more inputs and/or outputs than your PI/NodeMCU is giving you. One way to do that is to add more ports using the MCP23017 (or multiple even). It is a chip that works on the I2C bus and gives you 16 new in- and outputs.

How to connect them to your Raspberry Pi or your NodeMCU, I will discuss seperately. However the general workings of the chip is the same for both, so in this post I will explain how to connect it and what the different addresses are.

I2C is a bus which means you can have multiple devices on the same bus as long as each has a different address. The address for the MCP23017 is set by connecting pins to ground or Vdd. Let’s look at the pin layout:

MPC23017

As you can see there are 16 in- and outputs: GPA0-7 (bank A) and GPB0-7 (bank B). These are where you connect your digital in- and outputs to. Vdd is where you connect the + to (5v or 3.3v). Vss is connected to ground. SCL and SDA are the I2C bus channels and these you connect to your Raspberry Pi or NodeMCU bus. Note that you connect SCL to SCL and SDA to SDA (as opposed to Txd and Rxd which you switch). A0, A1 and A2 is where you set your address anywhere between 0x20 and 0x27 (hex 20 and hex 27). If you connect all three to ground, your device address is 0x20. If you connect A0 to Vdd and A1 and A2 to ground, your device address will be 0x21 etc. Also connect RESET to Vdd.

Once you have connected the power, connected the I2C bus and set the address, you are good to go.

To start, you have to tell the MCP23017 whether the ports are in- or outputs. You do this by writing to addresses IODIRA (0x00) for bank A and IODIRB (0x01) for bank B. There are 8 ports per bank which correspond to 8 bits. Setting a bit high means it is an input, setting a bit low means it is an output. I assume you can convert bits to a hex number, but a quick example to help you out:

Let’s say we want the first 4 ports of bank A to be inputs and the last 4 ports of bank A to be outputs. In bits this would mean that the first 4 bits are 1 and the last 4 bits are 0. If we write this out (starting with port 7 at the left and port 0 at the right), we get 00001111 which is the value in binary. If we convert this to hexadecimal we get 0x0F (add 1 for the bit at the right, than 2, than 4 and lastly 7. This gives 15 which is F in hexidecimal).

Of couse you can do the same for bank B. Now let’s tell the MCP23017 in pseudo code (the actual code of course differs per hardware platform) that the first 4 ports of bank A are inputs and the last 4 ports of bank A are outputs. We are assuming A0, A1 and A2 are connected to ground so the device address is 0x20.

device_address = 0x20
IODIRA = 0x00
bankAInOutput = 0x0F

write_to_i2c_bus(device_address,IODIRA,bankAInOutput)

Next we want to write to the outputs. We do a similar exercise, except we need a different address and we need to determine which ports to set high. Let’s say that we wish to turn all our outputs high. We need to convert this to a hexadecimal number as well. The first 4 bits (starting at the right) are for input, so we leave these at 0. The last 4 bits we want to set high. In binary we want to write the value 11110000 which corresponds to 0xF0. The address we want to write to is called OLATA and has address 0x14. Assuming we have executed our pseudo code above, we just need to do this to set the 4 outputs high:

OLATA = 0x14
All4OutputsHigh = 0xF0

write_to_i2c_bus(device_address,OLATA,All4OutputsHigh)

Next, let’s see what our input is doing. These are the first 4 bits. To read our bank we need address GPIOA which has address 0x12. When we read this address we do not only get our inputs back but also the state of our current outputs, in other words, the status of every port in our bank. In pseudo code this will look something like this:

 GPIOA = 0x12

AllOfBankA = read_from_i2c_bus(device_address,GPIOA)

You will get a byte back which corresponds to 8 bits, the 8 ports. If you get back for example 0xF5 that means your 4 outputs are high (0xF0) which is what you would expect since we just set these and the 4 inputs have status 0x05 which means the first input (GPA0) is high (bit 0 which corresponds to a 1) and GPA2, bit 2 which corresponds to a 4. GPA1 and GPA3 are low in this example. If your are unfamiliar with hexadecimal and bits, there is plenty on the internet to learn this (example link).

Well, this covers the basics. We can determine which ports are input and which are output and we can set a value to each and read the value from them.

In the MCP23017 chip there are 2 banks, A and B and each have seperate addresses to use but remember the device address (in our example 0x20) is the same. To summarize the addresses:

Determine which ports are input and output:
IODIRA (0x00) and IODIRB (0x01)

Set the output ports:
OLATA (0x14) and OLATB (0x15)

To read both output and input ports:
GPIOA (0x12) and GPIOB (0x13)

 This covers the very basics of the MCP23017.

Read the next steps in I2C and MCP23017, part 2

I2C on a Raspberry Pi

I2C on a Raspberry Pi

For one of my projects I needed more in- and outputs than was standard available on the Raspberry so I decided to investigate the I2C interface. There is a lot of information on the internet but getting all the tweaks right caused some issues which I will describe here.

After some research I found you need a MCP23017 I2C Port Expander to use the I2C bus. Basically what the chip does is convert the I2C signal into 16! different input and outputs per chip (yes, you can have more than one of these attached to your Pi).

After ordering the chip through AliExpress I used the following website to get started:

How To Use A MCP23017 I2C Port Expander With The Raspberry Pi

And yes, I managed to copy the example to my own bread board:

Continue reading “I2C on a Raspberry Pi”