dotnet / iot

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.
MIT License
2.13k stars 574 forks source link

Is it possible to open a pin with PinMode.Input and PinMode.Output? #1982

Closed INTERNALINTERFERENCE closed 1 year ago

INTERNALINTERFERENCE commented 1 year ago

I'm trying to write 1/0 to pin and listening all changes.

my code:

using System.Device.Gpio;
using System.Device.Gpio.Drivers;

var _gpioController = new GpioController(
    numberingScheme: PinNumberingScheme.Logical,
    driver: new SysFsDriver());

var relayPin = 128 + 21;
var highTime = 1000;
var lowTime = 200;

System.Console.WriteLine("start openning");

_gpioController.OpenPin(
    relayPin,
    PinMode.Input );

System.Console.WriteLine("opened");

System.Console.WriteLine("registering handlers");

_gpioController.RegisterCallbackForPinValueChangedEvent(
    pinNumber: relayPin,
    eventTypes: PinEventTypes.Rising,
    callback: (_, args) =>
    {
        Console.WriteLine($"{args.PinNumber} is rising");
    });

System.Console.WriteLine("try to set new pin mode");

_gpioController.SetPinMode(
    pinNumber: relayPin,
    mode: PinMode.Output);

System.Console.WriteLine("start relay test");

while (true)
{
    _gpioController.Write(relayPin, PinValue.High);
    Thread.Sleep(highTime);

    _gpioController.Write(relayPin, PinValue.Low);
    Thread.Sleep(lowTime);
}

Unfortunately only _gpioController.Write.. works. Is it possible to listen and to write?

raffaeler commented 1 year ago

Hello, you can't have both at the same time, this has nothing to do with the dotnet/iot library but how any microcontroller works. The solution is very simple: change the pin mode right after you finish reading/writing.

FYI If you instead need to "split" the read/write operations on two different pins, you can do it with a couple of components.

INTERNALINTERFERENCE commented 1 year ago

hmm may you give me an example please? I don't have any actions that will notify about finish reading/writing

raffaeler commented 1 year ago

I don't know your use-case but basically I can imagine you have 1-wire communication to a sensor.

Since there is the risk of collisions (both ends try to write), the 1-wire protocols typically avoid by establishing when you can write or when you should wait and just read. For example, you ask for a temperature by writing a command on the line and the sensor responds with a fixed number of bytes. In this case the sensor never tries to write without being asked to do so first.

The DHT11 and DHT22 (temperature/humidity) sensors are another good example of 1-wire protocol. We have a binding for them and you can see how you can change pinmode and making reads/writes in this method: https://github.com/dotnet/iot/blob/main/src/devices/Dhtxx/DhtBase.cs#L183

joperezr commented 1 year ago

@INTERNALINTERFERENCE the main issue with your code is that, as @raffaeler explained, you can't really have a pin operating both as input and output. The easiest way to do a hello world program that tests for detecting events uses two pins instead of just one. You connect them via a wire physically, and set one as input (and configure the callback exactly like you do in your code) and then you set the other pin as output. Then you write to the output pin 0s and 1s, and your callback should get fired every time that you write a 1 to the output pin after writing a 0.

INTERNALINTERFERENCE commented 1 year ago

@joperezr I have only one pin (it is relay) and I don't understand how can I get another pin. I tried to change pin mode right after writing and reading but it's not work. Maybe I'm too bad.. .

I tried following:

var _gpioController = new GpioController(
    numberingScheme: PinNumberingScheme.Logical,
    driver: new SysFsDriver());

var relayPin = 128 + 21;
var highTime = 1000;
var lowTime = 200;

_gpioController.OpenPin(
    relayPin,
    PinMode.Input);

_gpioController.RegisterCallbackForPinValueChangedEvent(
    pinNumber: relayPin,
    eventTypes: PinEventTypes.Rising,
    callback: (_, args) =>
    {
        Console.WriteLine($"{args.PinNumber} is rising");
    });

_gpioController.SetPinMode(
    relayPin,
    PinMode.Output);

while (true)
{
    _gpioController.Write(relayPin, PinValue.High);

    _gpioController.SetPinMode(
        relayPin,
        PinMode.Input);

    Thread.Sleep(highTime);

    _gpioController.SetPinMode(
        relayPin,
        PinMode.Output);

    _gpioController.Write(relayPin, PinValue.Low);

    _gpioController.SetPinMode(
        relayPin,
        PinMode.Input);

    Thread.Sleep(lowTime);
}
INTERNALINTERFERENCE commented 1 year ago

and my code doesn't work

INTERNALINTERFERENCE commented 1 year ago

I want to describe

I have two classes: InputLogicalControl, OutputLogicalControl. There is Initialize method in ILC and it contains following:

public void Initialize()
{
     var openedPin = OpenPin( pin );
     _gpioController.SetPinMode(
            pinNumber: openedPin, 
            mode: PinMode.Input );

     _gpioController.RegisterCallbackForPinValueChangedEvent( 
              pinNumber: openedPin,
              eventTypes: PinEventTypes.Rising,
              callback: ( _, args ) =>
              {
                  State = args.PinNumber switch
                  {
                      0 => LogicalStateType.Off,
                      1 => LogicalStateType.On,
                      _ => State
                  };
              } );

   _gpioController.RegisterCallbackForPinValueChangedEvent(
            pinNumber: openedPin,
            eventTypes: PinEventTypes.Falling,
            callback: ( _, args ) =>
            {
                State = args.PinNumber switch
                {
                    0 => LogicalStateType.Off,
                    1 => LogicalStateType.On,
                    _ => State
                };
            } );
}

OLC contains two methods: SetStateOn, SetStateOff. So I don't understand how I can realize logic for this methods. SetStateOn just should write 1 to pin without any notifications (as I see)

INTERNALINTERFERENCE commented 1 year ago

I can do smth like that:

public void SetStateOn()
{
   // avoid open pin because we already did this
   ...
    _gpioController.SetPinMode(
          relayPin,
          PinMode.Output);

    _gpioController.Write(
          relayPin,
          PinValue.High );

    _gpioController.SetPinMode(
          relayPin,
          PinMode.Input);          
}

But if we change the pin value, then we will no longer receive any notifications about pin value changes (ILC.Initialize).

raffaeler commented 1 year ago

Let me see if I understand correctly.

Did you try to unsubscribe and re-subscribe the notifications every time you switch the pinmode? The behavior you see (losing the notification) could depend on our driver but also on the underlying (native) driver.

The alternative solution @joperezr suggested is extremely simple and may resolve your issue. You just connect your single wire to two different pins of the board. Then you use one pin as read and the other as write. Be aware that using this wiring, you will also get back the "echo" of what you are writing.

I suggested instead to add some hardware (specifically a 7407 open collector IC) to avoid pushing the line to zero or one with the write pin, when you are reading.

INTERNALINTERFERENCE commented 1 year ago

Let's see my simple example

// open pin
_gpioController.OpenPin(pin, PinMode.Input);

// register handlers
_gpioController.RegisterCallbackForPinValueChangedEvent(
    pinNumber: relayPin,
    eventTypes: PinEventTypes.Rising,
    callback: (_, args) =>
    {
        Console.WriteLine($"{args.PinNumber} is rising");
    });

_gpioController.RegisterCallbackForPinValueChangedEvent(
    pinNumber: relayPin,
    eventTypes: PinEventTypes.Falling,
    callback: (_, args) =>
    {
        Console.WriteLine($"{args.PinNumber} is falling");
    });

ConsoleKeyInfo cki;
cki = Console.ReadKey();
if (cki.Key == ConsoleKey.C)
{
    // change pin mode because we need to write
     _gpioController.SetPinMode
    (
        relayPin,
        PinMode.Output
    );

    // write value to pin
    _gpioController.Write(
        relayPin,
        PinValue.High
    );

    // change pin mode because we need to listen
    _gpioController.SetPinMode
    (
        relayPin,
        PinMode.Input
    );
}

I have a problem with last two steps. I wrote the value to the pin and only then changed the pin modе. Thats why I don't see any notifications such as: {pinNumber} is rising, {pinNumber} is falling I don't understand how can I solve this problem

raffaeler commented 1 year ago

Did you try to re-subscribe the notification after writing?

INTERNALINTERFERENCE commented 1 year ago

Yes, it doesn't work

INTERNALINTERFERENCE commented 1 year ago

I can't handle notifications because PinMode is output while I writing

INTERNALINTERFERENCE commented 1 year ago

All I want is following behaviour:

  1. Press button and set 1/0 to pin
  2. See notification about this
INTERNALINTERFERENCE commented 1 year ago

now I can use this but it only writes without any notifications


_gpioController.OpenPin(
    relayPin,
    PinMode.Output);

while (true)
{
    _gpioController.Write(relayPin, PinValue.High);
    Thread.Sleep(highTime);

    _gpioController.Write(relayPin, PinValue.Low);
    Thread.Sleep(lowTime);
}
raffaeler commented 1 year ago

Please try unsubscribing before changing the pinmode to write and then resubscribe after changing it back to read. If this doesn't work it may be a bug.

In any case, if you expect the reads to happen almost immediately, notifications are not very helpful. You can just poll the line and detect the changes in a loop. The interval would be your sampling rate and depends on the speed of the devices that is writing. You should sample at least twice the speed of the device according to Nyquist theorem.

INTERNALINTERFERENCE commented 1 year ago

I think ur a little bit wrong. In my opinion if we resubscribe after changing the pin mode this is works: https://gist.github.com/INTERNALINTERFERENCE/063a433f98164ff5419b9905db57b9b0

thanks for help! By the way I found bug: Let's imagine that some pin has PinValue.Low and we need to change this to Pin.Value.High. Let's do it!

1. _controller.SetPinMode(pin, output);
2. _controller.Write(pin, PinValue.High);
// because we need to notify
3. _controller.SetPinMode(pin, input);

And after setting pin mode to input PinValue will be low again!

INTERNALINTERFERENCE commented 1 year ago

is is worth to create another issue?

raffaeler commented 1 year ago
1. _controller.SetPinMode(pin, output);
2. _controller.Write(pin, PinValue.High);
// because we need to notify
3. _controller.SetPinMode(pin, input);

And after setting pin mode to input PinValue will be low again!

If your device is pulling down the line, there is nothing you can do in software to prevent that. When you set to input the board leaves the gpio free to change its value. If you are reading a low value, the device is pulling it down. Try detaching the device from the gpio and read the pin value again.

Also, I see at the beginning of this thread that you are using the sysfs driver. If you are running on a supported board such as the raspberry pi, you should use the specific driver instead. Did you try it?

INTERNALINTERFERENCE commented 1 year ago

When you set to input the board leaves the gpio free to change its value

but.. if I change pin mode to output it will change value too

I use this board and I don't know what driver should I use sysfs works and it is good :)

INTERNALINTERFERENCE commented 1 year ago

using LibGpiodDriver I can't even open pin

raffaeler commented 1 year ago

but.. if I change pin mode to output it will change value too

When you read, the pin is in high impedence and the value only depends on the external sensor. Probably your device is pulling the value down, therefore you read a low. If you are waiting for the device to answer, you should just continue reading/sampling the value at regular intervals.

I use this board

I don't know that board, sorry.

INTERNALINTERFERENCE commented 1 year ago

thanks for help! I appreciate this