MCP23017 Port Expander

gregstewart90
Posts: 59
Joined: Thu Jan 19, 2017 5:17 pm

MCP23017 Port Expander

Postby gregstewart90 » Mon Jan 23, 2017 11:49 pm

I am trying to use a MCP23017 port expander in c via i2c with the ESP32. The only example code for i2c I have been able to find is https://github.com/espressif/esp-idf/tr ... herals/i2c. This code is for a light sensor.

Arduino has some code for using this IC on the aduino uno, but I have been unable to get any code working.

Code: Select all

//Arduino Code for the MCP23017
#include <Wire.h>

void setup() { Wire.begin(); //creates a Wire object

// set I/O pins to outputs
Wire.beginTransmission(0x20); //begins talking to the slave device
Wire.write(0x00); //selects the IODIRA register
Wire.write(0x00); //this sets all port A pins to outputs
Wire.endTransmission(); //stops talking to device
Wire.beginTransmission(0x20);//begins talking again to slave device
Wire.write(0x01); //selects the IODIRB register
Wire.write(0x00); // sets all port B pins to outputs
Wire.endTransmission(); //ends communication with slave device
}

void loop()
{
Wire.beginTransmission(0x20); //starts talking to slave device
Wire.write(0x12); //selects the GPIOA pins
Wire.write(00000011); // turns on pins 0 and 1 of GPIOA
Wire.endTransmission(); //ends communication with the device
Wire.beginTransmission(0x20); //starts talking to slave device
Wire.write(0x13); //selects the GPIOB pins
Wire.write(00000001); //turns on pin 0 of GPIOA
Wire.endTransmission();//ends communication with the device
}
Any help would be greatly appreciated.

MarcusW
Posts: 6
Joined: Fri Dec 23, 2016 5:00 am

Re: MCP23017 Port Expander

Postby MarcusW » Thu Jan 26, 2017 1:52 am

I've not used the Arduino interface, but did get the MCP23017 to work using Eclipse on the ESP8266.

A couple of things you may want to look at:

I was using chip address 0x40 instead of 0x20 shown in your example, but the start transmission function may be doing that translation for you.

I wrote to the output latch registers OLATA (0x14) and OLATB (0x15) instead of the GPIOx registers (0x12-13).

I also noticed you are using "Wire.write(00000011);" I believe in C syntax that this is writing the decimal number eleven (with lots of leading zeros), and not the binary value the comments say you are expecting.

Hope it helps.

MarcusW
Posts: 6
Joined: Fri Dec 23, 2016 5:00 am

Re: MCP23017 Port Expander

Postby MarcusW » Thu Jan 26, 2017 2:00 am

One other common I2C Problem: Do you have pull-up resistors on the clock and data lines to 3V3? 2K to 5K are common values.

User avatar
kolban
Posts: 1683
Joined: Mon Nov 16, 2015 4:43 pm
Location: Texas, USA

Re: MCP23017 Port Expander

Postby kolban » Thu Jan 26, 2017 4:12 am

Here is a link to a sample I am working upon ...

https://github.com/nkolban/esp32-snippe ... s/mcp23017

This is a port of the awesome Adafruit library for MCP23017 support. I ported this from Arduino APIs to native ESP-IDF APIs ... however, big warning, I haven't actually tested it yet ... It compiles cleanly and is likely to be close ... but again ... I haven't yet performed a single test. If/when I do, I'll update this post with additional text.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32

gregstewart90
Posts: 59
Joined: Thu Jan 19, 2017 5:17 pm

Re: MCP23017 Port Expander

Postby gregstewart90 » Thu Jan 26, 2017 3:34 pm

Thanks for the help!
One other common I2C Problem: Do you have pull-up resistors on the clock and data lines to 3V3? 2K to 5K are common values.
The ESP-32 has internal pull up resistors which I am using. Thanks for the suggestion!

I was able to get outputs working with the following code.

Code: Select all

/*
 * test_main.c
 *
 *  Created on: Jan 24, 2017
 *      Author: Greg
 */

#include <stdio.h>
#include "driver/i2c.h"
#define MCP23017_ADDR  0x23    /*!< slave address for MCP23017 sensor */
#define ACK_CHECK_EN   0x1
#define I2C_MASTER_NUM I2C_NUM_1   /*!< I2C port number for master dev */
#define I2C_MASTER_SCL_IO    19    /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO    18    /*!< gpio number for I2C master data  */
#define I2C_MASTER_FREQ_HZ    100000     /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define WRITE_BIT  I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT   I2C_MASTER_READ  /*!< I2C master read */

void app_main(void)
{
	int i2c_master_port = I2C_MASTER_NUM;
	i2c_config_t conf;
	conf.mode = I2C_MODE_MASTER;
	conf.sda_io_num = I2C_MASTER_SDA_IO;
	conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
	conf.scl_io_num = I2C_MASTER_SCL_IO;
	conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
	conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
	i2c_param_config(i2c_master_port, &conf);
	i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);

	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
	int ret;
	printf("Driver Installed...\n");
	printf("Moving on to outputs...\n");
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN);//Access IODIRA
	i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN);//Set all as outputs on A
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 1\n");
	}

	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x12, ACK_CHECK_EN);//Access GPIOA
	i2c_master_write_byte(cmd, 11000011, ACK_CHECK_EN);// Set 0,1,6,7 as high, the rest low
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 2\n");
	}

	printf("Outputs now set...\n");
	printf("Moving on to inputs...\n");
	vTaskDelay(30 / portTICK_RATE_MS);
	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x01, ACK_CHECK_EN);// Access IODIRB
	i2c_master_write_byte(cmd, 11111111, ACK_CHECK_EN); // Set all as inputs on B
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 3\n");
	}



	while(1){
		vTaskDelay(30 / portTICK_RATE_MS);

		int sensor_data_h;

		cmd = i2c_cmd_link_create();
		i2c_master_start(cmd);
		i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | READ_BIT, ACK_CHECK_EN);
		i2c_master_read_byte(cmd, &sensor_data_h, 0x13);//Read data back on B
		i2c_master_stop(cmd);
		ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
		i2c_cmd_link_delete(cmd);
		if (ret == ESP_FAIL) {
			printf("ERROR\n");
		}else{
			printf("Passed step 4\n");
			printf("Result: %02x\n", sensor_data_h);//Should show hex value of input.
		}
	}

	return;

}

But my inputs never return anything but 0x00.

BTW kolban I am a huge fan of your work. I've used your tutorials and ESP32 book to get to this point. Excellent job!

gregstewart90
Posts: 59
Joined: Thu Jan 19, 2017 5:17 pm

Re: MCP23017 Port Expander

Postby gregstewart90 » Thu Jan 26, 2017 9:01 pm

SOLVED!

The code is now working. I needed to write to the slave and then read it.

Code: Select all

/*
 * test_main.c
 *
 *  Created on: Jan 24, 2017
 *      Author: Greg
 */

#include <stdio.h>
#include "driver/i2c.h"
#define MCP23017_ADDR  0x23    /*!< slave address for MCP23017 sensor */
#define ACK_CHECK_EN   0x1
#define I2C_MASTER_NUM I2C_NUM_1   /*!< I2C port number for master dev */
#define I2C_MASTER_SCL_IO    19    /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO    18    /*!< gpio number for I2C master data  */
#define I2C_MASTER_FREQ_HZ    100000     /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define I2C_MASTER_RX_BUF_DISABLE   0   /*!< I2C master do not need buffer */
#define WRITE_BIT  I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT   I2C_MASTER_READ  /*!< I2C master read */
#define ACK_VAL    0x0         /*!< I2C ack value */
#define NACK_VAL   0x1         /*!< I2C nack value */

void readFunction(uint8_t* data_h, i2c_cmd_handle_t cmd){


	vTaskDelay(30 / portTICK_RATE_MS);

	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x13, ACK_CHECK_EN);// Access IODIRB
	i2c_master_stop(cmd);
	int ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 4\n");
	}

	vTaskDelay(30 / portTICK_RATE_MS);

	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | READ_BIT, ACK_CHECK_EN);
	i2c_master_read_byte(cmd, data_h, ACK_VAL);
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 4\n");
	}

}


void app_main(void)
{
	int i2c_master_port = I2C_MASTER_NUM;
	i2c_config_t conf;
	conf.mode = I2C_MODE_MASTER;
	conf.sda_io_num = I2C_MASTER_SDA_IO;
	conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
	conf.scl_io_num = I2C_MASTER_SCL_IO;
	conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
	conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
	i2c_param_config(i2c_master_port, &conf);
	i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);

	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
	int ret;
	printf("Driver Installed...\n");
	printf("Moving on to outputs...\n");
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN);//Access IODIRA
	i2c_master_write_byte(cmd, 0x00, ACK_CHECK_EN);//Set all as outputs on A
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 1\n");
	}

	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x12, ACK_CHECK_EN);//Access GPIOA
	i2c_master_write_byte(cmd, 0xC3, ACK_CHECK_EN);// Set 0,1,6,7 as high, the rest low
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 2\n");
	}

	printf("Outputs now set...\n");
	printf("Moving on to inputs...\n");
	vTaskDelay(30 / portTICK_RATE_MS);
	cmd = i2c_cmd_link_create();
	i2c_master_start(cmd);
	i2c_master_write_byte(cmd, MCP23017_ADDR << 1 | WRITE_BIT, ACK_CHECK_EN);
	i2c_master_write_byte(cmd, 0x01, ACK_CHECK_EN);// Access IODIRB
	i2c_master_write_byte(cmd, 0xff, ACK_CHECK_EN); // Set all as inputs on B
	i2c_master_stop(cmd);
	ret = i2c_master_cmd_begin(I2C_NUM_1, cmd, 1000 / portTICK_RATE_MS);
	i2c_cmd_link_delete(cmd);
	if (ret == ESP_FAIL) {
		printf("ERROR\n");
	}else{
		printf("Passed step 3\n");
	}

	uint8_t data_h;
	while(1){
		readFunction(&data_h, cmd);
		printf("Result.: %02x\n", data_h);//Should show hex value of input.
	}
	return;

}

