Custom Search

PIC 16F876 I2C Slave

The file http://sirloon.net/loonaweb/sirblog/categories/sirbot-project/stuff/simple_i2c_slave.jal is an example of using a PIC 16F88 as an I2C slave device.

This file can be compiles as is under the sirbot system by copying the “sirbot/jal/lab/basic” folder to (for example) “sirbot/jal/lab/i2cSlave”, dropping the “simple_i2c_slave.jal file in to it and changing the Makefile to compile “simple_i2c_slave.jal” instead of “basic.jal”.

Unfortunately, I need to use the larger 16F876a processor.  This is unfortunate, because the sirbot software works nicely under linux, and I could not quickly find a way of transplanting the required lib files for the 876 into it.  I tried, but failed to get the raw JAL system working under linux, but the Starter Kit supplied with Bert van Dam’s book PIC Microcontrollers : 50 Projects for Beginners (me!) and Experts installs and runs fine under windows XP.

With the default install, there is a C:\PICdev folder with the compiler and tools.  In the folder c:\PICdev\Projects you will find samples from the book.  I created a new folder in there and coppied the “simple_i2c_slave.jal” file to it.

Getting it to compile was as simple as using JAL Edit to remove the 3 sirbot include lines and replace them with “include 16F876a_bert”.

I have modified the file to act on commands from the i2c bus.  It is addressed at 0×2e, the commands are as follows:-

0×01: Next Byte is output to Port B

0×02: Next Byte is output to the PIC Serial Port

0×03: Next Bytes Read come from PIC Serial Port

0×04: Next Bytes come from ADC on Port A,0

0×05: Toggles Port A,1

Here is the modified file:-

-- Sebastien Lelong Copyright (c) 2008, http://sirloon.net
--
-- This file is part of the Sirbot Project (http://sirbot.org)
-- Released under the GPL license
--

-- !! changes by Phill, ProjectNotes.co.uk for
-- use with the 16F876a as an I/O extender for the Midge/OpenWrt
-- & Sunspot software driven routers.

-- Note
-- A I load and run this through the tinybld bootloader,
-- I'm not worried about fuses
include 16F876a_bert

--include sb_config
--include sb_protocol
--include sb_mainboard

-- !! 16F876a port C3&4 are the I2C i/o pins
-- AppNote says must be configured as input first
--pin_b4_direction = input

-- Ooops! Forgot to set PORT B to output
-- Didn't see it until I Tried to use Port B from the I2C

pin_b0_direction = output
pin_b1_direction = output
pin_b2_direction = output
pin_b3_direction = output
pin_b4_direction = input -- is LVP PGM input
pin_b5_direction = output
pin_b6_direction = output
pin_b7_direction = output

pin_c6_direction = output -- tx
pin_c7_direction = input  -- rx
pin_c3_direction = input
pin_c4_direction = input

-- For testing purpose (checking if slave is responding to START/STOP signals)
-- you may want to activate 7bits address with interrupts (see spec)
;;SSPCON = 0b_0011_1110    -- slave 7bit address, start/stop interrupt
SSPCON = 0b_0011_0110    -- slave 7bit address

-- I2C slave hardware
-- Careful: we're in 7bits i2c address, *but* PIC
-- wants an address coded on 8bits, that is, with read/write bit
-- In master, slave address is:
--         0x2E <=> 0b_0010_1110
-- So, in slave, by left-shifting once, we have:
--        0x5C <=> 0b_0101_0110
SSPADD = 0x5C

-- init SSPSTAT
BF = false
WCOL = false
SSPOV = false
SSPIF = false
-- enable interrupts
SSPIE = true
GIE = true
PEIE = true

-- !! no ready/busy leds, we want port B all OUT
-- "Ready !" LED
-- var bit ready_led_direction is pin_b0_direction
-- var bit ready_led is pin_b0
-- ready_led_direction = output
-- Error LED
-- var bit error_led_direction is pin_b3_direction
-- var bit error_led is pin_b3
-- error_led_direction = output

var byte tmpstat
var byte tmpbuf
var byte data

-- !! add a few temp registers of our own
var byte serialtemp
var byte command
var bit a1f
command = 00
a1f = off
-- !! set up our a1 toggle bit
pin_a1_direction = output
pin_a1 = a1f
-- !! set up our adc in pin
pin_a0_direction = input

-- !!

-- test to set SSPBUF to see if it's changed while receiving
-- the first byte address
SSPBUF = "A"

function read_i2c() return byte is
tmpbuf = SSPBUF
return tmpbuf
end function

procedure write_i2c(byte in what) is
-- wait 'til buffer is empty
while BF loop end loop
var bit dosend = true
while dosend
loop
WCOL = false
-- try to write into buffer, checking collision
SSPBUF = what
if ! WCOL
then
-- ok, done
dosend = false
end if
-- else continue trying
end loop
CKP = 1
end procedure

procedure proceed_state_1() is
-- state 1:write operation, last byte is address, buffer full
-- byte is an address, it we get here, we just know master
-- wants to talk to us...
-- and we also know address is recognized (BF is set, see spec)
-- anyway, we must read buffer to reset BF bit
read_i2c()
end procedure

procedure proceed_state_2() is
-- state 2: write operation, last byte is data, buffer full
-- got data, need to echo char + 1
data = read_i2c()
-- ultimate data processing... :) -- !! here do something useful
if command == 01 then
portb = data
data = 0
end if
if command == 02 then
serial_hw_write(data)
data = 0
end if

command = data -- Not in a command, this must be a command.
-- if we don't handle it here, it may be a read data command
if command == 05 then
a1f = ! a1f
pin_a1 = a1f
command = 0 -- done with this command
end if

end procedure

procedure proceed_state_3() is
-- state 3: read operation, last byte is address, buffer empty
-- master wants to get a value from us

--!! decide what data to return
if command == 03 then
data = serialtemp -- last serial rx char
serialtemp = 0 -- clear it
end if
if command == 04 then
data = ADC_read_low_res(0)
end if
write_i2c(data)
end procedure

procedure proceed_state_4() is
-- state 4: read operation, last byte is data, buffer empty
-- master still wants to get a value from us
write_i2c(data)
-- note: this shouldn't occur
end procedure

procedure proceed_state_5() is
-- state 5: nack
-- master doesn't want to talk with us anymore
-- reset slave logic
-- AN734 does not talk about setting CKP, whereas spec says
-- it must be set. Some people say it can be error prone.
CKP = 1
data = 0
end procedure

procedure proceed_error() is
-- something went wrong, that is, XOR operations did not match
-- SSPSTAT bits
-- Just log current status
serial_hw_write("E")
serial_hw_write(SSPSTAT)
end procedure

procedure ssp_handler() is
pragma interrupt
if SSPIF
then
SSPIF = false
tmpstat = SSPSTAT
-- mask out unimportant bit
tmpstat = tmpstat & 0b_0010_1101
-- check state 1: write operation, last byte is address, buffer full
if (tmpstat ^ 0b_0000_1001) == false
then
proceed_state_1()
-- check state 2: write operation, last byte is data, buffer full
elsif (tmpstat ^ 0b_0010_1001) == false
then
proceed_state_2()
-- check state 3: read operation, last byte is address, buffer empty
elsif (tmpstat ^ 0b_0000_1100) == false
then
proceed_state_3()
-- check state 4: read operation, last byte is data, buffer empty
elsif (tmpstat ^ 0b_00101100) == false
then
proceed_state_4()
-- check state 5: nack
elsif (tmpstat ^ 0b_0010_1000) == false
then
proceed_state_5()
-- check only got a start signal (when using interrupts)
else
proceed_error()
end if
else
-- another interrupt. Weird...
--        ready_led = low
--        delay_10ms(100)
--        ready_led = high
serial_hw_write("*")
end if
end procedure
-- don't need status leds
--ready_led = low

--error_led = high
--delay_10ms(30)
--error_led = low
--delay_10ms(30)
--error_led = high
--delay_10ms(30)
--error_led = low
--delay_10ms(30)
--error_led = high
--delay_10ms(30)
--error_led = low

--ready_led = high
forever loop
-- just loop until interrupt is raised
-- !! poll the serial port

if RCIF then
serial_hw_read(serialtemp)
serial_hw_write(serialtemp) -- echo to confirm hardware is working!
end if

end loop

————————————————————————————


Powered by WebRing.