Skip to content

Changing I2C pins on the fly (Question/suggestion, not a bug) #5669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
leifclaesson opened this issue Sep 14, 2021 · 4 comments
Closed

Changing I2C pins on the fly (Question/suggestion, not a bug) #5669

leifclaesson opened this issue Sep 14, 2021 · 4 comments
Assignees
Labels
Type: Question Only question

Comments

@leifclaesson
Copy link
Contributor

Hi!

On an already finished custom PCB based on the WROOM-32U module, I realized I needed another I2C bus.
I'm already using one high-speed I2C bus locally on the PCB for an OLED display, and the second I2C bus is brought out to headers for use to interface with external I/O expander boards with relays, also custom.
It's not a matter of running out of addresses -- with too many external boards the signalling just became unreliable -- the fact that these boards live in an electrical box around lots of 240 volt wiring might have something to do with it.

I have plenty of other pins brought out to headers, so I looked for a way to change TwoWire(0) or TwoWire(1) pins on the fly, and was rather surprised that there wasn't a way!

So, I dug into the Arduino Core source code and found esp32-hal-i2c.c and after some analysis, I wrote the following new function into the aforementioned file:

void i2cSetPins(uint8_t i2c_num, uint8_t sda, uint8_t scl)
{
    if(i2c_num > 1) {
        return;
    }

    i2c_t * i2c = &_i2c_bus_array[i2c_num];

    if(i2c->sda >= 0){
        i2cDetachSDA(i2c, i2c->sda);
    }
    if(i2c->scl >= 0){
        i2cDetachSCL(i2c, i2c->scl);
    }
    i2c->sda = sda;
    i2c->scl = scl;

    if(i2c->sda >= 0){
        i2cAttachSDA(i2c, i2c->sda);
    }
    if(i2c->scl >= 0){
        i2cAttachSCL(i2c, i2c->scl);
    }
}

With this function, I can change the I2C pins before calling the normal Wire functions, and change them back again afterwards.

Let me make absolutely clear, it works perfectly and it solved my problem.

My questions are:

  1. Is it a bad idea to change the pins on the fly like this, using this method? If so, why, and what would be a better solution to use more pins and have more I2C buses on an esp32?
  2. If it's not a bad idea, should it be officially supported without needing to modify the arduino core files?

As the code is written now, although you could call the i2cAttach/Detach functions from your own code, you don't have access to the i2c_dev_t * pointer unless you subclass TwoWire and save it from the i2cInit return value, and even if you do that, the i2c_dev_t and related definition such as enums are all private so you still couldn't change the sda/scl pins in the structure.

So, I tried simply modifying my copy, and it works beautifully. The lack of access to the i2c_dev_t pointer is why I simply used the i2c_num value (0 or 1), if I'm hacking the core code anyway why bother making it pretty.

Is there a better way? I'd be surprised if I was the last/only person to need more i2c buses by pin reassignment even if it's probably not all that common.

@chegewara
Copy link
Contributor

I can say it is good or bad idea, because i didnt test it, but some time ago i found that you can call as many times as you want with Wire.begin(sda, scl) to change pins. I dont know how this will behave in real app, but it did work to me (by accident) in some i2c scanner.

@leifclaesson
Copy link
Contributor Author

leifclaesson commented Sep 14, 2021

I can say it is good or bad idea, because i didnt test it, but some time ago i found that you can call as many times as you want with Wire.begin(sda, scl) to change pins. I dont know how this will behave in real app, but it did work to me (by accident) in some i2c scanner.

Interesting! I'm more surprised that works reliably since i2cInit (called internally by Wire.begin) does lots of things, including "checking the i2c line state" and failing completely if the line states are low, so that would seem risky to me. :) Very good to know it did work for you though.

@SuGlider SuGlider self-assigned this Sep 14, 2021
@SuGlider
Copy link
Collaborator

SuGlider commented Sep 14, 2021

@leifclaesson , please check PR #5664
The code for version 1.0.2 will change to run based on ESP-IDF instead of the current code in esp32-hal-i2c.c

As @chegewara already said, you can call TwoWire::begin(sda, scl, clock) with new pins or new i2c bus clock at any time, and doing so, reset it to the pins and values you need on the fly.
As a caveat, TwoWire::begin will reset/flush i2c bus and reset rx buffer.

This same functionality (changing pins on the fly with begin) will work in this refactoring new i2c version as well. I've tested it and it works fine as well in the PR code.

On any Core Version (1.0.6 to 2.0.1), the methods TwoWire::setSDA (v2.01 only), TwoWire::setSCL (v2.01 only), TwoWire::setPins and TwoWire::setClock can only be used before calling TwoWire::begin.

@SuGlider
Copy link
Collaborator

Interesting! I'm more surprised that works reliably since i2cInit (called internally by Wire.begin) does lots of things, including "checking the i2c line state" and failing completely if the line states are low, so that would seem risky to me. :) Very good to know it did work for you though.

@leifclaesson , could you please check if the code in the PR #5664 works "with the line states in low"? Just curious...

Another option is to use IDF function:
esp_err_t i2c_set_pin(i2c_port_t i2c_num, int sda_io_num, int scl_io_num, bool sda_pullup_en, bool scl_pullup_en, i2c_mode_t mode)

You can use it directly with Arduino code.
Example:
i2c_set_pin(I2C_NUM_0, sda_pin, scl_pin, 1, 1, I2C_MODE_MASTER);

Thanks.

@SuGlider SuGlider added the Type: Question Only question label Oct 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Question Only question
Projects
None yet
Development

No branches or pull requests

4 participants