From 1449ec643f98e0a15fffa8938ce3a2083da8d498 Mon Sep 17 00:00:00 2001 From: Christopher Vagnetoft Date: Thu, 19 Jun 2014 12:27:31 +0200 Subject: [PATCH] cleaned up gpio and device code, implemented dummy/dry run mode --- composer.lock | 4 +- examples/pcd8544.php | 24 +++ lib/Device/Device.php | 91 ++++++++---- lib/Device/Display/Pcd8544Device.php | 78 +++++++++- lib/DummyGpioPin.php | 209 +++++++++++++++++++++++++++ lib/Gpio.php | 86 ++++++----- lib/GpioPin.php | 2 +- lib/GpioWatcher.php | 71 +++++++++ 8 files changed, 481 insertions(+), 84 deletions(-) create mode 100644 examples/pcd8544.php create mode 100644 lib/DummyGpioPin.php create mode 100644 lib/GpioWatcher.php diff --git a/composer.lock b/composer.lock index 39d701d..8bff2a6 100644 --- a/composer.lock +++ b/composer.lock @@ -15,7 +15,7 @@ "source": { "type": "git", "url": "http://satis.noccylabs.info/packages/php-crap.git", - "reference": "33d3929fb5fab4056ea9993b8d148aa3ba44e4c3" + "reference": "aede74280244c61564d648139f5fa2318834471e" }, "type": "library", "extra": { @@ -38,7 +38,7 @@ } ], "description": "Crap is an exception/error/assertion handling library", - "time": "2014-06-14 01:11:31" + "time": "2014-06-18 19:21:45" }, { "name": "noccylabs/sansi", diff --git a/examples/pcd8544.php b/examples/pcd8544.php new file mode 100644 index 0000000..773cd69 --- /dev/null +++ b/examples/pcd8544.php @@ -0,0 +1,24 @@ +setName("pcd8544_1") + ->setPin("sda", $gpio[0]) + ->setPin("scl", $gpio[1]) + ->setPin("res", $gpio[2]) + ->setPin("sce", $gpio[3]) + ->setPin("dc", $gpio[4]) + ->initialize() + ; + + diff --git a/lib/Device/Device.php b/lib/Device/Device.php index b65a348..cb91558 100644 --- a/lib/Device/Device.php +++ b/lib/Device/Device.php @@ -18,16 +18,22 @@ */ namespace NoccyLabs\Gpio\Device; +use NoccyLabs\Gpio\GpioPin; -abstract class Device implements GpioAwareInterface +abstract class Device { protected $name; - protected $gpio; + protected $description; protected $pins = array(); + public function __construct() + { + $this->configure(); + } + protected function configure() { // call on ->addGpioPin etc here @@ -44,39 +50,33 @@ abstract class Device implements GpioAwareInterface } public function setName($name) - {} + { + $this->name = $name; + return $this; + } public function getName() - {} + { + return $this->name; + } public function setDescription($description) - {} + { + $this->description = $description; + return $this; + } public function getDescription() - {} - - /** - * Allocate a GPIO (as defined in hardware) pin to the device. - * - */ - public function addGpioPin($gpio, $name, $description=null) { - $pin = new GpioPin($gpio); - $pin->setLabel($description); - $this->pins[$name] = $pin; + return $this->description; + } + + public function addPin($name, $description=null) + { + $this->pins[$name] = null; + return $this; } - /** - * Allocate a logical (wiring-pi 0-based pin number) pin to the device. - * - */ - public function addLogicalPin($logical, $name, $description=null) - { - $pin = $this->gpio[$logical]; - $pin->setLabel($description); - $this->pins[$name] = $pin; - } - /** * Get all allocated pins * @@ -94,20 +94,49 @@ abstract class Device implements GpioAwareInterface { return $this->pins[$name]; } + + public function setPin($pin_name, $pin) + { + if (array_key_exists($pin_name, $this->pins)) { + $pin->setLabel($this->name.".".$pin_name); + $this->pins[$pin_name] = $pin; + return $this; + } + throw new \Exception(); + } /** * Get a pin as a property from its name * */ - protected function __get($pin_name) + public function __get($pin_name) { return $this->getPin($pin_name); } - public function delayMillis($ms) - {} + public function __set($pin_name, GpioPin $pin) + { + $this->setPin($pin_name, $pin); + } - public function delayMicros($us) - {} + protected function delayMillis($ms) + { + usleep($ms*1000); + } + + protected function delayMicros($us) + { + usleep($us); + } + + protected function shiftOut(GpioPin $pdata, GpioPin $pclk, $byte) + { + for ($bit = 0; $bit < 8; $bit++) { + $bval = 1<<$bit; + $pclk->setValue(0); + $pdata->setValue(($byte & $bval) == $bval); + $pclk->setValue(1); + } + } } diff --git a/lib/Device/Display/Pcd8544Device.php b/lib/Device/Display/Pcd8544Device.php index ee4daee..6396ece 100644 --- a/lib/Device/Display/Pcd8544Device.php +++ b/lib/Device/Display/Pcd8544Device.php @@ -28,12 +28,12 @@ class Pcd8544Device extends Device $this ->setName("pcd8544") ->setDescription("Philips PCD8544 LCD Display Driver") - ->addLogicalPin(0, "dc", "data/command") - ->addLogicalPin(1, "sce", "chip select") - ->addLogicalPin(2, "scl", "clock") - ->addLogicalPin(3, "sda", "data") - ->addLogicalPin(4, "res", "reset") - ->addLogicalPin(9, "bl", "backlight") + ->addPin("dc", "data/command") + ->addPin("sce", "chip select") + ->addPin("scl", "clock") + ->addPin("sda", "data") + ->addPin("res", "reset") + ->addPin("bl", "backlight") ; } @@ -46,4 +46,70 @@ class Pcd8544Device extends Device { $this->bl->setValue((bool)$state); } + + public function initialize() + { + $this->lcdInit(); + } + + public function lcdSend($byte, $command=false) + { + // assume clk is hi + // Enable display controller (active low). + $this->sce->setValue(0); + + $this->dc->setValue((int)$command); // command or data + + $this->shiftOut($this->sda, $this->scl, $byte); + + // Disable display controller. + $this->sce->setValue(1); + +/* + if (!$command) { + $this->cx++; + if ($this->cx > (LCD_X_RES - 1)) { + cx = 0; cy++; + #ifdef PCD8544_FIX_YALIGN + // Soft wrapping when FIX_YALIGN is defined + lcd_cursor(cx,cy); + #endif + } + } +*/ + } + + public function lcdInit() + { + $this->sce->setDirection("out"); + $this->res->setDirection("out"); + $this->dc->setDirection("out"); + $this->scl->setDirection("out"); + $this->sda->setDirection("out"); + + $this->res->setValue(1); // set RES + $this->sce->setValue(0); // reset SCE + $this->res->setValue(0); // pull RES low + $this->res->setValue(0); // and back hick + + + // Send sequence of command + $this->lcdSend( 0x21, true ); // LCD Extended Commands. + // lcd_send( 0xC8, true ); // Set LCD Vop (Contrast). + $this->lcdSend( 0x80 | 0x70, true ); // Set LCD Vop (Contrast). + $this->lcdSend( 0x06, true ); // Set Temp coefficent to 2. + $this->lcdSend( 0x13, true ); // LCD bias mode 1:100. + #ifdef PCD8544_FIX_YALIGN + $this->lcdSend( 0x45, true ); // LCD blank - Shift LCD 5 up (row starts at 1) + #endif + $this->lcdSend( 0x20, true ); // LCD Standard Commands, Horizontal addressing mode. + $this->lcdSend( 0x40, true ); // LCD blank + $this->lcdSend( 0x08, true ); // LCD blank + $this->lcdSend( 0x0C, true ); // LCD in inverse mode. + + //$this->lcdClear(); + + } + } + diff --git a/lib/DummyGpioPin.php b/lib/DummyGpioPin.php new file mode 100644 index 0000000..38428b9 --- /dev/null +++ b/lib/DummyGpioPin.php @@ -0,0 +1,209 @@ + + */ + +namespace NoccyLabs\Gpio; + +use NoccyLabs\Gpio\Exception\HardwareException; + +use NoccyLabs\Sansi\Charset as CS; + +/** + * GPIO pin implementation. This class wraps all interaction with a GPIO pin, + * thus decoupling it from the main Gpio class. + * + */ +class DummyGpioPin extends GpioPin +{ + public function getPin() + { + return $this->pin; + } + + private function findHardware() + { + return "dummy"; + } + + public function __destruct() + { + } + + public function setDirection($direction) + { + if (!in_array($direction, array("in", "out"))) { + throw new \Exception; + } + $this->direction = $direction; + $this->sysfsWrite($this->pin, "direction", $direction); + return $this; + } + + public function getDirection() + { + return $this->sysfsRead($this->pin, "direction"); + } + + public function setValue($value) + { + $this->value = (bool)$value; + $this->sysfsWrite("{$this->pin}", "value", (int)$this->value); + return $this; + } + + public function getValue() + { + return $this->sysfsRead($this->pin, "value"); + } + + /** + * Set a descriptive label for the pin, used for debugging and troubleshooting. + * + * @param string $label + * @return NoccyLabs\Gpio\GpioPin + */ + public function setLabel($label) + { + $this->label = (string)$label; + return $this; + } + + /** + * Get the assigned label for this pin. + * + * @return string The label + */ + public function getLabel() + { + return $this->label; + } + + public function export() + { + if (file_exists("/sys/class/gpio/gpio{$this->pin}")) { + return $this; + } + $this->sysfsWrite(null, "export", $this->pin); + if (!file_exists("/sys/class/gpio/gpio{$this->pin}")) { + throw new HardwareException("Unable to export pin {$this->pin}"); + } + $this->hardware = $this->findHardware(); + return $this; + } + + public function unexport() + { + $this->sysfsWrite(null, "unexport", $this->pin); + return $this; + } + + public function sysfsWrite($pin, $file, $value) + { + if ($pin!==null) { + $path = "/sys/class/gpio/gpio{$pin}/{$file}"; + } else { + $path = "/sys/class/gpio/{$file}"; + } + printf("%16.4f \e[32;1m%-5s\e[0m \e[1m%-15s\e[0m (\e[33m%s\e[0m) => \e[36;1m%-10s\e[0m \n", + microtime(true), + "write", + $this->label, + $file, + $value + ); + } + + public function sysfsRead($pin, $file) + { + $path = "/sys/class/gpio/gpio{$pin}/{$file}"; + printf("%16.4f \e[31;1m%-5s\e[0m \e[1m%-15s\e[0m (\e[33m%s\e[0m)\n", + microtime(true), + "read", + $this->label, + $file + ); + return 0; + } + + public function setEdge($edge) + { + if (!in_array($edge, array("rising", "falling", "both", "none"))) { + throw new \Exception; + } + $this->edge = $edge; + $this->sysfsWrite($this->pin, "edge", $edge); + return $this; + } + + public function getEdge() + { + return $this->sysfsRead($this->pin, "edge"); + } + + public function setHandler(callable $handler=null) + { + $this->gpio->setInterruptHandler($this->pin, $this->fd); + return $this; + } + + public function doInterrupt() + { + fseek($this->fd,0,SEEK_SET); + $val = fgets($this->fd); + } + + public function dumpStatus($ansi=false) + { + if ($ansi) { + $status = "\e[44;37;1m GPIO{$this->pin} \e[0m\n"; + $direction = + ($this->direction=="input"?(CS::chr(0x2190)):(CS::chr(0x2192))). + " ".$this->direction; + $edge = $this->edge; //""; + /*if ($this->edge == "rising") { + $edge.= CS::chr(0x21A5)." rising"; + } elseif ($this->edge == "falling") { + $edge.= CS::chr(0x21A7)." falling"; + } else { + $edge.= "\e[0m".$this->edge; + }*/ + } else { + $status = "********** GPIO{$this->pin} **********\n"; + $direction = $this->direction; + } + + + foreach(array( + "Direction" => $this->getDirection(), + "Value" => ($this->getValue()?1:0), + "Edge" => $this->getEdge(), + "Label" => $this->label, + "Hardware" => $this->hardware, + "Int count" => 0 + ) as $k=>$v) { + if ($ansi) { + $status.= sprintf(" \e[32;1m%10s\e[0m: \e[0m%s\e[0m\n", $k, $v); + } else { + $status.= sprintf(" %-10s: %s\n", $k, $v); + } + } + error_log($status); + return $this; + } + +} diff --git a/lib/Gpio.php b/lib/Gpio.php index a94a690..21586c5 100644 --- a/lib/Gpio.php +++ b/lib/Gpio.php @@ -40,9 +40,11 @@ class Gpio implements \ArrayAccess /** @var NoccyLabs\Gpio\GpioMapperInterface */ protected $mapper; - public function __construct($force=false) + protected $dummy; + + public function __construct($dummy=false) { - if (!$force) { + if (!$dummy) { if (!file_exists("/sys/class/gpio")) { throw new HardwareException("gpio sysfs is not available"); } @@ -50,44 +52,7 @@ class Gpio implements \ArrayAccess throw new HardwareException("gpio sysfs is not writable"); } } - } - - public function refresh() - { - $read = $this->fd_gpio; - $write = array(); - $except = $read; - if (count($read)>0) { - stream_select($read, $write, $except, 0); - foreach($except as $fd) { - $pin = $this->getPinFromFd($fd); - $pin->doInterrupt(); - - } - } - } - - public function getPinFromFd($fd) - { - foreach($this->fd_pins as $pinfd=>$pin) { - if ($pinfd == $fd) { return $pin; } - } - return false; - } - - protected $fd_gpio = array(); - protected $fd_pins = array(); - - public function enableInterrupt(GpioPin $pin, $fd) - { - $this->fd_gpio[] = $fd; - - $this->fd_pins[$fd] = $pin; - - } - - public function disableInterrupt(GpioPin $pin) - { + $this->dummy = $dummy; } public function setMapper(GpioMapperInterface $mapper=null) @@ -96,26 +61,59 @@ class Gpio implements \ArrayAccess return $this; } + /** + * Get a pin, optionally via previously specified mapper. + * + * @interface ArrayAccess + * @param int + */ public function offsetGet($index) { - if ($this->mapper) { $index = $this->mapper->mapLogicalToGpioPin($index); } + if ($this->mapper) { + $index = $this->mapper->mapLogicalToGpioPin($index); + } if (empty($this->gpio[$index])) { - $this->gpio[$index] = new GpioPin($index, $this); + if ($this->dummy) { + $gpio = new DummyGpioPin($index, $this); + } else { + $gpio = new GpioPin($index, $this); + } + $this->gpio[$index] = $gpio; } return $this->gpio[$index]; } - + + /** + * Check if a GPIO is exported + * + * @interface ArrayAccess + * @param int + */ public function offsetExists($index) { return array_key_exists($index, $this->gpio); } - + /** + * Not callable, get the requested pin via offsetGet() instead. + * + * @interface ArrayAccess + * @throws Exception + * @param int + * @param mixed + */ public function offsetSet($index,$value) { throw new \Exception(); } + /** + * Unlink and unexport an exported GPIO + * + * @interface ArrayAccess + * @param int + */ public function offsetUnset($index) { + $this->gpio[$index]->unexport(); unset($this->gpio[$index]); } diff --git a/lib/GpioPin.php b/lib/GpioPin.php index d1fb257..79fbed8 100644 --- a/lib/GpioPin.php +++ b/lib/GpioPin.php @@ -154,7 +154,7 @@ class GpioPin public function sysfsWrite($pin, $file, $value) { - if ($pin) { + if ($pin!==null) { $path = "/sys/class/gpio/gpio{$pin}/{$file}"; } else { $path = "/sys/class/gpio/{$file}"; diff --git a/lib/GpioWatcher.php b/lib/GpioWatcher.php new file mode 100644 index 0000000..93c387e --- /dev/null +++ b/lib/GpioWatcher.php @@ -0,0 +1,71 @@ + + */ + +namespace NoccyLabs\Gpio; + +use NoccyLabs\Gpio\Exception\HardwareException; + +/** + * Watch GPIO for interrupts (hardware or software) + * + * + * + */ +class GpioWatcher +{ + protected $fd_gpio = array(); + protected $fd_pins = array(); + + public function refresh() + { + $read = $this->fd_gpio; + $write = array(); + $except = $read; + if (count($read)>0) { + stream_select($read, $write, $except, 0); + foreach($except as $fd) { + $pin = $this->getPinFromFd($fd); + $pin->doInterrupt(); + + } + } + } + + public function getPinFromFd($fd) + { + foreach($this->fd_pins as $pinfd=>$pin) { + if ($pinfd == $fd) { return $pin; } + } + return false; + } + + public function enableInterrupt(GpioPin $pin, $fd) + { + $this->fd_gpio[] = $fd; + + $this->fd_pins[$fd] = $pin; + + } + + public function disableInterrupt(GpioPin $pin) + { + } + + +}