FXLS8471Q 与 i.MX6Q 接口 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
简介 硬件连接 设备树配置 重建图像 要求 Kernel 器件树 应用程序驱动程序 主要 初始化驱动程序 Write Read 构建并运行SPI应用程序驱动程序 结束语 简介 本文档介绍了如何将Xtrinsic FXLS8471Q 数字加速度计与 IMX6Q 处理器连接并使用。为此,UDOO Quad 开发板与适用于 IMX6QSABRE-SD 开发板的 NXP Linux 映像(内核为 3.14.56,而非 Udoobuntu)一起使用,以模拟使用自定义开发板,自定义和构建 UDOO Quad 开发板此类映像的过程如下所述: 使用 QT5 为 UDOO Quad 构建 Linux 映像 在传感器方面,使用了FRDM-FXS-MULTI(-B) 传感器扩展板,该扩展板具有 2013 年推出的许多 Xtrinsic 传感器,包括FXSL8471Q 。 硬件连接 来自 FRDM-FXS-MULTI 的 SPI 信号被路由到 UDOO Quad 的 SPI1 模块。FXLS8471Q 提供了一个 INT 引脚,如下图所示,但在此实现中使用了轮询方法。请注意,芯片选择不由 SPI 模块自动控制,因此该引脚配置为 GPIO。除了这些信号之外,还连接了复位和电源引脚。 下图显示了 FRDM-FXS-MULTI 中使用的与 UDOO Quad板连接的引脚。 UDOO Quad 侧使用的引脚如下图所示。您可以在此处/download/files/pinout 索引中找到 UDOO Quad 引脚排列图。 设备树配置 正如本文档开头所述,使用了适用于 IMX6Q-SABRESD 的 NXP Linux 映像。为了定制此图像以用于 UDOO Quad 板,需要构建一个 .dtb与之匹配。此任务已完成,并从UDOOboard (UDOOboard) · GitHub上的 UDOO 内核 Linux 存储库获取下面列出的 dts 和 dtsi 源文件。 imx6qdl-udoo.dtsi imx6qdl-udoo-externalpins.dtsi imx6q-udoo-hdmi.dts 这些文件被复制到 IMX6Q-SABRESD 构建源文件夹中,并生成了 imx6q-udoo.dtb。此过程已在“使用 QT5 为 UDOO Quad 构建 Linux 映像”中进行了描述 以下代码片段展示了如何为 UDOO Quad 板设置 SPI 配置中涉及的节点。请注意,每个设备树都应与您的自定义板相匹配。 在imx6qdl-udoo-externalpins.dtsi中验证ecspi1节点是否与下面显示的节点匹配。 &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>;
};
}; 正如您在上面的代码片段中看到的, ecspi1节点使用pinctrl_ecspi1作为引脚配置节点,该节点在同一个文件中定义,如下所示。 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
>;
}; 但是,您可能知道 i.MX 设备上的每个引脚最多有 8 种潜在功能,另一方面,一个功能可以在不同的引脚上使用。 例如,ECSPI1_MISO 可以映射到 4 个不同的引脚 并且每个引脚可以具有不同的功能。从引脚的所有可用功能中选择一个作为焊盘(引脚)名称。在下图中,选择 DISP0_DATA22 作为焊盘名称。 现在回到pinctrl_ecspi1节点,这里用到的宏定义在imx6q-pinfuc.h 中,如你看到的 DISP0_DAT20 - DISP0_DAT23 是与 ECSPI 信号一起使用的焊盘。 需要检查 DISP0_DAT20 - DISP0_DAT23 没有其他配置,如果有,则需要将其注释掉或删除。在这种情况下, imx6qdl-udoo-externalpins.dtsi中这些引脚还有其他配置,但它们已被注释掉。 &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}}
>;
}; 最后,在imx6q-udoo-hdmi.dts中通过包含 ecspi1 节点引用并将状态属性设置为“okay”来启用 ECSPI。 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";
};
注意:这可以在 .dtsi 中完成但副作用是包括 dtsi 在内的任何配置都会默认启用 ECSPI1。 最后需要重建设备树并将其复制到 SD 卡的 FAT 分区。下一节将解释此过程。 重建图像 要求 为了构建图像和应用程序,需要安装一个工具来交叉编译代码,这样我们就可以在主机上为 ARM 架构生成可执行文件。这个工具叫做meta-toolchain,下面的命令用来安装它。 在以下说明中,认为构建目录是 ~/fsl-release_bsp/build_imx6qsabresd_qt5/ $ cd ~/fsl-release_bsp $源设置环境build_imx6qsabresd_qt5 $ bitbake 元工具链 $ sh tmp/deploy/sdk/fsl-imx-x11-glibc-x86_64-meta-toolchain-cortexa9hf-vfp-neon-toolchain-.sh Kernel 为了启用内核 SPI 驱动程序,必须在 menuconfig 中进行设置并重建 zImage。 转到我们创建的 Yocto 构建目录中的内核目录。 $ cd /tmp/work-shared/imx6qsabresd/kernel-source$ 启动 imx 的 menuconfig $ source /opt/fsl-imx-x11/ /environment-setup-armv7a-vfp-neon-poky-linux-gnueabi $ make imx_v7_defconfig $ make menuconfig 通过转到设备驱动程序 --> SPI 支持并将“*”设置为 SPI 设备驱动程序来启用 SPI 驱动程序,保存更改然后退出。 并构建zImage $ 制作 zImage 生成的文件位于 /tmp/work-shared/imx6qsabresd/kernel-source/arch/arm/boot$ 中,必须将其复制到 SD 卡的 FAT 分区。 器件树 要构建设备树,请转到我们创建的 Yocto 构建目录中的内核目录。 $ cd /tmp/work-shared/imx6qsabresd/kernel-source$ 构建 dtb 文件 $ source /opt/fsl-imx-x11/ /environment-setup-armv7a-vfp-neon-poky-linux-gnueabi $ make imx_v7_defconfig $ make imx6q-udoo-hdmi.dtb 生成的文件位于 /tmp/work-shared/imx6qsabresd/kernel-source/arch/arm/boot/dts$ ,必须将其重命名为imx6q-udoo.dtb并复制到 SD 卡的 FAT 分区。 应用程序驱动程序 SPI 内核驱动程序使用一个名为spi_ioc_transfer的结构体来描述单次 SPI 传输。该结构体包含指向用户空间缓冲区的指针,其中包含发送和接收数据、缓冲区长度、速度、每字位数以及其他配置。更多详细信息,请参阅内核源代码中的/include/uapi/linux/spi/spidev.h 文件。下一节将解释应用程序驱动程序。 应用程序驱动程序的源文件以及设备树源可以在附加的.zip文件中找到。 主要 以下代码显示函数 main,其中使用“/dev/spidev0.0”初始化 SPI 驱动程序在 menuconfig 中启用 SPIdev 驱动程序并按照上一节所示重建内核后,它将列在/dev下(在目标根文件系统中)。然后,它读取“我是谁”注册表,仅用于健全性检查目的,初始化和校准传感器,并进入无限循环,只要有新数据准备好,它就会读取传感器。原始数据以 6 个字节返回,因此设法获取 X、Y 和 Z 值,最后将这些值转换为 G 的值,并等待按下某个键以继续读取以下值。 /****************************************************************************** * 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);
} 初始化驱动程序 spi_init函数为驱动程序“/dev/spidev0.0”打开一个文件它作为参数从 main() 传递,然后读取 SPI 配置参数仅用于提供信息。最后初始化 spi_ioc_transfer 类型的 struct xfer 。 /********************************* 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 SPI 通信从片选引脚的下降沿开始。通过传输 R/W 位的 1 来启动写入操作。然后,8 位寄存器地址 ADDR[7:0] 被编码在第一个和第二个序列化字节中。要写入的数据从第三个序列化字节开始。位的顺序如下: 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] SPI 通信以片选引脚的下降沿完成。 您可以在下面看到数组buf保存目标地址和要传输的数据,然后将xfer结构配置为指向buf作为传输缓冲区,并将数据的长度设置为 2 + 数据大小(前 2 个字节用于分成 2 个字节的目标地址)。最后通过ioctl命令开始传输。 /******************** 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;
} 下面是将值 0x3D 写入 CTRL_REG1 (0x2A) 的写入操作。 Read 类似地,通过传输 R/W 位的 0 来启动读取操作。然后,8 位寄存器地址 ADDR[7:0] 被编码在第一个和第二个序列化字节中。数据从 MISO 引脚读取(MSB 优先)。 在这种情况下,数组buf保存要读取的地址,而第三个字节只是要传输的虚拟字节。结构xfer保存传输缓冲区指针(在本例中为buf)和接收缓冲区(为AccData),还指定了要写入的命令的长度和要读取的数据的长度,最后使用ioctl调用执行读取命令。 /********************** 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;
} 下面的屏幕截图显示了读取操作,该操作从 WHO_AM_I 寄存器(0x0D)读取正确的值 0x6A。 构建并运行SPI应用程序驱动程序 为了构建应用程序,请将源文件和 Makefile(附件)保存在主机上的任何位置,然后转到保存它们的目录。然后使用以下命令的 Make 文件通过 Meta-toolchain 构建应用程序。 构建应用程序驱动程序 $ cd $ source /opt/fsl-imx-x11/ /environment-setup-armv7a-vfp-neon-poky-linux-gnueabi $ make 将会在同一文件夹中创建一个spi_test文件,将此文件复制到 /home/root 中的根文件系统分区上的 sdcard 中。最后,在启动目标时执行应用程序。您必须看到以下输出。 结束语 本文档总结了创建 SPI 应用程序驱动程序的步骤。正如您所看到的,需要重建设备树和内核,为此需要安装 metatoolchain。应用程序驱动采用SPI内核驱动,其主要功能如下: 初始化SPI驱动程序 Read Write 初始化传感器 校准传感器 轮询数据就绪函数 我想说应用程序驱动程序的核心是读写功能,它们配置 SPI 内核驱动程序并以传感器所需的格式向其传递数据。 有关驱动程序的具体详细信息,请参阅附件 .zip该文件包含应用程序驱动程序代码、构建的 Makefile 和设备树源。 我希望您发现本文档有用。 卡洛斯
記事全体を表示