-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Receiving poll events in the sigio callback for UartSerial #5035
Comments
cc @hasnainvirk |
@darbyShaw readable() and writeable() being ambiguous is correct. I will send a patch through.
Method 2:
Plus, you can't really use a Callback template to set a variable. While constructing a Callback object, you can set a constant but not a variable. So on your sigio(), you don't know whether its readable() or writable(). You must check by poll or by invoking readable()/writable() which are currently ambiguous. Please try the methods stated above while we will prepare a patch for the bug. |
@darbyShaw One more thing came in to my mind, UARTSerial is essentially a serial with buffering you may have UARTSerial writable most of the time. Internally, UARTSerial will take data from buffers and write to the peripheral when peripheral is writable. |
I've created a PR which resolves the ambiguity. In the interim, the simplest workaround is to resolve the ambiguity by calling The strange thing is, I recall this ambiguity popping up during development, and thought we had already put some explicit overrides in UARTSerial to avoid it. |
(This escaped our attention because our code working with UARTSerial doesn't directly use the UARTSerial type, but is all written using |
Thank you so much for helping me out! I'll try using the FileHandle. |
I guess I also want to be able to attach my IRQ handler for receive on UartSerial which is not possible. I am thinking of using the Serial interface since UartSerial doesn't really help for higher baud rates for packets(even of size 128). I keep missing some bytes or read some invalid data. |
UARTSerial will call your sigio (from interrupt context) whenever data has become available. It will be draining data from the serial port under interrupt. There's no reason your interrupt handler would do a better job. Your responsibility is to make sure your reading thread drains the buffers fast enough in response to that sigio. If you don't, then data would be lost. Depending on the behaviour of your application, you might need bigger buffers. There is a prototype extension to UARTSerial adding HW flow control, which could stop the sender if you don't read fast enough (and if the HW flow control lines are wired). See here: #4428 I think @RobMeades is happy with that change, in which case I should make a PR for it. That may be required because some platforms can't actually read from the port under PIO interrupt fast enough to keep up with line rate - see #4722 |
@kjbracey-arm: yes, happy with that HW flow control change, making a PR would be good. |
@kjbracey-arm |
Waking a thread to do the read as soon as you get sigio should be plenty fast enough, at least at baud rates like 115,200. Are you making sure you read everything available every time you wake - read until you get The lwip PPP implementation I linked to above manages fine sending and receiving 1500-byte IP packets via a 256-byte serial buffer at 115,200 baud - sigio calls Do you even need to be non-blocking? If you have a dedicated thread, maybe you could just do blocking reads, and not have a sigio handler. |
I do read everything, that I am sure. I will look into lwip implementation. |
Blocking reads may not be appropriate for your application - it would mean a dedicated read thread that didn't have other stuff to do. But a blocking UARTSerial::read would only block that thread - it will sleep properly and it won't slow down other threads. That's unlike Serial, which is a totally non-buffered, spinning implementation - any read from Serial when the device isn't readable will keep that thread running continuously, guaranteeing 100% CPU utilisation and slowing other threads. You'll also note that Serial::read(100) will spin until all 100 bytes is available. It won't give you partial data, meaning it's never really safe to do a multibyte read. UARTSerial::read(100) will block until at least some data is available. You could certainly create your own interrupt-based buffer-driven implementation on top of Serial/RawSerial, and lots of people have, due to the inadequacies of Serial, but I don't see a reason to expect them to perform better than UARTSerial. The only 2 areas of potential improvement I'm aware of are:
Now, at really high line rates without flow control, it's possible Serial works reliably but UARTSerial doesn't, simply because Serial can devote 100% of foreground CPU time to reading the serial port - UARTSerial reading by 1 byte per interrupt in the background may not be able to keep up. (The catch is that Serial would drop stuff in between read calls as it can't buffer). What data rate are you actually using here? If it's 115,200, I think you're driving it wrong. If it's more like 1Mbps, then you probably need the flow control, and would benefit from testing an async RX patch. |
Do look at the lwip_ppp.c implementation - that's a more-than-textbook working example of non-blocking event-driven operation. From the other side, AtCmdParser shows it in blocking-with-timeout operation. |
Okay. I am trying to use the FileHandle approach used in lwip_ppp.c I can now receive 128 bytes but after receiving them, I don't get the next 128. Basically I am trying to implement a simple YModem protocol with ACKs and stuff. When I did it with read_async, I ran into an Overrun error which I realised after I put a print with semihost in the UART_Error_Callback. |
Error indications aren't in the base target serial HAL API, so aren't available in UARTSerial. Why is the number 128 significant? Is it the buffer size? Or is it the amount you're reading? Maybe you're reading 128 bytes, and waiting for the next sigio? But you never get another sigio because you didn't actually empty the buffers? sigio only happens when the FileHandle becomes readable (ie changes from non-readable to readable). But that means you have to empty it first. Do be aware that putting serial-based printfs into a program accessing another serial port is a very good way to guarantee you lose data. "stdout" is totally unbuffered, and will probably block the thing that's supposed to be reading from your serial port. |
128+header+trailer(3+2) is the packet size for YModem protocol. This is how I do it: void rxCpltCallback(void){ int main(void)
}` |
|
Okay. Thank you for those pointers. The buffer size used initially was the default 256. Here's what I tried: I removed sigio() altogether. I removed sleeps and I used the poll system call to get each packet this way:
I think this should work, but I still don't get the entire packet. |
I wouldn't think you need to handle overruns or any other serial error specially - I would expect it to be sufficient to spot the CRC failure or 1-second timeout which arises from such a loss, and then send a NAK to cause a retransmit. On the read, you should at least be setting the read length to (buffer size - lengthRead). But for YMODEM you should be reading the amount indicated by the block length marker. My copy of the spec seems to basically say "wait 10 seconds for a start byte, either SOH or STX". Once you have that, you should then be reading 2+128+2 or 2+1024+2, depending on which byte it is, with a 1-second time out. If your buffer size is 256, and you're using 128-byte packets, I don't really see what the problem could be. The entire YMODEM packet will always fit in the buffer, so it doesn't actually matter how slowly you read it. Is it that you're getting 128 bytes of data total, or just 1 128-byte packet (so 133 total?). Are you actually sending the ACK so you get the second packet? |
That code as written seems like it can never terminate - you have an inner while loop you don't need, and if it ever sees a whole packet, it breaks out from the inner loop and sets the length count back to zero, leaving you to go around the outer loop again. As you're no longer using sigio, you don't need to read until you get EAGAIN (which you were doing to make sure you'd fully processed the sigio callback) - you can just do one read per poll
|
Ah yes. Actually I had written it without EAGAIN initially without nonblocking set to false. So I didnt have the inner while loop. I just added it to see if it worked with non blocking. I will check that. But with blocking and poll it just gave me an event for the first time after which revents never had POLLIN set and poll doesn't return > 0. I basically receive partial data. I will try out what you suggested and get back. May be I am missing something trivial. My packet size is 3+128+2 = 133 |
If using poll, you shouldn't need non-blocking - if poll says it's readable, then that /should/ be a guarantee that the next read will not block. (Assuming a working FileHandle that doesn't magically become non-readable). EAGAIN should not be possible on the first read after poll indicates an event. I'm still not clear what you mean by "receive partial data". What YMODEM block size are you using? 128 or 1024?
|
I use ymodem block size 128. I fail to get the first block itself so ACK is not an issue. I am not sure about this but I think I see an overrun before receiving the entire packet even at baud 9600. I see it in serial_api.c in serial_readable(serial_t *) which is called with SerialBase::readable(). |
It could be your platform has some interrupt latency problem meaning serial port reception is never reliable. If a driver is disabling interrupts too long, the serial port is usually the first victim to suffer. To get problems at 9600, it would have to be pretty bad though. Do you have any other code or drivers active that could be interfering? If so, try deactivating it. Previously you said you got the first 128, but not the second 128. Can you be more precise about the situation now - you now say you're not receiving the first block. How much of the first block are you getting? Precisely? Are you losing a couple of bytes of the 133? I see you're using an STM32F4. I know we have seen problems with that chip - see #4722, #4734 and #4780. Do you have a version of mbed OS with pull #4734 integrated? Also note that the STM32F4 has NO hardware serial FIFO - only a single-byte data register. This means it is much more susceptible to overrun due to interrupt latency than most other SoCs. It may be that you are compelled to use hardware flow control at higher rates sooner than you would be on other devices. That in turn requires this pending PR to give you access to hardware flow control with UARTSerial: #5088, and you obviously need those lines wired up and working to whatever you're talking to. |
Thank you so much for your time and prompt replies. Mbed-os has got awesome response time for support.
I handled some occassional overruns with a Ymodem NAK to the transmitter and everything started working in a flow. |
@darbyShaw Is this considered resolved? |
@0xc0170 Yes resolved using the Serial interface. Thank you. |
ARM Internal Ref: MBOTRIAGE-179 |
Type: Question/ Suggested Enhancement
Hello all,
I use mbed OS 5 for nucleo-f401re.
I want to use UartSerial asynchronously using interrupts.
I see that UartSerial by default attaches to the rx_irq callback for RxIrq.
I attach my callback with the sigio function. My callback is called when wake is called from rx_irq. But since I have no way of knowing that whether my callback was called for a read or a write event(since I cannot call the readable and writable functions on my object(they are ambiguous and static cast to the base SerialBase does not work since it is private inheritance)), I was thinking of passing the revents to the callback(_sigio_cb) after poll.
Example:
Is this the correct way to do it?
If yes, we would possibly want the events passed to the callback function.
The text was updated successfully, but these errors were encountered: