I2C I²C (Inter-Integrated Circuit) What is I2C I2C is pronounced "eye-squared see". It is also known as "TWI" because of the initial patent issues of this BUS. This is a popular, low throughput (100-1000Khz), half-duplex BUS that only uses two wires regardless of how many devices are on this BUS. Many sensors use this BUS because of its ease of adding to a system. Figure x. of some devices connected up to an I2C bus Pins of I2C There are two pins for I2C: SCL: Serial clock pin SDA: Serial data pin The clock line is usually controlled by the Master with the exception that the slave may pull it low to indicate to the master that it is not ready to send data. The data line is bi-directional and is controlled by the Master while sending data, and by the slave when it sends data back after a repeat-start condition described below. Open-Collector/Open-Drain BUS I2C is an open-collector BUS, which means that no device shall have the capability of internally connecting either SDA or SCL wires to power source. The communication wires are instead connected to the power source through a "pull-up" resistor. When a device wants to communicate, it simply lets go of the wire for it to go back to logical "high" or "1" or it can connect it to ground to indicate logical "0".  This achieves safe operation of the bus (no case of short circuit), even if a device incorrectly assumes control of the bus. Figure x. Devices connected to I2C bus. Figure x. I2C device pin output stage. Pull-up resistor Using a smaller pull-up can acheive higher speeds, but then each device must have the capability of sinking that much more current. For example, with a 5v BUS, and 1K pull-up, each device must be able to sink 5mA. Why Use I2C Pros IO/Pin Count: 2 pins bus regardless of the number of devices. Synchronous: No need for agreed timing before hand Multi-Master Possible to have multiple masters on a I2C bus Multi-slave: 7-bit address allows up to an absolute maximum of 119 devices (because 8 addresses are reserved) You can increase this number using I2C bus multiplexers Cons Slow Speed: Typical I2C devices have a maximum speed of 400kHz Some devices can sense speeds up to 1000kHz or more Half-Duplex: Only one device can talk at a time Complex State Machine: Requires a rather large and complex state machine in order to handle communication Master Only Control: Only a master can drive the bus Exception to that rule is that a slave can stop stop the clock if it needs to hold the master in a wait state Hardware Signal Protocol Overhead This protocol includes quite a few bits, not associated with data to handle routing and handshaking. This slows the bus throughput even further Protocol Information I2C was designed to be able to read and write memory on a slave device. The protocol may be complicated, but a typical "transaction" involving read or write of a register on a slave device is simple granted a "sunny-day scenario" in which no errors occur. I2C at its foundation is about sending and receiving bytes, but there is a layer of unofficial protocol about how the bytes are interpreted.  For instance, for an I2C write transaction, the master sends three bytes and 99% of the cases, they are interpreted like the following: Device Address Device Register Data The code samples below illustrates I2C transaction split into functions, but this is the wrong way of writing an I2C driver. An I2C driver should be "transaction-based" and the entire transfer should be carried out using a state machine. The idea is to design your software to walk the I2C hardware through its state to complete an I2C transfer. Signal Timing Diagram Figure x. I2C communication timing diagram. Write Transaction Figure x. Master Transmit format The master always initiates the transfer, and the device reading the data should always "ACK" the byte. For example, when the master sends the 8-bit address after the START condition, then the addressed slave should ACK the 9th bit (pull the line LOW). Likewise, when the master sends the first byte after the address, the slave should ACK that byte if it wishes to continue the transfer. A typical I2C write is to be able to write a register or memory address on a slave device. Here are the steps: Master sends START condition followed by device address. Device that is addressed should then "ACK" using the 9th bit. Master sends device's "memory address" (1 or more bytes). Each byte should be ACK'd by the addressed slave. Master sends the data to write (1 or more bytes). Each byte should be ACK'd by the addressed slave. Master sends the STOP condition. To maximize throughput and avoid having to send three I2C bytes for each slave memory write, the memory address is considered "starting address". If we continue to write data, we will end up writing data to M, M+1, M+2 etc. The ideal way of writing an I2C driver is one that is able to carry out an entire transaction given by the function below. NOTE: that the function only shows the different actions hardware should take to carry out the transaction, but your software will be a state machine. void i2c_write_slave_reg(void) { // This will accomplish this: // slave_addr[slave_reg] = data; i2c_start(); i2c_write(slave_addr); i2c_write(slave_reg); // This is "M" for "memory address of the slave" i2c_write(data); /* Optionaly write more data to slave_reg+1, slave_reg+2 etc. */ // i2c_write(data); /* M + 1 */ // i2c_write(data); /* M + 2 */ i2c_stop(); } Read Transaction An I2C read is slightly more complex and involves more protocol to follow. What we have to do is switch from "write-mode" to "read-mode" by sending a repeat start, but this time with an ODD address.  This transition provides the protocol to allow the slave device to start to control the data line.  You can consider an I2C even address being "write-mode" and I2C odd address being "read-mode". When the master enters the "read mode" after transmitting the read address after a repeat-start, the master begins to "ACK" each byte that the slave sends. When the master "NACKs", it is an indication to the slave that it doesn't want to read anymore bytes from the slave. Again, the function shows what we want to accomplish. The actual driver should use state machine logic to carry-out the entire transaction. void i2c_read_slave_reg(void) { i2c_start(); i2c_write(slave_addr); i2c_write(slave_reg); i2c_start(); // Repeat start i2c_write(slave_addr | 0x01); // Odd address (last byte Master writes, then Slave begins to control the data line) char data = i2c_read(0); // NACK last byte i2c_stop(); } void i2c_read_multiple_slave_reg(void) { i2c_start(); i2c_write(slave_addr); i2c_write(slave_reg); // This will accomplish this: // d1 = slave_addr[slave_reg]; // d2 = slave_addr[slave_reg + 1]; // d3 = slave_addr[slave_reg + 2]; i2c_start(); i2c_write(slave_addr | 0x01); char d1 = i2c_read(1); // ACK char d2 = i2c_read(1); // ACK char d3 = i2c_read(0); // NACK last byte i2c_stop(); } I2C Slave State Machine Planning Before you jump right into the assignment, do the following: Read and understand how an I2C master performs slave register read and write operation Look at existing code to see how the master operation handles the I2C state machine function. Next to each of the master state, determine which slave state is entered when the master enters its state Determine how your slave memory or registers will be read or written In each of the states given in the diagrams below, your software should take the step, and the hardware will go to the next state granted that no errors occur. To implement this in your software, you should: Perform the planned action after observing the current state Clear the "SI" (state change) bit for HW to take the next step The HW will then take the next step, and trigger the interrupt when the step is complete Master Write In the diagram below, note that when the master sends the "R#", which is the register to write, then the slave state machine should save this data byte as it's INDEX location. Upon the next data byte, the indexed data byte should be written. Stop here and do the following:1.  Check I2c::I2cHandler()2. Compare the code to the state diagram below I2C Master Write Transaction Section 19.9.1 in LPC17xx User Manual Master Read In the diagram below, the master will write the index location (the first data byte), and then perform a repeat start. After that, you should start returning your indexed data bytes. I2C Master Read Transaction Section 19.9.2 in LPC17xx User Manual I2C Master transmitter statemachine I2C Slave Lab Assignment Objective Get hands on experience working with and understanding how I2C works by implementing an I2C slave device. Assignment The I2C master driver is already implemented in SJSU-Dev2. Study the existing I2C code: i2c.hpp file and extend it to handler slave mode operation. Flashing the example project CommandLine on to the board (completely unmodified) and connect it to your 2nd Slave Board which will contain your i2c slave driver. On your master board, you can just use the i2c terminal command to read and write to the I2C registers of a slave device. See help for the help about the command Part 0: Read the I2C Chapter IGNORE THE Software Implementation of the I2C chapter. It is not correct and does not match the proper information laid out in the state diagram and truth tables. Read over the I2C chapter and the various registers it contains. You may ignore the DMA registers. In order to grasp the amount of information in the chapter, you may need to read it multiple times. Draw out the state machine outlined in the chapter's state diagram and truth table for yourself. Part 1: Get I2C Slave Interrupt to fire In this Part 0 of this assignment, your objective is to simply to initialize your I2C slave to get its first state interrupt to fire. Add your InitializeAsSlave() method Add the slave address recognized state into your I2C slave driver and print a message when you hit this state inside the ISR. Connect this Master Board to the Slave Board by wire-wrapping the I2C SCL/SDA wires together. To test that your slave driver initialize is working, invoke i2c discover on the master board. Your slave board's address will appear. When you connect the two boards to I2C There will be two of each sensor with the same address, which too is sort of okay.  For example, the temperature sensor at the same address (from the 2 boards). #include #include #include "L1_Drivers/i2c.hpp" // Slave Board sample code reference int main(void) { // Create I2c object (defaults to I2C2) I2c i2c; // Pick any address other than an existing ones on the board. Use `i2c discover` to see what those are. const uint8_t slaveAddr = 0xC0; // Our devices read/write buffer (This is the memory your master board will read/write) volatile uint8_t buffer[256] = { 0 }; // I2C is already initialized before main(), so you will have to add initSlave() to i2c base class for your slave driver i2c.InitializeSlave(slaveAddr, buffer, sizeof(buffer)); // I2C interrupt will (should) modify our buffer. // So monitor the buffer, and print and/or light up LEDs // ie: If buffer[0] == 0, then LED ON, else LED OFF uint8_t prev = buffer[0]; while(true) { if (prev != buffer[0]) { printf("buffer[0] changed from 0x%08X to 0x%08X by the other Master Board\n", prev, buffer[0]); prev = buffer[0]; } } return 0; } Part 2: Implement I2C slave behavior in I2CHandler Using the state machine diagrams in the datasheet before you begin, make sure you fully understand the slave states. The code you implement will need to follow the state machine guidelines exactly. Extend I2C state machine to handle I2C slave operations. You may add printf statements to the I2C state machine code to identify what states you enter when the Master Board is trying to do an I2C transaction. Requirements See Canvas. Extra Credit:• Get multi byte read and write operation to work. • Do something creative with your slave board since you have essentially memory mapped the slave device over I2C. Maybe use buffer[0] to enable a blinking LED, and buffer[1] controls the blink frequency? You can do a lot more.  Just blinking the LEDs is not enough.