-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Expose CDC settings to sketch #3343
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
Conversation
Please resist the urge to use weak linked symbols as official published APIs. We already have serialEvent() as an example. Libraries that take a Stream or top-level class reference or pointer can't use serialEvent(), because the actual function name has to be used in the code. A common pitfall for novices trying to use serial events for Serial1, Serial2 or Serial3 involves getting the number in the wrong place in the function name. There's no compiler checking for the name or args or return types, because the function isn't declared in a header. For these and other reasons, weak linker symbols are generally a poor practice for public APIs. |
Good point. Having a proper callback is probably better, at the cost of two bytes of RAM, which seems acceptable. Making it more general might be a good idea then as well, having a callback that gets called on all setup packets? How would this function be named? Should the callback function return a bool and how should it be handled? Should returning true cause |
3ec54b9
to
b38cbc6
Compare
I just updated this PR, replacing the SEND_BREAK weak function with a more general |
Isn't this api (a bit) too low level? It is on the USB level rather than on the level of a serial line.
I would rather see the callbacks as a notification of the sketch and not let the sketch impact the driver behaviour more than necessary. More specifically: lineStateEvent() returns void: if the line state changes the sketch cannot/need not do anything about it. I like your idea of letting the callbacks take no arguments: baud rate and friends can be retrieved via your getters. |
Well, better is relative. Weak linking should usually be avoided for published APIs, so in that respect, yes, better. But whether it's a good idea to add interrupt context callbacks as an Arduino API is another question! Even experts have a tough time properly dealing with the pitfalls of interrupt context. At the very least, good documentation and examples that really do show volatile variables and proper use of noInterrupts() should be written. Exposing very low-level USB details should also be considered carefully. Just because you can do a thing does not necessarily mean you should do that thing. The non-callback stuff that corresponds to well known serial parameters look good to me. |
You are right that the callback is very low-level. Having multiple higher-level callbacks would be better, except that figuring out the list of methods we'd need is tricky. This would likely cost a lot of effort for some features that won't be used much and mostly by advanced users. Even if we come up with a proper API, it is likely that we'll miss a usecase. Even more, every callback takes up 2 bytes of RAM, even when it is unused. For these reasons, I think that a single low-level callback might be better. It enables a lot of usecases, against a minimal cost (both in terms of engineering time and RAM overhead).
I think that was your proposal?
Agreed. Of course, the reason for doing so is, in this case, that I need to forward SEND_BREAK requests from the CDC side to the UART side (and possibly also get notified about set line control and line state requests, though it seems I can get by with polling this for now). |
I realize I made a reasoning step I did not explain: if e.g. the baud rate gets changed, your on_cdc_setup_packet(const Setup&) callback does not pass the new baud rate: the sketch needs to get to get that using your getter (the baud rate is not in the Setup packet, it is in the 7 byte payload). Having read Paul's post I realize that working without callbacks may be a good option. You may have seen my write up about using the Leonardo as a serial converter. I think I can implement that use case, just with the getters: the sketch can check for a baud rate change or a line state change from the arduino loop(). The use case involves setting the uart's baud rate and forwarding DTR to an arduino pin. There may be a need for a line state change api in order not to miss a DTR drop pulse though. I never went further and created a change request to the arduino developers (like you do now) because I was not comfortable about the api and found nobody to exchange ideas with. Now Paul already ironed out the weak symbols (I used them too) and confirms the concerns I had about callbacks in irq mode. I don't understand very well (because I never used it myself) the use case for the SEND_BREAK. Do you need it for the xbee device you want to work with? To have an idea of what feature set to implement, I looked in the linux kernel to see what they do. The (device side) linux acm driver does not handle SEND_BREAK either. Basically they implement the same feature set as in the arduino core: line state and line encoding get/set. (cfr. drivers/usb/gadget/function/f_acm.c) Well. Whatever you come up with, I'll be happy to test it with the use case mentioned above. |
This may or may not help, but Teensy has long supported some of these features (from before Leonardo was created). The CDC baud rate is accessed with Serial.baud(). No complex callbacks or very low-level USB details are exposed to sketches. Here's the USB serial converter sketch for Teensy.
|
Yes, for a basic version, just polling the getters is sufficient (in fact, that's what I'm doing in my sketch as well, since I can't handle baud changes in an ISR, so setting a flag and polling that isn't really a lot easier, perhaps only a bit faster, than polling the accessors).
When talking to XBee devices and doing firmware upgrades or recovery, breaks can be useful. A break essentially means to pull the TX pin low for > 1 byte time. The XCTU tool uses this to pull DIN (the XBee's RX pin) low. Some of the XBee modules will stick in the bootloader if DIN is low at boot time (usually combined with some state of DTE and/or RTS). Ideally, I'd also be able to sample CTS and report that back to the host, but it turns out that there really is no way for a CDC device to report CTS through USB - it's simply not part of the protocol... |
I see. Break detection can indeed be a useful feature. Out of curiosity, I tried out an FTDI serial converter: not too surprisingly it forwards breaks correctly to its tx line. So an usb2serial sketch should be able to do that too. We could leave it to sketches to implement that using your on_cdc_setup_packet() callback as a hook to extend or customize the driver. But for break detection it equally makes sense to implement an api for direct use in sketches. Something like:
B.t.w., the best way to document/explain/motivate this feature request is to come up with a sketch like Paul's that uses the new api's to build a complete, usable usb2serial converter. |
Not sure if these would do the trick, since a "break request" from the USB host includes a break time (or, can specify that the break should be maintained until explicitely cleared by the USB host). Or perhaps I'm misunderstanding how you wanted these two methods to behave.
Excellent point. I already have that, so here it is:
Note that this directly touches the UART registers as well, so if we really want to support this usecase properly, we should also modify HardwareSerial to support sending breaks. |
I really hope these APIs can be thought out well enough that people aren't driven to accessing AVR-specific UART registers. Quite a lot of that type of code still exists, leftover from the many years before Serial.begin() supported non-8N1 encoding and Serial.availableForWrite() allowed non-blocking transmit. It's a terrible pain when people try to use non-AVR boards, which are becoming far more common lately. Even though I've said this many times, I'd like to repeat again how difficult interrupt context callbacks are, even for experts. In the code above, "breakEnd" is a 4-byte variable that's read and written in the interrupt, and read and written in the main program. It's not declared volatile, and there's no attempt at assuring atomic access. While these issues can be fixed in examples, when I see such code posted by the most talented and experienced Arduino developers and contributors, I feel very skeptical that novice Arduino users will ever be able to use such APIs without extremely difficult to diagnose troubles. |
Hm, excellent point. I just pointed out exactly this problem in another pullrequest, but hadn't properly looked at my own code yet :-) So, if we'd want to turn this into a proper API for break, how would that look? If polling is ok (which I guess would be, at least for this usecase, a single |
The api should be such that for regular use cases no irqmode callback is needed, right?
I would not do that. Time would start ticking from the moment you receive the CDC_SEND_BREAK request. This implies you must initiate the desired action from the irq. That is only possible via a callback, which is not what we want. I would rather have a getter that just returns the last break duration sent (and correctly synchronized with the irq handler of coarse). The problem with this approach is that if the host sends break(0xffff), and later on stops the break condition by sending break(0), the sketch might never notice the break at all if it was doing a lengthy operation. That is where the duo Alternatively, this situation could be simply ignored (but what is the point in creating an api that works most of the time?) . In Paul's sketch there is a similar possibility (at least theoretically) that the dtr() could toggle without the sketch noticing.
The most logical thing to do about this is add sendBreak() to HardwareSerial. We could also start out with a simple Serial.end()... |
Speaking of which: Serial_ has no availableForWrite() yet... |
That's still under discussion, but I mostly posted the sketch that works with the current version of the pullrequest, to get a bit more feeling for the usecase.
Ah, good point. So, a scheme where the break duration is returned only once seems better, then.
That's true, but one might wonder if there is still any value in interpreting a break even after it has been lifted. It does seem elegant to offer the choice to the sketch, but I'm not sure if this is possible without a signifcantly more complicated API? Ideally, yu'd keep a list of events, along with timestamps, which can be read by the sketch in order (including e.g. dtr-on / dtr-off events, to fix the missed dtr issue). Of course, this opens an entirely new can of worms regarding complexity and memory allocation, which I think we'll not want to get into. I'm not entirely sure if I understand your proposal. I think you're saying that If so, I guess that making the |
Yes that's it! The Btw, I think after a 'break 0', Now the breakAvailable() is not there anymore, I would not call the getter Another possible attention point: I hope in sketches the 0xffff does not get widening casted to a value of -1. (It normally does not since 0xffff is unsigned. Only a bug like this would cause a problem:
and it will give a compiler warning: signed-unsigned comparison) |
Looking at the rules for literals, I think this should be ok. 0xffff will end up as a positive So, the proposed API is a single
Did I get that right? @PaulStoffregen & @cmaglie, does that sound ok to you? |
Yes. I did some testing with the first part of the pull request: I used Paul's sketch verbatim. I noticed the sketch uses I added Serial_.availableForWrite(). I guess I'll have to make a separate issue for this? My changes are here (a fork of your branch). |
I like
Having a separate pullrequest might make it easier to merge both. Care to create one? This should probably also modify the SAM version of CDC.cpp (which I think is very similar). |
This allows a sketch to find out the settings chosen by the USB host (computer) and act accordingly. Other than reading the DTR flag and checking if the baudrate is 1200, the regular CDC code doesn't actually use any of these settings. By exposing these settings to the sketch, it can for example copy them to the hardware UART, turning the Leonardo into a proper USB-to-serial device. This can be useful to let the computer directly talk to whatever device is connected to the hardware serial port (like an XBee module). The Teensy core already supported these methods. This code was independently developed, but the method names were chosen to match the Teensy code, for compatibility (except that `dtr()` and `rtr()` return `bool`, while the Teensy version return a `uint8_t`). This change is applied to both the avr and sam cores, which have a very similar CDC implementation.
This allows detecting when the USB host sends a break request and what the value of the request was. See the comments in USBAPI.h for details. This just modifies the avr core, not the sam core.
end() already waited for the buffer to be empty, but then there could still be two bytes in the hardware registers that still need to be transmitted (which were dropped or kept in the buffer, depending on the exact timing). This changes the wait loop to a call to the flush() function, which already takes care of really waiting for all bytes to be transmitted, meaning it is safe to turn off the transmitter.
This makes the CDC "Serial" object on the Leonardo and similar boards support this recently introduced method as well. The CDC code in the sam core is not changed.
b38cbc6
to
4f87112
Compare
I updated this pullrequest:
With this commit, I was able to compile Paul's example unmodified, for the Leonardo. By adding a dummy I also tried adding As far as I'm concerned, this PR is ready to be merged. Any thoughts? |
@PaulStoffregen, @PeterVH, does this PR look ok to you now? |
Looks good to me, it seems to address all the issues. I'm merging it, let's continue the discussion here if there are more concerns. |
I want to build an USB-Serial Bridge, similar to paul but now with a leonardo or hoodloader. I also need to reset the mcu with a DTR change to implement a full Arduino-Serial converter sketch. I just dont understand how to use the break api. May someone add this to the docs? |
@NicoHood The break API is only when you need to send break signals (where TX is pulled low for > 1 byte time), which you likely don't need. The rest of this PR is useful for implementing a converter, by simply polling the |
@matthijskooijman : Sorry for the late response. I am glad this is merged: it is great functionality and the api feels good. During my vacation, I already used it on my Leonardo, with Paul's usb to serial sketch. It works great! |
I'm a bit lost, where exactly in the repo is the sketch code for usb serial converter ? |
@Netoperz, I don't think this is the right place to ask, and I'm not exactly sure what you're asking. If you want to know what part of the code handles the USB communication on a 32u4-based board (like the Leonardo), that code is here: https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/CDC.cpp and here: https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/USBCore.cpp |
There was a sketch some time ago, that was converting Arduino leonardo in to a USB <==> serial adapter, with dynamic baud rate detection. Some forum posts were pointing here. I'm looking for that sketch, because on OS X (after resent updates from apple @#$%!^) FTDI chips are not working correctly, even with signed drivers, when the FTDI part to usb bridge is detached and attached to the same USB socket is not recognized until reboot. There is no problem with boards with Atmega32u4, so i figured out, that best solution "on the go" will be to make Arduino Pro Micro or other leonardo to work as a UART to USB converter :) sorry if this is wrong place, i just followed wrong link probably. |
The sketch you are looking for, is above in Paul Stoffregen's comment. |
@Netoperz, ah, I misunderstood your question then. Indeed, the post @PeterVH has a sketch that I think should work with the current code. In this blogpost, I've published another (slightly more complete IIRC) version of such a sketch (scroll down to "Forwarding through the Arduino Leonardo"). Regarding your problem with FTDI - The current Arduino Uno and Mega boards use a 16u2 microcontroller instead of an FTDI chip, so you might be able to use one of those as well. |
Having these settings available to the sketch, allows turning the Leonardo into a proper USB->serial converter (without having to hardcode the baudrate), to allow e.g. a computer to talk to an XBee module.