Are concurrent I2C calls possible?

Discussion specific to NXT-G, NXC, NBC, RobotC, Lejos, and more.
aswin0
Posts: 201
Joined: 29 Sep 2010, 06:58

Are concurrent I2C calls possible?

Post by aswin0 »

I want to query two of my digital sensors as often as possible. When the two are on the same port one has to finish a request/reply cycle on one sensor before one can query the other. So querying the the two sensors is just as fast as querying one sensor twice.
But how about the two sensors being on different ports? Can one query the two at the same time. And, will this be as fast as querying a single sensor? I know how to do it, but I don't know how the firmware handles this.
I did an initial test, in robotC, that gave some disappointing results. I query a single sensor in 4msec, two sensors on the same port in 8 msec and two sensors on different ports in 24 msec. My I2C drivers are not thread save, this might have influenced the results of the last test.
My blog: nxttime.wordpress.com
mattallen37
Posts: 1818
Joined: 02 Oct 2010, 02:19
Location: Michigan USA
Contact:

Re: Are concurrent I2C calls possible?

Post by mattallen37 »

The NXT uses bit banged I2C, and so trying to do two two things at once, it is like multitasking. The NXT can only do one thing at a time. It would be like running parallel tasks. It would appear that they are both running at the same time, but it really has to take turns. If it has to keep changing code stacks, it makes sense it would be slower than running one, and then the other (or two on the same port).
Matt
http://mattallen37.wordpress.com/

I'm all for gun control... that's why I use both hands when shooting ;)
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: Are concurrent I2C calls possible?

Post by afanofosc »

All of the NXC I2C API functions are written so that they can be called simultaneously on separate threads so I think you could query two sensors on different ports faster than two back-to-back queries to a sensor on a single port. I'll run some timing tests with a couple HiTechnic devices later this evening.

I just ran some tests now and I am not seeing any kind of serialization in I2C calls using NXC and the standard firmware. Not 100% simultaneous, but each request is taking about 7 ms to execute and CurrentTick on one thread is showing a value that is 1 or 2 ms less than the value of CurrentTick on the second thread.
nxtimage_01.jpg
nxtimage_01.jpg (8.12 KiB) Viewed 9090 times
The code below is tweaked to remove a 15 millisecond delay in the thread-safe subroutines that underly the I2CBytes API function. I will be changing that code in the next release so that it does not always wait a minimum of 15 milliseconds.

Code: Select all

asm {
subroutine __ReadLSBytesJCH0
	acquire __CLSWMutex0
	set __CLSWArgs0.Port, 0
	mov __CLSWArgs0.ReturnLen, __RLSBytesCount0
	mov __CLSWArgs0.Buffer, __RLSReadBuf0
	syscall 21, __CLSWArgs0
	mov __RLSBResult0, __CLSWArgs0.Result
	release __CLSWMutex0
	brtst 4, __RLSBReturn0, __RLSBytesCount0
	arrinit __RLSReadBuf0, __constVal0, __RLSBytesCount0
	brtst 5, __RLSBError0, __RLSBResult0
	set __RLSBIterations0, 20
__RLSBDoCheckStatus0:
	acquire __CLSCSMutex0
	set __CLSCSArgs0.Port, 0
	syscall 23, __CLSCSArgs0
	mov __RLSBytesCount0, __CLSCSArgs0.BytesReady
	mov __RLSBResult0, __CLSCSArgs0.Result
	release __CLSCSMutex0
	sub __RLSBIterations0, __RLSBIterations0, __constVal1
	brtst 2, __RLSBError0, __RLSBIterations0
	brtst 0, __RLSBError0, __RLSBResult0
	brtst 4, __RLSBReadyToRead0, __RLSBResult0
	wait 1
	jmp __RLSBDoCheckStatus0
__RLSBReadyToRead0:
	acquire __CLSRMutex0
	set __CLSRArgs0.Port, 0
	mov __CLSRArgs0.BufferLen, __RLSBytesCount0
	syscall 22, __CLSRArgs0
	mov __RLSReadBuf0, __CLSRArgs0.Buffer
	mov __RLSBResult0, __CLSRArgs0.Result
	release __CLSRMutex0
	brtst 5, __RLSBError0, __RLSBResult0
	mov __RLSLastGoodRead0, __RLSReadBuf0
	jmp __RLSBDone0
__RLSBError0:
	mov __RLSReadBuf0, __RLSLastGoodRead0
__RLSBDone0:
	arrsize __RLSBytesCount0, __RLSReadBuf0
__RLSBReturn0:
	subret ____ReadLSBytes0_return
ends

subroutine __ReadLSBytesJCH1
	acquire __CLSWMutex1
	set __CLSWArgs1.Port, 1
	mov __CLSWArgs1.ReturnLen, __RLSBytesCount1
	mov __CLSWArgs1.Buffer, __RLSReadBuf1
	syscall 21, __CLSWArgs1
	mov __RLSBResult1, __CLSWArgs1.Result
	release __CLSWMutex1
	brtst 4, __RLSBReturn1, __RLSBytesCount1
	arrinit __RLSReadBuf1, __constVal0, __RLSBytesCount1
	brtst 5, __RLSBError1, __RLSBResult1
	set __RLSBIterations1, 20
__RLSBDoCheckStatus1:
	acquire __CLSCSMutex1
	set __CLSCSArgs1.Port, 1
	syscall 23, __CLSCSArgs1
	mov __RLSBytesCount1, __CLSCSArgs1.BytesReady
	mov __RLSBResult1, __CLSCSArgs1.Result
	release __CLSCSMutex1
	sub __RLSBIterations1, __RLSBIterations1, __constVal1
	brtst 2, __RLSBError1, __RLSBIterations1
	brtst 0, __RLSBError1, __RLSBResult1
	brtst 4, __RLSBReadyToRead1, __RLSBResult1
	wait 1
	jmp __RLSBDoCheckStatus1
__RLSBReadyToRead1:
	acquire __CLSRMutex1
	set __CLSRArgs1.Port, 1
	mov __CLSRArgs1.BufferLen, __RLSBytesCount1
	syscall 22, __CLSRArgs1
	mov __RLSReadBuf1, __CLSRArgs1.Buffer
	mov __RLSBResult1, __CLSRArgs1.Result
	release __CLSRMutex1
	brtst 5, __RLSBError1, __RLSBResult1
	mov __RLSLastGoodRead1, __RLSReadBuf1
	jmp __RLSBDone1
__RLSBError1:
	mov __RLSReadBuf1, __RLSLastGoodRead1
__RLSBDone1:
	arrsize __RLSBytesCount1, __RLSReadBuf1
__RLSBReturn1:
	subret ____ReadLSBytes1_return
ends
}

byte cmdBuf[] = {0x10, 0x40};
byte outbuf1[];
byte outbuf2[];

task one()
{
  byte out;
  unsigned long tick;
  while (true)
  {
    int count = 1;
    tick = CurrentTick();
  asm {
  	mov __RLSReadBuf0, cmdBuf
  	mov __RLSBytesCount0, count
  	subcall __ReadLSBytesJCH0, ____ReadLSBytes0_return
  	mov count, __RLSBytesCount0
  	mov outbuf1, __RLSReadBuf0
  }
  //  I2CBytes(S1, cmdBuf, count, outbuf1);
    unsigned long ct = CurrentTick();

    NumOut(0, LCD_LINE1, tick, true);
    if (count > 0)
      NumOut(0, LCD_LINE2, outbuf1[0]);
    NumOut(0, LCD_LINE3, ct-tick);
    NumOut(0, LCD_LINE4, ct);

    Wait(SEC_1);
  }
}

task two()
{
  byte out;
  unsigned long tick;
  while (true)
  {
    int count = 1;
    tick = CurrentTick();
  asm {
  	mov __RLSReadBuf1, cmdBuf
  	mov __RLSBytesCount1, count
  	subcall __ReadLSBytesJCH1, ____ReadLSBytes1_return
  	mov count, __RLSBytesCount1
  	mov outbuf2, __RLSReadBuf1
  }
  //  I2CBytes(S1, cmdBuf, count, outbuf1);
    unsigned long ct = CurrentTick();

    NumOut(0, LCD_LINE5, tick);
    if (count > 0)
      NumOut(0, LCD_LINE6, outbuf2[0]);
    NumOut(0, LCD_LINE7, ct-tick);
    NumOut(0, LCD_LINE8, ct);

    Wait(SEC_1);
  }
}

task main()
{
  Precedes(one, two);
  SetSensorLowspeed(S1);
  SetSensorLowspeed(S2);
}

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
gloomyandy
Posts: 323
Joined: 29 Sep 2010, 05:03

Re: Are concurrent I2C calls possible?

Post by gloomyandy »

It may depend which speed i2c you are using. In leJOS the standard speed (9.6Kbps) version is driven from a timer interrupt and so two (or more) on different ports will sort of run in parallel (in effect each bit gets sent in the "gap" between the bits on the other ports). However when using high speed mode (125Kbps), there is no "gap" as such so you will not gain any speed advantage... It may well be the same for RobotC (but I have no idea if it is)...

Andy
aswin0
Posts: 201
Joined: 29 Sep 2010, 06:58

Re: Are concurrent I2C calls possible?

Post by aswin0 »

Thanks all for the input.

I just did some clean tests without any function calls that are not thread safe.

In standard I2C mode it takes 4msec to query 2 bytes of data. It does not make a difference if I query one or two sensors in parallel.
In fast mode it takes 2msec to query 2 bytes of data when running just one task/sensor. When running 2 tasks/sensors in parallel it takes 2.6 msec to query both the sensors.

When querying 2 sensors in fast mode I get errors frequently, whenever an error occurs it is on both the ports and robotC does not recover from it. The only remedy is to reboot the NXT.
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: Are concurrent I2C calls possible?

Post by afanofosc »

Can you post your test program, please? It would benefit the LEGO MINDSTORMS community as a whole to see examples of these kind of timing tests.

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
aswin0
Posts: 201
Joined: 29 Sep 2010, 06:58

Re: Are concurrent I2C calls possible?

Post by aswin0 »

Sure.

The includes and the lines between // configure accl; are needed to initialize the accelerometer. These are quite specific to the custom sensor that I use on port 3 and can be removed if you want to use it with your own sensor.


Code: Select all

#include "math.c"
#include "matrixDirectives.c"
#include "I2C.c"
#define IMU_speed 25
#include "IMU_acclMatrix.c"



task tS1();
task tS3();
task both();
task main();

task main()
{
  //startTask(tS3);
  //startTask(tS1);
  startTask(both);
  while(true)
  {
    wait1Msec(1);
  }
}

task tS1()
{
  ubyte msg[16];
  ubyte reply[6];
  tSensors port=S1;
  float time;
  SensorType[port]=sensorI2CCustomFastSkipStates;
  while(true)
  {
    ClearTimer(T1);
	  msg[0]=2;
	  msg[1]=0x04;
	  msg[2]=0x45;
	  while (nI2CStatus[port] != NO_ERR)
	    wait1Msec(1);
	  sendI2CMsg(port, msg[0], 2);
	  while (nI2CBytesReady[port]==0 )
	    wait1Msec(1);
		readI2CReply(port, reply[0], 2);
		time=499.0/500.0*time+Time1[T1]/500.0;
		nxtDisplayTextLine(1,"%3.2f", time);
	}

}

task tS3()
{
  ubyte msg[16];
  ubyte reply[2];
  tSensors port=S3;
  float time;
  SensorType[port]=sensorI2CCustomFastSkipStates;
   // configure accl;
  I2C_accl.port=port;
  I2C_accl.address=0xa6;
  Accl_init();
  accl.initialized=true;
   // configure accl;

  erasedisplay();
  while(true)
  {
    ClearTimer(T3);
	  msg[0]=2;
	  msg[1]=0xa6;
	  msg[2]=0x1b;
	  while (nI2CStatus[port] != NO_ERR)
	    wait1Msec(1);
	  sendI2CMsg(port, msg[0], 2);
	  while (nI2CBytesReady[port]==0 )
	    wait1Msec(1);
		readI2CReply(port, reply[0], 2);
		time=499.0/500.0*time+Time1[T3]/500.0;
		nxtDisplayTextLine(3,"%3.2f", time);
	}
}

task both()
{
  ubyte msg[16];
  ubyte reply[6];
  tSensors port1=S1;
  float time;
  SensorType[port1]=sensorI2CCustomFastSkipStates;
  tSensors port=S3;
  SensorType[port]=sensorI2CCustomFastSkipStates;
    // configure accl;
  I2C_accl.port=port;
  I2C_accl.address=0xa6;
  Accl_init();
  accl.initialized=true;
  // configure accl;
  erasedisplay();
  while(true)
  {
    ClearTimer(T1);
	  msg[0]=2;
	  msg[1]=0x04;
	  msg[2]=0x45;
	  while (nI2CStatus[port1] != NO_ERR)
	    wait1Msec(1);
	  sendI2CMsg(port, msg[0], 2);
	  while (nI2CBytesReady[port1]==0 )
	    wait1Msec(1);
		readI2CReply(port1, reply[0], 2);
	  msg[0]=2;
	  msg[1]=0xa6;
	  msg[2]=0x1b;
	  while (nI2CStatus[port] != NO_ERR)
	    wait1Msec(1);
	  sendI2CMsg(port, msg[0], 2);
	  while (nI2CBytesReady[port]==0 )
	    wait1Msec(1);
		readI2CReply(port, reply[0], 2);
		time=499.0/500.0*time+Time1[T1]/500.0;
		nxtDisplayTextLine(1,"%3.2f", time);
	}

}

