Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add event handling methods #2140

Merged
merged 7 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/devices/Mcp23xxx/Mcp23x1x.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,25 @@ protected Mcp23x1x(BusAdapter device, int reset, int interruptA, int interruptB,
/// Reads the interrupt pin for the given port if configured.
/// </summary>
public PinValue ReadInterrupt(Port port) => InternalReadInterrupt(port);

/// <summary>
/// Reads all bits of port A in a single operation.
/// </summary>
/// <returns></returns>
pgrawehr marked this conversation as resolved.
Show resolved Hide resolved
public int ReadPortA()
{
int value = ReadByte(Register.GPIO, Port.PortA);
return value;
}

/// <summary>
/// Reads all bits of port B in a single operation.
/// </summary>
/// <returns></returns>
pgrawehr marked this conversation as resolved.
Show resolved Hide resolved
public int ReadPortB()
{
int value = ReadByte(Register.GPIO, Port.PortB);
return value;
}
}
}
278 changes: 266 additions & 12 deletions src/devices/Mcp23xxx/Mcp23xxx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Device.Gpio;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;

Expand All @@ -18,6 +20,7 @@ public abstract partial class Mcp23xxx : GpioDriver
private readonly int _interruptA;
private readonly int _interruptB;
private readonly Dictionary<int, PinValue> _pinValues = new Dictionary<int, PinValue>();
private readonly ConcurrentDictionary<int, PinChangeEventHandler> _eventHandlers = new ConcurrentDictionary<int, PinChangeEventHandler>();
private BankStyle _bankStyle;
private GpioController? _controller;
private bool _shouldDispose;
Expand Down Expand Up @@ -177,7 +180,7 @@ protected void InternalWriteByte(Register register, byte value, Port port)
/// Read a byte from the given register.
/// </summary>
/// <remarks>
/// Writes to the A port registers on 16 bit devices.
/// Reads from the A port registers on 16 bit devices.
/// </remarks>
public byte ReadByte(Register register) => InternalReadByte(register, Port.PortA);

Expand Down Expand Up @@ -289,7 +292,7 @@ public void Enable()
/// Reads interrupt value
/// </summary>
/// <param name="port">Port to read interrupt on</param>
/// <returns>Value of intterupt pin</returns>
/// <returns>Value of interrupt pin</returns>
protected PinValue InternalReadInterrupt(Port port)
{
int pinNumber = port switch
Expand All @@ -315,6 +318,10 @@ protected PinValue InternalReadInterrupt(Port port)
/// </returns>
public PinValue ReadInterrupt() => InternalReadInterrupt(Port.PortA);

private byte SetBit(byte data, int bitNumber) => (byte)(data | (1 << bitNumber));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can be static


private byte ClearBit(byte data, int bitNumber) => (byte)(data & ~(1 << bitNumber));

/// <summary>
/// Sets a mode to a pin.
/// </summary>
Expand All @@ -329,10 +336,6 @@ protected override void SetPinMode(int pinNumber, PinMode mode)

ValidatePin(pinNumber);

byte SetBit(byte data, int bitNumber) => (byte)(data | (1 << bitNumber));

byte ClearBit(byte data, int bitNumber) => (byte)(data & ~(1 << bitNumber));

if (pinNumber < 8)
{
byte value = mode == PinMode.Output
Expand Down Expand Up @@ -564,20 +567,271 @@ protected override PinMode GetPinMode(int pinNumber)
}
}

