Pmod communication through I2C

Hardware: PYNQ-Z1 board, Pmod AD2


I’m trying to communicate with my PmodAD2 through I2C using the jupyter notebooks and MicroBlaze. Here’s what I have so far:

%%microblaze base.PMODA
#include <i2c.h>
#include <pyprintf.h>

// Pmod address (on PMODA port)
#define AD2IICAddr          1073741824

// Configuration
#define CH3                 7
#define CH2                 6
#define CH1                 5
#define CH0                 4
#define REF_SEL             3
#define FLTR                2
#define BitTrialDelay       1
#define SampleDelay         0

#define BitMask             0xFFF

float read_i2c() {

   // Open device (i2c)
   // i2c i2c_open_device(unsigned int device)
   i2c pmod_ad2 = i2c.open_device(AD2IICAddr);

   unsigned char WriteBuffer[1];    
   unsigned char cfgValue = (1 << CH3)   |
              (1 << CH2)                 |
              (1 << CH1)                 |
              (1 << CH0)                 |
              (0 << REF_SEL)             |
              (0 << FLTR)                |
              (0 << BitTrialDelay)       |
              (0 << SampleDelay);
   WriteBuffer[0]=cfgValue;

   // Write config to pmod
   // void i2c_write(i2c dev_id, unsigned int slave_address, unsigned char* buffer, unsigned int length)
   i2c_write(pmod_ad2, AD2IICAddr, WriteBuffer, 1)
}

I got the physical pmod address from overlay.ip_dict. When I call the function, it buffers endlessly on the last line without throwing an error. I am new to using communication protocols and not entirely sure why this may be happening.

I am assuming the slave address in the i2c_write() function is the physical address to the pmod, but not totally sure. Also I wasn’t sure what to put down for length so I went with 1. Am I supposed to be using the bitmask?

Thank you.

1 Like

Hi,
You need i2c_open_device(0), and not the physical address of the controller.

For the SLAVE address in i2c_write(), you need to use the I2C address. You can usually find this in the datasheet/userguide.

Cathal

Thank you for your answer even though it may be a beginners topic. Ok so I found the address to be 010 1000 (or 0x28) in the reference manual. Unfortunately, the code still buffers indefinitely on the writing step. I also tried going a bit higher level since I don’t care about efficiency at the moment:

from pynq.overlays.base import BaseOverlay
from pynq.lib import MicroblazeLibrary

base = BaseOverlay('base.bit')
mb = MicrolazeLibrary(base.PMODA, ['i2c'])

device = mb.i2c_open_device(0)

# Writing through I2C
write_buf = bytearray(1)
write_buf[0] = 0xF0                # Because config was 11110000 for PMOD AD2
device.write(0x28, write_buf, 1)          

But the outcome was the same. Could it be a problem with my PYNQ’s image? I did tinker with some .c files (mainly pmod_adc.c) when trying to do this a different way, but I don’t think that file is related to the usage here.

Apologies, I missed that you are using the PMOD.
Instead of i2c_open(), you need i2c_open_device(). i2c_open() is for a MicroBlaze where there is no IO switch. The I2C controller SCL SDA are connected directly to IO pins. (E.g. Arduino IOP).
With the PMOD, there is an IO switch. i.e. you have some flexibility which pins you connect the I2C controller to, so you need to specify them.

Check this example, which should also be on your board.
https://github.com/Xilinx/PYNQ/blob/master/boards/Pynq-Z1/base/notebooks/microblaze/microblaze_python_libraries.ipynb

Cathal

2 Likes

Hey GBellPort, here’s a Pmod I2C example snippet I wrote for some of my students using the PYNQ-Z1.
Make sure to specify which of the Pmod pins you’re using for SDA & SCL when opening the device.

Hope it may help:

import time
from pynq import Overlay
from pynq.lib import MicroblazeLibrary

## Pmod I2C Pin Definitions
PMOD_SDA_PIN = 6    ## or 2
PMOD_SCL_PIN = 7    ## or 3

EXAMPLE_I2C_ADDRESS = 0x77         ## I2C address for the slave device
EXAMPLE_REGISTER_ADDRESS = 0x14    ## RAM (offset) address for a data register on the slave device

## Obtain a reference to the Base Overlay (bitstream) loaded into the PL
overlay = Overlay('base.bit')

## Using the PmodA IO Processor for this example
iop = overlay.iop_pmoda

## Instantiate the Python object that wraps the library functions defined in 'i2c.h' for the IOP
lib = MicroblazeLibrary(iop, ['i2c'])

## Open an I2C device object on the specified SDA and SCL pins
i2c_device = lib.i2c_open(PMOD_SDA_PIN, PMOD_SCL_PIN)

## Example for writing a 16-bit value to a register
##   NOTE: The exact I2C comms procedure / packet structure will likely be unique for each 
##         I2C sensor; these specifications should be covered in datasheets for the device
write_value = 0xABCD
buffer = bytearray(3)
buffer[0] = EXAMPLE_REGISTER_ADDRESS  ## Typically the MSB of the buffer will be a register offset for writes
buffer[1] = (write_value >> 8) & 0xFF
buffer[2] = write_value & 0xFF
i2c_device.write(EXAMPLE_I2C_ADDRESS, buffer, len(buffer))

time.sleep(10e-3)    ## A ~10 ms delay between read / write operations may be necessary

## Example for reading a 16-bit register value back from the device
num_bytes_to_read = 2
i2c_device.read(EXAMPLE_I2C_ADDRESS, buffer, num_bytes_to_read)
read_value = (buffer[0] << 8) | buffer[1]
print(read_value)

## Close the I2C device handle once finished
i2c_device.close()
1 Like

Thank you both @cathalmccabe and @VicerExciser. I was finally able to get it working and learn a whole lot about I2C comms in the process. I’ll leave the solution I was able to piece together for reference:

from pynq.lib import MicroblazeLibrary, Pmod_DAC
from pynq.overlays.base import BaseOverlay
lib = MicroblazeLibrary(ol.iop_pmoda, ['i2c'])

# Parameters 
SDA_PIN = 6
SCL_PIN = 7
# See PMOD AD2 reference manual for the following values
AD2_ADDRESS = 0b0101000
config = 0b11110000           

device = lib.i2c_open(SDA_PIN, SCL_PIN)

# Writing
buf= bytearray(1)
buf[0] = config
device.write(AD2_ADDRESS, buf, len(buf))

time.sleep(10e-3)

# Reading
readings = []
buf = bytearray(2)
print('Reading...')
for _ in range(4):    # Since I want to read from the 4 channels
    device.read(AD2_ADDRESS, buf, len(buf))
    reading = ((buf[0] & 0xFF) << 8) | buf[1]
    readings.append(reading)
    time.sleep(10e-3)

device.close()

It is also worth mentioning that my Pmod AD2 had the SCL and SDA pins mislabeled. It shows SCL is connected to Pmod pins 2/6 and SDA on 7/3. I kept getting no response before I tried changing the pin orders like in the example above.