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
————————————————————————————