/// <inheritdoc/>
/// <summary>
/// Enables interrupts for a specified pin. On 16-Pin devices, Pins 0-7 trigger the INTA pin and Pins 8-15
/// trigger the INTB pin. The interrupt signals are configured as active-low.
/// </summary>
/// <param name="pinNumber">The pin number for which an interrupt shall be triggered</param>
/// <param name="eventTypes">Event(s) that should trigger the interrupt on the given pin</param>
/// <exception cref="ArgumentException">EventTypes is not valid (must have at least one event type selected)</exception>
public void EnableInterruptOnChange(int pinNumber, PinEventTypes eventTypes)
{
byte oldValue, newValue;
if (eventTypes == PinEventTypes.None)
{
throw new ArgumentException("No event type specified");
}

Port port = Port.PortA;
if (pinNumber >= 8)
{
pinNumber -= 8;
port = Port.PortB;
}

// Set the corresponding bit in the GPINTEN (Interrupt-on-Change) register
oldValue = InternalReadByte(Register.GPINTEN, port);
newValue = SetBit(oldValue, pinNumber);
InternalWriteByte(Register.GPINTEN, newValue, port);
oldValue = InternalReadByte(Register.INTCON, port);
// If the interrupt shall happen on either edge, we clear the INTCON (Interrupt-on-Change-Control) register,
// which will trigger an interrupt on every change. Otherwise, set the INTCON register bit and set the
// DefVal register.
if (eventTypes == (PinEventTypes.Falling | PinEventTypes.Rising))
{
newValue = ClearBit(oldValue, pinNumber);
}
else
{
newValue = SetBit(oldValue, pinNumber);
}

InternalWriteByte(Register.INTCON, newValue, port);

oldValue = InternalReadByte(Register.DEFVAL, port);
// If we clear the bit, the interrupt occurs on a rising edge, if we set it, it occurs on a falling edge.
// If INTCON is clear, the value is ignored.
if (eventTypes == PinEventTypes.Rising)
{
newValue = ClearBit(oldValue, pinNumber);
}
else
{
newValue = SetBit(oldValue, pinNumber);
}

InternalWriteByte(Register.DEFVAL, newValue, port);

// Finally make sure that IOCON.ODR is low and IOCON.INTPOL is low, too (interrupt is low-active, the default)
// For this register, it doesn't matter which port we use, it exists only once.
oldValue = InternalReadByte(Register.IOCON, Port.PortA);
newValue = ClearBit(oldValue, 1);
newValue = ClearBit(newValue, 2);
InternalWriteByte(Register.IOCON, newValue, Port.PortA);
}

private static Port GetPortForPinNumber(int pinNumber)
{
Port port = Port.PortA;
if (pinNumber >= 8)
{
port = Port.PortB;
}

return port;
}

/// <summary>
/// Disables triggering interrupts on a certain pin
/// </summary>
/// <param name="pinNumber">The pin number</param>
public void DisableInterruptOnChange(int pinNumber)
{
byte oldValue, newValue;
if (pinNumber < 8)
{
// Set the corresponding bit in the GPINTEN (Interrupt-on-Change) register
oldValue = InternalReadByte(Register.GPINTEN, Port.PortA);
newValue = ClearBit(oldValue, pinNumber);
InternalWriteByte(Register.GPINTEN, newValue, Port.PortA);
}
else
{
oldValue = InternalReadByte(Register.GPINTEN, Port.PortB);
newValue = ClearBit(oldValue, pinNumber);
InternalWriteByte(Register.GPINTEN, newValue, Port.PortB);
}
}

private void InterruptHandler(object sender, PinValueChangedEventArgs e)
{
Port port = e.PinNumber == _interruptA ? Port.PortA : Port.PortB;
int interruptPending = InternalReadByte(Register.INTF, port);
int newValues = InternalReadByte(Register.GPIO, port);
int offset = 0;
if (port == Port.PortB)
{
offset = 8;
}

int mask = 1;
int pin = 0;

while (mask < 0x10)
{
if ((interruptPending & mask) != 0)
{
CallHandlerOnPin(pin + offset, newValues & mask);
}

mask = mask << 1;
pin++;
}
}

/// <summary>
/// Calls the event handler for the given pin, if any.
/// </summary>
/// <param name="pin">Pin to call the event handler on (if any exists)</param>
/// <param name="valueFlag">Non-zero if the value is currently high (therefore assuming the pin value was rising), otherwise zero</param>
private void CallHandlerOnPin(int pin, int valueFlag)
{
if (_eventHandlers.TryGetValue(pin, out var handler))
{
handler.Invoke(this, new PinValueChangedEventArgs(valueFlag != 0 ? PinEventTypes.Rising : PinEventTypes.Falling, pin));
}
}

