esl / erlang_ale

Erlang Actor Library for Embedded -- An embedded framework from Erlang Solutions
Apache License 2.0
207 stars 65 forks source link

Unexpected GPIO interrupt #26

Closed ethrbh closed 9 years ago

ethrbh commented 9 years ago

hello Frank,

Sorry that I write a new ticket, but I think I have found a new fault in the latest Erlang/ALE, surround GPIO interrupt handling. My observation is that an "unexpected" interrupt on the GPIO, at least my registered process has received a message from gpio driver if, I do NOT read GPIO value after register interrupt but before set interrupt. Below example I use GPIO-17 as an input, and wired to GROUND.

Here is how I mean this: Unexpected interrupt occurred if I do the following steps {ok, Gpio17} = gpio:start_link(17, input). gpio:register_int(Gpio17,MY PROCESS PID). gpio:set_int(Gpio17,rising).

No unexpected interrupt if I do this: {ok, Gpio17} = gpio:start_link(17, input). gpio:register_int(Gpio17,MY PROCESS PID). gpio:read(Gpio17). gpio:set_int(Gpio17,rising).

At the end of this ticket you can see the whole example module I wrote to test this GPIO interrupt, but please find its printout here:

First run when NO GPIO read has been done and unexpected interrupt occurred 1> ex_gpio_int:start_1(). {ok,<0.35.0>}

=INFO REPORT==== 27-Apr-2015::21:05:59 ===
    "Interrupt has been occured on GPIO."
    gpio: 17
    interrupt_condition: rising
2>

2nd run when read GPIO value before call gpio:set_int/2 1> ex_gpio_int:start_2(). {ok,<0.35.0>} 2>

Please let me know if I did something wrong, or confirm me if this is a fault in ALE.

Here comes the example module %% @author ethrbh %% @doc @todo Add description to ex_gpio_int.

-module(ex_gpio_int).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

%% ====================================================================
%% API functions
%% ====================================================================
-export([start_1/0, start_2/0]).

%% ====================================================================
%% Defines
%% ====================================================================
-define(SERVER, ?MODULE).
-define(DO_NOT_READ_PIN_VALUE_BEFORE_SET_INT, do_not_read_pin_value).
-define(READ_PIN_VALUE_BEFORE_SET_INT, read_pin_value).
-define(INT_PIN, 17).

start_1() ->
    case whereis(?SERVER) of
        P when is_pid(P) ->
            {ok, P};
        _->
            gen_server:start({local, ?SERVER}, ?MODULE, [?DO_NOT_READ_PIN_VALUE_BEFORE_SET_INT], [{timeout, 1000}])
    end.

start_2() ->
    case whereis(?SERVER) of
        P when is_pid(P) ->
            {ok, P};
        _->
            gen_server:start({local, ?SERVER}, ?MODULE, [?READ_PIN_VALUE_BEFORE_SET_INT], [{timeout, 1000}])
    end.

%% ====================================================================
%% Behavioural functions 
%% ====================================================================
-record(state, {}).

%% init/1
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:init-1">gen_server:init/1</a>
-spec init(Args :: term()) -> Result when
    Result :: {ok, State}
            | {ok, State, Timeout}
            | {ok, State, hibernate}
            | {stop, Reason :: term()}
            | ignore,
    State :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
init([DoReadPinValueBeforeSetInt]) ->
    {ok, Gpio17} = gpio:start_link(?INT_PIN, input),
    gpio:register_int(Gpio17,self()),
    case DoReadPinValueBeforeSetInt of
        ?READ_PIN_VALUE_BEFORE_SET_INT ->
            gpio:read(Gpio17);
        _-> do_nothing
    end,

    gpio:set_int(Gpio17,rising),

    {ok, #state{}}.

%% handle_call/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_call-3">gen_server:handle_call/3</a>
-spec handle_call(Request :: term(), From :: {pid(), Tag :: term()}, State :: term()) -> Result when
    Result :: {reply, Reply, NewState}
            | {reply, Reply, NewState, Timeout}
            | {reply, Reply, NewState, hibernate}
            | {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason, Reply, NewState}
            | {stop, Reason, NewState},
    Reply :: term(),
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ====================================================================
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%% handle_cast/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_cast-2">gen_server:handle_cast/2</a>
-spec handle_cast(Request :: term(), State :: term()) -> Result when
    Result :: {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason :: term(), NewState},
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_cast(_Msg, State) ->
    {noreply, State}.

%% handle_info/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:handle_info-2">gen_server:handle_info/2</a>
-spec handle_info(Info :: timeout | term(), State :: term()) -> Result when
    Result :: {noreply, NewState}
            | {noreply, NewState, Timeout}
            | {noreply, NewState, hibernate}
            | {stop, Reason :: term(), NewState},
    NewState :: term(),
    Timeout :: non_neg_integer() | infinity.
%% ====================================================================
handle_info({gpio_interrupt, Pin, Condition},State) ->
    error_logger:info_report(["Interrupt has been occured on GPIO.", 
                               {gpio, Pin}, 
                               {interrupt_condition, Condition}]),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

%% terminate/2
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:terminate-2">gen_server:terminate/2</a>
-spec terminate(Reason, State :: term()) -> Any :: term() when
    Reason :: normal
            | shutdown
            | {shutdown, term()}
            | term().
%% ====================================================================
terminate(_Reason, _State) ->
    ok.

%% code_change/3
%% ====================================================================
%% @doc <a href="http://www.erlang.org/doc/man/gen_server.html#Module:code_change-3">gen_server:code_change/3</a>
-spec code_change(OldVsn, State :: term(), Extra :: term()) -> Result when
    Result :: {ok, NewState :: term()} | {error, Reason :: term()},
    OldVsn :: Vsn | {down, Vsn},
    Vsn :: term().
%% ====================================================================
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% ====================================================================
%% Internal functions
%% ====================================================================

thanks for your help, /Robi

fhunleth commented 9 years ago

What you're seeing are the semantics of how the Linux GPIO system works. Basically, a GPIO pin comes up in the "changed" state. Once you read it, it's no longer "changed". Erlang/ALE's interrupt mechanism just wraps this and sends an event whenever the GPIO is in the "changed" state.

In my code, I either only use interrupt events or only poll a GPIO. When I only use interrupts, the first interrupt initializes the state. In other systems, I'm used to there being a race condition between polling reads and interrupts where you can get an old interrupt after your read, and then your state is wrong. That's why I only do one or the other. Having said that, I think that the Linux GPIO system does the right thing and can't give delayed interrupts. It seems like queuing in the Erlang layer might be able to reorder an interrupt after a polled read, but I haven't thought it through.

It sounds like the documentation may need some clarification on this point. If there's some text that you think could be added to make this behavior less surprising, please send it over.

ethrbh commented 9 years ago

hello Frank,

Tanks for the clerification. I "come from PIC MCU world" where the interrupt flag should be cleared at the time when configure interrupt for RB change or Timer, ect. I think GPIO behavior is fine, and as you has mentioned only the documentation should be update accordingly, what I can do of course.

Just FYI, in the "ale_handler" I wrote and what is under review this is fixed in "code level" but not in documentation. Do fix is very easy, I just made a read on the GPIO what is going to be used for interrupt before set the interrupt conditions. It working, at least for me and my application what uses MCP7940n RTC device does not get "dummy" interrupt at all.

thanks, /Robi

knewter commented 9 years ago

is everyone comfortable marking this issue closed then?

ethrbh commented 9 years ago

hello,

This is fine for me. Please close it.

thanks, /Robi