I2C on the Papilio One Zpuino Core

For the last couple of months I’ve been working on a variety of projects that require the use of I2C in one way or another. I’ve been wanting to implement them using the Zpuino but was limited by it’s lack of an I2C core.

This isn’t an insurmountable problem. There is a very popular Wishbone compatible core on the OpenCores site that’s been used in a variety of projects and I was able to implement it using the instructions on the Zpuino web site and the documentation from the OpenCores web site. However, there were a couple of tricks I had to pull on the hardware side to get it to work so I thought I’d document them here in case they would be of use for someone else.

High Impedance Buffer Versus Separate Data/Clock Lines

An I2C SDA and SCL (data/clock) line is normally implemented using a high-impedance buffer as shown in this figure taken from the OpenCores I2C documentation.

Image

The buffer input is pulled to ground and the output tied to V+ through a pullup resistor. To transmit a 1 the buffer is disabled, causing the line to be pulled high by the pullup resistor. To transmit a 0 the buffer is enabled and the line is pulled low. This is implemented in VHDL as:

SCL <= '0' when (SCLo = '0') else 'Z';
SDA <= '0' when (SDAo = '0') else 'Z';
SCLi <= SCL;
SDAi <= SDA;

Implementing it in this manner in the Zpuino would require assigning SCL and SDA to a fixed set of pins but I wanted the pins to be assignable using the Zpuino’s Peripheral Pin Select (PPS) function. I found the answer to this dilemma in a TI application report describing how to interface the TMS320C54x DSP to an I2C bus. It involves using 4 pins (2 each for the SCL and SDA lines) and a few resistors to simulate the I2C high-impedance buffer. The circuit is shown below. For a complete description on how it works see the application report.

Image

Hooking It Up To PPS

I described how to hook a core up to PPS in a previous post but this case presents an interesting problem because the data and clock signal levels are not carried by the lines themselves but by the buffer enable signals. Therefore, it’s the buffer enable signal that should be connected to the gpio line in the PPS process, not the data or clock output signal. The PPS process for my latest Zpuino implementation on the Papilio One board is shown below.

process(
   gpio_spp_read,
   sigmadelta_spp_data,
   timers_pwm,
   spi2_mosi,
   spi2_sck,
   uart2_tx,
   sync_tx_o,
   adc_cs_o,
   adc_clk_o,
   i2c_scl_padoen_o,
   i2c_sda_padoen_o)
begin
   gpio_spp_data <= (others => DontCareValue);

   -- GPIO output pins.
   -- Note: The i2c output pins are set to the pad enable pins in order to allow
   -- pps to work. The pad enable pins are the same level as the output and can
   -- be used to feed an open collector buffer if desired.
   gpio_spp_data(0) <= sigmadelta_spp_data(0); -- PPS0 : SIGMADELTA DATA
   gpio_spp_data(1) <= timers_pwm(0); -- PPS1 : TIMER0
   gpio_spp_data(2) <= timers_pwm(1); -- PPS2 : TIMER1
   gpio_spp_data(3) <= spi2_mosi; -- PPS3 : USPI MOSI
   gpio_spp_data(4) <= spi2_sck; -- PPS4 : USPI SCK
   gpio_spp_data(5) <= sigmadelta_spp_data(1); -- PPS5 : SIGMADELTA1 DATA
   gpio_spp_data(6) <= uart2_tx; -- PPS6 : UART2 DATA
   gpio_spp_data(7) <= sync_tx_o; -- PPS7 : Synchronous transmit data - JAC
   gpio_spp_data(8) <= adc_cs_o; -- PPS8 : ADC chip select - JAC
   gpio_spp_data(9) <= adc_clk_o; -- PPS9 : ADC clock - JAC
   gpio_spp_data(10)<= i2c_scl_padoen_o; -- PPS10: I2C clock - JAC
   gpio_spp_data(11)<= i2c_sda_padoen_o; -- PPS11: I2C data - JAC

   -- GPIO input pins.
   spi2_miso <= gpio_spp_read(0); -- PPS0 : USPI MISO
   uart2_rx <= gpio_spp_read(1); -- PPS1 : UART2 receive
   sync_tx_clk_i <= gpio_spp_read(2); -- PPS2 : Synchronous transmit clock - JAC
   sync_rc_i <= gpio_spp_read(3); -- PPS3 : Synchronous receive data - JAC
   sync_rc_clk_i <= gpio_spp_read(4); -- PPS4 : Synchronous receive clock - JAC
   adc_data0_i <= gpio_spp_read(5); -- PPS5 : ADC data stream 0 - JAC
   adc_data1_i <= gpio_spp_read(6); -- PPS6 : ADC data stream 1 - JAC
   i2c_scl_pad_i <= gpio_spp_read(10); -- PPS10: I2C clock - JAC
   i2c_sda_pad_i <= gpio_spp_read(11); -- PPS11: I2C data - JAC
end process;

Testing It All

To test the final product I put together a simple sketch (shown below) to write a value to an I2C device register and immediately read it back out. The circuit worked just as expected so now it’s on to completing the projects that instigated this in the first place.

/* Define our counter base IO address to slot 9
*/
#define I2C_SLOT 12

/* Define I2C registers.
*/
#define I2C_BASE IO_SLOT(I2C_SLOT)
#define I2C_PRERlo REGISTER(I2C_BASE, 0)
#define I2C_PRERhi REGISTER(I2C_BASE, 1)
#define I2C_CTR REGISTER(I2C_BASE, 2)
#define I2C_TXR REGISTER(I2C_BASE, 3)
#define I2C_RXR REGISTER(I2C_BASE, 3)
#define I2C_CR REGISTER(I2C_BASE, 4)
#define I2C_SR REGISTER(I2C_BASE, 4)

/* Define PPS signal lines.
*/
#define I2C_SCL 10
#define I2C_SDA 11

/* Define FPGA pins
*/
#define I2C_SCL_O WING_C_7
#define I2C_SCL_I WING_C_6
#define I2C_SDA_O WING_C_5
#define I2C_SDA_I WING_C_4

/* I2C core control register bit definitions.
*/
#define CTR_EN 0x80 // i2c core enable bit.
// When set to ‘1’, the core is enabled.
// When set to ‘0’, the core is disabled.

#define CTR_IEN 0x40 // i2c core interrupt enable bit.
// When set to ‘1’, interrupt is enabled.
// When set to ‘0’, interrupt is disabled.

/* I2C core command register bit definitions.
*/
#define CR_STA 0x80 // generate (repeated) start condition
#define CR_STO 0x40 // generate stop condition
#define CR_RD 0x20 // read from slave
#define CR_WR 0x10 // write to slave
#define CR_ACK 0x08 // when a receiver, send ACK (ACK = ‘0’) or NACK (ACK = ‘1’)
#define CR_IACK 0x01 // interrupt acknowledge. When set, clears a pending interrupt.

/* I2C status register bit definitions.
*/
#define SR_RxACK 0x80 // received acknowledge from slave.
// This flag represents acknowledge from the addressed slave.
// ‘1’ = No acknowledge received
// ‘0’ = Acknowledge received

#define SR_BUSY 0x40 // i2c bus busy
// ‘1’ after START signal detected
// ‘0’ after STOP signal detected

#define SR_AL 0x20 // arbitration lost
// This bit is set when the core lost arbitration.
// Arbitration is lost when:
// a STOP signal is detected, but non requested
// The master drives SDA high, but SDA is low.

#define SR_TIP 0x02 // transfer in progress.
// ‘1’ when transferring data
// ‘0’ when transfer complete

#define SR_IF 0x01 // interrupt Flag. This bit is set when an
// interrupt is pending, which will cause a
// processor interrupt request if the IEN bit is set.
// The Interrupt Flag is set when:
// one byte transfer has been completed
// arbitration is lost

/* I2C core register definitions.
*/
#define REG_CLKOE 0x09
#define REG_DIV1 0x0C
#define REG_XDRV 0x12
#define REG_CAPLOAD 0x13
#define REG_PBHI 0x40
#define REG_PBLO 0x41
#define REG_QCNT 0x42
#define REG_MATRIX1 0x44
#define REG_MATRIX2 0x45
#define REG_MATRIX3 0x46
#define REG_DIV2 0x47

/* I2C core read/write flags.
*/
#define I2C_M_WR 0x00
#define I2C_M_RD 0x01

/* Define I2C devivce address.
*/
#define I2C_DEVICE_ADDRESS 0x69
void setup()
{
   pinMode(I2C_SCL_O, OUTPUT);
   pinModePPS(I2C_SCL_O, HIGH);
   outputPinForFunction(I2C_SCL_O, I2C_SCL);

   pinMode(I2C_SCL_I, INPUT );
   inputPinForFunction (I2C_SCL_I, I2C_SCL);

   pinMode(I2C_SDA_O, OUTPUT);
   pinModePPS(I2C_SDA_O, HIGH);
   outputPinForFunction(I2C_SDA_O, I2C_SDA);

   pinMode(I2C_SDA_I, INPUT );
   inputPinForFunction (I2C_SDA_I, I2C_SDA);

   Serial.begin(9600);
}

int count = 0;
void loop()
{
   Serial.print("Calculating tuned frequency...\r\n");
   while (!Serial.available());
   while (Serial.available())
      Serial.read();

   // Write the value to the device register.
   int lengthTransferred = 0;

   // Write the device address.
   I2C_TXR = 0x69 << 1;
   I2C_CR = CR_WR | CR_STA;
   while ((I2C_SR & SR_TIP) != 0);

   // Get the acknowledgement.
   lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 1 : 0;
   if (lengthTransferred == 1)
   {
      // Write the register.
      I2C_TXR = REG_DIV1;
      I2C_CR = CR_WR;
      while ((I2C_SR & SR_TIP) != 0);

      // Get the acknowledgement.
      lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 2 : 0;
   }

   if (lengthTransferred == 2)
   {
      // Write the value.
      I2C_TXR = count;
      I2C_CR = CR_WR | CR_STO;
      while ((I2C_SR & SR_TIP) != 0);

      // Get the acknowledgement.
      lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 3 : 0;
   }

   Serial.print("Length transferred during write = ");
   Serial.print(lengthTransferred);
   Serial.print("\r\n");

   // Read the value back from the device.
   lengthTransferred = 0;

   // Write the device address.
   I2C_TXR = (0x69 << 1);
   I2C_CR = CR_WR | CR_STA;
   while ((I2C_SR & SR_TIP) != 0);

   // Get the acknowledgement.
   lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 1 : 0;

   // Write the register.
   if (lengthTransferred == 1)
   {
      // Write the register.
      I2C_TXR = REG_DIV1;
      I2C_CR = CR_WR;
      while ((I2C_SR & SR_TIP) != 0);

      // Get the acknowledgement.
      lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 2 : 0;
   }

   // Write the read address.
   if (lengthTransferred == 2)
   {
      // Write the read address...
      I2C_TXR = (0x69 << 1) | 0x01;
      I2C_CR = CR_WR | CR_STA;
      while ((I2C_SR & SR_TIP) != 0);

      // Get the acknowledgement.
      lengthTransferred = ((I2C_SR & SR_RxACK) == 0) ? 3 : 0;
   }

   // Read the value.
   int value = 0;
   if (lengthTransferred = 3)
   {
      // Read the value.
      I2C_CR = CR_RD | CR_STO | CR_ACK;
      while ((I2C_SR & SR_TIP) != 0);
      value = I2C_RXR;
      lengthTransferred = 4;
   }
   else
   {
      I2C_CR = CR_STO;
      while ((I2C_SR & SR_TIP) != 0);
   }

   Serial.print("Length transferred during read = ");
   Serial.print(lengthTransferred);
   Serial.print("\r\n");
   Serial.print("Value read = ");
   Serial.print(value);
   Serial.print("\r\n");
   count++;
}
Advertisements

Comments are closed.

Tabula Candida

Doodles of a distracted historian

rtl-sdr.com

A blog about RTL-SDR (RTL2832) and cheap software defined radio

DuWayne's Place

Computers, Electronics, and Amateur Radio from KC3XM

QRP HomeBuilder - QRPHB -

Computers, Electronics, and Amateur Radio from KC3XM

Open Emitter

Computers, Electronics, and Amateur Radio from KC3XM

Ripples in the Ether

Emanations from Amateur Radio Station NT7S

m0xpd's 'Shack Nasties'

Computers, Electronics, and Amateur Radio from KC3XM

%d bloggers like this: