-
Notifications
You must be signed in to change notification settings - Fork 34
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_xxx_lo() and i2c_xxx_hi() are not interrupt safe #5
Comments
In my tests, on the initial lib version, when i working on the first fork of the lib, i increased the speed from the original 10kHz until 160kHz on a 328p@16MHz Another possibility is try to modify the hi-lo pin macro to standard arduino methods (digitalwrite). |
You could make the declarations and macros smarter. Alternatively, to keep it a bit simpler, it could do what it is doing now for AVR, but have a fallback mode that uses digitalxxx() routines when not on avr. The digitalXXX() routines don't have near the overhead on the other processors as the AVR so it is pretty decent compromise. |
@bperrybap, thank you for mentioning this. The digitalWrite(), pinMode() and digitalRead() could be a fall back. For other processors the OneWire.h has macros and inline functions that we can use. |
You can't use cbi/sbi instructions when the register and bit are not known at compile time. The stuff in the OneWire header is what I was talking about where you set the width of the register based on the processor. This can work, but what makes it very messy and not full proof is that in some cases you really want to pick the width based on the Arduino core rather than the processor type and the Arduino IDE does not create a define for the Arduino core so you have to resort to using processor defines which can have issues when there are multiple cores for a given processor or processor type. What you have proposed (using a methodology of picking the register width based on processor) with masking interrupts, but also having a fall back to digitalWrite() (which I call Arduino core code mode - in my libraries that do this kind of thing), will work. I've done lots of looking at this kind of stuff over the years. But in fm's new LiquidCrystal library, I added the code to allow doing indirect port i/o for ARM and Pic32 with a fall back to arduino core code mode. Having been through this over the years, I can say you will spend lots of time looking at compiler output and assembler listings and there will be surprises. The best thing you can do is to have a fall back mode so that for any case that you don't explicitly support, the code can fall back gracefully and still work. In my openGLCD library I provide an override define that can be used to force the code to use the Arduino routines. This is an emergency switch that a user can flip if they are running into an odd situation that "breaks" things. |
These are my fall back macros. Perhaps the OneWire.h has not the ideal functions, since the I2C can be defined with or without internal pullup resistors. #define i2c_sda_lo() digitalWrite(_sdaPin, LOW); pinMode(_sdaPin, OUTPUT) |
If you create a branch for this development, I'll be happy to test it out on Teensy 3.0/3.1/3.2/LC & Chipkit Uno32 with my hd44780 library. |
There are some issues, for example the repeated start does not work. I started to fix that with a test version on my own computer, but then the clock duty cycle got bad. Testato worked so hard on that, to be able to make it as fast as possible. |
The macros i2c_xxx_lo() and i2c_xxx_hi() are not interrupt safe can cause register corruption when the pins being used are in a resister that is also being used by another library that modifies the register at ISR level.
This is definitely the case on the AVR, and depending on the core implementation might also be true on ARM and Pic32 cores (should the code be updated to support the register widths to support those architectures)
The problem is that the macros use this construct:
to clear & set bits.
The AVR has very primitive bit set/clear h/w. (ARM & PIC32 have better h/w).
The AVR h/w implemented bit set/clear operations as bit set/clear instructions vs as bit set/clear registers.
The AVR bit set/clear instructions are atomic; however, they come with a MAJOR limitation. That is you have to know the register and bit to form the instruction.
The avr-gcc compiler has a kludge in it that will use the bit set/clear instructions to create the AVR atomic bit set/clear instructions when possible.
But in order for it to be possible, the port address and bit has to be known at compile time.
If they are, the compiler will generate AVR sbi or cbi instructions.
If they are not, the compiler will generate multiple instructions which consist of
This is an interruptible sequence.
This can and eventually does cause register corruption when another library modifies the same register being updated at ISR. One such library that does this ISR register updates is the servo library. (IR library is another)
Here is an example of one of the timing/sequence that will create the corruption:
WHAMO! the port register just got corrupted!
This is because the port register was modified underneath the i2c_sda_lo() macro so it never saw the modification so it wrote servo library bit back to the way it was before the ISR modified it.
This can be fixed, but it isn't pretty.
Given that the register and pin are configured runtime, the register and bit mask are determined runtime, so there is no way to use the AVR atomic bit set/clear instructions.
The only way to ensure atomicity is to mask interrupts during the operation.
Here are some possible solutions:
Either of this will impact performance.
A simple alternative might be to put the atomicity code in the macros but then have a define that could be turned on to the either enable/disable the atomicity code.
For example, the atomicity code might be put into each macro but then be disable by default and then if there is an issue, the user could turn on a define in the library header to enable atomicity.
Likewise, it could be enabled by default and for those that need/want more performance they could set the define to disable it.
The second option is the most robust but it does cause a performance impact even if when the atomicity code is not needed.
There is no easy "it just works" solution.
The text was updated successfully, but these errors were encountered: