Before start, please review this tutorial.
In the DUNE experiment, the DAPHNE photon detection system has undergone an evolution, leading to the utilization of SOM (System on Modules). This shift aims to achieve greater compactness in hardware and software task implementation, enhanced system control, and operational security. Considering the context of the DUNE experiment, where this system will be positioned 1 km underground in Fermilab's caverns and accessibility for subsequent system servicing will be challenging, stringent security and robustness in the system implementation are imperative.
Taking center stage in this scenario is the KRIA KR260 as the chosen SOM to manage the photon detection system's operation.
In this tutorial, I will demonstrate the necessary steps to include the AXI IIC peripheral in the PL to communicate with various I2C interface chips. This peripheral connects to the PS through the AXI port and will be assigned an address in the Petalinux memory map. Simultaneously, a driver managed by the Device Tree will be mounted, allowing this hardware to be visible in the device list at /dev.
Once the project is configured in Vivado 2022.2, synthesis and implementation are completed, the bitstream is created, and the platform is exported, the Device Tree Overlay will be sent to the KRIA KR260. The embedded hardware will be implemented, and control will be given to the peripherals through the AXI port from Petalinux 2022.2.
Also, please visit the following link containing important information:- Configuration of I2C in Linux.IIC Peripheral implementation
To integrate the I2C as an IP Core in the PL side and utilize it in the KRIA KR260 using Petalinux, we'll need to add the AXI IIC IP. We can locate this IP in the IP Catalog and incorporate it into the block design.
Upon adding the IP, configuration can be achieved by double-clicking on the block. In this particular instance, we'll modify the SCL Clock to 400 kHz
and set the Address mode to 7 bits
. Feel free to make any additional changes deemed necessary for your specific project.
After completing these steps, proceed with the IP connections. You can utilize the Automatic connection tool available in the upper part of the block design window to establish the AXI Interface connection. Additionally, ensure that you connect the Interrupt to the PS side in the ZYNQ UltraScale+.
In this scenario, the approach to adding more interrupts to the PS involves adding more inputs to the Concat IP block. Subsequently, a direct connection can be established between the Concat IP block and the AXI Interrupt Controller IP to facilitate the incorporation of additional interrupts.
Constrains definitionIn this example, we will use the PMOD1 connector to connect the I2C
peripheral to the exterior of the KRIA.
Create the constrains file comms.xdc
using the following pinout
We also have the definitions of UART because this project is a continuation of this tutorial.
##################### PMOD 1 Upper ################################
set_property PACKAGE_PIN H12 [get_ports {kria_uart_rxd}]
set_property IOSTANDARD LVCMOS33 [get_ports {kria_uart_rxd}]
set_property PACKAGE_PIN E10 [get_ports {kria_i2c_sda_io}]
set_property IOSTANDARD LVCMOS33 [get_ports {kria_i2c_sda_io}]
set_property PULLUP TRUE [get_ports {kria_i2c_sda_io}]
#set_property PACKAGE_PIN D10 [get_ports {pmod1_io_tri_io[2]}]
#set_property IOSTANDARD LVCMOS33 [get_ports {pmod1_io_tri_io[2]}]
#set_property PACKAGE_PIN C11 [get_ports {pmod1_io_tri_io[3]}]
#set_property IOSTANDARD LVCMOS33 [get_ports {pmod1_io_tri_io[3]}]
##################### PMOD 1 Lower ################################
set_property PACKAGE_PIN B10 [get_ports {kria_uart_txd}]
set_property IOSTANDARD LVCMOS33 [get_ports {kria_uart_txd}]
set_property PACKAGE_PIN E12 [get_ports {kria_i2c_scl_io}]
set_property IOSTANDARD LVCMOS33 [get_ports {kria_i2c_scl_io}]
set_property PULLUP TRUE [get_ports {kria_i2c_scl_io}]
#set_property PACKAGE_PIN D11 [get_ports {pmod1_io_tri_io[6]}]
#set_property IOSTANDARD LVCMOS33 [get_ports {pmod1_io_tri_io[6]}]
#set_property PACKAGE_PIN B11 [get_ports {pmod1_io_tri_io[7]}]
#set_property IOSTANDARD LVCMOS33 [get_ports {pmod1_io_tri_io[7]}]
Once you reach this stage, you can proceed with the synthesis, implementation, and bitstream generation processes, which will then be loaded onto the PL side. This procedure generates an address map for the AXI interface. Subsequently, this address interface can be accessed from the PS side using the devmem
command in Linux.
Export the platform and name it as kria_i2c_base.xsa
.
In the project's root folder, we'll add the following files. Please note that the generated platform name for this project is kria_i2c_base.xsa
:
xsct_config.tcl
hsi::open_hw_design kria_i2c_base.xsa
createdts -hw kria_i2c_base.xsa -zocl -platform-name kr260_i2c -git-branch xlnx_rel_v2022.2 -overlay -compile -out ./dtg_kr260_v0
exit
This file contains the commands that will be executed by the Xilinx Source Commands Tool for the creation of the Device Tree.
Remember to create the shell.json
file:
nano shell.json
Then, copy and paste the following content into shell.json
:
{
"shell_type": "XRT_FLAT",
"num_slots": "1"
}
The following file will execute the necessary terminal commands to automatically generate the Device Tree and send it to the KRIA board. Please review the script and modify the necessary parts to fit your project, such as the KRIA board's IP address.
DT_generation.sh
echo ""
echo ""
echo "#############################################################################"
echo "Creacion del device tree"
echo "#############################################################################"
echo ""
echo ""
source /tools/Xilinx/Vitis/2022.2/settings64.sh
cd ./Kria_I2C/
xsct xsct_config.tcl
echo ""
echo ""
echo "#############################################################################"
echo "Compilacion del device tree"
echo "#############################################################################"
echo ""
echo ""
dtc -@ -O dtb -o ./dtg_kr260_v0/dtg_kr260_v0/kr260_i2c/psu_cortexa53_0/device_tree_domain/bsp/pl.dtbo ./dtg_kr260_v0/dtg_kr260_v0/kr260_i2c/psu_cortexa53_0/device_tree_domain/bsp/pl.dtsi
cd ./i2c_file_transfer/
cp ../dtg_kr260_v0/dtg_kr260_v0/kr260_i2c/psu_cortexa53_0/device_tree_domain/bsp/pl.dtbo ./ & cp ../Kria_I2C.runs/impl_1/kria_bd_wrapper.bin ./
mv kria_bd_wrapper.bin kr260_i2c.bit.bin
mv pl.dtbo kr260_i2c.dtbo
echo ""
echo ""
echo "#############################################################################"
echo "Envio de archivos a la Kria"
echo "#############################################################################"
echo ""
echo ""
echo petalinux | scp kr260_i2c.bit.bin kr260_i2c.dtbo shell.json petalinux@192.168.1.12:/home/petalinux
This script enables the loading of the Device Tree onto the KRIA board. Please review the script and modify the parts necessary to fit your project requirements before executing it.
Run PL Design on KR260This part of the tutorial is execute in the KRIA KR260, you need to connect to it through SSH using the follow command and your access password:
ssh petalinux@xilinx-kr260-starterkit-20222
Once the files have been successfully transferred to the KR260, create a directory in the /lib/firmware/xilinx
directory with the same name as the device tree blob and .bin
file, and copy them into it:
sudo mkdir/lib/firmware/xilinx/kr260_i2c
sudo mv kr260_i2c.dtbo kr260_i2c.bit.bin shell.json /lib/firmware/xilinx/kr260_i2c
At this point, the PL design will show up similar to an accelerated application using the xmutil
commands:
sudo xmutil listapps
Unload the default application and then load the PL design, which flashes the PL design’s bitstream into the PL and loads its device tree overlay:
sudo xmutil unloadapp
sudo xmutil loadapp kr260_i2c
Once done, you can verify the IIC device added on the PS side using the following command:
i2cdetect -l
You should receive the following output in the console:
i2c-1 unknown Cadence I2C at ff030000 N/A
i2c-2 unknown ZynqMP DP AUX N/A
i2c-3 unknown i2c-1-mux (chan_id 0) N/A
i2c-4 unknown i2c-1-mux (chan_id 1) N/A
i2c-5 unknown i2c-1-mux (chan_id 2) N/A
i2c-6 unknown i2c-1-mux (chan_id 3) N/A
i2c-7 i2c xiic-i2c 80070000.i2c I2C adapter
Where i2c-7
corresponds to the peripheral with 0x80070000
address as indicated in the AXI address map, representing the AXI IIC IP.
Additionally, grant the necessary permissions to execute and control the peripheral:
sudo chmod 777 /dev/i2c-7
To check the connected devices, utilize the following command:
i2cdetect -y -r 7
You'll receive a response similar to the following, in this case, there is a device connected at address 23
:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
At the same time, we can observe the signals with the help of a Logic Analyzer.
To conduct this test, we will set up the following connection using a BH1750
light intensity measurement module for the experiments.
We will install the SMBus
package for python using the following command:
python -m pip install smbus
Python CodeCreate the file bh1750_iic.py
with the following script:
#!/usr/bin/env python2
# vim: expandtab ts=4 sw=4
# Inspired by http://www.raspberrypi-spy.co.uk/2015/03/bh1750fvi-i2c-digital-light-intensity-sensor/
import smbus
import time
class BH1750():
""" Implement BH1750 communication. """
# Define some constants from the datasheet
POWER_DOWN = 0x00 # No active state
POWER_ON = 0x01 # Power on
RESET = 0x07 # Reset data register value
# Start measurement at 4lx resolution. Time typically 16ms.
CONTINUOUS_LOW_RES_MODE = 0x13
# Start measurement at 1lx resolution. Time typically 120ms
CONTINUOUS_HIGH_RES_MODE_1 = 0x10
# Start measurement at 0.5lx resolution. Time typically 120ms
CONTINUOUS_HIGH_RES_MODE_2 = 0x11
# Start measurement at 1lx resolution. Time typically 120ms
# Device is automatically set to Power Down after measurement.
ONE_TIME_HIGH_RES_MODE_1 = 0x20
# Start measurement at 0.5lx resolution. Time typically 120ms
# Device is automatically set to Power Down after measurement.
ONE_TIME_HIGH_RES_MODE_2 = 0x21
# Start measurement at 1lx resolution. Time typically 120ms
# Device is automatically set to Power Down after measurement.
ONE_TIME_LOW_RES_MODE = 0x23
def __init__(self, bus, addr=0x23):
self.bus = bus
self.addr = addr
self.power_down()
self.set_sensitivity()
def _set_mode(self, mode):
self.mode = mode
self.bus.write_byte(self.addr, self.mode)
def power_down(self):
self._set_mode(self.POWER_DOWN)
def power_on(self):
self._set_mode(self.POWER_ON)
def reset(self):
self.power_on() #It has to be powered on before resetting
self._set_mode(self.RESET)
def cont_low_res(self):
self._set_mode(self.CONTINUOUS_LOW_RES_MODE)
def cont_high_res(self):
self._set_mode(self.CONTINUOUS_HIGH_RES_MODE_1)
def cont_high_res2(self):
self._set_mode(self.CONTINUOUS_HIGH_RES_MODE_2)
def oneshot_low_res(self):
self._set_mode(self.ONE_TIME_LOW_RES_MODE)
def oneshot_high_res(self):
self._set_mode(self.ONE_TIME_HIGH_RES_MODE_1)
def oneshot_high_res2(self):
self._set_mode(self.ONE_TIME_HIGH_RES_MODE_2)
def set_sensitivity(self, sensitivity=69):
""" Set the sensor sensitivity.
Valid values are 31 (lowest) to 254 (highest), default is 69.
"""
if sensitivity < 31:
self.mtreg = 31
elif sensitivity > 254:
self.mtreg = 254
else:
self.mtreg = sensitivity
self.power_on()
self._set_mode(0x40 | (self.mtreg >> 5))
self._set_mode(0x60 | (self.mtreg & 0x1f))
self.power_down()
def get_result(self):
""" Return current measurement result in lx. """
data = self.bus.read_word_data(self.addr, self.mode)
count = data >> 8 | (data&0xff)<<8
mode2coeff = 2 if (self.mode & 0x03) == 0x01 else 1
ratio = 1/(1.2 * (self.mtreg/69.0) * mode2coeff)
return ratio*count
def wait_for_result(self, additional=0):
basetime = 0.018 if (self.mode & 0x03) == 0x03 else 0.128
time.sleep(basetime * (self.mtreg/69.0) + additional)
def do_measurement(self, mode, additional_delay=0):
"""
Perform complete measurement using command
specified by parameter mode with additional
delay specified in parameter additional_delay.
Return output value in Lx.
"""
self.reset()
self._set_mode(mode)
self.wait_for_result(additional=additional_delay)
return self.get_result()
def measure_low_res(self, additional_delay=0):
return self.do_measurement(self.ONE_TIME_LOW_RES_MODE, additional_delay)
def measure_high_res(self, additional_delay=0):
return self.do_measurement(self.ONE_TIME_HIGH_RES_MODE_1, additional_delay)
def measure_high_res2(self, additional_delay=0):
return self.do_measurement(self.ONE_TIME_HIGH_RES_MODE_2, additional_delay)
def main():
#bus = smbus.SMBus(0) # Rev 1 Pi uses 0
bus = smbus.SMBus(7) # Rev 2 Pi uses 1
sensor = BH1750(bus)
print("Sensitivity: {:d}".format(sensor.mtreg))
for measurefunc, name in [(sensor.measure_low_res, "Low Res "),
(sensor.measure_high_res, "HighRes "),
(sensor.measure_high_res2, "HighRes2")]:
print("{} Light Level : {:3.2f} lx".format(name, measurefunc()))
print("--------")
sensor.set_sensitivity((sensor.mtreg + 10) % 255)
time.sleep(1)
if __name__=="__main__":
main()
You must to see the following answer in the console:
This is the second in a series of three tutorials where we will demonstrate how to configure the UART
, I2C
, and SPI
interfaces from the PL and utilize them with the PS.
Comments