path: root/gpio.scm
diff options
Diffstat (limited to 'gpio.scm')
1 files changed, 176 insertions, 0 deletions
diff --git a/gpio.scm b/gpio.scm
new file mode 100644
index 0000000..ca51064
--- /dev/null
+++ b/gpio.scm
@@ -0,0 +1,176 @@
+;;; guile-gpio --- Guile interface to the Linux GPIO Sysfs API
+;;; Copyright © 2016 David Thompson <>
+;;; This file is part of guile-gpio.
+;;; Guile-gpio is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU Lesser General Public License as
+;;; published by the Free Software Foundation; either version 3 of the
+;;; License, or (at your option) any later version.
+;;; Guile-gpio is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; General Public License for more details.
+;;; You should have received a copy of the GNU Lesser General Public
+;;; License along with guile-gpio. If not, see
+;;; <>.
+;;; Commentary:
+;; Wrapper over the Linux GPIO Sysfs API.
+;;; Code:
+(define-module (gpio)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-9)
+ #:export (make-pin
+ pin?
+ interrupt-generating-pin?
+ pin-number
+ pin-exported?
+ pin-export!
+ pin-unexport!
+ pin-high?
+ pin-low?
+ pin-set!
+ pin-edge
+ set-pin-edge!
+ pin-direction
+ set-pin-direction!
+ call-with-pin
+ with-pins))
+(define-record-type <pin>
+ (make-pin number)
+ pin?
+ (number pin-number))
+(define %gpio-sysfs "/sys/class/gpio")
+(define (read-boolean port)
+ "Read boolean value, as specified by the GPIO Sysfs API, from PORT.
+The value is represented as a single character: \"1\" for true and
+\"0\" for false, naturally."
+ (eqv? (read-char port) #\1))
+(define (pin-directory pin)
+ "Return the directory name associated with PIN in the GPIO Sysfs
+ (string-append %gpio-sysfs "/gpio" (number->string (pin-number pin))))
+(define (pin-value-file pin)
+ "Return the file name where the value of PIN is stored."
+ (string-append (pin-directory pin) "/value"))
+(define (pin-edge-file pin)
+ "Return the file name where the edge of PIN is stored."
+ (string-append (pin-directory pin) "/edge"))
+(define (pin-direction-file pin)
+ "Return the file name where the direction of PIN is stored."
+ (string-append (pin-directory pin) "/direction"))
+(define (pin-exported? pin)
+ "Return #t if PIN has been exported, meaning that it can be
+manipulated in userspace."
+ (file-exists? (pin-directory pin)))
+(define pin-export!
+ (let ((export-file (string-append %gpio-sysfs "/export")))
+ (lambda (pin)
+ "Export PIN so that it may be manipulated in userspace."
+ (call-with-output-file export-file
+ (lambda (port)
+ (display (number->string (pin-number pin)) port))))))
+(define pin-unexport!
+ (let ((unexport-file (string-append %gpio-sysfs "/unexport")))
+ (lambda (pin)
+ "Relinquish userspace control of PIN."
+ (call-with-output-file unexport-file
+ (lambda (port)
+ (display (number->string (pin-number pin)) port))))))
+(define (pin-high? pin)
+ "Return if PIN is set to a high value."
+ (call-with-input-file (pin-value-file pin) read-boolean))
+(define (pin-low? pin)
+ "Return #t if PIN is set to a low value."
+ (not (pin-high? pin)))
+(define (pin-set! pin high?)
+ "Set PIN to a high value if HIGH?, otherwise set PIN to a low
+ (call-with-output-file (pin-value-file pin)
+ (lambda (port)
+ (display (if high? "1" "0") port))))
+(define (interrupt-generating-pin? pin)
+ "Return #t if PIN is capable of generating interrupts."
+ (file-exists? (pin-edge-file pin)))
+(define (pin-edge pin)
+ "Return the signal edges of PIN that trigger interrupts. The edge
+type may be one of four possible values: none, rising, falling, or
+ (call-with-input-file (pin-edge-file pin) read))
+(define (set-pin-edge! pin edge)
+ "Set the signal EDGE type for PIN that will trigger an interrupt.
+The edge type may be one of four possible values: none, rising,
+falling, or both. PIN must be an input pin."
+ (match edge
+ ((or 'none 'rising 'falling 'both)
+ (call-with-output-file (pin-edge-file pin)
+ (lambda (port)
+ (write edge port))))))
+(define (pin-direction pin)
+ "Return the direction of PIN, either 'in' or 'out'."
+ (call-with-input-file (pin-direction-file pin) read))
+(define (set-pin-direction! pin direction)
+ "Set the direction of PIN to DIRECTION, either 'in' or 'out'."
+ (match direction
+ ((or 'in 'out)
+ (call-with-output-file (pin-direction-file pin)
+ (lambda (port)
+ (write direction port))))))
+(define* (call-with-pin number proc #:key direction edge)
+ "Import GPIO pin NUMBER and apply PROC with an object representing
+that pin. When PROC exits, the pin is unexported. If DIRECTION is
+specified, set the pin to that direction, otherwise use the system
+default. If EDGE is specified, set the pin to that edge, otherwise
+use the system default. Only interrupt generating input pins may
+specify a value for EDGE."
+ (let ((pin (make-pin number)))
+ ;; Try to export before entering the dynamic-wind. This way, if
+ ;; the pin was already exported it won't be automatically
+ ;; unexported, potentially screwing up some other process that was
+ ;; using it.
+ (pin-export! pin)
+ (dynamic-wind
+ (const #t)
+ (lambda ()
+ ;; It's important to set the direction first, because only
+ ;; input pins may set their edge.
+ (when direction (set-pin-direction! pin direction))
+ (when edge (set-pin-edge! pin edge))
+ (proc pin))
+ (lambda ()
+ (pin-unexport! pin)))))
+(define-syntax with-pins
+ (syntax-rules ()
+ ((_ () body ...)
+ (begin body ...))
+ ((_ ((name number kwargs ...) . rest) body ...)
+ (call-with-pin number
+ (lambda (name)
+ (with-pins rest body ...))
+ kwargs ...))))