:: ATMEL Two Wire Interface and the Wire Library ::

This Wiki page is a deep dive into the Arduino board's Two Wire library. It specifically references the Arduino Duemilanove with its ATmega328 chip. It explores the source code and describes the hardware in the computer, and how the library makes it tick.

Contents

Introduction

Greetings, fellow Arduinoid! GreyGnome here. Confused about I2C? Want to get your Arduino to chatter with other ICs? Me too. Imagine getting your nice shiny new Arduino, scouring the tutorials scattered about the Internet, wiring up your shiny new I2C-compatible IC, typing up your first sketch using the Wire library, uploading it, only to find... nothing. No I2C bliss. No chatter back and forth.

This was my experience. And the problem began, as they often do, from ignorance. I have the necessary I2C timing diagrams but really didn't know what Arduino's Wire library was doing- under the hood. Why should I use it, if it hardly seems to work? Why not just write my own routines, send my signals out some digital pin (there are plenty, right?), and be done with it? What does Wire have that I couldn't write myself, which would be perhaps even less opaque and easier to use?

In short, how does a sketch written using the Wire library translate to the serial I2C signals between the Arduino and MPR084?

Well, by the end of this exploration I hope to answer all those questions. For there are many more wise and talented than I who have not only written but successfully implemented the Arduino's Wire library, so the problem is probably not out there somewhere. It's here, in my head, as I type this. And as a beginner, I'm inclined to think that I simply don't know any better. Time to get educated- after that, I can decide what do do and how to do it.

So- join me on this journey into the bowels of the Arduino and the Atmel MCU (microcontroller unit).

Assumptions

In this tutorial I assume you:

  • have a basic understanding of the I2C standard. Wikipedia has a nice article.
  • don't need me to http://lmgtfy.com/ . If you want to understand, say, the MPR084, you can readily Google and find its datasheet faster than I can type http://lmgtfy.com/?q=mpr084+datasheet .
  • are working on an Arduino board very similar to the Duemilanove (the Uno is very close, and uses the same processor). If you are using a Mega, for instance, you will have to be aware of any differences.
  • have a copy of the ATmega328 datasheet, revision Rev. 8271C – 08/10. Newer revisions are fine, I'm sure, but my page numbers might be off a little bit.

Setup

We begin with the Arduino Duemilanove and the MPR084 IC. This is a touch sensitive keyboard controller with 8 inputs. It uses the I2C standard for serial communication over the two wires: SDA for data and SCL for clock. The Arduino is the I2C master and the MPR084 is the slave.

The MPR084

In order to test the communications, I want to run something simple. So I will attempt to read a value from the MPR084. The device contains a register that you can query which contains version information for the part. I will query its first 16 bytes. This means I need to:

  • Properly address the MPR084
  • Send the proper register address
  • Read in the data

The datasheet for the MPR084 says:

The pointer generally auto-increments after each data byte is read using the same rules as for a write... Thus, a read is initiated by first configuring the MPR084’s command byte by performing a write... The master can now read ‘n’ consecutive bytes from the MPR084, with the first data byte being read from the register addressed by the initialized command byte.

The following are the write command and the read command message formats for the MPR084. First, the write:

Then the read:

Level Shifting

There is a twist, however: The Arduino Duemilanove provides 5 volt power to its ATmega328 processor. The MPR084 runs on 3.3 volts. Its communication pins are not designed to handle over-voltages, so the signals from the Atmega328 need to be converted to 3.3 volts. For the SDA line, the inverse is true as well: The MPR084, when it speaks to the Arduino, needs to send its signals at a 5V level. This is done with a level shifter, and the principle source I used to design mine is here: http://www.kip.uni-heidelberg.de/lhcb/Publications/external/AN97055.pdf (if that link disappears, search for Application Note AN97055 from Phillips Semiconductor). The MOSFET transistor I used was the TN2106N3-G, but I'm sure there are many others that will work.

The SCL line is the clock that synchronizes the I2C components. The master- which is the ATmega328 in this case- provides the clock signal and the slave listens. My first mistake was assuming that the conversation is simply one-way. However a review of Wikipedia's I2C article shows that the slave can hold the SCL line low if it wants to slow the master down; this is called "clock stretching". So the SCL conversation is not one-way. Thus, I will use the same MOSFET level shifter for SCL that I use for SDA. My level shifter looks like this:

The Sketch

Basic Format

According to various examples on the web (for example, see this example: http://tronixstuff.wordpress.com/2010/10/20/tutorial-arduino-and-the-i2c-bus/ [search on the page for "reading data from"]) then, a basic sketch to read I2C data should look something like this:

Wire.beginTransmission(DEVICE_ADDRESS);
Wire.send(some_data); // send some data
Wire.endTransmission();
Wire.requestFrom(DEVICE_ADDRESS, number_of_bytes);
data = Wire.receive();

...or something quite similar. For more examples, see the list at http://playground.arduino.cc/Main/InterfacingWithHardware#i2c .

Putting it together

...However, after reviewing the tutorials and the datasheet, it appears to me that what I need to do is:

  • Write to the MPR084 (from p. 7 of the MPR084 datasheet, "a read is initiated by first configuring the MPR084’s command byte by performing a write")
  • Read a byte from the MPR084

As to what I write, that I don't know. Essentially I want to send a no-op because my only interest really is in the read at this point. So, on to the documentation to figure out how a no-op might look. Or maybe just an innocuous write command, apropos of nothing. ...Back in a few hours...

...Annnnd... I'm back! Seemed like a blink of an eye, didn't it? Anyway, it appears to me that what I need to do is:

  • Send a "command byte", which would be the address of the register that I wanted to read, then my "n" data bytes. In my case, n = 0, so I will send a stop bit after sending the register address ("command byte").
  • Immediately following, I can attempt my read. (Note: It will be interesting to see how this turns out, since the astute observer will note in the published message format, that during a read, the R/~W bit is set to 1 [for the read] and the command byte [which should be the register address] is sent. But if a write must precede a read, what is the purpose of the command byte in the read message format? Either I am confused, or the writers of the data sheet are.)

Theoretically then, my sketch will look like this. I am going to attempt to read 16 bytes of the Sensor Information Register, Address 0x14 (it should be ASCII data).

Here's the sketch:

 
#include <Wire.h>
// #define TWI_FREQ 1L // For future testing
// For reference. These are the ANALOG pins:
const int i2cSCLPin =  5; // the number of the I2C interface, SCL pin
const int i2cSDAPin = 4;  // SDA pin

#define MPR084_ADDR 0x5C  // assumes the MPR084 AD0 pin is low
#define regINFO 0x14      // Sensor Information Register

char c;
void setup() {
  Serial.begin(19200); // For reporting
  Serial.print("MPR084 test.\n");
  Wire.begin();
  Wire.beginTransmission(MPR084_ADDR);
  Wire.send(regINFO); // This would be my command byte
  Wire.endTransmission();
  Wire.beginTransmission(MPR084_ADDR);  // Now I send my read command
  Wire.requestFrom(regINFO, 16); // Not sure how much data there is, let's try this.
  while (Wire.available()) {
    c = Wire.receive();
    if (c != 0) {
      Serial.print(c);
    }
  }
}

void loop() {
  // Nothing to see here, move along...
}

The trick- and the point of this whole article- is how does this sketch translate to the message format? What electrical signals actually traverse the SDA and SCL lines of the Arduino and the MPR084?

Inside the ATmega328

In order to fully understand the Wire library code, we need to understand what it needs to do to the MCU, to get it set up.

Review of the datasheet (http://www.atmel.com/dyn/resources/prod_documents/8271S.pdf) for the processor shows how the Two Wire system works. To note are:

  • The MCU includes a hardware TWI module which handles the I2C communication. ...Interestingly, this means that the communication is not handled exclusively via the software library, as you might think! The library interfaces with a hardware component. See p. 222 of the datasheet.
  • "The AVR TWI is byte-oriented and interrupt based...." (section 21.6 on p. 224). This is interesting; it means that the way you handle the communication is
    • You set up registers.
    • You let the TWI module do its thing.
    • You go do something else; your 16MHz MCU is not tied up controlling a 100KHz serial communication.
    • The TWI module sends an interrupt when it finishes, to notify the processor of the change in conditions (including successful operations, and/or errors).

The Answer

So now we know: Why do you not want to roll your own I2C code? Because not only is it done for you with the Two Wire library, but it's done better because it's done asynchronously. Don't tie up your 16 MIP ATmega328! Let the TWI hardware handle the communication asynchronously via the Two Wire library. How slick is that? Very slick. (BTW, if you want to see some roll-your-own code, Google is your friend, a nice example is here: http://www.ermicro.com/blog/?p=744)

Registers

Here are some of the registers involved with the Two Wire Interface (TWI) hardware module:

RegisterNameFunction
TWCRTwo Wire Control RegisterControls the actions of the TWI module
TWSRTwo Wire Status RegisterReports the status of the TWI actions
TWDRTwo Wire Data/Address RegisterContains the data you want to transmit or have received
TWBRTwo Wire Bit Rate RegisterControls the frequency of the clock (SCL)

Deep Dive: Use The Source, Luke

To dig into this further, I'm going to need to go to the source. That's right, the very code that makes the Wire library be all... wire-y. I've got to dig in and see what goes on.

I run the Arduino development platform on a MacBook Pro. In perusing the Arduino installation for information, I stumbled into this handy directory: /Applications/Arduino.app/Contents/Resources/Java/libraries/Wire. Your platform will have a very similar path, starting where you installed it (on Windows machines, I'll guess the "Program Files" folder.) And what should I find in there, but the following files and directories:

 
Wire.cpp        examples/       keywords.txt
Wire.h          gccdump.s       utility/

And what should I find in the utility directory but the following files:

 
twi.c   twi.h

...note that files ending in ".cpp" generally contain C++ programming language source code. Files ending in .s are assembly language code, files with a .h are header files, and files ending in .c are C programs. As we're dealing with the Wire library here, I'm guessing that the Wire.cpp file is the source code for it- and, perusing it, I discover that it is!

So this is great. Wire.cpp is the first place to go to show us what is going on.

Using my favorite text editor, I open the file and search for "begin". This is what I find:

 
// Public Methods //////////////////////////////////////////////////////////////

void TwoWire::begin(void)
{
  rxBufferIndex = 0;
  rxBufferLength = 0;

  txBufferIndex = 0;
  txBufferLength = 0;

  twi_init();
}

...what the heck is that? Well, without getting too far into the nitty-gritty of C++ and object-oriented programming (OOP), this is a "method". If you don't know anything about OOP, just replace "method" with the word "function" or "subroutine" and you basically have the right idea. This: void TwoWire::begin(void) means, "I am defining a method (aka, 'function') that returns nothing (aka, 'void'), is part of the TwoWire class, has the name of 'begin', and takes nothing (aka, 'void') for its arguments." That sounds dandy, but what does that have to do with our sketch? Remember that in our sketch, the first statement we execute that has to do with the Two Wire library is Wire.begin(). But we've found void TwoWire::begin(void). What's going on?

The answer lies at the end of the Wire.cpp file. There, we find a statement that goes like this:

 
TwoWire Wire = TwoWire();

This means, "create a TwoWire object and name it Wire that's of the TwoWire class type." Again, if you're not an OOP programmer, don't worry about the fact that there are two "TwoWire"s in the statement. Suffice it to say that we have an object called Wire. Now if we want to use any of its methods, we simply call it like this:

 
object.method

Where our object is called Wire and the method we want to run (with no arguments) is called begin, we simply do:

 
Wire.begin()

There. Now you know: when you program using the Wire library, you are doing C++ programming.

So, the Wire.begin() method call simply sets 4 variables to 0, then calls twi_init().

Things now get a little more complicated. twi_init() is not of the format object.method. What is it, and where is its code? Judicious use of UNIX command line tools (essentially, "grep") shows that twi_init() can be found in the utility/twi.c file. Thus the C++ method calls a C programming language function, and this is where things get delicious.

The code is actually fairly hairy, requiring study of the Atmel ATmega328 datasheet and a lot of digging through the source code and header files (those that end in .h), which contain a lot of definitions of the components in the above code. Since we're using an Arduino Duemilanove with its ATmega328, the twi_init code can be shortened to this:

 
void twi_init(void)
{
   twi_state = TWI_READY;
   // activate internal pull-ups for twi
   sbi(PORTC, 4);
   sbi(PORTC, 5);
   // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

Wire.begin()

So let's put it together. We have:

 
Wire.begin() calls
twi_init() which calls
some sbi and cbi functions, and some stuff that looks like registers.

Armed with the source code and the ATmega328 data sheet, then, we can decipher twi_init() like so:

sbi is defined as #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) (in the utility/twi.c file) so that sbi(PORTC, 4) becomes:

 
[@_SFR_BYTE(PORTC) |= _BV(4))@]

Here are the other definitions in this statement: (these definitions are created in the header ".h" files, and look something like this: #define PORTC _SFR_IO8(0x08))

stringdefined valuenotes
_BV(bit) (1 << (bit))  
************************
PORTC_SFR_IO8(0x08) 
_SFR_IO8(io_addr)((io_addr) + __SFR_OFFSET) 
__SFR_OFFSET0x20as long as __AVR_ARCH < 100
__AVR_ARCH5For the ATmega328. See the file using_tools.html in the hardware/tools/avr/doc
************************
_SFR_BYTE(sfr)_MMIO_BYTE(_SFR_ADDR(sfr)) 
_MMIO_BYTE(mem_addr)(*(volatile uint8_t *)(mem_addr))notes
_SFR_ADDR(sfr)_SFR_MEM_ADDR(sfr)notes
_SFR_MEM_ADDR(sfr)((uint16_t) &(sfr))notes
stringvaluenotes

So, finally, tracking all these definitions and definitions-of-definitions to their source, we have:

sbi(PORTC,4) is

 
	_SFR_BYTE(PORTC) |= _BV(4))
	_SFR_BYTE(PORTC) |= (1 << (4))
	_SFR_BYTE(_SFR_IO8(0x08)) |= (1 << (4))
	_SFR_BYTE((0x08) + __SFR_OFFSET) |= (1 << (4))
	_SFR_BYTE((0x08) + 0x20) |= (1 << (4))
	_MMIO_BYTE(_SFR_ADDR((0x08) + 0x20)) |= (1 << (4))
	_MMIO_BYTE(_SFR_MEM_ADDR((0x08) + 0x20)) |= (1 << (4))
	_MMIO_BYTE( ((uint16_t) &((0x08) + 0x20)) ) |= (1 << (4))
	(*(volatile uint8_t *)((uint16_t) &(0x08 + 0x20))) |= (1 << 4)

Ugh. That last line: what does it mean? Strangely, this line is easier to read starting from the right and moving to the left. First, we have the (1 << 4). This means we take an integer "1" and shift it left 4 bits. An integer "1" is a two-byte value (see http://www.arduino.cc/en/Reference/Int). So we end up with 0000000000010000 in binary. The "|=" operator says that you need to perform a boolean "or" operation on this with whatever is on the left, and stick it into whatever is on the left.

The thing on the left, besides being a qualitative mess, is quantitatively: The numbers 0x08 ("8", in hexadecimal) and 0x20 (hexadecimal as well). What is 0x20 in decimal, you may ask? Don't ask. You're in computer-land now, the land of bits and bytes, and decimal is of no use here. Suffice it to say two things: 1.) You won't need to convert this number to decimal because you'll never use the decimal value, and 2.) 0x08 + 0x20 is 0x28, which makes life easy.

So we take our result 0x28 and perform "&" on it, which means that it is a reference (an address), because it is part of the lvalue (the stuff to the left of the |= assignment; see http://www-numi.fnal.gov/offline_software/srt_public_context/WebDocs/Companion/glossary/reference.html). The uint16_t is a "cast" that ensures that the result is considered a 16-bit unsigned integer type. That is then cast to a "volatile" unsigned 8 bit integer. Thus two things have taken place: First, we have lopped off the leftmost 8 bits of this 16bit value, which is fine because the PORTC register is 8 bits wide. Secondly, we have declared that the contents of the register can change at any time, so if the compiler attempts to optimize this section of code it will not assume that this value, once set, is what it was when it was set (which is important if you are trying to test the register's value- you'll always want to read it, instead of making any assumptions). So we're taking the contents of 0x28 and setting the 5th bit from the right to "1". If it's already 1, it stays a 1. So why is 0x28 important? What is the significance?

It happens that I/O registers are addressed as memory (p. 20 of the ATmega328 data sheet). And, "When addressing I/O Registers as data space using LD and ST instructions, 0x20 must be added to these addresses." ...again from p. 20. So, referring to the Register Summary on p. 532, we see that 0x08... or, 0x28 when addressed using the LD and ST (load and store) instructions, is... wait for it... PORTC. So we're setting the 5th bit from the right of PORTC, which is called bit 4 because the first bit is bit 0. This command, then, sets PORTC4. Now we know that we have to give up two Arduino pins for the TwoWire library, and the description in section 13 starting on page 76 describes the ports. On page 87, we see the description for Port C, Bit 4: "SDA, 2-wire Serial Interface Data: When the TWEN bit in TWCR is set (one) to enable the 2-wire Serial Interface, pin PC4 is disconnected from the port and becomes the Serial Data I/O pin for the 2-wire Serial Interface." Ah-hah! Finally! After all those definitions, we arrive at one fact: PORTC Bit 4 enables the SDA line for the TwoWire interface. And, as it happens, PORTC Bit 4 is pin 27 of the ATmega328 28-pin DIP package, as found in the Arduinoa Duemilanove. Pin 27 is connected to the Arduino I/O pin 5 on jumper J2, which is labelled Analog In 4 on my Duemilanove.

sbi(PORTC, 4);

In summary: According to the top Wire library reference page, "On most Arduino boards, SDA (data line) is on analog input pin 4". This command sets bit 4 in the PORTC register such that "pin PC4 is disconnected from the port and becomes the Serial Data I/O pin for the 2-wire Serial Interface." (p. 87 in the ATmega328 datasheet).

Summary of sbi(PORTC, 4);

Holy smokes, what an involved and convoluted path we have taken to get here! Was it worth it? Perhaps not, at first blush. But since you're working on a computer I'll guess you are comfortable with abstractions- the abstractions that, say, involve taking a chunk of silicon, metal, plastic, and electricity which enable you to read these words on the World Wide Web with nary a thought to the various gates and signals and protocols and software layers (firmware, languages, other languages on top of those languages) that were required to allow you to surf the web. You, dear reader, are reading this quite far from the bits and bytes and signals and gates that are now enabling your Internet experience, and in much the same way, to the programmer writing

 
sbi(PORTC, 4)

beats the heck out of writing

 
(*(volatile uint8_t *)((uint16_t) &(0x08 + 0x20))) |= (1 << 4)

because all the abstractions that have enabled him to write the former make it a whole lot simpler and more readable in the long run. The abstractions in this case are all the lines that transform this (*(volatile uint8_t *)((uint16_t) &(0x08 + 0x20))) |= (1 << 4) into this sbi(PORTC,4).

twi_init();, Following Lines

At the risk of repeating myself, I'll repeat the twi_init code here:

 
void twi_init(void)
{
   twi_state = TWI_READY;
   // activate internal pull-ups for twi
   sbi(PORTC, 4);
   sbi(PORTC, 5);
   // initialize twi prescaler and bit rate
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
  // enable twi module, acks, and twi interrupt
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
}

The first line of this function is a simple variable assignment; no hocus-pocus there. We have just done a deep dive into the second line, sbi(PORTC, 4);.

Line 3

We can see that the third line is essentially the same, only this time we set bit 5 of PORTC. According to the ATmega328 datasheet), "SCL, 2-wire Serial Interface Clock: When the TWEN bit in TWCR is set (one) to enable the 2- wire Serial Interface, pin PC5 is disconnected from the port and becomes the Serial Clock I/O pin for the 2-wire Serial Interface." So we have configured the second I/O pin to be SCL; we now have configured the MCU's pins to enable their function as I/O for its Two Wire hardware. We will cover the first part of the condition, "When the TWEN bit in TWCR is set..." in a little while. Assuming the TWEN bit is set, our I/O pins should be good to go.

Line 5, cbi(TWSR, TWPS0)

We'll explain this line cbi(TWSR, TWPS0); without going into all the gross detail of the sbi line. The definitions of that line are as follows:

(see (path on your platform to)/hardware/tools/avr/avr/include/avr/iom328p.h)

stringdefined valuenotes
cbi(sfr, bit)(_SFR_BYTE(sfr) &= ~_BV(bit)) 
TWSR_SFR_MEM8(0xB9)see file hardware/tools/avr/avr/include/avr/iom328p.h
****************note that _SFR_ASM_COMPAT is 0
TWPS00 
_BV(bit) (1 << (bit))  
************************
_SFR_MEM8(mem_addr)_MMIO_BYTE(mem_addr)see file sfr_defs.h in the above directory
_MMIO_BYTE(mem_addr)(*(volatile uint8_t *)(mem_addr))notes
xxx
************************

Tracking all the definitions and definitions-of-definitions to their source, in the same way that sbi(PORTC,4) was done, we have:

cbi(TWSR, TWPS0) is

 
	#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
	_SFR_BYTE(TWSR) &= ~_BV(TWPS0))
	_SFR_BYTE(0xB9) &= ~_BV(0))
	_SFR_BYTE(0xB9) &= ~(1 << 0)
	_SFR_MEM8(0xB9) &= ~(1 << 0)
	_MMIO_BYTE(0xB9) &= ~(1 << 0)
	(*(volatile uint8_t *)(0xB9)) &= ~(1 << 0)
	(*(volatile uint8_t *)(0xB9)) &= ~(b00000001 << 0)
	(*(volatile uint8_t *)(0xB9)) &= ~(b00000001)
	(*(volatile uint8_t *)(0xB9)) &= b11111110)

