Setting up a button on Hannah

How do you set up a button using the Hannah board?

Is there code documentation for this?

I’m also interested in learning more about how to do this. Can we get a code example please?

I haven’t tested this code as is on an imp but here is some excerpts from a script I have. This will allow you to use the single interrupt on the Hannah IO Expander to receive interrupts from each of the pins (in this case the two buttons). Also, note I have add or changed some functions in the IOExpander class from the example code.

`// IO Expander Class for SX1509
class IOExpander
{

I2CPort = null;
I2CAddress = null;
IRQ_Callbacks = array(16);

constructor (port, address) {
    
    // Configure port and save address
    if (port == I2C_12) {
        
        // Configure I2C bus on pins 1,2
        hardware.configure(I2C_12);
        I2CPort = hardware.i2c12;            
    }
    else if (port == I2C_89) {
        
        // Configure I2C bus on pins 8,9
        hardware.configure (I2C_89)
        I2CPort = hardware.i2c89;
    }
    else {
        
        // Problem somewhere
        server.log(format("Invalid I2C port specified: %c", port));
    }
    I2CAddress = address << 1;
    hardware.pin1.configure(DIGITAL_IN, getIRQSources.bindenv(this));
}

// Read a byte
function read(register) {

    // Read and return data if successful
    local data = I2CPort.read(I2CAddress, format("%c", register), 1);
    if (data != null) return data[0];
    
    // Error, return -1
    server.log("I2C Read Failed");
    return -1;
}

// Write a byte
function write (register, data) {
    I2CPort.write(I2CAddress, format("%c%c", register, data));
}

// Write a bit to a register
function writeBit (register, bitn, level) {
    local value = read(register);
    value = (level == 0)?(value & ~(1<<bitn)):(value | (1<<bitn));
    write(register, value);
}

// Write a masked bit pattern
function writeMasked (register, data, mask) {
   local value = read (register);
   value = (value & ~mask) | (data & mask);
   write (register, value);
}

// Set a GPIO direction
function setDir (gpio, output){
    writeBit (gpio>=8?0x0e:0x0f, gpio&7, output?0:1);
}

// Set a GPIO level
function setPin (gpio, level){
    writeBit (gpio>=8?0x10:0x11, gpio&7, level?1:0);
}

// Enable/disable a GPIO internal pull-up resistor
function setPullUp (gpio, enable) {
    writeBit (gpio>=8?0x06:0x07, gpio&7, enable);
}

// Set GPIO interrupt mask
function setIRQMask (gpio, enable) {
    writeBit (gpio>=8?0x12:0x13, gpio&7, enable);
}

// Set GPIO interrupt edges
function setIRQEdges (gpio, rising, falling) {
    local addr = 0x17 - (gpio>>2);
    local mask = 0x03 << ((gpio&3)<<1);
    local data = (2*falling + rising) << ((gpio&3)<<1);    
    writeMasked (addr, data, mask);
}

// Clear an interrupt
function clearIRQ (gpio) {
    writeBit (gpio>=8?0x18:0x19, gpio&7, 1);
}

function setIRQCallBack(pin, func){
    IRQ_Callbacks[pin] = func;
}

function clearIRQCallBack(pin){
       IRQ_Callbacks[pin] = null;
}

function getIRQSources(){
    //0x18=RegInterruptSourceB (Pins 15->8), 1 is an interrupt and we write a 1 to clear the interrupt
    //0x19=RegInterruptSourceA (Pins 7->0), 1 is an interrupt and we write a 1 to clear the interrupt
   local sourceB = read(0x18);
   local sourceA = read(0x19);

    local irqSources = array(16);
    
    local j = 0;
    for(local z=1; z < 256; z = z<<1){
        irqSources[j] = ((sourceA & z) == z);
        irqSources[j+8] = ((sourceB & z) == z);
        j++;
    }
    //server.log(format("irqSource=%s", byteArrayString(irqSource)));
    
    //TODO: This could be in the loop above if performance becomes an issue
    for(local pin=0; pin < 16; pin++){
        if(irqSources[pin]){
            IRQ_Callbacks[pin]();
            clearIRQ(pin);
        }
    }
    
   //Clear the interrupts   //Currently callback functions handle this
   //write(0x18, 0xFF);
   //write(0x19, 0xFF);
   return irqSources;    //Array of the IO pins and who has active interrupts
}

// Get a GPIO input pin level
function getPin (gpio) {
    //If gpio pin is greater than or equal to 8 then its staus is in the 0x10 register, else its in the 0x11 register.  Then left shift to create a mask for the particular pin and return true or false based on its value
    return (read(gpio>=8?0x10:0x11)&(1<<(gpio&7))) ? 1 : 0;
}

}

// PushButton Class for Hannah
class PushButton extends IOExpander
{
// IO Pin assignment
pin = null;
irq = null;

// Output port
outPort = null;
//Callback function for interrupt
callBack = null;

constructor(port, address, btnPin, irqPin, out, call)
{
    //server.log("Contructing PushButton")
    base.constructor(port, address);

    // Save assignments
    pin = btnPin;
    irq = irqPin;
    outPort = out;
    callBack = call;

    // Set event handler for irq
    if (irqPin != null) //This is handled by our IOExpander class
        irqPin.configure(DIGITAL_IN, irqHandler.bindenv(this));
    else
        setIRQCallBack(btnPin, irqHandler.bindenv(this))

    // Configure pin as input, irq on both edges
    setDir(pin, 0);
    setPullUp(pin,1)
    setIRQMask(pin, 0);
    setIRQEdges(pin, 1, 1);
    
   //server.log("PushButton Constructed")
}

function irqHandler()
{
    local state = null;

        // Get the pin state
        state = getPin(pin)?0:1;

        // Output to port and display on node
        if (outPort != null) outPort.set(state);
        //server.show(format("Push Button %d = %d", pin, state));
        //server.log(format("Push Button %d = %d", pin, state));
        if (callBack != null && state == 1) callBack()  //Only call the callback on the push down event, not the release


    // Clear the interrupt
    clearIRQ(pin);
}

function readState()
{
    local state = getPin(pin);

    //server.log(format("debug %d", state));
    return state;
}

}

//Instantiate the buttons
pushButton1 <- PushButton(I2C_89, 0x3e, 0, null, null, callbackFunction1);
pushButton2 <- PushButton(I2C_89, 0x3e, 1, null, null, callbackFunction2);

//imp.configure and the rest of your code goes here`

Thank you! This code example seems to work well.

ok, this is an old thread and we have a new Hannah, but in my opinion concerning the push buttons, there is no difference
Trying to run this code I get an exception:

Thu Jun 20 2013 14:49:07 GMT+0200: Device booting Thu Jun 20 2013 14:49:07 GMT+0200: ERROR: the index 'callbackFunction1' does not exist Thu Jun 20 2013 14:49:07 GMT+0200: ERROR: at main:206 Thu Jun 20 2013 14:49:12 GMT+0200: Power state: online=>offline

Can anyone help me out?
I’m not a bitshifter, but trying to learn…

Just make those 2 functions in your own code!

Can we make the Imp sleep and wake up with this code? How?

Just make those 2 functions in your own code!
I think I tried this, although I'm not sure that I know what I'm doing... Do you have an example?

@sbright33,

the hardware wakeup is set on the pin1 that on Hannah is connected to the INT_L signal of the Expander, so you can absolutely program the expander to wakeup the imp.

I was hoping someone would add a few lines of code to do it. I’m sure that’s all it would take since you verified my idea.

@DolfTraanberg before you instantiate the buttons define two functions like this:

`
function callbackFunction1(){
server.log(“Button 1 Pressed!”)
}

function callbackFunction2(){
server.log(“Button 2 Pressed!”)
}
`

Right now the Pushbutton class is setup to only fire the callback functions when the buttons are pressed down, but its pretty trivial to pass a state variable and call them when they are released as well or pass in two functions to the constructor that are called independently…

