The I2C Protocol on a PIC
I2C lets one PIC talk to many chips over just two shared wires, using addresses instead of dedicated wiring for each device.
I2C (Inter-Integrated Circuit) is a communication protocol built for exactly the situation UART struggles with: talking to several chips at once. Instead of a dedicated pair of wires per device, every device on an I2C bus shares just two lines — SDA (data) and SCL (clock) — and each device is told apart from the others by a unique address, typically seven bits long, agreed upon in that chip's datasheet.
One device on the bus acts as the controller (traditionally called the "master"), generating the clock signal on SCL and initiating every exchange. Every other device is a peripheral (traditionally "slave"), which only speaks when the controller addresses it directly. A PIC can act as either role, though for a beginner project it's most common to have the PIC act as the controller reading from a sensor.
A typical I2C transaction looks like: the controller sends a "start" condition, sends the target device's address plus a bit saying whether it wants to read or write, waits for that device to acknowledge, then sends or receives one or more bytes, and finally sends a "stop" condition to release the bus. The details of framing that sequence are usually hidden behind a library function, so most day-to-day I2C code looks like ordinary function calls rather than raw bit manipulation.
#define SENSOR_ADDR 0x48
unsigned char read_sensor(void) {
i2c_start();
i2c_write((SENSOR_ADDR << 1) | 1); // address + read bit
unsigned char value = i2c_read_nack();
i2c_stop();
return value;
}