1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

I2C lcd controller using PIC16F690

Discussion in 'Microcontrollers' started by arhi, Jun 30, 2008.

  1. arhi

    arhi Member

    Joined:
    Apr 17, 2008
    Messages:
    887
    Likes:
    12
    Location:
    Belgrade, .rs
    I had a need for I2C lcd controller and was using some old software i2c library I made long time ago for 16F628 (16F84) but as 16F690 is even cheaper then 16F628 and have hardware ssp (not mssp so it cannot be master - but I might be wrong there) I decided to rewrite the code and use the hw i2c capable uC for the job ... as it might be useful to someone else, and I kinda do not have time to make a "site" with all the useful things I could share - this seams like a valid place - not to mention that I might get some useful comments :D

    here is the 16F690 code (the I2C slave LCD controller) - written in PICC CCS C

    Code (text):

    #include <16F690.h>

    #device adc=8

    #FUSES WDT                      //Watch Dog Timer
    #FUSES HS                       //HS Oscilator
    #FUSES NOPROTECT                //Code not protected from reading
    #FUSES NOBROWNOUT               //No brownout reset
    #FUSES NOMCLR                   //Master Clear pin used for I/O
    #FUSES NOCPD                    //No EE protection
    #FUSES NOPUT                    //No Power Up Timer
    #FUSES NOIESO                   //Internal External Switch Over mode disabled
    #FUSES NOFCMEN                  //Fail-safe clock monitor disabled

    #use delay(clock=20000000)
    #use i2c(Slave,Fast,sda=PIN_B7,scl=PIN_B6,restart_wdt,force_hw,address=0xAA)



    //how big the buffer is .. ~78 is the largest I could make on 16f690
    //33 is enough for lcd operations, everything else is pure bonus :)
    #define ALCTD 75

    BYTE incoming, state;
    BYTE address;
    BYTE buffer[ALCTD];
    BYTE changed[ALCTD];

    #int_SSP
    void  SSP_isr(void) {
      state = i2c_isr_state();

      if(state == 1){  //First received byte is address
        incoming = i2c_read();
        address = incoming;
        if (address > ALCTD) address = 0;
      }

      if(state == 2){  //Second received byte is data
        incoming = i2c_read();
        buffer[address] = incoming;
        changed[address] = 1;
      }

      if(state == 0x80){  //Master is asking for data
        i2c_write (buffer[address]);
      }
    }

    // PIC     LCD
    // C0      enable
    // C1      rs
    // C2      rw
    // C3      NC
    // C4      D4
    // C5      D5
    // C6      D6
    // C7      D7

    struct lcd_pin_map {                 // This structure is overlayed
               BOOLEAN enable;           // on to an I/O port to gain
               BOOLEAN rs;               // access to the LCD pins.
               BOOLEAN rw;               // The bits are allocated from
               BOOLEAN unused;           // low order up.  ENABLE will
               int     data : 4;         // be pin B0.
            } lcd;

    #locate lcd = getenv("sfr:PORTC")    // This puts the entire structure over the port
    #define set_tris_lcd(x) set_tris_c(x)

    #define lcd_type 2           // 0=5x7, 1=5x10, 2=2 lines
    #define lcd_line_two 0x40    // LCD RAM address for the second line

    BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6};

    struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; // For write mode all pins are out
    struct lcd_pin_map const LCD_READ = {0,0,0,0,15}; // For read mode data pins are in

    BYTE lcd_read_byte() {
          BYTE low,high;
          set_tris_lcd(LCD_READ);
          lcd.rw = 1;
          delay_cycles(1);
          lcd.enable = 1;
          delay_cycles(1);
          high = lcd.data;
          lcd.enable = 0;
          delay_cycles(1);
          lcd.enable = 1;
          delay_us(1);
          low = lcd.data;
          lcd.enable = 0;
          set_tris_lcd(LCD_WRITE);
          return( (high<<4) | low);
    }

    void lcd_send_nibble( BYTE n ) {
          lcd.data = n;
          delay_cycles(1);
          lcd.enable = 1;
          delay_us(2);
          lcd.enable = 0;
    }

    void lcd_send_byte( BYTE address, BYTE n ) {
          lcd.rs = 0;
          while ( bit_test(lcd_read_byte(),7) ) ;
          lcd.rs = address;
          delay_cycles(1);
          lcd.rw = 0;
          delay_cycles(1);
          lcd.enable = 0;
          lcd_send_nibble(n >> 4);
          lcd_send_nibble(n & 0xf);
    }

    void lcd_init() {
        BYTE i;
        set_tris_lcd(LCD_WRITE);
        lcd.rs = 0;
        lcd.rw = 0;
        lcd.enable = 0;
        delay_ms(15);
        for(i=1;i<=3;++i) {
           lcd_send_nibble(3);
           delay_ms(5);
        }
        lcd_send_nibble(2);
        for(i=0;i<=3;++i)
           lcd_send_byte(0,LCD_INIT_STRING[i]);
    }

    void main(){
       BYTE i;

       setup_adc_ports(NO_ANALOGS|VSS_VDD);
       setup_adc(ADC_OFF);
       setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
       setup_wdt(WDT_2304MS|WDT_DIV_16);
       setup_timer_1(T1_DISABLED);

       lcd_init();

       enable_interrupts(INT_SSP);
       enable_interrupts(GLOBAL);

       //init buffers
       for(i=0;i<32;i++){
         changed[i]=0;
         buffer[i]=0;
       }

      while (TRUE){
        #asm
        clrwdt
        #endasm

        //update 1st line
        for(i=0;i<16;i++){
          if (changed[i]){
            changed[i]=0;
            lcd_send_byte(0,0x80|i); //go to 1.row, i'th place
            lcd_send_byte(1,buffer[i]);        
          }
        }

        //update 2nd line
        for(i=16;i<32;i++){
          if (changed[i]){
            changed[i]=0;
            lcd_send_byte(0,0x80|(i+lcd_line_two-16)); //go to 2.row, i'th place
            lcd_send_byte(1,buffer[i]);        
          }
        }

        if (changed[32]){ // send controll code
          changed[32] = 0;
          lcd_send_byte(0,1); //clear screen    
        }

        for(i=33;i<ALCTD;i++){
         //do whatever .. controll the free oi pins on the uC...
        }

      }
    }

     

    the master code, looks like this (mikroC c):

    Code (text):

    void i2c_putc(unsigned char c){
      unsigned char static position = 0;
      if (c == '\f'){ // \f will clear screen
        I2C_Start();
        I2C_Wr(0xAA);
        I2C_Wr(32);
        I2C_Wr('x');
        I2C_Stop();
        position=0;
      } else if (c=='\n'){  //move position to beginning of the second line
        position=16;
      } else {
        I2C_Start();
        I2C_Wr(0xAA);
        I2C_Wr(position++);
        I2C_Wr(c);
        I2C_Stop();
        if (position > 31) position=0;
      }
    }

    void i2c_print(char *c){
       unsigned char i;
       i=0;
       while (c[i]){
          i2c_putc(c[i]);
          i++;
       }
    }

    char x[] = "\fHello World!\nsecond line";

    void main(){
      Delay_ms(200); //wait for the i2c slaves to come online
      I2C_Init(100000); //"fast" i2c initialisation
      i2c_print(x);
    }
     
     
    Last edited: Jun 30, 2008
  2. arhi

    arhi Member

    Joined:
    Apr 17, 2008
    Messages:
    887
    Likes:
    12
    Location:
    Belgrade, .rs
    Of course, the "lcd" routine is standard one that comes with ccs, I only removed functionality I do not need and changed the port definition...
     
  3. arhi

    arhi Member

    Joined:
    Apr 17, 2008
    Messages:
    887
    Likes:
    12
    Location:
    Belgrade, .rs
    Software I2C slave implementation

    Ah, and as I noticed lot of people asking for "Software implementation of I2C slave" here is the old code that did just that. It pulls the clock low while writing to LCD in order to stretch the clock

    written in mikroC
    Code (text):

    #define SCL  PORTA.F0
    #define SDA  PORTA.F1

    #define SCLt TRISA.F0
    #define SDAt TRISA.F1

    #define ADDR 0xAA

    unsigned char data;
    unsigned char cnt;
    unsigned char posX;
    unsigned char posY;

    void readByte(){
      cnt  = 0;
      data = 0;
      while(cnt < 8){
        while ( SCL);  //wait for SCL to go down
        while (!SCL);  //wait SCL to go high
        data = (data << 1) | SDA;
        cnt++;
      }
      __asm CLRWDT
    }

    void ack(){
      while(SCL);
      SDAt = 0;
      SDA = 0;
      while(!SCL); while(SCL);
      SDAt = 1;
    }

    void main(){
       SCLt = 1;
       SDAt = 1;
       CMCON = 0b00000111 ; //ALL Digital (comparators off)
       TRISB = 0;
       PORTB = 0;
       TRISA.F2 = 0;
       PORTA.F2 = 0;

       posX = 1;
       posY = 1;
       data = '-';

       Lcd_Custom_Config(&PORTB, 7, 6, 5, 4, &PORTB, 2, 1, 3);
       Lcd_Custom_Cmd(LCD_CLEAR);
       Lcd_Custom_Cmd(LCD_CURSOR_OFF);
       Lcd_Custom_Chr_Cp(data);

       __asm CLRWDT

       while(1){ //main loop
    mainloop:
         SCLt = 1;
         SDAt = 1;
         while(! (SDA & SCL)) __asm CLRWDT;
         while (SDA) __asm CLRWDT ;
         if (SCL){ //START condition
             readByte(); // READ ADDRESS  (i2c id)

             //ACK if we are targeted   (ignore Read bit)
             if (data == ADDR ){
               ack();
             } else {
               goto mainloop;  // not for us, ignore it
             }

             readByte(); // Position
             ack();
             if (data < 16){
               posX = data+1;
               posY = 1;
             } else {
               posX = data-15;
               posY = 2;
             }

             readByte(); // READ DATA
             ack();
             SCLt = 0;     //Hold the clock line low
             SCL = 0;      //Untill you finish writing to LCD
             if (data == '\f') {
               Lcd_Custom_Cmd(LCD_CLEAR);
               posX = 1;
               posY = 1;
             } else if (data == '\n') {
               posY=2;
               posX=1;
             } else {
               Lcd_Custom_Chr(posY, posX, data);
               posX++;
             }
             SCLt = 1;
         } // if
       }// while
    } // main
     
     
    Last edited: Jun 30, 2008
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,151
    Likes:
    337
    Location:
    Brisbane Australia
    ONLINE

    Well done, you may want to consider posting the hex file as most people don't have access to the CCS compiler. A slight improvement would be if it could be made to use the internal oscillator thereby reducing the cost and complexity.

    Edit, How did you decide what address to give it? Has anyone found a comprehensive list of I²C device addresses?

    Mike.
     
    Last edited: Jun 30, 2008
  6. arhi

    arhi Member

    Joined:
    Apr 17, 2008
    Messages:
    887
    Likes:
    12
    Location:
    Belgrade, .rs
    That's not a problem, I can even post a .lst .cof and other ones generated by CCS .. but I believe this can be easily transcribed to any C as it is fairly simple :)
    Thing is, I started with the software implementation, and for 100K i2c 20MHz is kinda minimum :( .. and I was not sure how to setup 16F628(a) to run on 20MHz using internal osc :( ... then, I have just continue with external osc with 690 too (689 will do the job too, even 687) ... somehow, when I set the pic to HS I'm sure what freq it runs on :D I just look at the crystal :)

    In general, with the 690 I will use other pin's for different things as it has more then enough time to run some non rt operation while idle :)
    From what I learned, you need to "apply" for the address. I do not have any device locally that uses AA so I used that address. And as I have source :) I can easily change it ... no way I'm gonna go trough birocracy ...

    an generale, this is something one can build on .. the sw one is not that "powerfull" but the key thing is that "clock stretching" so you can actually perform any action and you do not have to think about "timing" .. I used to use MCP23(0|s)17 (i2c / ssp port extender) for this, but darn 16F628(a) is cheaper :) you can program the address you want, and you can actually put more "logic" inside .. it is actually ~4 times faster to display char trough 16F628 then MCP23017
    (yes, mcp can go 1.7MHz and this is only 100kHz, but when you have other i2c devices on the bus, 100kHz is the safest speed for i2c bus)
     
  7. arhi

    arhi Member

    Joined:
    Apr 17, 2008
    Messages:
    887
    Likes:
    12
    Location:
    Belgrade, .rs
    Here is the PICC project, source, hex, lst .... whole dir .. for the 16F690 hw i2c controller

    Also the software implementation in mikroC is also attached (project, .c, .asm, ....) for the 16F628A

    p.s. Is there any valid reason .RAR is disallowed ?
     

    Attached Files:

Share This Page