-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
HardwareSerial library sometimes sends the same data twice #120
Comments
Yes, please do post a ready-to-run test case! |
OK .. not sure about github conventions and I can't see how to attach files so just pasting here: Arduino code - serial_test.ino: unsigned long nlines = 0;
#define MLEN 100
char diagmsg[MLEN+1];
volatile unsigned long cnt = 0;
volatile unsigned long duration = 0;
void setup() {
Serial.begin(500000);
delay(100);
cli();
TCCR4A = 0;
TCCR4B = (1 << WGM42) | (1 << CS40) | (1 << CS42); // prescale by 1024 and set CTC mode
TCNT4 = 0;
OCR4A = 8; // compare with 8 to get interrupt every 0.5 ms
TIMSK4 = (1 << OCIE4A); // enable timer overflow interrupt (and no others)
sei();
}
void loop() {
delay(20);
nlines++;
char c = '0' + nlines % 10;
int i=0;
while(i<MLEN) {
diagmsg[i] = c;
i++;
}
diagmsg[i] = '\0';
unsigned long start = micros();
Serial.print(diagmsg);
Serial.print(" ");
Serial.print(micros()-start);
Serial.print(" ");
Serial.print(cnt);
Serial.print(" ");
Serial.print(duration);
Serial.print(" ");
Serial.print(Serial.error_count());
Serial.print(" ");
Serial.println(nlines);
}
#define NLOOP 200
void test_isr() {
unsigned long start = micros();
for (int i=0; i<NLOOP; i++) {
cnt++;
}
duration = micros() - start;
}
ISR(TIMER4_COMPA_vect) {
test_isr();
} Python client, serial_test.py from __future__ import print_function
import serial
import time
if __name__ == "__main__":
link = serial.Serial('COM7', 500000)
print(link)
try:
n = 0
while True:
n += 1
start = time.clock()
response = link.readline()
stop = time.clock()
latency = (stop - start)*1000
nchars = len(response)
print("%d %5.2f ms nchars: %d: response: %s" %(n, latency, nchars, response))
finally:
print("closing serial link")
link.close() Note that the test case assumes that HardwareSerial.h and .cpp have been amended to add the |
Hi, I have the same issue. Sometimes the logic in the hardwareserial::write fails when the tx buffer is empty. I commented out the speedup code when the buffer is empty and made a flush() before reading ack:s from my device, then it started to work. My assumption is that the pointer overruns and the whole buffer is resent. I did not have this problem with version 1.0.x of IDE. |
I also had a serial error. Different than this but it makes sense that this is the same reason. I know its a lot but you can read this up here: as @mattskimi said its caused by this code. gcc seems (from what ive tested) to maybe optimize the code wrong. Then the data is a) sent twice and The code above seems to not return. For now I dont know why this happens. I have some workaround to fix my issue, but after reading this here it seems that I then still have doubled bytes (havent tested the output, since I send the same data byte). |
Replacing the udr pointer with the register fixes the issue for me: It seems that (on the left side) it loads the main class pointer, adds the value for the udr pointer (16) and subtracts it back (17), but the subtraction is wrong. And because of this the offset is -1 for _ucsra. Files: |
Maybe some inline asm can help? I tried but got a compile error: if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
//*_udr = c;
asm (
"st %a[_udr], %[c]\n\t" // (2) Save byte in buffer
// Outputs
: [_udr] "+e" (_udr) // Input and output
// Inputs
: [c] "r" (c)
);
sbi(*_ucsra, TXC0);
return 1;
}
|
So effectively, the Since 0xC000 is out-of-bounds for a 328p, we need to wonder how AVR handles this. By dumping the entire memory, as well as a bunch of bytes after it using this sketch, you can see that any reads after the end of the memory (0x900 and onward) return the same thing as reads from 0x100 and onward. Since the SRAM is mapped from 0x100 to 0x8ff, it seems that the SRAM region wraps around. This means that 0xC00 really reads from address hex((0xc000 - 0x100) % 0x800 + 0x100) = 0x800, which is some address well on the stack. This does not seem to be any particular value, so the address of However, trying to print Of course, the TXC0 bit is not cleared due to this bug, but I'm not sure how this would cause the described behaviour (since this bit is pretty much only used inside flush()). |
Have you looked at the other assembly? Maybe this isnt the only place that is wrong. It solves my issue/test case. But maybe there is more wrong with the code. Maybe also in other functions. One could try the newer 5.1 and see if that fixes the issue. If not, this is a very poor issue to fix. I am afraid that this could occur on other places as well. |
5.1 with LTO doesnt fix the problem. It lacks some debug information in the output, but now I know the place to search for:
The sketch was also tested on a real board! output: tested sketch: |
O3 output: Also tested O0 which seems to work \o/ (adds a lot more code) The test were made via uploading and trying. I did not look at the asm this time since it changed too much and I dont want to do that now. Rearranging the class members did not help with a (single) quick test. Should we report this upstream? This is a very bad testcase I think. At least one doesnt need an arduino to see the asm is incorrect. |
I guess that reducing the code to a smaller testcase would be the next step, so we can report a bug at gcc. Here's the steps I'd take:
After every modification, check the generated assembly to see if the bug still exists. If the bug disappears, undo the last change and try to make the change less invasive, or skip to the next change. I don't have time to dive in right now, perhaps @NicoHood can? |
Also, disabling optimization for a single function using an attribute was smart to try - that might very well be a useful workaround until we can get a fixed compiler. However, reducing the example code should be a first step, since this will also offer insight into what the essential parts are that cause this bug, which might help to figure out a less invasive workaround, and perhaps allows searching the source code for other instances of this bug. |
If you tell me how to compile those functions outside the IDE I can do this. I have linux with avr-gcc 4.8.1 and also 5.1 available. I just need to know how to manually compile them and get an asm output. Then I can try to debug it. |
Basically just take the avr-gcc command that Arduino uses for HardwareSerial.cpp (from the verbose output) and use that. That should produce a .o file (not linked into .elf yet), but that should already show this bug and can be disassembled using avr-objdump -S just like the .elf. Hope that helps? |
I setup a repo for this: I still use the IDE for easy debugging (for now). It seems that you need the tx function and that it needs to also contain the sbi instruction. Also notice it has nothing to do with inheritance nor with a virtual function. ASM can be found here: I think it would make sense to analyze the asm, maybe the asm is correct, even though I dont think so. A compiler error would be very bad. |
Now a reduced asm analysis: The code was reduced to focus on the gcc bug, not the serial functions. The code looks like this: //.h file
volatile uint8_t * const _ucsra; // 0,1
volatile uint8_t * const _ucsrb; // 2,3
volatile uint8_t * const _udr; // 4,5
volatile tx_buffer_index_t _tx_buffer_head; // 6
volatile tx_buffer_index_t _tx_buffer_tail; // 7
//.cpp
void HardwareSerial::_tx_udr_empty_irq(void)
{
sbi(*_ucsra, TXC0);
}
void HardwareSerial::write(uint8_t c)
{
// If the buffer and the data register is empty, just write the byte
// to the data register and be done. This shortcut helps
// significantly improve the effective datarate at high (>
// 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
*_udr = c;
sbi(*_ucsra, TXC0);
return;
}
while (0== _tx_buffer_tail) {
_tx_udr_empty_irq();
}
_tx_buffer[_tx_buffer_head] = c;
return;
} The asm looks like this with comments:
This is not a compiler bug. If you look closer at this, the pointer increment is correct. void HardwareSerial::write(uint8_t c)
{
*_udr = c;
}
void HardwareSerial::write(uint8_t c)
{
*_udr = c;
b4: dc 01 movw r26, r24
b6: 14 96 adiw r26, 0x04 ; 4
b8: ed 91 ld r30, X+
ba: fc 91 ld r31, X
bc: 15 97 sbiw r26, 0x05 ; 5
be: 60 83 st Z, r22
c0: 08 95 ret |
I also tried if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
UDR0 = c;
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
_NOP();
sbi(*_ucsra, TXC0);
return 1;
} But this still works. So I have no idea whats really wrong here!??! The only thing I can imagine of is that st takes 2 cycles instead of sts (as 16 bit). if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
cli();
*_udr = c;
sei();
sbi(*_ucsra, TXC0);
return 1;
} |
It must be a race condition or ISR problem. I still search for the reason, but if you take this example sketch, it works if you put cli() and sei() around write(): // arduino uno, 64 byte serial tx rx buffers, default
const int dledPin = 12;
const int ledPin = 13;
bool ledState = LOW;
unsigned long previousMillis = 0;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(dledPin, OUTPUT);
pinMode(11, OUTPUT);
pinMode(10, OUTPUT);
pinMode(9, OUTPUT);
digitalWrite(dledPin, 0);
Serial.begin(2000000);
}
void loop() {
unsigned long currentMillis = millis();
if ((currentMillis - previousMillis) >= 1000) {
ledState = !ledState;
digitalWrite(dledPin, 1);
for (int i = 0; i < 500; i++) {
cli();
Serial.write('a');
sei();
//digitalWrite(dledPin, 0);
Serial.flush();
//digitalWrite(dledPin, 1);
delay(6);
}
digitalWrite(dledPin, 1);
previousMillis = millis();
digitalWrite(ledPin, ledState);
}
} |
Possible fix, please try this. Sorry for the many posts, but before I forget something, I better write it down here. cli();
bool emptyBuffer = _tx_buffer_head == _tx_buffer_tail;
sei();
if (emptyBuffer && bit_is_set(*_ucsra, UDRE0)) {
*_udr = c;
sbi(*_ucsra, TXC0);
return 1;
} It seems to work for my testcase, even though it does not make sense to me (right now) why this should fix anything. The ISR only modifies the tail, not the head. And if we read the head first and then the tail that should be fine!? |
Could it be related to sbi() not being atomic for SFRs not in the low 32 byte I/O address space? |
Paul that might be a good idea. This also works for my testcase: size_t HardwareSerial::write(uint8_t c)
{
_written = true;
// If the buffer and the data register is empty, just write the byte
// to the data register and be done. This shortcut helps
// significantly improve the effective datarate at high (>
// 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
//cli();
bool emptyBuffer = _tx_buffer_head == _tx_buffer_tail;
//sei();
if (emptyBuffer && bit_is_set(*_ucsra, UDRE0)) {
cli();
*_udr = c;
sbi(*_ucsra, TXC0);
sei();
return 1;
}
tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE;
// If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
while (i == _tx_buffer_tail) {
if (bit_is_clear(SREG, SREG_I)) {
// Interrupts are disabled, so we'll have to poll the data
// register empty flag ourselves. If it is set, pretend an
// interrupt has happened and call the handler to free up
// space for us.
if(bit_is_set(*_ucsra, UDRE0))
_tx_udr_empty_irq();
} else {
// nop, the interrupt handler will free up space for us
}
}
_tx_buffer[_tx_buffer_head] = c;
_tx_buffer_head = i;
cli();
sbi(*_ucsrb, UDRIE0);
sei();
return 1;
} I think we also have this problem in the loop. At least there I could explain what happens. But since we never disable ISRs it doesnt solve our problem, but should be considered still: Pretending: // If the output buffer is full, there's nothing for it other than to
// wait for the interrupt handler to empty it a bit
while (i == _tx_buffer_tail) {
if (bit_is_clear(SREG, SREG_I)) {
// Interrupts are disabled, so we'll have to poll the data
// register empty flag ourselves. If it is set, pretend an
// interrupt has happened and call the handler to free up
// space for us.
cli();
bool flag = bit_is_set(*_ucsra, UDRE0);
sei();
if(flag){
_tx_udr_empty_irq();
}
} else {
// nop, the interrupt handler will free up space for us
}
} |
Ok, so this is not a miscompilation after all. The difference between add and subtract in the assembly is correct, because X is incremented while it is being used. Good catch. I'm surprised that disabling interrupts during
Perhaps some erratic behaviour is caused because we are writing 1 to a (read-only) flag register? Perhaps doing this will actually cause the flag to be set? If so, and the interrupt flag is set just before When you use the direct register name, I suspect that the actual To fix this, you would have to make sure to only set selected bits in the
This should write UCSRA with the current value of the 2 configuration bits, and TXC0 to clear that flag, while writing 0 to all other bits, hopefully preventing this problem. Your last point about the same problem occuring in the loop does not seem to hold, since the code in the loop only runs if interrupts are disabled ( |
I was inspired by this issue and did some work on the Cosa UART. I implemented a fast track and then a synchronization delay to achieve 99% effective baud-rate at 1 Mbps and 97% at 2 Mbps (Pro-micro Clone with Sparkfun FTDI, Linux). https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/IOStream/Driver/UART.cpp#L88 There are several issues with the code above. One is actually the order of clearing the TXC and writing to UDR. They need to be in the correct order or in an atomic block as above. To force race conditions I modified the Cosa UART Benchmark and added a Periodic Job with a period of as low as 100 us (10.000 interrupts per second). This really helped debug the code. https://github.com/mikaelpatel/Cosa/blob/master/examples/Benchmarks/CosaBenchmarkUART/CosaBenchmarkUART.ino I also used a Logic Analyzer to study the behavior. Something that really took some time to get working correctly at higher baud-rates, 5000+ interrupts per second, and the fast track was flush(). https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/IOStream/Driver/UART.cpp#L118 There is a spreadsheet with the results from the optimizations. https://github.com/mikaelpatel/Cosa/tree/master/examples/Benchmarks/CosaBenchmarkUART Cheers! |
@matthijskooijman You are right, we should not use sbi to clear a flag: My "fixes" just seem like a workaround to fix this specific testcase. It might be random or just a race condition. Adding/changing the code a bit "fixes" the issue, because the timing then might be correct again. since we are in a very small loop with a high Serial speed avery clock cycle change could lead the ISR to trigger on the correct or wrong place. Maybe even NOPs could "fix" this testcase, but not the issue itself. I have not that knowledge yet about the serial and its registers. As @mikaelpatel has stated the order might be totally wrong, but I dont know. It would be interesting to know if you have got a fix for this issue after your long testing experiments? For the last part of your answer: |
@mikaelpatel, I believe that this same issue is also present in the UART.cpp you linked, but you were probably aware of that (you're using @NicoHood, Did you get a chance to try the fix I suggested in my previous comment? AFAICS that should be a proper fix, if we analyzed the problem correctly now.
Not entirely sure what you mean here. Do you mean that if
First off, this seems to be the cause of the issue at hand, but it is very much related to this code, so let's discuss it here anyway. I actually think both the order needs to be correct and an atomic block is needed (I don't see an atomic block in the UART.cpp you linked, what did you mean with "as above" exactly?). Consider:
So, first UDR then TXC, with interrupts disabled, seems to be the only solution that is fully reliable. |
I was referring to the 2nd part of this comment (when called write() with interrupts disabled): No I did not test your fix yet. in a few hours I will try. I also think that the order itself is correct (from what I read) (2nd option inside an atomic block) I suggest to put every pointer that is accessed via interrupt into an atomic block, since the pointer loading and writing is not a single instruction (at least for cbi and sbi, setting (=) would be okay I think.) |
Ok, so you are saying that it might be needed to disable interrupts during
Awesome, thanks.
I think that all access are already atomic when needed (assuming a 1-byte head/tail index). Atomic blocks are only needed for read-modify-write cycles of variables that are also written by an ISR, just reading the variable from an ISR won't cause any problems. The problem at hand is caused by a hardware register behaving somewhat unexpectedly, so it's not a normal atomicity problem caused by a read-modify-write being interrupted. |
My fault, you are right. How obvious. Well will test this in a few hours :) |
Huh? You're testing if |
Yes I was trying to fix the flush, because both issues are related. And if the flush doesnt work, this issue is still there. Because the flush only hangs if a byte was sent twice. Why should an atomic block fix the issue? If the interrupt occurs after setting udr it adds the flag the the a register. the isr ends and the code does the same again. why should this fix anything? I mean it does fix my flush testcase, but I still dont see the why it should. I know that my testcase is not good and not realted to the issue. but if this fails, the issue also fails. I could test things for sure, but you never know if its just a race condition that luckly happens/doesnt happen. Idk i have no idea. I also have not enough knowledge, please someone else should test this and not rely on me for this. |
I think there are two issues here:
Since both of these issue can be fixed using an atomic block, they might seem like the same issue, but really are two issues. Also, I don't think the atomic block is the right fix for issue 1, since it still violates the datasheet recommendations. So I would propose to apply both the atomic block and the changes to the Does this clarify? |
Do you think this issue is fixed then but the flush issue not? |
Woops, I meant the
Not sure what you mean here. I've suggested fixes for both issues? |
@matthijskooijman @NicoHood
|
@mikaelpatel I believe that approach suffers from problem 1 described in https://github.com/arduino/Arduino/issues/3745#issuecomment-142244433 and problem 1 described in https://github.com/arduino/Arduino/issues/3745#issuecomment-142365575j. I haven't tested any of this though, so far I've just been theorizing based on @NicoHood's testing. Could you check these comments to see if you think my reasoning is sound? |
@matthijskooijman
Putting the clear of the TXC flag before writing data to UDR could give a flush error, i.e. miss a TXC flag and continue. Putting the clear after could give another type of flush error, i.e., lock up. The clear must be atomic together with the write of UDR. I had an interrupt handler for TX completion to support RS485 (bus disconnect). This interrupt handler was enabled by the UDR empty interrupt handler when the output buffer was empty. This obviously did not work anymore with the fast track and higher baud-rates. After deciding to refactor this code and moving it to the RS485 driver the Cosa UART::flush() code became much simpler. I have run the Cosa UART benchmark with different background interrupt loads and baudrates and it is stable with the correct end-of-transmission synchronization (i.e. flush). My focus has now moved to optimizing itoa/utoa (integer to string conversion). The standard AVR library implementation is not so fast and has become a bottle-neck. I found a bug-report and implementation that was useful. https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=627899 Cheers! |
I will test this today. Dont we also need an atomic block around this? |
Yes, that is what I was trying to convey before :-) I still believe this statement is wrong:
See point 1 in this comment. This might write 1 to various error / interrupt flags, which are supposed to be written as 0 always according to the datasheet. However, I also suspect that because this happens atomically, this will not actually cause problems, since the bits will always be written 1 when they are already 1 (without the atomic block, they could be cleared between reading and writing the register). I have tried to verify if writing a 1 to the |
@NicoHood The Cosa UART interrupt handler always checks for empty buffer. https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/IOStream/Driver/UART.cpp#L204 I believe that there must be either an atomic block in write() as you suggest or a check for empty buffer first in https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/HardwareSerial.cpp#L81. Cheers! |
Wouldnt it be also an idea to add the check at the beginning of the ISR as you suggested? If we check this first and then return (and disable the ISR flag) that would work around the problem for sure and solve it maybe. It might not be the best solution but a clear fix? |
Ah, so then the new bytes is written to the buffer just before the ISR triggers, which reads the byte from the buffer and disables itself (then being re-enabled by To fix this, I'd prefer an atomic block in write over a check in the ISR - best to keep a consistent state than to mop it up later? |
Ahm one thing I want to mention (has nothing to do with your previous comment): You can see that it always sends 1 byte and then should flush. So in this case the ISR should never ever be enabled right? Because it uses the first shortcut and then flushes the data. However the sketch crashes. This can only be cause if the ISR bit was set (which it should never ever to) or the TXC0 bit is cleared and stays cleared. Maybe it happens like this: This might not fix the sending the data twice problem. |
@NicoHood, everything you say in your last comment seems correct to me. This is what I described under point 2 in https://github.com/arduino/Arduino/issues/3745#issuecomment-142365575 and indeed, it would explain a flush lockup, but not a double transmit. |
@NicoHood @matthijskooijman
I have run this successfully up to 2 Mbps using the Cosa build support (with LTO) and serial monitor (miniterm.py). No transfer errors. The effect of LTO shows up as increased idle time. Please note that idle time is due to buffer full and in Cosa is a yield with sleep. Arduino is burning amps instead. Might be something to consider if you want to support low-power design? Cheers! |
Sorry its hard for me to follow sometimes. May you please try out arduino/Arduino#3865 yourself? It fixes my testcase at least. And from theory all bugs should now be removed. If not we are definitely setting some wrong registers. Maybe the @johnholman @mattskimi may you please try this out with your testcases as well? |
I'm sorry to come back to this after such a long time - I've been using my workaround, which has proved reliable. However I finally upgraded to 1.8.4 and find that the problem is still there. I can now confirm that NicoHood's pull request arduino/Arduino#3865 also seems to correct the error. To demonstrate the original issue here I've attached a simplified test program which doesn't require prior changes to the library code. Targeted at the Mega, it uses timer interrupt 4 to generate a heavy ISR load. When connected to serial monitor at 500K baud the output should look like this: 1 111111111111111111111111111111111111111111111111111111111111 14350 (the number at the end of each is the number of ISR calls so far) This is sample output when the error is present, here appearing as repeated data at lines 7 and 12. 1 111111111111111111111111111111111111111111111111111111111111 7700 I do apologise for the delay on getting to this, but hope it receives attention as in less extreme situations the underlying problem may occur very rarely and remain undetected but still have potentially serious consequences. |
Good to re-activate this issue, it still seems relevant to me (even though it completely dropped off my radar). Looking back at the issue and @NicoHood's PR, I think there's two symptoms here: Double transmits, and flush lockups. Looking at the code, we can identify three separate issues here:
@NicoHood's PR fixes 2 and 3 and I think fixes both symptoms. Issue 1 is not fixed by the PR, but it would be good to also fix it (suggested fix here). The fix had some testing, and IIUC my suggested fix didn't actually fix either the double transmit, or the flush lockup, but looking at it now I think it shouldn't (it should just fix datasheet compatibility). One interesting point is that clearing I'll add a comment to the PR too, to see what can be improved there. |
A race condition (likely exacerbated by FreeRTOS which allows task switching inside the uart1_write() routine) causes the arduino to retransmit old data. One reason could be the "TX empty" ISR is called even though the transmit buffer is empty (i.e. _tx_buffer_head == _tx_buffer_tail). This causes the _tx_buffer_tail to be incremented past _tx_buffer_head, resulting in the arduino transmitting old data. Alternatively, what might be happening is that the "TX empty" ISR might be enabled BEFORE _tx_buffer_head is incremented, which results in _tx_buffer_head being incremented past _tx_buffer_tail. (Ref: The HardwareSerial::write() code, a memory barrier might need to be added in between the `_tx_buffer_head = i` and `sbi(*_ucsrb, UDRIE0)`) The raspi code (particularly ucomm_SampleAssembler) is written with the assumption that old packets won't be received again. However, the above problem results in behavior which breaks this assumption, and bad things happen as a result. Fix this by wrapping uart1_write() with a critical section, so that no task switching or ISRs will be called when it is in progress. This should hopefully fix the race condition occurring with _tx_buffer_head/_tx_buffer_tail. Ref: https://github.com/arduino/Arduino/issues/3745
PR arduino/Arduino#6855 fixes both issues discussed here and has been approved by @matthijskooijman. It is based on @NicoHood's PR arduino/Arduino#3865 (now closed) but incorporates suggestions for improvement made by @matthijskooijman. |
Closing as fixed by arduino/Arduino#6855 |
Preserve values of configuration bits MPCMn and U2Xn. Avoid setting other read-only bits for datasheet conformance. See arduino/ArduinoCore-avr#120 for details
…dc14 55de1dc14 ISO c++ doesn't support binary contants however GNU c++ does c3890dc86 Fix weak attributions on operator new and delete 22d364ecc Mute unused variable warnings 2ffc04d4f Update abi and new It's now up to date with the one ArduinoCore-avr uses 133dcfd3d Fix unused variable and guard warning 7827dd8b3 Improve how TXCn bit is cleared in USCRnA register Preserve values of configuration bits MPCMn and U2Xn. Avoid setting other read-only bits for datasheet conformance. See arduino/ArduinoCore-avr#120 for details 6103f33f3 Formatting c8bef0bf5 Improvements to HardwareSerial.cpp 45437133d Add null pointer check to String destructor 811b51677 Adding parenthesis around 'bitvalue' allowing correct macro expansion when using with ternary operator such as bitWrite(value, bit, some_computed_value == 5 ? 1: 0); e16f14c78 Add bitToggle macro 221900dc9 Removes unnecessary if branch(because length is checked in while statement below the if-clause). 13d1f7607 Improve wiring_shift function 64786bc16 Use ADC instead of combining ADCL and ADCH git-subtree-dir: avr/cores/MCUdude_corefiles git-subtree-split: 55de1dc14dc8c530388cb162564b6e1c95eb7a39
…dc14 55de1dc14 ISO c++ doesn't support binary contants however GNU c++ does c3890dc86 Fix weak attributions on operator new and delete 22d364ecc Mute unused variable warnings 2ffc04d4f Update abi and new It's now up to date with the one ArduinoCore-avr uses 133dcfd3d Fix unused variable and guard warning 7827dd8b3 Improve how TXCn bit is cleared in USCRnA register Preserve values of configuration bits MPCMn and U2Xn. Avoid setting other read-only bits for datasheet conformance. See arduino/ArduinoCore-avr#120 for details 6103f33f3 Formatting c8bef0bf5 Improvements to HardwareSerial.cpp 45437133d Add null pointer check to String destructor 811b51677 Adding parenthesis around 'bitvalue' allowing correct macro expansion when using with ternary operator such as bitWrite(value, bit, some_computed_value == 5 ? 1: 0); e16f14c78 Add bitToggle macro 221900dc9 Removes unnecessary if branch(because length is checked in while statement below the if-clause). 13d1f7607 Improve wiring_shift function 64786bc16 Use ADC instead of combining ADCL and ADCH git-subtree-dir: avr/cores/MCUdude_corefiles git-subtree-split: 55de1dc14dc8c530388cb162564b6e1c95eb7a39
…dc14 55de1dc14 ISO c++ doesn't support binary contants however GNU c++ does c3890dc86 Fix weak attributions on operator new and delete 22d364ecc Mute unused variable warnings 2ffc04d4f Update abi and new It's now up to date with the one ArduinoCore-avr uses 133dcfd3d Fix unused variable and guard warning 7827dd8b3 Improve how TXCn bit is cleared in USCRnA register Preserve values of configuration bits MPCMn and U2Xn. Avoid setting other read-only bits for datasheet conformance. See arduino/ArduinoCore-avr#120 for details 6103f33f3 Formatting c8bef0bf5 Improvements to HardwareSerial.cpp 45437133d Add null pointer check to String destructor 811b51677 Adding parenthesis around 'bitvalue' allowing correct macro expansion when using with ternary operator such as bitWrite(value, bit, some_computed_value == 5 ? 1: 0); e16f14c78 Add bitToggle macro 221900dc9 Removes unnecessary if branch(because length is checked in while statement below the if-clause). 13d1f7607 Improve wiring_shift function 64786bc16 Use ADC instead of combining ADCL and ADCH git-subtree-dir: avr/cores/MCUdude_corefiles git-subtree-split: 55de1dc14dc8c530388cb162564b6e1c95eb7a39
55de1dc1 ISO c++ doesn't support binary contants however GNU c++ does c3890dc8 Fix weak attributions on operator new and delete 22d364ec Mute unused variable warnings 2ffc04d4 Update abi and new It's now up to date with the one ArduinoCore-avr uses 133dcfd3 Fix unused variable and guard warning 7827dd8b Improve how TXCn bit is cleared in USCRnA register Preserve values of configuration bits MPCMn and U2Xn. Avoid setting other read-only bits for datasheet conformance. See arduino/ArduinoCore-avr#120 for details 6103f33f Formatting c8bef0bf Improvements to HardwareSerial.cpp 45437133 Add null pointer check to String destructor 811b5167 Adding parenthesis around 'bitvalue' allowing correct macro expansion when using with ternary operator such as bitWrite(value, bit, some_computed_value == 5 ? 1: 0); e16f14c7 Add bitToggle macro 221900dc Removes unnecessary if branch(because length is checked in while statement below the if-clause). 13d1f760 Improve wiring_shift function 64786bc1 Use ADC instead of combining ADCL and ADCH git-subtree-dir: avr/cores/MCUdude_corefiles git-subtree-split: 55de1dc14dc8c530388cb162564b6e1c95eb7a39
Running a Arduino Mega 2560 program that makes heavy use of interrupts and that reports by sending lines of text (about 150 characters) in the main loop via the serial port at 115200 baud, I noiticed that additional spurious characters were sent on average every 2000 lines or so. Looking at the problem in more detail, what was transmitted was a line of exactly 66 characters consisting of the first few characters of the new line followed by last part of the line that had already been sent. The next line missed the first two characters, and then following ones were normal until the next occurrence.
These symptoms seemed consistent with void HardwareSerial::_tx_udr_empty_irq(void) occasionally being invoked at a time when the transmit buffer is in fact empty (i.e. _tx_buffer_head == _tx_buffer_tail), even though this should never happen. So I added some instrumentation:
where serial_errors is defined in HardwareSerial.h as a private member
and added a method to read and print the error count as part of the main loop.
This confirmed that the problem is indeed associated with the ISR being entered at a time when the buffer is empty, as the error count increments by one each time the problem occurs.
My original code is quite complicated, but I've been able to replicate the problem with a simple testcase in which the loop prints as before while a timer interrupt runs every 0.5ms with an associated ISR running for 0.3ms. The error seems to occur more often at higher baud rates - at 500,000 baud it occurs every few seconds. I can provide the testcase if required.
As a workaround, I've added this section to the start of _tx_udr_empty_irq:
This fix seems to work for me, but I'm certainly not convinced that it is correct in all circumstances.
I've not been able to figure out why the ISR is ever entered in this situation. Most but not all of the errors disappear if you comment out the part in the busy wait loop in write() that calls the ISR directly when interrupts are disabled globally. (No, I'm not printing to the serial port from an ISR!) Almost all disappear if in addition you comment out the optimisation that writes directly into the output register when the buffer is empty. However I've seen the problem very occasionally (but not with the test case) even when both optimisations are taken out.
The text was updated successfully, but these errors were encountered: