-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
I2C sw driver with support of multiple buses, Slow, Fast, FastPlus, and user-defined speed selection #2465
Conversation
Thanks Natalia. I've been meaning to do this, but too much on my plate. I will do a proper review of this in the next day or so. |
Is this in any way related to espressif/ESP8266_NONOS_SDK#160? |
Original driver in ESP8266_NONOS_SDK does not support clock stretching. Nodemcu version supports it since 2016, thanks to #1589 |
@sonaux Natalia, I haven't commented inline because my comments on this pass are general.
There are various approaches to this, from simply having a large
So here for example the approach to i2c_master_setDC(uint8 SDA, uint8 SCL) {
uint32_t this_SDA_SCL_mask = (SDA<<pinSDA) | (SCL<<pinSCL);
m_nLastSDA = SDA;
m_nLastSCL = SCL;
gpio_output_set(this_SDA_SCL_mask, pin_SDA_SCL_mask ^ this_SDA_SCL_mask, pin_SDA_SCL_mask, 0);
if(1 == SCL)
while(!(gpio_input_get()&(1<<pinSCL))) {}
} This a lot shorter and faster than the current version. (This was a source only exercise and need validating). And your rsr loop compiles down to rsr a6, ccount
.L3:
rsr a4, ccount
sub a4, a4, a6
bltu a4, a5, .L3 So big tick there.
So overall well done and thanks, but I do think that we need another iteration. Terry 😄 PS. I am not sure if that clock stretch algo is correct. This needs review. |
Thanks for a feedback! It's my first time coding in C since university classes 15 years ago, so i'm really new to this. Had to reread some textbooks at first. I've got the idea to #ifdef code to allow compile-time selection of old/new version. |
If you want to work more informally, then you can use the Gitter IM for nodemcu/nodemcu-firmware or we can ping-pong emails. I am happy to help anyone get over the leaning hump -- if they are here to contribute constructively. I am sorry to say that macros are part of Lua and the SDK, so that's life. If you do want to get a handle on the Xtensa architecture then something like this useful synopsis An short guide to Xtensa assembly language would help, or the biggy: Xtensa Instruction Set Architecture (ISA) Reference Manual. Just look at the generated code before you think that macros hurt, for example you couldn't beat the 3 instruction wait loop. PS. I use a BitScope logic analizer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a quick code review it seems that certain omissions potentially violate the I2C protocol standard. See my inline comments.
Where should I put |
@sonaux Usually configuration knobs go in |
new commit adds features:
My concern is memory allocation for array of i2c state structures. Now is defined as |
New version is disabled by default by |
You could define it as an array of pointers and have the structures allocated dynamically on demand by the |
Some general comments that if we are going to do a major rework of this driver that we should also consider fixing. And to this end, I've been doing a review of I²C use across all of our modules; none use the direct interface to the I²C driver. Instead they all go through the
This The driver header We also have 5 other entries that aren't used anywhere: Lastly I did find an error in i2c_master_writeByte( (uint8_t) (
(address << 1) + (direction == PLATFORM_I2C_DIRECTION_TRANSMITTER ? 0 : 1)
);
// Low-level returns nack (0=acked); we return ack (1=acked).
return ! i2c_master_getAck(); |
init() is called from gpio_init() at the end of routine.
|
And I haven't looked at ESP32 code yet... I see i2c master software driver is the same and used same way, except that bus IDs 1 and 2 routed to hadrware i2c. It would be very easy to integrate new driver into dev-esp32 branch. |
I agree, and the constants for the hardware drivers could be mapped to higher IDs (e.g. 100, 101) to free up 0-9 for the SW driver. |
@sonaux Natalia, I've pulled your last push version (which is suspect is now behind your local dev PC version) and had a play / looked at the code. My main concern here is the code for GPIO16. I understand and support your goals of (i) supporting fast or better I²C; and (ii) supporting multiple I²C buses. A major issue here is the lack of usable IOs exposed on the ESP8266, and so your desire to have GPIO16 as an option is also understandable. However, the constraints on using GPIO16 are poorly documented by Espressif. GPIO0-15 all share a unified H/W interface and can be set to open-drain and in this case an internal pull-up isn't mandatory (though I use an extra 10K external pull-up resistor anyway.) GPIO16 has a separate H/W interface and it's main purpose allow the RTC to wake the CPU after deep sleep, but it can be used a GPIO so long as interrupts aren't required. It's also used for the key LED. I believe that also has an internal pull-down permanently attached and we therefore configure it as input and must use an external pull-up (and since we are connecting GND to 3.3V through a couple of pull-ups is this creating a ~mA current draw). It is also problematic toggling between output and input, so we can't implement clock stretching if SCL is on GPIO16. So IMO the whole area of using GPIO16 is a potential minefield. It's implementation needs to be properly researched and documented (both in the code and some form of application note. My vote would be to defer this work to a later PR. For example, you've cut and pasted the GPIO16.c functions into the code, but in this case would it not be better just leaving them out of line? Also since including the testing for GPIO16 add extra runtime code paths it would be nice to ensure that this can be optimized out during compile, eg. #ifdef I2C_MASTER_GPIO16_ENABLED
#define IS_PIN16(n) ((n)==16)
#else
#define IS_PIN16(n) (0)
#endif |
Sorry for delay, at this time new driver works in all configurations, but some TODOs and improvements I wanted is not done yet. For example:
|
@sonaux Natalia, don't worry about the timescales. We are all doing this pro-bono so other priorities can intrude. The main thing is that you are working this, so others don't need to step in. Thanks Terry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Please help clarify correct NUM_I2C
for old version.
Fine for me. I dropped it for the next cut. Ideally @sonaux would fix the conflicts in |
If will be trailing spaces. You are going to see a few of these. Easy to resolve on the merge. |
- Standard(Slow, 100kHz), Fast(400kHz) and FastPlus(1MHz) modes or an arbitrary clock speed - Sharing SDA line over multiple I²C buses to save available pins - GPIO16 pin can be used as SCL pin, but it does not support clock stretching and selected bus will be limited to FAST speed.
… lines in ACK read/write
Rebased and changed path to source file in documentation |
@TerryE I currently only have hardware that supports slow speed. Will see if I can try it on the weekend with several of these devices if it helps. |
I just tried this on a 128x64 oled display and it ran at 400kHz just fine (the old code was at 48kHz). It isn't quite that much faster as the device itself inserts delays, but the display updates much better now. (I was using a logic analyzer to look at the speeds on the wire). The only issue that I have with this PR is that you need to comment out a #define in user_config.h to enable the new driver. If this really is backwards compatible with the old code (except that it is much faster), then it probably ought to be the standard. |
I've tried it on my current (slow) I²C devices and this doesn't seem to have broken anything, so I suggest that we merge this into dev. Any objections? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO ok to merge after master drop.
I have tried all avaiable speeds: i2c.SLOW, i2c.FAST and i2c.FASTPLUS with firmware builded by cloud builder but always 47kHz clk speed appeared. |
@Michal78S That's because the default option in the firmware is to use old I2C driver. See https://github.com/nodemcu/nodemcu-firmware/blob/dev/app/include/user_config.h#L175 . |
True, I can do that once it's available on master. Please ping me should I forget that. |
This is working well for me with a ADS1115 and ADS1015. The faster bus speeds allow me to sample at maximum rates. Thanks! |
Did You add this option to the cloud builder? I'm still waiting for it ... |
You need to keep an eye on that. It depends on https://github.com/nodemcu/nodemcu-firmware/milestone/13 |
Ping, ping... |
Is there any chance to put this solution to the cloud builder? |
Maybe we should undefine I2C_MASTER_OLD_VERSION by default. |
I have just tried this new driver with 3x mcp23017 at |
Fixes #2305.
dev
branch rather than formaster
.docs/en/*
.Following @TerryE suggestions in #2305 for use of rsr.ccount instruction I was able to get good formula for calculation of delays.
Old delays did not take in account that running code takes some time too, and function i2c_master_writeByte contained extra unneeded (i hope) calls for setDC and delays, so overall speed was 35-40 kHz, not 100.
I tested module with all I have: good old analog oscilloscope, couple of displays (u8g2.ssd1306_i2c_128x64_noname, u8g2.ssd1306_i2c_128x32_univision), sensor BMP180, chip EEPROM 24LC01B.
SCL speed is independent of CPU clock speed until 250kHz, where delay reaches zero at CPU80MHz and ~400kHz clock achievable only at CPU160MHz.
Also I tried to replace gpio_output_set() with assembler code (s16i [gpio_mask], [gpio_addr]...), but only got 300 kHz SCL at CPU80MHz.