Pin Changed callback

I am trying to understand how the pinXchanged callback works. I am trying to use it in a home security system project, where I would read a change in magnetic sensors. The system was using an Arduino, but since the Imp gives me the ability to communicate over HTTPS, it seems to be the way to go, so I am converting it to imp002 power.

I have my code set up in the same way that the “Reading and Writing Pins” example shows, and I am testing it with a button, but I get what I think is some bouncing. I read another post that mentioned adding a 500ms sleep to debounce, but that doesn’t seem to give me better results.

Could someone explain exactly how the pinChanged call back works? If I press the button to change the pin state, and it bounces, does the imp register and fire multiple callbacks or only one? How often does it check? Is there a way to change that? I can obviously wait till I have debounced in my function before I read the pinstate, but that doesn’t seem to help.

Also, the example shows pulling the pin to ground with a switch, and no pulldown resistor in place. Is this ok to do? I’d love to not have to add a resistor to every sensor, like I did with the Arduino.

Hope someone can get me sorted out. The pinChanged function is a really cool feature of the imp, and I’d love to not have to loop through a sensor check constantly.

I use this function - but in an application that does not require de-bounce.

I think you should assume it may create multiple calls immediately after one another even within a few milliseconds. The code could flip a variable from 1 to 0 or something like that. At the same time you could call a function (via wakeup) that will execute some ms later; like 100ms later (but only if the variable did not already change).

So there might be many calls to the function but only the first one that flips the variable will do anything… and furthermore 100ms later (for example) you can check the pin to see if it is at the level it would be if the button has been pressed.

The pins can have either pull-down or pull-up resistors so in this way it is more flexible than Arduino and it seems you would not need any external resistors except to prevent you accidentally setting the pin as an output and shorting it.

I added a sleep function for 1 second and I still got what looked like bouncing, which is what is confusing me. Unless I am using sleep wrong, if I say imp.sleep(1.0); I shouldn’t see my function run twice in less than a second.

I have not used the sleep command ever so I am not sure what that would do.

Maybe two callbacks or even more than that are being queued up before the first one even runs. That is why I suggested to set a (global) variable to a new value and use that to help ignore the subsequent calls.

When you’re in one callback, another can be scheduled - the pin change is detected by an IRQ handler in the OS, which then causes events to be queued.

Instead of sleeping, you should take a time (eg, from hardware.millis()) and use this to discard events that happen within a set period from the first one, eg:

`
// Time of last valid switch event
lastevent <- 0;

function pinchanged() {
if (hardware.pinx.read() == 0) {
// is this likely a bounce?
if ((hardware.millis()-lastevent)>1000) {
// new event, process
lastevent = hardware.millis();
} else {
// likely a bounce, ignore
}
}
}
`
As for resistors, yes - traditionally you’ll have switches from the IO pin to ground, hence need to configure the pin as DIGITAL_IN_PULLUP. Usually, ground is preferred for switches because it gives a direct path from one side of the switch to ground, which is the ideal case for sinking an ESD strike.

Ok, I understand how this would debounce the switch going from 1-0, but what if it is going from 0-1?

As for resistors, did you mean, yes, I should put a resistor in between the switch and ground, or no, it isn’t necessary? I have my sensor pins configured as DIGITAL_IN_PULLUP, since it is best to configure security sensors as normally closed.

If your switch is connected between Ground and the Input then you use pullup resistors in the configuration (DIGITAL_IN_PULLUP). You do not need to add external resistors to your circuit. When your switch is open the pullup resistor will make sure the input moves to the high level. When your switch is closed it will be connected to Ground and will certainly be at the low level.

or

If your switch is connected between the 3.3V Power rail and the Input then you use pull-down resistors in the configuration and you still do not need external resistors.

and

I don’t think there would be any harm to add 1k resistors in series with your switch as that could prevent some accidental shorting situations. but… you do not need this for function.

You can debounce for both edges; here’s an example. Here we ignore any events within 50ms of last one, but also set a 100ms timer in case we got both transitions very quickly - you only process events on change of state, so additional callbacks will be ignored.

Generally you’d want to cancel callbacks before queuing a new one, but that API hasn’t reached the wild yet :slight_smile:

`// Time of last valid switch event
lastevent <- 0;

// Last processed status
laststatus <- -1;

function event() {
local status=hardware.pinx.read();
if (status != laststatus) {
laststatus = status;
// process your switch down or up here
}
}

function pinchanged() {
// is this likely a bounce?
if ((hardware.millis()-lastevent)>50) {
// new event, process
lastevent = hardware.millis();

// read pin, deal with 1 or 0
event();

// set timer to call back and get in sync in case this is the last event we see
imp.wakeup(0.1, event);

} else {
// likely a bounce, ignore
}
}`

…the other option is always just an RC filter on the pin :wink:

Thanks Hugo, I really appreciate the replies, and I think I’ll go with your last comment. I’d rather have simple, clean code, and add a little bit to the hardware side. The callbacks seem to work pretty well even with no software debounce, so I am going to add an RC filter. Also, I really like the idea of using the callbacks, vs a loop like my Arduino is doing.

Any idea what a good time would be for the filter? Looks like a 100k resistor and a 1uF cap would give me about 230 ms. Do you know how often the imp polls its IRQs? (If I said that correctly).

It doesn’t poll; they’re real level-sensitive interrupts. (Well, the block in question is clocked logic, I suppose, but the hardware latency must be in the nanoseconds.)

Peter

Here is a pushButton class I developed for my Hannah Project that is similar to @Hugo’s:

`class PushButton
{
// IO _pin assignment
_pin = null;
_name = null;
_callBack = null;
_lastState = -1; // Store the last state so we know when we’ve changed
_debounceActive = false; //None of the debounce code is really needed on Hannah (the pushbuttons are nice and clean or the IOExpander is making life good) but its nice to have for other applications

//_pin is an integer of the _pin number on the IOExpander
//call is a function(state) where state is a 0 or 1 equal to the state of the pushbutton (1 is pressed down)
constructor(btnpin, name = null, call = null) {
    //server.log("Contructing PushButton")

    // Save assignments
    _pin = btnpin;
    _callBack = call;
    _name = (name==null)?"Button"+_pin : name

    // Set event handler for irq
    IOExpander.setIRQCallBack(_pin, irqHandler.bindenv(this))

    // Configure _pin as input, irq on both edges
    IOExpander.setDir(_pin, 0);
    IOExpander.setPullUp(_pin,1)
    IOExpander.setIRQMask(_pin, 0);
    IOExpander.setIRQEdges(_pin, 1, 1);
    
    check_state(); //Init the _lastState var
    
   //server.log("PushButton Constructed")
}

function read(){
    return _lastState;
}

// This function is called whenever the IO Expander detects a change
function irqHandler() {
    if (!_debounceActive) {
        local data = check_state();
        local state = data[0];
        local changed = data[1];
 
        //server.log(format("Push Button %d = %d", _pin, state));
        if (_callBack != null && changed == true) _callBack(state)
        if(changed == true) agent.send("ButtonPress", {[this._name] = state})
        // Clear the interrupt
        //IOExpander.clearIRQ(_pin); //Taken care of by the IOExpander Class
        
        // Ignore bounces
        _debounceActive = true;
        imp.wakeup(0.00001, debounce.bindenv(this));  //Change to whatever amount of time you need
    }
}

function readState() {
    local state = IOExpander.getPin(_pin)?0:1;
    //server.log(format("debug %d", state));
    return state;
}

// Debounce code: ignore transitions after event
function debounce() {
    _debounceActive = false; // We can take notice of transitions from now onward
    local last = _lastState;
    local data = check_state()
    
    if (data[1] == true && _callBack != null) _callBack(data[0]);
}
 
function check_state() {
    local s = readState();
    local changed = false;
    
    if (s != _lastState) { // Has the state changed? //TODO: Should this be returned?
        _lastState = s;
        changed = true;
    }
    return [s, changed];
}

}`

You’ll need to change the IOExpander class calls to imp native hardware calls but it provides a nice interface to interact with pin state change events:

pushButton1 <- PushButton(PIN_CONSTANT, "Pin Name", function(state){ server.log(this._name+ " = " +state); if(state == 1){ //Do some stuff when pin is high } else { //Do some stuff when pin is low } //Do some stuff anytime the pin changes });