IF2C: Command Set, API, Code Examples

There are two (2) APIs of interest here:

  1. API for the DL-Host that talks to the IF2C, for example the DL0102GXN as a host, as provided in sample firmware. This is covered here.
  2. API for the IF2C accessory itself.

A simple communications example is here.

HOST-IF2C Interface

The HOST talks to the IF2C accessory by SPI with a !SS (slave select) signal. On the current DLITE/(H)DL Family with the GPIO interface for stacking accessories, PORTB4 is a good candidate the for the !SS signal to the IF2C. The IF2C MCU runs at a slower speed (because it can and ought to for this application) so the DL-Host needs to use a slower SPI speed when talking to the IF2C. A different SPI mode is also used. A separate initialization function for the IF2C is probably warranted, as well as a way to track what SPI device the DL-Host is talking to, so that the SPI port is correctly initialized depending on the currently selected slave device.

Command Set for the DL-Host IF2C API

// Add to file(s) (for example):
// dlite_commands.c/h
// This addition is present starting in firmware revision:
// FW E2019.00016

#define DCMD_DO_IF2C 0xDB

extern const char c_do_if2c[DLITE_STD_CMD_LEN] =
{DLITE_CMD_PREFIX, DCMD_DO_EEPROM, 0x00, 0x00, DLITE_CMD_SUFFIX};
extern const char c_tx_do_if2c_ok[17] = "IF2C COMMAND OK\r\0";
extern const char c_tx_do_if2c_err[20] = "IF2C COMMAND ERROR\r\0";

// ARG 1: Command and options
#define DCMD_DO_IF2C_SET_TXCHAN_ON 0x01
#define DCMD_DO_IF2C_SET_TXCHAN_OFF 0x03 // Specific channel off
#define DCMD_DO_IF2C_SET_TXCHAN_ALLOFF 0x05 // All channels off
#define DCMD_DO_IF2C_SET_RXCHAN_ON 0x10 // Only one channel ever on at a time for Rx
#define DCMD_DO_IF2C_SET_RXCHAN_ALLOFF 0x20

// ARG 2: param is channel, if implemented
// 0x00 means all off
// For commands to turn all off, ARG 2 is unimplemented
// 0x01 - 0x08 = Channel number (Base 1))

Example HOST-IF2C Interface Header

This code handles the interface from the HOST to the IF2C to “convert” commands coming in to the (H)DL to the IF2C implementation.

/* 
File: hdl0108if2c-dl0102.h
Interface for the DL0102G(XN), DLITE0100A1, (H)DL Family to the
accessory board: HDL-0108-IF2C
*/
#include <stdbool.h>
#ifndef HDL0108IF2C_DL0102_H
#define HDL0108IF2C_DL0102_H
#ifdef __cplusplus
extern "C" {
#endif

#define INIT_SYSTEM_WITH_IF2C 1

// Functions adapted from MCC Pin Manager auto-generated code
#define IF2C_nSS_TRIS TRISBbits.TRISB4
#define IF2C_nSS_LAT LATBbits.LATB4
#define IF2C_nSS_PORT PORTBbits.RB4
#define IF2C_nSS_WPU WPUBbits.WPUB4
#define IF2C_nSS_OD ODCONBbits.ODCB4
#define IF2C_nSS_ANS ANSELBbits.ANSB4
#define IF2C_nSS_SetHigh() do { LATBbits.LATB4 = 1; } while(0)
#define IF2C_nSS_SetLow() do { LATBbits.LATB4 = 0; } while(0)
#define IF2C_nSS_Toggle() do { LATBbits.LATB4 = ~LATBbits.LATB4; } while(0)
#define IF2C_nSS_GetValue() PORTBbits.RB4
#define IF2C_nSS_SetDigitalInput() do { TRISBbits.TRISB4 = 1; } while(0)
#define IF2C_nSS_SetDigitalOutput() do { TRISBbits.TRISB4 = 0; } while(0)
#define IF2C_nSS_SetPullup() do { WPUBbits.WPUB4 = 1; } while(0)
#define IF2C_nSS_ResetPullup() do { WPUBbits.WPUB4 = 0; } while(0)
#define IF2C_nSS_SetPushPull() do { ODCONBbits.ODCB4 = 0; } while(0)
#define IF2C_nSS_SetOpenDrain() do { ODCONBbits.ODCB4 = 1; } while(0)
#define IF2C_nSS_SetAnalogMode() do { ANSELBbits.ANSB4 = 1; } while(0)
#define IF2C_nSS_SetDigitalMode() do { ANSELBbits.ANSB4 = 0; } while(0)
#define IF2C_SELECT() IF2C_nSS_SetLow()
#define IF2C_DESELECT() IF2C_nSS_SetHigh()

#define POST_SELECT_DELAY_NS 1000000
#define POST_COMM_DELAY_US 100000

#define IF2C_CMD_SETRXCHANON 0b10100000
#define IF2C_CMD_SETRXCHANALLOFF 0b10100000
#define IF2C_CMD_SETTXCHANON 0b11000010
#define IF2C_CMD_SETTXCHANOFF 0b11000011
#define IF2C_CMD_ARGSETALLTXCHANOFF 0x0F

// SPI Setup
// IF2C SPI MODE IS 1 (not 3: (1,1) CPOL=1, CPHA=1)
// However other (H)DL peripherals are different
// Idle should be high (active low) => CPOL = 1
// But data changes on idle=>active (high=>low) which is CKE = 0
// so that it can be sampled in the middle from low=>high (active=>idle) clock
#define IF2C_SCLK_POL 0 // Mode 1: pol:0, pha:1, in theory
#define IF2C_SCLK_PHA 1
#define IF2C_SPI_MODE8 0 // 8-bit, not 16-bit word
// FSCK = FP / (Primary Prescaler x Secondary Prescaler)
// 1 MHz = 70 MHz / (p x s)
// ( p x s ) = 70 / 1 = 70
// Available primary values:
// 11 1:1
// 10 4:1
// 01 16:1
// 00 64:1
//
// Available secondary values:
// 111 1:1
// 110 2:1
// 101 3:1
// 100 4:1
// 011 5:1
// 010 6:1
// 001 7:1
// 000 8:1
//
// 70/80 = 70 / (16 x 5) = 875 kHz
// Secondary: SPRE: 000 = 8:1 while 111=1:1
// Primary: PPRE: 00 = 64:1, 01=16:1, 10=4:1, 11=1:1
// Don't set both to 1:1
#define IF2C_SPI_SPRE_521KHZ 0b000
#define IF2C_SPI_SPRE_1042KHZ 0b100
#define IF2C_SPI_SPRE_695KHZ 0b010
#define IF2C_SPI_PPRE 0b01
#define IF2C_SPI_SPRE 0b000
// Measured values
// PPRE 0b01 and SPRE 0b000 gives measured clock at 521 kHz
// on a typical HOST (H)DL device
// IF2C was previously tested at reliable max of only 50-100kHz
// Using PPRE 0b00 is 4x slower 64:1
// Using SPRE 0b000 is already the slowest divisor
// So this is about 130kHz

// Function prototypes
unsigned char If2cHandler(unsigned char arg1, unsigned char arg2);
void InitIf2c(void);
unsigned int SetIf2cTxChan(unsigned char chan, bool on_not_off, bool full_off);
unsigned int SetIf2cRxChan(unsigned char chan);
void If2cSelect(void);
void If2cDeselect(void);

#ifdef __cplusplus
}
#endif
#endif /* HDL0108IF2C_DL0102_H */

Example HOST-IF2C Interface Source

Formatting adapted and code modified, please verify matching source to header conventions and naming.

#include "hdl0108if2c-dl0102.h"
#include "user.h"
#include "dlite_commands.h"
#include <xc.h>

/*
* TODO
* Probably good to add channel range sanity checking
* (0=off, 1-8=chans allowed)
*/

void InitIf2c() {
IF2C_nSS_SetDigitalMode();
IF2C_nSS_SetDigitalOutput();
IF2C_DESELECT(); // Should pull high
}

unsigned char If2cHandler(unsigned char arg1, unsigned char arg2) {

unsigned char rBuf[2]; unsigned char err = 0;
unsigned char * pBuf = rBuf;
unsigned int len = 2;
unsigned int res = 0x0000;

// Such that we can set the SPI to what is needed here and indicate to the
// main code that SPI configuration is no longer suited to the usual
// accessories for which it might have previously been initialized ClearActiveSpiDevice();
// Initialize SPI for the IF2C
// This is not done in the basic SPI setup because the main code
// probably wants to set up for other accessories - and/or we are
// preserving this behavior for backwards compatibility

SetupSpi(
IF2C_SCLK_POL,
IF2C_SCLK_PHA,
IF2C_SPI_MODE8,
IF2C_SPI_PPRE,
IF2C_SPI_SPRE);

switch (arg1) {
case DCMD_DO_IF2C_SET_TXCHAN_ON:
res = SetIf2cTxChan(arg2, true, false);
break;
case DCMD_DO_IF2C_SET_TXCHAN_OFF:
res = SetIf2cTxChan(arg2, false, false);
break;
case DCMD_DO_IF2C_SET_TXCHAN_OFF_WSHUNT:
res = SetIf2cTxChan(arg2, false, true);
break;
case DCMD_DO_IF2C_SET_TXCHAN_ALLOFF:
res = SetIf2cTxChan(IF2C_CMD_ARGSETALLTXCHANOFF, false, false);
break;
case DCMD_DO_IF2C_SET_TXCHAN_ALLOFF_WSHUNT:
res = SetIf2cTxChan(IF2C_CMD_ARGSETALLTXCHANOFF, false, true);
break;
case DCMD_DO_IF2C_SET_RXCHAN_ON:
case DCMD_DO_IF2C_SET_RXCHAN_ALLOFF:
res = SetIf2cRxChan(arg2);
break;
default:
err = 0x01;
}

rBuf[0] = (res&0xff00)>>8; // first byte is in the MSB place of the int result
rBuf[1] = (res&0xff); // 2nd byte from the transaction is in the LSB

if (!err)
_ProcessTx((char *)pBuf, len); // Raw data bytes
else
ProcessTxString((char *)c_tx_do_if2c_err); return err;
}
void If2cSelect(void) {
IF2C_SELECT();
}
void If2cDeselect(void) {
IF2C_DESELECT();
}

unsigned int SetIf2cTxChan(
unsigned char chan, bool on_not_off, bool full_off) {
unsigned char cmd = 0x00;
unsigned char cmdIfOff = 0x00;
cmdIfOff = full_off ? IF2C_CMD_SETTXCHANOFFWSHUNT : IF2C_CMD_SETTXCHANOFF;
cmd = on_not_off ? IF2C_CMD_SETTXCHANON : cmdIfOff;
return _SPITxRxWord2(
&If2cDeselect, &If2cSelect,
POST_SELECT_DELAY_NS, POST_COMM_DELAY_US,
cmd, chan);
}

unsigned int SetIf2cRxChan(unsigned char chan) {
// chan is arg2 from the dl command packet
unsigned char cmd = 0x00;
switch (chan) {
case 0x00:
cmd = IF2C_CMD_SETRXCHANALLOFF;
break;
default:
cmd = IF2C_CMD_SETRXCHANON|(chan);
}
// The SPI function pushes the bytes into a 16-bit word for Tx
// For our Rx chan control cmd example, 2nd byte is not used
return _SPITxRxWord2(
&If2cDeselect, &If2cSelect,
POST_SELECT_DELAY_NS, POST_COMM_DELAY_US,
cmd, 0xff);
}

Example HOST-IF2C Command Calls to the Interface Code

Something like this could go into the host receive command loop for the switch on the command type:

case DCMD_DO_IF2C:
if (If2cHandler(arg1, arg2) == (uchar)0x00)
ProcessTxString((char *)c_tx_do_if2c_ok);
break;

And don’t forget about initializing the SPI and/or managing the SPI configurations on the host:

#ifdef INIT_SYSTEM_WITH_IF2C
#if INIT_SYSTEM_WITH_IF2C
InitIf2c();
#endif
#endif

And example of the SetupSpi code, with plenty of comments and tested/commented lines for illustration, left over dev notes:

void SetupSpi( unsigned char polarity, unsigned char phase, unsigned char word_not_byte, 
unsigned char primary_prescaler_bits,
unsigned char secondary_prescaler_bits) {

SPI2STAT = 0x0; // Disable SPI2 module first
SPI2CON1 = 0x01e1;
// Double-check - below taken from CE436
// FRAMEN = 0, SPIFSD = 0, DISSDO = 0, MODE16 = 0
// SMP = 0, CKP = 1, CKE = 1, SSEN = 0
// MSTEN = 1, SPRE = 0b000 PPRE = 0b01

SPI2CON1bits.MSTEN = 1; // Master mode
SPI2CON1bits.SMP = 1; // 1 = sample at end of output, set only after master bit set SPI2CON1bits.SSEN = 0; // In master, GPIO is used for !SS
SPI2CON1bits.MODE16 = word_not_byte; // ADIS uses word mode

// Clock rate: SPI1,3,4 max rate master half-duplex 15 MHz, same SPI2
//SPI2CON1bits.SPRE = ADIS_SPI2_SPRE_521KHZ; // Secondary - safest, slowest //SPI2CON1bits.SPRE = ADIS_SPI2_SPRE_1042KHZ; // Easily clearing 1kHz for manual capture on adis, but concern

SPI2CON1bits.SPRE = secondary_prescaler_bits; // Compromise, easily clears 1kHz, room for longer delay if needed
SPI2CON1bits.PPRE = primary_prescaler_bits; // Primary

SPI2CON1bits.CKE = phase; // Clock edge
SPI2CON1bits.CKP = polarity; // Clock polarity

SPI2STATbits.SPIEN = 1; // Enable SPI2
}