-
-
Notifications
You must be signed in to change notification settings - Fork 129
Breakage caused by PinStatus and PinMode types #25
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
Comments
I can confirm more code in the wild which does an ifdef check on INPUT_PULLUP (and falls back to pinMode & digitalWrite), because INPUT_PULLUP was added to Arduino later than the others. |
One way to fix that, without sacrificing the enum, is to add As for the convert-from-boolean point, I guess one could introduce a class type to wrap these constants and add the appropriate conversion operators on them, though that might become a bit more complex than one would like. It does keep the advantage of separate types for each of these. It would also allow splitting PinStatus and SignalFlank (or whatever), and still allow using |
The digitalRead can be handled with an operator...
|
The digitalWrite can handled with an overload...
Ideally, the unsigned / bool function is placed in a header file and marked to be inlined. |
Maybe a stupid question: Why does |
I agree with Floessie - having digitalRead/digitalWrite use pinStatus() is no better than the code that expects/passes booleans. There is no reason to overload the enum with functionality from two separate sets of functionality... |
Today another report came in of a popular library broken by this: Minimal demonstration sketch: #include <LiquidCrystal_I2C.h>
void setup() {}
void loop() {}
Reported at: |
Could something like this work for everyone? (it is from the Lines 1 to 16 in 55cbc08
|
@facchinm this is probably a dumb question, but it's not clear to me exactly how this would be implemented. I get the idea of overloading the functions. But I was trying to modify the Arduino megaAVR Boards core code to test this solution against the known issues reported so far in the thread. I tried just replacing the |
@per1234 it's not a dumb question at all! |
Thanks for the explanation! I agree that the amount of C code in existence affected by this issue is probably very small. I hadn't considered that the solution would be limited to C++/.ino. |
I'll have to agree with @WestfW and @Floessie on this one. I understand that @facchinm tries to provide a solution that will keep the new implementation, but what do we need this new implementation in the first place? It only leaves ut with a workaround that's not compatible with C libraries, not any benefits as far as I can see. |
I'd be interested to know what problem is solved by introducing the PinStatus type. If this is about type safety, then I think there is such a thing as getting carried away. For example, it seems perfectly natural to pass a bool as the second argument to digitalWrite(). In that case, introducing PinStatus is not only confusing but seems like reinventing the wheel, as the language already provides a completely adequate type. If in fact this change is about type safety, then I'd have to wonder how big the problem was in the first place. Perhaps worse, the current situation is inconsistent between various boards, with megaAVR architecture demanding a PinStatus type (e.g. for digitalWrite), and regular AVR being more permissive. In fact, PinStatus is not defined for the AVR architecture. |
Jack! I hope you've been well! Excellent question. I can imagine two possibilities.
|
Question 1: Looking at However, on a normal AVR, CHANGE is defined to be 1 (see Is this intended? Question 2: Is there a |
Hi @bxparks
|
Hi @facchinm, I don't use The reason for asking is that my AceButton library stores a value of #if HIGH != 1
#error HIGH must be defined to be 1
#endif
#if LOW != 0
#error LOW must be defined to be 0
#endif Which enforces that my "UNKNOWN" value is different from either Thanks for the pointer to |
The whole idea of HIGH and LOW is to hide the implementations details (HIGH could be 0 and LOW 0x12 in some random funky architecture, but your Arduino code will run seamlessly). |
Overloading digitalWrite will break much of the Arduino ecosystem. A tremendous amount of code has been built up over the last decade equating digitalWrite & digitalRead values with the C/C++ convention of zero representing logical false to mean LOW and non-zero representing logical true to mean HIGH. |
@PaulStoffregen of course most (all?) the architectures are sane and HIGH is 1 while LOW is zero but since the APIs don't specify the value of those macros/enum/whatever (and never did) the code runs "by chance" 🙂 |
I guess I'm having a hard time seeing what advantages the I also understand that writing: pinStatus = (pinStatus != LOW)? LOW : HIGH; is probably more clear to most people than pinStatus ^= 0x1; but it would be nice to have the option of writing the second form in places where it makes sense (e.g. deep inside a library where app developers are not expected to visit often). The XOR can save a few bytes of flash memory and CPU cycles, since the compiler cannot optimize the first into the second. (Interestingly, when I wrote the first version using With regards to In any case, it looks like the new #if (ARDUINO_API_VERSION >= 10000)
...
#endif to target different platforms appropriately. |
|
That sort of code is pervasive throughout the Arduino ecosystem. I fear these API changes could bring widespread misery. |
I tried to protect myself against those assumptions with my compile-time checks, e.g.: #if HIGH != 1
#error HIGH must be defined to be 1
#endif
#if LOW != 0
#error LOW must be defined to be 0
#endif But even that breaks under this new API. :-/ |
That's presumably because HIGH and LOW are now part of an enum rather than mere constants. You can probably do:
Since HIGH and LOW are still compile-time constants, this won't normally produce any actual code, but they no longer need to be preprocessor-time constants. (There are a bunch of places where it might be useful to use this sort of thing in the Arduino code. But that's for another time/issue!) |
(This is getting slightly off topic, but hopefully it's useful to someone else facing a similar problem with compile-time checking of @WestfW: Thanks for the pointer! I didn't know about the
After reading the GCC guide on #define CONCAT_(x, y) x##y
#define CONCAT(x,y) CONCAT_(x,y)
#define COMPILE_TIME_ASSERT(cond, msg) \
extern char CONCAT(compile_time_assert, __LINE__)[(cond) ? 1 : -1];
COMPILE_TIME_ASSERT(HIGH == 1, "HIGH must be 1")
COMPILE_TIME_ASSERT(LOW == 0, "LOW must be 0") Works on both a |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
For what it's worth, I decided to explicitly blacklist the ArduinoCore-API for most the 15-20 Arduino libraries that I publish publicly, using something like the following at the top of the main header file: #if defined(ARDUINO_API_VERSION)
#error Platforms using ArduinoCore-API not supported
#endif One, it gives a short, understandable error message to the end-user, instead of hundreds of lines of inscrutable compiler errors. Two, it completely stopped the repeated bug tickets against my libraries, saying "Your libraries does not work with [MegaAVR | Nano Every | RaspberryPi Pico | MKR | etc...]". Three, it saved me dozens (maybe hundreds?) of hours of development and debugging time that I don't have to spend porting my libraries to the new Arduino API, and hundreds of dollars for buying the various microcontrollers that I no longer have to test and validate. |
FWIW, I'm keeping an eye on this with concern about how this is likely to affect the Arduino ecosystem, and eventually what I'll need to support on Teensy boards. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I like that the PinStatus enum names LOW = 0 and HIGH = 1 as used in the text of : https://docs.arduino.cc/built-in-examples/basics/DigitalReadSerial from
and the
line from https://docs.arduino.cc/built-in-examples/communication/SerialCallResponse It doesn't seem fair to call depending on LOW=0/HIGH=1 as bad code if the reference documentation has been pushing it for years On the other hand, isn't this PinStatus enum inconsistent with the either-or text from https://www.arduino.cc/reference/en/language/functions/digital-io/digitalread/ and And doesn't this break expectations for this code: If you call |
IMO this whole concept of pin enums was something that would be great if we were starting from scratch low end ARMs where we could afford to piss away some flash like that. But we''er on AVRs, there's a long established tradition of the 0/1 being legal for digitalWrite, and it's easy to come up with plausible use cases and common ideoms where it falls over. That's why I 86'ed it from my cores. (the whole Arduino API was sort of a disaster IMO - at this point it's been pretty much gutted on my cores as people have complained (with good reason) about the bloat it introduced (virtual functions are the devil's creation). |
If we were starting from scratch, "pins" should probably have been objects. |
Why are virtual functions the devils creation? Please inform me about the performance cost you think they'll incur ;) Note: I'll grant you that they do incur a cost, but not as great as many people apparently think. |
One issue with virtual functions is that they will get linked in regardless if they are used. |
I'm only just running into this issue in Arduino CI; I found this thread by running a google search for the actual definition of Arduino CI enables unit testing an Arduino library by mocking the entire set of Arduino built-ins, such that they can be fed scripted inputs and have their outputs analyzed. See the definitions of To make code more testable in a library that I am writing, I prefer to follow the dependency injection pattern. This simplifies the problem of testing, since I can precisely simulate the inputs I want my library to handle by writing a mocked version of But what is the correct type signature of this function???For this to have 2 different signatures for 2 different boards spells disaster for any library that wants to work for multiple boards -- it looks like I have to pick one signature or the other, and using preprocessor directives to sort this out seems like a very unsustainable way to go. How can I encapsulate the possibly changing signature of this function in (1) an Arduino library that I write and (2) my mocks for testing Arduino libraries as part of Arduino CI? |
Addendum, even overloading isn't an obvious solution here, because in some cases |
Number 1 rule: never abuse the API and use the APIs the way they are documented.
You do things like when you call functions like digitalWrite() always use HIGH or LOW for the value parameter, not a variable. For digitalRead() always compare the return value to LOW or HIGH and then do things based on that evaluation. My libraries do this and it didn't have any issues with the different types for HIGH and LOW. If you are trying to do something special or out of the ordinary for testing / emulation purposes, again you cannot ever assume any type for the symbols HIGH and LOW given the way the API is documented and defined. If you for some reason absolutely need their type you can get to them by using decltype() So I would think that between a clever combination of use of macros to remap function names and the use decltype() you could get to anything you want / need for your simulation / emulation purposes. stuff like
You could even create a typedef for the decltype(HIGH) that would hide this away |
Untyped object-like macros, IMHO this is half the problem with the API.
…On Tue, May 2, 2023 at 8:59 AM Ian ***@***.***> wrote:
I'm only just running into this issue in Arduino CI
<https://github.com/Arduino-CI/arduino_ci>; I found this thread by
running a google search for the actual definition of digitalWrite, and so
far this discussion is very discouraging.
Arduino CI enables unit testing an Arduino library by mocking the entire
set of Arduino built-ins, such that they can be fed scripted inputs and
have their outputs analyzed. See the definitions of HIGH and LOW
<https://github.com/Arduino-CI/arduino_ci/blob/6f326f8070e619802ff6efe07bc3340313806d1f/cpp/arduino/ArduinoDefines.h#L5-L6>
and digitalRead/digitalWrite
<https://github.com/Arduino-CI/arduino_ci/blob/6f326f8070e619802ff6efe07bc3340313806d1f/cpp/arduino/Godmode.h#L166-L167>
respectively.
To make code more testable, I prefer to follow the dependency injection
<https://allencch.wordpress.com/2017/12/05/c-unit-test-and-dependency-injection/>
pattern. This simplifies the problem of testing, since I can simply write a
mocked version of digitalRead and use it to precisely simulate the inputs
I want my library to handle. All I need to do is declare the precise type
of the digitalRead function.
But what is the correct type signature of this function???
For this to have 2 different signatures for 2 different boards spells
disaster for any library that wants to work for multiple boards -- it looks
like I have to pick one signature or the other, and using preprocessor
directives to sort this out seems like a very unsustainable way to go.
How can I encapsulate the possibly changing signature of this function in
(1) an Arduino library that I write and (2) my mocks for testing Arduino
libraries as part of Arduino CI?
—
Reply to this email directly, view it on GitHub
<#25 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANSCUI5C3ZFFPIRA74LY3VLXEEOL5ANCNFSM4GM5J24Q>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Thanks for the tip on decltype() @Michael-Brodsky hits the nail on the head - #define was horribly overused throughout the API. And where things were typed, int's were overused and uint8_t's underused.... But now it is too late to fix most of this stuff - there's too much code in the wild, and we long ago reached the point where the API was set in stone effectively. We can't go changing it without destroying compatibility short of straight-up magic, where one could shout "abracadabra!"* while brandishing an illuminated, arduino powered wand, and have all the code in existence changed to match a new API *I've been told that this was originally an invocation of the demon-god of numbers and math; I don't know enough about such mythology to know if that's true or not, but that's the demon upon whose power you'd need to call to retroactively correct an API. Based on the absence of anyone working such magic, I conclude that the demon must demand too high a price for such alterations of reality (I certainly would if I were such a demon - it looks like a lot of work, even with unholy supernatural powers you'd expect of that sort of entity). It could also be that demons like that don't exist, and we'd need a full on time machine, and physics has pretty much constrained those out of the realm of possibility (well, for backward time travel. Forwards time travel is easy. I just used a time machine to travel forwards a half hour writing this crap) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Arduino's incredible success has been attributed to design for ease of learning and lowering barriers for new users who may have little or no programming experience. Traditionally, removing novice user barriers has been of paramount importance. In the many years Arduino had a developer's mail list, over and over heated discussions involved programmers arguing Arduino should more closely follow certain programming standards. Time and time again, the Arduino developers would explain Arduino's not-so-standard design choices to simplify learning, usually commenting that numerous other far more powerful IDEs exist on the market for professional programmers who want standards compliance, and that people who choose Arduino IDE do so because it is an alternative where very different design choices were made to prioritize ease of learning. Maybe Arduino's goals have shifted in recent years? I really don't know. They certainly are now selling "Pro" products. I don't speak for Arduino, and since the pandemic I've not been to any conferences where I've had an opportunity to speak with Arduino people in person. But regarding what is and isn't the goal, your (perhaps rhetorical) question, traditionally Arduino's overriding design goal has been simplicity for novice users, especially removing barriers beginners and non-programmers experience while using it to accomplish their projects. Seeing all this focus on ISO C++ standard compliance, done in a manner which causes compile errors with so many programs already published in the Arduino ecosystem, really feels like Arduino priorities & values may have shifted from the formula that paved Arduino's popularity and success in a market filled with far more advanced and standards compliant programming tools. |
I think we are in perfect agreement here, and would point to the 5833 libraries in the library manager as evidence of how many people are working to make the novice user experience more accessible. Would you agree that the novice user is the one who ultimately benefits from improvements to the quality and breadth of libraries (in that they can avoid writing all of that code from scratch, for any given algorithm, data structure, or attached circuit)? I'd also point out that there is an active community of developers; the forums suggest 372 forum topics per week are discussed. Would you agree that the ability for advanced users to demonstrate bugs through unit tests -- directly copy/pastable into a novice's project -- is at least as useful as an advanced user trying to explain the (mis)behavior of a code snippet in English? TL;DRI agree completely that removing barriers for novice users is the goal, and I don't mean to suggest any features that would detract from that goal. What I am suggesting is that at the individual level we lack continuity between the novice, intermediate, and advanced skill sets, and at the group level we lack some mechanisms that could greatly improve collaboration. |
Would someone have a hint on how to write a generally compatible declaration like:
but above fails on the frameworks that don't define PinStatus :-(
but above fails on the frameworks that do define PinStatus :-( I'm finding it really troublesome to maintain compatibility based on checking a bunch of #if defined(TARGET_RP2040) etc |
I would assume something like: |
This project changes
LOW
,HIGH
,INPUT
,INPUT_PULLUP
, andOUTPUT
from macros to enums:ArduinoCore-API/api/Common.h
Lines 10 to 23 in e1eb8de
I'm concerned that this will cause breakage of a significant amount of existing code.
An example is the popular Keypad library. Compilation of both the original library and the version in Library Manager fails once this change is made.
This commonly used code will also now fail:
I understand that the root cause of these errors is bad code and that any code which followed best practices will have no problems with this change. However, I fear there is a lot of bad code in widespread use that currently works fine. In the case of the Keypad library, it is unlikely it can even be fixed since Chris--A has gone AWOL. I'm sure there are other such abandoned projects.
I do like the spirit of this change (though lumping
CHANGE
,FALLING
, andRISING
intoPinStatus
is questionable). I'm open to being convinced that it's worth the breakage and, if so, I'm willing to help ease the transition by providing user support and submitting PRs to fix broken code. I just think this warrants some consideration before ArduinoCore-API goes into more widespread use.Some previous discussion on the topic:
digitalWrite
has a wrong signature ArduinoCore-megaavr#68The text was updated successfully, but these errors were encountered: