In the last set of notes, I set up an Arduino to have its built in LED blink. This was a very straightforward, and so the step up to the LCD will be pretty significant. I will start by learning some about the communication protocol that is supported by the Arduino, its called Inter-Integrated Circuit Protocol (I2C). # Inter Integrated Circuit Protocol (I2C) I am learning about this protocol using [this video](https://www.youtube.com/watch?v=CAvawEcxoPU&t=14s). The I2C protocol is a simple way to send data over short distances. It is used by many LCD devices to communicate with the Arduino. The protocol itself is very straightforward and consists of just two wires/signals: one is the clock (SCL), and the other is the data (SDA). The process to send data is as follows: 1. A **START** condition is initiated. This occurs when the SDA line transitions from high to low while the SCL line is high. 2. The I2C address for the device is sent and subsequently acknowledged (ACK) by the device. 3. Data is sent. A note here on the relationship between the SDA and SCL signals: the SDA signal only transitions when the SCL is low. This condition ensures that the START and STOP conditions are unique and can be detected. Each data byte is acknowledged (ACK) by the receiver. 4. If no more data is to be sent, the process ends with a **STOP** condition. This occurs when the SDA line transitions from low to high while the SCL line is high. # Implementation Let's get into some implementation of all this in code. ## Set up and the Start Condition The first things we need to figure out is 1. how to initialize the i2c 2. how to send data 3. how to send the start condition ### Initialize the Two wire interface For this we will make use of some `utils` headers, namely: ```c #include <util/twi.h> // i2c/twi ``` as part of this we will also include `avr` headers to make life easier as well as the define for the address of the device. ```c #include <avr/io.h> #include <util/twi.h> // i2c/twi library #define SSD1306_ADDR 0x3C // Default I2C address for SSD1306 ``` I know I have this device by looking at the description of the product page, it states > Embedded Driver IC: SSD1306. Communication: I2C/IIC Interface, only need two I / O ports so we start by creating a function to initialize the device, the things we need to do in this step are: 1. set the frequency of the clock using the TWBR (two wire bit rate register) 2. enable the two wire interface using both the TWCR (two wire control register) and TWEN (two wire enable constant) Here is the basic function that does this: ```c void i2c_init() { // set frequency to 100kHz with 16MHz clock TWBR = 72; // enable the two wire interface TWCR = (1 << TWEN); } ``` the first thing we do is set TWBR to 72, this is where we set the frequency for the clock SCL. We update the two wire control register to enable the two wire interface. To do this we do use the TWEN constant the has the shift amount needed to mask ON the TWCR that will enable the two wire interface. So before we want to send a start condition we first need to make. ### Adding ability to send data The next thing we to do is create a function that will let us send data, here is the function: ```c void i2c_send_byte(uint8_t data) { // load data TWDR = data; // clear interrupt flag and start transmission TWCR = (1 << TWINT) | (1 << TWEN); // wait for completion while (!(TWCR & (1 << TWINT))) ; } ``` The first we do is load the data into the TWDR (two wire data register), this is the register that holds that we want to transmit over the i2c bus. We then need to setup the TWCR so we do two things 1. clear any interrupt flags 2. send enable flags for the protocol We do this with the following: ```c TWCR = (1 << TWINT) | (1 << TWEN); ``` this is pretty much the same process we did in all other register manipulation code, but still lets break it down a bit. We do `(1 << TWEN)` to move `00000001` to the left by TWEN amount. We do the same thing with TWINT we shift `00000001` by TWINT, we OR these two to get the full set of bits we wish to set the TWCR to. The last thing we want to review is the while loop the end. This loop essentially just goes on forever until we get a an interrupt indicating that the transmission has finished. Lets break this down so we understand what exactly is going on. Again we have a bit-wise & we will start to breakdown: ```c TWCR & (1 << TWINT) TWCR & ( 00000001 << TWINT) // if we look at the header file we would see that TWINT is 7 TWCR & ( 00000001 << 7) TWCR & ( 10000000) ``` the and operator will basically just isolate the bit for the interrupt in the control register, we then apply the `!` if the bit is set then the negation will make this false if the bit is not set then the negation will make this true. In the context of the while loop if the interrupt bit is set then the loop stops, if its not set then loop will continue. ### The Start Condition Next we will send the start condition, here is what this look like: ```c void i2c_start() { TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))) ; } ``` this is pretty much the same thing we have done with everything so far. We send active bits to the TWCR for the following: - TWINT - TWSTA - TWEN We then do the following and wait for an interrupt flag again, the same way we did in the previous functions. Ok so at this point we have the code required to start sending data to the LCD. Let's start putting this all together into a function that sends all the data required to make the device work. ```c void ssd1306_init() { i2c_init(); // Start I2C transmission i2c_start(); i2c_send_byte(SSD1306_ADDR << 1); // Send address with write bit // Send initialization commands i2c_send_byte(0x00); // Command stream i2c_send_byte(0xAE); // Display off i2c_send_byte(0xD5); // Set display clock i2c_send_byte(0x80); i2c_send_byte(0xA8); // Set multiplex i2c_send_byte(0x3F); i2c_send_byte(0xD3); // Set display offset i2c_send_byte(0x00); i2c_send_byte(0x40); // Set start line i2c_send_byte(0x8D); // Charge pump i2c_send_byte(0x14); i2c_send_byte(0x20); // Memory mode i2c_send_byte(0x00); i2c_send_byte(0xA1); // Segment remap i2c_send_byte(0xC8); // COM direction i2c_send_byte(0xDA); // COM pins i2c_send_byte(0x12); i2c_send_byte(0x81); // Contrast i2c_send_byte(0xCF); i2c_send_byte(0xD9); // Pre-charge i2c_send_byte(0xF1); i2c_send_byte(0xDB); // VCOM detect i2c_send_byte(0x40); i2c_send_byte(0xA4); // Resume display i2c_send_byte(0xA6); // Normal display (not inverted) i2c_send_byte(0xAF); // Display on i2c_stop(); } ``` Lets break this down, we first call the init function we wrote earlier as well as the start condition since we want to start sending some data to the device. The first piece of data we send over is the address of the LCD, this is a common device and the address is known to be `SSD1306_ADDR` we shift this over by 1 since it will give us room the r/w bit. We next send a series of commands that will get the device up and ready to receive graphics instructions. Let's now step through each of the data transmission to learn what is all needed to get the device up and ready. ```c i2c_send_byte(0x00) // command stream start i2c_send_byte(0xAE) // display off ``` we start by sending `0x00`, this tells the protocol that a command is going to be sent next, the next command will turn off the display. This basically means that we want the display to be off while we do all this set up work. Also note that when I say send, we are sending this bit of data over the i2c bus, which ultimately makes it to the address for the device we had set up. Next we want to set up some clock information to do this we use the following: ```c i2c_send_byte(0xD5); // Set display clock i2c_send_byte(0x80); ``` this bit of code first tells the device we ant to change the frequency the second is the value we want to set it to. We can think about it as the parameter to the command. This same pattern is how we carry out all of the instructions that follow.