读取ICM20602(二)GD32F470通过SPI读取ICM20602

目录

〇、写在前面

一、GD32F4系列的SPI总线简述

1.结构框图

2.相关标志位

3.工作流程

4.相关函数

4.1 spi_init

4.2 spi_i2s_flag_get

4.3 spi_i2s_data_transmit

4.4 spi_i2s_data_receive

二、代码实现

1.寄存器操作层的实现思路

1.1 单寄存器写入

1.2 单寄存器读取

1.3 多寄存器读取

2.寄存器操作层代码

2.1 单寄存器写入

2.2 单寄存器读取

2.3 多寄存器读取


〇、写在前面

前文链接:读取ICM20602(一)STM32通过SPI读取ICM20602

虽然副标题格式和前一篇文章一模一样,但我保证这篇真不是水货!

在这篇文章里,我会着重讲述我将ICM20602库移植至梁山派(主控GD32F470ZGT6)的过程与要点,以及过程中的各种坑,供有意进行驱动移植或更深入地理解SPI通讯协议的各位进行参考。

一、GD32F4系列的SPI总线简述

1.结构框图

截取自GD32F4的UserManual

2.相关标志位

GD32F4系列的UserManual在21.6节中对SPI相关标志位描述如下:

21.6.1.
状态标志位
· 发送缓冲区空标志位( TBE
当发送缓冲区为空时, TBE 置位。软件可以通过写 SPI_DATA 寄存器将下一个待发送数据写
入发送缓冲区。
· 接收缓冲区非空标志位(RBNE
当接收缓冲区非空时, RBNE 置位,表示此时接收到一个数据,并已存入到接收缓冲区中,软 件可以通过读 SPI_DATA 寄存器来读取此数据。
· SPI通信进行中标志位( TRANS
TRANS 位是用来指示当前传输是否正在进行或结束的状态标志位,它由内部硬件置位和清除, 无法通过软件控制。该标志位不会产生任何中断。
21.6.2.
错误标志
· 配置错误标志(CONFERR
在主机模式中, CONFERR 位是一个错误标志位。在硬件 NSS 模式中,如果 NSSDRV 没有
使能,当 NSS 被拉低时, CONFERR 位被置 1 。在软件 NSS 模式中,当 SWNSS 位为 0 时, CONFERR 位置 1 。当 CONFERR 位置 1 时, SPIEN 位和 MSTMOD 位由硬件清除, SPI 关 闭,设备强制进入从机模式。
CONFERR 位清零之前, SPIEN 位和 MSTMOD 位保持写保护,从机的 CONFERR 位不能 置 1 。在多主机配置中,设备可以在 CONFERR 位置 1 时进入从机模式,这意味着发生了系统控制的多主冲突。
· 接收过载错误(RXORERR
RBNE 位为 1 时,如果再有数据被接收, RXORERR 位将会置 1 。这说明,上一帧数据还
未被读出而新的数据已经接收了。接收缓冲区的内容不会被新接收的数据覆盖,所以新接收的数据丢失。
· 帧错误(FERR
TI 从机模式下,从机也要监视 NSS 信号,如果检测到错误的 NSS 信号,将会置位 FERR
标志位。例如, NSS 信号在一个字节的中间位发生翻转。
· CRC错误( CRCERR
CRCEN 位置 1 时, SPI_RCRC 寄存器中接收到的 CRC 值将会和紧随着最后一帧数据接收到的 CRC 值进行比较。当两者不同时, CRCERR 位将会置 1

可以发现,在GD32F4系列的SPI共有三个状态标志位,分别用于标志发送缓冲区是否有数据、接收缓冲区是否有数据、SPI是否在通讯中。关于这三个标志位的置位与复位条件,会在下一节中详细描述。

3.工作流程

发送流程
在完成初始化过程之后, SPI 模块使能并保持在空闲状态。在主机模式下,当软件写一个数据 到发送缓冲区时,发送过程开始。在从机模式下,当 SCK 引脚上的 SCK 信号开始翻转,且 NSS 引脚电平为低,发送过程开始。所以,在从机模式下,应用程序必须确保在数据发送开始 前,数据已经写入发送缓冲区中。 当 SPI 开始发送一个数据帧时,首先将这个数据帧从数据缓冲区加载到移位寄存器中,然后开始发送加载的数据。在数据帧的第一位发送之后,TBE (发送缓冲区空)位置 1 TBE 标志位置 1 ,说明发送缓冲区为空,此时如果需要发送更多数据,软件应该继续写 SPI_DATA 寄存器。 在主机模式下,若想要实现连续发送功能,那么在当前数据帧发送完成前,软件应该将下一个 数据写入 SPI_DATA 寄存器中。
接收流程
在最后一个采样时钟边沿之后,接收到的数据将从移位寄存器存入到接收缓冲区,且 RBNE (接收缓冲区非空)位置 1 。软件通过读 SPI_DATA 寄存器获得接收的数据,此操作会自动清除 RBNE 标志位。在 MRU MRB 模式中,为了接收下一个数据帧,硬件需要连续发送时钟信号,而在全双工主机模式(MFD )中,当发送缓冲区非空时,硬件才接收下一个数据帧。

以上内容节选自GD32F4系列的UserManual。由此我们可以得知,在全双工常规连接的SPI主机模式下,其工作流程为:

1.初始化完成后,SPI处于空闲状态,此时NSS引脚为高电平,TBE标志位为1,RBNE标志位为0;

2.当软件写一个数据到发送缓冲区时,发送过程开始,TBE置0;

3.将发送缓冲区的数据存入移位寄存器并开始发送,当数据的第一位发送后,TBE置1,此时可继续往缓冲区中写入数据,但在当前帧发送完成前缓冲区的数据不会发送;

4.在最后一个采样时钟沿后,将移位寄存器中的数据存入接收缓冲区,RBNE置1;

5.当软件读取接收缓冲区的数据后,RBNE置0,本帧通讯结束。

图示如下:

这里为什么没讲 TRANS 标志位的置位和复位条件呢?因为原文里真没有……原文里关于 TRANS标志位的置位和复位条件只有标志位介绍的一句话,因此我们只知道,TRANS 标志位会在通讯开始时置1,通讯完成后置0。

为什么要着重说这个呢?因为和HAL库不同,GD32的库是没有TransmitReceive这么好使的函数的,因此每次SPI数据交换时都需要我们去留意标志位的情况,并以此判断是否可以发送或接收数据。

4.相关函数

实现与ICM20602的SPI通讯,仅需spi_init、spi_i2s_flag_get、spi_i2s_data_transmit、spi_i2s_data_receive 四个函数即可。

4.1 spi_init

截取自GD32F4库函数的UserGuide

 其中结构体spi_parameter_struct成员参数如下:

截取自GD32F4库函数的UserGuide

 因为和STM32的配置相似,所以没啥好说的,按照前一篇文章所说进行配置即可。

4.2 spi_i2s_flag_get

截取自GD32F4库函数的UserGuide
截取自GD32F4库函数的UserGuide

 该函数的功能为获取对应SPI寄存器标志位状态,返回值为SET或RESET,其中SET为1,RESET为0。在SPI通讯过程中,我们需要重点关注的三个标志位分别为SPI_FLAG_TBE、SPI_FLAG_RBNE、SPI_FLAG_TRANS。

4.3 spi_i2s_data_transmit

截取自GD32F4库函数的UserGuide

 值得注意的是,该函数源码实现如下:

/*!
    \brief      SPI transmit data
    \param[in]  spi_periph: SPIx(x=0,1,2,3,4,5)
    \param[in]  data: 16-bit data
    \param[out] none
    \retval     none
*/
void spi_i2s_data_transmit(uint32_t spi_periph, uint16_t data)
{
    SPI_DATA(spi_periph) = (uint32_t)data;
}

因此,该函数的功能仅仅是将传入的数据写进SPI的发送缓冲区中,发送缓冲区是否为空、后续发送是否成功之类的问题是一概不管的,所以我们需要结合spi_i2s_flag_get函数来确定发送时机。

4.4 spi_i2s_data_receive

截取自GD32F4库函数的UserGuide

 该函数源码实现如下:

/*!
    \brief      SPI receive data
    \param[in]  spi_periph: SPIx(x=0,1,2,3,4,5)
    \param[out] none
    \retval     16-bit data
*/
uint16_t spi_i2s_data_receive(uint32_t spi_periph)
{
    return ((uint16_t)SPI_DATA(spi_periph));
}

同样的,该函数的功能仅仅是将SPI接收缓冲区中的数据读出,接收缓冲区是否有数据、缓冲区中的数据是否完整之类的问题同样是不管的。此外,GD32F4的UserManual中特别提到,“……而在全双工主机模式(MFD)中,当发送缓冲区非空时,硬件才接收下一个数据帧。因此,该函数的使用不仅得结合spi_i2s_flag_get函数来确定时机,还得保证在读取之前数据已经传输完成。

二、代码实现

本库构成思路如下:

 其中,SPI驱动层即各厂商的SPI函数库,不同单片机系列与设计厂商的SPI驱动层也各不相同;

寄存器操作层则负责通过各厂商的SPI函数库实现对ICM20602的单寄存器读取、单寄存器写入及多寄存器读取函数,也是将驱动移植至其它单片机平台时唯一需要实现的部分;

功能函数层则由寄存器操作实现对ICM20602的配置、读取等操作,本层建立在寄存器操作层上,仅由ICM20602的硬件决定,与寄存器操作层的实现方式无关。

1.寄存器操作层的实现思路

注:按照ICM20602时序图,在NSS引脚拉低后需要有一个持续时间不小于2ns的延时,因此在寄存器操作开始前会有读/写命令与地址的或运算充当延时。

1.1 单寄存器写入

由于不需要进行接收,寄存器写入的过程中仅考虑SPI_FLAG_TBE和SPI_FLAG_TRANS即可,基本流程如下:

1.2 单寄存器读取

在执行寄存器读取操作时,需要保证寄存器读取到的数据是本周期传输过来的数据,因此地址和读指令写入完成后需要复位RBNE标志位,保证其中的数据不会被误读。图示如下:

1.3 多寄存器读取

与单寄存器读取相似(其实可以直接使用多次单寄存器读取操作取代,只是速度会慢一点),图示如下:

2.寄存器操作层代码

2.1 单寄存器写入

/* 
* 功能  ICM20602写寄存器 
* 参数  addr	寄存器地址
*		dat		需要写入的数据
* 返回  无
*/
static void icm20602_writeReg(uint8_t addr, uint8_t dat)
{
	uint8_t cmd;
	
	ICM_NSS_SELECT;
	cmd = addr | REGISTER_WRITE; /* 注:此处的运算实质上起到了NSS引脚拉低后的2ns延时作用,因此不可移除 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		spi_i2s_data_receive(ICM_SPI);/* 读取读SPI_DATA寄存器中的数据以复位RBNE标志位 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
		spi_i2s_data_transmit(ICM_SPI, cmd);/* 发送地址与写指令 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */	
	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
		spi_i2s_data_transmit(ICM_SPI, dat);/* 发送需要写入寄存器的值 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TRANS))/* 等待数据传输完毕 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		spi_i2s_data_receive(ICM_SPI);/* 读取读SPI_DATA寄存器中的数据以复位RBNE标志位 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	ICM_NSS_RELEASE;
}

2.2 单寄存器读取

/* 
* 功能  ICM20602读寄存器 
* 参数  addr	寄存器地址
*		dat		读取得到的数据
* 返回  无
*/
static void icm20602_readReg(uint8_t addr, uint8_t *dat)
{
	uint8_t cmd;
	uint8_t res;
	
	ICM_NSS_SELECT;
	cmd = addr | REGISTER_READ; /* 注:此处的运算实质上起到了NSS引脚拉低后的2ns延时作用,因此不可移除 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		res = spi_i2s_data_receive(ICM_SPI);/* 读取读SPI_DATA寄存器中的数据以复位RBNE标志位 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
		spi_i2s_data_transmit(ICM_SPI, cmd);/* 发送地址与读指令 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TRANS));/* 等待数据传输完毕 */	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		spi_i2s_data_receive(ICM_SPI);/* 本周期(以缓冲区内数据发送完成为结束)内收到的数据不能使用 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */

	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
		spi_i2s_data_transmit(ICM_SPI, 0x00);/* 传输数据以进行接收 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TRANS));/* 等待数据传输完毕 */	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		res = spi_i2s_data_receive(ICM_SPI);/* 这个周期收到的才是传感器返回的数据 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	ICM_NSS_RELEASE;
	
	*dat = res;
}

2.3 多寄存器读取

/* 
* 功能  ICM20602读取多字节数据 
* 参数  addr	寄存器地址
*		dat		读取得到的数据
*		len		需要读取的数据长度
* 返回  无
*/
static void icm20602_readBytes(uint8_t addr, uint8_t *dat_array, uint8_t len)
{
	uint8_t cmd;
	uint8_t *dat_ptr = dat_array;
	uint8_t i;
	
	ICM_NSS_SELECT;
	cmd = addr | REGISTER_READ; /* 注:此处的运算实质上起到了NSS引脚拉低后的2ns延时作用,因此不可移除 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		spi_i2s_data_receive(ICM_SPI);/* 读取读SPI_DATA寄存器中的数据以复位RBNE标志位 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
		spi_i2s_data_transmit(ICM_SPI, cmd);/* 发送地址与读指令 */
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TRANS));/* 等待数据传输完毕 */	
	while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))
		spi_i2s_data_receive(ICM_SPI);/* 本周期(以缓冲区内数据发送完成为结束)内收到的数据不能使用 */
	while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
	
	for(i = 0; i < len; i++)
	{
		while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE))
			spi_i2s_data_transmit(ICM_SPI, *dat_ptr);/* 传输数据以进行接收 */
		while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TRANS));/* 等待数据传输完毕 */
		while(SET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_RBNE))		
			*dat_ptr = spi_i2s_data_receive(ICM_SPI);/* 这个周期收到的才是传感器返回的数据 */
		while(RESET == spi_i2s_flag_get(ICM_SPI, SPI_FLAG_TBE));/* 等待发送缓冲区清空 */
		dat_ptr++;
	}
	ICM_NSS_RELEASE;
}

至此,寄存器操作层已全部实现,然后就可以根据芯片手册的描述配置传感器的各项参数了。为避免本期文章太过冗长,完整版的代码我会在下一篇文章中开源。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 很高兴为您服务!STM32 HAL库的SPI DMA功能可帮助您在不使用CPU的情况下转移数据,从而节省CPU的资源。DMA可以自动地将数据从SPI口发送到内存,而无需CPU的参与。它可以提高系统的性能和数据传输速度。 ### 回答2: STM32 HAL库中的SPI DMA是指通过直接内存访问(DMA)来实现SPI数据传输的功能。 SPI (串行外设接口)是一种常用的串行通信协议,它可以连接MCU与外部设备进行通信。而DMA是一种直接内存访问技术,它可以在不经过CPU的干预下,直接将数据从一个地方传输到另一个地方。通过使用DMA进行SPI数据传输,可以提高效率和性能。 在STM32 HAL库中,使用SPI DMA可以通过以下步骤来实现SPI数据传输: 1. 配置SPI外设和DMA通道:首先要配置SPI外设的参数,例如设置SPI通信模式、数据大小、时钟极性和相位等。然后要配置DMA通道的参数,例如选择DMA传输方向、传输数据大小和MODE模式等。 2. 配置DMA传输缓冲区:为SPI和DMA设置传输缓冲区,这些缓冲区将用于存储传输的数据。 3. 启动DMA传输:使用HAL库中的函数来启动DMA传输。这将使DMA开始从传输缓冲区中读取数据并将其传输到SPI外设中。 4. 等待传输完成:使用HAL库中的函数来等待DMA传输完成。一旦传输完成,可以触发一个传输完成中断。 通过以上步骤,我们可以在STM32 HAL库中实现SPI DMA数据传输。这种方式可以使CPU更有效地利用时间来执行其他任务,提高系统的性能和效率。同时,SPI DMA也可以更好地支持大容量数据传输,减少了CPU的负载,提供了更好的实时性能。 ### 回答3: STM32 HAL库提供了一种简化SPI DMA(Direct Memory Access,直接内存存取)传输的方法。SPI是串行外设接口的一种通信协议,它能够同时传输和接收数据,而DMA是一种无需CPU参与的数据传输方法,可以提高数据传输效率和系统性能。 STM32 HAL库中的SPI DMA功能主要通过一系列API函数来实现。首先,我们需要初始化SPI外设的基本参数,包括通信模式(全双工、单工等)、时钟极性和相位配置、主从模式等。然后,开启DMA传输和中断以及SPI外设本身。接下来,通过调用HAL_SPI_Transmit_DMA和HAL_SPI_Receive_DMA函数来配置DMA传输缓冲区和传输长度,并启动传输。在传输完成后,通过检查相关中断标志位或回调函数来检测传输状态。 SPI DMA的优点在于减少了CPU的工作负担,使得CPU在数据传输期间可以处理其他任务,提高了系统的实时性和效率。同时,DMA传输还可以减少因CPU参与而产生的传输延迟,加快数据传输速度。此外,SPI DMA应用于具有高速数据传输需求的场景,如存储器读写、音频、图形显示等,能够更好地满足系统对快速、稳定数据传输的要求。 需要注意的是,在使用SPI DMA时,需要仔细考虑数据的传输顺序和互斥访问问题,以免造成数据冲突和错误。此外,DMA传输涉及到DMA缓冲区的管理和回调函数的设置,需要仔细调试和测试,以确保数据的正确传输和处理。 总之,STM32 HAL库提供了一种方便简化的SPI DMA传输方法,通过充分利用DMA传输的特点,可以提高系统性能和数据传输效率,适用于高速数据传输的应用场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值