0xB9 is the address of the TWSR (Two Wire Status Register). What we are doing here is doing a Boolean AND with the binary number b11111110, which sets the TWPS0 (0'th) bit of the TWSR to 0 (see p. 244 of the ATmega328 datasheet).

Line 6, cbi(TWSR, TWPS1)

Likewise the following command cbi(TWSR, TWPS1) will ensure that the value of the TWPS1 (1st) bit of the TWSR is 0.

Line 7, TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;

The two lines above set the value of the Two Wire Bit Rate Generator "prescaler" to 1. The next line is

 
  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;

The CPU_FREQ and TWI_FREQ values come from utility/twi.h, and are:

 
CPU_FREQ 16000000L
TWI_FREQ 100000L

Appropriately-comma'ed for readability, these are: 16,000,000 and 100,000 respectively. Thus the equation resolves to 72, and the TWBR is set to that value. Here is how line 7 resolves:

 
TWBR = ((16,000,000 / 100,000) - 16) / 2
TWBR = (160 - 16) / 2
TWBR = 144 / 2
TWBR = 72
_SFR_MEM8(0xB8) = 72
_MMIO_BYTE(0xB8) = 72
(*(volatile uint8_t *)(0xB8))=72

So our clock (SCL) frequency will be SCL = CPU clock / (16 + 2(TWBR) ⋅ (PrescalerValue)). Our clock is 16 MHz, TWBR is 72, Prescaler Value is 1, so we have:

 
SCL = 16,000,000 / 16 + 144 * 1 
SCL = 16,000,000 / 160
SCL = 100,000

This is a rather pedestrian I2C frequency of 100 Khz, which is what the TWI_FREQ was defined as. If you want to change the clock frequency of the I2C bus, you can create your own cbi command after Wire.begin() and adjust the Prescaler Value according to Table 21-7 on p. 244 of the ATmega328 datasheet. Or, you can redefine the TWI_FREQ value, and run the TWBR line again in your own sketch. For example, TWBR = ((CPU_FREQ / 200000 ) - 16) / 2; to set the I2C clock frequency to 200 kHz (the equation evaluates to 32, so you could also simply put this line in your sketch immediately following Wire.begin(): TWBR=32; /* == 200kHz SCL frequency */).

Note that TWBR is an 8-bit register, so its maximum value could be 255. With a PrescalerValue of a maximum of 64, this means our SCL frequency can be at a minimum 489 Hz (16,000,000 / (16 + 2 * 255 * 64).

Line 9, TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);

This code sets the bits in the TWCR, the Two Wire Control Register, mentioned in the TWI Hardware Registers section. And as mentioned in Line 3, above, "According to the ATmega328 datasheet), 'SCL, 2-wire Serial Interface Clock: When the TWEN bit in TWCR is set (one) to enable the 2- wire Serial Interface, pin PC5 is disconnected from the port and becomes the Serial Clock I/O pin for the 2-wire Serial Interface.'". So here we go: You can guess that by the _BV(TWEN) statement, we are enabling the TWEN bit. At this point the Two Wire hardware should be enabled, the I/O pins are connected to it, and we should be ready for I2C communication. Let's take a quick dive into this statement, like we did with the previous ones.

TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA); is:

 
	#define TWCR _SFR_MEM8(0xBC)
	#define TWEN 2
	#define TWIE 0
	#define TWEA 6
	#define _BV(bit) (1 << (bit))
	_SFR_MEM8(0xBC) = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);
	_MMIO_BYTE(0xBC) = _BV(2) | _BV(0) | _BV(6);
	_MMIO_BYTE(0xBC) = (1 << 2) | (1 << 0) | (1 << 6);
	(*(volatile uint8_t *)(0xBC)) = (b00000001 << 2) | (b00000001 << 0) | (b0000001 << 6);
	(*(volatile uint8_t *)(0xBC)) = (b00000100     ) | (b00000001)      | (b1000000 << 6);
	(*(volatile uint8_t *)(0xBC)) = b10000101;

Note that "|" is a boolean OR operation. So we are setting the TWEN, TWIE, and TWEA bits in the TWCR register. Refer again to our favorite ATmega328 datasheet, p. 243.

The TWEN flag, BIT 2, "enables TWI operation and activates the TWI interface. When TWEN is written to one, the TWI takes control over the I/O pins connected to the SCL and SDA pins..." Thus when TWCR is configured, the work done earlier to set the SDA and SCL lines are activated (see e.g. sbi(PORTC, 4);).

The TWIE bit, bit 0, enables the TWINT flag. Thus, the TWI hardware will send interrupts to the MCU as long as the I-bit in the SREG is set. The SREG is the AVR Status Register, and the I-bit is bit 7, the "Global Interrupt Enable". If this bit is cleared, no interrupts are serviced. If set, interrupts are serviced for those particular interrupts that are enabled (as here, where we enable the TWI interrupt). The SREG bit is cleared by hardware after an interrupt has occurred, and is set by the RETI (return from interrupt) instruction at the end of an interrupt service routine.

The TWEA bit, bit 6, controls the generation of the acknowledge pulse. The acknowledge pulse is sent from the slave to the master after receiving its address, or sent by the master to the slave after receiving data. Turning this bit off essentially disconnects the ATmega328 from the I2C bus.

The End of Wire.begin()

And there we have it. Line 9 of twi_init() is the last line of that function; it returns, and Wire.begin() returns, and we are on to the next line in the sketch. To some it up, then, Wire.begin() has accomplished the following:

 
- Set the following variables: in Wire.begin():
  rxBufferIndex = 0;
  rxBufferLength = 0;
  txBufferIndex = 0;
  txBufferLength = 0;
- Perform the following tasks in twi_init():
  + Set the variable twi_state = TWI_READY;
  + activate the two ports for the TWI, analog input 4 for SDA and 5 for SCL.
   sbi(PORTC, 4);
   sbi(PORTC, 5);
- Initialize twi prescaler to 1.
  cbi(TWSR, TWPS0);
  cbi(TWSR, TWPS1);
- Set the TWI bit rate to 100kHz.
  TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
- Enable the twi module, the twi interrupt, and TWI acknowledgements.
  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);

Notice that we've done a lot of work, but not a single bit has traversed the TWI pins, not yet. Tune in to the next section to see how that takes place!

Wire.beginTransmission()

Our next line in our Sketch is Wire.beginTransmission(MPR084_ADDR). Let's break it down:

Wire.beginTransmission() is found, of course, in the Wire.cpp file. It is another method in the Wire object. Refer back to the beginning of our deep dive for more information. This method's source goes like this:

 
beginTransmission((uint8_t)address);

Hmm. Not very helpful. It simply casts the address to an unsigned 8-bit integer type, then calls the method beginTransmission() with an unsigned 8-bit integer argument. Fair enough, now what does THAT do? Here it is:

 
void TwoWire::beginTransmission(uint8_t address)
{
  // indicate that we are transmitting
  transmitting = 1;
  // set address of targeted slave
  txAddress = address;
  // reset tx buffer iterator vars
  txBufferIndex = 0;
  txBufferLength = 0;
}

Quite simple really. beginTransmission(), then, does nothing more than set a number of variables in the Wire object, including assigning our MPR084 address to the txAddress variable.

So ends our examination of the Wire.beginTransmission() call. Very little to see here; let's move on.

Wire.send(regINFO)

This method is defined as follows:

 
void TwoWire::send(uint8_t data)
{
  if(transmitting){
  // in master transmitter mode
    // don't bother if buffer is full
    if(txBufferLength >= BUFFER_LENGTH){
      return;
    }
    // put byte in tx buffer
    txBuffer[txBufferIndex] = data;
    ++txBufferIndex;
    // update amount in buffer   
    txBufferLength = txBufferIndex;
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(&data, 1);
  }
}

Note that we set the "transmitting" variable to 1, in the beginTransmission method. Also, BUFFER_LENGTH is defined in the Wire.h file as follows: #define BUFFER_LENGTH 32. So we can see that we can send a maximum of 32 bytes at once. txBuffer is defined as uint8_t TwoWire::txBuffer[BUFFER_LENGTH];.

What happens if you try to send more data than 32 bytes? You are simply ignored. This is not good programming practice at all, as this means there is no error checking. However, it could perhaps be forgiven since we are on a small computer, after all. Reducing the error checking speeds things up and uses less memory. Still, I'm not comfortable with this code.

Regardless, this code is a simple fill of the array txBuffer. It seems to me that calling it send() is a misnomer, but there you have it. At this point, no data has yet been sent over the TWI; we've merely done a bit of housekeeping. Essential housekeeping, mind you.

Specific to our sketch, we have placed the regINFO data (0x14) into the txBuffer, incremented the txBufferIndex to 1, and set the txBufferLength to be 1 as well.

Wire.endTransmission()

The endTransmission() method is interesting, in that it finally seems like something happens on the TWI busses. Here is the method:

 
uint8_t TwoWire::endTransmission(void)
{
  // transmit buffer (blocking)
  int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1);
  // reset tx buffer iterator vars
  txBufferIndex = 0;
  txBufferLength = 0;
  // indicate that we are done transmitting
  transmitting = 0;
  return ret;
}

Not much going on there: A call to twi_writeTo(), setting some variables, and returning a return code. Much of the housekeeping work that we've done before comes into play here as arguments: The address of our I2C slave in the txAddress variable, our data in the txBuffer, and the length of the buffer in txBufferLength. So let's see what twi_writeTo does. Here's the function, with line numbers:

 
   uint8_t twi_writeTo(uint8_t address, uint8_t* data, uint8_t length, uint8_t wait)
   {
01.  uint8_t i;
02.
03.  // ensure data will fit into buffer
04.  if(TWI_BUFFER_LENGTH < length){
05.    return 1;
06.  }
07.
08.  // wait until twi is ready, become master transmitter
09.  while(TWI_READY != twi_state){
10.    continue;
11.  }
12.  twi_state = TWI_MTX;
13.  // reset error state (0xFF.. no error occured)
14.  twi_error = 0xFF;
15.
16.  // initialize buffer iteration vars
17.  twi_masterBufferIndex = 0;
18.  twi_masterBufferLength = length;
19.
20.  // copy data to twi buffer
21.  for(i = 0; i < length; ++i){
22.    twi_masterBuffer[i] = data[i];
23.  }
24.
25.  // build sla+w, slave device address + w bit
26.  twi_slarw = TW_WRITE;
27.  twi_slarw |= address << 1;
28.
29.  // send start condition
30.  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);
31.
32.  // wait for write operation to complete
33.  while(wait && (TWI_MTX == twi_state)){
34.    continue;
35.  }
36.
37.  if (twi_error == 0xFF)
38.    return 0;   // success
39.  else if (twi_error == TW_MT_SLA_NACK)
40.    return 2;   // error: address send, nack received
41.  else if (twi_error == TW_MT_DATA_NACK)
42.    return 3;   // error: data send, nack received
43.  else
44.    return 4;   // other twi error
   }

Whoa. Heavy. Ok, let's dig:

twi_writeTo(), lines 1-7

A little bit of housekeeping. We declare a variable uint8_t i;, we do some error checking, in to return if the length of the data is too long:

 
if(TWI_BUFFER_LENGTH < length){
    return 1;
}

and that's it.

twi_writeTo(), lines 9-11

Here's the code: while(TWI_READY != twi_state){ continue; } In twi_init(), we set twi_state = TWI_READY;. twi_state is a variable whose scope is across all the functions in the twi.c file, declared at the beginning of the file: static volatile uint8_t twi_state;. So here we can see that, because the variable was set in an earlier function, it does equal TWI_READY.

twi_writeTo(), lines 12-27

More setup and such. We set twi_state = TWI_MTX;, where TWI_MTX is a number used internally to describe the current operational mode of the TWI circuitry. I believe TWI_MTX stands for "TWI master transmit."

The following lines should be self-explanatory by this point:

 
  // reset error state (0xFF.. no error occured)
  twi_error = 0xFF;

  // initialize buffer iteration vars
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length;

  // copy data to twi buffer
  for(i = 0; i < length; ++i){
    twi_masterBuffer[i] = data[i];
  }

...In them, we see that we have a library variable (declared at the beginning of twi.c: static volatile uint8_t twi_error;, so its scope covers all the functions in that file). This variable reflects the error state of the TWI hardware. We also set our buffer index to 0 and we have another variable in the function that gets assigned the value of the length of our data buffer. Finally, we fill the twi_masterBuffer (defined at the beginning of the twi.c file as static uint8_t twi_masterBuffer[TWI_BUFFER_LENGTH];) with our data.

Next we have the following variable, declared at the beginning of twi.c: static uint8_t twi_slarw;. In the I2C world "SLA+R/W" is defined as a slave address plus either a write bit or a read bit. Also in the I2C world, addresses are 7 bits long. So what we are doing is this code

 
  // build sla+w, slave device address + w bit
  twi_slarw = TW_WRITE;
  twi_slarw |= address << 1;

is:

  • Assign the write bit to twi_slarw.
  • Take the address of our device and shift it left one bit.
  • Using a boolean OR operation, assign the left-shifted address to twi_slarw.

Thus we'll have an 8 bit data stream that begins with the 7 bit address.

twi_writeTo(), line 30

 
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

This line should look somewhat familiar. In Wire.begin() we did the following: - Enable the twi module, the twi interrupt, and TWI acknowledgements.

  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);

So we're redoing some of the work here. Thus the statement in Wire.begin() was unnecessary, and if we're concerned about every cycle on our Arduino, somewhat wasteful. But I don't think we're that concerned, because the I2C frequency is only 100kHz.

We have some additional bits that we're setting: TWINT and TWSTA. Here's what they do:

Setting the TWINT bit clears the TWINT flag. That bears repeating: Setting the bit to a one, clears the TWINT flag. Clearing the flag starts the operation of the TWI. ...HEY! Finally! There it is! ...Clearing the flag starts the operation of the TWI. So now you know: the TWI hardware is started simply by setting a flag in the TWCR register (provided, of course, the other conditions are set such as enabling the TWI hardware with the TWEN bit and setting up the SDA and SCL pins).

Setting the TWSTA bit to one tells the TWI that it should become the master on the TWI bus. So the first thing it will do, provided the TWI bus is free, is generate a START condition. What is a START condition? The clock line SCL remains high, while the master changes the SDA line to low. This is unique because during normal data transfer, the SDA data line changes state only when the SCL clock line is low. When the data changes to low while the SCL is high, this is the signal to all I2C devices on the bus that a master is about to initiate a communication.

twi_writeTo(), lines 33-35

These lines:

 
  while(wait && (TWI_MTX == twi_state)){
    continue;
  }

are resolved as follows: The call to twi_writeTo() from Wire's endTransmission() method looks like this:

 
int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1);

