Tuesday 18 December 2012

I2C port expander driving an LCD screen, click encoder and more

Adventures with the MCP23017 I2C port expander

Since working on the Panelolu I have been planning a project using the MCP23017 port expander. This chip gives 16 additional I/O ports which can be accessed using the I2C protocol, an easy to use bus communication protocol that is supported in the Arduino environment using the Wire library. My first exposure to I2C was during development of the Panelolu when I came across the Hobbytronics I2C LCD backpack, Adafruit also do a fancy 3 colour backlight LCD. I started this project in August but then put it on the back burner due to other commitments.

I re-invigorated the project recently as we get a lot of emails from people wanting to use the Panelolu with a Melzi or a Sanguinololu where pins on the expansion header are dedicated to other things. In both these cases the original Panelolu took too many pins. Added to this Nophead has announced he will be using Melzi on the Mendel90 which I feel is the best printer design out there right now.

Hardware setup.

The setup I have tested on uses a Sanguinololu but any similar reprap controller should work. The ISP header and 4 more pins are required:


Leaving 6 pins for other uses.

The circuit is setup as in the schematic




The Sanguinololu ISP and Expansion header pins are allocated as follows:
  • SCL and SDA are connected to pin 12 and 13 of the MCP23017
  • MOSI, MISO, SCK and A0 provide the SD card signals as they do in the Panelolu and SDSL.
  • A1 is the interrupt pin and I added an LED to this to help with debugging which is not illustrated in the schematic.
  • 5V and GND are distributed as required
from the MCP23017 the LCD display, Buzzer, Click Encoder and 3 LEDs are connected. There are 2 spare pins so further LEDS or something else could be connected.

Software Setup

I began testing in August with the library from Adafruit which does a great job of exposing the basic read and write functions of the expander. Recently Lincomatic updated the LiquidTWI2 library which extends the Adafruit library and I switched to that. It does not provide access to the interrupt functionality of the MCP23107 so I added some additional functions to the library:


//check registers
uint8_t LiquidTWI2::readRegister(uint8_t reg) {
  // read a register
  Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
  wiresend(reg);   
  Wire.endTransmission();
 
  Wire.requestFrom(MCP23017_ADDRESS | _i2cAddr, 1);
  return wirerecv();
}

//set registers

void LiquidTWI2::setRegister(uint8_t reg, uint8_t value) {
    Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
    wiresend(reg);
    wiresend(value);
    Wire.endTransmission();
}


In addition I added a function to switch the buzzer pin rapidly at a certain frequency to make a sound

//cycle the buzzer pin for a certain duration (ms) and at a certain freq (hz)
void LiquidTWI2::buzz(long duration, uint8_t freq) {
  int currentRegister = 0;
  // read gpio register
  Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
  wiresend(MCP23017_GPIOA);   
  Wire.endTransmission();
 
  Wire.requestFrom(MCP23017_ADDRESS | _i2cAddr, 1);
  currentRegister = wirerecv();
  duration *=1000; //convert from ms to us
  int period = (1.0 / freq) * 1000000; //*1000000 as the delay is in us
  long elapsed_time = 0;
  while (elapsed_time < duration)
  {
        Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
        wiresend(MCP23017_GPIOA);
        wiresend(currentRegister |= M17_BIT_BZ);
        while(Wire.endTransmission());
        delayMicroseconds(period / 2);
        Wire.beginTransmission(MCP23017_ADDRESS | _i2cAddr);
        wiresend(MCP23017_GPIOA);
        wiresend(currentRegister &= ~M17_BIT_BZ);
        while(Wire.endTransmission());
        elapsed_time += (period);
   }
}



I need to get up and running on GitHub until I do so the T3P3 version of LiquidTWI2 is available here. just download the as a zip and extract into the libraries subdirectory of the arduino folder so it looks something like:

C:\arduino-0023\libraries\LiquidTWI2\

At this time I have only tested it with Arduino 0023.

A1 is not a hardware interrupt pin so the PinChangeInt library for the Arduino is required. I used version 2.19, just download it according to the instructions and put in the libraries subdirectory of the arduino folder.

Example Sketch

I adapted the HelloWorld_i2c sketch that comes with LiquidTWI2, the full sketch is in the example sub directory of the T3P3 version of LiquidTWI. In essence the program uses the LiquidTWI2 library to setup communication with the MCP23017, configure and display information on the LCD and set the buzzer and LED pins.

It also sets interrupts on the click encoder pins and switch which trigger an interrupt on A1 every time the click encoder is turned or switch is pressed.

(from setup() )
...
    lcd.setRegister(MCP23017_GPINTENA,0x07); //enable interrupts on 3 pins
  lcd.setRegister(MCP23017_DEFVALA,0x07); //set the default values as 1

  //set to compare with register values set to 1 with DEFVALB 
  lcd.setRegister(MCP23017_INTCONA,0x07);
  lcd.setRegister(MCP23017_IOCONA,0x02); //enable active high for interrupt pin
  //read the interrupt capture register to reset it
  uint8_t reg = lcd.readRegister(MCP23017_INTCAPA);
  pinMode(interruptPin, INPUT); //set A1 (pin 30) as input

  //attach an interrupt using the pinChangeInt library to interruptPin
  PCintPort::attachInterrupt(interruptPin, &quickInt, RISING);

... 


The state of the click encoder and switch pins are are captured at the interrupt point and read, determining which way the encoder is turned or if the switch was pressed.

  void handleInterrupt() {
    //reset the flag
    rotating=false;  
    //get the interrupt status of the pins
    //this will clear the flag allowing further interrupts
    intCap=lcd.readRegister(MCP23017_INTCAPA);
    uint8_t test = intCap & 0b00000111;

    //only deal with the situation where the encoder is turned without the
    //click button held down
    if (test == 0b101) encoderPos += 1;
    if (test == 0b110) encoderPos -= 1;
    //mask out the encoder bits and look at the click button
    test &= 0b100;
    if (test == 0b000) clickButton = LOW;


    // while interrupts are still occurring clear the register (switch bouncing) 
    while(digitalRead(interruptPin) > 0)
    {
      
      lcd.readRegister(MCP23017_INTCAPA);
      delay (10);
    }
  }


The program does not make use of the SD card part of the design - that is planned for later when this is integrated into Marlin firmware. Also further testing on the buzzer is required as I believe the frequency is fairly limited by the I2C protocol and associated processing.

The video below demonstrated the example in operation.

Now off to make it work with Marlin firmware.



Thursday 29 November 2012

Panelolu on RAMPS and PrintrBoard issues


Panelolu on RAMPS


Update: The Panelolu2 has replaced the Panelolu. It is much quicker to install, has adapter boards to no messing with cables and uses less pins. The blog post with the information is here, it is available to buy in our webshop.

The original idea for the Panelolu was based on the PanelMax which works with RAMPS. What I had not got around to doing was determining the changes needed to the pin mapping to cable for the Panelolu to work on RAMPS. Thankfully Ian Stratford has done the hard work and worked it all out.

Update: Ian has also made the Panelolu page on the reprap wiki - thanks!

The tables below refer to the RAMPS pin outs from the reprap wiki - the picture is below



The tables below cover the differences between the RAMPS setup and the original instructions: Panelolu on Sanguinololu.

Ribbon Cable number
Function
Pin Sanguinololu
Pin RAMPS
1
GND
GND
NA
2
12V
12V
NA
3
LCD 1 (VSS - Ground)
LCD 5 (RW - Read/Write)
LCD 16 (K - Backlight Anode-)
Breadboard GND for encoder, switch, contrast, reset.
GND
GND (AUX-4)
4
LCD 2 (VDD +3.3 to +5v)
5V on breadboard, for brightness
5V
5V (AUX-4)
5
LCD 14 (DB7 - data bit)
A4
D29
6
LCD 4 (RS - register select)
PWM D12
D16
7
LCD 13 (DB6 - data bit)
A3
D27
8
Encoder1
TX1 D11
D35
9
LCD 12 (DB5 - data bit)
A2
D25
10
Encoder2
RX1 D10
D37
11
LCD 11 (DB4 - data bit)
A1
D23
12
LCD 6 (E - clock)
SDA D17
D17
13
SD (CS pin)
A0
D53
14
(via SDSL plug, NC) Encoder click
SCL D16
D31            
15
(via SDSL plug, NC)


16
(via SDSL plug, NC)


17
SD Ground
GND
GND
18
(via SDSL plug, NC) Reset
RST
No RST pin?
19
SD (DI pin)
MOSI
MOSI D51
20
SD (SCK pin)
SCK
SCK D52
21
SD 5V
5V
5V
22
SD (DO pin)
MISO
MISO D50
23
NA
NA

24
NA
NA





Other
LCD 3 (VO – contrast adjuster) to C on breadboard


Other
LCD 15 (A – Backlight Anode+) to B on breadboard (brightness)



RAMPS AUX-4
Ribbon Cable number
Function
Pin Sanguinololu
Pin RAMPS
6
LCD 4 RS (register select)
PWM D12
D16
12
LCD 6 E (clock)
SDA D17
D17
11
LCD 11 DB4
A1
D23
9
LCD 12 DB5
A2
D25
7
LCD 13 DB6
A3
D27
5
LCD 14 DB7
A4
D29
14
SD, encoder click
SCL D16
D31      

(Piezo Buzzer +)

D33
8
Encoder1
TX1 D11
D35
10
Encoder2
RX1 D10
D37
3
Ground
GND
GND
4
5V
5V
5V

RAMPS AUX-3 / SPI
Ribbon Cable number
Function
Pin Sanguinololu
Pin RAMPS
17
SD Ground
GND
GND

NC

NC
20
SD (SCK pin)
SCK
SCK D52
13
SD (CS pin)
A0
D53
22
SD (DO pin)
MISO
MISO D50
19
SD (DI pin)
MOSI
MOSI D51
21
SD 5V
5V
5V

NC

D49

To quote Ian
"I have managed to connect up the Panelolu kit to my RAMPS board with great success! Everything works except the reset button, as there is no reset pin on RAMPS. I added a piezo electric buzzer as well (from an old PC). Cabling is pretty straightforward, I just needed to trace the connection to the right pin. I built the Panelolu kit as per the Sanguinololu instructions, then used a couple of different sources to work out the correct pins on the RAMPS board. Then all I had to do was uncomment the #define ULTIPANEL line in Marlin. I did check the pins.h file, but it's already all in there for RAMPS. Worked first time!!"
Its now on my todo list to make up a cable to connect to RAMPS, something I intend to use on a dual extruder M90.

Printrboard issues

One of our customers, Bernardo, highlighted an issue with the Printrboard design: the Y-endstop is on the SS pin. Bernardo found the solution on Colin Bradburne's blog which I wont repeat here but if you are intending on using an SD card with Printrboard it is well worth reading his blog.