IMP with SingleTact Sensor

Hello,

I have been trying to figure out how to read data from the single tact force sensor with the electric IMP April devboard… But every time i do a measurment i get a null :0x00 value. Why is that ? Could someone help me with this?

i2c <- hardware.i2c12;
hardware.i2c12.configure(CLOCK_SPEED_100_KHZ);
const i2cA = 0x004;
i2cAddress <- i2cA << 1;
local force = i2c.read(i2cAddress,"\x85",2);
server.log (force);

I’m not an i2c expert, but I was curious about your post.

Is this the specs you are using for Tact (see i2c section):

Address seems to be 0x04 … you have 0x004. Not sure if that makes any difference.

Read value is 2 bytes (MSB, LSB) as you have specified:
i2c.read(i2cAddress,"\x85",2);

But the PDF shows two registers 132 and 133.
decimal 133 = x85
should you be referencing x84 instead?

Finally, there is an example using an i2c temp sensor:

Not the same thing as your sensor but it might trigger some ideas.

Yes it is the same same senor you listed.
I tried out x84 aswell, but it was the same result. The values coming out should be in the specified range as listed in the datasheet.

Seems like a simple task, but the result is not so easy to achieve :grinning_face_with_smiling_eyes:

Check back here … you’ll get a response from a real expert ( not me ) :confused:

If you can post your whole device code that would be helpful.

So far i have gotten the device code to this point, as my only task for now is to understand how to read the I2C value from Pins 1/2 .

The code:

i2c <- hardware.i2c12;
hardware.i2c12.configure(CLOCK_SPEED_100_KHZ);

i2cAddress <- 0x04 << 1;
server.log(“SingleTact i2cAddress:” + i2cAddress);

const FS_scalingMSB = “\x0A”
const FS_scalingLSB = “\x0B”

function ReadSensor(){
local force = i2c.read(i2cAddress,"\x84",2);
// if (force == null) server.log("i2c error "+ i2c.readerror());
return force;
}

function getData() {
i2c.write(i2cAddress, FS_scalingMSB)
local Force = ReadSensor();
server.log("Force = " + Force);

}

imp.wakeup(0.1, getData);

As far as concerned, i tried to figure out the arduino sample code and implement it into to the imp. Maybe someone knows how to do such :slight_smile: https://github.com/SingleTact/ExampleArduinoInterface/blob/master/SingleTactDemo.ino

What value are you getting for i2c.readerror()? This will help you debug — see Understanding I2C Errors.

Do you have pull-up resistors on the I2C lines SDA and SCL? The SingleTact does not include these resistors and neither does the imp, so you will need to add these if you haven’t already.

Check out I2C Explained if you’re new to I2C.

Thank you for the response Smitty,

I am getting the error “-2”. I do have two pull-up resistors of 4.7k Ohm, to both SDA and SCL lines. I don’t quite get the meaning of how to initate to i2c read operation, the I2C guide is still a bit frustrating to me to clearly work this out.

So far i came up with this code. The sensor adress is 0x04, which i shifed by 1. From the datasheet in order to read the values i need to initate bytes 10,11 and read from bytes 132 and 133. Am i doing this correct?

hardware.i2c89.configure(CLOCK_SPEED_100_KHZ);
local i2c = hardware.i2c89;
local i2cAddress = 0x04 << 1;
server.log(“SingleTact i2cAddress:” + i2cAddress);
function readSensor(){
i2c.write(i2cAddress,"\x0A\x0B" + “”);
imp.sleep(1.0);
local force = i2c.read(i2cAddress,"\x84",2);
server.log("Force = " + force);
if (force == null) server.log("i2c error "+ i2c.readerror());
}
readSensor();

The -2 error suggests an addressing problem.

The line local force = i2c.read(i2cAddress, "\x84", 2); should be sufficient to read back the two bytes at 0x84 and 0x85, but section 2.4.3 in the manual says “where a Read operation is not preceded by a Read Request operation the read location defaults to 128 (the sensor output location)”, so I would also try local force = i2c.read(i2cAddress,"\x80",2); and see what happens.

I don’t think you need the i2c.write(i2cAddress, "\x0A\x0B" + ""); so far as I can see from the manual — and all it’s doing is writing the value 0x0B to the register at 0x0A.

I tried x80 aswell, but got the same result - “null = 0 x0”, as far as adressing it should be 0x04, when i try to log adress it shows “8”, maybe this could be wrong? The slave adress should be 0x04.

8 is what you should expect there (4 >> 1 equals 4 x 2).

Your read ops are probably always going to give you null until we get the addressing issue sorted as this is preventing your code from reading from or writing to the sensor.

Try this code:

function debugI2C(i2c = null) {
    if (i2c == null) throw "debugI2C() requires a non-null I2C object";
    for (local i = 2 ; i < 256 ; i += 2) {
        if (i2c.read(i, "", 1) != null) server.log(format("Device at 8-bit address: 0x%02X (7-bit address: 0x%02X)", i, (i >> 1)));
    }
}

It’ll show up valid devices on the I2C bus (eg. hardware.i2c89) you pass in.

Looking at some other code, for the Raspberry Pi, it reads the data you want as follows:

// read sensor data
local force = i2c.read(0x04 << 1, "\x04", 2);

so you might want to try that too. That line is preceded by:

// write zero to baseline registers
i2c.write(0x04 << 1, "\x02\x29\x02\x00\x00\xFF");

Thank you Smitty,

I plugged the sensor in Arduino and changed the address, that was the issue.
Some data is coming out, but it is repeating , when set on a loop. Is there a way to loop this structure in order to get new values each reading?

My updated code:

hardware.i2c89.configure(CLOCK_SPEED_100_KHZ);
local i2c = hardware.i2c89;
local i2cAddress = 0x04 << 1;
const Measurment_freq = 5.0;
function readSensor()
{
local breakFlag = false;
function readData()
{
local force = i2c.read(i2cAddress, “\x04”, 2);
server.log(readData);
}
i2c.write(i2cAddress, “\x02\x29\x02\x00\x00\xFF”);
imp.wakeup(1.0,readData);
if (!breakFlag)
{
imp.wakeup(Measurment_freq,readSensor)
}
}
readSensor();

Bit confused by your code:

hardware.i2c89.configure(CLOCK_SPEED_100_KHZ);
local i2c = hardware.i2c89;
local i2cAddress = 0x04 << 1;
const Measurment_freq = 5.0;

function readSensor()
{
  local breakFlag = false;
  function readData()
    {
      local force = i2c.read(i2cAddress, “\x04”, 2);
      server.log(readData);
    }
  i2c.write(i2cAddress, “\x02\x29\x02\x00\x00\xFF”);
  imp.wakeup(1.0,readData);
  if (!breakFlag)
    {
      imp.wakeup(Measurment_freq,readSensor)
    }
}

readSensor();

The main issue here appears to be that you’re doing server.log(readData), which is logging the function reference, vs the data you read.

If you change this to server.log(force) then you should see a bit more. The data is, I believe MSB then LSB so this would log it in decoded form:

server.log((force[0]<<8)+force[1])

…but wow that chip is not very i2c. Seems to be much more designed for UART and had I2C forced upon it…

I was trying to create a loop, that shows value every 5s, so i could figure out the correct values that i would need, so i could convert the sensor data output to force (N).

Will try these modifications :slight_smile:

Reading the datasheet, I don’t think your code is doing this correctly. When you do the read, you’re sending 0x04 as the first databyte - not the address - then reading 2 bytes. The datasheet indicates that if there’s no read command sent (and you’re not sending one) any read will read from offset 128, so that’s likely what you’re reading.

The first 2 bytes are “frame index”, which increment on every read, so you should see the number increase every 5 seconds. Do you see that?

What you seem to need to do is to read the “output digital scaling” value (bytes 10 & 11), and the “sensor baseline” value (bytes 41 and 42), and use these, combined with the sensor reading, to work out the force.

To read these two, you need to send a read command to the chip as noted in section 2.4.2. Something like this should work:

// Get output digital scaling 16 bit value
i2c.write(i2cAddress, "\x01\x0a\x02\xff"); // read command, 2 bytes at offset 10
local ods_raw = i2c.read(i2cAddress, "", 2);
local outputdigitalscaling = (ods_raw[0]<<8)+ods_raw[1];

// Get sensor baseline 16 bit value
i2c.write(i2cAddress, "\x01\x29\x02\xff"); // read command, 2 bytes at offset 41
local bl_raw = i2c.read(i2cAddress, "", 2);
local baseline = (bl_raw[0]<<8)+bl_raw[1];

// Now read the actual value; no read command is required to be sent first, but we'll read 6 bytes to fetch registers 128-133 inclusive
local reading = i2c.read(i2cAddress, "", 6);
local frameindex = (reading[0]<<8)+reading[1];
local timestamp = (reading[2]<<8)+reading[3];
local sensoroutput = (reading[4]<<8)+reading[5];

// calculate singletact_output per datasheet section 2.5. Note multiplication of outputdigitalscaling by a float to ensure a float division happens
local output = ( (sensoroutput - baseline) / (1.0 * outputdigitalscaling) ) + 255;
server.log(format("frameindex %d, timestamp %fs, output %f", frameindex, timestamp*0.0001, output));

Thank you very much Hugo for the amazing explanation. Tweaked my code a bit, and it works now :slight_smile:

Oh, I should have noted - you should only need to read scaling/baseline once at powerup (those appear to be factory calibrated values which will always be the same for a particular device).

The 6 byte read and calculation can then be done any time you need to read force.