My blog: nxttime.wordpress.com
gloomyandy
Posts: 323
Joined: 29 Sep 2010, 05:03

Re: Are concurrent I2C calls possible?

Post by gloomyandy »

Hi,
I'm a little confused as to what the actual results are that you see. So when you run the test what are the results displayed on the screen in the two modes?

Thanks

Andy
aswin0
Posts: 201
Joined: 29 Sep 2010, 06:58

Re: Are concurrent I2C calls possible?

Post by aswin0 »

These are the results

Code: Select all

              Fast I2C      Standard I2C
S1.          2.00 msec.  3.96 msec
S3/S1      2.60 mesc   4.01 msec
Both       4.00 msec.  Not tested
My blog: nxttime.wordpress.com
afanofosc
Site Admin
Posts: 1256
Joined: 26 Sep 2010, 19:36
Location: Nashville, TN
Contact:

Re: Are concurrent I2C calls possible?

Post by afanofosc »

I don't really understand how the time= equation works but it seems to do the right thing in my NXC version. Here it is:

Code: Select all


task tS1()
{
  SetSensorLowspeed(S1);
  byte msg[] = {0x02, 0x42};
  byte reply[6];
  byte count;
  float time;
  unsigned long T1;
  while(true)
  {
    count = 2;
    T1 = CurrentTick();
    while (I2CCheckStatus(S1) != NO_ERR)
      Wait(0);
    I2CWrite(S1, count, msg);
    while (I2CBytesReady(S1) == 0)
      Wait(0);
    I2CRead(S1, count, reply);
    time = 499.0/500.0*time + (CurrentTick()-T1) / 500.0;
    TextOut(0, LCD_LINE1, FormatNum("%3.2f" , time));
  }
}

task tS3()
{
  SetSensorLowspeed(S3);
  byte msg[] = {0x02, 0x42};
  byte reply[6];
  byte count;
  float time;
  unsigned long T3;
  while(true)
  {
    count = 2;
    T3 = CurrentTick();
    while (I2CCheckStatus(S3) != NO_ERR)
      Wait(0);
    I2CWrite(S3, count, msg);
    while (I2CBytesReady(S3) == 0)
      Wait(0);
    I2CRead(S3, count, reply);
    time = 499.0/500.0*time + (CurrentTick()-T3) / 500.0;
    TextOut(0, LCD_LINE3, FormatNum("%3.2f" , time));
  }
}

task both()
{
  SetSensorLowspeed(S1);
  SetSensorLowspeed(S3);
  byte msg1[] = {0x02, 0x42};
  byte msg2[] = {0x02, 0x42};
  byte reply[6];
  byte count;
  float time;
  unsigned long T1;
  while(true)
  {
    count = 2;
    T1 = CurrentTick();
    while (I2CCheckStatus(S1) != NO_ERR)
      Wait(0);
    I2CWrite(S1, count, msg1);
    while (I2CBytesReady(S1) == 0)
      Wait(0);
    I2CRead(S1, count, reply);
    while (I2CCheckStatus(S3) != NO_ERR)
      Wait(0);
    I2CWrite(S3, count, msg2);
    while (I2CBytesReady(S3) == 0)
      Wait(0);
    I2CRead(S3, count, reply);
    time = 499.0/500.0*time + (CurrentTick()-T1) / 500.0;
    TextOut(0, LCD_LINE1, FormatNum("%3.2f" , time));
  }
}

task main()
{
  Precedes(tS1, tS3);
//  Precedes(both);
/*
  SetSensorLowspeed(S1);
  SetSensorLowspeed(S3);
  
  while (true)
  {
    TextOut(0, LCD_LINE1, I2CVendorId(S1, I2C_ADDR_DEFAULT));
    TextOut(0, LCD_LINE2, I2CVendorId(S3, I2C_ADDR_DEFAULT));
    Wait(SEC_1);
  }
*/
}
There's got to be a straightforward explanation why RobotC can make an I2C call (in slow mode) in half the time it takes the standard firmware to do the same thing. I get about 8ms per transaction using the above code. I guess I will pour over the firmware source code carefully to see if there is anything I can do to speed it up without breaking anything.

John Hansen
Multi-platform LEGO MINDSTORMS programming
http://bricxcc.sourceforge.net/
Post Reply

Who is online

Users browsing this forum: Semrush [Bot] and 3 guests