-
Notifications
You must be signed in to change notification settings - Fork 68
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
VBUS - Low Power - Interrupt #125
Comments
Well, just depending on which I/O pin is connected VBUS, so depending on your board. check variants files of your board for For GNAT board, you can see variant.h it's then in variant.cpp you search for So |
I am still pondering that one. Is it good enough to add 2 APIs: USBDevice.enableHotPlug() Idea being that at reset the code detects whether USB is connected or not. After the initial determination it only checks if hotplug got enabled. |
Sot sure what's we are able to do on this point, you know better than I. The idea is to be able to wake from sleep on hot plug and then do some things (like wait 5s to be able to flash the device or whatsoever ) |
Still toying with that. Key questions:
N.b. it is possible that a battery charger attaches to your USB port. So depending upon whether you need to know that the answers to the last 2 questions might be interesting to me. |
It could be a solution and I think :
would be the same thing no ?
In my precise case, VBUS detection would do the trick, but It would be even better to detect a "real" USB connection ! But I guess it's not the same quantity of work ^^' I precise that it's a custom board and I have PA8 well connected to VBUS from my USB connector. |
Mind checking out the code I dropped this evening on github ? USBDevice:
begin() is called automatically as well as the first attach(). So in a nutshell, begin() sets up the whole internals and get USB_VBUS detection going. end() nukes everything. attach() attaches the protocol stack, so that when USB_VBUS gets detected, USB gets going. detach() removes the stack, but keeps USB_VBUS detection alive. wakeup() is the remote wakeup should you be in suspended USB bus mode. attach() means USB_VBUS present, connected() means attached() and the USB stack active, configured() means that the USB host has set the USB configuration (speed, power ...), suspended() means the bus got suspended. enableWakeup() means your USB_VBUS is tracked during STOP, and disableWakeup() disables that again. |
Waow ! I will test that tomorrow ;) Big thanks ! |
Thomas, Just to be sure to do the correct way A)
B)
C)
|
A) Yes. If USB_VBUS is present at startup everything works like before. The USB stack is running. Depending upon the timing you might call the USBDevice.onConnect before the USB_VBUS logic says that the signal is good. So that is race condition I perhaps need to avoid. B) Yes, then onDisconnect() fires. The USB stack is stopped automatically. If you call USBDevice.end() it also nukes the USB_VBUS detection (which you may or may not want to do). C) Yes, onConnect() fires, but the stack automaitcally gets going. No need to wait for anything. Looks like I need to rename attach() / detach() to something else. The idea is that "attach()" is really "if USB_VBUS is present, start the stack, otherwise do so when USB_VBUS is detected in the future". "detach()" is really "if connected, stop the STACK, if not connected, disable the automatic start of the USB stack on detecting USB_VBUS". The APIs are meant for 2 purposes: With onConnect() and onDisconnect() you could control for example as to whether your sketch owns DOSFS, or USB/MSC. With attach() / detach() you can control STOP mode. Remember, as long as USB is connected L0 cannot enter STOP mode (it lingers then in SLEEP). So if it matters, you could call USBDevice.detach() before you enter STOP (for a longer time) and USBDevice.attach() after you leave STOP mode. |
An alternative scheme would be that "setup()" gets called via "USBDevice.begin()" not not with "USBDevice.attach()". Only when SerialUSB.begin() is called USBDevice.attach() is called from there. This would allow you then to install the callbacks before you call SerialUSB.begin(). However in that variant I needed to add a "USBMSC" class, so that a USBMSC.begin() would also call USBDevice.attach() as side-effect. |
Thomas,
Interesting, was not aware of. I saw when measuring power consumption that calling I'll try to write and post a sketch demonstrating this new USB Stuff, it will talk more than writing text. |
Thomas, I tried with mitigated success this following sketch on a gnat board (but should work on any other as soon as VBUS is wired on the correct pin and defined in the variant). It output debug one Serial and also on SerialUSB if it's connected of course See my comments below #include "STM32L0.h"
// Choose FTDI Serial debug Port
// On Gnat D9 (Serial3) connected to RX of FTDI to we can
// see debug even if no USB
#define SERIAL_DEBUG Serial3
#define debug(VAL) { SERIAL_DEBUG.print(VAL); if (debug_USB) {Serial.print(VAL);} }
#define debugln(VAL) { SERIAL_DEBUG.println(VAL); if (debug_USB) {Serial.println(VAL);} }
#define debugflush() { SERIAL_DEBUG.flush(); if (debug_USB) {Serial.flush();} }
void ledOn() { digitalWrite(LED_BUILTIN, LOW); }
void ledOff() { digitalWrite(LED_BUILTIN, HIGH); }
bool debug_USB ;
bool VUSB;
bool configuredUSB;
bool connectedUSB;
bool suspendedUSB;
volatile bool changedUSB;
void usbSuspend()
{
debugln("** USB Suspend **");
}
void usbResume()
{
debugln("** USB Resume **");
}
void usbConnect()
{
debugln("** USB Connected **");
changedUSB = true;
}
void usbDisconnect()
{
debugln("** USB Disconnected **");
changedUSB = true;
}
bool manageUSBState()
{
bool _usb = USBDevice.attached();
connectedUSB = USBDevice.connected();
configuredUSB = USBDevice.configured();
suspendedUSB = USBDevice.suspended();
// Enable debug output thru USB port if all is fine
if ( configuredUSB ) {
debug_USB = true;
} else {
debug_USB = false;
}
debug("VUSB attached="); debug(_usb);
debug(" connected="); debug(connectedUSB);
debug(" configured="); debug(configuredUSB);
debug(" suspended="); debug(suspendedUSB);
debug(" serialUSB="); debugln(debug_USB);
return _usb;
}
void setup()
{
debug_USB = false;
pinMode( LED_BUILTIN, OUTPUT);
ledOff();
SERIAL_DEBUG.begin(115200);
while(!SERIAL_DEBUG);
debugln("\r\n*** Setup Serial Debug OK ***");
USBDevice.onConnect(usbConnect);
USBDevice.onDisconnect(usbDisconnect);
USBDevice.onSuspend(usbSuspend);
USBDevice.onResume(usbResume);
// Don't try to get USBDevice.attached() it's false
// all related stuff may not be ready so classic wait
// USB serial port ready (or not) need to be done
// Wait connected in 8s max
Serial.begin(115200);
while (!Serial && millis()<=8000 ) {
ledOn();
delay(10);
ledOff();
delay(750);
}
if (USBDevice.configured() ) {
debug_USB = true;
debugln("*** Serial USB OK ***");
} else {
debugln("*** Serial USB not configured ***");
}
// Enable usb plug to wake up
USBDevice.enableWakeup();
}
void loop()
{
// Get and show USB State
if (changedUSB) {
changedUSB = false;
debugln("USB Change");
VUSB = manageUSBState();
if (VUSB) {
ledOn();
} else {
ledOff();
}
} else {
debugln ("timer");
}
debugln("Going to sleep...");
debugflush();
STM32L0.deepsleep(10000);
debug("Waked by ");
} So with this sample everything is working fine I can plug and unplug the USB, it's detected. So except I need to wait to be able to wake and do stuff, all is working fine. And once waked debug goes thru Serial and SerialUSB fine because as you see in the logs USB is So I decided to change callback void usbConnect()
{
STM32L0.wakeup();
debugln("** USB Connected **");
changedUSB = true;
} And I'm waked immediately but then, I don't have anymore debug on USBSerial, looking at the logs looks like the So I decided to fire the void usbResume()
{
STM32L0.wakeup();
debugln("** USB Resume **");
changedUSB = true;
} I'm waked after resume, but still no luck, usb never get |
Ok, looks like I got it fixed added a delay in if (changedUSB) {
changedUSB = false;
debugln("USB Change");
delay(2000);
VUSB = manageUSBState();
if (VUSB) {
ledOn();
} else {
ledOff();
}
} else {
debugln ("timer");
} I tried to replace this delay with a loop until here is the final sketch working on my side #include "STM32L0.h"
#include "stm32l0_gpio.h"
// Choose FTDI Serial debug Port
// On Gnat D9 (Serial3) connected to RX of FTDI to we can
// see debug even if no USB
#define SERIAL_DEBUG Serial3
#define debug(VAL) { SERIAL_DEBUG.print(VAL); if (debug_USB) {Serial.print(VAL);} }
#define debugln(VAL) { SERIAL_DEBUG.println(VAL); if (debug_USB) {Serial.println(VAL);} }
#define debugflush() { SERIAL_DEBUG.flush(); if (debug_USB) {Serial.flush();} }
void ledOn() { digitalWrite(LED_BUILTIN, LOW); }
void ledOff() { digitalWrite(LED_BUILTIN, HIGH); }
bool debug_USB ;
bool VUSB;
bool configuredUSB;
bool connectedUSB;
bool suspendedUSB;
volatile bool changedUSB;
void usbSuspend()
{
debugln("** USB Suspend **");
}
void usbResume()
{
STM32L0.wakeup();
debugln("** USB Resume **");
changedUSB = true;
}
void usbConnect()
{
debugln("** USB Connected **");
}
void usbDisconnect()
{
debugln("** USB Disconnected **");
debug_USB = false;
changedUSB = true;
}
bool manageUSBState()
{
bool _usb = USBDevice.attached();
connectedUSB = USBDevice.connected();
configuredUSB = USBDevice.configured();
suspendedUSB = USBDevice.suspended();
// Enable debug output thru USB port if all is fine
debug_USB = configuredUSB;
debug("USB attached="); debug(_usb);
debug(" connected="); debug(connectedUSB);
debug(" configured="); debug(configuredUSB);
debug(" suspended="); debug(suspendedUSB);
debug(" serialUSB="); debugln(debug_USB);
return _usb;
}
void setup()
{
debug_USB = false;
pinMode( LED_BUILTIN, OUTPUT);
ledOff();
SERIAL_DEBUG.begin(115200);
while(!SERIAL_DEBUG);
debugln("\r\n*** Setup Serial Debug OK ***");
USBDevice.onConnect(usbConnect);
USBDevice.onDisconnect(usbDisconnect);
USBDevice.onSuspend(usbSuspend);
USBDevice.onResume(usbResume);
// Don't try to get USBDevice.attached() it's false
// all related stuff may not be ready so classic wait
// USB serial port ready (or not) need to be done
// Wait connected in 8s max
Serial.begin(115200);
while (!Serial && millis()<=8000 ) {
ledOn(); delay(10); ledOff(); delay(750);
}
if (USBDevice.configured() ) {
debug_USB = true;
debugln("*** Serial USB OK ***");
} else {
debugln("*** Serial USB not configured ***");
}
// Enable usb plug to wake up
USBDevice.enableWakeup();
}
void loop()
{
// Get and show USB State
if (changedUSB) {
changedUSB = false;
delay(2000);
}
VUSB = manageUSBState();
if (VUSB) {
ledOn();
} else {
ledOff();
}
debugln("Going to sleep...");
debugflush();
STM32L0.deepsleep(10000);
debug("Waked by ");
if (changedUSB) {
debug("Change");
} else {
debug ("Timer");
}
} |
I need to recheck the code here. I ran into something similar yesterday where the code went throu a suspend/resume sequence, but did not properly restart up SerialUSB (and/or USB/MSC). The USBDevice.configured() tracks directly the state of the USB Stack. So I am puzzeled why that would not show things properly. With STM32L0.wakeup(). The rule is simple. If there is a callback and you have attached it, you need to call STM32L0.wakeup(). If there is a callback and you have nothing attached to it, it will wake you up. The idea is to allow calllback drive code that does not bail out of STM32L0.sleep() / STM32L0.deepsleep(). I am constantly forth and back on this one. On one hand that is a powerful tool. On the other hand, the set of events that will wake you up is so big to begin with that unless you attach callbacks to every single one, you'll be woken up most of the time anyway. |
Thomas, I tested the consumption on gnat with the above sketch, the graph below is when It finished to start and setup of course. Then I removed USB. As you can see you have peak every 10s (it's the wakeup) and between then some small ones may be related to USB looking to happen every 200ms now reseting the same board (with no usb connected), wait the setup to finish and here the new graph, as you ca see all "glitch" in between the 10s sleep are not present anymore |
Noticed something more, |
Thomas: this description of the programming lock-up looks similar to a situation I noticed on my devices during the SerialProtocolTest..... |
Give me a day or 2. Need to rework some other parts of USB first. |
I've recently also been fiddling with USB powersaving (not so much suspend/resume, but I needed to do some runtime detection of being attached or not and shutting down USB sufficiently during sleep). I was using 0.10.0 until now, but it seems these changes will really help my case, so I'll switch to git master next. However, while reading the code and this issue, there's a few things that are still unclear, which I've listed below. Some of these might be misunderstanding on my side, but I'm asking anyway just in case there's a genuine bug in the code. Others are just my feedback on how I am (not) understanding the API as present, as input for improving that API.
That would seem elegant, because it would also prevent showing up as an USB device if you don't call |
That was intentional. A USB_VBUS pin is required going forward (or for STM32L4/L4+/WB detection via PVM1).
Naming is always tricky. The USBDevice.attach() came in with the ArduinoCore-samd heritage. However this conflicts with the conventions that the USB spec uses, where "attach" means that the physical connection is established (connector plugged in, VBUS is present). If you have a better suggestion of how to name "USBDevice.attach()" I am all ears. This function really attaches the USB stack to the connector so to speak.
"connected" should be obvious. It means that the peer (host) has established a connection (bus reset issued, address configured). That is important, because it's possible to have a battery connected via USB. Guess I should add the proper BCD detection logic and add USBDevice.detected() which returns 0 while nothing is detected, and a non-zero when something is detected, and of course what ...
USB_VBUS detection is per default disabled during STOP mode. That is why this API is there. The reason is that there is additional power draw on this pin if you enable detection. So there needed to be a choice.
This is an area of constant rework. The API is modeled more or less after uITRON's "tsk_slp" / "wuk_tsk". Idea is simply that not a "interrupt" condition bails out of sleep/deepsleep, but a event condition. The enableWakeup() / disableWakeup() riffraff might go away. The problem is fundamentally fine control over what constitutes such a event condition. The current model is to statically configure this set (enableWakeup/disableWakeup for defaults, STM32L0.wakeup() for interrupt handlers and callbacks). It might be better to just pass in (optionally) a set/bitmask of events you are interested in to wakeup from. Current problem I am looking at is as to how big such a mask needed to be. RSX11M-Plus ended up doing something similar, but they ended up using 96 bits ...
That is bad naming. Also something I switch forth and back on. "deepsleep" == STOP. POLICY_RUN (used by "delay()") is simple __WFE() without disabling interrupts, while POLICY_SLEEP is SLEEP via __WFI() and bus downclocking. On internal revisions of the core deepsleep() had been replaced again by STM32L0.stop(). |
Thanks for your quick and detailed response :-)
Ok, but what's the reason for this? It seems that the old behavior of assuming VBUS present if there is no pin is reasonable (I even have a commit ready that I wanted to submit a PR for after some more testing). This is also what the USB hardware in STM32F4 implements (that has a VBUS pin for bus voltage detection, but can be disabled to assume VBUS is always enabled). Of course, it is better to have a VBUS detection pin, but that's not always an option (on existing boards, at least).
Hm, I was under the impression that "Attached" was equivalent to "Pullup enabled" in USB standard terminology, but looking through the USB1.1 and 2.0 spec documents, that does not seem to be the case (though I could not even find any mention of the practice of disabling the pullup to force re-enumeration, only some mentions that the pullup must be powered indirectly from VBUS, so it must be disabled when VBUS is gone). Not sure where I got this idea, then.
Yeah, so maybe
Right, that makes sense. I guess I was mostly confused by the implementation, but attaching a debugger I see that the suspend and resume interrupts are indeed triggered directly after a (MCU and thus USB) reset, so I guess that's why the state ends up in CONNECTED. Still, this does not really feel right, since now the state is STARTING when the suspend callback fires, does not switch to SUSPENDED (because that only happens when the state is CONNECTED), and then switches to CONNECTED when the resume callback fires. If this is all intentional, then it's fine, but it feels a little bit like the code has a bug that gets masked because a suspend/resume cycle is triggered because the first SOF doesn't arrive immediately.
Yeah, I totally agree that having this API is useful and essential. Even apart from the power draw, you might not want to wake up your device on VBUS presence as well. My comment was mostly about the naming of the API.
I guess that would make sense. It seems you've even implemented this already (using an "APPLICATION" even right now). Another approach could be to leave this to the user, similar to how the POSIX sleep (and other blocking) functions work (where you specify a sleep interval and the function blocks for that long, but if a signal happens, the sleep returns
Ah, I see now. I was switching between 0.0.10 and master, so didn't notice that it was renamed rather than added, my bad. As for naming: |
The reason is BCD. As soon as VBUS is detected, BCD should be started. If the outcome of BCD is that a host is attached, then the USB stack should be started (and the peripheral enabled). If there is no GPIO for VBUS, you do not get a "attached" event, and all you can do is to start BCD. And if that says "nothing on the other end", you wont start the USB stack later again. STM32F4 ... Not of interest to me honestly. But it uses the same USB_OTG peripheral as far as I recall. That one if you enable it sucks up 4mA. I have not found a way with that one to make use of the builtin VBUS detection without firing up the whole peripheral, so pointless. There might be a way to put the USB peripheral in lowpower mode (USB_FS has that), and assume a "suspended" state and simply wait for the wakeup (which in essence is the same as a reset event on the bus). Haven't tried that ever though. For STM32WB we are using VDD_USB directly. You can use a LED to drop VBUS from 5V down to 3V3, and via PVM1 you can detect the "attach" event. Hence no additional GPIO wasted. So I am reluctant to invest time there.
Not really a big leap. STM32L0.sleep() just needed to return say a bool. "true" says "I timed out", and "false" says "I got interrupted". "interruped" as "seen a STM32L0.wakeup() either direct or implied". It's not quite the same as POSIX there. A signal is something you attach code to. Here if you attach code to something, you don't want to wakeup. You want to let the code you attached to decide whether to wake up or not I guess I have to explain where all of this is coming from. First off I do not believe that it is a wise idea to expose a RTOS at user level for many, many reasons. So a lot of the standard techniques that imply a RTOS (or POSIX) are not applicable. You typically have cases like SYSTICK, which often fires in 1ms intervals. STM32L0.sleep() should not return from just any ISR like this firing. Really it should only bail out if user observable state changes. So like Serial1 receiving data. With "attachInterrupt()" things get more complicated. Suppose you have a GPIO that triggers a async I2C read from a sensor. In reality you want to bail out of STM32L0.sleep() only when the sensor data is ready, not when the GPIO triggers the ISR. Hence the need for SM32L0.wakeup(). The real crux is now "user observable state". So you have your USBDevice. Are you really always interested when the USB peripheral goes into suspended mode ? Most applications do not really ever care. So you need somehow a way to say what user observable state you are interested in. One way (which is what I have implemented right now) is to say per peripheral (Serial1, USBDevice), whether the user observable state matters or not (which is the enableWakeup()/disableWakeup() API set). That model makes sense, because the default is to look at all user observable state, but if an application wants, it can remove from this set. The downside is that it's a fairly static, and a fairly obscure model. The alternative is to model all of this instead of using POSIX sleep() after POSIX select(). There you define the set of events (aehm file descriptors, whether it's a read/write/exception condition) that you are interested in. Anyway, food for thought. Other folks seem to have run into that very issue as well, not fully understanding ... and with crummy workarounds. Look at ArduinoBLE and HCITransport.wait() ... There the really would like to power down till a HCI event arrives ...
I am back in STM32WB calling this STM32WB.stop(). Ever chip vendor under the sun uses different names. Some say "deepsleep" and mean what ST calls STANDBY. Some use the same name to describe ST's STOP mode. So it's confusing either way. My original intention to use "deepsleep" was to get away from STOP as the code really cycles throu RUN/SLEEP/STOP as needed. If a peripheral is active (like I2C transmitting) in the background and would block STOP mode, then the code will stay in SLEEP for a while till it can enter STOP. And Arduino-LowPower does not make this any easier, so some implementations do it one way "deepsleep" as STOP and some use STANDBY for "deepsleep" ... Even worse some implementation (SAMD) kill SYSTICK (their timebase for millis() and micros()) in ANY mode, while some others ... You get the idea there. It seems cleaner to put this into a STM32L0 class and name it after the specific vendor's nomenclatura and be done. There does not seem to be any "right" or "good" way ... |
Forgive my ignorance, but what is BCD in this context? I also suspect that, given I haven't heard of it, I won't be needing BCD and the same might hold for others, so if not having VBUS would mean that USB works, but BCD does not, I think I can live with that.
Sounds like a neat trick that I probably don't really need, but might try anyway :-)
Yeah, but in POSIX that means that your signal handler would set some value, that makes the main code decide whether to retry (which is usually the default) or abort on
Agreed, sleeping is going to involve platform-specific knowledge in any case, I'm afraid. |
Since I really needed this support (we built two dozen prototype boards without that VBUS pin), I implemented that a while ago and now that that code ran fine for a while, I put it into a PR: #175. Let's continue discussion on this subject, what BCD is and if and why it should block supporting boards without a VBUS pin in that PR rather than here. |
Hi,
I searched in all files and git issues but I could not find this information : is it possible to generate an interruption from VBUS ?
I think you understand why : I want to immediatly etablish a link with CMWX (in order to flash it or communicate with it) as he is in stop mode.
Thanks
The text was updated successfully, but these errors were encountered: