Over the last few days I have developed a operational solution for an I2C Hardware Slave for Microchip microcontrollers.
This is extremely easy to use and makes the implementation of a slave very easy.
This implementation is an ISR. It is therefore highly reliant on processing time and the adherence with the I2C specification. There is an I2C handler and this is not intended to be user configurable and it is not intended to edited by the user.
As an ISR if the user turns on/off the interrupts in the user routines this WILL cause I2C protocol errors and overruns. Do not use interrupts without considering the impact on the I2C protocol.
When developing solutions adding serial or LCD output must be fully optimised to ensure I2C protocol, in terms of timing, is maintained. Longs strings will cause I2C overruns.
Interfacing to Slave attached devices is relatively simple. This is completed in the user code.
How does this work?
You have a Master I2C device. It writes and reads using the I2C protocol.
The Slave responds to the writes and read. The Slave code exposed the message in a simple message queue. The user can examine the message queue and take appropriate action or return values to the Master.
Hardware I2C ISR Handler Library
This library provides an ISR to implement a stateful I2C hardware slave.
This is a GCB implementation of Microchip Application Note AN734. According to AN734, and, Sebastien Lelong and Joep Suijs who created the JAL implementation, there are 5 possible I2C states. During ISR, each of this states are detected. This ISR provides a standard skeleton to implement an I2C hardware slaves, while client code must implement several callbacks the ISR is expecting to call while processing states.
Callbacks in the library:
HI2CSlave_State_1 ( in I2Ctemp as byte ) - called when I2C address matches (master starts a talk)
HI2CSlave_State_2 ( in I2CByte as byte) - called when master is writing a byte. Slave is thus receiving this byte. This callback takes this bytes as argument
HI2CSlave_State_3 called when master wants to read a byte from slave. Thus, slave should send a byte using HI2C2Send
HI2CSlave_State_4 called when master still wants to read a byte from slave. That is, master required to read (state 3) and now still want to read a byte slave should send a byte using HI2C2Send
HI2CSlave_State_5 called when master does not want to talk to slave anymore usually a good place to reset data or slave's logic
HI2CSlave_State_Error called when something wrong happens. You can do what you want in this case, like resetting the PIC, log some information using usart, ... called any cases other than states 1, 2, 3, 4 or 5
Also, there is an enable the line below to enable testing of the clock stretching feature it will add an additional delay of 200us in the interrupt handler so were sure that clock stretching is required for 100 KHz I2C operation
The I2C Message Handler Library
The I2C Message Handler library provides the interface for the user to supply a procedure to process the received message.
Basically, this I2C slave waits for a full message to arrive. Then it calls the User procedures to process the message and (optional) prepare a response. Subsequently, this library will pass the response data over to the master, if it wants to have them.
Callbacks:
HI2C_Process_In_Message( HI2CIndex as byte ) called when I2C a Master starts a talk. HI2CIndex is the length of the incoming packet.
HI2C_Process_Outgoing_Message called when I2C an Master needs to a talk.
The HI2CBUFFER should be populated with the data to be sent.
All other methods are Callbacks from the ISR routine.
Demonstration Code
The demonstration code creates I2C Slave on a specific I2C address.
The slave responds to a write of 4 bytes to I2C address 0x4C (which is really Read Address 0x4D) and the Slave responds with two data bytes – from two pots. The results are shown on a terminal.
Callbacks:
HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte ) called when slave has a full buffer.
HI2C_Process_Out_Message called when slave is requested to respond to a masters request.
Users should redirect all or any of the standard callbacks like HI2CSlave_State_Error, HI2CSlave_State_1-5 using #define to call specific routines. #define HI2CSlave_State_Error MyHI2CSlave_State_Error
Code in the package
The tests to showcase the new GCB capabilities are:
1. Demonstrate how to configure the I2C Slave address and to configure the I2C queue
2. Respond to I2C discovery with the response from the Write and Read addresses
3. Demonstration code on using how to use the I2C Slave using GCB Message Queue
a. Receive I2C data from an I2C Master to this I2C Slave – with the I2C Slave handling the incoming via a Message Queue, and,
b. Send I2C data from this I2C Slave to the I2C Master. The data being sent should be sensor data sourced from the Slave.
Software Configuration
1. GCB v0.95.010
2. HW I2C ISR Library ( new .h ). This file is not intended to be edited by a user.
3. HW I2C Message Queue Library ( new .h ). This file is not intended to be edited by a user.
4. Example 16F demonstration code ( new )
Installation of new capabilities
Install GCB v0.95.010, copy the two .h into INCLUDE folder. These files are not intended to be edited by a user.
Using GCB v0.95.010, copy the hwI2C .h into INCLUDE\LOWLEVEL folder. This file is not intended to be edited by a user.
Install the Demo user GCB file into the any suitable code development folder. This file is intended to be edited by the user.
Message Queue Structure
Essentially the I2C messages are exposed in the Message Queue. The Message Queue is a buffer. The size of the buffer is user determined but this will be based on the maxiumum I2C message to be communicated.
The methods expose the buffer and with very simple GCB logic users can respond to the specifics of the I2C message.
HI2CSend(TargetGCBI2CAddress) ;WriteOp
HI2CSend(outvar) ;first value = 1
HI2CSend(outvar+1) ;then the value = 2
HI2CSend(outvar+2) ;then the value
HI2CSend(outvar+3) ;then the value
HI2CSend(outvar+4) ;then the value
HI2CSend(outvar+5) ;then the value=6
HI2CStop
Is exposed as 83:01:02:03:04:05:06: in the buffer array
The queue is user specified. And addressed via HI2CBUFFER(1) thru to HI2CBUFFER(N) where N is HI2CMESSAGESIZE. HI2CMESSAGESIZE will confirm message size.
HI2CBUFFER(0) will contain the first sent byte (the Write instruction)
Master Write then Read (aka an EEPROM addressing scheme) All Master messages of this type MUST be the same structure in terms of the length to the Send/Write component of the message.
HI2CStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;WriteOp
HI2CSend(outvar) ;first value = 1
HI2CReStart ;generate a restart signal
HI2CSend(TargetGCBI2CAddress XOR 1) ;inidicate a read
HI2CReceive(Val1, ACK) ;read one byte
HI2CReceive(Val2, NACK) ;read one byte and conclude
HI2CStop 82
Would provide a queue of 1 bytes long.
Reading the Incoming Queue
HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte ) is called when slave has a full buffer.
The variable HI2CMESSAGESIZE will specify the buffer input queue length.
If the demo code the incoming queue is copied to a local queue for display purpose.
A use case could be read the byte pattern or first byte and respond with specific sensors results – which would be placed in the output queue.
Writing to the outgoing Queue
HI2C_Process_Out_Message is called when slave is requested to respond to a masters request for data.
Data bytes should be placed in the buffer in the correct order where buffer element 0 is the first data byte transmitted.
The supporting libraries are intended to hide the complexities of the state engine, and the buffer is intended to make things easy.
Use Case #1
Emulate DS1307 where code would be
do
HI2CReStart ;generate a start signal
HI2CSend(AddrWrite) ;inidcate a write
loop While HI2CAckPollState
HI2CSend(0) ;begin with address 0
HI2CSend(DecToBcd(DS_Sec)) ;then set the three
HI2CSend(DecToBcd(DS_Min)) ;consecutive values
HI2CSend(DecToBcd(DS_Hour))
HI2CStop
This could update the 3 memory addresses in the slave device. Slave code would be….
Sub HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte )
Select CASE HI2CBUFFER[0]
Case 0x00
if (HI2CMESSAGESIZE = 4) then
SecVar = HI2CBUFFER[1]
MinVar = HI2CBUFFER[2]
HourVar= HI2CBUFFER[3]
end if
….
….
Use Case #2
Response to various message types. This is working/tested code.
sub HI2C_Process_In_Message ( in HI2CMESSAGESIZE )
'We have data!!
Select CASE HI2CBUFFER(0)
Case 0x80
if (HI2CMESSAGESIZE = 1) then
' cmd 0x80 - request version
HI2CBUFFER(1) = REV_HI_BYTE 'application code version, write one
HI2CBUFFER(2) = REV_LO_BYTE 'byte, read three bytes
end if
CASE 0x81
if (HI2CMESSAGESIZE = 1) then
' cmd 0x81 - turn on power to temperature sensors
I2C_TS_EN = on 'power to temp sensors "on"
HI2CBUFFER(1) = 0xFF 'report status of power to TS
end if
CASE 0x82
if (HI2CMESSAGESIZE = 1) then
' cmd 0x82 - turn off power to temperature sensors
I2C_TS_EN = off ' power to temp sensors "off"
HI2CBUFFER(1) = 0x00
end if
CASE 0x55
' cmd 0x55 - set PWM duty on 7 channels
if (HI2CMESSAGESIZE = 8) then 'write 8 bytes, first command,
BB = HI2CBUFFER(1) ' seven bytes with values from
GG = HI2CBUFFER(2) '0 to 100 for setting PWM in persents.
HR = HI2CBUFFER(3) '85,xx,xx,xx,xx,xx,xx,xx"
EQW = HI2CBUFFER(4) ' read 8 bytes for verification
FR = HI2CBUFFER(5) '
WW = HI2CBUFFER(6) '
UV = HI2CBUFFER(7) '
end if
' cmd 0x83 - request User ID
CASE 0x83
if (HI2CMESSAGESIZE = 8) then
' User_ID () 'Call procedure reading User ID to write 1 byte
BB = HI2CBUFFER(1) ' seven bytes with values from
GG = HI2CBUFFER(2) '0 to 100 for setting PWM in persents.
HR = HI2CBUFFER(3) '85,xx,xx,xx,xx,xx,xx,xx"
EQW = HI2CBUFFER(4) ' read 8 bytes for verification
FR = HI2CBUFFER(5) '
WW = HI2CBUFFER(6) '
UV = HI2CBUFFER(7) '
end if
End Select
end sub
The GCB code related to this is as follows:
do
HI2CStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;inidcate a write
I2CRetry++
loop While HI2CAckPollState and I2CRetry <> 255
HI2CSend(0X55)
HI2CSend(outvar) ;then the value
HI2CSend(outvar+1) ;then the value
HI2CSend(outvar+2) ;then the value
HI2CSend(outvar+3) ;then the value
HI2CSend(outvar+4) ;then the value
HI2CSend(outvar+5) ;then the value
HI2CSend(outvar+6) ;then the value
HI2CStop
outvar++
‘init the vars
eepromVal1 = 0X55
eepromVal2 = 0X55
do
HI2CReStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;indicate a write
loop While HI2CAckPollState
HI2CSend(0x80)
HI2CReStart
HI2CSend(TargetGCBI2CAddress XOR 1) ;set the read flag
HI2CReceive(eepromVal1, ACK) ;read one byte
HI2CReceive(eepromVal2, ACK) ;read one byte
HI2CReceive(eepromVal3, NACK) ;read one byte and conclude
HI2CStop
HSerPrint eepromVal1
HSerPrint ":"
HSerPrint eepromVal2
HSerPrint ":"
HSerPrint eepromVal3
HSerPrintCRLF
do
HI2CReStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;indicate a write
loop While HI2CAckPollState
HI2CSend(0x81) ;number of bytes to request or low address
HI2CReStart
HI2CSend(TargetGCBI2CAddress XOR 1) ;set the read flag
HI2CReceive(eepromVal1, ACK) ;read one byte
HI2CReceive(eepromVal2, NACK) ;read one byte
HI2CStop
HSerPrint eepromVal1
HSerPrint ":"
HSerPrint eepromVal2
HSerPrintCRLF
do
HI2CReStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;indicate a write
loop While HI2CAckPollState
HI2CSend(0x82)
HI2CReStart
HI2CSend(TargetGCBI2CAddress XOR 1) ;set the read flag
HI2CReceive(eepromVal1, ACK) ;read one byte
HI2CReceive(eepromVal2, NACK) ;read one byte
HI2CStop
HSerPrint eepromVal1
HSerPrint ":"
HSerPrint eepromVal2
HSerPrintCRLF
do
HI2CStart ;generate a start signal
HI2CSend(TargetGCBI2CAddress) ;indicate a write
I2CRetry++
loop While HI2CAckPollState and I2CRetry <> 255
HI2CSend(0X83)
HI2CSend(outvar) ;then the value
HI2CSend(outvar+1) ;then the value
HI2CSend(outvar+2) ;then the value
HI2CSend(outvar+3) ;then the value
HI2CSend(outvar+4) ;then the value
HI2CSend(outvar+5) ;then the value
HI2CSend(outvar+6) ;then the value
HI2CStop
outvar++
I will post a package to SVN very soon.
Last edit: Anobium 2016-11-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I tested the above with a PIC16F690 on a Low Pin Count demo board talking to the XPress board and it worked first time. I flashed and Powered the Master with a PICKit 2 and had GCBasic set to Program the XPress board directly. The 3rd USB cable is a TTL / Serial bridge that I used to run I2C detection code on the PIC16F690 Prior to loading the host code:
Well done Anobium, a very useful library.
I already have some ideas I want to try with it.
Evan, this is awesome stuff. This is a great solution to create "custom" I2C peripherals using PICs. I have a couple of questions about the in message sub..
In your video demo using the xpress board as a slave, the sub uses this code...
sub HI2C_Process_In_Message ( in HI2CMESSAGESIZE )
'We have data!! Just send some the First byte back
Select CASE HI2CBUFFER(0)
Case 0x23
if (HI2CMESSAGESIZE = 2) then
porta = HI2CBUFFER(1)
end if
end Select
end sub
In the demo file for the 16f88 the in message sub uses this code...
sub HI2C_Process_In_Message ( in HI2CMESSAGESIZE )
'We have data!!
Select CASE HI2CBUFFER(0)
Case 0x00
LED0 = HI2CBUFFER(1)
Case 0x01
LED1 = HI2CBUFFER(1)
Case 0x02
LED2 = HI2CBUFFER(1)
end Select
end sub
On the xpress demo, what is the reason behind sending the 0x23 after the address? Is this just a "double check" or does it represent an I2C register? In the 16f88 code, you just read the byte after the address and use that as data.
Also, I want to use this for a simple experiment using a 12f1840 which has a mssp, but I noticed in the above post that you say these libaries should work with all 16f chips. Is it possible to use the 12f1840 with this library?
Thanks again for a GREAT example!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thank you. If you can go to Youtube - select like and thumbs up please. The new Youtube video scoring means visits with no rating will not show in the searches! So, everyone - it is critical to select like and give thumbs etc. Take a few moments every time to help.
Back the question:
The diagram above show an i2c packet. Essentially, an address and data (data...data). The library handles everything and the routine exposes presents the data )data...data) in the buffer.
In one example I used 0x23 as a 'command' byte, as shiwn in this diagram . I could have therefore have 255 [0 to 255] commands and your CASE statement could complete 255 different commands.
In the other example - I simply read the data and did an action based upon the incoming data.
Think of the first example as simulation an EEPROM with the second example as being a trivial example to set some LEDs.
Regarding the 12f. Should work. However, if not, the library functions are documented. I think there is one place where I had to adapt the code for 16f or 18f - so, this is the area that may need adapation.
Please ENSURE you have the latest library from the v0.96.00 build. I did fix an issue in this release.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thats a great explanation. I will try the code with a 16f1825 first just to see it work and then try the 12f. I downloaded the entire great cow basic yesterday so can I assume it has the latest liabrary? If not, where do I find that library? Will keep you posted on progress and thanks again for the awesome work. I will go back to you tube and respond also.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi Evan, I am having difficulty making this work. So I basically took the code from the 16f1939 example and changed the relevant pins to use the 16f1825's I2C and changed the led pins. No issues when compiling and flashing. I did not see any serial prints when I sent data to the pic from the in message sub. I do have pullups on the I2C bus.
I hooked up another micro to scan the I2C bus and the 16f does not show up. So I attaced a 24lc32 eeprom to the same bus and it shows up at address (h50) which is correct. I tried 100 and 400khz bus speed (in the scanner and pic). The leds flash and hserprint works when pic is powered up, so pretty sure pic is working. No error leds light up. Sending and receiving I2C data to the eeprom work as expected. When I send a byte of data to address 0x30 from a master device, it should print the data on the serial terminal from the in message sub.
I hooked up a logic analyser to the bus and see what happens when the scanner sees the eeprom, but no response from the pic. Eeprom shows read from 50 with ACK. Pic shows read from 30 with NAK.
I am not sure if my config has anything to do with it. I am using portC.1 (pin 9) for SDA and port C.0 (pin 10) for SCK. Here is the code I am using...
''''********************************************************************************;---'''Configuration#chip 16F1825, 20#config OSC = HS, Pllen = OFF#include<HWI2C_MessageInterface.h> 'Defines a set of callbacks - you do NOT need to define HI2CSlave_State_1-5#define HI2CSlaveEnableStartStopInterrupts'RedirectstandardErrorhandlertomyErrorhandler#define HI2CSlave_State_Error MyHI2CSlave_State_Error'Buffersize#define HI2CBUFFERSIZE 16'errormessagingLEDs#define HI2CSlaveSSPOVOverflowErrorLED LED0#define HI2CSlaveStateNotHandledErrorLED LED1'setupalertLEDs#ifdef HI2CSlaveStateNotHandledErrorLEDDirHI2CSlaveStateNotHandledErrorLEDout#endif#ifdef HI2CSlaveSSPOVOverflowErrorLEDDirHI2CSlaveSSPOVOverflowErrorLEDout#endif'RequiredI2Csettings-CHANGEPORTSifrequired#define hi2c_BAUD_RATE 400#define hi2c_DATA PORTc.1#define hi2c_CLOCK PORTc.0'InitialiseI2CMaster'I2CpinsneedtobeinputforSSP2moduleDirhi2c_DATAinDirhi2c_CLOCKinHI2CSetAddress0x30HI2CModeSlaveHI2CSlave_ISR_Init'Thisisrequiredtoinitialisethelibrary'THISCONFIGOFTHESERIALPORTWORKSWITHmax232THENTOPC'USARTsettings#define USART_BAUD_RATE 9600#define USART_TX_BLOCKINGdirportc.4out;---'''DefineHardwaresettings#define LED0 porta.0 ;pin 1#define LED1 porta.1 ;pin 2#define LED2 porta.2 ;pin 3dirLED0out;0,1and2areoutputs(LEDs)dirLED1out;0,1and2areoutputs(LEDs)dirLED2out;0,1and2areoutputs(LEDs;---'''VariablesdimHI2CForLoopasbyte;---'''Mainbodyofprogramcommenceshere.Repeat20LED0=!LED0LED1=!LED1LED2=!LED2wait100msendRepeathserprint"before interrupt setup"hserprintcrlfOnInterruptSSP1ReadycallHI2CSlave_ISR_HandlerdoForever'ThisterminaloutputWILLcausetimingissues.ThisNOTaRecommendmethodtoshowthedatastream-useHserSendinHEX!'DostuffLoopsubHI2C_Process_In_Message(inHI2CMESSAGESIZE)'Wehavedata!!hserprint"I2C data = "hserprintHI2CBUFFER(0)hserprintcrlfendsubsubHI2C_Process_Out_Message'Wanttopostprocessthedata?doithere.endsub#define HI2CSlave_State_Error MyHI2CSlave_State_ErrorsubMyHI2CSlave_State_Error(inHI2CErrorCode)dimI2Ctempasbyte'HI2CErrorCode=Whatcausedtheerror...'SSPSTATwilltellyouthestatus'RecommendtoconsumetheBufferDoWhileBF=1I2Ctemp=SSPBUFhserprint"I2Ctemp = "hserprintI2CtemphserprintcrlfLoopHSerSend0SelectCaseHI2CErrorCode#ifdef HI2CSlaveSSPOVOverflowErrorLEDCaseHI2CSlaveSSPOVOverflowErrorHSerSend1#endif#ifdef HI2CSlaveStateNotHandledErrorLEDCaseHI2CSlaveStateNotHandledErrorHSerSend2#endifEndSelectendSub#define HI2CMessageHandler_State_On_Start myHI2CMessageHandler_State_On_StartsubmyHI2CMessageHandler_State_On_Start'letuserprocessbufferendsub#define HI2CMessageHandler_State_On_Stop myHI2CMessageHandler_State_On_StopsubmyHI2CMessageHandler_State_On_Stop'letuserprocessbufferendsub
P.S. I am using the pic16f1825 with a 20mHz crystal. I can run other test programs and the setup is working (hserprint, leds flash etc.). Not sure if clock speed has anything to do with it.
Also, what is the minumum code setup that will work for this, i.e. I just want to send a byte of data from the master once in a while and perform a small task on the pic with that data byte. I don't need any response or error handeling. Can I simply strip those things out of the program or does the library need all these things in the program to work correctly?
I will not give up!
Thanks!
Last edit: viscomjim 2017-01-22
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
This will is because the code is using SSPIF as the interrupt, and guess what, your chip needs SSP1IF.
Test please.
You should try this as an alternaitve method. This is what I will put in the library to resolve.
#script
If nobit(SSPIF) then
If bit(SSP1IF) then
SSPIF=SSP1IF
end if
if nobit(SSP1IF) then
Warning "SSPIF not mapped to SSP1IF ….library is likely to fail"
End if
End if
#endscript
Last edit: Anobium 2017-01-22
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi Evan, as I could not get the 16f1825 working, I switched over to the xpress board just to see and it is working great, so at least now I know for sure that I am sending the data correctly. This is working fine using the 4 leds on the xpress board, and I can see it working. I can send the data pretty quickly to the point where you cant see the leds changing, counting from 0 to 15. I have to add a 3ms delay between counts just to see the last led flashing quickly. So, seems to work very well. I will switch back to the 16f1825 and try your code addition by adding...
#define SSPIF SSP1IF
I have never used scripts before, so where does the script actually go?
As far as the xpress board or other pic, and timing seems to be critical, I wonder how to go about doing something like this...
I want to be able to send a data byte to the pic slave I2C device, so I will just send the address and the data and the data will be HI2CBUFFER(0). I want to use that byte for another operation that the pic will do. I will be sending the i2c data to the pic every 50ms or so. During this 50ms period, I need to perform a simple operation with the HI2CBUFFER(0) byte and send a serial stream that takes no longer than 2 ms to send in completion (just calculating the amount of data being sent at a high baud rate). Unfortunately, i have to use the hserprint command as I am sending ascii text to another unit that requires it.
I noticed if I use just the...
hserprint "I2C data = "
hserprint HI2CBUFFER(0)
hserprintcrlf
in the "in message" sub as before, on the xpress board setup, it does seem to hose things up timing wise when I try to print the byte to the terminal. Does the hserprint command have a large overhead or what is causing the timing issues? I am assuming that the i2c interrupt only occurs when new data is sent from a master and the library code throws the data (assuming one byte is sent) to the HI2CBUFFER(0). Is there a limit to the amount or speed at which you can receive the byte and do some processing before the next byte is received? Should the extra operations not be done in the "in message" sub? Sorry for all the questions.
Thanks!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Try the attachment. I have updated the library to cater for chips with SSP1IF. You can look at the script in the file to review how this is resolved programmatically.
Timing. It is important to understand the timing. 'A small delay is required to give time for the eeprom to save the data' I just took that from a page off a Maxim I2C EEProm device, and, if you search the same page it states 'It is important that we give the 24C04 enough time to write. This typically takes several milliseconds after the "stop-condition." Consult the data sheet of your IC to make sure that you use the correct timing.'. This is no different for your implementation. The timing of the 'process in message' buffer needs to understood and the master i2c device needed to handle appropiately.
Using HSerPrint and the other output commands all take time and the master is simply sending data onto the i2c bus. You have choices.
- On the master check for a NAK from the slave. A NAK would indicate the master is busy, I think we do this in the EEProm using HI2CAckPollState (a Great Cow BASIC HWI2C method).
- Add an appropiate delay to the master, in a similar manner to the writing to an EEprom.
- We could enhance the library to include clock line stretching, see below.
-
But, using HSer routine are not slow they just take time. Consider using HSerSend and get the master to do the hard work. Sending a raw data stream removes the use of String handling in the slave device. So, rather then sending just the one data byte, send the compete string (as bytes) and then use HserSend to essentially print raw ASCII to the terminal.
Do try clock line stretching. Simply add 'CKP = 0' as the first command within your HI2C_Process_In_Message method. CKP will be handled by the library appropiately after your code has completed. Set CKP and let me know the results.
Re Interrupts. There is only one interrupt defined. The interrupt supports the I2C routines. So what is happening? The master is overrunning the input buffer. There are error routines in the library to show that this has occurred, in your code this is a routine called 'MyHI2CSlave_State_Error ( in HI2CErrorCode ). I am 100% certain you are getting buffer overruns because the master is simply sending data whilst the slave is send Hser data. This is the same problem as any EEProm and this is not specific to these libraries - timing is important with I2C.
I will give these suggestions a try and consider the amount of time required for the actions. I am still playing with the xpress board currently, but will be switching back to the 16f1825 using the new handler code. It seems the 12f1840 has the same SSP1IF so it may work also as a small I2C "custom" controller. I may break up my operation between two pics, one to read the i2c from the master (i'm only reading the lower 4 bits from the master (0 - F)) and continously outputting them on 4 gpios. Then the second can constantly poll the 4 gpio bits and create the constant serial string needed. This would be totally async. but should work for what I am doing. Will keep trucking along. This is a great addition to the GCB arsenol, and I appreciate your work! This will have lots of uses for sure.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Over the last few days I have developed a operational solution for an I2C Hardware Slave for Microchip microcontrollers.
This is extremely easy to use and makes the implementation of a slave very easy.
This implementation is an ISR. It is therefore highly reliant on processing time and the adherence with the I2C specification. There is an I2C handler and this is not intended to be user configurable and it is not intended to edited by the user.
As an ISR if the user turns on/off the interrupts in the user routines this WILL cause I2C protocol errors and overruns. Do not use interrupts without considering the impact on the I2C protocol.
When developing solutions adding serial or LCD output must be fully optimised to ensure I2C protocol, in terms of timing, is maintained. Longs strings will cause I2C overruns.
Interfacing to Slave attached devices is relatively simple. This is completed in the user code.
How does this work?
You have a Master I2C device. It writes and reads using the I2C protocol.
The Slave responds to the writes and read. The Slave code exposed the message in a simple message queue. The user can examine the message queue and take appropriate action or return values to the Master.
Hardware I2C ISR Handler Library
This library provides an ISR to implement a stateful I2C hardware slave.
This is a GCB implementation of Microchip Application Note AN734. According to AN734, and, Sebastien Lelong and Joep Suijs who created the JAL implementation, there are 5 possible I2C states. During ISR, each of this states are detected. This ISR provides a standard skeleton to implement an I2C hardware slaves, while client code must implement several callbacks the ISR is expecting to call while processing states.
Callbacks in the library:
HI2CSlave_State_1 ( in I2Ctemp as byte ) - called when I2C address matches (master starts a talk)
HI2CSlave_State_2 ( in I2CByte as byte) - called when master is writing a byte. Slave is thus receiving this byte. This callback takes this bytes as argument
HI2CSlave_State_3 called when master wants to read a byte from slave. Thus, slave should send a byte using HI2C2Send
HI2CSlave_State_4 called when master still wants to read a byte from slave. That is, master required to read (state 3) and now still want to read a byte slave should send a byte using HI2C2Send
HI2CSlave_State_5 called when master does not want to talk to slave anymore usually a good place to reset data or slave's logic
HI2CSlave_State_Error called when something wrong happens. You can do what you want in this case, like resetting the PIC, log some information using usart, ... called any cases other than states 1, 2, 3, 4 or 5
Also, there is an enable the line below to enable testing of the clock stretching feature it will add an additional delay of 200us in the interrupt handler so were sure that clock stretching is required for 100 KHz I2C operation
The I2C Message Handler Library
The I2C Message Handler library provides the interface for the user to supply a procedure to process the received message.
Basically, this I2C slave waits for a full message to arrive. Then it calls the User procedures to process the message and (optional) prepare a response. Subsequently, this library will pass the response data over to the master, if it wants to have them.
Callbacks:
HI2C_Process_In_Message( HI2CIndex as byte ) called when I2C a Master starts a talk. HI2CIndex is the length of the incoming packet.
HI2C_Process_Outgoing_Message called when I2C an Master needs to a talk.
The HI2CBUFFER should be populated with the data to be sent.
All other methods are Callbacks from the ISR routine.
Demonstration Code
The demonstration code creates I2C Slave on a specific I2C address.
The slave responds to a write of 4 bytes to I2C address 0x4C (which is really Read Address 0x4D) and the Slave responds with two data bytes – from two pots. The results are shown on a terminal.
Callbacks:
HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte ) called when slave has a full buffer.
HI2C_Process_Out_Message called when slave is requested to respond to a masters request.
Users should redirect all or any of the standard callbacks like HI2CSlave_State_Error, HI2CSlave_State_1-5 using #define to call specific routines. #define HI2CSlave_State_Error MyHI2CSlave_State_Error
Code in the package
The tests to showcase the new GCB capabilities are:
1. Demonstrate how to configure the I2C Slave address and to configure the I2C queue
2. Respond to I2C discovery with the response from the Write and Read addresses
3. Demonstration code on using how to use the I2C Slave using GCB Message Queue
a. Receive I2C data from an I2C Master to this I2C Slave – with the I2C Slave handling the incoming via a Message Queue, and,
b. Send I2C data from this I2C Slave to the I2C Master. The data being sent should be sensor data sourced from the Slave.
Software Configuration
1. GCB v0.95.010
2. HW I2C ISR Library ( new .h ). This file is not intended to be edited by a user.
3. HW I2C Message Queue Library ( new .h ). This file is not intended to be edited by a user.
4. Example 16F demonstration code ( new )
Installation of new capabilities
Install GCB v0.95.010, copy the two .h into INCLUDE folder. These files are not intended to be edited by a user.
Using GCB v0.95.010, copy the hwI2C .h into INCLUDE\LOWLEVEL folder. This file is not intended to be edited by a user.
Install the Demo user GCB file into the any suitable code development folder. This file is intended to be edited by the user.
Message Queue Structure
Essentially the I2C messages are exposed in the Message Queue. The Message Queue is a buffer. The size of the buffer is user determined but this will be based on the maxiumum I2C message to be communicated.
The methods expose the buffer and with very simple GCB logic users can respond to the specifics of the I2C message.
Is exposed as 83:01:02:03:04:05:06: in the buffer array
The queue is user specified. And addressed via HI2CBUFFER(1) thru to HI2CBUFFER(N) where N is HI2CMESSAGESIZE. HI2CMESSAGESIZE will confirm message size.
HI2CBUFFER(0) will contain the first sent byte (the Write instruction)
Master Write then Read (aka an EEPROM addressing scheme) All Master messages of this type MUST be the same structure in terms of the length to the Send/Write component of the message.
Would provide a queue of 1 bytes long.
Reading the Incoming Queue
HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte ) is called when slave has a full buffer.
The variable HI2CMESSAGESIZE will specify the buffer input queue length.
If the demo code the incoming queue is copied to a local queue for display purpose.
A use case could be read the byte pattern or first byte and respond with specific sensors results – which would be placed in the output queue.
Writing to the outgoing Queue
HI2C_Process_Out_Message is called when slave is requested to respond to a masters request for data.
Data bytes should be placed in the buffer in the correct order where buffer element 0 is the first data byte transmitted.
The supporting libraries are intended to hide the complexities of the state engine, and the buffer is intended to make things easy.
Use Case #1
Emulate DS1307 where code would be
This could update the 3 memory addresses in the slave device. Slave code would be….
Use Case #2
Response to various message types. This is working/tested code.
The GCB code related to this is as follows:
I will post a package to SVN very soon.
Last edit: Anobium 2016-11-04
Documentation attached.
I tested the above with a PIC16F690 on a Low Pin Count demo board talking to the XPress board and it worked first time. I flashed and Powered the Master with a PICKit 2 and had GCBasic set to Program the XPress board directly. The 3rd USB cable is a TTL / Serial bridge that I used to run I2C detection code on the PIC16F690 Prior to loading the host code:

Well done Anobium, a very useful library.
I already have some ideas I want to try with it.
Cheers
Chris
Last edit: Chris Roper 2016-11-04
Slaves implementEd during the testing of this code are:
16f18855
16f1939
16f88
16f690
These libraries should work on all 16f chips with SSP or MSSP modules. 18f will require someone to test.
Last edit: Anobium 2016-11-04
And, a Youtube video. See here
Enjoy.
Evan, this is awesome stuff. This is a great solution to create "custom" I2C peripherals using PICs. I have a couple of questions about the in message sub..
In your video demo using the xpress board as a slave, the sub uses this code...
In the demo file for the 16f88 the in message sub uses this code...
On the xpress demo, what is the reason behind sending the 0x23 after the address? Is this just a "double check" or does it represent an I2C register? In the 16f88 code, you just read the byte after the address and use that as data.
Also, I want to use this for a simple experiment using a 12f1840 which has a mssp, but I noticed in the above post that you say these libaries should work with all 16f chips. Is it possible to use the 12f1840 with this library?
Thanks again for a GREAT example!
Thank you. If you can go to Youtube - select like and thumbs up please. The new Youtube video scoring means visits with no rating will not show in the searches! So, everyone - it is critical to select like and give thumbs etc. Take a few moments every time to help.
Back the question:
The diagram above show an i2c packet. Essentially, an address and data (data...data). The library handles everything and the routine exposes presents the data )data...data) in the buffer.
In one example I used 0x23 as a 'command' byte, as shiwn in this diagram
. I could have therefore have 255 [0 to 255] commands and your CASE statement could complete 255 different commands.
In the other example - I simply read the data and did an action based upon the incoming data.
Think of the first example as simulation an EEPROM with the second example as being a trivial example to set some LEDs.
Regarding the 12f. Should work. However, if not, the library functions are documented. I think there is one place where I had to adapt the code for 16f or 18f - so, this is the area that may need adapation.
Please ENSURE you have the latest library from the v0.96.00 build. I did fix an issue in this release.
Thats a great explanation. I will try the code with a 16f1825 first just to see it work and then try the 12f. I downloaded the entire great cow basic yesterday so can I assume it has the latest liabrary? If not, where do I find that library? Will keep you posted on progress and thanks again for the awesome work. I will go back to you tube and respond also.
The latest is v0.96.00. If you downloaded yesterday then this is ok.
Next release v0.96.01 will be out very soon - with patches for 18f, plus revised .dat files to simplify support.
Hi Evan, I am having difficulty making this work. So I basically took the code from the 16f1939 example and changed the relevant pins to use the 16f1825's I2C and changed the led pins. No issues when compiling and flashing. I did not see any serial prints when I sent data to the pic from the in message sub. I do have pullups on the I2C bus.
I hooked up another micro to scan the I2C bus and the 16f does not show up. So I attaced a 24lc32 eeprom to the same bus and it shows up at address (h50) which is correct. I tried 100 and 400khz bus speed (in the scanner and pic). The leds flash and hserprint works when pic is powered up, so pretty sure pic is working. No error leds light up. Sending and receiving I2C data to the eeprom work as expected. When I send a byte of data to address 0x30 from a master device, it should print the data on the serial terminal from the in message sub.
I hooked up a logic analyser to the bus and see what happens when the scanner sees the eeprom, but no response from the pic. Eeprom shows read from 50 with ACK. Pic shows read from 30 with NAK.
I am not sure if my config has anything to do with it. I am using portC.1 (pin 9) for SDA and port C.0 (pin 10) for SCK. Here is the code I am using...
P.S. I am using the pic16f1825 with a 20mHz crystal. I can run other test programs and the setup is working (hserprint, leds flash etc.). Not sure if clock speed has anything to do with it.
Also, what is the minumum code setup that will work for this, i.e. I just want to send a byte of data from the master once in a while and perform a small task on the pic with that data byte. I don't need any response or error handeling. Can I simply strip those things out of the program or does the library need all these things in the program to work correctly?
I will not give up!
Thanks!
Last edit: viscomjim 2017-01-22
Add #define SSPIF SSP1IF to your main program.
This will is because the code is using SSPIF as the interrupt, and guess what, your chip needs SSP1IF.
Test please.
You should try this as an alternaitve method. This is what I will put in the library to resolve.
Last edit: Anobium 2017-01-22
And, you may have issues with
use this as an alternative, and, use Terminal in HEX mode. Then, you can see lots of data without the serial overheads.
Hi Evan, as I could not get the 16f1825 working, I switched over to the xpress board just to see and it is working great, so at least now I know for sure that I am sending the data correctly. This is working fine using the 4 leds on the xpress board, and I can see it working. I can send the data pretty quickly to the point where you cant see the leds changing, counting from 0 to 15. I have to add a 3ms delay between counts just to see the last led flashing quickly. So, seems to work very well. I will switch back to the 16f1825 and try your code addition by adding...
I have never used scripts before, so where does the script actually go?
As far as the xpress board or other pic, and timing seems to be critical, I wonder how to go about doing something like this...
I want to be able to send a data byte to the pic slave I2C device, so I will just send the address and the data and the data will be HI2CBUFFER(0). I want to use that byte for another operation that the pic will do. I will be sending the i2c data to the pic every 50ms or so. During this 50ms period, I need to perform a simple operation with the HI2CBUFFER(0) byte and send a serial stream that takes no longer than 2 ms to send in completion (just calculating the amount of data being sent at a high baud rate). Unfortunately, i have to use the hserprint command as I am sending ascii text to another unit that requires it.
I noticed if I use just the...
in the "in message" sub as before, on the xpress board setup, it does seem to hose things up timing wise when I try to print the byte to the terminal. Does the hserprint command have a large overhead or what is causing the timing issues? I am assuming that the i2c interrupt only occurs when new data is sent from a master and the library code throws the data (assuming one byte is sent) to the HI2CBUFFER(0). Is there a limit to the amount or speed at which you can receive the byte and do some processing before the next byte is received? Should the extra operations not be done in the "in message" sub? Sorry for all the questions.
Thanks!
Try the attachment. I have updated the library to cater for chips with SSP1IF. You can look at the script in the file to review how this is resolved programmatically.
Timing. It is important to understand the timing. 'A small delay is required to give time for the eeprom to save the data' I just took that from a page off a Maxim I2C EEProm device, and, if you search the same page it states 'It is important that we give the 24C04 enough time to write. This typically takes several milliseconds after the "stop-condition." Consult the data sheet of your IC to make sure that you use the correct timing.'. This is no different for your implementation. The timing of the 'process in message' buffer needs to understood and the master i2c device needed to handle appropiately.
Using HSerPrint and the other output commands all take time and the master is simply sending data onto the i2c bus. You have choices.
- On the master check for a NAK from the slave. A NAK would indicate the master is busy, I think we do this in the EEProm using HI2CAckPollState (a Great Cow BASIC HWI2C method).
- Add an appropiate delay to the master, in a similar manner to the writing to an EEprom.
- We could enhance the library to include clock line stretching, see below.
-
But, using HSer routine are not slow they just take time. Consider using HSerSend and get the master to do the hard work. Sending a raw data stream removes the use of String handling in the slave device. So, rather then sending just the one data byte, send the compete string (as bytes) and then use HserSend to essentially print raw ASCII to the terminal.
Do try clock line stretching. Simply add 'CKP = 0' as the first command within your HI2C_Process_In_Message method. CKP will be handled by the library appropiately after your code has completed. Set CKP and let me know the results.
Re Interrupts. There is only one interrupt defined. The interrupt supports the I2C routines. So what is happening? The master is overrunning the input buffer. There are error routines in the library to show that this has occurred, in your code this is a routine called 'MyHI2CSlave_State_Error ( in HI2CErrorCode ). I am 100% certain you are getting buffer overruns because the master is simply sending data whilst the slave is send Hser data. This is the same problem as any EEProm and this is not specific to these libraries - timing is important with I2C.
Pleasure.
I will give these suggestions a try and consider the amount of time required for the actions. I am still playing with the xpress board currently, but will be switching back to the 16f1825 using the new handler code. It seems the 12f1840 has the same SSP1IF so it may work also as a small I2C "custom" controller. I may break up my operation between two pics, one to read the i2c from the master (i'm only reading the lower 4 bits from the master (0 - F)) and continously outputting them on 4 gpios. Then the second can constantly poll the 4 gpio bits and create the constant serial string needed. This would be totally async. but should work for what I am doing. Will keep trucking along. This is a great addition to the GCB arsenol, and I appreciate your work! This will have lots of uses for sure.