Introduction to I2C and SPI with PIC Microcontrollers

Microcontrollers from Microchip’s PIC family are widely used in embedded systems, from simple sensor nodes to complex industrial controllers. A key skill for any embedded developer is the ability to interface peripherals using serial communication protocols. Two of the most common are I²C (Inter‑Integrated Circuit) and SPI (Serial Peripheral Interface). Mastering these protocols allows you to connect sensors, memory chips, displays, ADCs, and many other devices to your PIC design with minimal wiring and high reliability.

This article provides a practical, in‑depth guide to implementing I²C and SPI on PIC microcontrollers. We will cover hardware setup, register configuration, typical code sequences, and common debugging techniques. By the end you will have the knowledge to confidently add serial communication to your next PIC project.

Overview of I²C and SPI

Both protocols are synchronous serial buses, but they differ in architecture, speed, and application.

I²C (Inter‑Integrated Circuit)

I²C uses only two wires: SDA (Serial Data) and SCL (Serial Clock). Every device on the bus has a unique 7‑bit or 10‑bit address, and communication is master‑slave. The master generates the clock and initiates transactions. Multiple masters can share the bus with arbitration. Standard mode runs at 100 kHz, fast mode at 400 kHz, and high‑speed mode up to 3.4 MHz. I²C is ideal for peripherals that do not require blazing speed, such as temperature sensors, real‑time clocks, EEPROMs, and small displays.

Because only two wires are needed, I²C saves I/O pins, making it attractive for small PICs. However, the protocol is more complex than SPI because of addressing, acknowledge bits, and clock stretching.

SPI (Serial Peripheral Interface)

SPI uses four lines: MOSI (Master Out Slave In), MISO (Master In Slave Out), SCLK (Serial Clock), and SS (Slave Select). It is a full‑duplex bus – data is sent and received simultaneously. The master generates the clock and selects the slave by pulling its chip‑select (SS) pin low. SPI is significantly faster than I²C, with speeds into tens of MHz, and is the protocol of choice for high‑throughput devices such as SD cards, graphical displays, ADCs, and digital potentiometers.

SPI is simpler to implement in hardware but requires more pins – one dedicated SS line per slave. Many PIC microcontrollers include a dedicated Master Synchronous Serial Port (MSSP) module that can be configured for either I²C or SPI.

Hardware Considerations for I²C and SPI

Before diving into code, you must get the hardware right.

Pull‑up resistors: I²C lines are open‑drain; they require external pull‑up resistors (typically 4.7 kΩ to 10 kΩ) to pull SDA and SCL to VDD. The value depends on bus capacitance and speed. For long buses or fast mode, lower resistor values may be needed to meet rise‑time specifications.

Voltage levels: Ensure your PIC and the peripheral operate at the same logic voltage (e.g., 3.3 V or 5 V). If they differ, use level shifters such as the SparkFun BOB‑12009 or a simple MOSFET circuit.

Bus length and capacitive loading: For longer wires (above a few inches), keep the bus capacitance below the maximum specified in your PIC datasheet (PIC16F1xxx datasheet often states 400 pF for I²C). For SPI, longer lines may cause signal reflections; use slower clock rates and shielded cables if needed.

Decoupling capacitors: Place 0.1 µF capacitors near each IC’s power pins to suppress noise that can corrupt data.

Configuring I²C on PIC Microcontrollers

Most modern PICs include the Master Synchronous Serial Port (MSSP) module that supports both I²C and SPI. We’ll focus on the MSSP in I²C master mode for a typical 8‑bit PIC (e.g., PIC16F18877).

Key Registers for I²C

  • SSPxADD – Baud rate generator reload value. The I²C clock frequency is derived from the system clock using a formula: FSCL = FOSC / (4 * (SSPxADD + 1)). For 100 kHz with a 16 MHz oscillator, SSPxADD = 39.
  • SSPxSTAT – Status register. Bits indicate bus collision, write collision, and whether a start/stop condition has been detected.
  • SSPxCON1 – Control register 1. Selects I²C mode (master, slave) and enables the module.
  • SSPxCON2 – Master mode control. Commands for sending Start, Restart, Stop, and acknowledging.
  • SSPxBUF – Data buffer. Write to send, read to receive.

I²C Master Initialization Sequence

  1. Disable the MSSP module by clearing the SSPEN bit (SSPxCON1<5>).
  2. Set I²C master mode (typically 0x08 in SSPxCON1 bits <3:0>).
  3. Configure the baud rate by loading SSPxADD with the proper value.
  4. Set up SDA and SCL pins as inputs with weak pull‑ups (or use external resistors).
  5. Enable the module by setting SSPEN.
  6. Optionally enable interrupts for the MSSP.

Example: Reading from an LM75 Temperature Sensor

The LM75 has a 7‑bit address (typically 0x48 for the A0‑A2 pins grounded). To read the temperature register (pointer 0x00), you must first write the pointer, then generate a repeated start and read.

Write pointer sequence: Start → address byte (0x90) → wait for ACK → pointer byte (0x00) → wait for ACK → Stop.

Read temperature sequence: Start → address byte (0x91) → wait for ACK → read two bytes (MSB, LSB) → NACK on last byte → Stop.

Each byte exchange requires polling the SSPxSTAT register for the BF (Buffer Full) bit and the SSPxIF interrupt flag. A common mistake is forgetting to send the ACK/NACK after receiving data. Use the ACKDT and ACKEN bits in SSPxCON2 to control acknowledge.

Common I²C Pitfalls

  • Clock stretching: Some slaves hold SCL low to slow down the master. Enable clock stretching support in the MSSP (set the CKP bit appropriately).
  • Bus collision: If another master tries to drive the bus, the MSSP sets the BCLIF flag. Use this to retry the transaction.
  • Stuck bus: If a slave does not release SDA after a transmission, the bus may hang. Implement a timeout that sends nine clock pulses to recover.

Configuring SPI on PIC Microcontrollers

The MSSP can also operate in SPI master or slave mode. We’ll cover master mode, which is most common.

Key Registers for SPI

  • SPIxBRG – Baud rate generator. For SPI, the clock frequency is FOSC / (2 * (SPIxBRG + 1)).
  • SPIxSTAT – Status register. Contains the BF bit, write collision, receive overflow flags.
  • SPIxCON1 – Control register 1. Selects master/slave, clock polarity (CKP), clock phase (CKE), and sample point (SMP).
  • SPIxCON2 – Additional control (e.g., frame mode, SS2 enable).
  • SPIxBUF – Data buffer for transmit and receive (buffered).

SPI Master Initialization Sequence

  1. Disable the SPI module (clear SSPEN).
  2. Set the desired clock polarity and phase. Mode 0 (CKP=0, CKE=1) is the most common: data is sampled on the rising edge and shifted on the falling edge.
  3. Configure the baud rate in SPIxBRG. For 1 MHz with a 16 MHz clock, SPIxBRG = 7.
  4. Select master mode (SSPM bits = 0b0000 for SPI master, clock = FOSC/4).
  5. Set the SS pin direction. On many PICs, the SS pin is a dedicated input in master mode. For slave selection, you must toggle a separate GPIO pin connected to the slave’s chip‑select.
  6. Enable the module.

Example: Interfacing with an MCP3008 8‑channel ADC

The MCP3008 is a 10‑bit ADC that communicates via SPI in mode 0. To read a channel (e.g., channel 0), the master sends three bytes: a start bit, a single‑ended/differential bit, and the channel address. The slave simultaneously returns two data bytes.

Transaction sequence:
1. Drive SS low.
2. Write 0x01 (start bit) to SPIxBUF – wait for BF bit to clear.
3. Write (channel | 0x80) to SPIxBUF – read the first garbage byte from the previous transfer.
4. Write 0x00 (dummy) to SPIxBUF – read the high 8 bits of conversion result.
5. Write 0x00 again – read the low 2 bits and trailing zeros.
6. Drive SS high.

Note that every write to SPIxBUF triggers a simultaneous receive. You must read the buffer after each write to avoid overflow.

Managing Multiple SPI Slaves

Each slave requires a unique chip‑select pin. Use one GPIO per slave. Ensure there is a small delay between changing the SS pin and starting the clock to meet setup time requirements of the slave. Many PICs allow you to use the hardware SS pin for automatic slave‑select generation only in slave mode; in master mode you must do it manually.

Debugging I²C and SPI Communication

When things don’t work, follow a methodical approach:

  • Use a logic analyzer or oscilloscope. This is the single most effective debugging tool. Check that the clock is present, data transitions occur, and addresses are correct. For I²C, verify start/stop conditions and acknowledge bits.
  • Check pull‑up resistors. If SDA or SCL stay low, you may have a device holding the bus. Disconnect all slaves except one and try again.
  • Verify baud rate. Measure the clock signal with a scope. For I²C, the clock frequency must be within the slave’s specification.
  • Examine address and command bytes. A common mistake is sending a 7‑bit address without shifting left by one (the R/W bit goes in LSB). Remember that the address byte is (address << 1) | R/W.
  • For SPI, check clock polarity and phase. Many slaves are sensitive to the exact mode. Consult the slave’s datasheet. Also ensure that the chip‑select line is active low and driven accordingly.

If hardware debugging reveals correct waveforms, the issue may be in your firmware. Use debug LEDs or a serial console to print status registers. Many PIC development boards include an on‑board programmer/debugger (e.g., MPLAB PICkit 4) that can help you step through the code.

Software Bit‑Banging as a Backup

If the hardware peripheral is unavailable or if you must use a PIC that lacks an MSSP, you can implement I²C or SPI in software using GPIO pins. For I²C bit‑banging, you toggle SDA and SCL directly, respecting timing delays. For SPI, you shift bits out through a GPIO and simultaneously read the input pin. Bit‑banging is flexible but consumes CPU cycles and is slower. It is also a good way to learn the protocol at a low level.

Advanced Topics

I²C Multi‑Master and Clock Speed Tuning

When multiple masters share the bus, you must handle arbitration carefully. The MSSP in PIC supports multi‑master mode; it monitors the bus and will relinquish control if another master sends data. Use the bus collision interrupt (BCLIF) to detect loss of arbitration and retry after a random delay.

For faster I²C (e.g., 400 kHz), enable slew‑rate control on the output pins (if available) and use stronger pull‑up resistors (e.g., 2.2 kΩ). Some PICs have a dedicated SSPxADD that can be set to values smaller than zero by using a prescaler.

SPI Daisy‑Chaining and DMA

Some SPI devices support daisy‑chaining, where the data output (MISO) of one device feeds the input (MOSI) of the next. This allows multiple slaves with a single chip‑select line. The MSSP can handle daisy‑chaining if the devices support it.

For high‑speed SPI, consider using the PIC’s Direct Memory Access (DMA) controller (available on many newer PICs). DMA can transfer data to/from the SPI buffer without CPU intervention, freeing the core for other tasks. The DMA peripheral is configured with source, destination, and count, then triggered by the SPI interrupt.

Conclusion

I²C and SPI are essential tools in any embedded designer’s kit. With the flexible MSSP module found in most PIC microcontrollers, implementing these protocols is straightforward once you understand the register setup and timing parameters. Always start with a simple test – a dummy write/read loop – to verify your hardware and software foundation.

Datasheets remain your best friend: Microchip’s official datasheets provide exact register maps and timing diagrams. Application notes such as AN734 (I²C tips) and AN1009 (SPI use) are valuable resources. Practice with a development board and a logic analyzer, and you will quickly gain confidence in adding any I²C or SPI peripheral to your PIC‑based projects.