A lightweight and efficient C library for SPI (Serial Peripheral Interface) communication in Linux environments, primarily designed for single-board computers like Raspberry Pi.
- Features
- Requirements
- Installation
- Quick Start
- API Reference
- Error Handling
- Advanced Usage
- Hardware Considerations
- Debugging
- Examples
- Limitations
- Version History
- Contributing
- Simple API - Minimal learning curve with intuitive function names
- Configurable SPI modes - Support for modes 0, 1, 2, and 3
- Adjustable transfer speed - Set custom clock frequencies
- Flexible bits-per-word - Configure data width (typically 8 bits)
- Safety checks - Automatic validation of transfer sizes to prevent buffer overflows
- Error handling - Return codes instead of aborts (V2.0+)
- Debug support - Multiple verbosity levels for troubleshooting
- Zero dependencies - Only requires Linux kernel with spidev support
- Linux kernel with
spidevdriver support - Read/write permissions for
/dev/spidevX.Ydevices - C compiler (Clang, GCC, or compatible)
# Debian/Ubuntu
sudo apt-get install build-essential
# Fedora/RHEL
sudo dnf install gcc make
# Arch Linux
sudo pacman -S base-develEnable SPI interface on Raspberry Pi:
sudo raspi-config
# Navigate to: Interface Options -> SPI -> EnableVerify SPI devices are available:
ls -l /dev/spidev*
# Expected output: /dev/spidev0.0, /dev/spidev0.1, etc.# Build the static library
make
# Install to user directory ($HOME/lib and $HOME/include)
make install
# Or install system-wide (requires sudo)
sudo make PREFIX=/usr/local install
# Uninstall
make uninstall # or: sudo make PREFIX=/usr/local uninstall
# Clean build artifacts
make cleanInstallation paths:
- Default:
$HOME/lib/libspi.aand$HOME/include/spi_base.h - System-wide:
/usr/local/lib/libspi.aand/usr/local/include/spi_base.h
make CC=gcc
make CC=gcc installCopy spi_base.c and spi_base.h into your project and compile directly:
gcc -o my_program my_program.c spi_base.c -Wall -O2After running make install:
gcc -o my_program my_program.c -lspi -L$HOME/lib -I$HOME/include#include "spi_base.h"
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int fd;
int ret;
// Open SPI device
fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) {
perror("Failed to open SPI device");
return 1;
}
// Configure SPI parameters
ret = set_spi_mode(fd, 3); // SPI Mode 3
if (ret < 0) {
fprintf(stderr, "Failed to set SPI mode\n");
close(fd);
return 1;
}
ret = set_spi_bpw(fd, 8); // 8 bits per word
if (ret < 0) {
fprintf(stderr, "Failed to set bits per word\n");
close(fd);
return 1;
}
ret = set_spi_speed(fd, 500000); // 500 kHz
if (ret < 0) {
fprintf(stderr, "Failed to set speed\n");
close(fd);
return 1;
}
// Prepare data to transmit
tx[0] = 0x01;
tx[1] = 0x80;
tx[2] = 0x00;
// Perform SPI transfer
ret = spi_trans(fd, 3);
if (ret < 0) {
fprintf(stderr, "SPI transfer failed\n");
close(fd);
return 1;
}
// Read received data
printf("Received: 0x%02X 0x%02X 0x%02X\n", rx[0], rx[1], rx[2]);
close(fd);
return 0;
}Compile and run:
gcc -o quick_start quick_start.c spi_base.c
sudo ./quick_start # May require sudo for /dev/spidev accessint set_spi_mode(int fd, uint32_t mode);Set SPI communication mode.
Parameters:
fd- File descriptor of the SPI devicemode- SPI mode (0-3)
Returns: 0 on success, negative error code on failure
SPI Modes:
| Mode | CPOL | CPHA | Description |
|---|---|---|---|
| 0 | 0 | 0 | Clock idle low, data captured on rising edge |
| 1 | 0 | 1 | Clock idle low, data captured on falling edge |
| 2 | 1 | 0 | Clock idle high, data captured on falling edge |
| 3 | 1 | 1 | Clock idle high, data captured on rising edge |
int set_spi_bpw(int fd, uint8_t bpw);Set bits per word (data width).
Parameters:
fd- File descriptor of the SPI devicebpw- Bits per word (typically 8)
Returns: 0 on success, negative error code on failure
int set_spi_speed(int fd, uint32_t speed);Set SPI clock frequency in Hz.
Parameters:
fd- File descriptor of the SPI devicespeed- Speed in Hz (e.g., 500000 for 500 kHz)
Returns: 0 on success, negative error code on failure
Common speeds:
- 125000 (125 kHz) - Very slow, for long cables
- 500000 (500 kHz) - Default, reliable
- 1000000 (1 MHz) - Standard speed
- 10000000 (10 MHz) - High speed (check device specs)
void set_spi_delay(uint16_t usecs);Set delay between transfers in microseconds.
Parameters:
usecs- Delay in microseconds
Returns: Nothing (cannot fail)
int spi_trans(int fd, uint32_t len);Perform full-duplex SPI transfer using global tx[] and rx[] buffers.
Parameters:
fd- File descriptor of the SPI devicelen- Number of bytes to transfer (1-32)
Returns: 0 on success, -EINVAL if length invalid, -EIO on transfer failure
Usage:
tx[0] = 0xAA;
tx[1] = 0xBB;
spi_trans(fd, 2);
printf("Received: 0x%02X 0x%02X\n", rx[0], rx[1]);int spi_write(int fd, const void *buf, size_t n);Write data to SPI device (transmit only).
Parameters:
fd- File descriptor of the SPI devicebuf- Pointer to data buffern- Number of bytes to write (1-32)
Returns: 0 on success, negative error code on failure
Usage:
uint8_t data[] = {0x10, 0x20, 0x30};
spi_write(fd, data, 3);int spi_read(int fd, void *buf, size_t n);Read data from SPI device (receive only).
Parameters:
fd- File descriptor of the SPI devicebuf- Pointer to receive buffern- Number of bytes to read (1-32)
Returns: 0 on success, negative error code on failure
Usage:
uint8_t data[4];
spi_read(fd, data, 4);void pabort(const char *s);Print error message and exit the program. Use for critical errors only.
Parameters:
s- Error message string
Usage:
if (fd < 0)
pabort("Cannot open SPI device");void print_binary(unsigned int n, int bits);Print integer value in binary format (LSB first).
Parameters:
n- Value to printbits- Number of bits to display
Usage:
print_binary(0xA5, 8); // Prints: 10100101void spi_msg(const char *s, int len, uint8_t *buf);Print buffer contents in binary format with label.
Parameters:
s- Label stringlen- Buffer lengthbuf- Pointer to data buffer
extern uint8_t tx[TR_BUF_LEN]; // Transmit buffer (32 bytes)
extern uint8_t rx[TR_BUF_LEN]; // Receive buffer (32 bytes)
extern struct spi_ioc_transfer tr; // Transfer configuration
extern int verbose_spi; // Debug verbosity level (0-2)Constants:
TR_BUF_LEN- Maximum buffer size (32 bytes)SPEED- Default SPI speed (500000 Hz)
All functions return error codes instead of calling exit():
int ret = set_spi_mode(fd, 3);
if (ret < 0) {
fprintf(stderr, "Error: %d\n", ret);
// Handle error gracefully
}Common error codes:
0- Success-EINVAL- Invalid parameter (e.g., length > 32 bytes)-EIO- I/O error during transfer-errno- Other system errors (seeerrno.h)
int retry_transfer(int fd, int len, int max_retries) {
int ret;
for (int i = 0; i < max_retries; i++) {
ret = spi_trans(fd, len);
if (ret == 0)
return 0; // Success
fprintf(stderr, "Attempt %d failed, retrying...\n", i + 1);
usleep(10000); // Wait 10ms
}
return ret; // All retries failed
}fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0)
pabort("Cannot open SPI device - is SPI enabled?");int open_any_spi_device(void) {
const char *devices[] = {
"/dev/spidev0.0",
"/dev/spidev0.1",
"/dev/spidev1.0"
};
for (int i = 0; i < 3; i++) {
int fd = open(devices[i], O_RDWR);
if (fd >= 0) {
printf("Opened: %s\n", devices[i]);
// Test configuration
if (set_spi_mode(fd, 3) == 0 &&
set_spi_bpw(fd, 8) == 0 &&
set_spi_speed(fd, 500000) == 0) {
return fd; // Success
}
close(fd);
}
}
return -1; // No device available
}// Try mode 3, fallback to mode 1
int ret = set_spi_mode(fd, 3);
if (ret < 0) {
fprintf(stderr, "Mode 3 failed, trying mode 1...\n");
ret = set_spi_mode(fd, 1);
if (ret < 0)
pabort("Both modes failed");
}uint16_t read_adc_channel(int fd, uint8_t channel) {
if (channel > 7)
return 0;
// MCP3008 command format
tx[0] = 0x01; // Start bit
tx[1] = (0x80 | (channel << 4)); // Single-ended mode + channel
tx[2] = 0x00;
if (spi_trans(fd, 3) < 0)
return 0;
// Extract 10-bit result
uint16_t result = ((rx[1] & 0x03) << 8) | rx[2];
return result;
}For data larger than 32 bytes, split into chunks:
int transfer_large_data(int fd, uint8_t *data, size_t total_len) {
size_t offset = 0;
while (offset < total_len) {
size_t chunk_size = (total_len - offset > 32) ? 32 : (total_len - offset);
// Copy chunk to tx buffer
memcpy(tx, data + offset, chunk_size);
// Transfer chunk
int ret = spi_trans(fd, chunk_size);
if (ret < 0)
return ret;
// Copy received data if needed
memcpy(data + offset, rx, chunk_size);
offset += chunk_size;
}
return 0;
}SPI Buses:
- SPI0 (
/dev/spidev0.0,/dev/spidev0.1) - Recommended, all modes work - SPI1 (
/dev/spidev1.0,/dev/spidev1.1) - Modes 1 and 3 may not work reliably
Pin Mapping (SPI0):
| Signal | GPIO | Physical Pin |
|---|---|---|
| MOSI | 10 | 19 |
| MISO | 9 | 21 |
| SCLK | 11 | 23 |
| CE0 | 8 | 24 |
| CE1 | 7 | 26 |
| Device Type | Recommended Mode | Typical Speed |
|---|---|---|
| ADC (MCP3008, MCP3204) | Mode 0 or 3 | 1-2 MHz |
| DAC (MCP4921) | Mode 0 | 10-20 MHz |
| SD Card | Mode 0 | 400 kHz - 25 MHz |
| Flash Memory | Mode 0 or 3 | 1-50 MHz |
| RFID (RC522) | Mode 0 | 1-10 MHz |
- Keep wires short - Reduces noise and allows higher speeds
- Use ground plane - Connect all grounds together
- Add pull-up resistors - 10kΩ on MISO if device doesn't drive it
- Level shifting - Use for 5V devices with 3.3V Raspberry Pi
- Decoupling capacitors - 100nF close to device power pins
verbose_spi = 2; // Maximum verbosityVerbosity levels:
0- Silent (default, production use)1- Configuration info and errors2- Detailed transfer data in binary format
SPI mode: 3
SPI bits per word: 8
SPI speed [Hz]: 500000
Send data:
0. 10000000
1. 11000000
2. 00000000
Receive data:
0. 00000000
1. 00000011
2. 11111111
Problem: open() returns "Permission denied"
# Solution: Add user to spi group
sudo usermod -a -G spi $USER
# Or run with sudo (not recommended for development)Problem: set_spi_mode() returns -1
# Solution: Check if SPI is enabled
ls /dev/spidev*
# If no devices, enable SPI in raspi-configProblem: No data received (rx[] all zeros)
- Check wiring (MISO connection)
- Verify device is powered
- Ensure correct SPI mode
- Try lower speed
Problem: Garbage data received
- Check clock polarity/phase (try different modes)
- Reduce transfer speed
- Check for noise/interference
# Build example program
make example
# Run (may need sudo for device access)
sudo ./spi_example
# Clean
make clean-exampleSee Quick Start section above.
float read_max6675_temperature(int fd) {
// Read 16 bits from MAX6675
if (spi_trans(fd, 2) < 0)
return -1.0;
// Combine bytes (MSB first)
uint16_t raw = (rx[0] << 8) | rx[1];
// Check for errors
if (raw & 0x04)
return -1.0; // Thermocouple not connected
// Extract temperature (bits 15-3, resolution 0.25°C)
raw >>= 3;
return raw * 0.25;
}int init_spi_safe(const char *device, uint32_t speed) {
int fd = open(device, O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
// Try mode 3 first (common for ADCs)
if (set_spi_mode(fd, 3) < 0) {
fprintf(stderr, "Mode 3 failed, trying mode 0...\n");
if (set_spi_mode(fd, 0) < 0) {
close(fd);
return -1;
}
}
if (set_spi_bpw(fd, 8) < 0 || set_spi_speed(fd, speed) < 0) {
close(fd);
return -1;
}
return fd;
}- Buffer size - Fixed 32-byte limit per transfer (defined by
TR_BUF_LEN) - Thread safety - Global
tx[]/rx[]buffers are not thread-safe - Single-threaded design - Not suitable for concurrent SPI operations
- Platform - Linux-only (requires spidev kernel driver)
- Hardware quirks - Some mode combinations may not work on certain SPI buses (Raspberry Pi SPI1)
For larger transfers:
// Split into 32-byte chunks (see Advanced Usage section)For multi-threading:
// Use mutex to protect global buffers
pthread_mutex_t spi_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&spi_mutex);
tx[0] = 0xAA;
spi_trans(fd, 1);
result = rx[0];
pthread_mutex_unlock(&spi_mutex);For thread-safe design:
// Future enhancement: Add reentrant functions
// int spi_trans_r(int fd, uint8_t *tx_buf, uint8_t *rx_buf, uint32_t len);- Breaking change: Functions now return error codes instead of calling
exit() - Improved error handling and validation
- Better support for production code
- Enhanced security with buffer overflow checks
- Improved error diagnostics
- Added
spi_write()andspi_read()functions - Debug output improvements
- Initial release
- Basic SPI communication functionality
Contributions are welcome! Areas for improvement:
- Thread-safe API - Add reentrant functions with explicit buffers
- Dynamic buffers - Support for transfers > 32 bytes
- Context-based design - Eliminate global state
- More examples - Common sensors and devices
- Unit tests - Automated testing framework
Please submit issues and pull requests via the project repository.
This library is provided as-is for educational and commercial use. Check the LICENSE file for details.
For issues, questions, or contributions:
- Create an issue in the project repository
- Check existing documentation and examples
- Review the
example.cfile for usage patterns
Author: See git history Last Updated: 2025-10-01 Version: 2.0