瀏覽代碼

iot core. Initial import.

woollybah 5 年之前
父節點
當前提交
21517b4289

+ 24 - 0
LICENSE

@@ -0,0 +1,24 @@
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+Copyright (c) 2019 Bruce A Henderson
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 9 - 1
README.md

@@ -1,2 +1,10 @@
 # iot.mod
 # iot.mod
-Suite of Iot modules for the Raspberry Pi
+Provides a suite of modules for the Raspberry Pi, easing the creation of applications which interact with different sensors, input devices and displays.
+
+The IoT modules are based on the .NET Core IoT libraries, and simplify access to the GPIO pins, I2C and SPI interfaces.
+
+
+
+### License
+
+`iot.mod` is licensed under the MIT license.

+ 340 - 0
core.mod/common.bmx

@@ -0,0 +1,340 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import pub.stdc
+Import brl.event
+Import "glue.c"
+
+Rem
+bbdoc: Pin modes supported by the GPIO controllers and drivers.
+End Rem
+Enum EPinMode
+	Input
+	Output
+	InputPullDown
+	InputPullUp
+End Enum
+
+Rem
+bbdoc: Different numbering schemes supported by GPIO controllers and drivers.
+End Rem
+Enum EPinNumberingScheme
+	Logical
+	Board
+End Enum
+
+Rem
+bbdoc: Event types that can be triggered by the GPIO.
+End Rem
+Enum EPinEventTypes Flags
+	None = 0
+	Rising = 1
+	Falling = 2
+End Enum
+
+Rem
+bbdoc: Represents a value for a pin.
+End Rem
+Enum EpinValue
+	Low = 0
+	High = 1
+End Enum
+
+Rem
+bbdoc: A motion started event.
+End Rem
+Global MOTION_START_EVENT:Int = AllocUserEventId("Motion started")
+Rem
+bbdoc: A motion stopped event.
+End Rem
+Global MOTION_STOP_EVENT:Int = AllocUserEventId("Motion stopped")
+
+
+Function defaultcomparator_compare:Int(a:EpinValue, b:EpinValue )
+	Return a.Ordinal() - b.Ordinal()
+End Function
+
+Type TIntArrayList
+
+	Field data:Int[0]
+	Field size:Int
+	
+	Method New(initialCapacity:Int)
+		data = New Int[initialCapacity]
+	End Method
+	
+	Method Add(value:Int)
+		CheckAndResize()
+		data[size] = value
+		size :+ 1
+	End Method
+
+	Method CheckAndResize()
+		If size = data.length Then
+			Local newSize:Int = size + 1 + size * (2/3.0)
+			data = data[..newSize]
+		End If
+	End Method
+	
+	Method Operator[]:Int(index:Int)
+		Return data[index]
+	End Method
+	
+	Method Remove(value:Int)
+		For Local i:Int = 0 Until size
+			If data[i] = value Then
+				data = data[..i] + data[i+1..]
+				Exit
+			End If
+		Next
+	End Method
+	
+End Type
+
+Rem
+bbdoc: General Iot exception.
+End Rem
+Type TIotException Extends TBlitzException
+
+	Field message:String
+
+	Method ToString:String() Override
+		Return message
+	End Method
+	
+End Type
+
+Type TPlatformNotSupportedException Extends TIotException
+
+	Method New()
+		message = "Platform not supported"
+	End Method
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TArgumentException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TUnauthorizedAccessException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TInvalidOperationException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TArgumentOutOfRangeException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TArgumentNullException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TIOException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Type TNotImplementedException Extends TIotException
+End Type
+
+Type TNotSupportedException Extends TIotException
+
+	Method New(message:String)
+		Self.message = message
+	End Method
+
+End Type
+
+Enum EI2cFunctionalityFlags Flags
+	NONE = 0
+	I2C_FUNC_I2C = $00000001
+	I2C_FUNC_SMBUS_BLOCK_DATA = $03000000
+End Enum
+
+Enum EI2cSettings:UInt
+	I2C_FUNCS = $0705
+	I2C_SLAVE_FORCE = $0706
+	I2C_RDWR = $0707
+	I2C_SMBUS = $0720
+End Enum
+
+Enum EI2cMessageFlags:Short Flags
+	I2C_M_WR = $0000
+	I2C_M_RD = $0001
+	I2C_M_TEN = $0010
+	I2C_M_RECV_LEN = $0400
+	I2C_M_NO_RD_ACK = $0800
+	I2C_M_IGNORE_NAK = $1000
+	I2C_M_REV_DIR_ADDR = $2000
+	I2C_M_NOSTART = $4000
+End Enum
+
+Enum ESpiSettings:UInt
+	SPI_IOC_WR_MODE = $40016b01
+	SPI_IOC_RD_MODE = $80016b01
+	SPI_IOC_WR_BITS_PER_WORD = $40016b03
+	SPI_IOC_RD_BITS_PER_WORD = $80016b03
+	SPI_IOC_WR_MAX_SPEED_HZ = $40046b04
+	SPI_IOC_RD_MAX_SPEED_HZ = $80046b04
+End Enum
+
+Enum ESpiMode Flags
+	None = $00
+	SPI_CPHA = $01
+	SPI_CPOL = $02
+	SPI_CS_HIGH = $04
+	SPI_LSB_FIRST = $08
+	SPI_3WIRE = $10
+	SPI_LOOP = $20
+	SPI_NO_CS = $40
+	SPI_READY = $80
+	SPI_MODE_0 = None
+	SPI_MODE_1 = SPI_CPHA
+	SPI_MODE_2 = SPI_CPOL
+	SPI_MODE_3 = SPI_CPOL | SPI_CPHA
+End Enum
+
+Struct i2c_msg
+	Field addr:Short
+	Field flags:Short
+	Field length:Short
+	Field buf:Byte Ptr
+	
+	Method New(addr:Short, flags:Short, length:Short, buf:Byte Ptr)
+		Self.addr = addr
+		Self.flags = flags
+		Self.length = length
+		Self.buf = buf
+	End Method
+	
+End Struct
+
+Struct i2c_rdwr_ioctl_data
+	Field msgs:Byte Ptr
+	Field nmsgs:UInt
+	
+	Method New(msgs:Byte Ptr, nmsgs:UInt)
+		Self.msgs = msgs
+		Self.nmsgs = nmsgs
+	End Method
+End Struct
+
+Const O_RDWR:Int = $0002
+
+Struct spi_ioc_transfer
+	Field tx_buf:Byte Ptr
+	Field rx_buf:Byte Ptr
+	Field length:UInt
+	Field speed_hz:UInt
+	Field delay_usecs:Short
+	Field bits_per_word:Byte
+	Field cs_change:Byte
+	Field pad:UInt
+	
+	Method New(tx_buf:Byte Ptr, rx_buf:Byte Ptr, length:UInt, speed_hz:UInt, bits_per_word:Byte, delay_usecs:Short)
+		Self.tx_buf = tx_buf
+		Self.rx_buf = rx_buf
+		Self.length = length
+		Self.speed_hz = speed_hz
+		Self.bits_per_word = bits_per_word
+		Self.delay_usecs = delay_usecs
+	End Method
+End Struct
+
+Enum EDataFlow
+	MsbFirst
+	LsbFirst
+End Enum
+
+Extern
+	Function open_:Int(path:String, flags:Int)
+	Function ioctl_:Int(fd:Int, request:UInt, data:Byte Ptr)'="void ioctl_(int, unsigned int, int *)!"
+	Function ioctli_:Int(fd:Int, request:UInt, data:Int)="ioctli_"
+	Function ioctl_:Int(fd:Int, request:UInt, data:i2c_rdwr_ioctl_data Var)="void ioctl_(int, unsigned int, int *)!"
+	Function ioctl_:Int(fd:Int, request:UInt, data:spi_ioc_transfer Var)="void ioctl_(int, unsigned int, int *)!"
+	Function ioctli2_:Int(fd:Int, request:UInt, data:EI2cFunctionalityFlags Var)="void ioctl_(int, unsigned int, int *)!"
+	Function ioctlsp_:Int(fd:Int, request:UInt, data:ESpiMode Var)="void ioctl_(int, unsigned int, int *)!"
+	
+	Function write_:Long(fd:Int, buf:Byte Ptr, count:Size_T)
+	Function read_:Long(fd:Int, buf:Byte Ptr, count:Size_T)
+	Function close_(fd:Int)="close"
+End Extern
+
+Struct SPinValuePair
+	Field pinNumber:Int
+	Field pinValue:EPinValue
+	
+	Method New(pinNumber:Int, pinValue:EPinValue)
+		Self.pinNumber = pinNumber
+		Self.pinValue = pinValue
+	End Method
+	
+	Method New(pinNumber:Int, pinValue:Int)
+		Self.pinNumber = pinNumber
+		If pinValue = 0 Then
+			Self.pinValue = EPinValue.Low
+		Else
+			Self.pinValue = EpinValue.High
+		End If
+	End Method
+	
+End Struct
+
+
+Function DelayMicroseconds(microseconds:Int, allowThreadYield:Int)
+	
+End Function
+
+

