mindsqualls wrote:
The suggested algorithm probably only works if you are sending a command to the sensor and expect exactly 1 byte in reply. Reading the distance from the ultrasonic sensor is such a case, but other commands like “Read product ID†and “Read sensor type†will return more bytes.
I have to admit, I never tried to read "strings" like product ID from the sensors using this method. However, the way I described is currently the only way to read data via I2C in the RWTH - Mindstorms NXT Toolbox for MATLAB. It works with multiple sensors, including HiTechnic Compass, ColorV1 + V2, Accelerometer, + Codatex RFID. Those sensor's data lengths are longer than 1 byte. So I can't confirm your problems.
mindsqualls wrote:
I also introduced a small wait-period in the algorithm. Having sent the LsWrite(), the data will not be ready in the sensor right way, so why not wait until the chances are better that it is before calling the LsRead()? The wait should not be too short, and not be too long either, but in between there is probably an optimal value.
Yep, good idea, I tested this waiting period myself, but I chose not to keep it inside production code.
Improvements:
Response-time: 104 ms -> 81 ms
Commands: 3.9 -> 2.1
That is really cool, unfortunately I don't remember the exact numbers for our improvements, and also I currently don't have an NXT to test it
. But as I faintly remember, a 10 to 12 Hz polling rate of the US sensor via Bluetooth (which is about 100 to 83 ms latency) is what we achieved... Whenever I get the chance, I should really confirm this!
Maybe it helps if I post my corresponding code. I too discovered some possible error states etc. The function in question is called COM_ReadI2C, code here:
http://www.mindstorms.rwth-aachen.de/tr ... _ReadI2C.m
Introductory comment (just describes what I posted in this thread before + my observations about adding a waiting pause as you did):
Code: Select all
%% Try requesting answer until it is ready
% Usually one should issue the LSWrite command, then keep requesting
% the I2C bus-status with LSGetStatus until it doesn't return an error
% and until the amount of bytes one requested is availabled, and then
% finally retrieve the data with LSRead.
%
% I found that the step with LSGetStatus can be omitted. Each direct
% command, including LSRead, has its own error message included (the
% statusbyte). If instead of using LSGetStatus and THEN using LSRead,
% we just use LSRead straight ahead, this has 2 advantages:
% - most of the time, the I2C sensor is ready anyway. So we save the
% time (especially via Bluetooth) that LSGetStatus needs.
% - if the sensor is not ready, we can keep polling it with LSRead. The
% error messages are the same, but once they stop (and data is ready),
% we already got the data as payload - having saved another call!
%
% Compared to the traditional way, this yields a speed up of about 100%
% for both Bluetooth and USB.
%
%
% Note that there is a tiny chance for further improvement, depending
% on the sensor. If we add a short pause here in the magnitude of 1ms
% to 10ms, it could lead to one failed LSRead being saved. Imagine
% this: Instead of asking the sensor straight away if it's ready, and
% then waiting 60ms for one more Bluetooth packet to arrive, we could
% wait 10ms (if that is enough) and then get the sensors answer with
% the next packet straight away.
% Testing showed that this is NOT necessary and most of the time NOT
% helping with Bluetooth, a fast computer and the ultrasonic sensor.
% However, different sensors may show different behaviour...
Algorithm:
Code: Select all
% use a timeout so we don't get stuck
startTime = clock();
timeOut = 1; % in seconds
status = -1; % initialize
BytesRead = 0;
% if we get error 224, it seems the sensor was not opened properly
% or, alternatively, it was opened, but due to termination of a NXC
% program, it was "closed" again... so let's warn about this, but only
% once...
ShowedWarning224 = false;
%% Actual request-loop
timedOut = false;
while ((status ~= 0) || ( BytesRead < 1))
% note that we suppress statusbyte-error-warnings by requesting the
% 3rd optional output argument of NXT_LSRead()
[data BytesRead status] = NXT_LSRead(Port, handle);
% discovery: if we get an error 221, it seems there is nothing that
% can be done to retrieve the data. we will run into a timeout. so
% why not retry the whole reading process?
if (status == 221) % 221 = "Communication bus error"
if isdebug()
textOut(sprintf(['! COM_ReadI2C failed, recursion depth ' num2str(RecursionDepth) ' @ ' datestr(now, 'HH:MM:SS.FFF') '\n']))
end%if
% recursively retry this whole thing!
ReturnBytes = COM_ReadI2C(Port, RequestLen, DeviceAddress, RegisterAddress, handle, RecursionDepth + 1);
return
elseif (status == 224) % 224 = "Specified channel/connection not configured or busy"
% for this error, we do nothing but showing a warning once...
if (RecursionDepth == 1) && (ShowedWarning224 == false)
warning('MATLAB:RWTHMindstormsNXT:Sensor:I2CnotConfiguredOrBusy', 'It seems the specified digital sensor is either busy or was not opened properly. If rebooting the NXT does not help, make sure the correct Open* command for this sensor has been executed, and no NXC/NBC program running on the brick has been terminated (or is currently being terminated) before/while sensor data are read. Trying to continue...');
ShowedWarning224 = true;
end%if
end%if
% already timed out?
if etime(clock, startTime) > timeOut
timedOut = true;
break
end%if
end%while
Please note that that function can be called recursively to re-try retrieving data, a simple check at the beginning is:
Code: Select all
% Check for maximum recursion depth! This can happen when this function
% repeatedly cannot read valid data via I2C. We set it by hand to 3
% tries. This condition will happen when for example you pull out the
% sensorcable of the US sensor just while it is retrieving data...
if RecursionDepth > 3
return
end%if
This probably exactly what you discovered:
mindsqualls wrote:
The second, 0xDD (Communication buss error), is a bit more problematic. If the bus ends up in this state it will just continue to fail until the error is cleared. It is easy enough to solve this; you just need to do a LsWrite(). Any LsWrite() should do, and I simply chose to resend the original one (from step 1).
Maybe I didn't realize how easy this error is to circumvent. Anyway, this is why the whole COM_ReadI2C can be called recursively to re-try getting the data...
I can't tell whether the code I posted is always faster than the original version with LSGetStatus for all sensors, but here are some example calls from our sensor functions:
HiTechnic Color V1 + V2
Code: Select all
% see http://www.hitechnic.com for more register information
% retrieve 14 bytes from device 0x02, register 0x42 - 0x4F
% in mode 0 only the first 4 bytes are needed
lng = 14;
if mode == 0
lng = 4;
end
%waitUntilI2CReady(port, handle);
data = COM_ReadI2C(port, lng, uint8(2), uint8(66), handle);
HiTechnic Compass:
Code: Select all
% retrieve 2 bytes from device 0x02, register 0x44
data = COM_ReadI2C(port, 2, uint8(2), uint8(68), handle);
HiTechnic Accelerator:
Code: Select all
% retrieve 6 bytes from device 0x02, register 0x42
data = COM_ReadI2C(port, 6, uint8(2), uint8(66), handle);
Codatex RFID:
Code: Select all
%% Get sensor data
waitUntilI2CReady(port, handle);
% read 5 bytes from register 0x42
data = COM_ReadI2C(port, 5, uint8(4), uint8(66));
datahex = dec2hex(data);
That waitUntilI2CReady(), which just polls LSGetStatus, is needed as the RFID sensor behaves a bit differently (and has more complex commands) then the usual HiTechnic / LEGO digital sensors.