Anobium - 2017-12-31

In December 2017 I have developed an operational solution for an I2C Hardware Slave for Microchip 18FK42 range of microcontrollers. The 18FK42 range of microcontrollers have I2C implemented using a different I2C hardware module to the ‘Legacy’ I2C.

See here for operational solution for an I2C Hardware Slave for Microchip Legacy microcontrollers.

This is an extremely simple solution and makes the implementation of a slave very easy.

Interfacing to Slave attached devices is relatively simple. This is completed in the user code - I have provided demonstration code here

How does this work?

You have a Master I2C device. It writes and reads using the I2C protocol. Your master could be any microcontroller that supports hardware or software i2C.

The Slave responds to the writes and read. The Slave code exposes the I2C message sent by the Master 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 very loosy based on Microchip Application Notes AN734 and TB3159. According to AN734, and, Sebastien Lelong and Joep Suijs who created the JAL implementation, there are 5+1 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 the write request is completed. The user call back is called at this point as we have received the complete I2C packet.
  • HI2CSlave_State_4 called when master wants to read a byte from slave.
  • HI2CSlave_State_5 called when master still wants to more bytes from slave. That is, master required to read and now still want to read a byte slave should send a byte(s) until the Master sends a NACK.
  • HI2CSlave_State_6 called when master does not want to talk to slave anymore usually a good place to reset data or slave's logic

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 0x70 and the Slave responds with two data bytes – from a pot.

Callbacks:

  • HI2C_Process_In_Message ( in HI2CMESSAGESIZE as byte ) called when slave has a received a complete I2C packet.
  • HI2C_Process_Out_Message called when slave is requested to respond to a Master request.

Demonstration and Library Code

To showcase the new GCB capabilities are:

  • Demonstrate how to configure the I2C Slave address and to configure the I2C queue
  • Respond to I2C discovery with the response from the Write and Read addresses
  • Demonstration code on using how to use the I2C Slave using GCB Message Queue
  • Receive I2C data from an I2C Master to this I2C Slave – with the I2C Slave handling the incoming via a Message Queue, and,
  • 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.98.+
  2. HW I2C ISR KMode Library. This file is not intended to be edited by a user.
  3. HW I2C Message Queue Kmode Library. This file is not intended to be edited by a user.
  4. Example 16F demonstration code.

Installation of new capabilities

Install GREAT COW BASIC, copy the libraries into INCLUDE folder, if not part of your installation.

Using GREAT COW BASIC 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 GREAT COW BASIC 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 GREAT COW BASIC logic users can respond to the specifics of the I2C message.

HI2CSend( Target I2CAddress )   ;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( Target I2CAddress)   ;WriteOp                  
HI2CSend(outvar)       ;first  value  = 1
HI2CReStart   ;generate a restart signal
HI2CSend(Target I2CAddress XOR 1) ;inidicate a read
HI2CReceive(Val2, NACK)  ;read one byte and conclude
HI2CStop

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 GREAT COW BASIC code related to this is as follows:

  do
    HI2CStart                              ;generate a start signal
    HI2CSend(TargetGREAT COW BASICI2CAddress)                       ;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(TargetGREAT COW BASICI2CAddress)                       ;indicate a write
  loop While HI2CAckPollState
  HI2CSend(0x80)  
  HI2CReStart
  HI2CSend(TargetGREAT COW BASICI2CAddress 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(TargetGREAT COW BASICI2CAddress)                       ;indicate a write
  loop While HI2CAckPollState
  HI2CSend(0x81)  ;number of bytes to request or low address
  HI2CReStart
  HI2CSend(TargetGREAT COW BASICI2CAddress 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(TargetGREAT COW BASICI2CAddress)                       ;indicate a write
  loop While HI2CAckPollState
  HI2CSend(0x82)  
  HI2CReStart
  HI2CSend(TargetGREAT COW BASICI2CAddress 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(TargetGREAT COW BASICI2CAddress)                       ;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++

Enjoy

 

Last edit: Anobium 2017-12-31