Continue to Site

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.

  • 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.

I2C lcd controller using PIC16F690

Status
Not open for further replies.

arhi

Member
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:
#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:
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:
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:
#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:
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:
Well done, you may want to consider posting the hex file as most people don't have access to the CCS compiler.
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 :)
A slight improvement would be if it could be made to use the internal oscillator thereby reducing the cost and complexity.
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 :)
Edit, How did you decide what address to give it? Has anyone found a comprehensive list of I²C device addresses?
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)
 
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 ?
 

Attachments

  • picc_hw-i2c_slave.zip
    27.3 KB · Views: 1,269
  • mikroC_sw-i2c_slave.zip
    31.7 KB · Views: 1,096
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top