This project is also going to create a Vivado and PetaLinux design which works with SPI and I2C sensors mounted on the ZUBoard along with supporting I2C Click modules.
We will then use the I2C Click interface to work with a Micro Click Brushless 3 board to control a simple BLDC motor.
Vivado BuildTo getting started is very simple in Vivado we need to create a project targeting the ZUBoard
Select RTL Project, but do not specify the sources
Select the ZUBoard, if it is not already installed click on the install button
Click finish to open the project
With the project open - create a new block diagram
Leave the name etc unchanged
Add in a Zynq MPSoC processing element
Run the block automation to configure the Processing System correctly for the ZUBoard configuration
From the boards tab drag across onto the block diagram the following
- PL Click I2C
- PL Push Button
- PL RGB1 LED
- PL RGB2 LED
- PL TempSensor I2C
- PL SyzygyDNA I2C
The result should look like the below
Run the connection automation
This will give a resultant block diagram as below
Re-Customise the Zynq MPSoC, disable the AXI HPM1 FPD interface on the PS/PL tab
On the same PS/PL interface enable the interrupts from PL to PS IRQ0
On the IO configuration tab enable SPI1 and set it to EMIO - this makes the pins available in the PL and means we can route them to the FPGA IO.
Add in a Concat block to provide for the interrupts
Set the concat block to have 5 inputs
On the AXI GPIO blocks enable the interrupts
Remember to do this for both AXI GPIO
Connect all of the blocks interrupt outputs to the concat block input, connect the output of the concat block to the Zynq MPSoC interrupt input.
Make the EMIO pins for master SPI external
Add in a constant block and connect it to the EMIO SPI SS input
The completed design should look like below
Create a HDL wrapper, let Vivado manage it
Create a new constraints file and add in the following
set_property IOSTANDARD LVCMOS18 [get_ports emio_spi1_m_o_0]
set_property IOSTANDARD LVCMOS18 [get_ports emio_spi1_s_i_0]
set_property IOSTANDARD LVCMOS18 [get_ports emio_spi1_sclk_o_0]
set_property IOSTANDARD LVCMOS18 [get_ports emio_spi1_ss_o_n_0]
set_property PACKAGE_PIN F6 [get_ports emio_spi1_sclk_o_0]
set_property PACKAGE_PIN E6 [get_ports emio_spi1_s_i_0]
set_property PACKAGE_PIN E5 [get_ports emio_spi1_m_o_0]
set_property PACKAGE_PIN G7 [get_ports emio_spi1_ss_o_n_0]
set_property PULLUP true [get_ports tempsensor_i2c_pl_scl_io]
set_property PULLUP true [get_ports tempsensor_i2c_pl_sda_io]
set_property PULLUP true [get_ports syzygydna_i2c_pl_scl_io]
set_property PULLUP true [get_ports syzygydna_i2c_pl_sda_io]
set_property PULLUP true [get_ports click_i2c_pl_scl_io]
set_property PULLUP true [get_ports click_i2c_pl_sda_io]
Build the project and once built export the XSA.
Create a new petalinux project, run the petalinux set up scripts
create a new project
petalinux-create -t project -n ZUBoard_sensors --template zynqMP
Customise the project with the XSA just exported -change directory into the project just created and run the command pointing to the XSA
petalinux-config --get-hw-description=/home/adiuvo/hdl_projects/zuboard_petalinux
This will open the dialog
Close the dialog without making any changes
Run the command to cusomise the kernel
petalinux-config -c kernel
Modify the drivers to include the UserSpace SPI, exit and save the settings
Run the command to customise the roofs
petalinux-config -c rootf
Enable the I2Ctools this can be very useful
Under the directory ZUBoard_live/project-spec/meta-user/recipes-bsp/device-tree/files edit the file system_user.dsti as below
/include/ "system-conf.dtsi"
/ {
};
&spi0 {
status = "okay";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};
&spi1 {
status = "okay";
spidev@0 {
compatible = "rohm,dh2228fv";
spi-max-frequency = <50000000>;
reg = <0>;
};
};
&axi_iic_0 {
clock-frequency = <100000>;
status = "okay";
};
&axi_iic_1 {
clock-frequency = <100000>;
status = "okay";
};
&axi_iic_2 {
clock-frequency = <100000>;
status = "okay";
};
Build the project using the command
petalinux-build
Wait for the build to be completed and then run the command
petalinux-package --boot --fsbl zynqmp_fsbl.elf --u-boot u-boot.elf --pmufw pmufw.elf --fpga system.bit --force
This will create the bootfile, copy the boot.bin, image.scr and image.ub onto the SD Card.
Hardware TestOnce the board has booted over the UART terminal we should be able to sign into the ZuBoard and run a command
ls/dev
This will show the I2C and SPI Dev interfaces
As we have the I2C tools installed we can also use them to scan the I2C networks, running the command i2detect will show all of the i2c drivers
sudo i2cdetect -l
We can then scan a particular I2C interface to determine what is connected.
If you want you can even read and write over the bus using I2Ctools. This can be achieved using the command sudo i2cget 3 0x3f 0x01. This is getting from I2C bus 3, at device address 0x3F the contents of register 0x01 which is the whoami register on the temp sensor. According to the data sheet the value of 0xA0 is the correct result
sudo i2cget 3 0x3f 0x01
To get started with out SW development we need to first insert the SD Card, ensure the ZUBoard is set to boot from SD, connect the click Brushless 3 module and power on the board.
Crucially we need to have a Ethernet connection also for the debugging and SW development.
Using the XSA file exported from Vivado we can open a new Vitis project and develop our software
Create a new application project
Select new application project and target the XSA exported from Vivado
Select PSU_CORTEXa53_SMP as the target type
Notice the operating system is Linux
Select hello world applicaiton
This will open the Vitis work space for the project
Within here we can modify the helloworld code with this below and it will test the temperature sensors on the ZUBoard
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#define ARRAY_SIZE(array) sizeof(array)/sizeof(array[0])
static const char *spi_device = "/dev/spidev1.0";
static const char *i2c_device = "/dev/i2c-1";
static const char address = 0x3F;
static uint8_t bits = 8;
static uint32_t speed = 1000000;
static uint32_t mode =SPI_MODE_3 ;
static uint16_t delay;
int main(){
int spi;
int i2c;
int ret = 0;
unsigned char spi_tx_buf[2];
unsigned char spi_rx_buf[2];
unsigned char i2c_tx_buf[2];
unsigned char i2c_rx_buf[2];
int temp;
float spi_temp_degC;
int16_t spi_temperature;
float i2c_temp_degC;
int16_t i2c_temperature;
char temp_l;
char temp_h;
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data msgset[1];
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)spi_tx_buf,
.rx_buf = (unsigned long)spi_rx_buf,
.len = ARRAY_SIZE(spi_tx_buf),
.delay_usecs = delay,
.speed_hz = 0,
.bits_per_word = 0,
};
//Open SPI and I2C
spi = open(spi_device, O_RDWR);
if (spi < 0)
printf("can't open SPI device\n\r");
i2c = open(i2c_device, O_RDWR);
if (i2c < 0)
printf("can't open I2C device\n\r");
//configure SPI
ret = ioctl(spi, SPI_IOC_WR_MODE32, &mode);
if (ret == -1)
printf("can't set spi mode\n\r");
ret = ioctl(spi, SPI_IOC_RD_MODE32, &mode);
if (ret == -1)
printf("can't get spi mode\n\r");
ret = ioctl(spi, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret == -1)
printf("can't set bits per word\n\r");
ret = ioctl(spi, SPI_IOC_RD_BITS_PER_WORD, &bits);
if (ret == -1)
printf("can't get bits per word\n\r");
ret = ioctl(spi, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret == -1)
printf("can't set max speed hz\n\r");
ret = ioctl(spi, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
if (ret == -1)
printf("can't get max speed hz\n\r");
//enable SPI sensor sampling
spi_tx_buf[0] = (char) 0x10; //enable sampling
spi_tx_buf[1] = (char) 0x20;
ret = ioctl(spi, SPI_IOC_MESSAGE(1), &tr);
if (ret == -1)
printf("IOCTL error\n\r");
//check SPI temp sensor can be detected
spi_tx_buf[0] = (char) 0x8F; // read who am I
spi_tx_buf[1] = (char) 0x00;
ret = ioctl(spi, SPI_IOC_MESSAGE(1), &tr);
if (ret == -1)
printf("IOCTL error\n\r");
if (spi_rx_buf[1] == 0xb3)
printf("LPS22 Detected\n\r");
//check I2C temp sensor can be detected
i2c_tx_buf[0] = 0x01;
msgs[0].addr = address;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = i2c_tx_buf;
msgs[1].addr = address;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = i2c_rx_buf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 2;
if (ioctl(i2c, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_read");
}
if (i2c_rx_buf[0] == 0xa0)
printf("STTS22H Detected\n\r");
//set sampling on the I2C temp sensor
i2c_tx_buf[0] = 0x04;
i2c_tx_buf[1] = 0x0c;
msgs[0].addr = address;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = i2c_tx_buf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 1;
if (ioctl(i2c, I2C_RDWR, &msgset) < 0)
perror("ioctl(I2C_RDWR) in i2c_write");
while(1){
//read temperature from the SPI sensor
spi_tx_buf[0] = (char) 0xab;
spi_tx_buf[1] = (char) 0x00;
ret = ioctl(spi, SPI_IOC_MESSAGE(1), &tr);
if (ret == -1)
printf("IOCTL error\n\r");
temp_l = spi_rx_buf[1];
spi_tx_buf[0] = (char) 0xac;
spi_tx_buf[1] = (char) 0x00;
ret = ioctl(spi, SPI_IOC_MESSAGE(1), &tr);
if (ret == -1)
printf("IOCTL error\n\r");
temp_h = spi_rx_buf[1];
temp = ((temp_h <<8)|(temp_l));
if ((temp & 0x8000) == 0) //msb = 0 so not negative
{
spi_temperature = temp;
}
else
{
// Otherwise perform the 2's complement math on the value
spi_temperature = (~(temp - 0x01)) * -1;
}
spi_temp_degC = (float) spi_temperature /100.0;
//Read temperature from the I2C sensor
i2c_tx_buf[0] = 0x06;
msgs[0].addr = address;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = i2c_tx_buf;
msgs[1].addr = address;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 2;
msgs[1].buf = i2c_rx_buf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 2;
if (ioctl(i2c, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_read");
}
i2c_temperature = i2c_rx_buf[1] << 8 | i2c_rx_buf[0];
i2c_temp_degC = (float) i2c_temperature / 100;
printf("SPI Temp Deg C %4.2f I2C Temp Deg C %4.2f Difference Temp Deg C %4.2f \n\n\r",spi_temp_degC, i2c_temp_degC, (spi_temp_degC - i2c_temp_degC) );
usleep(1000000);
}
}
To debug this we first need to set a remote target connection, to do this we need the IP address of the ZUBoard
Once this is installed we can run the application and we will see the temperatures being reported.
Now to begin working with the Brushless Click 3, this is controlled over I2C.
Create a new application project targeting the same platform, again select the hello world application and replace the code with the one below.
#include <stdint.h>
#include <stdio.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <linux/ioctl.h>
#include <fcntl.h>
#include <linux/types.h>
static const char *i2c_device = "/dev/i2c-2";
static const char address = 0x52;
uint8_t i2cread(uint8_t device, uint8_t addr, uint8_t reg){
unsigned char i2c_tx_buf[2];
unsigned char i2c_rx_buf[2];
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data msgset[1];
i2c_tx_buf[0] = reg;
msgs[0].addr = addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = i2c_tx_buf;
msgs[1].addr = addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 1;
msgs[1].buf = i2c_rx_buf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 2;
if (ioctl(device, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_read");
}
return i2c_rx_buf[0];
}
uint8_t i2cwrite(uint8_t device, uint8_t addr, uint8_t reg,uint8_t data ){
unsigned char i2c_tx_buf[2];
unsigned char i2c_rx_buf[2];
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data msgset[1];
i2c_tx_buf[0] = reg;
i2c_tx_buf[1] = data;
msgs[0].addr = addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = i2c_tx_buf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 1;
if (ioctl(device, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_read");
}
return i2c_rx_buf[0];
}
int main()
{
int i2c;
uint8_t voltage;
uint8_t status;
uint8_t fault;
uint8_t sysopt;
uint8_t motor;
i2c = open(i2c_device, O_RDWR);
if (i2c < 0)
printf("can't open I2C device\n\r");
i2cwrite(i2c, address, 0x03, 0x40);
i2cwrite(i2c, address, 0x28, 0xfe);
i2cwrite(i2c, address, 0x2A, 0x0d);
i2cwrite(i2c, address, 0x27, 0x04);
i2cwrite(i2c, address, 0x20, 0x2f);
i2cwrite(i2c, address, 0x01, 0x80);
i2cwrite(i2c, address, 0x00, 0x01);
while(1){
status = i2cread(i2c,address, 0x11);
printf("Speed Reg 1 is %x \n", status);
status = i2cread(i2c,address, 0x10);
printf("Status Reg is %x \n", status);
fault = i2cread(i2c,address, 0x1E);
printf("Fault Reg is %x \n", fault);
for (int i = 1; i < 10; i++) {
sysopt = i2cread(i2c,address, (0x22 + i));
printf("sysopt%d Reg is %x \n", i, sysopt);
}
//sysopt5 = i2cread(i2c,address, 0x27);
//printf("sysopt5 Reg is %x \n", sysopt5);
//sysopt5 = i2cread(i2c,address, 0x27);
//printf("sysopt5 Reg is %x \n", sysopt5);
voltage = i2cread(i2c,address, 0x1A);
printf("Supply voltage is %f V\n", ((float) voltage * 30.0 / 256.0));
motor = i2cread(i2c,address, 0x20);
printf("motor Reg is %x \n", motor);
motor = i2cread(i2c,address, 0x00);
printf("Speed Reg0 is %x \n", motor);
motor = i2cread(i2c,address, 0x01);
printf("Speed Reg1 is %x \n", motor);
usleep(1000000);
}
return 0;
}
When we run this and connect it to the motor we can see the motor start and its rotation.
Motor Starting and running
We can see any messages we output in the console in Vitis.
In this project we have seen how we are able to create Vivado designs for the ZUBoard. Import these Vivado designs into PetaLinux and create a Embedded Linux solution which enables us to us the embedded Linux features to work with sensors and physical drives.
Comments