diff --git a/src/devices/Seesaw/README.md b/src/devices/Seesaw/README.md index e6f3977b57f25e2c8dbc93ad16012b0f0b124d82..76fa09f694980d6c7c1cd7f5941f6f9516c9f8cc 100644 --- a/src/devices/Seesaw/README.md +++ b/src/devices/Seesaw/README.md @@ -76,6 +76,12 @@ This sample duplicates the functionality of the rpi-more-blinking-lights sample ![eesaw Sample Blinking Lights](SeesawSampleBlinkingLights_bb.png) +### Connecting to a Seesaw based rotary encoder sample + +This sample connects a Raspberry Pi to an Adafruit I2C QT Rotary Encoder + +![Seesaw sample encoder](SeesawSampleEncoder.png) + ## Binding Notes When using Seesaw devices with a Raspberry Pi it has been observed that errors sometimes happen on the I2C bus. The nature of this error may be the 'clock stretching' [bug](http://www.advamation.com/knowhow/raspberrypi/rpi-i2c-bug.html) or may just be that the breakout board cannot accommodate the default I2C speed. @@ -96,4 +102,4 @@ In general the Seesaw technology allows user the embedding of the following type * [X] EEPROM (although untested) * [X] Capacitive Touch * [ ] Keypad -* [ ] Rotary Encoder +* [X] Rotary Encoder diff --git a/src/devices/Seesaw/Seesaw.sln b/src/devices/Seesaw/Seesaw.sln index 6b1aa7e1761d527435859e1c6527b2577bb2553d..4bc275435682a970e267bed7e5b0eae9d1808187 100644 --- a/src/devices/Seesaw/Seesaw.sln +++ b/src/devices/Seesaw/Seesaw.sln @@ -1,17 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{A15390DB-F07B-4014-9840-5F792089E026}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seesaw.Sample.BlinkingLights", "samples\Seesaw.Sample.BlinkingLights.csproj", "{413D2811-7A97-44AC-9D82-099209920BBE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Seesaw.Sample.BlinkingLights", "samples\Seesaw.Sample.BlinkingLights.csproj", "{413D2811-7A97-44AC-9D82-099209920BBE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seesaw.Sample.Capabilities", "samples\Seesaw.Sample.Capabilities.csproj", "{5807BB95-184F-47CC-A8A4-63519079B525}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Seesaw.Sample.Capabilities", "samples\Seesaw.Sample.Capabilities.csproj", "{5807BB95-184F-47CC-A8A4-63519079B525}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seesaw.Sample.SoilSensor", "samples\Seesaw.Sample.SoilSensor.csproj", "{164336C0-7E90-4082-B98D-0AEBB60717E3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Seesaw.Sample.SoilSensor", "samples\Seesaw.Sample.SoilSensor.csproj", "{164336C0-7E90-4082-B98D-0AEBB60717E3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seesaw", "Seesaw.csproj", "{F87E6203-1DA1-4648-9A1C-AB39C0CDE7E7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Seesaw", "Seesaw.csproj", "{F87E6203-1DA1-4648-9A1C-AB39C0CDE7E7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Seesaw.Sample.Encoder", "samples\Seesaw.Sample.Encoder.csproj", "{2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,9 +24,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {413D2811-7A97-44AC-9D82-099209920BBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {413D2811-7A97-44AC-9D82-099209920BBE}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -74,10 +73,29 @@ Global {F87E6203-1DA1-4648-9A1C-AB39C0CDE7E7}.Release|x64.Build.0 = Release|Any CPU {F87E6203-1DA1-4648-9A1C-AB39C0CDE7E7}.Release|x86.ActiveCfg = Release|Any CPU {F87E6203-1DA1-4648-9A1C-AB39C0CDE7E7}.Release|x86.Build.0 = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|x64.Build.0 = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Debug|x86.Build.0 = Debug|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|Any CPU.Build.0 = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|x64.ActiveCfg = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|x64.Build.0 = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|x86.ActiveCfg = Release|Any CPU + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {413D2811-7A97-44AC-9D82-099209920BBE} = {A15390DB-F07B-4014-9840-5F792089E026} {5807BB95-184F-47CC-A8A4-63519079B525} = {A15390DB-F07B-4014-9840-5F792089E026} {164336C0-7E90-4082-B98D-0AEBB60717E3} = {A15390DB-F07B-4014-9840-5F792089E026} + {2AF6BC30-E051-45A3-AA35-9CBB7E6352BA} = {A15390DB-F07B-4014-9840-5F792089E026} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FAE88427-BD54-45C3-B93A-0FCBC92E93F0} EndGlobalSection EndGlobal diff --git a/src/devices/Seesaw/SeesawEncoder.cs b/src/devices/Seesaw/SeesawEncoder.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fd4d771fd4aa1e6d2d9f70303178cfdfc9c99a0 --- /dev/null +++ b/src/devices/Seesaw/SeesawEncoder.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; + +namespace Iot.Device.Seesaw +{ + public partial class Seesaw : IDisposable + { + /// + /// Read the current position of the encoder. + /// + /// Which encoder to use, defaults to 0. + /// The encoder position as a 32 bit signed integer. + /// The hardware does not support Adafruit SeeSaw encoder functionality. + public int GetEncoderPosition(byte encoder = 0) + { + if (!HasModule(SeesawModule.Encoder)) + { + throw new InvalidOperationException($"The hardware on I2C Bus {I2cDevice.ConnectionSettings.BusId}, Address 0x{I2cDevice.ConnectionSettings.DeviceAddress:X2} does not support Adafruit SeeSaw encoder functionality"); + } + + return BinaryPrimitives.ReadInt32BigEndian(Read(SeesawModule.Encoder, SeesawFunction.EncoderPosition + encoder, 4, 8000)); + } + + /// + /// Set the current position of the encoder. + /// + /// Encoder position. + /// Which encoder to use, defaults to 0. + /// The hardware does not support Adafruit SeeSaw encoder functionality. + public void SetEncoderPosition(int position, byte encoder = 0) + { + if (!HasModule(SeesawModule.Encoder)) + { + throw new InvalidOperationException($"The hardware on I2C Bus {I2cDevice.ConnectionSettings.BusId}, Address 0x{I2cDevice.ConnectionSettings.DeviceAddress:X2} does not support Adafruit SeeSaw encoder functionality"); + } + + Span buffer = stackalloc byte[4]; + BinaryPrimitives.WriteInt32BigEndian(buffer, position); + + Write(SeesawModule.Encoder, SeesawFunction.EncoderPosition + encoder, buffer); + } + + /// + /// The change in encoder position since it was last read. + /// + /// Which encoder to use, defaults to 0. + /// The encoder change as a 32 bit signed integer. + /// The hardware does not support Adafruit SeeSaw encoder functionality. + public int GetEncoderDelta(byte encoder = 0) + { + if (!HasModule(SeesawModule.Encoder)) + { + throw new InvalidOperationException($"The hardware on I2C Bus {I2cDevice.ConnectionSettings.BusId}, Address 0x{I2cDevice.ConnectionSettings.DeviceAddress:X2} does not support Adafruit SeeSaw encoder functionality"); + } + + return BinaryPrimitives.ReadInt32BigEndian(Read(SeesawModule.Encoder, SeesawFunction.EncoderDelta + encoder, 4, 8000)); + } + + /// + /// Enable the interrupt to fire when the encoder changes position. + /// + /// Which encoder to use, defaults to 0. + /// The hardware does not support Adafruit SeeSaw encoder functionality. + public void EnableEncoderInterrupt(byte encoder = 0) + { + if (!HasModule(SeesawModule.Encoder)) + { + throw new InvalidOperationException($"The hardware on I2C Bus {I2cDevice.ConnectionSettings.BusId}, Address 0x{I2cDevice.ConnectionSettings.DeviceAddress:X2} does not support Adafruit SeeSaw encoder functionality"); + } + + WriteByte(SeesawModule.Encoder, SeesawFunction.EncoderIntenset + encoder, 0x01); + } + + /// + /// Disable the interrupt from firing when the encoder changes. + /// + /// Which encoder to use, defaults to 0. + /// The hardware does not support Adafruit SeeSaw encoder functionality. + public void DisableEncoderInterrupt(byte encoder = 0) + { + if (!HasModule(SeesawModule.Encoder)) + { + throw new InvalidOperationException($"The hardware on I2C Bus {I2cDevice.ConnectionSettings.BusId}, Address 0x{I2cDevice.ConnectionSettings.DeviceAddress:X2} does not support Adafruit SeeSaw encoder functionality"); + } + + WriteByte(SeesawModule.Encoder, SeesawFunction.EncoderIntenclr + encoder, 0x01); + } + } +} diff --git a/src/devices/Seesaw/SeesawModules.cs b/src/devices/Seesaw/SeesawModules.cs index 08c371068b16839a9f75f2eca19b9ca176416dc8..4e9e6ad48364a5c8ae1a3b3d47e74ad49ef2ce51 100644 --- a/src/devices/Seesaw/SeesawModules.cs +++ b/src/devices/Seesaw/SeesawModules.cs @@ -138,6 +138,24 @@ namespace Iot.Device.Seesaw /// Touch channel offset TouchChannelOffset = 0x10, + + // encoder functions + + /// Status + EncoderStatus = 0x00, + + /// Enable encoder interrupt + EncoderIntenset = 0x10, + + /// Clear encoder interrupt + EncoderIntenclr = 0x20, + + /// Encoder position + EncoderPosition = 0x30, + + /// Encoder position delta + EncoderDelta = 0x40 + } } } diff --git a/src/devices/Seesaw/SeesawSampleEncoder.fzz b/src/devices/Seesaw/SeesawSampleEncoder.fzz new file mode 100644 index 0000000000000000000000000000000000000000..0b6b75e403e5a1f6c67f0060eb831c523a053dc8 Binary files /dev/null and b/src/devices/Seesaw/SeesawSampleEncoder.fzz differ diff --git a/src/devices/Seesaw/SeesawSampleEncoder.png b/src/devices/Seesaw/SeesawSampleEncoder.png new file mode 100644 index 0000000000000000000000000000000000000000..49e18a6f90ff25b41c71d86841906f8186c27ee8 Binary files /dev/null and b/src/devices/Seesaw/SeesawSampleEncoder.png differ diff --git a/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.cs b/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.cs new file mode 100644 index 0000000000000000000000000000000000000000..7df8fc41b324e494fe3a08a3556cc396e2cb1aa1 --- /dev/null +++ b/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Device.Gpio; +using System.Device.I2c; +using Iot.Device.Seesaw; + +const byte AdafruitSeesawRotaryEncoderI2cAddress = 0x36; +const byte AdafruitSeesawRotaryEncoderI2cBus = 0x1; +const byte AdafruitSeesawRotaryEncoderPushSwitchPin = 24; + +const int EncoderPositionInitialValue = 100; +const int HostInterruptPin = 6; + +using GpioController gpioController = new(PinNumberingScheme.Logical); +using Seesaw seesawDevice = new(I2cDevice.Create(new I2cConnectionSettings(AdafruitSeesawRotaryEncoderI2cBus, AdafruitSeesawRotaryEncoderI2cAddress))); + +// set initial encoder position value +seesawDevice.SetEncoderPosition(EncoderPositionInitialValue); + +// enable interrupt for position changes on Seesaw encoder +seesawDevice.EnableEncoderInterrupt(); + +// enable interrupt for rotary encoder push switch +uint encoderPushSwitchPinMask = 1U << (AdafruitSeesawRotaryEncoderPushSwitchPin); +seesawDevice.SetGpioPinMode(AdafruitSeesawRotaryEncoderPushSwitchPin, PinMode.InputPullUp); +seesawDevice.SetGpioInterrupts(encoderPushSwitchPinMask, true); + +// enable host interrupt and register callback +gpioController.OpenPin(HostInterruptPin, PinMode.InputPullUp); +gpioController.RegisterCallbackForPinValueChangedEvent(HostInterruptPin, PinEventTypes.Falling, (s, e) => +{ + uint interruptFlags = seesawDevice.ReadGpioInterruptFlags(); + if ((interruptFlags & encoderPushSwitchPinMask) > 0) + { + // interrupt for push switch pin -> push switch status changed + bool pushSwitchReleased = seesawDevice.ReadGpioDigital(AdafruitSeesawRotaryEncoderPushSwitchPin); + Console.WriteLine($"Encoder switch {(pushSwitchReleased ? "released" : "pressed")}"); + return; + } + + int encoderPosition = seesawDevice.GetEncoderPosition(); + Console.WriteLine($"Encoder position changed: {encoderPosition}"); +}); + +// press Enter to exit application +Console.ReadLine(); diff --git a/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.csproj b/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.csproj new file mode 100644 index 0000000000000000000000000000000000000000..656c4af8169ddecc2d4ce9ea60ebbf0c97301e70 --- /dev/null +++ b/src/devices/Seesaw/samples/Seesaw.Sample.Encoder.csproj @@ -0,0 +1,13 @@ + + + Exe + $(DefaultSampleTfms) + false + + + + + + + +