/// <summary>
/// Calls an event handler if the given pin changes.
/// </summary>
/// <param name="pinNumber">Pin number of the MCP23xxx</param>
/// <param name="eventTypes">Whether the handler should trigger on rising, falling or both edges</param>
/// <param name="callback">The method to call when an interrupt is triggered</param>
/// <exception cref="InvalidOperationException">There's no GPIO controller for the master interrupt configured, or no interrupt lines are configured for the
/// required port.</exception>
/// <remarks>Only one event handler can be registered per pin. Calling this again with a different handler for the same pin replaces the handler</remarks>
protected override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes,
PinChangeEventHandler callback) => throw new NotImplementedException();
PinChangeEventHandler callback)
{
if (_controller == null)
{
throw new InvalidOperationException("No GPIO controller available. Specify a GPIO controller and the relevant interrupt line numbers in the constructor");
}

EnableInterruptOnChange(pinNumber, eventTypes);
Port port = GetPortForPinNumber(pinNumber);
if (port == Port.PortA)
{
if (_interruptA < 0)
{
throw new InvalidOperationException("No GPIO pin defined for interrupt line A. Please specify an interrupt line in the constructor.");
}

if (!_eventHandlers.Any(x => x.Key <= 7))
{
_controller.RegisterCallbackForPinValueChangedEvent(_interruptA, PinEventTypes.Falling, InterruptHandler);
}

_eventHandlers[pinNumber] = callback;
InternalReadByte(Register.GPIO, Port.PortA); // Clear the interrupt flags
}
else
{
if (_interruptB < 0)
{
throw new InvalidOperationException("No GPIO pin defined for interrupt line B. Please specify an interrupt line in the constructor.");
}

if (!_eventHandlers.Any(x => x.Key >= 8))
{
_controller.RegisterCallbackForPinValueChangedEvent(_interruptB, PinEventTypes.Falling, InterruptHandler);
}

_eventHandlers[pinNumber] = callback;
InternalReadByte(Register.GPIO, Port.PortB); // Clear the interrupt flags
}
}

/// <inheritdoc/>
protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback) =>
throw new NotImplementedException();
protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback)
{
if (_controller == null)
{
// If we had any callbacks registered, this would have thrown up earlier.
throw new InvalidOperationException("No valid GPIO controller defined. And no callbacks registered either.");
}

if (_eventHandlers.TryRemove(pinNumber, out _))
{
Port port = GetPortForPinNumber(pinNumber);
if (port == Port.PortA)
{
if (!_eventHandlers.Any(x => x.Key <= 7))
{
_controller.UnregisterCallbackForPinValueChangedEvent(_interruptA, InterruptHandler);
}
}
else
{
if (!_eventHandlers.Any(x => x.Key >= 8))
{
_controller.UnregisterCallbackForPinValueChangedEvent(_interruptB, InterruptHandler);
}
}
}
}

/// <inheritdoc/>
protected override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) => pinNumber;

/// <inheritdoc/>
/// <summary>
/// Waits for an event to occur on the given pin.
/// </summary>
/// <param name="pinNumber">The pin on which to wait</param>
/// <param name="eventTypes">The event to wait for (rising, falling or either)</param>
/// <param name="cancellationToken">A timeout token</param>
/// <returns>The wait result</returns>
/// <remarks>This method should only be used on pins that are not otherwise used in event handling, as it clears any
/// existing event handlers for the same pin.</remarks>
protected override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes,
CancellationToken cancellationToken) => throw new NotImplementedException();
CancellationToken cancellationToken)
{
ManualResetEventSlim slim = new ManualResetEventSlim();
slim.Reset();
PinEventTypes eventTypes1 = PinEventTypes.None;
void InternalHandler(object sender, PinValueChangedEventArgs pinValueChangedEventArgs)
{
if (pinValueChangedEventArgs.PinNumber != pinNumber)
{
return;
}

if ((pinValueChangedEventArgs.ChangeType & eventTypes) != 0)
{
slim.Set();
}

eventTypes1 = pinValueChangedEventArgs.ChangeType;
}

AddCallbackForPinValueChangedEvent(pinNumber, eventTypes, InternalHandler);
slim.Wait(cancellationToken);
RemoveCallbackForPinValueChangedEvent(pinNumber, InternalHandler);

if (cancellationToken.IsCancellationRequested)
{
return new WaitForEventResult()
{
EventTypes = PinEventTypes.None, TimedOut = true
};
}

return new WaitForEventResult()
{
EventTypes = eventTypes1, TimedOut = false
};
}

/// <inheritdoc/>
protected override bool IsPinModeSupported(int pinNumber, PinMode mode) =>
Expand Down
Loading