GitHub

An OCaml library for interfacing with Raspberry Pi hardware using Linux APIs. Includes GPIO, SPI, I2C, and drivers for various display and sensor modules.

  • GPIO Character Device API - Uses /dev/gpiochipN (not deprecated sysfs)
  • SPI Communication - Hardware SPI support via /dev/spidev
  • I2C Communication - I2C bus support for sensors and peripherals
  • Hardware Drivers:
    • Waveshare 2.13" ePaper Display V4 (with partial update support)
    • MAX7219 LED matrix display (chainable)
    • LCD2004 20x4 character display with I2C backpack
    • DS3231 Real-Time Clock
    • AT24C32 I2C EEPROM
  • Graphics Libraries:
    • 1-bit framebuffer with rotation support (0°, 90°, 180°, 270°)
    • 5×7 ASCII bitmap font
    • Drawing primitives (lines, rectangles, circles)
  • Pure OCaml with ctypes - No C code, uses OCaml FFI
  • 32-bit and 64-bit compatible

Requirements

opam install ctypes ctypes-foreign dune

Building

opam exec -- dune build

Examples

ePaper Display Graphics

Demo of graphics capabilities on ePaper display:

sudo opam exec -- dune exec test/epaper_graphics.exe

Shows text, lines, rectangles, circles, and complex scenes.

MAX7219 LED Matrix Display

Drive multiple chained 8×8 LED matrices:

sudo opam exec -- dune exec test/test_max7219.exe

Real-Time Clock

DS3231 I2C RTC with temperature sensor:

sudo opam exec -- dune exec test/test_rtc.exe

I2C EEPROM

AT24C32 EEPROM read/write:

sudo opam exec -- dune exec test/test_eeprom.exe

Combined Clock Display

MAX7219 display showing time from DS3231 RTC:

sudo opam exec -- dune exec test/clock.exe

Basic GPIO

LED blinking, button reading, PWM:

sudo opam exec -- dune exec test/test_led.exe
sudo opam exec -- dune exec test/test_button.exe
sudo opam exec -- dune exec test/test_pwm.exe

I2C Bus Scanner

Scan for I2C devices on the bus:

sudo opam exec -- dune exec test/i2c_scan.exe

LCD2004 Display

20x4 character LCD with I2C backpack:

sudo opam exec -- dune exec test/test_lcd2004.exe
sudo opam exec -- dune exec test/seven_seg_clock.exe

Library Modules

GPIO (lib/gpio.ml)

Modern character device API for GPIO control:

let chip = Gpio.open_chip () in  (* Auto-detects GPIO chip *)
let led = Gpio.setup_output chip 17 in
Gpio.write led true;
Gpio.release_line led;
Gpio.close_chip chip

SPI (lib/spi.ml)

Hardware SPI communication:

let spi = Spi.open_device ~speed_hz:1000000 "/dev/spidev0.0" in
Spi.write_bytes spi [0x42; 0xFF];
Spi.close_device spi

I2C (lib/i2c.ml)

I2C bus communication:

let fd = I2c.open_device 1 in  (* Open /dev/i2c-1 *)
I2c.set_slave_address fd 0x68;
I2c.write_bytes fd [0x00; 0x45];
let data = I2c.read_bytes fd 7 in
I2c.close_device fd

ePaper Display (lib/epaper.ml, lib/epd2in13_v4.ml)

Waveshare 2.13" ePaper display with partial update support:

let display = Epaper.init () in
Epd2in13_v4.init display;
(* Full refresh *)
Epd2in13_v4.display display buffer;
(* Set base image for partial updates *)
Epd2in13_v4.display_partial_base display buffer;
(* Partial updates (only changed pixels) *)
Epd2in13_v4.display_partial display buffer;

Framebuffer Graphics (lib/framebuffer.ml)

1-bit graphics with rotation support (0°, 90°, 180°, 270°):

(* Create 250×122 framebuffer with 90° rotation *)
let fb = Framebuffer.create ~rotation:90 250 122 0xFF in
(* Drawing primitives *)
Framebuffer.draw_line fb 0 0 100 100 true;
Framebuffer.draw_circle fb 50 50 20 true;
Framebuffer.draw_filled_rect fb 10 10 30 40 true;
(* Get rotated buffer for display *)
let buffer = Framebuffer.to_list_rotated fb in

Font Rendering (lib/font.ml)

5×7 ASCII bitmap font:

let width = Font.draw_string fb 10 10 "Hello, World!" true in
let char_width = Font.draw_char fb x y 'A' true in

MAX7219 (lib/max7219.ml)

LED matrix display driver with chaining:

let display = Max7219.init "/dev/spidev0.0" 4 in  (* 4 daisy-chained displays *)
Max7219.set_intensity display 5;
Max7219.set_row display 0 3 0x7F;  (* device 0, row 3 *)
Max7219.clear_all display;
Max7219.close display;

DS3231 RTC (lib/ds3231.ml)

Real-time clock with temperature sensor:

let rtc = Ds3231.open_device 1 in  (* Open on I2C bus 1 *)
(* Read date/time *)
let dt = Ds3231.read_datetime rtc in
Printf.printf "%02d:%02d:%02d\n" dt.hours dt.minutes dt.seconds;
(* Write date/time *)
let dt = { year = 2025; month = 1; date = 15; day = 3;
           hours = 14; minutes = 30; seconds = 0 } in
Ds3231.write_datetime rtc dt;
(* Read temperature *)
let temp = Ds3231.read_temperature rtc in
Printf.printf "Temperature: %.2f°C\n" temp;
Ds3231.close_device rtc;

AT24C32 EEPROM (lib/at24c32.ml)

I2C EEPROM with page writing:

let eeprom = At24c32.open_device 1 0x57 in  (* Bus 1, address 0x57 *)
(* Single byte operations *)
At24c32.write_byte eeprom 0x0000 0x42;
let value = At24c32.read_byte eeprom 0x0000 in
(* Multi-byte operations *)
At24c32.write_bytes eeprom 0x0010 [0x01; 0x02; 0x03];
let data = At24c32.read_bytes eeprom 0x0010 3 in
(* String operations *)
At24c32.write_string eeprom 0x0100 "Hello, EEPROM!";
let str = At24c32.read_string eeprom 0x0100 64 in
At24c32.close_device eeprom;

Hardware Connections

ePaper Display (Waveshare 2.13" V4)

  • VCC → 3.3V
  • GND → Ground
  • DIN → SPI0 MOSI (GPIO 10)
  • CLK → SPI0 SCLK (GPIO 11)
  • CS → SPI0 CE0 (GPIO 8) - or any GPIO if using software CS
  • DC → GPIO 25
  • RST → GPIO 17
  • BUSY → GPIO 24
  • PWR → GPIO 18 (optional)

Note: When using hardware SPI CS, the CS pin is automatically managed by the SPI driver.

MAX7219 LED Matrix

  • VCC → 5V
  • GND → Ground
  • DIN → SPI0 MOSI (GPIO 10)
  • CLK → SPI0 SCLK (GPIO 11)
  • CS → SPI0 CE0 (GPIO 8)

DS3231 RTC

  • VCC → 3.3V or 5V
  • GND → Ground
  • SDA → I2C1 SDA (GPIO 2)
  • SCL → I2C1 SCL (GPIO 3)

AT24C32 EEPROM

  • VCC → 3.3V or 5V
  • GND → Ground
  • SDA → I2C1 SDA (GPIO 2)
  • SCL → I2C1 SCL (GPIO 3)
  • WP → Ground (write enable)
  • A0, A1, A2 → Ground (address pins)

Enabling SPI and I2C

# Enable interfaces using raspi-config
sudo raspi-config
# Interface Options → SPI → Enable
# Interface Options → I2C → Enable
# Reboot
sudo reboot
# Verify devices exist
ls /dev/spidev* /dev/i2c*

Permissions

GPIO, SPI, and I2C require appropriate permissions:

# Add user to gpio, spi, i2c groups
sudo usermod -a -G gpio,spi,i2c $USER
# Log out and back in for changes to take effect
# Or run with sudo
sudo opam exec -- dune exec test/test_max7219.exe

Technical Details

GPIO Character Device API

Uses modern /dev/gpiochipN interface with ioctl calls:

  • GPIO_V2_GET_LINE_IOCTL - Request GPIO lines
  • GPIO_V2_LINE_SET_VALUES_IOCTL - Set output values
  • GPIO_V2_LINE_GET_VALUES_IOCTL - Read input values

All ioctl constants use int32 for 32-bit compatibility.

ePaper Partial Updates

The display has two RAM buffers (0x24 and 0x26) for hardware pixel comparison:

  • Set base image → writes to both buffers
  • Partial update → controller compares and updates only changed pixels
  • Results in flicker-free updates (ideal for clocks, status displays)

Framebuffer Rotation

Supports 0°, 90°, 180°, 270° rotation:

  • Rotation applied when converting buffer to display format
  • Allows landscape/portrait modes without redrawing logic
  • Useful for physical mounting orientations

32-bit Compatibility

All ioctl constants are defined as int32 to work on both 32-bit and 64-bit systems. On 32-bit OCaml, the int type is only 31 bits, so large ioctl values like 0xC250B407 require int32.

Project Structure

gpio/
├── lib/
│   ├── ioctl.ml          - Low-level ioctl wrapper
│   ├── gpio.ml           - GPIO character device API
│   ├── spi.ml            - SPI communication
│   ├── i2c.ml            - I2C communication
│   ├── epaper.ml         - Generic ePaper driver
│   ├── epd2in13_v4.ml    - Waveshare 2.13" V4 specific
│   ├── max7219.ml        - MAX7219 LED matrix
│   ├── ds3231.ml         - DS3231 RTC
│   ├── at24c32.ml        - AT24C32 EEPROM
│   ├── framebuffer.ml    - Graphics primitives
│   ├── font.ml           - Bitmap font rendering
│   └── dune              - Library definitions
├── test/
│   ├── test_max7219.ml   - LED matrix demo
│   ├── clock.ml          - RTC + LED matrix clock
│   ├── test_rtc.ml       - RTC test
│   ├── test_eeprom.ml    - EEPROM test
│   ├── epaper_graphics.ml - Graphics demo
│   ├── test_epaper.ml    - ePaper test
│   ├── test_led.ml       - LED blink
│   ├── test_button.ml    - Button input
│   ├── test_pwm.ml       - Software PWM
│   ├── test_lcd2004.ml   - LCD2004 display test
│   ├── seven_seg_clock.ml - 7-segment clock on LCD
│   ├── i2c_scan.ml       - I2C bus scanner
│   └── dune              - Test executables
└── dune-project          - Project configuration

Read the original on github.com ↗