// ------------------------------------------------------------------------------------------- // I2C Advanced Master // ------------------------------------------------------------------------------------------- // // This creates an I2C master device with simple read/write commands and a small // addressable memory. Note that this communication adds a protocol layer on top of // normal I2C read/write procedures. As such, it is meant to pair with the advanced_slave // sketch. The read/write commands are described below. // // This code assumes the slave config has 256 byte memory and I2C addr is 0x44. // // Tests are as follows: // Pull pin12 input low to send/receive 256 bytes to/from slave in 32 byte blocks. // Pull pin11 input low to send/receive 256 bytes to/from slave in single block. // Pull pin10 input low to send/receive 256 bytes to/from slave in single block, using // non-blocking commands. // Pull pin9 input low to send/receive 256 bytes to/from slave in single block, using // non-blocking commands in DMA mode. // Pull pin8 input low to run I2C rate sweep test. This sweeps the I2C rates on both master // and slave and times the duration of a 256 byte transfer at each rate. // // For basic I2C communication only, refer to basic_master and basic_slave example sketches. // // This example code is in the public domain. // // ------------------------------------------------------------------------------------------- // Slave protocol is as follows: // ------------------------------------------------------------------------------------------- // WRITE - The I2C Master can write to the device by transmitting the WRITE command, // a memory address to store to, and a sequence of data to store. // The command sequence is: // // START|I2CADDR+W|WRITE|MEMADDR|DATA0|DATA1|DATA2|...|STOP // // where START = I2C START sequence // I2CADDR+W = I2C Slave address + I2C write flag // WRITE = WRITE command // MEMADDR = memory address to store data to // DATAx = data byte to store, multiple bytes are stored to increasing address // STOP = I2C STOP sequence // ------------------------------------------------------------------------------------------- // READ - The I2C Master can read data from the device by transmitting the READ command, // a memory address to read from, and then issuing a STOP/START or Repeated-START, // followed by reading the data. The command sequence is: // // START|I2CADDR+W|READ|MEMADDR|REPSTART|I2CADDR+R|DATA0|DATA1|DATA2|...|STOP // // where START = I2C START sequence // I2CADDR+W = I2C Slave address + I2C write flag // READ = READ command // MEMADDR = memory address to read data from // REPSTART = I2C Repeated-START sequence (or STOP/START if single Master) // I2CADDR+R = I2C Slave address + I2C read flag // DATAx = data byte read by Master, multiple bytes are read from increasing address // STOP = I2C STOP sequence // ------------------------------------------------------------------------------------------- // SETRATE - The I2C Master can adjust the Slave configured I2C rate with this command // The command sequence is: // // START|I2CADDR+W|SETRATE|RATE0|RATE1|RATE2|RATE3|STOP // // where START = I2C START sequence // I2CADDR+W = I2C Slave address + I2C write flag // SETRATE = SETRATE command // RATE0-3 = I2C frequency (uint32_t) LSB-to-MSB format // ------------------------------------------------------------------------------------------- #include // Command definitions #define WRITE 0x10 #define READ 0x20 #define SETRATE 0x30 // Function prototypes void print_i2c_setup(void); void print_i2c_status(void); void test_rate(uint8_t target, uint32_t rate); // Memory #define MEM_LEN 256 uint8_t databuf[MEM_LEN]; void setup() { pinMode(LED_BUILTIN,OUTPUT); // LED digitalWrite(LED_BUILTIN,LOW); // LED off pinMode(12,INPUT_PULLUP); // Control for Test1 pinMode(11,INPUT_PULLUP); // Control for Test2 pinMode(10,INPUT_PULLUP); // Control for Test3 pinMode(9,INPUT_PULLUP); // Control for Test4 pinMode(8,INPUT_PULLUP); // Control for Test5 Serial.begin(115200); // Setup for Master mode, pins 18/19, external pullups, 400kHz Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, 400000); Wire.setDefaultTimeout(250000); // 250ms default timeout } void loop() { size_t addr, len; uint8_t target = 0x44; // slave addr uint32_t count; // // A sequence of different read/write techniques. // Pull respective control pin low to initiate sequence. // // All tests will first write values to the slave, then read back the values. // The readback values should match. // // The LED is turned on during I2C operations. If it gets stuck on then the // ISR likely had a problem. This can happen with excessive baud rate. // // Various forms of the Wire calls (blocking/non-blocking/STOP/NOSTOP) are // used in the different tests. // if(digitalRead(12) == LOW) { digitalWrite(LED_BUILTIN,HIGH); // LED on Serial.print("---------------------------------------------------------\n"); Serial.print("Test1 : Using blocking commands:\n"); Serial.print(" 1) WRITE memory in 32 byte blocks\n"); Serial.print(" 2) READ back memory in 32 byte blocks\n"); Serial.print("---------------------------------------------------------\n"); // Writing to Slave -------------------------------------------------------- for(addr = 0; addr < 256; addr += 32) // sweep addr in 32byte blocks { for(len = 0; len < 32; len++) // prepare data to send databuf[len] = (addr+len)^0xFF; // set data (equal to bit inverse of memory address) Serial.printf("I2C WRITE 32 bytes to Slave 0x%0X at MemAddr %d\n", target, addr); Serial.print("Writing: "); for(len = 0; len < 32; len++) { Serial.printf("%d ",databuf[len]); } Serial.print("\n"); Wire.beginTransmission(target); // slave addr Wire.write(WRITE); // WRITE command Wire.write(addr); // memory address Wire.write(databuf, 32); // write 32 byte block Wire.endTransmission(); // blocking write (when not specified I2C_STOP is implicit) print_i2c_status(); // print I2C final status } // Reading from Slave ------------------------------------------------------ for(addr = 0; addr < 256; addr += 32) // sweep addr in 32byte blocks { Wire.beginTransmission(target); // slave addr Wire.write(READ); // READ command Wire.write(addr); // memory address Wire.endTransmission(I2C_NOSTOP); // blocking write (NOSTOP triggers RepSTART on next I2C command) Wire.requestFrom(target,32,I2C_STOP);// blocking read (request 32 bytes) Serial.printf("I2C READ 32 bytes from Slave 0x%0X at MemAddr %d\n", target, addr); Serial.print("Received: "); // print received bytes while(Wire.available()) { Serial.printf("%d ", Wire.readByte()); } Serial.print("\n"); print_i2c_status(); // print I2C final status } digitalWrite(LED_BUILTIN,LOW); // LED off delay(500); // delay to space out tests } if(digitalRead(11) == LOW) { digitalWrite(LED_BUILTIN,HIGH); // LED on Serial.print("---------------------------------------------------------\n"); Serial.print("Test2 : Using blocking commands:\n"); Serial.print(" 1) WRITE entire memory in a single 256 byte block\n"); Serial.print(" 2) READ back entire memory in a single 256 byte block\n"); Serial.print("---------------------------------------------------------\n"); // Writing to Slave -------------------------------------------------------- addr = 0; for(len = 0; len < 256; len++) // prepare data to send databuf[len] = (addr+len)^0xFF; // set data (equal to bit inverse of memory address) Serial.printf("I2C WRITE 256 bytes to Slave 0x%0X at MemAddr %d\n", target, addr); Serial.print("Writing: "); for(len = 0; len < 256; len++) { Serial.printf("%d ",databuf[len]); } Serial.print("\n"); Wire.beginTransmission(target); // slave addr Wire.write(WRITE); // WRITE command Wire.write(addr); // memory address Wire.write(databuf, 256); // write 256 byte block Wire.endTransmission(I2C_STOP); // blocking write (using explicit I2C_STOP) print_i2c_status(); // print I2C final status // Reading from Slave ------------------------------------------------------ Wire.beginTransmission(target); // slave addr Wire.write(READ); // READ command Wire.write(addr); // memory address Wire.endTransmission(I2C_NOSTOP); // blocking write (NOSTOP triggers RepSTART on next I2C command) Wire.requestFrom(target,256,I2C_STOP); // blocking read (request 256 bytes) Serial.printf("I2C READ %d bytes from Slave 0x%0X at MemAddr %d\n", Wire.available(), target, addr); Serial.print("Received: "); // print received bytes while(Wire.available()) { Serial.printf("%d ", Wire.readByte()); } Serial.print("\n"); print_i2c_status(); // print I2C final status Serial.printf("Rate (Hz): %d\n", Wire.i2c->currentRate); digitalWrite(LED_BUILTIN,LOW); // LED off delay(500); // delay to space out tests } if(digitalRead(10) == LOW) { digitalWrite(LED_BUILTIN,HIGH); // LED on Serial.print("---------------------------------------------------------\n"); Serial.print("Test3 : Using ISR NON-blocking commands:\n"); Serial.print(" 1) WRITE a 256 byte block to Slave. While block is\n"); Serial.print(" transferring, perform other commands.\n"); Serial.print(" 2) READ back the 256 byte block from Slave. While\n"); Serial.print(" block is transferring, perform other commands.\n"); Serial.print("---------------------------------------------------------\n"); // Set operating mode to ISR Wire.setOpMode(I2C_OP_MODE_ISR); // Writing to Slave -------------------------------------------------------- addr = 0; for(len = 0; len < 256; len++) // prepare data to send databuf[len] = (addr+len)^0xFF; // set data (equal to bit inverse of memory address) Serial.printf("I2C WRITE 256 bytes to Slave 0x%0X at MemAddr %d\n", target, addr); Serial.print("Writing: "); for(len = 0; len < 256; len++) { Serial.printf("%d ",databuf[len]); } Serial.print("\n"); Wire.beginTransmission(target); // slave addr Wire.write(WRITE); // WRITE command Wire.write(addr); // memory address Wire.write(databuf, 256); // write 256 byte block Wire.sendTransmission(); // NON-blocking write (when not specified I2C_STOP is implicit) Serial.print("...write sent, counting while waiting for Wire.done()...\n"); count = 1; while(!Wire.done()) count++; // Since write is non-blocking, do some counting while waiting Serial.printf("Counted to: %d\n", count++); print_i2c_status(); // print I2C final status // Reading from Slave ------------------------------------------------------ Wire.beginTransmission(target); // slave addr Wire.write(READ); // READ command Wire.write(addr); // memory address Wire.endTransmission(I2C_NOSTOP); // blocking write (NOSTOP triggers RepSTART on next I2C command) Wire.sendRequest(target,256,I2C_STOP); // NON-blocking read (request 256 bytes) // Since request is non-blocking, do some other things. Serial.print("...request sent, doing one thing then waiting for Wire.finish()...\n"); // After doing something, use finish() to wait until I2C done Wire.finish(); Serial.printf("I2C READ %d bytes from Slave 0x%0X at MemAddr %d\n", Wire.available(), target, addr); Serial.print("Received: "); // print received bytes while(Wire.available()) { Serial.printf("%d ", Wire.readByte()); } Serial.print("\n"); print_i2c_status(); // print I2C final status digitalWrite(LED_BUILTIN,LOW); // LED off delay(500); // delay to space out tests } if(digitalRead(9) == LOW) { digitalWrite(LED_BUILTIN,HIGH); // LED on Serial.print("---------------------------------------------------------\n"); Serial.print("Test4 : Using DMA NON-blocking commands:\n"); Serial.print(" 1) WRITE a 256 byte block to Slave. While block is\n"); Serial.print(" transferring, perform other commands.\n"); Serial.print(" 2) READ back the 256 byte block from Slave. While\n"); Serial.print(" block is transferring, perform other commands.\n"); Serial.print("---------------------------------------------------------\n"); // Set operating mode to DMA Serial.print("Trying to set DMA mode : "); Wire.setOpMode(I2C_OP_MODE_DMA); if(Wire.i2c->opMode == I2C_OP_MODE_DMA) Serial.printf("OK (Channel %d)\n",Wire.i2c->DMA->channel); else Serial.print("Failed, using ISR\n"); // Writing to Slave -------------------------------------------------------- addr = 0; for(len = 0; len < 256; len++) // prepare data to send databuf[len] = (addr+len)^0xFF; // set data (equal to bit inverse of memory address) Serial.printf("I2C WRITE 256 bytes to Slave 0x%0X at MemAddr %d\n", target, addr); Serial.print("Writing: "); for(len = 0; len < 256; len++) { Serial.printf("%d ",databuf[len]); } Serial.print("\n"); Wire.beginTransmission(target); // slave addr Wire.write(WRITE); // WRITE command Wire.write(addr); // memory address Wire.write(databuf, 256); // write 256 byte block Wire.sendTransmission(); // NON-blocking write (when not specified I2C_STOP is implicit) Serial.print("...write sent, counting while waiting for Wire.done()...\n"); count = 1; while(!Wire.done()) count++; // Since write is non-blocking, do some counting while waiting Serial.printf("Counted to: %d\n", count++); print_i2c_status(); // print I2C final status // Reading from Slave ------------------------------------------------------ Wire.beginTransmission(target); // slave addr Wire.write(READ); // READ command Wire.write(addr); // memory address Wire.endTransmission(I2C_NOSTOP); // blocking write (NOSTOP triggers RepSTART on next I2C command) Wire.sendRequest(target,256,I2C_STOP); // NON-blocking read (request 256 bytes) // Since request is non-blocking, do some other things. Serial.print("...request sent, doing one thing then waiting for Wire.finish()...\n"); // After doing something, use finish() to wait until I2C done Wire.finish(); Serial.printf("I2C READ %d bytes from Slave 0x%0X at MemAddr %d\n", Wire.available(), target, addr); Serial.print("Received: "); // print received bytes while(Wire.available()) { Serial.printf("%d ", Wire.readByte()); } Serial.print("\n"); print_i2c_status(); // print I2C final status digitalWrite(LED_BUILTIN,LOW); // LED off delay(500); // delay to space out tests } if(digitalRead(8) == LOW) { uint8_t fail=0; digitalWrite(LED_BUILTIN,HIGH); // LED on Serial.print("---------------------------------------------------------\n"); Serial.print("Test5 : Rate adjustment tests. This sweeps the I2C rates\n"); Serial.print(" on both Master and Slave and times the duration \n"); Serial.print(" of a 256 byte transfer at each rate.\n"); Serial.print("---------------------------------------------------------\n"); for(uint8_t opMode = I2C_OP_MODE_IMM; opMode <= I2C_OP_MODE_DMA; opMode++) { Wire.setOpMode((i2c_op_mode)opMode); // set op mode test_rate(target, 10000, fail); test_rate(target, 100000, fail); test_rate(target, 200000, fail); test_rate(target, 300000, fail); test_rate(target, 400000, fail); test_rate(target, 600000, fail); test_rate(target, 800000, fail); test_rate(target, 1000000, fail); test_rate(target, 1200000, fail); test_rate(target, 1500000, fail); test_rate(target, 1800000, fail); test_rate(target, 2000000, fail); test_rate(target, 2400000, fail); test_rate(target, 2800000, fail); test_rate(target, 3000000, fail); test_rate(target, 4000000, fail); test_rate(target, 5000000, fail); test_rate(target, 6000000, fail); // Restore normal settings // Change Slave rate Wire.beginTransmission(target); // slave addr Wire.write(SETRATE); // SETRATE command Wire.write((uint8_t)400000&0xFF); // rate LSB Wire.write((uint8_t)(400000>>8)&0xFF); Wire.write((uint8_t)(400000>>16)&0xFF); Wire.write((uint8_t)(400000>>24)&0xFF); // rate MSB Wire.endTransmission(); // blocking write // Change Master rate Wire.setClock(400000); fail = 0; // reset flag } Wire.setOpMode(I2C_OP_MODE_ISR); // restore default ISR mode print_i2c_status(); // print I2C final status // Restore normal settings (400kHz) // Change Slave rate Wire.beginTransmission(target); // slave addr Wire.write(SETRATE); // SETRATE command Wire.write((uint8_t)400000&0xFF); // rate LSB Wire.write((uint8_t)(400000>>8)&0xFF); Wire.write((uint8_t)(400000>>16)&0xFF); Wire.write((uint8_t)(400000>>24)&0xFF); // rate MSB Wire.endTransmission(); // blocking write // Change Master rate Wire.setClock(400000); digitalWrite(LED_BUILTIN,LOW); // LED off delay(500); // delay to space out tests } } // // print current setup // void print_i2c_setup() { Serial.print("Mode:"); switch(Wire.i2c->opMode) { case I2C_OP_MODE_IMM: Serial.print("IMM "); break; case I2C_OP_MODE_ISR: Serial.print("ISR "); break; case I2C_OP_MODE_DMA: Serial.printf("DMA[%d] ",Wire.i2c->DMA->channel); break; } Serial.printf("Pins: %d/%d ", Wire.i2c->currentSCL, Wire.i2c->currentSDA); } // // print I2C status // void print_i2c_status(void) { switch(Wire.status()) { case I2C_WAITING: Serial.print("I2C waiting, no errors\n"); break; case I2C_ADDR_NAK: Serial.print("Slave addr not acknowledged\n"); break; case I2C_DATA_NAK: Serial.print("Slave data not acknowledged\n"); break; case I2C_ARB_LOST: Serial.print("Bus Error: Arbitration Lost\n"); break; case I2C_TIMEOUT: Serial.print("I2C timeout\n"); break; case I2C_BUF_OVF: Serial.print("I2C buffer overflow\n"); break; default: Serial.print("I2C busy\n"); break; } } // // print I2C rate // void print_rate(uint32_t rate) { Serial.printf("%d Hz ", rate); } // // test rate // void test_rate(uint8_t target, uint32_t rate, uint8_t& fail) { uint32_t deltatime=0; size_t len; if(!fail) { for(len = 0; len < 256; len++) // prepare data to send databuf[len] = len; // set data (equal to addr) // Change Slave rate Wire.beginTransmission(target); // slave addr Wire.write(SETRATE); // SETRATE command Wire.write((uint8_t)rate&0xFF); // rate LSB Wire.write((uint8_t)(rate>>8)&0xFF); Wire.write((uint8_t)(rate>>16)&0xFF); Wire.write((uint8_t)(rate>>24)&0xFF); // rate MSB Wire.endTransmission(); // blocking write fail = Wire.getError(); if(!fail) { // Change Master rate Wire.setClock(rate); // Setup write buffer Wire.beginTransmission(target); // slave addr Wire.write(WRITE); // WRITE command Wire.write(0); // memory address Wire.write(databuf, 256); // write 256 byte block // Write to Slave elapsedMicros deltaT; Wire.endTransmission(); // blocking write deltatime = deltaT; fail = Wire.getError(); if(!fail) { Wire.beginTransmission(target); // slave addr Wire.write(READ); // READ command Wire.write(0); // memory address Wire.endTransmission(I2C_NOSTOP); // blocking write (NOSTOP triggers RepSTART on next I2C command) Wire.requestFrom(target,256,I2C_STOP);// blocking read fail = Wire.getError(); if(!fail) { for(len = 0; len < 256; len++) // verify block if(databuf[len] != Wire.readByte()) { fail=1; break; } } } } print_i2c_setup(); if(!fail) { // Print result Serial.print("256 byte transfer at "); print_rate(rate); Serial.printf(" (Actual Rate (Hz): %d) : %d us : ", Wire.getClock(), deltatime); print_i2c_status(); } else { Serial.printf("Transfer fail : %d us : ",deltatime); print_i2c_status(); } } }