CubePilot CubeOrange+ I2C2 with Lua and Arduino Problems

Hi all,
I am running Arducopter 4.5.5 on my Cube Orange Plus and I am trying to fetch some data from an Arduino via I2C using Lua Script.

I read this awesome blog here on this topic and tried to make my own piece of code: Unsupported Sensors? Try Arduino + Lua!

So I hooked up the wires from the I2C2 port of the Cube Orange to my Arduino Pro Mini like this:
> Pixhawk I2C2 SCL <==> Pro Mini SCL (A5)
> Pixhawk I2C2 SDA <==> Pro Mini SDA (A4)
> Pixhawk I2C2 GND <==> Pro Mini GND

Both the Pixhawk and Arduino are powered via different USB ports from my laptop.

So I made a simple Arduino code to see what kind of data comes from Lua I2C function calls, and it looks like this:

#include <Wire.h>
#define slave_address 0x24

void setup(){
  Serial.begin(115200);

  Wire.setClock(400000);
  Wire.begin(slave_address);
  Wire.onReceive(receive_data_func);
  Wire.onRequest(request_data_func);

  Serial.println("START");
  Serial.println("================\n");
}

void loop(){
  delay(1);
}

void receive_data_func(int howMany){
  Serial.print("receive_data_func called with howMany = "); Serial.println(howMany);
  
  while(Wire.available()){
    Serial.print("  => Data Received: 0x");
    Serial.println(Wire.read(), HEX);
  }
  Serial.print("\n===============\n\n");
}

void request_data_func(){
  Serial.println("request_data_func called");
  Serial.print("\n===============\n\n");
}

Then I made a simple Lua script to test i2c function calls. I used the safety switch as a condition, so the I2C functions get called only when I press the safety switch. The Lua script looks like this:

local i2c_bus_num = 0;
local slave_addr = 0x24;

local safety_switch_pressed = false;
local i2c_tried = false;

local arduino_i2c = i2c.get_device(i2c_bus_num, slave_addr);
arduino_i2c:set_retries(10);

function i2c_check_func()
	safety_switch_pressed = not SRV_Channels:get_safety_state();
	
	if(safety_switch_pressed == true and i2c_tried == false) then
		gcs:send_text(6, "  => Testing I2C");
		arduino_i2c:write_register(0x10, 0x20);
		i2c_tried = true;
	end
	
	if(safety_switch_pressed == false) then
		i2c_tried = false;
	end

	return i2c_check_func, 100   -- Run at every 100ms i.e. 10Hz
end

gcs:send_text(6, "  => Start")

return i2c_check_func()

With this setup, I am able to get the serial prints on my Arduino’s Serial Monitor when I press the Safety switch on my Pixhawk, like this:
image

My Questions are:

  • Since I am using the I2C2 port of the Cube Orange Plus, then why does the Lua script work with bus address 0? When I try with i2c bus address 1 (which is expected for I2C2), I do NOT receive any data on the Arduino’s serial monitor. Why is this? Isn’t I2C1 the expected bus 0?

  • The script only works if the Arduino starts after Pixhawk. If I reboot only the Pixhawk and try again (without making any changes to anything), then there is no data communication on the Arduino’s Serial Monitor. If I reset the Arduino after rebooting Pixhawk, then everything works normally. Why is this?

Please help.

@rmackay9 @tridge @Michael_Oborne @Yuri_Rage @iampete @ppoirier

Thanks,
Divyanshu

Issue 1: Not sure. Seems you’ve got it working.

Issue 2: this is likely an artifact of Arduino’s address reporting and/or serial port power interruption - I wouldn’t worry about it much.

Hello @Yuri_Rage
Thank you for your response. Really appreciate it.

After sending data to Arduino, now I am trying to read data from the Arduino, but I am not receiving any data on Pixhawk.
I need a bit of your guidance since you already have spent a lot of time on this.

So I updated my Arduino Code to use the Wire.write() function, and it looks like this:

#include <Wire.h>
#define slave_address 0x24

void setup(){
  Serial.begin(115200);

  Wire.setClock(400000);
  Wire.begin(slave_address);
  Wire.onReceive(receive_data_func);
  Wire.onRequest(request_data_func);

  Serial.println("START");
  Serial.println("================\n");
}

void loop(){
  delay(1);
}

void receive_data_func(int howMany){
  Serial.print("receive_data_func called with howMany = "); Serial.println(howMany);
  
  while(Wire.available()){
    Serial.print("  => Data Received: 0x");
    Serial.println(Wire.read(), HEX);
  }
  Serial.print("\n===============\n\n");
}

void request_data_func(){
  Serial.println("request_data_func called");
  
  while(Wire.available()){
    Serial.print("  => Data Received: 0x");
    Serial.println(Wire.read(), HEX);
  }

  Serial.println("  => Sending 0x12");
  Wire.write(0x12);

  Serial.print("\n===============\n\n");
}

I also update my Lua script to use the i2c read_registers() function, and it looks like this:

local i2c_bus_num = 0;
local slave_addr = 0x24;

local safety_switch_pressed = false;
local i2c_tried = false;

local arduino_i2c = i2c.get_device(i2c_bus_num, slave_addr);
arduino_i2c:set_retries(3);

local i2c_data_received = 0x00;

function i2c_check_func()
	safety_switch_pressed = not SRV_Channels:get_safety_state();
	
	if(safety_switch_pressed == true and i2c_tried == false) then
		gcs:send_text(6, "  => Testing I2C");
		arduino_i2c:write_register(0x10, 0x20);
		
		i2c_data_received = arduino_i2c:read_registers(0x11);
		gcs:send_text(6, "  => Got Data: " .. tostring(i2c_data_received));
		
		i2c_tried = true;
	end
	
	if(safety_switch_pressed == false) then
		i2c_tried = false;
	end

	return i2c_check_func, 100   -- Run at every 100ms i.e. 10Hz
end

gcs:send_text(6, "DTPC Technologies BDS_0005 L5_v10")

return i2c_check_func()

Now when I press the safety switch of Pixhawk, I get this on the Arduino Serial Monitor, confirming that on i2c read_registers() function, 0x11 gets sent to the Arduino on “receive_data_func()”, and the “Wire.write()” function gets called inside the Arduino’s “request_data_func()”:

But on the Mission Planner’s screen, I am getting this:
image

Can you please help with this? What am I doing wrong?

Thanks,
Divyanshu

You’re reinventing the wheel. Rather than forcing me into a code review, why don’t you just use the library from the thread you linked?

Hello @Yuri_Rage
Thank you for your reply.

Sorry for the trouble. Actually, I am just trying to learn to make the basic code so that I can use it in a bigger system with a lot of custom requirements.

Using the library and editing it, and then getting stuck will consume a lot of time since then I have to debug the library functions also.

Hope you understand.

Thank you.

I wonder if the calls to Serial between the request and send are causing timing issues. Try:

void request_data_func(){
  Wire.write(0x12);
  Serial.println("request_data_func called");
  
  while(Wire.available()){
    Serial.print("  => Data Received: 0x");
    Serial.println(Wire.read(), HEX);
  }

  Serial.println("  => Sent 0x12");

  Serial.print("\n===============\n\n");
}

I just merged some changes to the arduino-i2c-slave library that you might find useful. It won’t impact any of the issues you’re having with read/write calls, but the Lua examples are now far more elegant in the way they handle integer and floating point packing/unpacking.

I also just submitted it for inclusion in the official Arduino Library manager.

Hello @Yuri_Rage,

Thank you soo much for your fast replies. I am able to solve my issue.

As you correctly mentioned, the issue was the Serial commands inside the i2c read and i2c send data handlers.
After removing the serial commands from both handlers, the data is now being sent correctly to the Pixhawk via I2C. Thank you soo much for your help.

Also, I found a simple way to manage the conversion from 16-bit unsigned to 16-bit signed data, as explained below:

In my Arduino code, I am storing my sensor data in “int16_t” variables which would require 2 bytes to send, and the full range is from -32768 to +32767.

So to avoid sign conversion in Lua, I am adding “32768” to my sensor data before sending, and then send the 2 bytes to Lua, as shown below:

void i2c_send_temp_func(int16_t arg_temp){
  uint16_t i2c_send_data = (arg_temp + 32768);  // To handle negative numbers
  Wire.write((uint8_t)(i2c_send_data));         //Send lower byte
  Wire.write((uint8_t)(i2c_send_data >> 8));    // Send upper byte
}

Now on the Lua side, I receive and concatenate the 2 bytes, and just subtract “32768” from it, as shown below:

	local i2c_data_received = arduino_i2c:read_registers(read_cmd, 2);
	sensor_temp = ((i2c_data_received[2] << 8) | i2c_data_received[1]) - 32768;
	gcs:send_text(6, "  => "Got Temp: " .. tostring(sensor_temp));

This works well for both the complete 16-bit signed range and is also simple to understand and implement :wink:

Thanks,
Divyanshu

Indeed, unsigned integers are far easier to manage. In the official library, I use string packing, which is easily modified to handle any numeric type, despite being a bit of overkill for simple unsigned values. For signed integers, you’d use local fmt = ('i%d'):format(#b) (lowercase i). Maybe I should add a comment to that effect. The OneWire DS18B20 example shows how to handle floating point (doubles), which is really where the elegance of string packing shines in this use case.

local function unpack_uint(b)
    if type(b) ~= 'table' or #b == 0 then return ERROR_VALUE end
    local packed_string = string.char(table.unpack(b))
    local fmt = ('I%d'):format(#b)  -- unsigned integer of table size
    local val = string.unpack(fmt, packed_string)
    return val
end

Hi @Yuri_Rage ,
Thank you for your constant help.
I am able to send and read data to/from my secondary STM32 (using in place of Arduino) successfully.

I am reading around 50 bytes from my secondary board (STM32) via I2C in one go. The I2C bus is running at 400kHz, which would take around 1 ms to read the 50 bytes.

I am wondering could this I2C transfer time impact the flight control loop time or not?

Does the I2C transfer work in a blocking way or unblocking way?
Do you have any idea about it?