@sbright33 I haven’t played with putting the imp into deep sleep (I haven’t had any battery applications outside of Hannah yet and haven’t had time to play with it) but you should be able to do it without too much trouble - you’ll need to put all the various sensors to sleep before the imp and reconfigure the IO expander to only interrupt based on one button otherwise any interrupt to the IO expander will also wake the imp.

I’m very good at putting the April Imp to sleep and wake at a time or with audio on the Microphone. It would be trivial to change the hardware and keep the code the same. I even got it working without Wifi!!! But I just got my Hannah, so that is all new to me. Would be a great piece of code that many people will use. Please someone? I’ve got another project today so I won’t get to Hannah until next week. All we need to do is put the sensors to sleep? One more line of code to wake the Imp on Pin1?

thanks for the tips, but indeed, I tried those as well.
I made some extended logs to see whats happening:

Thu Jun 20 2013 19:46:23 GMT+0200: Device configured to be "Hannah Buttons" Thu Jun 20 2013 19:50:40 GMT+0200: Downloading new code Thu Jun 20 2013 19:50:41 GMT+0200: Contructing PushButton Thu Jun 20 2013 19:50:41 GMT+0200: I2CAddress: 0x7C Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setIRQCallBack Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setDir Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setPullUp Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setIRQMask Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setIRQEdges Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeMasked Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: PushButton Constructed Thu Jun 20 2013 19:50:41 GMT+0200: Contructing PushButton Thu Jun 20 2013 19:50:41 GMT+0200: I2CAddress: 0x7C Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setIRQCallBack Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setDir Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setPullUp Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:41 GMT+0200: **function called: write Thu Jun 20 2013 19:50:41 GMT+0200: **function called: setIRQMask Thu Jun 20 2013 19:50:41 GMT+0200: **function called: writeBit Thu Jun 20 2013 19:50:41 GMT+0200: **function called: read Thu Jun 20 2013 19:50:42 GMT+0200: **function called: write Thu Jun 20 2013 19:50:42 GMT+0200: **function called: setIRQEdges Thu Jun 20 2013 19:50:42 GMT+0200: **function called: writeMasked Thu Jun 20 2013 19:50:42 GMT+0200: **function called: read Thu Jun 20 2013 19:50:42 GMT+0200: **function called: write Thu Jun 20 2013 19:50:42 GMT+0200: PushButton Constructed Thu Jun 20 2013 19:50:42 GMT+0200: Device configured to be "Hannah Buttons"

but after this, nothing happens anymore, maybe still an address issue?

Would be a great piece of code that many people will use
working on that. first I have to get all features working. I can now show temperature, some LED stuff, the Hall switch and a bit off the buttons, but it is all still spaghetti code. Above code looks like a great template to use on all sensors, if I can get it to work. Lightsensor is a problem because it needs calibrating and the accelerator will be last, because it involves three variables, as the RGB led does.

Based on the code of deldrid1 above, I modified it, so that it works on Hannah rev.3
Currently, only pushbuttons and Hallswitch work.
I noticed there were some timing issues with clearing the interrupt bit.
So, if it happens that the Hannah seems to get unresponsive, cut of the power from the Hannah for at least 30 seconds.
For now I use imp.sleep() and imp.wakeup(). Maybe a code guru can give some insight how to solve this, because using the hallsensor as for example a RPM meter doesn’t work well.

If you want fast hall sensor stuff, you’re better off jumpering the sensor to one of the servo pins (which connect directly to imp lines). I2C, as a serial bus, is not fast.

thanks, but I wanted to explain the speed with this. only 10 readings a second. but there must be a way to improve this.

10 per second should be fine, I was thinking you were looking at hundreds of times per second.

On i2c remember that reading a single register takes a little more than 33 clocks (start, address, ack, subaddress, ack, restart, data, ack, stop). If you’re getting an interrupt and need to read then clear an interrupt register then re-enable something you might be doing 3 or 4 of these at least for every single interrupt. This is not very efficient.

ok, now I know why it took so long to get it working…
I was too optimistic, but it makes sense.
Now I must think of a kind of exception handling to push it to the edges.

Can anyone sleep and wake with button press?
Any code for the 2 new devices temp and accel?