Lurcher
Posts: 7
Joined: Wed Nov 16, 2016 5:53 pm

Re: MCP23017 Port Expander

Postby Lurcher » Thu Jan 26, 2017 11:17 pm

Your read routine is missing the data register address. This code (mcp23008) is a little messy because of the error printing, but you can see that you need to send write the register address you want to read from, and then read the result.

Code: Select all

/*************************************************************************
* Function : uint8_t EXP_read (uint8_t addr, uint8_t *data)
* Requires : PortExpanderInit must have been called.
* Input    : addr  Register address to read
* Output   : data from addressed register
* Overview : Read one of the Expander registers.
*            Automatically sends Device Address and dummy byte to get data.
* Note     : None
*************************************************************************/
uint8_t EXP_read (uint8_t addr, uint8_t *data)
{
   esp_err_t err;
   
   i2c_cmd_handle_t cmd = i2c_cmd_link_create();
   i2c_master_start(cmd);
   err = i2c_master_write_byte(cmd, (MCP23008_I2C_ADDR << 1) | EXP_DEV_ADR | WRITE_BIT, ACK_CHECK_EN);
   if (err) {
      xSemaphoreTake(print_mux, portMAX_DELAY);
      printf("mcp23008r_devaddr %d\n", err);
      xSemaphoreGive(print_mux);
   }
   err = i2c_master_write_byte(cmd, addr, ACK_CHECK_EN);
   if (err) {
      xSemaphoreTake(print_mux, portMAX_DELAY);
      printf("mcp23008r_addr %d\n", err);
      xSemaphoreGive(print_mux);
   }
   i2c_master_start(cmd);
   err = i2c_master_write_byte(cmd, (MCP23008_I2C_ADDR << 1) | EXP_DEV_ADR | READ_BIT, ACK_CHECK_EN);
   if (err) {
      xSemaphoreTake(print_mux, portMAX_DELAY);
      printf("mcp23008r_devaddr %d\n", err);
      xSemaphoreGive(print_mux);
   }
   err = i2c_master_read_byte(cmd, data, NACK_VAL);
   i2c_master_stop(cmd);
   if (err) {
      xSemaphoreTake(print_mux, portMAX_DELAY);
      printf("mcp23008r_data %d\n", err);
      xSemaphoreGive(print_mux);
   }
   i2c_master_stop(cmd);
   err = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 2000 / portTICK_RATE_MS);
   i2c_cmd_link_delete(cmd);
   if (err) {
      xSemaphoreTake(print_mux, portMAX_DELAY);
      printf("mcp23008r_cmd %d\n", err);
      xSemaphoreGive(print_mux);
   }
   return(err);
}

telanoc
Posts: 5
Joined: Fri Jan 13, 2017 1:02 am

Re: MCP23017 Port Expander

Postby telanoc » Sat Feb 04, 2017 11:11 pm

Neil, I worked on your Adafruit port a bit, and got it working pretty well. I wound up writing a generic I2C register read/write library and then changed your changes to use that instead. I wired a 23017's chips two ports together, added some LEDs, and wrote a test program which alternates between the two ports, lighting each LED on one port and reading the other ones inputs as that happens.

It's at https://github.com/telanoc/esp32_generic_i2c_rw if you want to take a look. I'm still pretty bad with git or I'd have figured out a pull request against your snippets. :)

User avatar
kolban
Posts: 1683
Joined: Mon Nov 16, 2015 4:43 pm
Location: Texas, USA

Re: MCP23017 Port Expander

Postby kolban » Sun Feb 05, 2017 4:03 pm

Good stuff!!! Ive added a link to your repository in the book of notes.
Free book on ESP32 available here: https://leanpub.com/kolban-ESP32

rohit269
Posts: 24
Joined: Mon Sep 11, 2017 4:22 am

Re: MCP23017 Port Expander

Postby rohit269 » Sun Jan 28, 2018 5:51 pm

Hello,

I'm interfacing MCP23017 IO expander with the ESP32. I was going to use the modified Adafruit_MCP23017 library: https://github.com/nkolban/esp32-snippe ... s/mcp23017

However, I noticed that it is a .cpp file.

My entire code is in C. Is there a way to use this library with a C coding environment?

Thanks

Who is online

Users browsing this forum: Google [Bot] and 151 guests