2025년 10월 9일 목요일

오렌지파이에서 C#으로 푸쉬버튼 이벤트 받기..

라즈베리파리를 이용하여 GPIO를 처리하기 위해서 2핀짜리 푸쉬버튼을
GPIO와 GND에 연결하고 System.Device.GPIO 로 버튼을 입력방향으로 만들고
버튼이 눌려짐을 감지하여 등록되어있던 델리케이트를 실행하는 방향으로 처리될것이다.

하지만 오렌지 파이에서는 이 부분이 먹히지 않는다.
아마도 i2c나 UART-ttl에 비해서 GPIO부분이 뭔가 통일이 안된 느낌이다.

해서 어떻게 처리를 할것인가를 고민해보았는데 
결론적으로 오렌지파이에 OS는 데비안이 주력 일것이고..
그렇다면 GPIOD.so를 직접적으로 P/Invoke 방식으로 포인팅하여서
파일의 변화를 감지하여 변화시점에 역호출하는 방식을 이용하면 될듯하다.

그렇게 하기 위해서 일단은 apt install을 처리할것이 있는데
다음 명령으로 라이브러리를 설치해 놓자.

sudo apt update
sudo apt install libgpiod-dev

이렇게 받아놓으면 아래폴더에
/lib/aarch64-linux-gnu/libgpiod.so  파일이 생성되고

이를 이용하여 다음과 같이 DllImport 처리를 한다.


using System;
using System.Runtime.InteropServices;

namespace GpioTest;

/// <summary>
/// 네이티브 바인딩
/// </summary>
public static class NativeBindings
{
    //라이브러리 파일명
    private const string LibGpiod = "libgpiod.so";

    //gpiod.h 의 풀업플레그값
    public const int GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP = 1 << 8;

    // so 파일 메서드 시그니처
    [DllImport(LibGpiod, EntryPoint = "gpiod_chip_open_by_name")]
    public static extern IntPtr GpiodChipOpenByName(string name);

    [DllImport(LibGpiod, EntryPoint = "gpiod_chip_close")]
    public static extern void GpiodChipClose(IntPtr chip);

    [DllImport(LibGpiod, EntryPoint = "gpiod_chip_get_line")]
    public static extern IntPtr GpiodChipGetLine(IntPtr chip, uint offset);

    [DllImport(LibGpiod, EntryPoint = "gpiod_line_request_input_flags")]
    public static extern int GpiodLineRequestInputFlags(IntPtr line, string consumer, int flags);

    [DllImport(LibGpiod, EntryPoint = "gpiod_line_request_input")]
    public static extern int GpiodLineRequestInput(IntPtr line, string consumer);

    [DllImport(LibGpiod, EntryPoint = "gpiod_line_get_value")]
    public static extern int GpiodLineGetValue(IntPtr line);

    [DllImport(LibGpiod, EntryPoint = "gpiod_line_release")]
    public static extern void GpiodLineRelease(IntPtr line);

}


그런다음 이를 사용할 모니터링 클래스


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace GpioTest;

/// <summary>
/// 버튼정보 구조체
/// </summary>
public class GpioButtonConfig
{
    public IntPtr LinePtr = IntPtr.Zero;
    public uint Lineoffset { get; set; } = 0;
    public string Description { get; set; } = "";
    public int LastValue { get; set; } = 1;         // 1 (HIGH): 떼어짐 (풀업 가정)
    public Action<uint>? d_Actor = null;
}

/// <summary>
/// Gpio 모니터
/// </summary>
public class GpioMonitor: IDisposable
{
    private IntPtr _chipPtr = IntPtr.Zero;
    private string ChipName = "";
    private List<GpioButtonConfig> _buttonConfigs = new List<GpioButtonConfig>();
    
    // 전체 버튼의 디바운스 타임
    private const int DebounceDelayMs = 250;
    private DateTime _lastPushDebounceTime = DateTime.MinValue;
    private DateTime _lastPullDebounceTime = DateTime.MinValue;

    /// <summary>
    /// 초기화처리
    /// </summary>
    public bool Initialize(List<GpioButtonConfig> a_configs, string a_chipName = "gpiochip1")
    {
        //
        _buttonConfigs.AddRange(a_configs);
        ChipName = a_chipName;

        //
        try
        {
            // 1. 칩열기
            _chipPtr = NativeBindings.GpiodChipOpenByName(ChipName);
            if (_chipPtr == IntPtr.Zero)
            {
                throw new Exception($"칩 {ChipName} 열기 실패. 권한 문제확인.");
            }

            // 2. 등록된 모든 라인 초기화 및 요청
            for (int i = 0; i < _buttonConfigs.Count; i++)
            {
                GpioButtonConfig one_config = _buttonConfigs[i];
                IntPtr linePtr = NativeBindings.GpiodChipGetLine(_chipPtr, one_config.Lineoffset);
                if (linePtr == IntPtr.Zero)
                {
                    Console.WriteLine($"경고: 라인 {one_config.Lineoffset} 가져오기 실패. 건너뜀");
                    continue;
                }

                // 3. 라인 요청 (Input + Pull Up)
                int flags = NativeBindings.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
                int ret = NativeBindings.GpiodLineRequestInputFlags(linePtr, one_config.Description, flags);
                if (ret < 0)
                {
                    Console.WriteLine($"경고: 라인 {one_config.Lineoffset} 풀업요청 실패.");
                }

                //라인포인터 추가 및 액션 바인딩
                one_config.LinePtr = linePtr;
                one_config.LastValue = NativeBindings.GpiodLineGetValue(linePtr);
                one_config.d_Actor += (lineno) => System.Console.WriteLine($"눌린 라인번호{lineno}번.");
                Console.WriteLine($"라인 {one_config.Lineoffset} ({one_config.Description}) 설정 완료.");
            }

            //
            if (!_buttonConfigs.Any())
            {
                throw new Exception("모니터링할 유효한 GPIO라인이 없습니다.");
            }

            //
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"\n 초기화 실패: {ex.Message}");
            Dispose();
            return false;
        }
    }


    /// <summary>
    /// 모니터링 시작
    /// </summary>
    public void StartMonitoring()
    {
        //루핑
        while (true)
        {
            foreach (var item in _buttonConfigs.Where(c => c.LinePtr != IntPtr.Zero))
            {
                int currentValue = NativeBindings.GpiodLineGetValue(item.LinePtr);

                //
                if (currentValue == 0 && item.LastValue == 1)
                {
                    //버튼 눌림 감지
                    DateTime nowtime = DateTime.Now;
                    if ((nowtime - _lastPushDebounceTime).TotalMilliseconds > DebounceDelayMs)
                    {
                        Console.WriteLine($"버튼 눌림 감지: {item.Description}");
                        item.d_Actor?.Invoke(item.Lineoffset);
                        _lastPushDebounceTime = nowtime;
                    }
                }
                else if (currentValue == 1 && item.LastValue == 0)
                {
                    //버튼 떼짐 감지
                    DateTime nowtime = DateTime.Now;
                    if ((nowtime - _lastPullDebounceTime).TotalMilliseconds > DebounceDelayMs)
                    {
                        Console.WriteLine($"버튼 떼임 감지: {item.Description}");
                        _lastPullDebounceTime = nowtime;
                    }
                }

                // 상태 업데이트
                item.LastValue = currentValue;
            }
            //한바퀴 다돌고 5ms Sleep
            Thread.Sleep(5);    
        }
    }

    /// <summary>
    /// 자원 해지
    /// </summary>
    public void Dispose()
    {
        foreach (var config in _buttonConfigs)
        {
            NativeBindings.GpiodLineRelease(config.LinePtr);
        }
        if (_chipPtr != IntPtr.Zero)
        {
            NativeBindings.GpiodChipClose(_chipPtr);
            Console.WriteLine("GPIO 자원 해제 완료.");
        }
    }
}


이렇게 한후 콘솔 메인에서 다음처리 실행해보면


using System;
using System.Collections.Generic;

namespace GpioTest;


/// <summary>
/// 메인 클래스
/// </summary>
public class MainApp
{
    /// <summary>
    /// 주진입점
    /// doent빌드 명령어: dotnet publish -c Release -r linux-arm64
    /// </summary>
    public static void Main(string[] args)
    {
        //모니터링할 버튼 리스트 정의
        //(PC6=70)
        var buttonConfigurations = new List<GpioButtonConfig>
        {
            new GpioButtonConfig { Lineoffset = 70, Description = "Button PC6" },
        };

        // GpioMonitor 인스턴스를 using 구문으로 생성하여 자동 해제되도록 처리
        using var monitor = new GpioMonitor();
        bool bsetup = monitor.Initialize(buttonConfigurations);
        if (!bsetup)
        {
            System.Console.WriteLine("GPIO버튼 초기화 실패!!");
            Environment.Exit(0);
        }

        //콘솔 종료키 감지
        Console.CancelKeyPress += (sender, e) =>
        {
            System.Console.WriteLine(Environment.NewLine + "종료요청 감지..");
            e.Cancel = true;
            Environment.Exit(0);
        };

        //모니터링 시작
        try
        {
            monitor.StartMonitoring();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"런타임 오류: {ex.Message}");
        }
    }
}




실행해보면 정상적으로 콘솔내용이 출력된다.  예시는 PC6를 예로 들었지만
다른 GPIO핀이라던가 복수개의 chip1에 있는 핀이라면
눌림 이벤트를 제어할수 있다.





ㅇㅇ





댓글 없음:

댓글 쓰기