ARM嵌入式学习(十七)--- IMX6ULL的SPI使用

张开发
2026/4/8 3:23:05 15 分钟阅读

分享文章

ARM嵌入式学习(十七)--- IMX6ULL的SPI使用
目录一、SPI基本概念1.组网方式2.SPI时序1CPOL2CPHA3.SPI读写数据二、SPI的配置注意1.ECSPIx_CONREG寄存器2.ECSPIx_CONFIGREG寄存器3.ECSPIx_PERIODREG寄存器4.ECSPIx_TXDATA / ECSPIx_RXDATA数据寄存器5.ECSPIx_STATREG寄存器 :注意三、ADXL345传感器的使用1.ADXL345传感器读写数据注意2.初始化ADXL345传感器3.获取ADXL345传感器三轴数据4.主函数四、总结一、SPI基本概念SPI 是英语 Serial Peripheral interface(串行外设接口)的缩写顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROMFLASH实时时钟AD 转换器还有数字信号处理器和数字信号解码器之间。SPI 是一种高速的全双工同步的串行通信总线并且在芯片的管脚上只占用四根线节约了芯片的管脚同时为 PCB 的布局上节省空间提供方便正是出于这种简单易用的特性现在越来越多的芯片集成了这种通信协议。1.组网方式标准SPI采用四线组网方式如下图CLK时钟信号线。与I2C类似的所有设备都都需要时钟信号线进行同步该信号由主机负责提供MOSI主出从入相当于主机的数据发送线MISO主入从出相当于主机的数据接收线CS片选信号与I2C不同SPI设备没有所谓的从机地址连接在总线上的设备通过CS片选信号选择。在同一时刻总线上只有一个外设被选择CS信号线通常是低电平有效的。以上的四线方式构成了SPI全双工串行同步通信方式。需要注意的是对于MOSI和MISO来说不是必须存在的可以只有MOSI或者只有MISO。当然这样的话就只能实现单工通信方式了这样的SPI成为三线SPI。至于到底采用三线方式还是四线方式取决于外设本身。例如OLCD只需要主机发送数据而不需要从机发送数据就可以使用三线SPI的连接方式。2.SPI时序SPI的数据传输的时序要理解SPI的通信时序。需要搞清楚SPI的两个重要概念。时钟极性(Clock Polarity 简称CPOL)和时钟相位(Clock Phase简称CPHA)。注意这两个概念都是跟时钟信号线相关的。1CPOL规定了时钟信号线在空闲时的状态CPOL0表示时钟信号线在空闲时为低电平CPOL1表示时钟信号线在空闲时为高电平2CPHA与I2C不同SPI的数据接收方并不是在时钟高或者低电平时采样的而是在时钟信号处于沿时至于是在上升沿还是下降沿取决于CPHA。CPHA0表示是在时钟信号变化的第一个沿时采样CPHA1表示是在时钟信号变化的第二个沿时采样因此不难理解这样的排列组合总共能够组合出四种方式具体使用哪种方式是由外设厂家规定的因此在使用SPI之前就不得不读懂外设的说明文档。一下就是四种方式的时序图无论哪种工作方式SPI这种串行通信方式都是MSB数据高位先行的3.SPI读写数据1发送数据和I2C类似大多数情况下与SPI外设的通信其实就是读写外设寄存器这些寄存器同样也有着各自的地址因此读写寄存器数据就成了如下所示首先是写寄存器2读取数据由于四线制SPI是全双工通信方式在发送一个字节数据的同时其实也就接收到了一个字节因此对于SPI来说发送其实也就是接收。IMX6ULL有一个发送缓冲寄存器同时也有一个接收缓冲寄存器发送完一个字节之后接收缓冲寄存器也会收到一个字节。因此读取外设寄存器时序如下二、SPI的配置I.MX6U 自带的 SPI 外设叫做ECSPI全称是 Enhanced Configurable Serial Peripheral Interface别看前面加了个“EC”就以为和标准 SPI 有啥不同的其实就是 SPI。ECSPI 有 64*32 个接收FIFO(RXFIFO)和 64*32 个发送 FIFO(TXFIFO)。I.MX6U 的 ECSPI可以工作在主模式或从模式我们使用主模式I.MX6U 有 4 个ECSPI每个 ECSPI 支持四个片选信号也就说如果你要使用 ECSPI 的硬件片选信号的话一个 ECSPI 可以支持 4 个外设。由于IMX6ULL-Mini开发板没有板载SPI外设我们只能通过外接方式连接一个SPI外设。本次实验我们将使用ADXL345三轴加速度传感器。下图可以看出ECSPI3的部分引脚和UART2的引脚之间是共用的。我们把ADXL345连接到ECSPI3的通道0.初始化这几个引脚IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0); IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0); IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0); IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0); IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10b0); IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10b0); IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10b0); IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0x10b0); GPIO1-GDIR | (1 20); GPIO1-DR | (1 20);注意这里查看ADXL345传感器手册看它是片选拉低读写数据还是片选拉高读写数据CS字母上面一个横线表示低电平有效我们接下来看一下 ECSPI 的几个重要的寄存器1.ECSPIx_CONREG寄存器这是 ECSPI 的控制寄存器•BURST_LENGTH(bit31:20)突发长度设置 SPI 的突发传输数据长度在一次 SPI 发送中最大可以发送 2^12bit 数据。可以设置 0X000~0XFFF分别对应 1~2^12bit。我们一般设置突发长度为一个字节也就是 8bitBURST_LENGTH7•CHANNEL_SELECT(bit19:18)SPI 通道选择一个 ECSPI 有四个硬件片选信号每个片选信号是一个硬件通道刚才查看了I.MX6U-ALPHA 开发板上的 ICM-20608 的片选信号接的是ECSPI3_SS0也就是 ECSPI3 的通道 0所以本实验设置为 0•DRCTL(bit17:16)SPI 的 SPI_RDY 信号控制位用于设置 SPI_RDY 信号为 0 的话不关心SPI_RDY 信号为 1 的话 SPI_RDY 信号为边沿触发为 2 的话 SPI_DRY 是电平触发。SPI_DRY 决定了如何启动一次数据传输手册第796页有SPI_RDY 的详细说明•PRE_DIVIDER(bit15:12)SPI 预分频ECSPI 时钟频率使用两步来完成分频此位设置的是第一步可设置 0~15分别对应 1~16 分频。从时钟树可以看出ECSPI时钟源是pll3_sw_clk 480MHz经过一个8分频的静态分频器实际工作频率是60MHz•POST_DIVIDER(bit11:8)SPI 分频值ECSPI 时钟频率的第二步分频设置分频值为2^POST_DIVIDER•CHANNEL_MODE(bit7:4)SPI 通道主/从模式设置CHANNEL_MODE[3:0]分别对应 SPI通道 3~0为 0 的话就是设置为从模式如果为 1 的话就是主模式。比如设置为 0X01 的话就是设置通道 0 为主模式•SMC(bit3)开始模式控制此位只能在主模式下起作用为 0 的话通过 XCH 位来开启 SPI突发访问为 1 的话只要向 TXFIFO 写入数据就开启 SPI 突发访问•XCH(bit2)此位只在主模式下起作用当 SMC 为 0 的话此位用来控制 SPI 突发访问的开启•HT(bit1)HT 模式使能位I.MX6ULL 不支持•EN(bit0)SPI 使能位为 0 的话关闭 SPI为 1 的话使能 SPI。base-CONREG 0; base-CONREG (0x7 20) | (14 12) | (1 8) | (1 4) | (1 3); 。。。。。。。。。。。。。。。。。 base-CONREG | (1 0);//配置好所有设置后再使能2.ECSPIx_CONFIGREG寄存器这个也是 ECSPI 的配置寄存器•HT_LENGTH(bit28:24)HT 模式下的消息长度设置I.MX6ULL 不支持•SCLK_CTL(bit23:20)设置 SCLK 信号线空闲状态电平SCLK_CTL[3:0]分别对应通道3~0为 0 的话 SCLK 空闲状态为低电平为 1 的话 SCLK 空闲状态为高电平•DATA_CTL(bit19:16)设置 DATA 信号线空闲状态电平DATA_CTL[3:0]分别对应通道3~0为 0 的话 DATA 空闲状态为高电平为 1 的话 DATA 空闲状态为低电平•SS_POL(bit15:12)设置 SPI 片选信号极性设置SS_POL[3:0]分别对应通道 3~0为 0 的话片选信号低电平有效为 1 的话片选信号高电平有效•SCLK_POL(bit7:4)SPI 时钟信号极性设置也就是 CPOLSCLK_POL[3:0]分别对应通道 3~0为 0 的话 SCLK 高电平有效(空闲的时候为低电平)为 1 的话 SCLK 低电平有效(空闲的时候为高电平)•SCLK_PHA(bit3:0)SPI时钟相位设置也就是CPHASCLK_PHA[3:0]分别对应通道3~0为 0 的话串行时钟的第一个跳变沿(上升沿或下降沿)采集数据为 1 的话串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。这里的时钟极性和相位需要查看我们使用的ADXL345传感器手册base-CONFIGREG (1 20) | (1 4) | (1 0);3.ECSPIx_PERIODREG寄存器•CSD_CTL(bit21:16)片选信号延时控制位用于设置片选信号和第一个 SPI 时钟信号之间的时间间隔范围为 0~63•CSRC(bit15)SPI 时钟源选择为 0 的话选择 SPI CLK 为 SPI 的时钟源为 1 的话选择32.768KHz 的晶振为 SPI 时钟源。我们一般选择 SPI CLK 作为 SPI 时钟源SPI CLK 的时钟频率为60MHz从在时钟树上对应的是ECSPI_CLK_ROOT该时钟将pll3_sw_clk经由静态8分频之后到达一个选通器由CSCDR2的ECSPI_CLK_SEL决定我们肯定是选择60MHz再经过CSCDR2的ECSPI_CLK_PODF进行分频我们配置为0选择SPI CLK。•SAMPLE_PERIO(bit14:0)采样周期寄存器可设置为 0~0X7FFF 分别对应 0~32767 个周期。4.ECSPIx_TXDATA / ECSPIx_RXDATA数据寄存器最后就是两个数据寄存器这两个寄存器都是 32位的如果要发送数据就向寄存器 ECSPIx_TXDATA 写入数据读取及存取 ECSPIx_RXDATA里面的数据就可以得到刚刚接收到的数据。使用例子要配合读写标志位使用 base-TXDATA data[i]; data[i] base-RXDATA;5.ECSPIx_STATREG寄存器 :这个是 ECSPI 的状态寄存器•TC(bit7)传输完成标志位为 0 表示正在传输为 1 表示传输完成•RO(bit6)RXFIFO 溢出标志位为 0 表示 RXFIFO 无溢出为 1 表示 RXFIFO 溢出•RF(bit5)RXFIFO 空标志位为 0 表示 RXFIFO 不为空为 1 表示 RXFIFO 为空•RDR(bit4)RXFIFO 数据请求标志位此位为 0 表示 RXFIFO 里面的数据不大于RX_THRESHOLD此位为 1 的话表示 RXFIFO 里面的数据大于 RX_THRESHOLD•RR(bit3)RXFIFO 就绪标志位为 0 的话 RXFIFO 没有数据为 1 的话表示 RXFIFO 中至少有一个字的数据。•TF(bit2)TXFIFO 满标志位为 0 的话表示 TXFIFO 不为满为 1 的话表示 TXFIFO 为满•TDR(bit1)TXFIFO 数据请求标志位为 0 表示 TXFIFO 中的数据大于 TX_THRESHOLD为 1 表示 TXFIFO 中的数据不大于 TX_THRESHOLD•TE(bit0)TXFIFO 空标志位为 0 表示 TXFIFO 中至少有一个字的数据为 1 表示 TXFIFO为空。我们使用第0位和第3位用于实现读写函数的判断标志位void spi_write(ECSPI_Type * base, unsigned char * data, unsigned char len) { int i 0; unsigned char invalid_data; for(i 0; i len; i) { while(!(base-STATREG (1 0))); base-TXDATA data[i]; while(!(base-STATREG (1 3))); invalid_data base-RXDATA; } } void spi_read(ECSPI_Type * base, unsigned char * data, unsigned char len) { int i 0; for(i 0; i len; i) { while(!(base-STATREG (1 0))); base-TXDATA 0xff; while(!(base-STATREG (1 3))); data[i] base-RXDATA; } }注意根据SPI原理啊实现的代码可以发现读数据和写数据实际上是一样的这里把这两个函数合并一下unsigned char spi_transfer(ECSPI_Type * base, unsigned char data) { unsigned char rxdata 0; while (!(base-STATREG (1 0))); base-TXDATA data; while (!(base-STATREG (1 3))); rxdata base-RXDATA; return rxdata; }三、ADXL345传感器的使用1.ADXL345传感器读写数据查看ADXL345的手册可以看到我们读写这个传感器的寄存器的时候如果高位是1则读高位是0则是写读写数据的代码为void adxl345_write_byte(unsigned char reg, unsigned char data) { GPIO1-DR ~(1 20); spi_transfer(ECSPI3, reg); spi_transfer(ECSPI3, data); GPIO1-DR | (1 20); } unsigned char adxl345_read_byte(unsigned char reg) { unsigned char data; GPIO1-DR ~(1 20); spi_transfer(ECSPI3, (reg | 0x80)); data spi_transfer(ECSPI3, 0xff); GPIO1-DR | (1 20); return data; }注意读写数据时要拉低片选信号再拉高2.初始化ADXL345传感器int adxl345_init(void) { adxl345_write_byte(0x31, 0x0B); adxl345_write_byte(0x2C, 0x0A); adxl345_write_byte(0x2D, 0x0B); unsigned char devid adxl345_read_byte(0x0); return devid; }这里我们不使用它的中断就不初始化了只初始化数据速率省电特性数据格式来简单的读一下它的数据.3.获取ADXL345传感器三轴数据代码为void adxl345_get_data(adxl345_t * data) { unsigned char xh, xl, yh, yl, zh, zl; xh adxl345_read_byte(0x32); xl adxl345_read_byte(0x33); yh adxl345_read_byte(0x34); yl adxl345_read_byte(0x35); zh adxl345_read_byte(0x36); zl adxl345_read_byte(0x37); >4.主函数#include MCIMX6Y2.h #include fsl_iomuxc.h #include core_ca7.h #include led.h #include beep.h #include key.h #include irq.h #include clk.h #include epit.h #include gpt.h #include uart.h #include stdio.h #include i2c.h #include adc.h #include lcd.h #include framebuffer.h #include spi.h #include adxl345.h void gpio1_io18_handler(void) { GPIO1-DR ^ (1 3); GPIO5-DR ^ (1 1); } void epit1_irq_handler() { GPIO1-DR ^ (1 3); GPIO5-DR ^ (1 1); } int main(void) { unsigned short data 0; system_irq_init(); clk_init(); led_init(); beep_init(); key_irq_init(gpio1_io18_handler); // epit1_init(epit1_irq_handler); gpt1_init(); uart_init(UART1); i2c_init(I2C1); int flag adc_init(ADC1); lcd_init(); lcd_info_t info; lcd_get_info(info); info.fore_color 0xff0000; info.back_color 0xffffff; fb_init(info); spi_init(ECSPI3); int ret adxl345_init(); char buf[20]; sprintf(buf, devid %x, ret); printf(all module init end\r\n); // lcd_clear(); lcd_fill(0, 0, 799, 479, 0xffffff); lcd_show_string(32*strlen(buf),400,300,20,32,buf); int i 0; while(1) { adxl345_t data; adxl345_get_data(data); unsigned char buf[100] {0}; sprintf(buf, x %d y %d z %d, data.x, data.y, data.z); lcd_show_string(32*strlen(buf),200,300,20,32,buf); delay_ms(100); } return 0; }连接好传感器把代码烧录到板子上移动传感器就可以看到LCD屏幕上数据的变化了。四、总结1.SPI的原理要搞懂片选、MISO、MOSI这些概念以及SPI的时序和读写数据的逻辑2.SPI比I2C通信快得多因为前者不需要应答。类似网络通信中的UDP和TCP通信3.传感器特点

更多文章