当前位置: 首页 > news >正文

ARM --- SPI通信协议简介,IMX6ULL使用SPI与ADXL345传感器进行数据通信

一. SPI 简介

串行 同步 全双工

1.1 简介

SPI全称是Serial Perripheral Interface,也就是串行外围设备接口。SPI 是Motorola公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线,SPI 时钟频率相比 I2C 要高很多,最高可以工作在几MHz,几十MHz甚至上百 MHz。SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要 4 根线,但是也可以使用三根线(单向传输)

1.2 SPI 四根线

①、CS/SS(Slave Select/Chip Select):这个是片选信号线,用于选择需要进行通信的从设 备。I2C 主机是通过发送从机设备地址来选择需要进行通信的从机设备的,SPI 主机不需要发送从机设备,直接将相应的从机设备片选信号拉低即可

②、SCLK(Serial Clock):串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟。

③、MOSI/SDO(Master Out Slave In/Serial Data Output):简称主出从入信号线,这根数据线只能用于主机向从机发送数据,也就是主机输出,从机输入。

④、MISO/SDI(Master In Slave Out/Serial Data Input):简称主入从出信号线,这根数据线只能用户从机向主机发送数据,也就是主机输入,从机输出。

SPI 通信都是由主机发起的,主机需要提供通信的时钟信号。主机通过 SPI 线连接多个从设备的结构如图所示:

1.3 SPI 的四种工作模式

SPI 有四种工作模式,通过串行时钟极性(CPOL)串行时钟相位(CPHA)的搭配来得到四种工作模式

串行时钟极性CPOL(Clock Polarity):

CPOL = 0代表串行时钟线空闲状态为低电平

CPOL = 1代表串行时钟线空闲状态为高电平

串行时钟相位CPHA(Clock Phase):

CPHA = 0代表在串行时钟线的第一个跳变沿进行数据采集

CPHA = 1代表在串行时钟线的第二个跳变沿进行数据采集

可知,通过组合,SPI 总共有四种工作模式:

CPOL CPHA
1 0:SCLK空闲为高电平,SPI下降沿采样
1 1:SCLK空闲为高电平,SPI上升沿采样
0 0:SCLK空闲为低电平,SPI上升沿采样
0 1:SCLK空闲为低电平,SPI下降沿采样

四种工作模式如下图所示:

CPOL = 0, CPHA = 1,这个工作模式的时序图如下图所示:

从图可以看出,SPI 的时序图很简单,不像 I2C 那样还要分为读时序和写时序,因为 SPI 是全双工的,所以读写时序可以一起完成。图中,CS 片选信号先拉低,选中要通信的从设备,然后通过MOSIMISO这两根数据线进行收发数据,MOSI 数据线发出了 0XD2 这个数据给从设备,同时从设备也通过 MISO 线给主设备返回了 0X66 这个数据

注意:

1、SPI协议与I2C不同的是,I2C协议规定了数据传输方式是高位先行(MSB),而SPI则是根据所选的从机不同,有不同的规定,但是,大多情况下(90%)都是高位先行(MSB)

2、数据的收发必须建立在片选线为低电平的前提下

1.4 SPI 与 I2C 的区别

1、I2C 固定两根线:SCL 与 SDA

SPI 三或者四根线:SCLK,CS,MOSI,MISO

2、I2C通过七位从机地址对从机进行寻址

SPI通过将对应的片选电平拉低拉选择与哪个从机进行通信

3、I2C 标准速率 100K,高速为400K

SPI 速率可达M,几十或者几百MHz

4、I2C:半双工 同步 串行

SPI:全双工 同步 串行

二. IMX6ULL 的 SPI 模块

2.1 IMX6ULL 的 ECSPI 简介

IMX6ULL 自带的 SPI 外设叫做 ECSPI,全称是 Enhanced Configurable Serial Peripheral Inte rface。ECSPI 有64*32 个接收 FIFO(RXFIFO)64*32 个发送 FIFO(TXFIFO)

IMX6ULL 有 4 个 ECSPI,每个 ECSPI 支持四个片选信号,ECSPI 特性如下:

①、全双工同步串行接口。

②、可配置的主/从模式

③、四个片选信号,支持多从机。

④、发送和接收都有一个 32x64 的 FIFO

⑤、片选信号 SS/CS,时钟信号 SCLK 极性可配置

⑥、支持 DMA。

2.2 ECSPI 时钟源的选择

如下图所示:时钟源来源于pll3_sw_clk并且经过了8 分频480/8 = 60 MHz,设置CSCDR2的ECSPI_CLK_SEL选择pll3作为时钟源

ECSPI_CLK_SEL0:选择pll3_sw_clk并且经过8 分频作为时钟源
1:选择 OSC 的 24MHz 作为时钟源

2.3 相关寄存器

2.3.1 ECSPIx_CONREG 控制寄存器

BURST_LENGTH

数据长度位

每次传输一字节的话就设置为7

CHANNEL_SELECT

通道选择位

00:通道0

01:通道1

10:通道2

11:通道3

PRE_DIVIDER

SPI 预分频

ECSPI 时钟频率使用两步来完成分频,此位设置的是第一步

可设置 0~15,分别对应 1~16 分频

注意:PRE DIVIDER和POST_DIVIDER这两个分配器决定了SPI最终的时钟·由于默认情况下SPI的时钟来源于pll3_sw_clk的8分频即480M/8=60MHz·如果需要SPI时钟信号为1MHz·则可将POST_DIVIDER设置为4分频·60/4=15MHz,再将15M进行15分频,15MHz/15=1MHz。

POST_DIVIDER

SPI 分频值

ECSPI 时钟频率的第二步分频设置

分频值为 2^ POST_DIVIDER

CHANNEL_MODE

SPI 通道主/从模式设置

CHANNEL_MODE[3:0]分别对应 SP I 通道 3~0:

为 0 的话就是设置为从模式

为 1 的话就是主模式

比如设置为 0X01 的话就是设置通道 0 为主模式

SMC

开始模式控制,此位只能在主模式下起作用

为 0 的话通过 XCH 位来开启 SPI 突发访问,

为 1 的话只要向 TXFIFO 写入数据就开启 SPI 突发访问

EN

SPI 使能位

为 0 的话关闭 SPI

为 1 的话使能 SPI

注意:必须在操作其他寄存器前设置这一位

2.3.2 ECSPIx_CONFIGREG 配置寄存器

SCLK_CTL

设置 SCLK 信号线空闲状态电平

SCLK_CTL[3:0]分别对应通道 3 ~0

为 0 的话 SCLK 空闲状态为低电平

为 1 的话 SCLK 空闲状态为高电平

SS_POL

设置 SPI 片选信号极性设置

SS_POL[3:0]分别对应通道 3~0

为 0 的 话片选信号低电平有效

为 1 的话片选信号高电平有效

SCLK_POL

SPI 时钟信号极性设置,也就是 CPOL

SCLK_POL[3:0]分别对应通 道 3~0

为 0 的话 SCLK 高电平有效(空闲的时候为低电平)

为 1 的话 SCLK 低电平有效(空闲 的时候为高电平)

注意:下面这两位要根据从机设备的要求来配置,

eg:和ADXL345进行通信的话,配置:CPOL = 1, CPHA = 1

SCLK_PHA

SPI 时钟相位设置,也就是 CPHA

SCLK_PHA[3:0]分别对应通道 3 ~0

为 0 的话串行时钟的第一个跳变沿(上升沿或下降沿)采集数据

为 1 的话串行时钟的第二 个跳变沿(上升沿或下降沿)采集数据

2.3.3 ECSPIx_STATREG 状态寄存器

RR

RXFIFO 就绪标志位

为 0 的话 RXFIFO 没有数据

