Sonsivri
 
*
Welcome, Guest. Please login or register.
Did you miss your activation email?
March 29, 2024, 10:13:49 10:13


Login with username, password and session length


Pages: [1]
Print
Author Topic: I2C LCD Breakout Board in MikroBasic for PIC (I2C slave, PIC to PIC I2C comms)  (Read 6712 times)
0 Members and 1 Guest are viewing this topic.
Jingjok
Inactive

Offline Offline

Posts: 1

Thank You
-Given: 0
-Receive: 7


« on: June 27, 2012, 05:07:39 17:07 »

Hi,

I recently built a LCD breakout board which I use for debugging purposes and did not find much info about I2C slave implementation, so I thought I would share this with the community.
To save ports instead of connecting the LCD module dirctly to the target system (which uses up at least 6 PIC ports), the board uses the I2C protocol, this requires only one data and one clock line (in addition to ground and +5 V).
I hope it is useful for someone who wants to learn how to implement an I2C slave (only write from master to slave is implemented) with a PIC. I chose the PIC 16F1826, mainly because it is cheap, but the same code should work (with minor modificationsn at the PIC initialisation) with any PIC that implements hardware I2C and has enough ports to drive the LCD module. The code below is written in the free version of MikroBasic Pro for PIC 5.61.


Hardware
The harware is rather simple, basically you can use the MikroBasic sample for the MikroBasic LCD library minus the crystal or clkin (the 16F1826 has an internal oscillator), in addition to that the I2C data and I2C clock lines need to be connected to your target system you want to communicte with. According to the I2C specification there needs to be a pull up (4.7K to 10K) on each of both I2C lines. SDA1 is pin 7 (RB1) and SCL1 is pin 10 (RB4) on the 16F1826.
The ports I used for the LCD module are not the same as the LCD library sample, you can change the actual ports used in the declaration section. I used a 4 x 16 LCD with a Hitachi 44780 compatible controller, you will need to modify the the breakout board software slightly if you use a 1 x 16 or 2 x 16 LCD module.


Software
The protocol layer on top of I2C I use is quite simple, similar to other serial LCD breakout boards.
Every byte received is just displayed at the current cursor position, then cursor pos is increased by one. If a COMMAND_ID (254) is received then the next byte is a LCD command which is forwarded to the LCD module.


I2C Master
This is the code the system that communicates with the LCD breakout board would run, it uses the standard master I2C library functions :

----------------------------------------------------------------------------------------
Code:
const MODULE_I2C_ADDRESS = 0x68
const COMMAND_ID = 254   ' if this byte is received the next byte is a command

....

sub procedure MyI2C_lcd_cmd (dim bytCmd as byte)
    I2C1_Start()          ' issue start signal
    I2C1_Wr(MODULE_I2C_ADDRESS)
    I2C1_Wr(COMMAND_ID)
    I2C1_Wr(bytCmd)
    I2C1_Stop()           ' issue stop signal
    delay_ms (100)
end sub

sub procedure MyI2C_lcd_Out (dim row as byte, dim column as byte, dim byref strOut as string[16])
dim i as byte
dim bytCmd as byte
    ' position cursor
   
    bytCmd = 0
    select case row
    case 2
       bytCmd = 0x40
    case 3
       bytCmd = 0x10
    case 4
       bytCmd = 0x50
    end select
    bytCmd = (bytCmd + column - 1) or %10000000

    I2C1_Start()          ' issue start signal
    I2C1_Wr(MODULE_I2C_ADDRESS)
    I2C1_Wr(COMMAND_ID)
    I2C1_Wr(bytCmd)       ' position cursor
    for i = 0 to strlen (strOut) - 1
        I2C1_Wr(byte(strOut[i]))            ' start from word at address (REG0)
    next i
    I2C1_Stop()           ' issue stop signal
end sub

sub procedure MyI2C_lcd_WordOut (dim row as byte, dim column as byte, dim wrdOut as word)
dim strOut as string [5]
    WordToStr (wrdOut, strOut)
    MyI2C_lcd_Out(row,column,strOut)
end sub


....

    InitI2C ()
....
    MyI2C_lcd_cmd (_LCD_CLEAR)

    MyI2C_lcd_Out (1,1,"Test...")
    MyI2C_lcd_WordOut (2,1,bytTest)
....

----------------------------------------------------------------------------------------



I2C Slave
This is the complete code of the LCD breakout board

----------------------------------------------------------------------------------------


Code:
program LCDbreakout

' The protocol is simple : Every byte received is just displayed at the current cursor pos, then cursor pos is increased
' If a COMMAND_ID (254) is received then the next byte is a LCD command which is forwarded to the LCD module

' The LCD address map for the 4 x 16 LCD is 1st line 0x00, 2nd line 0x40, 3rd line 0x10, 4th line 0x50


' Declarations section

const RXBUFFER_SIZE = 80
const COMMAND_ID = 254   ' if this byte is received the next byte is a command


' Lcd module connections
' the following must refer to the latches, not the ports, else read-modify-write problem at higher speeds !
dim LCD_RS as sbit at LATA3_bit
dim LCD_EN as sbit at LATA2_bit
dim LCD_D4 as sbit at LATB0_bit
dim LCD_D5 as sbit at LATA4_bit
dim LCD_D6 as sbit at LATB2_bit
dim LCD_D7 as sbit at LATB3_bit
dim LCD_RS_Direction as sbit at TRISA3_bit
dim LCD_EN_Direction as sbit at TRISA2_bit
dim LCD_D4_Direction as sbit at TRISB0_bit
dim LCD_D5_Direction as sbit at TRISA4_bit
dim LCD_D6_Direction as sbit at TRISB2_bit
dim LCD_D7_Direction as sbit at TRISB3_bit
' End Lcd module connections

dim bytRxByte as byte                      ' I2C received byte
dim bytBufferPos as byte                   ' current position in the receive buffer
dim bytRxBuffer as byte [RXBUFFER_SIZE]
dim bytRxBufferOld as byte [RXBUFFER_SIZE] ' needed to check if a value in the buffer changed

dim bytLCDcommand as byte                  ' last LCD command
dim bytLCDcommandFlag as byte              ' is set if a new command was received
dim bytLCDcommandFollows as byte           ' is set if next byte that will be received will be a command


' I2C slave interrupt handler, only write from master mode is implemented
sub procedure interrupt()

    if (PIR1.SSP1IF = 1) then
        ' I2C Interrupt

        PIR1.SSP1IF = 0 ' reset SSP interrupt flag

        if (SSPSTAT.BF = 1) and (SSPSTAT.S = 1) and (SSPSTAT.R_NOT_W = 0) then
            ' buffer full and start condition and write request
            ' receive data from master

            if (SSPSTAT.D_NOT_A = 0) then
                ' address was read, new transmission block
                bytRxByte = SSPBUF               ' get data to clear buffer
            end if

            if (SSPSTAT.D_NOT_A = 1) then
                ' data was read
                bytRxByte = SSPBUF               ' get data

                if bytLCDcommandFollows = 1 then
                    ' this is a command
                    bytLCDcommand = bytRxByte
                    'check if this is a cursor positioning command, then we need to adjust the buffer pointer
                    if bytLCDcommand.7 = 1 then
                        ' if bit 7 is set it means the command positions the cursor
                        bytBufferPos = bytLCDcommand and %01111111
                        if bytBufferPos >= 0x40 then
                            bytBufferPos = bytBufferPos - 24
                        end if
                    else
                        'some other command
                        bytLCDcommandFlag = 1
                    end if
                    bytLCDcommandFollows = 0
                else
                    ' not a command byte
                    if bytRxByte = COMMAND_ID then
                        'this means the next byte is a command
                        bytLCDCommandFollows = 1
                    else
                        ' this is a data byte
                        bytRxBuffer [bytBufferPos] = bytRxByte
                        bytBufferPos = bytBufferPos + 1
                        ' just in case of rollover
                        if bytBufferPos = RXBUFFER_SIZE then
                            bytBufferPos = 0
                        end if
                    end if
                end if
            end if
        end if

        If SSPCON1.SSPOV = 1 then
          ' overflow, byte was received without clearing buffer first, byte is lost
          ' not necessary
          SSPCON1.SSPOV = 0
        End if
    end if
end sub



' ------------------------------------------------------------
' Main Program
' ------------------------------------------------------------
dim i as byte
dim bytTemp as byte
dim bytDDRAMpos as byte

main:

    OSCCON = %11110000   ' 32 MHz
    ANSELA = 0           ' Set all ports to digital
    ANSELB = 0           '
    ADCON0.ADON = 0      ' A/D off
    CM1CON0.C1ON = 0     ' comparator 1 off
    CM2CON0.C2ON = 0     ' comparator 2 off
    DACCON0.DACEN = 0    ' DAC off


    TRISA = %00000000    ' PORTA output
    TRISB = %00010010    ' PORTB output, except B1 and B4 which are used for the I2C
    PORTA = 0
    PORTB = 0


    ' init SSP
    SSP1ADD = MODULE_I2C_ADDRESS ' Address (7bit). Lsb is don't care
    SSP1CON1 = %00110110 ' Set to I2C slave with 7-bit address
    SSP1CON2 = %00000000
    SSP1CON3 = %00011000
    SSP1MSK = %11111111
    SSP1STAT = %10000000


    ' SDA1 is pin 7 (RB1)
    ' SCL1 is pin 10 (RB4)

    ; init interrupts
    INTCON = %01000000
    PIE1 = %00001000
    PIE2 = %00000000
    PIR1 = %00000000
    PIR2 = %00000000


    ' the 4x16 LCD address map is 0x00, 0x40, 0x10, 0x50
    LCD_Init()
    LCD_Cmd (_LCD_CLEAR)
    LCD_Cmd (_LCD_CURSOR_OFF)

    LCD_Out (1,1, "0x00 I2C LCD 1.0")
    ' 2nd line
    LCD_Cmd (%11000000)
    LCD_Out_cp ("0x40")
    ' 3rd line
    LCD_Cmd (%10010000)
    LCD_Out_cp ("0x10")
    ' 4th line
    LCD_Cmd (%11010000)
    LCD_Out_cp ("0x50")


    delay_ms (3000)

    for i = 0 to RXBUFFER_SIZE - 1
        bytRxBuffer [i] = byte (" ")
        bytRxBufferOld [i] = 0
    next i

    bytLCDcommandFlag = 0
    bytLCDcommandFollows = 0
    bytBufferPos = 0

    ' enable interrupts
    INTCON.GIE = 1

    while true
        'main program loop
        if bytLCDcommandFlag = 1 then
            ' received an LCD command over I2C
            if bytLCDcommand = _LCD_CLEAR then
                for i = 0 to RXBUFFER_SIZE - 1
                    bytRxBuffer [i] = byte (" ")
                next i
            else
                LCD_Cmd (bytLCDcommand)
            end if
            bytLCDcommandFlag = 0
        end if

        for i = 0 to RXBUFFER_SIZE - 1
            bytTemp = bytRxBuffer[i]
            if bytTemp <> bytRxBufferOld[i] then
                ' some value in the buffer changed
                if i >= (RXBUFFER_SIZE div 2) then
                    bytDDRAMpos = (%10000000 or i) + 24
                else
                    bytDDRAMpos = (%10000000 or i)
                end if
                LCD_Cmd (bytDDRAMpos) 'set address
                LCD_Chr_Cp (bytTemp)
                bytRxBufferOld[i] = bytTemp
            end if
        next i
    wend
end.
----------------------------------------------------------------------------------------
Logged
Pages: [1]
Print
Jump to:  


DISCLAIMER
WE DONT HOST ANY ILLEGAL FILES ON THE SERVER
USE CONTACT US TO REPORT ILLEGAL FILES
ADMINISTRATORS CANNOT BE HELD RESPONSIBLE FOR USERS POSTS AND LINKS

... Copyright © 2003-2999 Sonsivri.to ...
Powered by SMF 1.1.18 | SMF © 2006-2009, Simple Machines LLC | HarzeM Dilber MC