关于ADXL345三轴加速度模块网上有很多都是关于I2C协议的,关于SPI的很少且对于新手没多少参考价值。于是我拿到模块想用SPI协议来读取信息,我们先来看一下这个模块。
这个模块一共有10个引脚,在连开发板时,需要用到6个引脚(包括3V3 和 GND),关于引脚的连线我们先看一下官方提供的资料手册。
在一开始,模块默认的SPI协议就是4线,根据手册,我们进行实物连接。
根据实物图进行连接
PB12---CS <--------------------> CS
PB13---SCK <--------------------> SCL
PB14---MISO <--------------------> SDO
PB15---MOSI <--------------------> SDA
注意不要接反了,3V3,GDN正常接3V3/5V 和 GND就行
我们这里要仔细阅读ADXL345的手册关于SPI的那一章
我们先来分析这段话
大体意思就是
SPI通信有两种接线方式:SPI是一种常见的通信协议,用于微控制器和传感器之间的数据交换。SPI可以以3线或4线模式工作。3线模式指的是只使用时钟线(SCLK)、数据线(SDI/SDO)和芯片选择线(CS)。4线模式则额外增加了一个数据线(SDO/SDI),专门用于从传感器到微控制器的数据传输。
通过寄存器设置通信模式:ADXL345内部有一个叫做
DATA_FORMAT
的寄存器,地址是0x31
。通过修改这个寄存器里的一个位(位D6),可以选择是使用3线还是4线SPI模式。如果把位D6清零,就表示使用4线模式;如果把位D6设为1,则表示使用3线模式。通信速度和时序:SPI通信的速度由时钟速度决定。这里提到,如果负载电容不超过100皮法拉(pF),SPI的最大时钟速度可以达到5MHz。时钟极性(CPOL)和时钟相位(CPHA)的设置决定了数据在时钟的哪个边缘被采样。这里提到的时钟极性为1、时钟相位为1,意味着数据在时钟的上升沿被采样。
CS引脚的处理:在SPI通信开始之前,需要确保CS(芯片选择)引脚处于高电平。这样做是为了确保在设置好时钟极性和相位之后,传感器处于正确的初始状态。
3线SPI的特别建议:如果你使用的是3线SPI模式,建议将SDO引脚通过一个10千欧姆(kΩ)的电阻下拉到地,或者直接接到VDD I/O(电源电压)。这是为了确保在通信时,SDO引脚的电平状态是确定的
大体意思就是
CS线的作用:CS是“Chip Select”的缩写,意为“芯片选择”。在SPI通信中,CS线用于告诉SPI从设备(在这个例子中是ADXL345传感器)“嘿,现在轮到你了,我们开始通信吧”。这根线在开始传输数据之前要设置为低电平,告诉从设备“现在开始传输数据”;传输结束之后,CS线要设置回高电平,表示“传输结束,你可以休息了”。
SCLK线的控制:SCLK代表“Serial Clock”,即串行时钟。这个时钟信号是由SPI主设备(比如STM32微控制器)提供的,它用来同步数据的发送和接收。当没有数据传输发生时,SCLK保持在高电平状态,等待下一次数据传输的开始。
SDI和SDO线:SDI是“Serial Data Input”的缩写,意为串行数据输入;SDO是“Serial Data Output”的缩写,意为串行数据输出。在SPI通信中,SDI线用于从主设备向从设备发送数据,而SDO线则用于从从设备向主设备发送数据。
数据更新和采样:在SPI通信中,数据在SCLK的下降沿(即时钟信号从高变低的时刻)更新,这意味着在这一刻,主设备和从设备会改变它们发送的数据。而在SCLK的上升沿(即时钟信号从低变高的时刻),主设备会采样从设备发送过来的数据。
多字节传输:如果你需要一次性发送或接收多个数据字节,就需要使用多字节位(MB)。在第一个字节传输之后,接下来的每个8个时钟脉冲会使得ADXL345自动切换到下一个寄存器进行读取或写入,无需额外的命令。
传输结束和CS失效:一旦停止发送时钟脉冲,数据传输就会停止,这时CS信号也就失效,表示当前的通信已经结束。
访问不同的寄存器:如果你需要读取或写入ADXL345中不连续的寄存器,那么在两次传输之间,CS信号必须先失效,然后再激活,以便开始新的传输。
大体意思就是
时序图的作用:时序图是一种图表,用来展示在SPI通信中,各种信号(比如CS、SCLK、SDI、SDO)随时间变化的状态。图38是针对3线式SPI的读取或写入操作的时序图,而图36和图37则是4线式SPI的读取和写入操作的时序图。这些图帮助我们理解在数据传输过程中,信号应该如何正确地变化。
逻辑阈值和时序参数:在SPI通信中,有一些关键的参数需要遵守,比如信号的高低电平阈值,以及信号变化的时间点。这些参数确保了数据能够正确无误地被发送和接收。表9和表10中列出了必须满足的逻辑阈值和时序参数,以保证SPI通信的正确性。
通信速率与输出数据速率(ODR)的匹配:SPI通信速率(也就是SPI时钟的速度)和输出数据速率(ODR,即传感器输出数据的频率)需要相互匹配。如果SPI通信速率很快(2MHz或更高),那么推荐使用较高的ODR,比如3200Hz或1600Hz。如果通信速率慢一些(至少400kHz),那么推荐使用800Hz的ODR。这样做是为了确保数据能够被正确地采样,不会因为速率不匹配而丢失数据或产生噪声。
速率设置不当的后果:如果你设置的ODR高于推荐的值,那么可能会因为SPI通信速率跟不上而导致数据采样出现问题,比如数据丢失或额外噪声。这就好比一个人试图在很短的时间内读完一本书,结果可能会漏掉一些内容或者读错字
再来看时序图
总结: 1.相位和极性都配置为1。
2.在对寄存器进行读时,地址高位为1;对寄存器进行写时地址高位为0。
3.读取多字节时,次高位也要为1。
啥意思呢?举个例子,假如一个寄存器地址为0X1F如果要读取这个寄存器里面的内容,在往DR寄存器写入指令时,需要将这个地址 或上 0X80,也就是 0X1F | 0X80 ,后面还会实际操作帮助理解。
重点:如果想要获取ADXL345的数据,那么每次就需要发送一个数据来完成数据的交换。不懂的需要自己去复习这相关的知识,这里不再赘述。
代码编写
先看SPI_ADXL345_Init() 初始化
void SPI_ADXL345_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
/* 使能 GPIO 时钟 PB13 PB14 PB15 PB12*/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
/*spi 时钟使能*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
/*配置引脚:SCK MISO MOSI PB13 PB14 PB15*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF; //引脚复用
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*配置引脚:CS PB12*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*设置引脚复用*/
GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_SPI2);
/* FLASH_SPI 模式配置 */
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//通讯方向双线全双工
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;//主机模式
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//数据帧大小是8位
/* 设置CPOL=1 CPHA=1 */
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;//片选信号由软件触发,
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;//时钟分频,
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;//高位先行
SPI_Init(SPI2,&SPI_InitStructure);
/*使能FLASH_SPI*/
SPI_Cmd(SPI2,ENABLE);
/*这里不用管,我是为了测试 */
GPIO_SetBits(GPIOB,GPIO_Pin_12);
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
}
需要注意的是,时钟分频从256开始调起,频率不宜太高
先看SPI_ADXL345_SendReadByte()
uint8_t SPI_ADXL345_SendReadByte(uint8_t byte)
{
SPITimeOut = 0x1000;
/*等待发送缓冲区为空,TXE标志为1,则说明发送缓冲区为空,可以写数据*/
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)
{
/*设置一个等待超时动作,如果一直等待就返回错误值*/
if((SPITimeOut--)==0)
/*编写一个回调函数,用作输出错误信息*/
return SPI_TIMEOUT_USERCALLBACK(0); //这里自己改成普通的printf()就行
}
/*写缓冲区,把要发送的数据写入发送缓冲区*/
SPI_I2S_SendData(SPI2,byte);
SPITimeOut = 0x1000;
/*等待接收缓冲区为非空,RXNE标志为1时,表示接收缓冲区接收到了新数据,可以去读*/
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET)
{
if((SPITimeOut--)==0)
/*编写一个回调函数,用作输出错误信息*/
return SPI_TIMEOUT_USERCALLBACK(1);//这里自己改成普通的printf()就行
}
return SPI_I2S_ReceiveData(SPI2);
}
查看ADXL345的手册,我可以看到相关寄存器
可以看到,发送命令0X00,就能得到ADXL345设备的ID号,验证模块是正常的并且还能验证通信成功。
那么我们是不是只要代码这么写呢:SPI_Flash_SendReadByte(0x00);
回到我们之前那句话:发送指令,地址最高位为 1 代表读,而我们想要读出设备ID,那么就需要将这个 地址 0X00 | 0X80 ----》 1000 0000,其实:后五位代表地址,最高位代表读还是写,次高位代表读取多个字节。所以 获取 ADXL345 的设备ID代码如下:
uint32_t SPI_ADXL345_ReadID(void)
{
uint32_t temp = 0;
/*开始通讯,CS低电平*/
GPIO_ResetBits(GPIOB,GPIO_Pin_12);
SPI_Flash_SendReadByte(0x80);
temp = SPI_Flash_SendReadByte(0xFF); //这里0XFF只是为了交换数据读出ID
/*结束通讯,CS高电平*/
GPIO_SetBits(GPIOB,GPIO_Pin_12);
return temp;
}
在main函数中初始化并调用这个函数,结果却发现 ,设备ID读不出来总是 0,反正就是读不出来真正的ID 0XE5。这是为什么呢,相信或许很多人都卡在了这一步。
浪费了我大半天的调试,和验证时序频率,调代码,查资料,终于发现
在模块中有个R4电阻,而ADXL345模块默认是关闭SPI协议的。解决办法就是需要手动将ADXL345 模块上的R4电阻给扣掉。
直接动手开干!
再次测试代码
成功读取设备ID号 E5,成功!后续就可以进一步读取三轴加速度信息,实现自己想要的功能啦!
官方的ADXL345手册网上很多,大家一定仔细查阅。关于后续的配置我会看情况继续更新。