Interfacing FXLS8471Q with i.MX6Q

Document created by Carlos_Musich Employee on Sep 7, 2017Last modified by ebiz_ws_prod on Aug 6, 2018
Version 4Show Document
  • View in full screen mode

 

Introduction

 

This document describes how to interface and use Xtrinsic FXLS8471Q digital accelerometer with IMX6Q processor. For this purpose, UDOO Quad board is used with NXP Linux image for IMX6QSABRE-SD board with Kernel 3.14.56 (not with Udoobuntu) to simulate working with a custom board, the process to customize and build such image for UDOO Quad board is described here: Building Linux Image with QT5 for UDOO Quad 

 

On the sensor side FRDM-FXS-MULTI(-B) sensor expansion board which features many of the Xtrinsic sensors introduced in 2013 including the FXSL8471Q is used.

 

Hardware Connections

 

 The SPI signals from FRDM-FXS-MULTI are routed to SPI1 module of UDOO Quad. FXLS8471Q provides an INT pin which is indicated on image below, however on this implementation polling method is used. Please note that Chip Select is not controlled automatically by SPI module, therefore this pin is configured as GPIO. Besides these signals, reset and power source pins were also connected.

 

The following figure shows the pins used in FRDM-FXS-MULTI which are connected with UDOO Quad board.

 

 

 

 

 

The pins used on the UDOO Quad side are shown in the images below. You can find UDOO Quad pinout diagram here Index of /download/files/pinout.  

 

 

 

Udoo Quad Pinout 1

 

 

 

 

 

 

 

 

 

 

Device Tree Configuration

 

As mentioned at the beginning of this document, a NXP Linux image for IMX6Q-SABRESD is used. In order to customize this image to be used with the UDOO Quad board it is necessary to build a .dtb that matches with it. This task was accomplished obtaining dts and dtsi source files listed below from Kernel Linux Repository for UDOO at UDOOboard (UDOOboard) · GitHub.

 

  • imx6qdl-udoo.dtsi
  • imx6qdl-udoo-externalpins.dtsi
  • imx6q-udoo-hdmi.dts

 

These files were copied into the IMX6Q-SABRESD build source folder and a imx6q-udoo.dtb was generated. This process is described on Building Linux Image with QT5 for UDOO Quad 

 

The following snippets show how the nodes involved on the SPI configuration were set for UDOO Quad board. Please note that each device tree should match your custom board.

 

In imx6qdl-udoo-externalpins.dtsi verify that ecspi1 node matches with the one shown below.

 

&ecspi1 {
    fsl,spi-num-chipselects = <1>;
    cs-gpios = <&gpio5 17 0>;
    pinctrl-0 = <&pinctrl_ecspi1>;
    pinctrl-names = "default";
    status = "disabled";

    spidev0: spi@0 {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <2000000>;
    };
};

 

 

As you can see on snippet above, ecspi1 node uses pinctrl_ecspi1 as the pin configuration node which is defined in the same file and it looks as follows.

 

       pinctrl_ecspi1: ecspi1grp {
            fsl,pins = <
            MX6QDL_PAD_DISP0_DAT22__ECSPI1_MISO  0x100b1
            MX6QDL_PAD_DISP0_DAT20__ECSPI1_SCLK  0x100b1
            MX6QDL_PAD_DISP0_DAT21__ECSPI1_MOSI  0x100b1
            MX6QDL_PAD_DISP0_DAT23__GPIO5_IO17   0x80000000
            >;
        };

 

However, as you may know each pin on i.MX devices has up to 8 potential functions, and on the other side, one function can be available in different pins.

 

For example, ECSPI1_MISO can be mapped to 4 different pins

 

 

and each pin can have different functions. From all the available functions in a pin one is chosen to be the pad (pin) name. In the image below DISP0_DATA22 was chosen to be the pad name.

 

 

 

Now, back to pinctrl_ecspi1 node, the macros used here are defined in imx6q-pinfuc.h, as you can see DISP0_DAT20 - DISP0_DAT23 are the pads used with the ECSPI signals.

 

