Unsupported Sensors? Try Arduino + Lua!

Hi @geofrancis, what are those nice multi pin sockets in the background of your last photo?

@pauljeff The big ones are 15 pin gx20 connectors. They come in 3 sizes, gx12, gx16 and gx20.

What if we don’t understand Lua ? :frowning:

You learn it, using examples and on-line videos.

haha nice suggestion

In a separate/private conversation, I was asked whether Lua provides a built-in map() function like the one provided by the Arduino libraries. I thought the answer might be of use to some of you:

To my knowledge, there is no map() function provided natively in Lua.

Here is the Arduino map() function from WMath.cpp:

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

In Lua, it looks like this:

local function map(x, in_min, in_max, out_min, out_max)
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
end

But the Arduino function does integer math, so if you want (nearly) identical output:

local function map(x, in_min, in_max, out_min, out_max)
    return math.floor((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)
end
2 Likes

How did you convert to celcius?

I had a very early version of the example script thats changed a lot to the latest version, but I had a look and I think you need to change line28 uint8_t units = USE_FAHRENHEIT; to USE_CELSIUS.

1 Like

@Yuri_Rage can you help?

I am trying to log multiple DS18B20 sensors in the data-flash logs.

I have modified the BS18B20.lua script with this additional line:

logger.write(‘MOTE’,‘Mt’ … idx,‘f’,‘O’,’-’,val)

Assuming that the same syntax used for the “gcs:send_named_float” would work. It does indeed add a new entry into the logs “MOTE” and stores temperature for MT0 but I am also expecting to see MT1, MT2 etc but I do not. What am I doing wrong?

See here for entire function

function update()
    for idx = 0, num_sensors - 1 do
        -- request to store sensor temperature in I2C registers for given index
        arduino_i2c:write_register(SET_SENSOR_INDEX, idx)
        -- now read the register data and collect its value as a float
        local val = unpack_ieee754_float(read_register_data())
        gcs:send_named_float('Mt' .. idx, val)
		logger.write('MOTE','Mt' .. idx,'f','O','-',val)	
						
    end
    return update, RUN_INTERVAL_MS
end

@tegwin,

It looks like each log entry must be fully formed at write time via a table of values or a discrete argument list. Try this:

function update()
    local log_data = {}
    local log_names = {}

    for idx = 0, num_sensors - 1 do
        -- request to store sensor temperature in I2C registers for given index
        arduino_i2c:write_register(SET_SENSOR_INDEX, idx)
        -- now read the register data and collect its value as a float
        local val = unpack_ieee754_float(read_register_data())
        gcs:send_named_float('Mt' .. idx, val)
        log_names[idx + 1] = string.format('Mt%d', idx)
        log_data[idx + 1] = val
    end
    logger.write('MOTE', table.concat(log_names, ', '), string.rep('f', #log_data), table.unpack(log_data))
    return update, RUN_INTERVAL_MS
end

(I edited this post a couple of times with increasingly elegant solutions)

1 Like

That works perfectly, thank you so much. This is going to be so useful for detecting failures and performance monitoring.

I have two questions for you.

  1. With multiple temperature sensors, what is the best way of identifying which physical sensor relates to which message? Is there some logic behind their numbering?
  2. I am using a Matek L431 Can node at each motor - in an ideal world I would have an arduino connected via i2c to each node with a temperature probes for each motor. Have you experimented with the L431? Any thoughts on if this might work to enable logging or each motor temp over the canbus?

You can eliminate the string length check - see my most recent edit.

There is no rhyme/reason to the sensor indexing other than that the sensors are found/reported in address order, which seems to be consistent, so I don’t think there’s a need to double check their position from one run instance to the next, so long as the sensors are not physically replaced (or additional sensors added).

You could add a method to retrieve the DS18B20 addresses to the Arduino sketch example if you wanted to be more specific in the indexing.

You’ll have to do some experimentation to determine which sensor corresponds to each index value.

I have not used the L431 at all yet, but it’s on my list of things to try. I have some mRo CAN nodes on hand and have used them to put serial GPS modules on the CANbus. I have not yet tried anything “fancier” than that, but I don’t see why you couldn’t get some I2C comms working using the method you describe.

1 Like

@Yuri_Rage Thank you.

I will have a poke around in AP_periph to see if I can get the i2c data from the arduino into an L431. All a bit new to me but with examples like yours its a bit easier to start to figure out how things work together.

2 Likes

@Yuri_Rage thanks for sharing this. I’m trying to implement for a 16bit digital flow sensor that uses a 2 byte register for flow measurement. I was able to get the sensor working on Arduino uno but am having issues outputting to ardupilot. When running the LUA script, I get ‘did not return a function error (0x0)’.

Code:
sfm3300_ardunio_to_px4.lua (3.1 KB)
sfm3300_ardunio_ino.txt (2.1 KB)

The problem looks to be either with implementation of I2C_slave on the arduino sketch or with the LUA script itself.

The lines circled in red below are the only I2C_slave commands that I’ve added to my existing ardunio sketch. Following this, there is a loop that continuously runs a function to output and print data from a single flow measurement register (flowSensor.begin() function is called from a header file). See attached sfm3300_ardunio_ino for full code.

With my LUA script, I suspect the issue is most likely in the update function shown below. I am unsure whether I’m using the correct register in the write_register function on line 81. I currently have the 16bit flow measurement register.

Would very much appreciate if you’d be able to take a look and provide any feedback - thanks.

@Yuri_Rage I updated both the ardunio and lua scripts to simplify- here’s where I landed. LUA script now runs, but outputs 0 instead of the expected flow rate. Any ideas on the issue here? Thanks

Arduino Sketch with I2C Slave commands:

LUA Script:

Code:
sfm3300_ardunio_to_px4.lua (2.0 KB)

Assuming the sketch code to read your sensor data is valid (which it does not appear to be since you seem to be ignoring bytes and also attempting to store them in varying types), you’re sending a float value but not collecting it properly in Lua. Look at the DS18B20 example. Floats are 4 bytes and have to be “reassembled” on the autopilot side. It would make more sense to send the raw data and do the conversion in Lua.

Additionally, you’re giving the Arduino the same address as the sensor, so even if any of the code was functional, it would be unpredictable in results.

But…since your device already appears to be an I2C device, there really isn’t a need to use my library. You could just interact with it directly from the autopilot via Lua.

The intent of this project is to provide data from a non-I2C device to the autopilot, using the Arduino and I2C as a conduit.

Thanks for the advice.I resorted to ardunio + lua because the standard ardupilot firmware can only read I2C devices with 8 bit registers and my device uses 16 bit registers.

I see the unpack_ieee754_float() function implemented in the ds18b20 example but am struggling to understand how it works and what needs to be changed for my use case.

My virtual registers should be 2 bytes where the 1st byte (register 0) stores the size of the data and the second stores the actual data (register 1). It looks like the unpack_ieee754_float() function in the ds18b20 example takes a 4x1 table as the input (perhaps to account for the additional data registers?) where I need to take a 2x1 table.

1 Like

Use your Arduino sketch to read the data and store it in an integer or float as needed. Use the example scripts to see how those types can be handled across the I2C interface between the Arduino and the autopilot.

And be careful - the library uses the Wire object already. If you reinitialize it for use with another I2C device, you may get strange results (I wrote this as a way to interface non-I2C devices).

1 Like

Thanks @Yuri_Rage . I was able to get a very basic example working where I write a fixed float using Slave.writeRegisters() in the Arduino sketch, then unpack the 32 bit number in LUA via unpack_ieee754_float() and output to Mission planner.

When I apply the same methodology to my sensor, read_register_data() outputs a nil table. So there appears to be an issue with reading the register from the Arduino in LUA. I verified that the Arduino sketch outputs the intended float data from the sensor, but when I connect the Arduino + sensor to the PX4 I2C port and run the LUA script there is an issue collecting the data (even before being unpacked).

The only issue I can think of here is incorrect inputs to the arduino_i2c = i2c.get_device(I2C_BUS, SLAVE_ADDR) function. In the LUA script, I set the SLAVE_ADDR to 0x09 (not the sensor address 0x40) which is the address used to designate the Arduino board. I also verified that I set the correct I2C_BUS index.

I’m not really sure what else to try at this point, would appreciate any other troubleshooting recommendations you may have. Thanks.

LUA script:

I suspect this has to do with mishandling the Wire object in your Arduino code.