Sending Ack/Nack values as I2C Slave

Im using an arduino nano 33 ble sense on the ArduinoCore-mbed boards package, and using the library Wire.h inside of this to communicate over i2c as a slave. Im trying to talk to a wiimote, and have hooked everything, including my two 4.7k pullup resistors.

The wiimote should ask for two bytes at 0xFE, and then write me a response back. When i ran my program, i got no response back:

16:13:44.244 -> Ardwiinote starting...
16:13:44.244 -> Starting device at 0x53
16:13:57.752 -> 
16:13:57.752 -> Recieving information...
16:13:57.892 -> 
16:13:57.892 -> Handling request to register 0xFE with offset 0x0
16:13:57.985 -> 	Sending bytes: 0x0,0x5

I am assuming the reason for the hang is that i had not implemented a way to ack/nack messages. But after looking through Wire.h, i had only found twi_reply(1); which is not available on my arm board. Im not sure if Wire.h ack’s automatically and the cause is elsewhere, or if it doesn’t and i have to find a workaround. Here is my code:

#include <Wire.h>

WiimoteEmu::MotionPlus motion_plus;
std::uint8_t current_address = motion_plus.INACTIVE_DEVICE_ADDR;
static std::uint8_t state = 0;

#define SERIAL_BAUD 115200
#define MAX_REQUEST_SIZE 32
#define CLOCK_FREQ 400000


static void receive_bytes(int count) {

  Serial.println("\nRecieving information...");
  delay(100);

  // Determine if recived bytes were for request or write
  // Depending on the amount of bytes sent
  // First byte recieved is ALWAYS address
  state = Wire.read();
  
  if (count > 1) {
    std::uint8_t* buffer;

    Serial.print("Recieved bytes to register 0x");
    Serial.println(state, HEX);
    delay(100);

    Serial.print("\tRecieved bytes: ");
    for (int i = 1; i < count; i++) {
      buffer[i-1] = Wire.read();

      Serial.print(i == 1 ? "0x" : ",0x");
      Serial.print(buffer[i-1], HEX);

      delay(50);
    }
    
    motion_plus.BusWrite(current_address, state, count-1, buffer);
  }
}

static void handle_request() {

  static std::uint8_t last_state = 0xFF;
  static int offset = 0;  // apparently offset starts at 0x08? im not sure if this is true
  std::uint8_t buffer[MAX_REQUEST_SIZE] = {0xFF};

  // Used to update offset if requested register is same
  if(last_state == state) offset += MAX_REQUEST_SIZE;
  else {
    last_state = state;
    offset = 0;
  }

  // Updates values in buffer to represent data at requested register
  int buffer_len = motion_plus.BusRead(current_address, state+offset, MAX_REQUEST_SIZE, buffer);

  Serial.print("\nHandling request to register 0x");
  Serial.print(state, HEX);
  Serial.print(" with offset 0x");
  Serial.println(offset, HEX);
  delay(100);
    
  if(buffer_len > 0){
     Serial.print("\tSending bytes: ");
     for(int i = 0; i < buffer_len; i++){
       Serial.print(i == 0 ? "0x" : ",0x");
       Serial.print(buffer[i], HEX);

       delay(50);
     }

     // Write data to I2C Master
     Wire.write(buffer, buffer_len);
   }
   else Serial.println("\tNo more bytes available for that que!");
}

void StartI2C(std::uint8_t addr){
  // Check if we should be starting at a new address or changing an old one
  if (addr != current_address){
    current_address = addr;
    Wire.end();
  }

  // Begins I2C communication at address
  Wire.begin(current_address);
  Wire.setClock(CLOCK_FREQ);

  // Sets up handlers to BusRead/BusWrite functions
  Wire.onReceive(receive_bytes);
  Wire.onRequest(handle_request);

  Serial.print("Starting device at 0x");
  Serial.println(current_address, HEX);
}

void setup() {
  // put your setup code here, to run once:
  // You'll need to dispatch ALL i2c reads and writes to the MotionPlus object.
  
  // Resets all state
  motion_plus.Reset();

  // Begins sensor data collection
  Magnetic.begin();
  Gyroscope.begin();
  Accelerometer.begin();

  // Starts serial communication
  Serial.begin(SERIAL_BAUD);
  while(!Serial);
  
  Serial.println("Ardwiinote starting...");

  // Begins I2C communication at inactive address
  StartI2C(current_address);
  
  delay(5000);
}

void UpdateSlaveAddress(){
  // after an Update() call the motion plus might decide on a different value
  // for its device detect pin. it pretends to the wii remote to disconnect and
  // connect on initialization. this is required for normal operation. You will
  // need to set a pin depending on this value. I can't remember if it's active
  // high or low and I assume there is a pull-up/down on the wii remote end.
  // for now, this function simply changes the slave address to an active one.
    
  if(motion_plus.ReadDeviceDetectPin() && current_address == motion_plus.INACTIVE_DEVICE_ADDR){
    StartI2C(motion_plus.ACTIVE_DEVICE_ADDR);
  }
  else if (!motion_plus.ReadDeviceDetectPin() && current_address == motion_plus.ACTIVE_DEVICE_ADDR){
    StartI2C(motion_plus.INACTIVE_DEVICE_ADDR);
  }
}

void UpdateGyro(){
  // Gather multiple sources of data for sensor fusion
  Nano33BLEMagneticData magneticData;
  Nano33BLEGyroscopeData gyroscopeData;
  Nano33BLEAccelerometerData accelerometerData;
  
  // Checks if data was gathered succesfully
  if(Magnetic.pop(magneticData),
    Gyroscope.pop(gyroscopeData),
    Accelerometer.pop(accelerometerData)){

    // Update quaternion values by giving the sensor fusion algrithom data
    MadgwickAHRSupdate(deg2rad(gyroscopeData.x), deg2rad(gyroscopeData.y), deg2rad(gyroscopeData.z), 
                       accelerometerData.x, accelerometerData.y, accelerometerData.z, 
                       magneticData.x, magneticData.y, magneticData.z);

    // Ideally, to simulate the actual hardware, this is triggered on a read of a
    // specific i2c area when the M+ is in a certain state. The interface is a bit
    // different because dolphin's emulation is a bit higher level here.
    motion_plus.PrepareInput(q0, q1, q2, q3);
  }
  else Serial.println("\nWARNING: ERROR OCCURED WHILE GRABBING SENSOR DATA\n");
}

void loop() {
  // put your main code here, to run repeatedly:
  // The MotionPlus object expects to have its Update function called
  // UPDATE_FREQ times a second (200hz). It has some internal "timers".
  
  motion_plus.Update();
  UpdateSlaveAddress();
  if(current_address == motion_plus.ACTIVE_DEVICE_ADDR) UpdateGyro();
}

Do you know how good (bug-free) the Arduino Nano 33 BLE Sense is as a I2C Slave ?

I don't know what the WiimoteEmu does, but there are a few things in the sketch that are not okay.

The interrupt handler "receive_byte()" has these mistakes:

  • It uses Serial functions. Don't use any Serial function.
  • It has a delay(100). Suspending a interrupt routine for 100ms is bad. One more of even worse, and 50ms delay for each byte makes it terrible.
  • The 'buffer' is only a pointer. There is no memory location for the data. Yet you use the pointer to store data.

The interrupts handler "handle_request()" has these mistakes:

  • Serial functions
  • delay()

Is there an example that shows the WiimoteEmu usage ?

Ah, my mistake. Now that you mention it delays in whats supposed to be a fast protocol isn't the best idea. This'll make debugging harder, but it should still be done. Also, my other 3 files are taken from the dolphin repository, so there shouldn't be any problems, but f you'd like, i can attach all 3 files in another reply! :slight_smile:

More links please ! What is the dolphin repository ?
Yes, debugging is harder. If the onRequest and onReceive handlers are super simple and fast and short, then there is not much to debug.
"Fast" is relative :wink: Your board has a processor running at 64MHz.

Okay, and thanks for your help! I want to make an arduino act like a wii motion plus to upgrade its gyroscope. I took code from this dolphin emulator’s repository to set up the rules for how the arduino should talk. Then i just hooked up its busread/buswrite functions to arduinos onrecieve and onrequest functions. I call it, “ardwiinote” hehe =)

Ardwiinote.zip (158 KB)

It has a delay(100)

Yes, terrible idea, but that terribleness is offset on an AVR-based Arduino by the odd fact that delay() does essentially nothing when in an interrupt routine. The argument is ignored. Not sure about the Nano 33 BLE, though.

The real disaster would of course be the serial.print() calls.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.