It is necessary to check it that there are no other configurations for DISP0_DAT20 - DISP0_DAT23 and if they are it is necessary to comment out them or delete them. In this case there were other configurations for these pins in imx6qdl-udoo-externalpins.dtsi and they were commented out.

 

 

&iomuxc {
    imx6q-udoo {
        // External Pinout GPIOs
        external_hog: hoggrp-2 {
        fsl,pins = <
            MX6QDL_PAD_CSI0_DAT11__GPIO5_IO29    0x80000000  // {{external-gpio-0}}
            MX6QDL_PAD_CSI0_DAT10__GPIO5_IO28    0x80000000  // {{external-gpio-1}}
            MX6QDL_PAD_SD1_CLK__GPIO1_IO20       0x80000000  // {{external-gpio-2}}
            MX6QDL_PAD_SD1_DAT0__GPIO1_IO16      0x80000000  // {{external-gpio-3}}
            MX6QDL_PAD_SD1_DAT1__GPIO1_IO17      0x80000000  // {{external-gpio-4}}
            MX6QDL_PAD_SD1_CMD__GPIO1_IO18       0x80000000  // {{external-gpio-5}}
            MX6QDL_PAD_SD4_DAT1__GPIO2_IO09      0x80000000  // {{external-gpio-6}}
            MX6QDL_PAD_SD4_DAT2__GPIO2_IO10      0x80000000  // {{external-gpio-7}}
            MX6QDL_PAD_SD1_DAT3__GPIO1_IO21      0x80000000  // {{external-gpio-8}}
            MX6QDL_PAD_SD1_DAT2__GPIO1_IO19      0x80000000  // {{external-gpio-9}}
            MX6QDL_PAD_GPIO_1__GPIO1_IO01        0x80000000  // {{external-gpio-10}}
            MX6QDL_PAD_GPIO_9__GPIO1_IO09        0x80000000  // {{external-gpio-11}}
            MX6QDL_PAD_GPIO_3__GPIO1_IO03        0x80000000  // {{external-gpio-12}}
            MX6QDL_PAD_SD4_DAT0__GPIO2_IO08      0x80000000  // {{external-gpio-13}}
            MX6QDL_PAD_CSI0_DAT4__GPIO5_IO22     0x80000000  // {{external-gpio-14}}
            MX6QDL_PAD_CSI0_DAT16__GPIO6_IO02    0x80000000  // {{external-gpio-15}}
            MX6QDL_PAD_CSI0_DAT14__GPIO6_IO00    0x80000000  // {{external-gpio-16}}
            MX6QDL_PAD_CSI0_DAT15__GPIO6_IO01    0x80000000  // {{external-gpio-17}}
            MX6QDL_PAD_CSI0_DAT12__GPIO5_IO30    0x80000000  // {{external-gpio-18}}
            MX6QDL_PAD_CSI0_DAT13__GPIO5_IO31    0x80000000  // {{external-gpio-19}}
            MX6QDL_PAD_EIM_D28__GPIO3_IO28       0x80000000  // {{external-gpio-20}}
            MX6QDL_PAD_EIM_D21__GPIO3_IO21       0x80000000  // {{external-gpio-21}}
            MX6QDL_PAD_DISP0_DAT6__GPIO4_IO27    0x80000000  // {{external-gpio-22}}
            MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28    0x80000000  // {{external-gpio-23}}
            MX6QDL_PAD_DISP0_DAT8__GPIO4_IO29    0x80000000  // {{external-gpio-24}}
            MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30    0x80000000  // {{external-gpio-25}}
            MX6QDL_PAD_DISP0_DAT10__GPIO4_IO31   0x80000000  // {{external-gpio-26}}
            MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05   0x80000000  // {{external-gpio-27}}
            MX6QDL_PAD_DISP0_DAT12__GPIO5_IO06   0x80000000  // {{external-gpio-28}}
            MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07   0x80000000  // {{external-gpio-29}}
            MX6QDL_PAD_DISP0_DAT14__GPIO5_IO08   0x80000000  // {{external-gpio-30}}
            MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09   0x80000000  // {{external-gpio-31}}
            MX6QDL_PAD_DISP0_DAT16__GPIO5_IO10   0x80000000  // {{external-gpio-32}}
            MX6QDL_PAD_DISP0_DAT17__GPIO5_IO11   0x80000000  // {{external-gpio-33}}
            MX6QDL_PAD_DISP0_DAT18__GPIO5_IO12   0x80000000  // {{external-gpio-34}}
            MX6QDL_PAD_DISP0_DAT19__GPIO5_IO13   0x80000000  // {{external-gpio-35}}
            //MX6QDL_PAD_DISP0_DAT20__GPIO5_IO14   0x80000000  // {{external-gpio-36}}
            //MX6QDL_PAD_DISP0_DAT21__GPIO5_IO15   0x80000000  // {{external-gpio-37}}
            MX6QDL_PAD_EIM_A16__GPIO2_IO22       0x80000000  // {{external-gpio-38}}
            MX6QDL_PAD_GPIO_18__GPIO7_IO13       0x80000000  // {{external-gpio-39}} (KEY_VOL_UP)
            MX6QDL_PAD_NANDF_D0__GPIO2_IO00      0x80000000  // {{external-gpio-40}} (HOME)
            MX6QDL_PAD_NANDF_D3__GPIO2_IO03      0x80000000  // {{external-gpio-41}} (SEARCH)
            MX6QDL_PAD_NANDF_D2__GPIO2_IO02      0x80000000  // {{external-gpio-42}} (BACK)
            MX6QDL_PAD_NANDF_D1__GPIO2_IO01      0x80000000  // {{external-gpio-43}} (MENU)
            MX6QDL_PAD_GPIO_19__GPIO4_IO05       0x80000000  // {{external-gpio-44}} (KEY_VOL_DOWN)
           // MX6QDL_PAD_DISP0_DAT22__GPIO5_IO16   0x80000000  // {{external-gpio-45}}
            //MX6QDL_PAD_DISP0_DAT23__GPIO5_IO17   0x80000000  // {{external-gpio-46}}
            MX6QDL_PAD_EIM_D25__GPIO3_IO25       0x80000000  // {{external-gpio-47}}
            MX6QDL_PAD_KEY_ROW1__GPIO4_IO09      0x80000000  // {{external-gpio-48}}
            MX6QDL_PAD_KEY_COL1__GPIO4_IO08      0x80000000  // {{external-gpio-49}}
            MX6QDL_PAD_EIM_OE__GPIO2_IO25        0x80000000  // {{external-gpio-50}}
            MX6QDL_PAD_EIM_CS1__GPIO2_IO24       0x80000000  // {{external-gpio-51}}
            MX6QDL_PAD_EIM_CS0__GPIO2_IO23       0x80000000  // {{external-gpio-52}}
            MX6QDL_PAD_EIM_D24__GPIO3_IO24       0x80000000  // {{external-gpio-53}}
            MX6QDL_PAD_GPIO_8__GPIO1_IO08        0x80000000  // {{external-gpio-54}}
            MX6QDL_PAD_GPIO_7__GPIO1_IO07        0x80000000  // {{external-gpio-55}}
            >;
        };

 

 

Finally, in imx6q-udoo-hdmi.dts enable ECSPI by including an ecspi1 node reference and setting the status property to "okay".

 

dts-v1/;
#include "imx6q.dtsi"
#include "imx6qdl-udoo.dtsi"
#include "imx6qdl-udoo-externalpins.dtsi"

/ {
    model = "UDOO Quad Board";
    compatible = "udoo,imx6q-udoo", "fsl,imx6q";        mxcfb1: fb@0 {
        compatible = "fsl,mxc_sdc_fb";
        disp_dev = "hdmi";
        interface_pix_fmt = "RGB24";
        mode_str ="1920x1080M@60";
        default_bpp = <32>;
        int_clk = <0>;
        late_init = <0>;
        status = "okay";
    };
};


&ecspi1 {
    status = "okay";
};

 

Note: This could be done in .dtsi but the side effect is that any configuration including the dtsi will have ECSPI1 enabled by default.

 

Finally it is necessary to rebuild the device tree and copy it to the FAT partition of the sdcard. This process is explained in next section.

 

Rebuilding Image

 

Requirements

In order to build an image as well as an application it is necessary to install a tool to cross-compile code, this way we will be able to generate executable files for ARM architecture in our host machine. This tool is called meta-toolchain, the following commands are used to install it.

 

In the following instructions it is considered that the build directory is ~/fsl-release_bsp/build_imx6qsabresd_qt5/

 

$ cd ~/fsl-release_bsp

$ source setup-environment build_imx6qsabresd_qt5

$ bitbake meta-toolchain
$ sh tmp/deploy/sdk/fsl-imx-x11-glibc-x86_64-meta-toolchain-cortexa9hf-vfp-neon-toolchain-<kernel>.sh

Kernel

 

In order to enable the kernel SPI driver it is necessary to set it in the menuconfig and rebuild zImage.

 

Go to kernel directory within the Yocto build directory that we created.

$ cd <build_directory>/tmp/work-shared/imx6qsabresd/kernel-source$

 

Launch menuconfig for imx

$ source /opt/fsl-imx-x11/<kernel version>/environment-setup-armv7a-vfp-neon-poky-linux-gnueabi

$ make imx_v7_defconfig

$ make menuconfig

 

Enable SPI Driver by going to Device Drivers --> SPI Support and seting '*' to SPI device Driver, save changes and then exit.

 

Menuconfig

 

And build zImage

$ make zImage

 

The generated file is located in <build_directory>/tmp/work-shared/imx6qsabresd/kernel-source/arch/arm/boot$ and it must be copied to the FAT partition of the SD card.

 

Device Tree

 

To build the device tree go to kernel directory within the Yocto build directory that we created.

$ cd <build_directory>/tmp/work-shared/imx6qsabresd/kernel-source$

 

Build dtb file

$ source /opt/fsl-imx-x11/<kernel version>/environment-setup-armv7a-vfp-neon-poky-linux-gnueabi

$ make imx_v7_defconfig

$ make imx6q-udoo-hdmi.dtb

 

The generated file is located in <build_directory>/tmp/work-shared/imx6qsabresd/kernel-source/arch/arm/boot/dts$ and it must be renamed as imx6q-udoo.dtb and copied to the FAT partition of the SD card.

 

Application Driver

 

The SPI kernel driver uses a structure named spi_ioc_transfer which describes a single SPI transfer. It holds pointers to userspace buffers with transmit and receive data, length of buffers, speed, bits per word among other configurations. For further details you can refer to /include/uapi/linux/spi/spidev.h on your kernel source. In the next section the application driver is explained.

 

The source files of the application driver as well as the device tree sources can be found in the attached.zip file.

 

Main

 

The following code shows function main where the SPI driver is initialized driver using "/dev/spidev0.0" which is listed under /dev (in the target root file system) after SPIdev driver is enabled in menuconfig and kernel is rebuilt as indicated in previous section. Then it reads who am i register just for sanity check purpose and initializes and calibrates the sensor and enters in an endless loop where it reads the sensor whenever there is new data ready. Raw data is returned in 6 bytes, so it is managed to get X, Y and Z values and finally these values are converted to G's values and it waits until a key is pressed to continue to read the following value.

 

 

/****************************************************************************** * Main ******************************************************************************/
int main(){
     //enableGPIO(); //In case of using interrupt instead of polling
     file = spi_init("/dev/spidev0.0"); //dev
     who();
     FXLS8471Q_Init();
     FXLS8471Q_Calibration();


     while(1)
     {
         checkData();

         if (DataReady) // Is a new set of data ready?
         {
             buffer = (unsigned char *)spi_read(OUT_X_MSB_REG, 6, file); // Read data output registers 0x01-0x06
             printf("AccData[0] = 0x%X \n AccData[1] = 0x%X \n AccData[2] = 0x%X \n AccData[3] = 0x%X \n AccData[4] = 0x%X \n AccData[5] = 0x%X \n", AccData[0], AccData[1], AccData[2], AccData[3], AccData[4], AccData[5]);

             Xout_14_bit = ((short) (AccData[0] << 8 | AccData[1])) >> 2; // Compute 14-bit X-axis output value
             Yout_14_bit = ((short) (AccData[2] << 8 | AccData[3])) >> 2; // Compute 14-bit Y-axis output value
             Zout_14_bit = ((short) (AccData[4] << 8 | AccData[5])) >> 2; // Compute 14-bit Z-axis output value

             Xout_g = ((float) Xout_14_bit) / SENSITIVITY_2G; // Compute X-axis output value in g's
             Yout_g = ((float) Yout_14_bit) / SENSITIVITY_2G; // Compute Y-axis output value in g's
             Zout_g = ((float) Zout_14_bit) / SENSITIVITY_2G; // Compute Z-axis output value in g's

             //printf(" X = %d Y = %d Z = %d \n\n", AccData[0], AccData[2], AccData[4]);
             //printf("Xval = %d Yval = %d Zval = %d \n", Xout_14_bit, Yout_14_bit, Zout_14_bit);
             printf(" XG = %f YG = %f ZG = %f \n\n", Xout_g, Yout_g, Zout_g);
             getchar();
        }
     }

     close(file);
}

Init Driver

 

The spi_init function opens a file for the driver "/dev/spidev0.0" which is passed as a parameter from main(), then the SPI configuration parameters are read just for informative purpose. Finally struct xfer which is of type spi_ioc_transfer is initialized.

 

/********************************* SPIdev Init **********************************************/
int spi_init(char filename[40])
{
int file;
unsigned char mode, lsb, bits;
unsigned int baudrate = 524250, speed;

printf("SPI Init \n");
if ((file = open(filename,O_RDWR)) < 0)
{
printf("Failed to open the bus.");
/* ERROR HANDLING; you can check errno to see what went wrong */
com_serial=0;
exit(1);
}

if (ioctl(file, SPI_IOC_RD_MODE, &mode) < 0)
{
perror("SPI rd_mode");
return -1;
}

if (ioctl(file, SPI_IOC_RD_LSB_FIRST, &lsb) < 0)
{
perror("SPI rd_lsb_fist");
return -1;
}

if (ioctl(file, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0)
{
perror("SPI bits_per_word");
return -1;
}

if (ioctl(file, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0)
{
perror("SPI max_speed_hz");
return -1;
}
printf("%s: spi mode %d, %d bits %s per word, %d Hz max\n",filename, mode, bits, lsb ? "LSB first" : "MSB first", baudrate);

xfer[0].len = 3; /* Length of command to write*/
xfer[0].cs_change = 0; /* Keep CS activated */
xfer[0].delay_usecs = 0; //delay in us
xfer[0].speed_hz = 524250; //speed
xfer[0].bits_per_word = 8; // bites per word 8

xfer[1].len = 4; /* Length of Data to read */
xfer[1].cs_change = 0; /* Keep CS activated */
xfer[1].delay_usecs = 0;
xfer[1].speed_hz = 524250;
xfer[1].bits_per_word = 8;

printf("SPI Init Finished \n");
return file;
}

 

 

Write

 

The SPI communication is started with the  falling edge on chip select pin. A write operation is initiated by transmitting a 1 for the R/W bit. Then the 8-bit register address, ADDR[7:0] is encoded in the first and second serialized bytes. Data to be written starts in the third serialized byte. The order of the bits is as follows:

 

Byte 0: R/W, ADDR[6], ADDR[5], ADDR[4], ADDR[3], ADDR[2], ADDR[1], ADDR[0]

Byte 1: ADDR[7], X, X, X, X, X, X, X

Byte 2: DATA[7], DATA[6], DATA[5], DATA[4], DATA[3], DATA[2], DATA[1], DATA[0]

 

The SPI communication is finished with the  falling edge on chip select pin.

 

A you can see below array buf keeps the destination address and the data to be transferred, then xfer structure is configured to point to buf as the transfer buffer and the length of the data es set to 2 + data size (the first 2 bytes are for the destination address which is splitted in 2 bytes). Finally the transfer is started by the ioctl command.

 

/******************** Write a byte to the FXLS8471Q ***************************
* Byte 0: 1,ADDR[6],ADDR[5],ADDR[4],ADDR[3],ADDR[2],ADDR[1],ADDR[0]
* Byte 1: ADDR[7],0,0,0,0,0,0,0
* Byte 2: DATA[7],DATA[6],DATA[5],DATA[4],DATA[3],DATA[2],DATA[1],DATA[0]
******************************************************************************/


void spi_write(int registerAddress, int nbytes, char data, int file)
{
unsigned char buf[32];
int status;


memset(buf, 0, sizeof buf);
buf[0] = 0x80 | registerAddress;
buf[1] = 0x80 & registerAddress;
buf[2] = data;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = nbytes + 2; /* Length of command to write*/
status = ioctl(file, SPI_IOC_MESSAGE(1), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return;
}

com_serial=1;
failcount=0;
}

 

 

Below is the write operation which writes the value 0x3D to the CTRL_REG1 (0x2A).

 

SPI Write.JPG.jpg

 

Read

 

Similarly a read operation is initiated by transmitting a 0 for the R/W bit. Then the 8-bit register address, ADDR[7:0] is encoded in the first and second serialized bytes. The data is read from the MISO pin (MSB first).

 

In this case the array buf keeps the address that is going to be read and the third byte is just a dummy byte to be transferred. Structure xfer keeps transfer buffer pointer which in this case is buf and receive buffer which is AccData, the lenght of the command to write and the length of data to read is also specified and finally the read command is executed with ioctl call.

 

 

/********************** Read a byte from the FXLS8471Q *********************** 
* Byte 0: 0,ADDR[6],ADDR[5],ADDR[4],ADDR[3],ADDR[2],ADDR[1],ADDR[0]
* Byte 1: ADDR[7],0,0,0,0,0,0,0
* Byte 2: 0,0,0,0,0,0,0,0
******************************************************************************/


char * spi_read(int registerAddress, int nbytes, int file)
{
int status;

memset(buf, 0, sizeof buf);
memset(AccData, 0, sizeof AccData);
buf[0] = 0x7F & registerAddress;
buf[1] = 0x80 & registerAddress;
buf[2] = 0x00;
xfer[0].tx_buf = (unsigned long)buf;
xfer[0].len = 2; /* Length of command to write*/
xfer[1].rx_buf = (unsigned long) AccData;
xfer[1].len = nbytes; /* Length of Data to read */
xfer[1].speed_hz = 524250;
status = ioctl(file, SPI_IOC_MESSAGE(2), xfer);
if (status < 0)
{
perror("SPI_IOC_MESSAGE");
return 0;
}

com_serial=1;
failcount=0;
return AccData;
}

 

 

The screenshot below shows the read operation which reads the correct value 0x6A from the WHO_AM_I register (0x0D).

 

SPI Read.JPG.jpg

 

Building and Run the SPI Application Driver

 

In order to build the application please save the source files and the Makefile (attached) on any place on your host machine and go to that directory where you saved them. Then build the application with the Meta-toolchain using the Make file with the following commands.

 

Build application driver

$ cd <folder wher spi device driver source is saved>

$ source /opt/fsl-imx-x11/<kernel version>/environment-setup-armv7a-vfp-neon-poky-linux-gnueabi

$ make

 

A spi_test file will be created in the same folder, copy this file into the sdcard on the Root File System partition in /home/root. Finally when booting the target execute the application. You must see the following output.

 

 

 

Conclusion

 

This document summarizes the steps to create a SPI application driver. As you saw it is necessary to rebuild the device tree and the kernel, and to do this it is necessary to install metatoolchain. The application driver uses the SPI kernel driver and its main functions are next:

  • Init SPI Driver
  • Read
  • Write
  • Init sensor
  • Callibrate Sensor
  • Polling data ready function

 

I would say that the core of the application driver are the read and write functions which configure the SPI kernel driver and pass data to it in the format required by the sensor.

 

For specific details on the driver please see the attached .zip file which contains the application driver code, the Makefile with the one it is build and the device tree sources.

 

I hope you find this document useful.

 

 

Carlos

Attachments

Outcomes