Thus,
wait=1].  And twi_state was set to TWI_MTX earlier, and TWI_MTX is defined as 2 in the twi.h file.  So the boolean of our while() statement above looks like:
 [=
  while(1 && (2 == 2)){
=]
...and, since [@(2 == 2)
returns a 1 in C and C++, and the boolean 1 && 1 is 1, and since 1 is equivalent to a boolean "true", what we have here is an infinite loop. There's nothing to show that those values will change. So we're stuck.

Getting unstuck, or SIGNAL(TWI_vect)

The only way for our processor to stop narcissistically talking to itself with its endless test of two equivalent values is to interrupt it. But how will it get interrupted? And what will it do once interrupted?

Question Two: What we'll do once interrupted

To answer the second question, let's take a bit of a journey. On line 332 of twi.c we see the following function: SIGNAL(TWI_vect). Here's how it dereferences:

 
#define TWI_vect _VECTOR(24)       /* defined in iom328p.h */
#define _VECTOR(N) __vector_ ## N  /* sfr_defs.h */
#define _VECTOR(N) __vector_24     /* ## is the pre-processor "token pasting operator" */
                                   /* the following comes from interrupt.h */
#define SIGNAL(vector) void vector (void) __attribute__ ((signal, __INTR_ATTRS)); void vector (void)
#define SIGNAL(__vector_24) void vector (void) __attribute__ ((signal, __INTR_ATTRS)); void vector (void)
void __vector_24 (void) __attribute__ ((signal, __INTR_ATTRS)); void __vector_24 (void)
#  define __INTR_ATTRS used, externally_visible
void __vector_24 (void) __attribute__ ((signal, used, externally_visible)); void __vector_24 (void)

The result is interesting. It says that we have a function __vector_24 which returns nothing and takes nothing as an argument. However, we are telling the compiler that we are smarter than it with the "__attribute__" declaration. We are saying the following:

  • That we have an AVR signal (ie, this is an interrupt for the AVR microprocessor)
  • That we are going to use this function, even if we don't call it directly in our program (the compiler may not include the code if we never appear to use it).
  • We are saying that this function needs to be visible in other "compilation units". Without getting too gory, I believe this essentially means we want a lot of visibility. We don't want to optimize too aggressively, which might hide this function from being visible during compilation of another file (for example).

Ok, now we know that our __vector_24 function has some attributes:

 
void __vector_24 (void) __attribute__ ((signal, used, externally_visible));

Now what? Setting aside the attribute declaration for a moment, we plug the rest of the line into line 332 of utility/twi.c. This is merely: void __vector_24 (void). The GCC preprocessor changes what was once:

 
SIGNAL(TWI_vect)
{
  switch(TW_STATUS){
    // All Master
    case TW_START:     // sent

(...etc...)

into

 
void __vector_24 (void)
{
  switch(TW_STATUS){
    // All Master
    case TW_START:     // sent

(...etc...)

__vector_24, oh __vector_24. How you have hurt my brain. Ok, this gets even gorier, but here's my understanding:

__vector_24 is defined in no C or C++ source or header file. Rather, it's part of the AVR-libc library. In there is some assembly language code with a jump table. The file is crt1/gcrt1.S, if you're interested. Google it. You can download the source for the avr-libc library and see it. Anyway, the jump table basically says that calling __vector_24 is equivalent to doing the following assembly language command:

 
 jmp __vector_24

Meaning, "jump to the memory location specified by __vector_24 and run the code there." Now somehow, I believe that the assembly language jump table is closely related to the Interrupt Vectors of the ATmega chip, as listed in the Interrupt Vectors table on p. 66 of the ATmega328 datasheet. As a matter of fact, in there we see this entry:

 
VectorNo.  Program Address  Source   Interrupt Definition
  25         0x0030         TWI      2-wire Serial Interface

Now I note that this is vector number 25, with a program address of 48 (decimal), and I note that the iom328p.h file has all the Interrupt Vectors listed in order exactly the same as the datasheet. I also note that the integer in my __vector_24 function name is half of 48- the program address number- and only 1 off from the vector number. So what I think is happening is that the assembly language jump table is getting laid into memory exactly where it should be such that the 16-bit memory address of my __vector_24 function winds up in location 0x0030. So that when a TWI interrupt takes place, the machine will grab the contents of address 0x0030 and that will point to the beginning of my function __vector_24, which is all the code that has been written in utility/twi.c starting on line 322.

Now, let's talk about Question 1:

Question One: How we're interrupted

The answer to how we're interrupted is simple. Again in Wire.begin() we enabled the TWI hardware interrupts:

- Enable the twi module, the twi interrupt, and TWI acknowledgements.

  TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA);

And note, from the ATmega328 datasheet: "Interrupts are issued after all bus events, like reception of a byte or transmission of a START condition..." (p. 224). So, we have

  • Set the TWIE bit in the TWCR, and
  • Told the TWI hardware to send the START condition, by setting TWSTA in the TWCR register in twi_writeTo().

Therefore, assuming the Global Interrupt Enable bit is set in SREG (and it should be at this point), we will get interrupted after the START condition is sent.

Tying it together #interrupt+function

Whew. Which is all to say this:

  • We have set up an Interrupt Handler called SIGNAL(TWI_vect)
  • We start the TWI hardware.
  • Our while loop on lines 33-35 of twi_writeTo() busily waits for twi_state to change.
  • Once the TWI has started and is ready, it signals us with an interrupt.
  • That interrupt is handled by the function SIGNAL(TWI_vect) (our "interrupt handler").
  • It processes our I2C request with the TWI hardware.

Oh, and one other thing:

  • The Wire library is blocking. Meaning: once it starts the TWI write, it goes into a busy wait state. So your 16MHz processor is waiting on a 100kHz I2C conversation.
  • That sucks.
  • (I could be wrong, but at this point, in the future I expect to be using the twi code in twi.c and bypass the Wire object itself.)

SIGNAL(TWI_vect) in depth

Remember that up until this point we have sent the TWI START condition: The SDA line is low while the clock remains high. Then an interrupt is sent, and the CPU looks in its interrupt handler vector table to see where to go to handle the interrupt. For this interrupt, it goes to __vector_24, which is the SIGNAL routine in utility/twi.c.

The first line of SIGNAL is the following:

 
	switch(TW_STATUS)

So SIGNAL's behavior is dependent on the value of TW_STATUS, which is defined as (in hardware/tools/avr/avr-4/include/util/twi.h):

 
#define TW_STATUS   (TWSR & TW_STATUS_MASK)

#define TW_STATUS_MASK (_BV(TWS7)|_BV(TWS6)|_BV(TWS5)|_BV(TWS4)|_BV(TWS3))
#define TWS3 3
#define TWS4 4
#define TWS5 5
#define TWS6 6
#define TWS7 7

So TW_STATUS looks at the top 5 bits of the TWI Status Register; the boolean AND with the TW_STATUS_MASK guarantees that the bottom 3 bits of TW_STATUS are zero.

"When the TWINT Flag is asserted, the TWI has finished an operation and awaits application response. In this case, the TWI Status Register (TWSR) contains a value indicating the current state of the TWI bus..." Thus speaketh the ATmega328 datasheet.

SIGNAL's function is now clear: It is called by the TWI hardware via an interrupt. By that point, the TWSR will have been loaded with a value indicating the state of the TWI bus. The switch statement will compare the value of TWSR with a number of cases, eg "case TW_START:", and if there's a match it will perform that section of code. In short:

  • TWI does something, thus interrupts the processor.
  • SIGNAL(TWI_vect) runs, checks the TWSR.
  • Depending on the status of the TWSR, we perform the appropriate task.
  • Repeat as necessary.

The First Interrupt

Now all the TWI hardware has done is sent the START condition. According to the ATmega328 datasheet, "After a START condition has been transmitted, the TWINT Flag is set by hardware, and the status code in TWSR will be 0x08...." In the (path on your machine to)/hardware/tools/avr/avr-4/include/util/twi.h, we see:

 
#define TW_START                0x08

So we are now at this point in the SIGNAL(TWI_vect) code:

 
    case TW_START:     // sent start condition
    case TW_REP_START: // sent repeated start condition
      // copy device address and r/w bit to output register and ack
      TWDR = twi_slarw;
      twi_reply(1);
      break;

So we fill TWDR with our address and our r/w bit (see ). Recall that TWDR is

 
Two Wire Data/Address Register    Contains the data you want to transmit or have received

Then we run twi_reply(1). Its code looks like:

 
void twi_reply(uint8_t ack)
{
  // transmit master read ready signal, with or without ack
  if(ack){
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);

Notice that our argument to twi_reply is 1, therefore within the function, ack is true (== 1). Recall above, in the [twi_writeTo()|] section, that we set the TWCR similarly to our TWCR statement in twi_reply. So here is what each bit does:

  • TWEN: Enables the TWI hardware. Which it already is, but we certainly don't want to set it to 0!
  • TWIE: Enables the TWINT flag.
  • TWINT: Setting this bit clears the TWINT flag, and starts the operation of the TWI hardware.
  • TWEA: Controls generation of the TWEA pulse.

Now according to the ATmega328 datasheet,

In order to enter MT mode, SLA+W must be transmitted. This is done by writing SLA+W to TWDR. Thereafter the TWINT bit should be cleared (by writing it to one) to continue the transfer.

I think the writers of the datasheet were confused (which confuses me), because they say that "...the TWINT bit should be cleared (by writing it to one)...", whereas in other places of the datasheet they say that you write the TWINT bit to one to clear the TWINT flag. Anyway...

So, we have SLA+W (the slave address of the MPR084 device, plus the Write bit) in the TWDR, we set the TWCR, and the TWI hardware should continue the conversation.

In SIGNAL(TWI_vect), the next line after the twi_reply(1) is simply a break. So we exit out of SIGNAL and back to our busy wait state in twi_writeTo(). Stuck again? Perhaps, but now we know how TWI interrupts the processor. So, after the twi_reply() takes place, we must receive another interrupt.

The Second Interrupt

Recall that we have written the value "regINFO" (the MPR084 register that we're interested in) into the txBuffer.

This interrupt takes place after the Slave Address plus Write bit have been written to the I2C bus. So the TWSR should contain information that the slave receiver acknowledged our information, and in the SIGNAL function, the following case section of code should be used:

 
    // Master Transmitter
    case TW_MT_SLA_ACK:  // slave receiver acked address
    case TW_MT_DATA_ACK: // slave receiver acked data
      // if there is data to send, send it, otherwise stop 
      if(twi_masterBufferIndex < twi_masterBufferLength){
        // copy data to output register and ack
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
        twi_stop();
      }
      break;

Our twi_masterBufferLength is 1, and the Index is 0. So we perform:

 
        // copy data to output register and ack
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);

and, just like before with the TW_START, the twi_reply(1) will send the contents of TWDR out onto the I2C bus. This will be the register we are interested, regINFO, or 0x14.

The Third Interrupt

Again, we should get interrupted once the data from the twi_reply() is sent over the I2C bus. Now our twi_masterBuffer has been exhausted- we only had 1 byte of data in it- so the twi_masterBufferIndex is no longer less that the twi_masterBufferLength. So we do not set the TWDR, we do not reply over the I2C bus.

Instead, we stop, as per this code. Note that the twi_masterBufferIndex is not less than the twi_masterBufferLength, so the else condition is reached:

 
     if(twi_masterBufferIndex < twi_masterBufferLength){
        // copy data to output register and ack
        TWDR = twi_masterBuffer[twi_masterBufferIndex++];
        twi_reply(1);
      }else{
        twi_stop();
      }
      break;

The TWI STOP is like the TWI START condition, only the flags are different:

 
TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

And so ends this I2C communication session.

My Sketch is WRONG

This is an unfortunate state of affairs because I am now convinced my sketch is wrong. I don't want to stop at this point, I should now be ready to read data bytes from the MPR084. Shoot. I have more work to do on my sketch. Where to go from here? I don't know, it's late and I'm tired so I'll have to read up some more.

(...later...) In the MPR084 datasheet, on p. 7 it says,

a read is initiated by first configuring the MPR084’s command byte by performing a write

However, on p. 8 they show the communication with the MPR084. Notice the schematics in the mpr084 section, labelled "Figure 10" and "Figure 11". The R/~W bits are not the same. In those schematics, if a write was sent first for each operation- that is, for both Write and Read- the bit should remain the same. But it is not. Given this state of affairs, that

  1. After sending our write command we perform the STOP condition, and
  2. The figures do not conform to the text,

I first concluded that the text in the datasheet is wrong, and that I must necessarily send a read command from the beginning. However, on close analysis, the text is correct but the diagrams are off. What needs to happen is this:

  • You send a write command to the MPR084, and give it the register address in the command byte. This sets up the device for subsequent reads.
  • Now you send a read command. The MPR084 reuses the address from the write for the register to read from.

It turns out that I am correct. Without further ado, here is

Updated Sketch: CORRECT

 
#include <Wire.h>
// For reference. These are the ANALOG pins:
const int i2cSCLPin =  5; // the number of the I2C interface, SCL pin
const int i2cSDAPin = 4;  // SDA pin

#define MPR084_ADDR 0x5C  // assumes the MPR084 AD0 pin is low
#define regINFO 0x14      // Sensor Information Register

char c;
void setup() {
  sleep 1000;
  Serial.begin(19200); // For reporting
  Serial.print("MPR084 test.\n");
  Wire.begin();
  Wire.beginTransmission(MPR084_ADDR);
  Wire.send(regINFO); // This would be my command byte
  Wire.endTransmission();
  Wire.requestFrom(MPR084_ADDR, 16); // Not sure how much data there is, let's try this.
  Wire.endTransmission();
  while (Wire.available()) {
    c = Wire.receive();
    if (c != 0) {
      Serial.print(c);
    }
  }
}

void loop() {
  // Nothing to see here, move along...
}

and my glorious I2C output, as seen in the Arduino serial monitor window, looks something like this:

 
VER:1_0_0Freescale,PN:MPR084,QUAL:EXTERNAL,

Epilogue

What Went Wrong

So, where did I go wrong? Why didn't it work the first time. Upon review, it seems my mistakes were few but costly:

  • I was correct in the section Putting it together. I did want to start out by sending a write, then write nothing (ie, immediately send a Stop condition), then read. But I didn't have the understanding or confidence to put that in place. I wasn't really sure what the Wire library was doing, so I didn't know that my first sketch had actually done that ...for the most part. But furthermore,
  • I had misread the arguments to the readFrom() method, and was sending it a register address instead of a device address. This is because I thought I needed to send the MPR084 slave address again using beginTransmission(). I didn't realize that beginTransmission() set the slave address in an internal Wire library variable, and therefore remembered it for subsequent method calls.

Where to Go from Here

Having finally spoken to my I2C device, where does one go? Onward, to greater horizons, of course! Now I am able to speak more confidently with my I2C device. I can construct my project. I am able to purchase other devices and create more gadgets. And, regarding the Wire library, I have a few ideas:

  • It needs better documentation, which I have endeavored to create here: on my Wire Library Detailed Reference page.
  • The Wire library lends itself to linear programming methods only. I may create something like Wire2.0, which actually would be different enough from the Wire library that I would give it a different name.
    • The SIGNAL function in the Wire library is excellent, and a ton- a TON- of work went into that thing. The finite state machine looks to be pretty complicated, and the SIGNAL function handles it nicely.
    • The Wire library itself has confusing method calls. The beginTransmission() call really doesn't begin the transmission.

(to be continued)

Share