Color Blink

This example is for the Hannah development board. It will introduce you to the I2C peripheral and the SX1509 IO expander, and provide some colorful eye candy too.

The IO Expander, one of Hannah's on-board peripherals, offers 16 GPIO lines with optional LED drive while requiring only two or three imp pins. Two of the pins are for the I2C port (clock and data) while the optional pin is for an interrupt input (which is not used by this example). The LED drive feature of the SX1509 is very useful - the chip can provide PWM intensity control with 8-bit resolution, timed blinking and automatic fading between levels. Hannah features an RGB LED connected to IO pins 7, 6 and 5 of the Expander. By programming the Expander appropriately we can recreate any color with 24-bit resolution.

Our example code will blink random colors.

The Code

// Color Blink code example for Hannah

IO Expander Class

The first section of code comprises a convenient class for driving the SX1509 IO expander. This functionality will be used by many applications and can be conveniently copied and pasted as required. We'll look at each method here in some detail, but for most purposes it's not necessary to be familiar with what's happening within the class to access the higher level features of the board. Some of the methods are not used by this example, but will be useful later.

// IO Expander Class for SX1509
class IoExpander
{
    i2cPort = null;
    i2cAddress = null;

The Expander is connected to the imp by I2C, a bus which can connect multiple peripherals using only two wires. Each peripheral has a unique address which is set by the manufacturer (sometimes with a options set by the hardware designer to allow for multiple devices of the same type to be connected to the same bus).

The constructor takes the I2C port ID (I2C_12 or I2C_89) and I2C hardware address, so the class can be easily applied to other hardware designs. In our case the SX1509BULTRT Datasheet and Hannah Schematic show us that the Expander's address is 0x3E, which we'll pass later when we construct the class. Note that the address is shifted before we save it in our member data. I2C addresses are 7 bits in size, while bit 0 is used to indicate a read (0) or write (1) operation, therefore we'll shift the address left by one bit.

    constructor(port, 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
        {
            server.log("Invalid I2C port specified.");
        }
 
        i2cAddress = address << 1;
    }

Now for the member functions.

The read method calls i2c.read to fetch a single byte from the specified register. Although the Expander has a single I2C address, it has many internal registers which are addressed separately. An I2C read operation comprises writing the hardware address and register offset before reading back the response - the i2c API class handles all of this for us.

Should the operation fail for any reason (typically an incorrect address) we'll return -1, otherwise we'll return the data.

    // Read a byte
    function read(register)
    {
        local data = i2cPort.read(i2cAddress, format("%c", register), 1);
        if(data == null)
        {
            server.log("I2C Read Failure");
            return -1;
        }
 
        return data[0];
    }

The write method calls i2c.write to send a single byte to the specified register. Any failure would be silent.

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

We could use the read and write methods to access the full functionality of the IO Expander, however for convenience we'll define some additional methods to help with common operations.

The writeBit method sets or clears a single bit in the specified register.

    // 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);
    }

The writeMasked function writes a bit pattern masked with the specified register.

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

The setPin method sets or clears a specified GPIO pin (the Expander has 16 GPIOs, identified as 0 through 15).

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

The setDir method configures the specified GPIO as either an input (0) or an output (1).

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

The setPullUp method enables or disables an internal pull up resistor for the specified GPIO.

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

The setIrqMask method configures whether the specified GPIO will trigger an interrupt. The Expander interrupt pin is connected to Hannah on pin 1 (which can be used to wake up the imp from deep sleep).

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

The setIrqEdges method configures which edges trigger an interrupt for the specified GPIO. Any combination of rising and falling edges may be specified (1 = will interrupt).

    // 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);
    }

The clearIrq method will clear an interrupt on the specified GPIO.

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

The getPin method will get the state of the specified GPIO.

    // Get a GPIO input pin level
    function getPin(gpio)
    {
        return (read(gpio>=8?0x10:0x11)&(1<<(gpio&7)))?1:0;
    }
}

RgbLed Class

We'll derive from IoExpander to create a class to represent an RGB LED. This is really three separate LEDs conveniently packaged together.

The constructor will accept and store the pin assignments for each LED (remember these are IO pins on the Expander, not imp pins) and configure the registers to set LED drive mode.

// RGB LED Class
class RgbLed extends IoExpander
{
    // IO Pin assignments
    pinR = null;
    pinG = null;
    pinB = null;
 
    constructor(port, address, r, g, b)
    {
        base.constructor(port, address);
 
        // Save pin assignments
        pinR = r;
        pinG = g;
        pinB = b;
 
        // Disable pin input buffers
        writeBit(pinR>7?0x00:0x01, pinR>7?(pinR-7):pinR, 1);
        writeBit(pinG>7?0x00:0x01, pinG>7?(pinG-7):pinG, 1);
        writeBit(pinB>7?0x00:0x01, pinB>7?(pinB-7):pinB, 1);
 
        // Set pins as outputs
        writeBit(pinR>7?0x0E:0x0F, pinR>7?(pinR-7):pinR, 0);
        writeBit(pinG>7?0x0E:0x0F, pinG>7?(pinG-7):pinG, 0);
        writeBit(pinB>7?0x0E:0x0F, pinB>7?(pinB-7):pinB, 0);
 
        // Set pins open drain
        writeBit(pinR>7?0x0A:0x0B, pinR>7?(pinR-7):pinR, 1);
        writeBit(pinG>7?0x0A:0x0B, pinG>7?(pinG-7):pinG, 1);
        writeBit(pinB>7?0x0A:0x0B, pinB>7?(pinB-7):pinB, 1);
 
        // Enable LED drive
        writeBit(pinR>7?0x20:0x21, pinR>7?(pinR-7):pinR, 1);
        writeBit(pinG>7?0x20:0x21, pinG>7?(pinG-7):pinG, 1);
        writeBit(pinB>7?0x20:0x21, pinB>7?(pinB-7):pinB, 1);
 
        // Set to use internal 2MHz clock, linear fading
        write(0x1e, 0x50);
        write(0x1f, 0x10);
 
        // Initialise as inactive
        setLevels(0, 0, 0);
        setPin(pinR, 0);
        setPin(pinG, 0);
        setPin(pinB, 0);
    }

The setLed method enables or disables each color segment, or makes no change given a null parameter. This will toggle the segment between off and the current brightness level.

    // Set LED enabled state
    function setLed(r, g, b)
    {
        if(r != null) writeBit(pinR>7?0x20:0x21, pinR&7, r);
        if(g != null) writeBit(pinG>7?0x20:0x21, pinG&7, g);
        if(b != null) writeBit(pinB>7?0x20:0x21, pinB&7, b);
    }

The setLevels method sets the intensity for each color segment or makes no change given a null parameter. Intensity control is achieved by varying the duty ratio of a PWM signal. The eye cannot see the flicker as the switching happens very quickly (using a 2MHz clock in this case).

    // Set red, green and blue intensity levels
    function setLevels(r, g, b)
    {
        if(r != null) write(pinR<4?0x2A+pinR*3:0x36+(pinR-4)*5, r);
        if(g != null) write(pinG<4?0x2A+pinG*3:0x36+(pinG-4)*5, g);
        if(b != null) write(pinB<4?0x2A+pinB*3:0x36+(pinB-4)*5, b);
    }
}

Blink Code

Finally we can implement our example functionality.

We'll construct an RGB LED using the correct parameters for the Hannah board.

// Construct an LED
led <- RgbLed(I2C_89, 0x3E, 7, 5, 6);

We'll change color every 500ms, using a wakeup timer to call change.

// Change to a random color every 500ms
function change()
{
    // Schedule the next change
    imp.wakeup(0.5, change);
 
    // Select a color at random
    local r = math.rand()%256;
    local g = math.rand()%256;
    local b = math.rand()%256;
 
    // Set the LED color
    led.setLevels(r, g, b);
}

We register with the server - no inputs or outputs required this time.

// Register with the server
imp.configure("Color Blink", [], []);

Finally we can set the color changer running.

// Enable the LED outputs and start color changing
led.setLed(1, 1, 1);
change();
 
// End of code.

For the full code listing click here.

colorblink.txt · Last modified: 2012/11/03 16:11 by hugo
 
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki