Thursday, 3 January 2013

I2C Port Expander & LCD working with Marlin

Previously...

The I2C port expander proved capable of driving a 20x4 LCD screen, reading the status of a quadrature encoder with click switch, driving some LEDs and a piezo buzzer.

Here is a quick video of this configuration working with Marlin firmware:


I had to make a few changes to marlin to make this happen which are detailed below.

Marlin Firmware integration

Lincomatic has forked Marlin and edited it to use his LiquidTWI2 library. This is a fast I2C LCD library designed for the adafruit RGB LCD shield, I added a couple of functions for this setup which Lincomatic has incorporated and updated the version number of LiquidTWI2 to 1.1.1. I used these as a start state as Lincomatic's version of Marlin was already proven with an I2C LCD -  all the line numbers below refer to this version.

The changes required are:

to configuration.h (line 292) add:

   #define PANELOLU2

   #ifdef PANELOLU2
   #define VERSAPANEL

   #define I2CENCODER //define this if you want to use the encoder over I2C
  #endif


This allows using the Panelolu2 with the encoder connected to either the port expander or directly to the main board.

to pins.h add (at line 657) :

  #ifdef ULTRA_LCD
    #ifdef NEWPANEL
      #define BEEPER -1

    #ifdef PANELOLU2
      #ifndef I2CENCODER
        #define BTN_EN1 10
        #define BTN_EN2 11 
        #define BTN_ENC 30  //the switch
      #endif
    #else
        //we have no buzzer installed
        #define BEEPER -1
        //LCD Pins
        #define LCD_PINS_RS        4
        #define LCD_PINS_ENABLE    17
        #define LCD_PINS_D4        30
        #define LCD_PINS_D5        29
        #define LCD_PINS_D6        28
        #define LCD_PINS_D7        27

        //The encoder and click button
        #define BTN_EN1 11
        #define BTN_EN2 10
        #define BTN_ENC 16  //the switch
      #endif //PANELOLU2

      //not connected to a pin
      #define SDCARDDETECT -1

      //from the same bit in the RAMPS Newpanel define
      //encoder rotation values
      #define encrot0 0
      #define encrot1 2
      #define encrot2 3
      #define encrot3 1
    
      #define BLEN_C 2
      #define BLEN_B 1
      #define BLEN_A 0

    #endif //Newpanel
  #endif //Ultipanel


to temperature.cpp replace

  #ifdef ULTIPANEL
    buttons_check();
  #endif

with (4 times at lines 956,980,1001,1022) :

  #ifdef ULTIPANEL
    #ifndef I2CENCODER
      buttons_check();
    #endif

  #endif

This is because Marlin uses a timer interrupt to run the temperature check code. Each time this interrupt runs, the status of the buttons on a Ultipanel-compatible controller are checked. If connected over I2C the encoder & click buttons cannot be checked within this interrupt routine as the Arduino wire library is blocking it. So we exclude this button check call and will check the encoder at another point.

On to ultralcd.pde. In the beep() and beepshort() functions replace

  //return;
  #ifdef ULTIPANEL

with (2 times at lines 189, 207) :
 
  //return;
  #ifdef PANELOLU2
     lcd.buzz(300,4000);
  #elif defined ULTIPANEL

This uses the buzzer on the port expander whenever beep() or beepshort() are called. Still in ultralcd.pde, in the lcd_status() function replace:

  #ifdef MCP23017_LCD
    if (lcd.readButtons() & BUTTON_SELECT)
      buttons |= EN_C;
  #endif


with (at line 225) :


  #ifdef MCP23017_LCD
    #ifndef I2CENCODER
      if (lcd.readButtons() & BUTTON_SELECT)
        buttons |= EN_C;
    #endif
  #endif


and


 //  buttons_check(); // Done in temperature interrupt

with (at line 252) :

  #ifdef I2CENCODER
    buttons_check(); //Done in temperature interrupt except if using I2C buttons
  #endif


As mentioned earlier Marlin uses the temperature check routine to call buttons_check() but as that is inside an interrupt routine we can't check it there - hence the check here.

Still within ultralcd.pde, the buttons_init() function has been edited so as not to throw errors if the buttons are not defined in pins.h (from line 281)



  #ifdef NEWPANEL
    #ifdef BTN_EN1
      pinMode(BTN_EN1,INPUT);
      WRITE(BTN_EN1,HIGH);
    #endif

    #ifdef BTN_EN2
      pinMode(BTN_EN2,INPUT);
      WRITE(BTN_EN2,HIGH);
    #endif

    #ifdef BTN_ENC
      pinMode(BTN_ENC,INPUT);
      WRITE(BTN_ENC,HIGH);
    #endif

    #if (SDCARDDETECT > -1)
    {
      pinMode(SDCARDDETECT,INPUT);
      WRITE(SDCARDDETECT,HIGH);
    }
    #endif

 
Still within ultralcd.pde the buttons_check() function has been edited to handle the different cases of an encoder connected via I2C or directly. The first part of the function was changed to the following (from line 310):


void buttons_check()
{
  uint8_t newbutton=0;
  #ifdef I2CENCODER
    uint8_t lrb = lcd.readButtons();
    if (lrb & ENCODER_A) newbutton |= EN_A;
    if (lrb & ENCODER_B) newbutton |= EN_B;
    if (lrb & ENCODER_C) newbutton |= EN_C;
  #elif defined(NEWPANEL)
    #ifdef BTN_EN1
      if(READ(BTN_EN1)==0)  newbutton|=EN_A;
    #endif

    #ifdef BTN_EN2
      if(READ(BTN_EN2)==0)  newbutton|=EN_B;
    #endif

    #ifdef BTN_ENC
      if((blocking<millis()) &&(READ(BTN_ENC)==0))
        newbutton|=EN_C;
    #endif     
    buttons=newbutton;


These changes allow for two configurations which can be switched between using the #define I2CENCODER within configuration.h :

with I2CENCODER defined:
  outputs (LCD, Buzzer, LEDs) use I2C
  inputs (Encoder with click switch) use I2C

with I2CENCODER not defined:
  outputs (LCD, Buzzer, LEDs) use I2C
  inputs (Encoder with click switch) are directly connected on the pins defined in pins.h

The reason I have included the option of directly connecting the encoder is because of the way marlin checks the buttons. Currently it's quite possible for encoder rotations to be lost if connected over I2C.


Next Steps

Since starting on this project Marlin has been updated to change how lcd displays are handled, splitting the menu system from the display implementation. There is currently a header file for the "standard" implementation using an LCD directly like the Panelolu. The obvious next step is to write a similar header file to use an I2C LCD using LiquidTWI2. In addition I need to add an option to use an interrupt on a pin to check the encoder status rather than a timer interrupt - this will ensure steps are not missed and the encoder can be safely used over I2C.

10 comments:

  1. Hi Tony,
    Could state the aim of this development in non-technical terms also? How will it be better than existing LCD/click encoder solutions?
    Less wires to connect it up? Cost less? Work faster? etc.
    I'll leave the in-depth tech to others to digest, but a general high-level overview of the advantages of your work over existing solutions would be interesting also to me and perhaps other followers.
    Tks.

    ReplyDelete
    Replies
    1. Yeah, I should have mentioned that! The main reason is that this will work with less pins on the Sanguinololu, allowing for those who use a pin for something else (ie some Huxleys switch the heated bed through an expansion header pin) to still have a LCD screen, encoder etc. Similarly the Melzi which does not have enough pins for the original Panelolu with direct LCD connection could use this to run a LCD and encoder. The other advantage is with the spare pins you can implement more functions like a buzzer, LEDs to indicate statuses etc.

      Delete
    2. Hope my new post makes this a little clearer;

      http://blog.think3dprint3d.com/2013/02/panelolu2.html

      Delete
  2. Tks Tony, that's great additional info.
    I presume the main benefit of any LCD/encoder control is to gain independence from the laptop. Would that also not require an SD reader for 100% independence? Does your work incorporate (or plan to incorporte) and SD card reader, or is that something that would have to be connected separately to the main board? (Perhaps there isn't enough bandwidth or whatever on that I2C for data as well as control.)

    ReplyDelete
    Replies
    1. The plan with the SD card reader is to keep it the same as it is currently is implemented. For the printrboard and Melzi that would mean they can continue to use the built in SD card (or use one remotely like in the Panelolu for Printrboard- with a firmware option to select between a remote or local card). For the Sanguinololu/Ramps the pins that are currently used for the SD card (by SDSL or SDRAMPS) would continue to be used for an external SD card reader (probably integrated into the same circuit board as the I2C chip etc).

      Reading the SD card over I2C is not a good option as SPI is faster than I2C.

      Delete
  3. This is great! I've been looking all day for a way to add a g-code controlled fan AND use the Panelolu. Thank you for the persistent work on this.

    ReplyDelete
  4. I have published a new post showing the implementation of this idea:
    The Panelolu2
    http://blog.think3dprint3d.com/2013/02/panelolu2.html

    ReplyDelete
  5. please
    How do I configure the mapping LCD_I2C_TYPE_PCA8574?
    thank you

    ReplyDelete
    Replies
    1. Hi Tugao

      No idea - I used the MCP23017

      Delete
  6. If I upload marlin firmware with comment out line #define panelolu2 and I am not plug in the panelolu2 LCD in printrboard then my printer is not connected with prontrface. please help me to solve this issue.

    ReplyDelete