Skip to content
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

Reduce code duplication for timeout handling in twi.c #1

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

hlovdal
Copy link

@hlovdal hlovdal commented Feb 17, 2019

Hi. I have some improvements to your issue_#42 branch. First I fetched the original commits from the original issue_#1476 branch in the Arduino repository to keep the history intact and rebased to the latest upstream commit. Then I introduced a macro for timeout handling to reduce code duplication and increase readability. There are also some code formatting cleanup, let me know if you want this in a different pull request.

Do a pull of upstream master branch before completing this pull request or let me know if you want me to rebase down to your latest master.

algernon and others added 14 commits December 11, 2018 00:01
Based on code originally by Rob van der Veer <[email protected]>, this adds
USBDevice.isSuspended(), so user sketches can run custom code in their `loop`
methods after checking if the device is suspended or not.

Signed-off-by: Gergely Nagy <[email protected]>
Use of the stk500v1 protocol for Arduino as ISP does not work with native USB boards on Windows. The arduino protocol does.

However, the arduino protocol makes it more likely that boards with an external USB interface chip will require the auto-reset circuitry to be disabled to allow them to be used as Arduino as ISP. That adds extra complexity to a process already difficult for the average Arduino user.

For this reason, a new programmer using the arduino protocol is added specifically for using native USB boards as Arduino as ISP and the previous Arduino as ISP configuration is retained for use with all other boards.
…programmer

Add ATmega32U4-compatible Arduino as ISP programmer
These are currently implemented by the Wire library, on twi.c
Remove commented out code for I2C interrupts on WInterrupts.c
@wmarkow
Copy link
Owner

wmarkow commented Mar 3, 2019

Hi @hlovdal, thanks for your input in this case. Indeed - my solution introduces some code duplication. I will take a look at this soon.

In your application is the issue (hang in endless while loops) easy to reproduce?

Copy link
Owner

@wmarkow wmarkow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done some review.


// update twi state
twi_state = TWI_READY;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hlovdal , is here (before waiting_timedout label) a return; statement missing? Currently the TWI is always disabled and initated again, no matter if it was timedout or not. Am i rght?

goto timedout_label; \
} \
} \
} while (0)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hlovdal, Is this do {} while(0) a way to put the macro around a simple code block, so the BUSYWAIT_WITH_TIMEOUT_UNTIL macro can be put multiple times in the same function and the compiler will not complain about multiple startMillis variable definition?

// executed in interrupt service routines where millis() cannot be used.
// https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/:
// "millis() relies on interrupts to count, so it will never increment inside an ISR."
// TODO: Document calculations behind the 25000 upper counter limit.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this pull request
arduino/Arduino@5e0fd77
similar solution has been done but they relied only on the max counter of 100000 iterations, not the exact time in milliseconds. The milliseconds aproach is independent from the cpu speed. However in the ISR we can not use millis().
I think I have also took a short look at the disassembled listing of twi_stop() method in one of my project. Roughly it contains around 20 instructions. I have assumed average 2 clock cycles per instruction which will give 40 clock cycles. With 16 MHz clock I did the math: 40 * 25000 / 16000000 = 0.0625s = 62.5 ms as the value of timeout. I'm not uite sure if I calculated this correctly :)
I think - if we goes this way - it would be possible to implement some macro that will calculate the upper counter limit based on current cpu clock (I think the clock value is defined as some variable somewhere - F_CPU?). I see those macros already defined in Arduino.h:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

I will figure something out.

// https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/:
// "millis() relies on interrupts to count, so it will never increment inside an ISR."
// TODO: Document calculations behind the 25000 upper counter limit.
// TODO: Make the upper counter limit reflect the twi_timeout_ms value.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems to be - more or less - to be defined by a similar macro as commented above.

@Koepel
Copy link

Koepel commented Jun 29, 2019

@wmarkow Could you delete the post at mathertel/Radio#27 ?
You could have dropped a message here: https://github.com/Koepel/How-to-use-the-Arduino-Wire-library/issues or send me a Personal Message in a forum. I have some notes, but perhaps I should write them at hlovdal ?

The Wire library is part of the Stream family.
If it is possible to override the Stream::setTimeout() for the low level I2C communcation then I prefer that.
The timeout of the Stream class is however a higher level timeout, but that high level timeout is irrelevant for the data which was read from the I2C bus. The packet of data has arrived or not, there is no timeout needed to read more bytes.
For example, when using Wire.readBytes() the timeout of the Stream class is used, which is not okay. There will never be more bytes.

The Wire library needs a low level timeout for the bus, but when using the higher level Stream functions (for example Wire.readBytes()), then the higher level timeout should be zero. I don't know what the nicest solution is for the low level and high level timeouts.

The name setTimeoutInMillis() is confusing. The setTimeout() is already in milliseconds.

The 'goto' is not needed in my opinion. The SDA or SCL could be shortcut to GND or to each other. That is a situation that might happen, and it can be handled with normal if-statements.

Nevertheless, an embedded system that can be stopped by an event from the outside is in my opinion a nightmare. So any timeout in the Wire library is better than no timeout.

@wmarkow
Copy link
Owner

wmarkow commented Jun 30, 2019

@Koepel, @mathertel, sorry for inconvenience - I have already deleted my post in mathertel's issue#27.

@Koepel, thanks for your input in the case. I will take a look at this setTimeoutInMillis method. Indeed it may be confusing since Stream::setTimeout() is available.
For know I have no idea how to avoid those goto without any code duplication. I my solution there is no goto but there is some code duplication. Hlovdal moved this to a macro which makes that code duplication is gone but the code readibility is also a bit harder.

Anyway thank, I will take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants