First Arduino I2C Experience

Background

So this is the first steps I’m taking toward a “big project”. The idea is to build a controller unit with sensors attached as individual units from which data will be collected and send to a cloud service for storage and presentation – ala IoT.

The bigger project will be discussed in a later post, but for now I am sharing my initial experiment with some notes (and questions… many questions).

Design Notes

This proof of concept project aims to explore I2C between two Arduino UNO’s to see if this is a viable approach to what I want to do. The idea eventually would be to have many “sensors” on a I2C bus providing the master with data on request.

Products Used

Below are some products I used.

Apart from the Open Source software stack, I did spend a lot of money – in my opinion – in obtaining what I required. However, I feel it was money well spent as I am using the equipment for a lot of other experiments and learning activities as well.

Reference Sites

Below are some sites I referenced to put the solution together.

Twitter links

To follow any of the guys I reference on Twitter, please have a look at their pages below:

Of course, my twitter page is at https://twitter.com/nicocoetzee1 🙂

Solution Overview – In Pictures

I have not managed to go into this whole diagramming or fritzing thing, so I am afraid you have to rely on my photos for now. I think this is one of the next skills I need to acquire!

Complete Set-up

2015-10-18 12.05.00

This image shows the SparkFun Redboard acting as the Master and with the LCD attached.

The basic set-up exactly mimics project 15 of the SparkFun Inventors Kit. You can download the PDF manual from their site. I have the version 3.1 kit, but the version 3.2 kit in the download is also still good.

Output on the LCD

20151018_120707

The LCD is updated every second with a new value it obtains from the slave “sensor” – although the slave only produces a random number between 0 and 1024 at the moment.

The idea is to now add a WiFi module in order to send this data to the cloud. Later, the random number generator will be replaced with some more useful sensor, like a temperature sensor. Finally I hope to hookup a whole bunch of sensors.

USB Connectivity

20151018_120800

I use a 8 port powered USB hub I got from a local computer dealer. Linux and USB can have it’s share of issues, but sometimes you take a change and in this case I think I got lucky!

I will go into more detail as to how to identify and address your devices a little further on in this article.

BitScope UI

I will be the first to admit that I no very little at this point about logic analyzers, but this is part of the learning path that I am on. At the moment, I have no idea really how to go into the analysis of what I see, but I was able to dump the readings to a CSV. I am sure that in time to come I will be able to make much better use of this piece of equipment.

I also have a proper hardware Oscilloscope and Logic Analyzer: the Rigol DS1102D – I will also be using this from time to time. I argued that it’s cheaper to blow up the BitScope than the Rigol, hence me sticking with the BitScope for now.

Sketches

Let me start to say that I have not tried to be efficient or neat with any of the sketches yet – I just want something that works at the moment. I am also still struggling to wrap my head around debugging strategies, so my thinking is to dump debug output to the serial interface from where I can monitor all the devices. This may not be the best approach for “production” solutions, but I am still thinking about long term strategies for debugging and perhaps even remote diagnostics.

From a project point of view, here is my project directory layout:

nicc777@electrostatic:~/PlatformIO_Projects/I2C_Test01$ tree
.
|-- Master
|   |-- lib
|   |   `-- readme.txt
|   |-- platformio.ini
|   `-- src
|       `-- maim.cpp
|-- Master_Sender_bb.png
|-- README
|-- Slave
|   |-- lib
|   |   `-- readme.txt
|   |-- platformio.ini
|   `-- src
|       `-- maim.cpp
`-- Webpage-Dumps

The “Webpage-Dump” directory is where I collected my info from the web for future reference – you never know how long these resources will remain on the web.

The Master Node

Content of “Master/src/maim.cpp” (note: “maim” is actually a typo, but that’s life happening for you).

#ifdef ENERGIA
  #include "Energia.h"
#else
  #include "Arduino.h"
#endif

#ifndef LED_PIN
  // Most Arduino boards already have a LED attached to pin 13 on the board itself
  #define LED_PIN 13
#endif

#include 
#include 

// Initialize the library with the pins we're using.
// (Note that you can use different pins if needed.)
// See http://arduino.cc/en/Reference/LiquidCrystal
// for more information:

const byte SLAVE_ADDRESS = 8;

LiquidCrystal lcd(12,11,5,4,3,2);
bool gotData;
int dataValue;
char dataValueStr[] = {' ', ' ', ' ', ' ', ' ', ' '};
int cPos;
int loopcount;

void clearLCDStatus()
{
  lcd.setCursor(0,1);
  lcd.print("              "); // Erase the largest possible number
}

void setup()
{
  loopcount = 0;
  // Init the LED and switch it off
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  Serial.begin(9600);
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Value:");
  Wire.begin ();
  delay(10000);
}

void resetValues()
{
  cPos = 0;
  dataValue = -1;
  gotData = false;
  for(int i=0; i<6; i++){     dataValueStr[i] = ' ';   } } void loop() {   loopcount = loopcount + 1;   Serial.print("Loop nr: ");   Serial.println(loopcount);      gotData = false;   clearLCDStatus();   lcd.setCursor(0,1);   lcd.print("-- no data --");   Serial.print("   About to send request for data... ");   //Wire.beginTransmission (SLAVE_ADDRESS);   Wire.requestFrom(SLAVE_ADDRESS, 4);   Serial.println("DONE. Waiting for data...");   Serial.println("");   delay(50);   while (Wire.available()) { // slave may send less than requested     char c = Wire.read(); // receive a byte as character     delay(50);     Serial.print("From slave: ");     Serial.println(c);     //Serial.print(c);         // print the character     dataValueStr[cPos] = c;     cPos = cPos + 1;   }   Serial.println("");   Serial.print("cPos=");   Serial.println(cPos);   Serial.println("");   if(cPos > 0){
    cPos = 0;
    gotData = true;
  }

  if( gotData ) {
    clearLCDStatus();
    lcd.setCursor(0,1);
    lcd.print(dataValueStr);
    Serial.print("   Value: ");
    Serial.println(dataValueStr);
  } else {
    clearLCDStatus();
    lcd.setCursor(0,1);
    lcd.print("-- no data --");
    Serial.print("   Value: ");
    Serial.println("-- no data --");
  }
  
  resetValues();
  delay(900);

}

This sketch is UGLY – I know!! But it get’s the job done, and I get plenty of debug information on the serial interface.

The Slave Node

Content of “Slave/src/maim.cpp”:

#ifdef ENERGIA
  #include "Energia.h"
#else
  #include "Arduino.h"
#endif

#ifndef LED_PIN
  // Most Arduino boards already have a LED attached to pin 13 on the board itself
  #define LED_PIN 13
#endif

// Example based on advice from http://gammon.com.au/i2c and https://www.arduino.cc/en/Tutorial/MasterReader

#include 

int randNumber;
int seconds;
long tCounter;
int incomingByte;
char spaceChar = ' ';

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  randNumber = random(1024);    // Simulate a reading from some sensor...
  char responseText[] = "";
  sprintf(responseText, "%d", randNumber);
  if(randNumber < 10){
    responseText[1] = spaceChar;
    responseText[2] = spaceChar;
    responseText[3] = spaceChar;
  } else if(randNumber < 100) {
    responseText[2] = spaceChar;
    responseText[3] = spaceChar;
  } else if(randNumber < 100) {     responseText[3] = spaceChar;   }   Wire.write(responseText); } void setup() {   Serial.begin(9600);   seconds = 0;   tCounter = 0.0;   incomingByte = 0;   randNumber = 5;   // Init the LED and switch it off   pinMode(LED_PIN, OUTPUT);   digitalWrite(LED_PIN, LOW);   Wire.begin(8);                // join i2c bus with address #8   Wire.onRequest(requestEvent); // register event } void loop() {   delay(100);   tCounter = tCounter + 100;   if( tCounter >= 1000 ) {
    seconds = seconds + 1;
    tCounter = 0.0;
  }

  // Serial Diagnostics: send data only when you receive data:
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();
  
    // Diagnostics to Serial...
    Serial.print(seconds);
    Serial.print(" ");
    Serial.println(randNumber);
  }

}

Note: The loop() function could be left empty, but I decided to dump all my debug stuff here – again dumping to the serial console.

Working with PlatformIO

Sketches and Multiple Boards

Well, I absolutely love this development platform! I just could never get used to the Arduino IDE for some reason – perhaps to do with my development background which has rarely involved IDE’s. With PlatformIO I do my coding in Vim and use the familiar command line environment on Linux. Although I have not don’t it for this project, you could easily add version control like Git for your project. I will probably be using Git for my major project(s).

Working with multiple platforms at the same time is wonderful, but you will need to know how to address the various platforms.

What I did was to insert one device at a time to see how they are identified. There are two tools you can use for this purpose:

  • PlatformIO’s commands
  • Linux commands

For PlatformIO, you can use the following command to identify the USB device(s):

nicc777@electrostatic:~$ platformio serialports list | tail
/dev/ttyUSB0
------------
Hardware ID: USB VID:PID=0403:6001 SNR=A5025X7S
Description: Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC 

/dev/ttyACM0
------------
Hardware ID: USB VID:PID=2341:0043 SNR=85332343332351F0F120
Description: ttyACM0

This is just an example showing the two Arduino’s. By the process of running the command after you plug in each device in turn, I found the following:

  • The BitScope was at /dev/ttyUSB0 (not as per the above – at that stage I removed the BitScope)
  • The Redboard was at /dev/ttyUSB1
  • The Arduino was at /dev/ttyACM0

With the above setup, you can compile and upload a sketch to a specific device using the following command (note: the BitScope was unplugged now, so the Redboard was at /dev/ttyUSB0):

nicc777@electrostatic:~/PlatformIO_Projects/I2C_Test01/Master$ platformio run --target upload --upload-port /dev/ttyUSB0 
[Sun Oct 18 10:01:58 2015] Processing sparkfun_redboard (platform: atmelavr, board: sparkfun_redboard, framework: arduino)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
avr-g++ -o .pioenvs/sparkfun_redboard/src/maim.o -c -fno-exceptions -fno-threadsafe-statics -g -Os -Wall -ffunction-sections -fdata-sections -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO_ARCH_AVR -DARDUINO_AVR_UNO -DARDUINO=10605 -DPLATFORMIO=020303 -I.pioenvs/sparkfun_redboard/FrameworkArduino -I.pioenvs/sparkfun_redboard/FrameworkArduinoVariant -I.pioenvs/sparkfun_redboard/Wire -I.pioenvs/sparkfun_redboard/Wire/utility -I.pioenvs/sparkfun_redboard/LiquidCrystal src/maim.cpp
src/maim.cpp: In function 'void loop()':
src/maim.cpp:74:36: warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second: [enabled by default]
Wire.requestFrom(SLAVE_ADDRESS, 4);
^
In file included from src/maim.cpp:12:0:
.pioenvs/sparkfun_redboard/Wire/Wire.h:59:13: note: candidate 1: uint8_t TwoWire::requestFrom(int, int)
uint8_t requestFrom(int, int);
^
.pioenvs/sparkfun_redboard/Wire/Wire.h:57:13: note: candidate 2: uint8_t TwoWire::requestFrom(uint8_t, uint8_t)
uint8_t requestFrom(uint8_t, uint8_t);
^
avr-g++ -o .pioenvs/sparkfun_redboard/firmware.elf -Os -mmcu=atmega328p -Wl,--gc-sections,--relax .pioenvs/sparkfun_redboard/src/maim.o -L.pioenvs/sparkfun_redboard -Wl,--start-group -lm .pioenvs/sparkfun_redboard/libFrameworkArduinoVariant.a .pioenvs/sparkfun_redboard/libFrameworkArduino.a .pioenvs/sparkfun_redboard/libWire.a .pioenvs/sparkfun_redboard/libLiquidCrystal.a -Wl,--end-group
avr-objcopy -O ihex -R .eeprom .pioenvs/sparkfun_redboard/firmware.elf .pioenvs/sparkfun_redboard/firmware.hex
BeforeUpload(["upload"], [".pioenvs/sparkfun_redboard/firmware.hex"])
"/home/nicc777/.platformio/packages/tool-avrdude/avrdude" -v -p atmega328p -C "/home/nicc777/.platformio/packages/tool-avrdude/avrdude.conf" -c arduino -b 115200 -D -P /dev/ttyUSB0 -U flash:w:.pioenvs/sparkfun_redboard/firmware.hex:i

avrdude: Version 6.0.1, compiled on Apr  3 2014 at 21:52:43
Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
Copyright (c) 2007-2009 Joerg Wunsch

System wide configuration file is "/home/nicc777/.platformio/packages/tool-avrdude/avrdude.conf"
User configuration file is "/home/nicc777/.avrduderc"
User configuration file does not exist or is not a regular file, skipping

Using Port                    : /dev/ttyUSB0
Using Programmer              : arduino
Overriding Baud Rate          : 115200
AVR Part                      : ATmega328P
Chip Erase delay              : 9000 us
PAGEL                         : PD7
BS2                           : PC2
RESET disposition             : dedicated
RETRY pulse                   : SCK
serial program mode           : yes
parallel program mode         : yes
Timeout                       : 200
StabDelay                     : 100
CmdexeDelay                   : 25
SyncLoops                     : 32
ByteDelay                     : 0
PollIndex                     : 3
PollValue                     : 0x53
Memory Detail                 :

Block Poll               Page                       Polled
Memory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack
----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
eeprom        65    20     4    0 no       1024    4      0  3600  3600 0xff 0xff
flash         65     6   128    0 yes     32768  128    256  4500  4500 0xff 0xff
lfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
hfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
efuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
lock           0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
calibration    0     0     0    0 no          1    0      0     0     0 0x00 0x00
signature      0     0     0    0 no          3    0      0     0     0 0x00 0x00

Programmer Type : Arduino
Description     : Arduino
Hardware Version: 3
Firmware Version: 4.4
Vtarget         : 0.3 V
Varef           : 0.3 V
Oscillator      : 28.800 kHz
SCK period      : 3.3 us

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e950f
avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: safemode: efuse reads as 0
avrdude: reading input file ".pioenvs/sparkfun_redboard/firmware.hex"
avrdude: writing flash (5432 bytes):

Writing | ################################################## | 100% 0.77s

avrdude: 5432 bytes of flash written
avrdude: verifying flash memory against .pioenvs/sparkfun_redboard/firmware.hex:
avrdude: load data flash data from input file .pioenvs/sparkfun_redboard/firmware.hex:
avrdude: input file .pioenvs/sparkfun_redboard/firmware.hex contains 5432 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.56s

avrdude: verifying ...
avrdude: 5432 bytes of flash verified

avrdude: safemode: lfuse reads as 0
avrdude: safemode: hfuse reads as 0
avrdude: safemode: efuse reads as 0
avrdude: safemode: Fuses OK (H:00, E:00, L:00)

avrdude done.  Thank you.

Debugging – Using the Serial Console

As with the example above, you can attach to a serial console by addressing the relevant serial device.

First I show an example of connecting to the Master and then the Slave:

nicc777@electrostatic:~/PlatformIO_Projects/I2C_Test01/Master$ platformio serialports monitor -p /dev/ttyUSB1
--- Miniterm on /dev/ttyUSB1: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Loop nr: 1
   About to send request for data... DONE. Waiting for data...

From slave: 2
From slave: 7
From slave: 6
From slave: 

cPos=4

   Value: 276                
Loop nr: 2
   About to send request for data... DONE. Waiting for data...

From slave: 9
From slave: 3
From slave: 0
From slave: 

cPos=4
nicc777@electrostatic:~$ platformio serialports monitor -p /dev/ttyACM0
--- Miniterm on /dev/ttyACM0: 9600,8,N,1 ---
--- Quit: Ctrl+]  |  Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
2 753
2 753
3 42
4 42

Final Thoughts

I am happy that I eventually got his far. The entire exercise took about 8 hours over two days. I do have some questions though and I will need to investigate these as time goes by.

The I2C Examples

In one of the resources I referred to (http://gammon.com.au/i2c) the hookup diagram shows some resistors being used between the two Arduinos. I have not figured out why that is necessary yet, but I must be honest in that I have not really tried. Perhaps it is mentioned on the page – but I was more interested in the sketches as examples at the time. I still need to get back to this item to understand it better.

I2C Blocking

Although it does not seem to be consistent, I have found that the master especially can block as soon as something funny happens on the I2C interface. For example, when I remove the slave and couple it again, the Master may not always start to read again – it may take another unplug and reconnection for the process to start going again.

I also found that un-hooking the BitScope probes from the I2C connectors caused a similar issue – nit sure yet why. Unplugging the slave and reconnecting it twice resolved the issue.

In future I plan to look at non-blocking options and perhaps RTOS or timeout strategies. I am not yet sure if any of these approaches will work, but I am sure to build viable production products these issues will have to be addressed in some way. Perhaps it even turns out that I2C is not the optimal strategy and I may need to look at other options. I was toying with the idea of using a Raspberry Pi as the Master and then communicate to the Slaves which may all by ESP8266 based.

Other Thoughts

As a long term strategy I would also need to consider times when a network is not available. I have some EEPROM 256k chips lying around, so perhaps I can dump the data in there and then synchronize to the cloud as soon as network connectivity is restored. But will my sketches be able to handle all this logic? Some serious thought needs to go into this.

I also mentioned the diagnostics. Serial diagnostics is fine in a lab environment, but how would I do this for products in the field? I may need to develop some remote command capability and find efficient ways to communicate with the master. Again this drives the case for using a Pi as I can then add a lot more complex code to the mix. It will however raise the price – I can better understand now the real issues engineers have to deal with!

In Conclusion

I hope someone else could find this useful. At the moment I have configured WordPress to close comments after 30 days, so if you need to chat to me about this specific page and you are not able to leave a comment, please see how you can contact me from this page: https://about.me/nico.coetzee

Have fun!

Advertisements
First Arduino I2C Experience