+ 37 - 0
core.mod/core.bmx

@@ -0,0 +1,37 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Rem
+bbdoc: IoT Core
+End Rem
+Module iot.core
+
+Import "gpio/gpiocontroller.bmx"
+
+Import "gpio/drivers/sysfsdriver.bmx"
+Import "gpio/drivers/libgpioddriver.bmx"
+Import "gpio/pwm/pwm.bmx"
+Import "gpio/i2c/i2cdevice.bmx"
+Import "gpio/spi/spidevice.bmx"

+ 31 - 0
core.mod/glue.c

@@ -0,0 +1,31 @@
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "brl.mod/blitz.mod/blitz.h"
+
+int open_(BBString * path, int flags) {
+	char * p = bbStringToUTF8String(path);
+	int fd = open(p, flags);
+	bbMemFree(p);
+	return fd;
+}
+
+int ioctl_(int fd, unsigned int request, int * data) {
+	return ioctl(fd, request, data);
+}
+
+int ioctli_(int fd, unsigned int request, int data) {
+	return ioctl(fd, request, data);
+}
+
+BBInt64 write_(int fd, void * buf, size_t count) {
+	return write(fd, buf, count);
+}
+
+BBInt64 read_(int fd, void * buf, size_t count) {
+	return read(fd, buf, count);
+}

+ 44 - 0
core.mod/gpio/drivers/libbcmhost.bmx

@@ -0,0 +1,44 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "link.bmx"
+
+Private
+Global _libbcmhost:Byte Ptr=LoadBcmHost()
+
+Function LoadBcmHost:Byte Ptr()
+	Return dlopen_("libbcm_host.so", 2)
+End Function
+
+Public
+
+Rem
+bbdoc: Returns #True if libbcm_host is available.
+End Rem
+Function LibBcmHostAvailable:Int()
+	Return _libbcmhost <> Null
+End Function
+
+Global bcm_host_get_peripheral_address:UInt()=LinkSymbol("bcm_host_get_peripheral_address")

+ 112 - 0
core.mod/gpio/drivers/libgpiod.bmx