为 1 的话表示 RXFIFO 中 至少有一个字的数据

注意:这一位判断是否接收到了数据

while( (ECSPI3->STATREG & (1 << 3)) == 0 );

TE

TXFIFO 空标志位

为 0 表示 TXFIFO 中至少有一个字的数据

为 1 表示 TXFI FO 为空

注意:这一位用于判断数据是否发送完毕

while( (ECSPI3->STATREG & (1 << 0)) == 0 );

三. IMX6ULL 使用 SPI 与ADXL345 通信

3.1 ADXL345 简介

ADXL345:三轴数字加速度计

3.1.1 主要参数

工作电压:2 v - 3.6 v

量程:±2g / ±4g / ±8g / ±16g

分辨率:±16g 时最高可达13bit

灵敏度:32 / (2^13 - 1) = 32/8191 = 0.0039 = 3.9mg

3.1.2 传感器读/写时序

1、写时序

可以看到:

1、在写入传感器时,先向其发送了一字节的寄存器地址,并且寄存器地址的最高位是 0 代表着写传感器,同理,最高位是 1 则代表读传感器

2、也可以看到,这款传感器的 SPI 时序,在时钟线空闲时呈高电平,并且是在时钟线的第二个边沿对数据进行采样,这些都要在 SPI 初始化函数中要进行相应的配置

2、读时序

3.1.3 主要配置寄存器

1、ID 寄存器:0x00

存放器件 ID,固定为:0xE5

2、输出速率设置寄存器 0x2C

3、休眠设置寄存器:0x2D

4、中断设置寄存器:0x2E

设置为禁止中断

5、分辨率设置寄存器

设置为满分辨率:13位

6、数据传感器

低字节数据存低地址,高字节数据存高地址

示例代码:

//原始数据 // x轴数据 ret.x = adxl345_read(0x32); ret.x |= adxl345_read(0x33) << 8; // y轴数据 ret.y = adxl345_read(0x34); ret.y |= adxl345_read(0x35) << 8; // z轴数据 ret.z = adxl345_read(0x36); ret.z |= adxl345_read(0x37) << 8;

3.2 SPI 初始化函数

这里使用 SPI3

注意:

1、将相应的引脚设置为相应的复用模式

2、在设置引脚的电气属性时注意:由于 SPI 是高速通信协议,所以需要设置高速压摆率,其余保持默认不变即可

3、在设置寄存器 CONREG 的 EN 位时,必须在操作其他寄存器前设置这一位,故这一步应该放在最前面

示例代码:

//初始化 SPI3 void spi3_init(void) { //1、引脚复用 IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0); //SPI3 片选线 IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0); //SPI3 时钟线 IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0); //SPI3 的MOSI IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0); //SPI3 的MISO //2、引脚电气属性 //注意这里要将 SRE 位置1,因为SPI是高速通信,需要设置高速压摆率,其他不变 IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10B1); IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10B1); //3、设置片选,GPIO1_IO20,低电平片选,故通信前,先设置为高电平 GPIO1->GDIR |= (1 << 20); GPIO1->DR |= (1 << 20); //4、配置相关寄存器 //控制寄存器 ECSPI3->CONREG = 0; ECSPI3->CONREG |= (1 << 0); //使能 SPI ECSPI3->CONREG |= (1 << 3); //向TXFIFO中写入数据立即启动一次通信 ECSPI3->CONREG |= (1 << 4); //将通道0设置为主机模式 ECSPI3->CONREG |= (2 << 8); //将Post Divider设置为4分频 ECSPI3->CONREG |= (0x0E << 12); //将Pre Divider设置为15分频 ECSPI3->CONREG |= (7 << 20); //数据长度为8bit //ECSPI3->CONREG |= (1 << 0); //使能 SPI //配置寄存器 ECSPI3->CONFIGREG = 0; ECSPI3->CONFIGREG |= (1 << 0); //设置通道0时钟相位为1,在第二个跳变沿进行采样 ECSPI3->CONFIGREG |= (1 << 4); //设置通道0时钟极性,时钟线空闲时为高电平 ECSPI3->CONFIGREG |= (1 << 20); //设置不工作时,时钟线为高电平 //使能 SPI,必须在操作其他寄存器前设置这一位 //ECSPI3->CONREG |= (1 << 0); }

3.3 SPI 读写函数

SPI是全双工移位通信:主从两侧各有一个移位寄存器。每个时钟沿到来时,主机通过 MOSI 往外“移出”1位,同时从 MISO “移入”1位。也就是——每“写”一位的同时必然“读”到一位

写即是读:往总线“写”一个字节(移出8位),同一过程里也会“读”回从机在MISO上的8位(可能是无效/占位字节)。

读即是写:从从机“读”数据,必须提供时钟,所以主机必须同时“写”出一个占位字节(通常是0xFF或0x00),用这些时钟把从机的数据“移入”。

示例代码:

使用标志位来判断读写是否完成

//SPI3 读写函数---写即是读,读即是写 unsigned int spi3_read_write(unsigned int data) { while( (ECSPI3->STATREG & (1 << 0)) == 0 ); ECSPI3->TXDATA = data; while( (ECSPI3->STATREG & (1 << 3)) == 0 ); return (ECSPI3->RXDATA); }

3.4 ADXL345 的读写函数

注意:

1、读传感器时,所发送的寄存器地址的最高位必须是 1

spi3_read_write(reg_addr | 0x80);

2、读传感器时,必须发送一字节的占位数据

3、读写前通过拉低片选线来进行选择传感器

示例代码:

//ADXL345 写函数 void adxl345_write(unsigned char reg_addr, unsigned char data) { //1、拉低片选 GPIO1->DR &= ~(1 << 20); //2、发送寄存器地址 spi3_read_write(reg_addr); //3、发送数据 spi3_read_write(data); //4、拉高片选 GPIO1->DR |= (1 << 20); } //ADXL345 读函数 unsigned char adxl345_read(unsigned char reg_addr) { unsigned char ret; //1、拉低片选 GPIO1->DR &= ~(1 << 20); //2、发送寄存器地址,读时,地址最高位是1 spi3_read_write(reg_addr | 0x80); //3、发送一个占位数据 ret = spi3_read_write(0xFF); //4、拉高片选 GPIO1->DR |= (1 << 20); return ret; }

3.5 ADXL345 初始化函数

前面介绍了 ADXL345 的一些寄存器,初始化传感器就是对这些寄存器进行配置

示例代码:

//ADXL345 初始化 void adxl345_init() { // 关闭中断 adxl345_write(0x2E, 0x08); // 满分辨率 13位 adxl345_write(0x31, 0x0B); // 速率 25HZ adxl345_write(0x2C, 0x08); // 工作模式 adxl345_write(0x2D, 0x0B); }

3.6 读取传感器数据函数

注意:在转换原始数据时需要乘 0.0039

示例代码:

//结构体定义 typedef struct ADXL345_Data { short x; short y; short z; float x_g; float y_g; float z_g; }ADXL345_Data_t; //读取原始数据 ADXL345_Data_t adxl345_get_vlalue(void) { ADXL345_Data_t ret; //原始数据 // x轴数据 ret.x = adxl345_read(0x32); ret.x |= adxl345_read(0x33) << 8; // y轴数据 ret.y = adxl345_read(0x34); ret.y |= adxl345_read(0x35) << 8; // z轴数据 ret.z = adxl345_read(0x36); ret.z |= adxl345_read(0x37) << 8; //转换后数据 ret.x_g = ret.x * 0.0039; ret.y_g = ret.y * 0.0039; ret.z_g = ret.z * 0.0039; return ret; }

主函数调用:

//初始化 SPI3 spi3_init(); //初始化 ADXL345 adxl345_init(); ADXL345_Data_t ret = { 0 }; unsigned char id = 0; int i, j, k; while(1) { id = adxl345_read(0); ret = adxl345_get_vlalue(); printf("ADXL345_ID is %#x\n",id); printf("x:%d y:%d z:%d\n", ret.x, ret.y, ret.z); i = ret.x_g * 10; if(i < 0) { i *= -1; printf("x_accel:-%d.%d ",i / 10, i % 10); } else { printf("x_accel:%d.%d ",i / 10, i % 10); } j = ret.y_g * 10; if(j < 0) { j *= -1; printf("y_accel:-%d.%d ",j / 10, j % 10); } else { printf("y_accel:%d.%d ",j / 10, j % 10); } k = ret.z_g * 10; if(k < 0) { k *= -1; printf("z_accel:-%d.%d\n",k / 10, k % 10); } else { printf("z_accel:%d.%d\n",k / 10, k % 10); } delay_ms(1000); } return 0;

结果:

http://www.jsqmd.com/news/1289172/

相关文章:

  • 2026广州代理记账机构靠谱合规排行|中小微企业服务商甄选权威指南+FAQ问答 - 互联网科技品牌测评
  • 2026年常州钟楼区系统门窗实力公司解析 - 优企名品
  • 2026年制造业节能型大型风扇源头厂家:韵蓝环境设备(安徽)有限公司 - 优企名品
  • PDF导航革命:3分钟为你的PDF文档添加智能书签目录
  • 3步搞定:如何轻松从Steam游戏页面批量提取完整数据
  • 猫抓插件:三步搞定网页资源下载,开启媒体嗅探新体验!
  • 合肥南亚理工学校|电话是多少?逐个拆解班型的适合人群 - hflgzz
  • 中小型企业财务软件选型效果实测指南
  • 精选!2026十大企业AI知识库管理系统开发服务商汇总
  • 告别熬夜做PPT✨Paperxie隐藏王牌!一键生成满分学术答辩PPT
  • AI预测“被告人是否上诉”准确率达89.3%,但92%律所尚未部署——这3个合规接入接口正在关闭
  • 暑期学习:自学java第十四天
  • 2026成都区别墅地下室漏水维修服务商推荐 10 家:正规实力机构盘点,选型避坑指南 - 鼎万建筑修缮
  • 终极Win11系统优化指南:4步深度定制你的Windows 11性能与隐私
  • 2026 天津南开区卫生间漏水维修机构大盘点:7 家正规靠谱服务商实力解析,附选型避坑全指南及注意事项 - 鼎万建筑修缮
  • 企业AI搭建:从零到落地的关键步骤
  • NBM5100A与PIC32MX675F512L的低功耗物联网电源管理方案
  • MarkdownTextView扩展开发实战:轻松实现自定义语法高亮
  • 推荐靠谱的环境试验箱销售厂家:首发 - 品牌推广大师
  • Steam游戏DLC解锁终极指南:SmokeAPI完整使用教程
  • 潍坊正规鸢都充电程序售卖租用
  • 突破性Windows网络性能测试方案:iperf3完整指南与下载安装
  • 2026年三轮车托运价格表:整车寄运费多少钱?不拆电池怎么算? - 快递物流资讯
  • 2026北京二手翡翠回收,如何避免被低价收购 - 一日一测评
  • 2026 玄奘路敦煌戈壁徒步|零基础新手完整筹备避坑指南
  • Xshell配色方案终极指南:250+主题打造高效开发环境
  • 如何用OCR4all处理早期印刷书籍?LAREX布局分析工具实战指南
  • 2026年07月 制造业过滤耗材领域:广东佳美滤清器有限公司的体系化能力剖析 - 优企名品
  • 2026南昌红谷滩管道疏通避坑指南 快速上门不踩雷 - 余生黄金回收
  • Xshell配色方案终极指南:免费获取250+专业终端主题