<h1 style='border: none'><center>Embedded Systems Lab 07 & 08</center></h1>
### <center>SPI & I2C Protocols</center>
###### Author: Mohammed Nafiz ALMadhoun
---
<p style='text-align: justify;'>
In this lab, we are going to talk about SPI & I2C communication protocols, we won't talk about the protocols themselves and explain every aspect of every protocol, but rather to make you familiar with these protocols and learn how to use them in real-life examples.
</p>
## **SPI Protocol**
<p style='text-align: justify;'>
We will start this section by opening Chapter 14 in the LPC111x Datasheet, as usual, the chapter will start with the initializing sequence, and then will explain each register to control the integrated SPI block.
</p>
### **Initiliazing Sequance:**
1. Select the pins correct functionality using `IOCONFIG` register.
2. Power up the peripheral using `SYSAHBCLKCTRL` register.
3. Enable the SPI clock using `SSPX_CLKDIV` register.
4. De-assert SPI Reset in `PRESETCTRL` register.
5. Set the peripheral configurations.
```c=
void setup_spi () {
LPC_IOCON->PIO0_2 = 0x1;
LPC_IOCON->PIO0_9 = 0x1;
LPC_IOCON->JTAG_TCK_PIO0_10 = 0x2;
LPC_SYSCON->SYSAHBCLKCTRL |= 1 << 11; // Enable SPI0
LPC_SYSCON->SSP0CLKDIV = 0x1; // Set CLKDIV = 1
LPC_SYSCON->PRESETCTRL |= 0x1; // De-asserted SPI Reset
LPC_SSP0->CR0 = 0x7; // Set frame size to 8 bit
LPC_SSP0->CR1 = 0x2; // Enable SPI 0
// Note, you will need to set the SPI frequency using CR0 and CPSR.
// PCLK / (CPSDVSR * [SCR+1])
}
```
Note that this is a minimal setup to let the MCU works as Master.
### **Writing Data**
<p style='text-align: justify;'>
To write data to the SPI protocol, you will need to write your data to the Data Register in the SPI block, the same register will be used to read data also.
</p>
<p style='text-align: justify;'>
In this example, we will write a function that writes data to the register and waits until the data is fully transmitted.
</p>
<p style='text-align: justify;'>
You can check the status register to check if there is data in the receive FIFO and transmit FIFO
</p>
```c=
// I used uint16_t here beacuse it's the max we can send.
void spi_send_data (uint16_t value) {
LPC_SSP0->DR = value;
while ((LPC_SSP0->SR & 0x1) == 0); // Wait until transmited
}
```
### **Example 1: APA102C RGB LED Strip**
In this example we will control an RGB LED Strip, first, we should open the [APA102C Datasheet](https://cdn-shop.adafruit.com/datasheets/APA102.pdf).
The datasheet didn't specify any configuration for the SPI, so that means the APA102C could work with any configuration!
By looking at page 3 (Function Description), the datasheet specifies how the data should be sent, each frame is a 32-bit wide data, which is divided into 8-bit frames, and you should start with all zeros frame.
The color frame should start with `111` and followed by 5-bit brightness, then 8-bit for the blue color, 8-bit for green, and 8-bit for red
```c=
void apa_write_start () {
spi_send_data(0);
spi_send_data(0);
spi_send_data(0);
spi_send_data(0);
}
void apa_write_color (char r, char g, char b) {
spi_send_data(0xFF); // Full birghtness
spi_send_data(b);
spi_send_data(g);
spi_send_data(r);
}
```
### **Example 2: NOKIA 3310 Display**
In this example, we are going to control a NOKIA 3310 display, which uses a `PCD8544` controller.
By looking at the [PCD8544 Datasheet](https://cdn-shop.adafruit.com/datasheets/pcd8544.pdf), we could notice that the maximum SPI data rate is 4MBit/S, the data is captured at the positive edge of the CLK, and its logic voltage is 3.3v.
Besides the SPI protocol, there is a D/C Pin, which indicates if the SPI frame is Data or Command.
```c=
void pcd_send_command(char command) {
LPC_GPIO2->DATA &= ~0x1; // Reset D/C Pin
spi_send_data(command);
}
void pcd_send_data(char command) {
LPC_GPIO2->DATA |= 0x1; // Set D/C Pin
spi_send_data(command);
}
```
In this example, I connected GPIO2 Pin 0 to D/C Pin.
<center>

Table 1 (Instruction Set)[^1]
</center>
[^1]: [PCD8544 Datasheet](https://cdn-shop.adafruit.com/datasheets/pcd8544.pdf)
Now by looking at Table 1 (Instruction Set) page 14, we can initialize and draw to the screen using the provided instructions.
1. We should power the display by sending 0x20.
2. Set Display control to normal display, by sending 0x0D
```c=
int main (void)
{
setup_spi();
LPC_GPIO2->DIR = 0x1; // Set GPIO2_0 As output (D/C)
pcd_send_command (0x20);
pcd_send_command (0x0D);
while (1) {
pcd_send_command (0x80); // Set x address to zero
pcd_send_command (0x40); // Set y address to zero
// Send Pixels Here
}
}
```
## **I²C Protocol**
<p style='text-align: justify;'>
As in the previous section, we will start by opening Chapter 15 in the LPC111x Datasheet, the chapter will start with the initialization sequence, then the configurations.
</p>
### **Initiliazing Sequance:**
1. Select the pins correct functionality using `IOCONFIG` register.
2. Power up the peripheral using `SYSAHBCLKCTRL` register.
3. De-assert I2C Reset in `PRESETCTRL` register.
4. Set the peripheral configurations.
```c=
void setup_i2c () {
LPC_IOCON->PIO0_4 = 0x1;
LPC_IOCON->PIO0_5 = 0x1;
LPC_SYSCON->SYSAHBCLKCTRL |= 1 << 5; // Enable I2C Clock
LPC_SYSCON->PRESETCTRL |= 0x2; // De-assert I2C Reset
// I2C_CLK = PCLK / (SCLH+SCLL)
LPC_I2C->SCLH = 60; // Setup clock divider SCLH
LPC_I2C->SCLL = 60; // Setup clock divider SCLL
LPC_I2C->CONSET = 1 << 6; // Enable I2C
}
```
Now to understand, how we should read or write data using I2C, we should decide in which mode our microcontroller will work.
1. Master Transmitter Mode.
2. Master Receiver Mode.
3. Slave Receiver Mode.
4. Slave Transmitter Mode.
The datasheet specifies the actions you should take in each mode in section 15.8.
### **Writing Data**
<p style='text-align: justify;'>
We will start with writing a function to send the start single, and the address of the slave device.
</p>
```c=
void i2c_send_start (char address) {
LPC_I2C->CONCLR = 1 << 3; // Clear I2C Interrupt flag
LPC_I2C->CONSET = 1 << 5; // Set Start flag
while ((LPC_I2C->CONSET & 0x8) == 0); // Busy Waiting, 0x8 == 1 << 3
LPC_I2C->DAT = address; // Write Salve Address (ADDRESS + R/W)
LPC_I2C->CONCLR = 1 << 3 | 1 << 5; // Clear I2C Interrupt flag, Start flag
while ((LPC_I2C->CONSET & 0x8) == 0); // Busy Waiting
}
```
<p style='text-align: justify;'>
In this function, we send the start bit by asserting the Start flag, then we send the address of the slave we want to communicate with.
</p>
```c=
void i2c_send_data (char value) {
LPC_I2C->DAT = value;
LPC_I2C->CONCLR = 1 << 3; // As documanted, we should clear SI.
while ((LPC_I2C->CONSET & 0x8) == 0);
}
```
In this function, we send 8-bit data, which is the maximum we can send as one frame.
```c=
void i2c_send_end () {
LPC_I2C->CONCLR = 1 << 3;
LPC_I2C->CONSET = 1 << 4; // Set Stop flag.
while ((LPC_I2C->CONSET & (1 << 4)) != 0);
}
```
<p style='text-align: justify;'>
Now, after sending all the data, we should send an end flag, which indicates the end of transmission and set the bus free.
</p>
### **Example 1: MCP4725 Digital to Analog Converter**
<p style='text-align: justify;'>
In this example, we are going to talk about the MCP4725 DAC, which contains an EEPROM, which is very useful if you want to save the digital value for the next system reset, in this example, we are going to focus on the Fast Mode, which just changes the digital value without writing it to the EEPROM.
</p>
You can check the [MCP4725 Datasheet Here](https://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf).
According to Figure 7-1 in the datasheet, the address of the MCP4725 will be `1 1 0 0 A2 A1 A0`, in which A2 and A1 are configured at manufacturing, and A0 is a configurable pin.
After sending the address, we will send the chip the instructions according to Figure 6-1.
```c=
int main (void)
{
setup_i2c();
i2c_send_start (0xC0); // Send Start address 0xC0 = 11000000
i2c_send_data (0x0F); // Send Fast Mode, PD1 & PD0 = 0, D11-D8 = 0xF
i2c_send_data (0xFF); // Send D7-D0 = 0xFF
i2c_send_end (); // Send End
while(1);
}
```
In this code, The output volt will be the VCC volt, as D11-D0 = 0xFFF.
### **Example 2: AT24C1024 EEPROM**
In this example, we are going to use an EEPROM to store and read data, so, the master will be the sender at first, then the receiver.
As usual, we will follow the [AT24C1024 Datasheet](http://ww1.microchip.com/downloads/en/devicedoc/doc1471.pdf) instructions:
1. The address of the chip will be `1 0 1 0 0 A1 P0` (See Figure 7).
2. To Write data, we should send two bytes of data as the address.
3. Send the data (One byte or more).
4. Send End.
**Note:** This chip is 1024 Mbit and needs 17-bit address to access each byte (1024 Mbit = 2^17 * 8), the address = P0 FirstByte SecondByte = 1 + 8 + 8 = 17-bit.
```c=
void eeprom_write_byte (uint16_t address, char data) {
i2c_send_start (0xA0);
i2c_send_data(address >> 8);
i2c_send_data(address && 0xFF);
i2c_send_data(data);
i2c_send_end();
delay_us(10000); // We should wait 10ms after writing.
}
```
**Note:** this code will use page 0 only, as the salve address = 0xA0.
Now to read a random byte, we will follow Figure 11 in the datasheet, which states that we should perform a dummy write, then follow with a repeated start and the address with the Read flag.
```c=
char eeprom_read_byte (uint16_t address) {
i2c_send_start (0xA0);
i2c_send_data(address >> 8);
i2c_send_data(address && 0xFF);
i2c_send_start (0xA1);
LPC_I2C->CONCLR = 1 << 3; // Clear SI.
while ((LPC_I2C->CONSET & 0x8) == 0); // Busy waiting util recive.
i2c_send_end();
return LPC_I2C->DAT;
}
```
<center>
### END OF LAB 7 & Lab 08</center>
<div style="display:none">
# Embedded Systems Lab 07 & Lab 08
</div>