LEGO EV3 UART sensors return stale values after mode switch
Describe the bug This appears to be a hardware issue. After switching modes, the LEGO EV3 Color sensor will return a stale value on the first data message that is received after switching modes.
Here is an example:

The first message requests to change the mode to 0 (reflected light). The second message is a data message that says the mode is 0 and the value is 0x27 (39%), however the sensor has already been moved to a position where it should be reading near 0. The third message is another data message with an expected value of 0x02.
To Reproduce Steps to reproduce the behavior:
- Hold the EV3 color sensor in a fixed position so that the measured value does not change.
- Using something that changes the mode and immediately reads the value, read one mode then another mode.
- Without changing from the second mode, move the sensor so that it will measure a new value.
- Change back to the first mode and read the value.
- The first time it is read, it will return a value from the original position of the sensor instead of the new position.
$ pybricks-repl
Pybricks MicroPython 6e98857e-dirty on 2020-04-09; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> c = ColorSensor(Port.S1)
>>> c.reflection()
60
>>> c.ambient()
106
>>> c.reflection()
43
>>> c.reflection()
0
>>> c.reflection()
0
>>>
Expected behavior ev3dev should anticipate the hardware bug and ignore the stale value.
Screenshots See above
Please paste the system info from the ev3dev VS Code extension or the output of `ev3dev-sysinfo -m` here!
The ultrasonic sensor seems to have the same issue. In one case it took a full 2 seconds before "good" data started to be received after changing modes.

Saleae Logic capture: ev3-ultrasonic-sensor.zip
$ pybricks-repl
Pybricks MicroPython 6e98857e-dirty on 2020-04-09; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> s = UltrasonicSensor(Port.S1)
>>> s.distance()
30
>>> s.presence()
False
>>> s.distance()
30
>>> s.distance()
166
>>> s.distance()
32
>>> s.presence()
True
>>> s.distance()
32
>>> s.distance()
32
>>> s.distance()
185
>>>
Same issue with EV3 IR sensor. In this test, it has a delay of about 37ms.

$ pybricks-repl
Pybricks MicroPython 6e98857e-dirty on 2020-04-09; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> s = InfraredSensor(Port.S1)
>>> s.distance()
0
>>> s.buttons(1)
[]
>>> s.distance()
1
>>> s.distance()
23
Given the fact that the delay can be very long and we can receive multiple "bad" data messages due to replies to keep-alive before getting "good" data, I'm not sure that it is possible to fix this.
Does this happen with newer sensors too? I'm trying to understand this with the 20N7 EV3 color sensor firmware disassembly and I can't find why the sensor would behave in this way. I can try to reproduce it using the stock lms2012 kernel with a 47N3 color sensor (without firmware code) and the 20N7 sensor.
If the "prettification" is correct, the following code should do COL-REFLECT measurements:
// prepare COL-REFLECT mode; this mode is set when a SELECT COL-REFLECT message is received
case STATE_REFLECT_SETUP: {
color_setup();
mainState = STATE_REFLECT_RUNNING;
forceSend = true;
break;
}
// do measurements in COL-REFLECT mode
case STATE_REFLECT_RUNNING: {
u8 newReflect = measureReflectivity(LED_RED);
if (newReflect != lastReflect || forceSend) {
lastReflect = newReflect;
transmit[0] = MSG_DATA | COL_REFLECT | MSGLEN_1;
transmit[1] = newReflect;
transmit[2] = transmit[0] ^ transmit[1] ^ 0xFF;
if (uartWrite(transmit, 3) == TX_OK) {
forceSend = false;
}
}
break;
}
The measureReflectivity() function directly performs the measurement and AFAIU it does not have any cache. Therefore the UART message that is sent from this code should always contain a fresh measurement. However, if the message buffer gets somehow retransmitted, the observed behaviour may occur. Alternatively, it may be that older sensors reuse the lastReflect value either in this code path or in the mode entry or the NACK resend code path (in this revision, NACK only sets forceSend). The COL-CAL entry state contains a "suspicious" acknowledgement message; perhaps a similar approach was previously used for other mode entries too:
// answer calibration request
case STATE_CALIBRATE_SETUP: {
color_setup();
transmit[0] = MSG_DATA | COL_CAL | MSGLEN_8;
transmit[1] = 0x00;
transmit[2] = 0x00;
transmit[3] = 0x00;
transmit[4] = 0x00;
transmit[5] = 0x00;
transmit[6] = 0x00;
transmit[7] = 0x00;
transmit[8] = 0x00;
transmit[9] = transmit[0] ^ 0xFF;
if (uartWrite(transmit, 10) == TX_OK) {
mainState = STATE_CALIBRATE_RUNNING;
}
break;
}
I'm thinking that the sensor firmware may have actually evolved quite a lot over time. If older revisions of gyro sensors were doing a soft reboot after switching modes and the newer ones don't do that anymore, I don't see why similar development couldn't have happened to the color sensor.
I have reproduced (hopefully) the same problem on both color sensors. However, I was able to trigger a similar behaviour also this way:
- point the sensor freely into a dark room
- switch to reflect mode
- switch to ambient mode
- readings should be around zero
- switch to reflect mode
- the first reading should be around 40 (very near the 39 in the first screenshot), subsequent readings should be near zero.
I think the problem lies in the sensing circuit. When the sensor is switched from dark ambient to reflect mode, the CAP pin is floated. I'd guess that as the voltage on the AMBIENT rail loses its stabilization from the C8 capacitor, it has effect on the DC-blocking capacitor used for color measurement. This could produce wrong measurement in the first cycle.

A similar issue happens when quickly switching from ambient (with sensor pointed towards bright light source) to reflection and back to ambient mode (this time with sensor pointed to darkness). First few readings in the second ambient measurement are higher than expected (around 10 in my case), then they drop to zero or one. Here, I think that the voltage on the C8 cap is preserved during the reflection measurements and it finally drops only once the CAP pin is grounded again.
I don't have detailed timing information for this, but I think the stock firmware mitigates this specific issue with the 10 ms delay for all UART sensors. Issues in other sensors seem unrelated to this one to me,
The ultrasonic sensor seems to have the same issue. In one case it took a full 2 seconds before "good" data started to be received after changing modes.
I've now remembered that I had a similar problem even without switching modes. This has really frustrated me at times, but I think this is inherent to its principle of operation. If the sensor is not able to decode the reflected wave properly, it has to ignore the measurement and reuse the old one. This leads to situations like this: https://youtu.be/3-URqjq4pio . I was only moving my hand in front of the sensor without moving the robot or the obstacles. Still, the measured value did change significantly. I think the only workaround possible is to expose some kind of unreliable attribute to the applications. It would signal that no non-modeswitch and non-NACK data message were received recently. Alternatively, if the age of the last non-modeswitch + non-nack data message was be exported from the kernel, userspace applications could derive this on their own.