@@ -0,0 +1,112 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "link.bmx"
+Import "../gpiodriver.bmx"
+
+Private
+Global _libgpiod:Byte Ptr=LoadGpiod()
+
+Function LoadGpiod:Byte Ptr()
+	Return dlopen_("libgpiod.so", 2)
+End Function
+
+Public
+
+Rem
+bbdoc: Returns #True if libgpiod is available.
+End Rem
+Function LibGpiodAvailable:Int()
+	Return _libgpiod <> Null
+End Function
+
+Const GPIOD_LINE_BULK_MAX_LINES:Int = 64
+
+Global gpiod_chip_open:Byte Ptr(path:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_open")
+Global gpiod_chip_open_by_name:Byte Ptr(name:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_open_by_name")
+Global gpiod_chip_open_by_number:Byte Ptr(num:UInt)=LinkSymbol(_libgpiod, "gpiod_chip_open_by_number")
+Global gpiod_chip_open_by_label:Byte Ptr(label:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_open_by_label")
+Global gpiod_chip_open_lookup:Byte Ptr(desc:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_open_lookup")
+Global gpiod_chip_close(chip:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_close")
+Global gpiod_chip_name:Byte Ptr(chip:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_name")
+Global gpiod_chip_label:Byte Ptr(chip:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_label")
+Global gpiod_chip_num_lines:UInt(chip:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_num_lines")
+
+Global gpiod_chip_get_line:Byte Ptr(chip:Byte Ptr, offset:UInt)=LinkSymbol(_libgpiod, "gpiod_chip_get_line")
+Global gpiod_chip_get_lines:Int(chip:Byte Ptr, offsets:UInt Ptr, numOffsets:UInt, bulk:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_get_lines")
+Global gpiod_chip_get_all_lines:Int(chip:Byte Ptr, bulk:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_chip_get_all_lines")
+Global gpiod_chip_find_line:Byte Ptr(chip:Byte Ptr, name$z)=LinkSymbol(_libgpiod, "gpiod_chip_find_line")
+
+Global gpiod_line_offset:UInt(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_offset")
+Global gpiod_line_name:Byte Ptr(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_name")
+Global gpiod_line_consumer:Byte Ptr(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_consumer")
+Global gpiod_line_direction:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_direction")
+Global gpiod_line_active_state:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_active_state")
+Global gpiod_line_is_used:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_is_used")
+Global gpiod_line_is_open_drain:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_is_open_drain")
+Global gpiod_line_is_open_source:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_is_open_source")
+Global gpiod_line_update:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_update")
+Global gpiod_line_needs_update:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_needs_update")
+Global gpiod_line_release(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_release")
+Global gpiod_line_get_value:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_get_value")
+Global gpiod_line_is_requested:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_is_requested")
+Global gpiod_line_set_value:Int(line:Byte Ptr, value:Int)=LinkSymbol(_libgpiod, "gpiod_line_set_value")
+Global gpiod_line_request_input:Int(line:Byte Ptr, consumer$z)=LinkSymbol(_libgpiod, "gpiod_line_request_input")
+Global gpiod_line_request_output:Int(line:Byte Ptr, consumer$z, defaultValue:Int)=LinkSymbol(_libgpiod, "gpiod_line_request_output")
+Global gpiod_line_request_both_edges_events:Int(line:Byte Ptr, consumer$z)=LinkSymbol(_libgpiod, "gpiod_line_request_both_edges_events")
+Global gpiod_line_event_wait:Int(line:Byte Ptr, timespec:STimeSpec Var)=LinkSymbol(_libgpiod, "gpiod_line_event_wait")
+Global gpiod_line_event_read:Int(line:Byte Ptr, lineEvent:SGpioLineEvent Var)=LinkSymbol(_libgpiod, "gpiod_line_event_read")
+Global gpiod_line_is_free:Int(line:Byte Ptr)=LinkSymbol(_libgpiod, "gpiod_line_is_free")
+
+Enum ELineRequestType
+	GPIOD_LINE_REQUEST_DIRECTION_AS_IS = 1
+	GPIOD_LINE_REQUEST_DIRECTION_INPUT
+	GPIOD_LINE_REQUEST_DIRECTION_OUTPUT
+	GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE
+	GPIOD_LINE_REQUEST_EVENT_RISING_EDGE
+	GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES
+End Enum
+
+Enum ELineRequestFlags Flags
+	GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN	= 1 Shl 0
+	GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE	= 1 Shl 1
+	GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW	= 1 Shl 2
+End Enum
+
+Public
+
+Struct SGpioLineEvent
+	Field timespec:STimeSpec
+	Field eventType:Int
+	
+	Method AsPinEventType:EPinEventTypes()
+		If eventType = 1 Then
+			Return EPinEventTypes.Rising
+		Else
+			Return EPinEventTypes.Falling
+		End If
+	End Method
+	
+End Struct

+ 483 - 0
core.mod/gpio/drivers/libgpioddriver.bmx

@@ -0,0 +1,483 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import brl.threadpool
+
+Import "../gpiodriver.bmx"
+Import "libgpiod.bmx"
+
+Type TLibGpiodDriver Extends TGpioDriver
+
+	Field chipPtr:Byte Ptr
+	
+	Field pinNumberToLines:TGpiodLine[]
+	Field pinNumberToEventHandler:TLibGpiodDriverEventHandler[]
+	
+	Field pool:TThreadPoolExecutor
+
+Private
+	Method New()
+	End Method
+Public
+
+	Method New(gpioChip:UInt = 0)
+
+		If Not LibGpiodAvailable() Then
+			Throw New TPlatformNotSupportedException()
+		End If
+		
+		chipPtr = gpiod_chip_open_by_number(gpioChip)
+		If Not chipPtr Then
+			Throw New TIOException("no chip found")
+		End If
+		
+		pool = TThreadPoolExecutor.newCachedThreadPool()
+		
+		Local count:Int = PinCount()
+		pinNumberToLines = New TGpiodLine[count]
+		pinNumberToEventHandler = New TLibGpiodDriverEventHandler[count]
+	End Method
+
+	Method PinCount:Int()
+		Return gpiod_chip_num_lines(chipPtr)
+	End Method
+	
+	Method ConvertPinNumberToLogicalNumberingScheme:Int(pinNumber:Int)
+		' not supported
+	End Method
+	
+	Method OpenPin(pinNumber:Int)
+		Local pin:TGpiodLine = TGpiodLine._create(gpiod_chip_get_line(chipPtr, UInt(pinNumber)))
+		If Not pin Then
+			Throw New TIOException("Error opening pin")
+		End If
+		
+		pinNumberToLines[pinNumber] = pin
+	End Method
+	
+	Method ClosePin(pinNumber:Int)
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		
+		If pin And Not IsListeningEvent(pinNumber) Then
+			pin.Dispose()
+			pinNumberToLines[pinNumber] = Null
+		End If
+		
+	End Method
+	
+	Method SetPinMode(pinNumber:Int, pinMode:EPinMode)
+		Local requestResult:Int = -1
+		
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		If pin Then
+			If pinMode = EPinMode.Input
+				requestResult = pin.RequestInput(String(pinNumber))
+			Else
+				requestResult = pin.RequestOutput(String(pinNumber))
+			End If
+			pin.SetPinMode(pinMode)
+		End If
+		
+		If requestResult = -1 Then
+			Throw New TIOException("Error setting pin mode : " + pinNumber)
+		End If
+	End Method
+	
+	Method GetPinMode:EPinMode(pinNumber:Int)
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		If Not pin Then
+			Throw New TInvalidOperationException("Pin not open : " + pinNumber)
+		End If
+		
+		Return pin.GetPinMode()
+	End Method
+	
+	Method IsListeningEvent:Int(pinNumber:Int)
+		Return pinNumberToEventHandler[pinNumber] <> Null
+	End Method
+	
+	Method IsPinModeSupported:Int(pinNumber:Int, pinMode:EPinMode)
+		' Libgpiod Api do not support pull up or pull down resistors for now.
+		Return pinMode <> EPinMode.InputPullDown And pinMode <> EPinMode.InputPullUp
+	End Method
+	
+	Method Read:EPinValue(pinNumber:Int)
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		If pin Then
+			Local pinValue:EPinValue
+
+			Local result:Int = pin.GetValue()
+			If result = -1 Or Not EPinValue.TryConvert(result, pinValue) Then
+				Throw New TIOException("Read pin error : " + pinNumber)
+			End If
+
+			Return pinValue
+		Else
+			Throw New TInvalidOperationException("Pin not opened : " + pinNumber)
+		End If
+	End Method
+	
+	Method Write(pinNumber:Int, value:EPinValue)
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		If Not pin Then
+			Throw New TIOException("Pin not opened : " + pinNumber)
+		End If
+		
+		If value = EPinValue.High Then
+			pin.SetValue(1)
+		Else
+			pin.SetValue(0)
+		End If
+	End Method
+	
+	Method AddCallbackForPinValueChangedEvent(pinNumber:Int, eventTypes:EPinEventTypes, context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		If eventTypes & EPinEventTypes.Rising Or eventTypes & EPinEventTypes.Falling Then
+			Local eventHandler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[pinNumber]
+			If Not eventHandler Then
+				eventHandler = PopulateEventHandler(pinNumber)
+			End If
+			
+			If eventTypes & EPinEventTypes.Rising Then
+				eventHandler.valueRising.AddLast(context, callback)
+			End If
+			
+			If eventTypes & EPinEventTypes.Falling Then
+				eventHandler.valueFalling.AddLast(context, callback)
+			End If
+		Else
+			Throw New TIOException("Invalid event type")
+		End If
+	End Method
+	
+	Method PopulateEventHandler:TLibGpiodDriverEventHandler(pinNumber:Int)
+		Local pin:TGpiodLine = pinNumberToLines[pinNumber]
+		If pin Then
+			If Not pin.IsFree() Then
+				pin.Dispose()
+				pin = TGpiodLine._create(gpiod_chip_get_line(chipPtr, UInt(pinNumber)))
+				pinNumberToLines[pinNumber] = pin
+			EndIf
+		End If
+		
+		Return New TLibGpiodDriverEventHandler(pinNumber, pin, pool)
+	End Method
+	
+	Method RemoveCallbackForPinValueChangedEvent(pinNumber:Int, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		Local eventHandler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[pinNumber]
+		If eventHandler Then
+			eventHandler.valueFalling.Remove(callback)
+			eventHandler.ValueRising.Remove(callback)
+			
+			If eventHandler.IsCallbackListEmpty() Then
+				pinNumberToEventHandler[pinNumber] = Null
+				eventHandler.Dispose()
+			End If
+		Else
+			Throw New TIOException("Not listening for event")
+		End If
+	End Method
+	
+	Method WaitForEvent()
+	End Method
+
+	Method Dispose()
+		If pinNumberToEventHandler Then
+			For Local i:Int = 0 Until pinNumberToEventHandler.length
+				Local handler:TLibGpiodDriverEventHandler = pinNumberToEventHandler[i]
+				If handler Then
+					handler.Dispose()
+					pinNumberToEventHandler[i] = Null
+				End If
+			Next
+			pinNumberToEventHandler = Null
+		End If
+		
+		If pinNumberToLines Then
+			For Local i:Int = 0 Until pinNumberToLines.length
+				Local pin:TGpiodLine = pinNumberToLines[i]
+				If pin Then
+					pin.Dispose()
+					pinNumberToLines[i] = Null
+				End If
+			Next
+			pinNumberToLines = Null
+		End If
+		
+		If chipPtr Then
+			gpiod_chip_close(chipPtr)
+			chipPtr = Null
+		End If
+		
+	End Method
+
+End Type
+
+Type TGpiodLine
+
+	Field linePtr:Byte Ptr
+	Field pinMode:EPinMode
+	
+	Function _create:TGpiodLine(linePtr:Byte Ptr)
+		If linePtr Then
+			Local this:TGpiodLine = New TGpiodLine
+			this.linePtr = linePtr
+			Return this
+		End If
+	End Function
+	
+	Method GetPinMode:EPinMode()
+		Return pinMode
+	End Method
+	
+	Method SetPinMode(value:EPinMode)
+		pinMode = value
+	End Method
+	
+	Method GetValue:Int()
+		Return gpiod_line_get_value(linePtr)
+	End Method
+	
+	Method SetValue:Int(value:Int)
+		Return gpiod_line_set_value(linePtr, value)
+	End Method
+	
+	Method RequestInput:Int(consumer:String)
+		Return gpiod_line_request_input(linePtr, consumer)
+	End Method
+	
+	Method RequestOutput:Int(consumer:String)
+		Return gpiod_line_request_output(linePtr, consumer, 0)
+	End Method
+	
+	Method RequestBothEdgesEvents:Int(consumer:String)
+		Return gpiod_line_request_both_edges_events(linePtr, consumer)
+	End Method
+
+	Rem
+	bbdoc: Waits for an event on the GPIO line.
+	returns: 0 if wait timed out, -1 if an error occurred, 1 if an event occurred.
+	End Rem
+	Method EventWait:Int(timespec:STimeSpec Var)
+		Return gpiod_line_event_wait(linePtr, timespec)
+	End Method
+	
+	Rem
+	bbdoc: Reads the last event from the GPIO line.
+	returns: 0 if the event was read correctly, -1 on error.
+	End Rem
+	Method EventRead:Int(event:SGpioLineEvent Var)
+		Return gpiod_line_event_read(linePtr, event)
+	End Method
+	
+	Method IsFree:Int()
+		Return gpiod_line_is_free(linePtr)
+	End Method
+
+	Method Dispose()
+		If linePtr Then
+			gpiod_line_release(linePtr)
+			linePtr = Null
+		End If
+	End Method
+
+	Method Delete()
+		Dispose()
+	End Method
+	
+End Type
+
+
+Type TLibGpiodDriverEventHandler
+
+	Field pool:TThreadPoolExecutor
+	
+	' PinChangeEventHandler(object sender, PinValueChangedEventArgs pinValueChangedEventArgs
+	Field valueRising:TCallbackList = New TCallbackList
+	Field valueFalling:TCallbackList = New TCallbackList
+	
+	Field pinNumber:Int
+	
+	Field _shutdown:Int
+	
+	Method New(pinNumber:Int, pin:TGpiodLine, pool:TThreadPoolExecutor)
+		Self.pool = pool
+		
+		Self.pinNumber = pinNumber
+		' CancellationTokenSource = new CancellationTokenSource ' TODO
+		SubscribeForEvent(pin)
+		
+		InitializeEventDetectionTask(pin)
+	End Method
+	
+	Method SubscribeForEvent(pin:TGpiodLine)
+		Local eventSuccess:Int = pin.RequestBothEdgesEvents("Listen " + pinNumber + " for both edge event")
+		If eventSuccess < 0 Then
+			Throw New TIOException("Request event error : " + pinNumber)
+		End If
+	End Method
+	
+	Method InitializeEventDetectionTask(pin:TGpiodLine)
+		
+		pool.execute(New TEventDetectionTask(Self, pinNumber, pin))
+		
+	End Method
+	
+	Method OnPinValueChanged(args:SPinValueChangedEventArgs, detectionOfEventTypes:EPinEventTypes)
+		If detectionOfEventTypes = EPinEventTypes.Rising And args.changeType = EPinEventTypes.Rising Then
+			If valueRising.size Then
+				For Local i:Int = 0 Until valueRising.size
+					Local cb:TCallback = valueRising.data[i]
+					cb.data(cb.context, Self, args)
+				Next
+			End If
+		Else
+			If valueFalling.size Then
+				For Local i:Int = 0 Until valueFalling.size
+					Local cb:TCallback = valueFalling.data[i]
+					cb.data(cb.context, Self, args)
+				Next
+			End If
+		End If
+	End Method
+
+	Method IsCallbackListEmpty:Int()
+		Return Not valueRising.size And Not valueFalling.size
+	End Method
+	
+	Method Dispose()
+		_shutdown = True
+	End Method
+	
+End Type
+
+Type TEventDetectionTask Extends TRunnable
+
+	Field eventHandler:TLibGpiodDriverEventHandler
+	Field pinNumber:Int
+	Field pin:TGpiodLine
+	
+	Field timespec:STimeSpec
+	
+	Method New(eventHandler:TLibGpiodDriverEventHandler, pinNumber:Int, pin:TGpiodLine)
+		Self.eventHandler = eventHandler
+		Self.pinNumber = pinNumber
+		Self.pin = pin
+		
+		timespec = New STimeSpec(0, 1000000)
+	End Method
+	
+	Method Run()
+	
+		While Not eventHandler._shutdown
+		
+			Local res:Int = pin.EventWait(timespec)
+			
+			' error
+			If res = -1 Then
+				Throw New TIOException("Event wait error " + pinNumber)
+			End If
+		
+			' event
+			If res = 1 Then
+			
+				Local event:SGpioLineEvent
+				
+				If pin.EventRead(event) = -1 Then
+					Throw New TIOException("Event read error " + pinNumber)
+				End If
+				
+				eventHandler.OnPinValueChanged(New SPinValueChangedEventArgs(event.AsPinEventType(), pinNumber), event.AsPinEventType())
+			
+			End If
+		
+		Wend
+	
+	End Method
+	
+End Type
+
+Type TCallback
+	Field context:Object
+	Field data(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs)
+	
+	Method New(context:Object, data(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		Self.context = context
+		Self.data = data
+	End Method
+End Type
+
+Type TCallbackList
+
+	Field data:TCallback[16]
+	Field size:Int
+
+	Method _ensureCapacity(newSize:Int)
+		If newSize >= data.length Then
+			data = data[.. newSize * 3 / 2 + 1]
+		End If
+	End Method
+
+	Method Clear()
+		For Local i:Int = 0 Until size
+			data[i] = Null
+		Next
+		size = 0
+	End Method
+
+	Method IsEmpty:Int()
+		Return size = 0
+	End Method
+	
+	Method AddLast(context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		_ensureCapacity(size + 1)
+		data[size] = New TCallback(context, callback)
+		size :+ 1
+	End Method
+	
+	Method Remove(callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		If size Then
+			Local offset:Int = -1
+			For Local i:Int = 0 Until size
+				If data[i].data = callback Then
+					offset = i
+				End If
+			Next
+			
+			If offset >= 0 Then
+				Local length:Int = size - offset
+				If length > 0 Then
+					ArrayCopy(data, offset + 1, data, offset, length)
+				End If
+				size :- 1
+				data[size] = Null
+			End If
+		End If
+	End Method
+			
+	Method _removeAt(index:Int)
+		data[index] = Null
+	End Method
+	
+End Type

+ 37 - 0
core.mod/gpio/drivers/link.bmx

@@ -0,0 +1,37 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "-ldl"
+
+Extern
+	Function dlopen_:Byte Ptr(dll:Byte Ptr, flag:Int)="dlopen"
+	Function dlsym_:Byte Ptr(dll:Byte Ptr, fname:Byte Ptr)="dlsym"
+End Extern
+
+Function LinkSymbol:Byte Ptr(lib:Byte Ptr, func:String)
+	If lib Then
+		Return dlsym_(lib, func)
+	End If
+End Function

+ 160 - 0
core.mod/gpio/drivers/sysfsdriver.bmx

@@ -0,0 +1,160 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import brl.filesystem
+Import brl.textstream
+Import "../gpiodriver.bmx"
+
+Rem
+bbdoc: A SysFs GPIO driver.
+End Rem
+Type TSysFsGpioDriver Implements IDisposable
+
+	Const GPIO_BASE_PATH:String = "/sys/class/gpio"
+Private
+	Field exportedPins:TIntArrayList = New TIntArrayList
+Public
+	Rem
+	bbdoc: Opens a pin in order for it to be ready to use.
+	End Rem
+	Method OpenPin(pinNumber:Int)
+		Local pinPath:String = GPIO_BASE_PATH + "/gpio" + pinNumber
+		
+		If Not FileType(pinPath) Then
+			SaveText(pinNumber, GPIO_BASE_PATH + "/export")
+			exportedPins.Add(pinNumber)
+		End If
+	End Method
+
+	Method ClosePin(pinNumber:Int)
+		Local pinPath:String = GPIO_BASE_PATH + "/gpio" + pinNumber
+		If FileType(pinPath) = FILETYPE_DIR Then
+			SetPinEventsToDetect(pinNumber, EPinEventTypes.None)
+			SaveText(pinNumber, GPIO_BASE_PATH + "/unexport")
+			exportedPins.Remove(pinNumber)
+		End If
+	End Method
+
+	Method SetPinMode(pinNumber:Int, pinMode:EPinMode)
+		If pinMode = EpinMode.InputPullDown Or pinMode = EpinMode.InputPullUp Then
+			Throw "This driver is generic so it does not support Input Pull Down or Input Pull Up modes."
+		End If
+		
+		Local directionPath:String = GPIO_BASE_PATH + "/gpio" + pinNumber + "/direction"
+		Local sysfsMode:String = ConvertPinModeToSysFsMode(pinMode)
+		
+		If FileType(directionPath) = FILETYPE_DIR Then
+			Try
+				SaveText(sysfsMode, directionPath)
+			Catch e:TStreamWriteException
+				Throw "Setting a mode to a pin requires root permissions."
+			End Try
+		Else
+			Throw "There was an attempt to set a mode to a pin that is not open."
+		End If
+	End Method
+	
+	Method ConvertPinModeToSysFsMode:String(pinMode:EPinMode)
+		Select pinMode
+			Case EPinMode.Input
+				Return "in"
+			Case EPinMode.Output
+				Return "out"
+		End Select
+		
+		Throw New TPlatformNotSupportedException(pinMode + " is not supported by this driver.")
+	End Method
+
+
+	Method SetPinEventsToDetect(pinNumber:Int, eventType:EPinEventTypes)
+		Local edgePath:String = GPIO_BASE_PATH
+	End Method
+	
+	Method ConvertSysFsModeToPinMode:EPinMode(sysfsMode:String)
+		sysfsMode = sysfsMode.Trim()
+		Select sysfsMode
+			Case "in"
+				Return EPinMode.Input
+			Case "out"
+				Return EPinMode.Output
+		End Select
+		
+		Throw New TArgumentException("Unable to parse " + sysfsMode + " as a pinMode.")
+	End Method
+	
+	Method Read:Int(pinNumber:Int)
+		Local result:Int
+		
+		Local valuePath:String = GPIO_BASE_PATH + "/gpio" + pinNumber + "/value"
+		If FileType(valuePath) = FILETYPE_DIR Then
+			Try
+				Local valueContents:String = LoadText(valuePath)
+				result = ConvertSysFsValueToPinValue(valueContents)
+			Catch e:TStreamReadException
+				Throw New TUnauthorizedAccessException("Reading a pin value requires root permissions.")
+			End Try
+		Else
+			Throw New TInvalidOperationException("There was an attempt to read from a pin that is not open.")
+		End If
+		
+		Return result
+	End Method
+	
+	Method ConvertSysFsValueToPinValue:Int(value:String)
+	End Method
+	
+	Method Write(pinNumber:Int, value:Int)
+	End Method
+	
+	Method ConvertPinValueToSysFs:String(value:Int)
+	End Method
+	
+	Method IsPinModeSupported:Int(pinNumber:Int, pinMode:EPinMode)
+	End Method
+	
+	Method GetPinEventsToDetect:Int(pinNumber:Int)
+	End Method
+	
+	Method StringValueToPinEventType:EPinEventTypes(value:String)
+	End Method
+	
+	Method PinEventTypeToStringValue:String(kind:EPinEventTypes)
+	End Method
+	
+	Method GetPinMode:EPinMode(pinNumber:Int)
+		Local directionPath:String = GPIO_BASE_PATH + "/gpio" + pinNumber + "/direction"
+		If FileType(directionPath) = FILETYPE_DIR Then
+			Try
+				Local sysfsMode:String = LoadText(directionPath)
+				Return ConvertSysFsModeToPinMode(sysfsMode)
+			Catch e:TStreamReadException
+				Throw New TUnauthorizedAccessException("Getting a mode to a pin requires root permissions.")
+			End Try
+		Else
+			Throw New TInvalidOperationException("There was an attempt to get a mode to a pin that is not open.")
+		End If
+	End Method
+	
+End Type

+ 251 - 0
core.mod/gpio/gpiocontroller.bmx

@@ -0,0 +1,251 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "drivers/libgpioddriver.bmx"
+Import "../common.bmx"
+
+Rem
+bbdoc: Represents a general-purpose I/O (GPIO) controller.
+End Rem
+Type TGpioController Implements IDisposable
+Private
+	Field driver:TGpioDriver
+	Field openPins:Int[]
+	
+	Method New()
+	End Method
+Public
+
+	Rem
+	bbdoc: The numbering scheme used to represent pins provided by the controller.
+	End Rem
+	Field numberingScheme:EPinNumberingScheme
+	
+	Method New(numberingScheme:EPinNumberingScheme = EPinNumberingScheme.Logical)
+		Self.numberingScheme = numberingScheme
+		driver = New TLibGpiodDriver()
+		openPins = New Int[PinCount()]
+	End Method
+
+	Rem
+	bbdoc: Returns the number of pins provided by the controller.
+	End Rem
+	Method PinCount:Int()
+		Return driver.PinCount()
+	End Method
+	
+	Rem
+	bbdoc: Gets the logical pin number in the controller's numbering scheme.
+	End Rem
+	Method GetLogicalPinNumber:Int(pinNumber:Int)
+		If numberingScheme = EPinNumberingScheme.Logical Then
+			Return pinNumber
+		Else
+			Return driver.ConvertPinNumberToLogicalNumberingScheme(pinNumber)
+		End If
+	End Method
+	
+	Rem
+	bbdoc: Opens a pin in order for it to be ready to use.
+	End Rem
+	Method OpenPin(pinNumber:Int)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("The selected pin is already open.")
+		End If
+		
+		driver.OpenPin(logicalPinNumber)
+		openPins[logicalPinNumber] = True
+	End Method
+	
+	Rem
+	bbdoc: Opens a pin and sets it to a specific mode.
+	End Rem
+	Method OpenPin(pinNumber:Int, pinMode:EPinMode)
+		OpenPin(pinNumber)
+		SetPinMode(pinNumber, pinMode)
+	End Method
+	
+	Rem
+	bbdoc: Closes an open pin.
+	End Rem
+	Method ClosePin(pinNumber:Int)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot close a pin that is not open.")
+		End If
+		
+		driver.ClosePin(logicalPinNumber)
+		openPins[logicalPinNumber] = False
+	End Method
+	
+	Rem
+	bbdoc: Sets the mode of a pin.
+	End Rem
+	Method SetPinMode(pinNumber:Int, pinMode:EPinMode)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot set a mode to a pin that is not open.")
+		End If
+		
+		If Not driver.IsPinModeSupported(logicalPinNumber, pinMode) Then
+			Throw New TInvalidOperationException("The pin does not support the selected mode.")
+		End If
+		
+		driver.SetPinMode(logicalPinNumber, pinMode)
+	End Method
+	
+	Rem
+	bbdoc: Gets the mode of a pin.
+	End Rem
+	Method GetPinMode:EPinMode(pinNumber:Int)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot get the mode of a pin that is not open.")
+		End If
+		
+		Return driver.GetPinMode(logicalPinNumber)
+	End Method
+	
+	Rem
+	bbdoc: Checks if a specific pin is open.
+	returns: #True if the specified pin is open, #False otherwise.
+	End Rem
+	Method IsPinOpen:Int(pinNumber:Int)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		Return openPins[logicalPinNumber]
+	End Method
+	
+	Rem
+	bbdoc: Checks if a pin supports a specific mode.
+	returns: #True if the mode is supported by the specified pin, #False otherwise.
+	End Rem
+	Method IsPinModeSupported:Int(pinNumber:Int, pinMode:EPinMode)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		Return driver.IsPinModeSupported(pinNumber, pinMode)
+	End Method
+	
+	Rem
+	bbdoc: Reads the current value of a pin.
+	End Rem
+	Method Read:EPinValue(pinNumber:Int)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot read from a pin that is not open.")
+		End If
+		
+		If driver.GetPinMode(logicalPinNumber) = EpinMode.Output Then
+			Throw New TInvalidOperationException("Cannot read from a pin that is set to Output mode.")
+		End If
+		
+		Return driver.Read(logicalPinNumber)
+	End Method
+	
+	Rem
+	bbdoc: Writes a value to a pin.
+	End Rem
+	Method Write(pinNumber:Int, value:EPinValue)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot write to a pin that is not open.")
+		End If
+		
+		If driver.GetPinMode(logicalPinNumber) <> EPinMode.Output Then
+			Throw New TInvalidOperationException("Cannot write to a pin that is not set to Output mode.")
+		End If
+		
+		driver.Write(logicalPinNumber, value)
+	End Method
+	
+	Rem
+	bbdoc: Writes the given pins with the given values.
+	End Rem
+	Method Write(pinValuePairs:SPinValuePair[], count:Int = 0)
+		If Not count Then
+			count = pinValuePairs.length
+		End If
+		For Local i:Int = 0 Until count
+			Write(pinValuePairs[i].pinNumber, pinValuePairs[i].pinValue)
+		Next
+	End Method
+	
+	Rem
+	bbdoc: Reads the given pins with the given pin numbers.
+	End Rem
+	Method Read(pinValuePairs:SPinValuePair[])
+		For Local i:Int = 0 Until pinValuePairs.length
+			Local pin:Int = pinValuePairs[i].pinNumber
+			pinValuePairs[i] = New SPinValuePair(pin, Read(pin))
+		Next
+	End Method
+Rem
+	Method WaitForEvent:SWaitForEventResult(pinNumber:Int, eventTypes:EPinEventTypes, timeoutMs:Int)
+		
+	End Method
+	
+	Method WaitForEvent:SWaitForEventResult(pinNumber:Int, eventTypes:EPinEventTypes, cancellationToken:TCancellationToken)
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot wait for events from a pin that is not open.")
+		End If
+		
+		Return driver.WaitForEvent(logicalPinNumber, eventTypes, cancellationToken)
+	End Method
+End Rem	
+
+	Rem
+	bbdoc: Adds a callback that will be invoked when @pinNumber has an event of type @eventType.
+	End Rem
+	Method RegisterCallbackForPinValueChangedEvent(pinNumber:Int, eventTypes:EPinEventTypes, context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot add callback for a pin that is not open.")
+		End If
+		
+		driver.AddCallbackForPinValueChangedEvent(logicalPinNumber, eventTypes, context, callback)
+	End Method
+	
+	Rem
+	bbdoc: Removes a callback that was being invoked for pin at @pinNumber.
+	End Rem
+	Method UnregisterCallbackForPinValueChangedEvent(pinNumber:Int, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs))
+		Local logicalPinNumber:Int = GetLogicalPinNumber(pinNumber)
+		If Not openPins[logicalPinNumber] Then
+			Throw New TInvalidOperationException("Cannot remove callback for a pin that is not open.")
+		End If
+		
+		driver.RemoveCallbackForPinValueChangedEvent(logicalPinNumber, callback)
+	End Method
+	
+	Method Dispose()
+		For Local pin:Int = EachIn openPins
+			driver.ClosePin(pin)
+		Next
+		
+		openPins = Null
+		driver.Dispose()
+	End Method
+
+End Type

+ 80 - 0
core.mod/gpio/gpiodriver.bmx

@@ -0,0 +1,80 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "../common.bmx"
+
+
+Type TGpioDriver Implements IDisposable Abstract
+
+	Method PinCount:Int() Abstract
+	
+	Method ConvertPinNumberToLogicalNumberingScheme:Int(pinNumber:Int) Abstract
+	
+	Method OpenPin(pinNumber:Int) Abstract
+	
+	Method ClosePin(pinNumber:Int) Abstract
+	
+	Method SetPinMode(pinNumber:Int, pinMode:EPinMode) Abstract
+	
+	Method GetPinMode:EPinMode(pinNumber:Int) Abstract
+	
+	Method IsPinModeSupported:Int(pinNumber:Int, pinMode:EPinMode) Abstract
+	
+	Method Read:EPinValue(pinNumber:Int) Abstract
+	
+	Method Write(pinNumber:Int, value:EPinValue) Abstract
+	
+	'Method WaitForEvent:SWaitForEventResult() Abstract
+	
+	Method AddCallbackForPinValueChangedEvent(pinNumber:Int, eventTypes:EPinEventTypes, context:Object, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs)) Abstract
+	
+	Method RemoveCallbackForPinValueChangedEvent(pinNumber:Int, callback(context:Object, sender:Object, pinValueChangedEventArgs:SPinValueChangedEventArgs)) Abstract
+	
+End Type
+
+Struct SWaitForEventResult
+	Field eventTypes:EPinEventTypes
+	Field timedOut:Int
+	
+	Method New(eventTypes:EPinEventTypes, timedOut:Int)
+		Self.eventTypes = eventTypes
+		Self.timedOut = timedOut
+	End Method
+End Struct
+
+Struct SPinValueChangedEventArgs
+	Field changeType:EPinEventTypes
+	Field pinNumber:Int
+	
+	Method New(changeType:EPinEventTypes, pinNumber:Int)
+		Self.changeType = changeType
+		Self.pinNumber = pinNumber
+	End Method
+	
+End Struct
+
+Function defaultcomparator_compare:Int(a:EPinEventTypes, b:EPinEventTypes )
+	Return a.Ordinal() - b.Ordinal()
+End Function

+ 248 - 0
core.mod/gpio/i2c/i2cdevice.bmx

@@ -0,0 +1,248 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import brl.threads
+Import "../../common.bmx"
+
+Rem
+bbdoc: The communications channel to a device on an I2C bus.
+End Rem
+Type TI2cDevice Implements IDisposable
+
+	Const DEFAULT_DEVICE_PATH:String = "/dev/i2c"
+
+	Field ReadOnly settings:TI2cConnectionSettings
+	Field deviceFileDescriptor:Int = -1
+	Field functionalities:EI2cFunctionalityFlags
+	Field devicePath:String
+	
+	Field initializationLock:TMutex = TMutex.Create()
+	
+	Method New(settings:TI2cConnectionSettings)
+		Self.settings = settings
+		devicePath = DEFAULT_DEVICE_PATH
+	End Method
+
+Private
+	Method Initialize()
+		If deviceFileDescriptor >= 0 Then
+			Return
+		End If
+		
+		Local deviceFileName:String = devicePath + "-" + settings.GetBusId()
+		
+		Try
+			initializationLock.Lock()
+		
+			deviceFileDescriptor = open_(deviceFileName, O_RDWR)
+
+			If deviceFileDescriptor < 0 Then
+				Throw New TIOException("Cannot open I2C device file '" + deviceFileName + "'.")
+			End If
+
+			Local tempFlags:EI2cFunctionalityFlags
+			Local result:Int = ioctli2_(deviceFileDescriptor, EI2cSettings.I2C_FUNCS.Ordinal(), tempFlags)
+			
+			If result < 0 Then
+				functionalities = EI2cFunctionalityFlags.None
+			Else
+				functionalities = tempFlags
+			End If
+		Finally
+			initializationLock.Unlock()
+		End Try
+	End Method
+	
+	Method Transfer(writeBuffer:Byte Ptr, writeLength:Size_T, readBuffer:Byte Ptr, readLength:Size_T)
+		If functionalities & EI2cFunctionalityFlags.I2C_FUNC_I2C Then
+			ReadWriteInterfaceTransfer(writeBuffer, writeLength, readBuffer, readLength)
+		Else
+			FileInterfaceTransfer(writeBuffer, writeLength, readBuffer, readLength)
+		End If
+	End Method
+	
+	Method ReadWriteInterfaceTransfer(writeBuffer:Byte Ptr, writeLength:Size_T, readBuffer:Byte Ptr, readLength:Size_T)
+		Local messages:i2c_msg[2]
+		Local messageCount:UInt
+		
+		If writeBuffer Then
+			messages[messageCount] = New i2c_msg(Short(settings.GetDeviceAddress()), Short(EI2cMessageFlags.I2C_M_WR), Short(writeLength), writeBuffer)
+			messageCount :+ 1
+		End If
+		
+		If readBuffer Then
+			messages[messageCount] = New i2c_msg(Short(settings.GetDeviceAddress()), Short(EI2cMessageFlags.I2C_M_RD), Short(readLength), readBuffer)
+			messageCount :+ 1			
+		End If
+		
+		Local msgset:i2c_rdwr_ioctl_data = New i2c_rdwr_ioctl_data(messages, messageCount)
+		
+		Local result:Int = ioctl_(deviceFileDescriptor, EI2cSettings.I2C_RDWR.Ordinal(), msgset)
+		
+		If result < 0 Then
+			Throw New TIOException("Error performing I2C data transfer.")
+		End If
+	End Method
+	
+	Method FileInterfaceTransfer(writeBuffer:Byte Ptr, writeLength:Size_T, readBuffer:Byte Ptr, readLength:Size_T)
+		Local result:Int = ioctli_(deviceFileDescriptor, EI2cSettings.I2C_SLAVE_FORCE.Ordinal(), settings.DeviceAddress)
+		
+		If result < 0 Then
+			Throw New TIOException("Error performing I2C data transfer.")
+		End If
+		
+		If writeBuffer Then
+			result = write_(deviceFileDescriptor, writeBuffer, writeLength)
+			
+			If result < 0 Then
+				Throw New TIOException("Error performing I2C data transfer.")
+			End If
+		End If
+		
+		If readBuffer Then
+			result = read_(deviceFileDescriptor, readBuffer, readLength)
+			
+			If result < 0 Then
+				Throw New TIOException("Error performing I2C data transfer.")
+			End If
+		End If
+	End Method
+	
+Public
+	Rem
+	bbdoc: Reads a byte from the I2C device.
+	End Rem
+	Method ReadByte:Byte()
+		Initialize()
+		
+		Local result:Byte
+		Transfer(Null, 0, Varptr result, 1)
+		Return result
+	End Method
+	
+	Rem
+	bbdoc: Reads data from the I2C device.
+	End Rem
+	Method Read(buffer:Byte Ptr, length:Size_T)
+		Initialize()
+		
+		Transfer(Null, 0, buffer, length)
+	End Method
+	
+	Rem
+	bbdoc: Writes a byte to the I2C device.
+	End Rem
+	Method WriteByte(value:Byte)
+		Initialize()
+		
+		Transfer(Varptr value, 1, Null, 0)
+	End Method
+	
+	Rem
+	bbdoc: Writes data to the I2C device.
+	End Rem
+	Method Write(buffer:Byte Ptr, length:Size_T)
+		Initialize()
+		
+		Transfer(buffer, length, Null, 0)
+	End Method
+	
+	Rem
+	bbdoc: Performs an atomic operation to write data to and then read data from the I2C bus on which the device is connected, and sends a restart condition between the write and read operations.
+	End Rem
+	Method WriteRead(writeBuffer:Byte Ptr, writeLength:Size_T, readBuffer:Byte Ptr, readLength:Size_T)
+		Initialize()
+	
+		Transfer(writeBuffer, writeLength, readBuffer, readLength)
+	End Method
+	
+	Rem
+	bbdoc: Returns the path to I2C resources located on the system.
+	End Rem
+	Method GetDevicePath:String()
+		Return devicePath
+	End Method
+	
+	Rem
+	bbdoc: Sets the path to I2C resources located on the system.
+	End Rem
+	Method SetDevicePath(devicePath:String)
+		Self.devicePath = devicePath
+	End Method
+
+	Rem
+	bbdoc: Returns the connection settings of a device on an I2C bus. 
+	End Rem
+	Method GetConnectionSettings:TI2cConnectionSettings()
+		Return settings
+	End Method
+	
+	Method Dispose() Override
+		If deviceFileDescriptor >= 0 Then
+			close_(deviceFileDescriptor)
+			deviceFileDescriptor = -1
+		End If
+	End Method
+End Type
+
+Rem
+bbdoc: The connection settings of a device on an I2C bus.
+End Rem
+Type TI2cConnectionSettings
+
+	Field ReadOnly busId:Int
+	Field ReadOnly deviceAddress:Int
+	
+	Rem
+	bbdoc: Creates a new instance of #TI2cConnectionSettings.
+	End Rem
+	Method New(busId:Int, deviceAddress:Int)
+		Self.busId = busId
+		Self.deviceAddress = deviceAddress
+	End Method
+	
+	Rem
+	bbdoc: Creates a copy of a #TI2cConnectionSettings.
+	End Rem
+	Method New(other:TI2cConnectionSettings)
+		busId = other.busId
+		deviceAddress = other.deviceAddress
+	End Method
+
+	Rem
+	bbdoc: Returns the bus id that the I2C device is connected to.
+	End Rem
+	Method GetBusId:Int()
+		Return busId
+	End Method
+	
+	Rem
+	bbdoc: Returns the bus address of the I2C device.
+	End Rem
+	Method GetDeviceAddress:Int()
+		Return deviceAddress
+	End Method
+	
+End Type

+ 173 - 0
core.mod/gpio/pwm/pwm.bmx

@@ -0,0 +1,173 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import "../../common.bmx"
+Import brl.filesystem
+Import brl.textstream
+
+Rem
+bbdoc: Represents a single PWM channel.
+End Rem
+Type TPwmChannel Implements IDisposable
+
+	Private
+	
+	Field ReadOnly chip:Int
+	Field ReadOnly channel:Int
+	
+	Field ReadOnly chipPath:String
+	Field ReadOnly channelPath:String
+	
+	Field dutyCycleStream:TStream
+	Field frequencyStream:TStream
+	
+	Field frequency:Int
+	Field dutyCycle:Double
+	
+	Public
+	
+	Method New(chip:Int, channel:Int, frequency:Int = 400, dutyCycle:Double = 0.5)
+		Self.chip = chip
+		Self.channel = channel
+		Self.chipPath = "/sys/class/pwm/pwmchip" + chip
+		Self.channelPath = chipPath + "/pwm" + channel
+		
+		Validate()
+		Open()
+		
+		' avoid opening the file for operations changing relatively frequently
+		dutyCycleStream = OpenStream("utf8::" + channelPath + "/duty_cycle", True)
+		frequencyStream = OpenStream("utf8::" + channelPath + "/period", True)
+
+		SetFrequency(frequency)
+		SetDutyCycle(dutyCycle)
+	End Method
+
+Private
+	Method Validate()
+		If Not FileType(chipPath) Then
+			Throw New TArgumentException("The chip number " + chip + " is invalid or is not enabled.")
+		End If
+		
+		Local npwmPath:String = chipPath + "/npwm"
+		
+		Try
+			Local s:String = LoadText(npwmPath)
+			Local numberOfSupportedChannels:Int = s.ToInt()
+			
+			If channel < 0 Or channel >= numberOfSupportedChannels Then
+				Throw New TArgumentException("The PWM chip " + chip + " does not support the channel " + channel + ".")
+			End If
+			
+		Catch e:Object
+			Throw New TArgumentException("Unable to parse the number of supported channels at " + npwmPath)
+		End Try
+	End Method
+
+	Method Close()
+		If FileType(channelPath) = FILETYPE_DIR Then
+			SaveText(channel, chipPath + "/unexport", ETextStreamFormat.UTF8, False)
+		End If
+	End Method
+
+	Method Open()
+		If Not FileType(channelPath) Then
+			SaveText(channel, chipPath + "/export", ETextStreamFormat.UTF8, False)
+		End If
+	End Method
+Public
+	Rem
+	bbdoc: Returns the frequency in hertz.
+	End Rem
+	Method GetFrequency:Int()
+		Return frequency
+	End Method
+	
+	Rem
+	bbdoc: Gets the frequency period, in nanoseconds.
+	End Rem
+	Function GetPeriodInNanoseconds:Int(frequency:Int)
+		Return ((1.0:Double / frequency) * 1000000000)
+	End Function
+	
+	Rem
+	bbdoc: Sets the frequency in hertz.
+	End Rem
+	Method SetFrequency(value:Int)
+		If value < 0 Then
+			Throw New TArgumentOutOfRangeException("Value must not be negative.")
+		End If
+		
+		Local periodInNanoseconds:Int = GetPeriodInNanoseconds(value)
+		frequencyStream.SetSize(0)
+		frequencyStream.WriteString(periodInNanoseconds)
+		frequencyStream.Flush()
+		frequency = value
+	End Method
+	
+	Rem
+	bbdoc: Returns the duty cycle represented as a value between 0.0 and 1.0.
+	End Rem
+	Method GetDutyCycle:Double()
+		Return dutyCycle
+	End Method
+	
+	Rem
+	bbdoc: Sets the duty cycle represented as a value between 0.0 and 1.0.
+	End Rem
+	Method SetDutyCycle(value:Double)
+		If value < 0 Or value > 1 Then
+			Throw New TArgumentOutOfRangeException("Value must be between 0.0 and 1.0.")
+		End If
+		
+		' In Linux, the period needs to be a whole number and can't have decimal point.
+		Local dutyCycleInNanoseconds:Int = GetPeriodInNanoseconds(frequency) * value
+		dutyCycleStream.SetSize(0)
+		dutyCycleStream.WriteString(dutyCycleInNanoseconds)
+		dutyCycleStream.Flush()
+		dutyCycle = value
+	End Method
+	
+	Rem
+	bbdoc: Starts the PWM channel.
+	End Rem
+	Method Start()
+		SaveText("1", channelPath + "/enable", ETextStreamFormat.UTF8, False)
+	End Method
+	
+	Rem
+	bbdoc: Stops the PWM channel.
+	End Rem
+	Method Stop()
+		SaveText("0", channelPath + "/enable", ETextStreamFormat.UTF8, False)
+	End Method
+
+	Method Dispose() Override
+		dutyCycleStream.Close()
+		frequencyStream.Close()
+		Close()
+	End Method
+	
+End Type

+ 263 - 0
core.mod/gpio/spi/spidevice.bmx

@@ -0,0 +1,263 @@
+' Copyright (c) .NET Foundation and Contributors
+' Copyright (c) 2019 Bruce A Henderson
+' 
+' All rights reserved.
+' 
+' Permission is hereby granted, free of charge, to any person obtaining a copy
+' of this software and associated documentation files (the "Software"), to deal
+' in the Software without restriction, including without limitation the rights
+' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+' copies of the Software, and to permit persons to whom the Software is
+' furnished to do so, subject to the following conditions:
+' 
+' The above copyright notice and this permission notice shall be included in all
+' copies or substantial portions of the Software.
+' 
+' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+' OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+' SOFTWARE.
+' 
+SuperStrict
+
+Import brl.threads
+
+Import "../../common.bmx"
+
+Rem
+bbdoc: Represents a SPI communication channel.
+End Rem
+Type TSpiDevice
+
+	Const DEFAULT_DEVICE_PATH:String = "/dev/spidev"
+	Const SPI_IOC_MESSAGE_1:UInt = $40206b00
+
+Private
+
+	Field deviceFileDescriptor:Int = -1
+	Field ReadOnly settings:TSpiConnectionSettings
+	Field devicePath:String
+
+	Field initializationLock:TMutex = TMutex.Create()
+Public
+
+	Method New(settings:TSpiConnectionSettings)
+		Self.settings = settings
+		devicePath = DEFAULT_DEVICE_PATH
+	End Method
+
+Private
+	Method Initialize()
+		If deviceFileDescriptor >= 0 Then
+			Return
+		End If
+		
+		Try
+			initializationLock.Lock()
+			
+			If deviceFileDescriptor >= 0 Then
+				Return
+			End If
+		
+			Local deviceFileName:String = devicePath + settings.GetBusId() + "." + settings.GetChipSelectLine()
+		
+			deviceFileDescriptor = open_(deviceFileName, O_RDWR)
+		
+			If deviceFileDescriptor < 0 Then
+				Throw New TIOException("Cannot open SPI device file '" + deviceFileName + " '.")
+			End If
+			
+			Local Mode:ESpiMode = SpiSettingsToSpiMode()
+
+			Local result:Int = ioctlsp_(deviceFileDescriptor, ESpiSettings.SPI_IOC_WR_MODE.Ordinal(), Mode)
+			
+			If result = -1 Then
+				Throw New TIOException("Cannot set SPI mode to " + settings.Mode.ToString())
+			End If
+			
+			Local dataLengthInBits:Byte = settings.GetDataBitLength()
+			
+			result = ioctl_(deviceFileDescriptor, ESpiSettings.SPI_IOC_WR_BITS_PER_WORD.Ordinal(), Varptr dataLengthInBits)
+			
+			If result = -1 Then
+				Throw New TIOException("Cannot set SPI data bit length to " + settings.GetDataBitLength())
+			End If
+
+			Local clockFrequency:Int = settings.GetClockFrequency()
+
+			result = ioctli_(deviceFileDescriptor, ESpiSettings.SPI_IOC_WR_MAX_SPEED_HZ.Ordinal(), clockFrequency)
+
+			If result = -1 Then
+				Throw New TIOException("Cannot set SPI clock frequency to " + settings.GetClockFrequency())
+			End If
+			
+		Finally
+			initializationLock.Unlock()
+		End Try
+	End Method
+
+	Method SpiSettingsToSpiMode:ESpiMode()
+		
+		Local Mode:ESpiMode = settings.GetMode()
+		
+		If settings.GetChipSelectLineActiveState() = EPinValue.High Then
+			Mode :| ESpiMode.SPI_CS_HIGH
+		End If
+
+		If settings.GetDataFlow() = EDataFlow.LsbFirst Then
+			Mode :| ESpiMode.SPI_LSB_FIRST
+		End If
+		
+		Return Mode
+
+	End Method
+
+	Method Transfer(writeBuffer:Byte Ptr, readBuffer:Byte Ptr, length:UInt)
+	
+		Local tr:spi_ioc_transfer = New spi_ioc_transfer(writeBuffer, readBuffer, length, UInt(settings.GetClockFrequency()), Byte(settings.GetDataBitLength()), 0)
+		
+		Local result:Int = ioctl_(deviceFileDescriptor, SPI_IOC_MESSAGE_1, tr)
+		
+		If result < 1 Then
+			Throw New TIOException("Error performing SPI data transfer.")
+		End If
+	
+	End Method
+	
+Public
+	Method GetConnectionSettings:TSpiConnectionSettings()
+	End Method
+	
+	Method ReadByte:Byte()
+		Initialize()
+		
+		Local result:Byte
+		Transfer(Null, Varptr result, 1)
+		
+		Return result
+	End Method
+	
+	Method Read(buffer:Byte Ptr, length:UInt)
+		Initialize()
+		
+		Transfer(Null, buffer, length)
+	End Method
+	
+	Method WriteByte(value:Byte)
+		Initialize()
+		
+		Transfer(Varptr value, Null, 1)
+	End Method
+	
+	Method Write(buffer:Byte Ptr, length:UInt)
+		Initialize()
+		
+		Transfer(buffer, Null, length)
+	End Method
+	
+	Method TransferFullDuplex(writeBuffer:Byte Ptr, writeLength:UInt, readBuffer:Byte Ptr, readLength:UInt)
+		Initialize()
+		
+		If writeLength <> readLength Then
+			Throw New TArgumentException("Parameters 'writeLength' and 'readLength' must have the same length.")
+		End If
+		
+		Transfer(writeBuffer, readBuffer, writeLength)
+	End Method
+
+	Method Dispose()
+		If deviceFileDescriptor >= 0 Then
+			close_(deviceFileDescriptor)
+			deviceFileDescriptor = -1
+		End If
+	End Method
+	
+End Type
+
+
+
+Type TSpiConnectionSettings
+
+	Field busId:Int
+	Field chipSelectLine:Int
+	Field Mode:ESpiMode = ESpiMode.SPI_Mode_0
+	Field dataBitLength:Int = 8 ' 1 byte
+	Field clockFrequency:Int = 500000 ' 500 KHz
+	Field dataFlow:EDataFlow = EDataFlow.MsbFirst
+	Field chipSelectLineActiveState:EPinValue = EPinValue.Low
+	
+	Method New(busId:Int, chipSelectLine:Int)
+		Self.busId = busId
+		Self.chipSelectLine = chipSelectLine
+	End Method
+	
+	Method New(other:TSpiConnectionSettings)
+		busId = other.busId
+		chipSelectLine = other.chipSelectLine
+		Mode = other.Mode
+		dataBitLength = other.dataBitLength
+		clockFrequency = other.clockFrequency
+		dataFlow = other.dataFlow
+		chipSelectLineActiveState = other.chipSelectLineActiveState
+	End Method
+	
+	Method GetBusId:Int()
+		Return busId
+	End Method
+	
+	Method SetBusId(value:Int)
+		busId = value
+	End Method
+	
+	Method GetChipSelectLine:Int()
+		Return chipSelectLine
+	End Method
+	
+	Method SetChipSelectLine(value:Int)
+		chipSelectLine = value
+	End Method
+	
+	Method GetMode:ESpiMode()
+		Return Mode
+	End Method
+	
+	Method SetMode(value:EspiMode)
+		Mode = value
+	End Method
+	
+	Method GetDataFlow:EDataFlow()
+		Return dataFlow
+	End Method
+	
+	Method SetDataFlow(value:EDataFlow)
+		dataFlow = value
+	End Method
+	
+	Method GetChipSelectLineActiveState:EPinValue()
+		Return chipSelectLineActiveState:EPinValue
+	End Method
+	
+	Method SetChipSelectLineActiveState(value:EPinValue)
+		chipSelectLineActiveState:EPinValue = value
+	End Method
+	
+	Method GetDataBitLength:Int()
+		Return dataBitLength
+	End Method
+	
+	Method SetDataBitLength(value:Int)
+		dataBitLength = value
+	End Method
+	
+	Method GetClockFrequency:Int()
+		Return clockFrequency
+	End Method
+	
+	Method SetClockFrequency(value:Int)
+		clockFrequency = value
+	End Method
+	
+End Type