STM32G474单片机开发入门(十四)SPI总线详解及NRF2401模块实战
文章目录
- 一.概要
- 二.SPI总线基本概念
- 1.SPI总线内部框图
- 2.SPI总体特征
- 3.SPI通讯时序
- 三.NRF24L01模块介绍
- 1.NRF24L01模块基本特点
- 2.NRF24L01模块引脚定义
- 3.单片机与NRF24L01模块连接
- 4.单片机与NRF24L01模块通讯协议
- 四.单片机与NRF24L01模块通讯实验
- 1. 硬件准备
- 2.创建CubeMX工程
- 3.实验结果
- 五.小结
一.概要
SPI总线是由Motorola公司提出,是一种三线同步接口:同步信号、输入信号和输出信号。
另外每个扩展芯片还需要一根片选线,主器件通过片选线选通与其通信的从器件。
SPI是全双工的,即数据的发送和接收可同时进行。如果仅对从器件写数据,主器件可以丢弃同时读入的数据;反之,如果仅读数据,可以在命令字节后,写入任意数据。数据传送以字节为单位,并采用高位在前的格式。
二.SPI总线基本概念
SPI总线一般由四个信号组成。
MISO:主设备输入/从设备输出管脚。该管脚在从模式下发送数据,在主模式下接收数据。
MOSI:主设备输出/从设备输入管脚。该管脚在主模式下发送数据,在从模式下接收数据。
SCK:串口时钟,作为主设备的输出,从设备的输入
/SS:从设备选择,低电平有效。这是一个可选的管脚,用来选择从设备。它的功能是用来作为“片选管脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的/SS管脚可以由主设备当作一个标准的IO来驱动。/SS管脚作为输出管脚,并在SPI设置为主模式时拉低;从机的/SS管脚连接到主设备/SS管脚的SPI设备,会检测到低电平,如果它们被设置为/SS硬件模式,就会自动进入从设备状态
1.SPI总线内部框图
一个主机跟一个从机典型应用连接如下图:
MOSI脚相互连接,MISO脚相互连接。这样,数据在主和从之间串行地传输(MSB位在前)。 通信总是由主设备发起。主设备通过MOSI脚把数据发送给从设备,从设备通过MISO引脚回传数据。这意味全双工通信的数据输出和数据输入是用同一个时钟信号同步的,时钟信号由主设备通过SCK脚提供。
2.SPI总体特征
● 3线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8或16位传输帧格式选择
● 主或从操作
● 支持多主模式
● 8个主模式波特率预分频系数(最大为fPCLK/2)
● 从模式频率 (最大为fPCLK/2)
● 主模式和从模式的快速通信
● 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变
● 可编程的时钟极性和相位
● 可编程的数据顺序,MSB在前或LSB在前
● 可触发中断的专用发送和接收标志
● SPI总线忙状态标志
● 支持可靠通信的硬件CRC
3.SPI通讯时序
数据传输的时钟依靠主处理器的时钟脉冲。时钟的设置是根据时钟极性(CPOL)和时钟相位(CPHA)两个参数来决定的。
主和从必须配置成相同的时序模式。
CPOL 0:SCK在空闲状态保持低电平。
CPOL 1:SCK在空闲状态保持高电平。
CPHA 0:SCK的第1个(奇数)边沿进行数据位采样接收,数据在第2个时钟边沿发送数据。
CPHA 1:SCK的第2个(偶数)边沿进行数据位采样接收,数据在第1个时钟边沿发送数据。
根据CPOL和CPHA的不同组合,可以有四种不同的时钟极性和相位模式,分别为:
模式0:CPOL=0,CPHA=0
模式1:CPOL=0,CPHA=1
模式2:CPOL=1,CPHA=0
模式3:CPOL=1,CPHA=1
目前最常用的是CPOL 0,CPHA 0,就是空闲时SCK时钟为低电平,采样时刻为第1个边沿即上升沿。
总线空闲时:
SCK信号强制为低,SS强制为高,MOSI/MISO管脚处于高阻状态。
在传输数据时:
SS被拉低,从设备选中,主设备开始控制SPI时钟,1/2个SCK周期后,主机发送数据到MOSI管脚,从机发送数据到MISO管脚。此时数据在SCK信号的上升沿被捕获,保持到SCK的下降沿。
在发送单个帧的时候,当数据帧的所有位发送完成后,最后一个数据位被捕获的一个SCK周期后,SS返回高电平。
程序中,我们一般调用库函数HAL_SPI_TransmitReceive,就能实现SPI通讯:
/*** @brief Transmit and Receive an amount of data in blocking mode.* @param hspi pointer to a SPI_HandleTypeDef structure that contains* the configuration information for SPI module.* @param pTxData pointer to transmission data buffer* @param pRxData pointer to reception data buffer* @param Size amount of data to be sent and received* @param Timeout Timeout duration* @retval HAL status*/
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,uint32_t Timeout)HAL_SPI_TransmitReceive(&hspi1,&TxData, &RxData, 1,100)//里面的参数分别是:SPI号,发送的数组,接收的数组,数据长度,超时时间(单位ms)
三.NRF24L01模块介绍
1.NRF24L01模块基本特点
NRF24L01模块是一款基于2.4GHz ISM频段的无线收发模块,传输距离根据速度不同有所差别:
15米:2Mbps速率下,无遮挡环境约15米。
30米:1Mbps速率下,无遮挡环境约30米。
50米:250kbps速率下,无遮挡环境约50米 。
其他基本特点如下:
2.NRF24L01模块引脚定义
3.单片机与NRF24L01模块连接
单片机与NRF24L01模块连接,主要是连接SPI相关的引脚,再加上CE等控制引脚。
4.单片机与NRF24L01模块通讯协议
单片机与NRF24L01模块采用SPI通讯方式,由下图可知CPHA=0,CPOL=0。
读寄存器指令
读配置寄存器时使用指令0x00(或宏定义为READ_REG),后跟5位寄存器地址(AAAAA)。例如读取CONFIG寄存器(地址0x00)时,发送0x00即可返回寄存器值。
读取RX有效数据
通过指令0x61(或宏定义为RD_RX_PLOAD)读取接收FIFO中的数据(1~32字节)。数据从字节0开始顺序读取,完成后FIFO内容自动清除。
写寄存器指令
写寄存器命令为0x20(即001A AAAA),其中低5位AAAAA表示目标寄存器地址。
数据字节按低字节到高字节传输,每个字节内高位在前(MSB)。
写TX有效数据
写TX有效数据的命令为WR_TX_PLOAD(指令码0xA0),后跟1~32字节的有效载荷数据。该命令需在CS置低时通过SPI总线发送,且数据需按低字节在前、高字节在后的顺序传输。
NRF24L01模块主要寄存器定义如下:
如上图所示,模块进入接收模式,就需要CONFIG寄存器的bit0为1。
模块接收数据大致流程:
1.初始化接收地址、使能通道0、配置CONFIG为接收模式。
2.置高CE,模块持续监听2.4GHz频段。
3.检测IRQ中断,读取STATUS确认RX_DR标志。
4.通过RX_PLD读取数据,处理有效载荷。
四.单片机与NRF24L01模块通讯实验
1. 硬件准备
STLINK接STM32G474RET6开发板,STLINK接电脑USB口,板子插上NRF2401模块,板子USB口插上USB线并接电脑。NRF2401模块如下图所示,插在板子上的NRF2401接口。
2.创建CubeMX工程
如下图所示,打开STM32CubeMX软件,新建工程。
如下图所示,Part Number处输入STM32G474RE,再双击就创建新的工程。
如下图所示,配置下载口引脚,PA13为SWD的SWDIO脚,PA14为SWD的SWCLK脚。
如下图所示,配置SPI1,PA5,PA6,PA7为SPI引脚,速度1M左右,片选采用软件方式。
如下图所示,PA2,PA4配置成输出,PA4连接CE,PA2连接CS,PA3配置成输入,PC8配置成输出用于驱动LED灯。
如下图所示,配置系统主频170Mhz,使用内部16MHZ晶振。
配置工程文件名,保存路径,KEIL5工程输出方式,生成工程。
如下图所示,增加代码。
如下图所示,确认SPI驱动调用是否正确。
主要代码如下
//SPI读写数据函数
uint8_t SPI_ReadWriteByte(uint8_t TxData)
{
uint8_t RxData; if(HAL_SPI_TransmitReceive(&hspi1,&TxData, &RxData, 1,100)==HAL_OK){return RxData;}return 0;
}
//在指定位置读出指定长度的数据
//*pBuf:数据指针
//返回值,此次读到的状态寄存器值
uint8_t NRF24L01_Read_Buf(uint8_t reg_addr,uint8_t *pBuf,uint8_t data_len)
{uint8_t status,i; CSN_L; //使能SPI传输status=SPI_ReadWriteByte(reg_addr); //发送寄存器值(位置),并读取状态值 for(i=0;i<data_len;i++)pBuf[i]=SPI_ReadWriteByte(0);//读出数据CSN_H; //关闭SPI传输return status; //返回读到的状态值
}
//在指定位置写指定长度的数据
//*pBuf:数据指针
//返回值,此次读到的状态寄存器值
uint8_t NRF24L01_Write_Buf(uint8_t reg_addr, uint8_t *pBuf, uint8_t data_len)
{uint8_t status,i; CSN_L; //使能SPI传输status = SPI_ReadWriteByte(reg_addr); //发送寄存器值(位置),并读取状态值for(i=0; i<data_len; i++)SPI_ReadWriteByte(*pBuf++); //写入数据 CSN_H; //关闭SPI传输return status; //返回读到的状态值
}
//上电检测NRF24L01是否在位
//写5个数据然后再读回来进行比较,
//相同时返回值0,表示在位;
//否则返回1,表示不在位.
uint8_t NRF24L01_Check(void)
{uint8_t buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};uint8_t buf1[5];uint8_t i; NRF24L01_Write_Buf(SPI_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址. NRF24L01_Read_Buf(TX_ADDR,buf1,5); //读出写入的地址 for(i=0;i<5;i++)if(buf1[i]!=0XA5) break; if(i!=5) return 1; //NRF24L01不在位return 0; //NRF24L01在位
} int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();//SysTick配置成1ms中断/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();//16M内部晶振,170MHZ系统主频/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();//PC8,PA2,PA4配置成输出,PA3配置成输入MX_SPI1_Init();//SPI1初始化,PA5:PI1_SCK,PA6:SPI1_MISO,PA7:SPI1_MOSI/* USER CODE BEGIN 2 */Init_NRF24L01(); //初始化NRF24L01 while(NRF24L01_Check())//查询是否存在24L01{}RX_Mode();//配置成读模式,就是数据接收模式,调用NRF24L01_RxPacket进行数据接收,如要发送就调用TX_Mode();进行初始化,调用NRF24L01_TxPacket实现数据发送/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */HAL_Delay(200);//等待200MsHAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8);//LED灯闪烁}/* USER CODE END 3 */
}
如果模块数据发送,main函数中调用函数如下:
TX_Mode();//配置成发送模式,如果是接收模式,调用 RX_Mode();
while(1)
{if(NRF24L01_TxPacket(tmp_buf)==TX_OK)//NRF2401发送,如果是接收,调用if(NRF24L01_RxPacket(tmp_buf)==0){HAL_Delay(3000); }
}
如果模块接收,main函数中调用函数如下:
RX_Mode();//接收模式
while (1)
{HAL_Delay(100);//等待100msmemset(tmp_buf,0x00,sizeof(tmp_buf));if(NRF24L01_RxPacket(tmp_buf)==0)//接收数据,内容放tmp_buf{}}
3.实验结果
板子复位,能看到LED灯闪烁,说明单片片与NRF2401模块SPI通讯成功,如果需要测试NRF2401的数据通讯,需要两块板子,一块main函数是发送的代码,另一块main函数是接收的代码,接收的板子就能收到数据。
五.小结
STM32的SPI口是一种同步、全双工的通信标准,常用于短距离通信,如与Flash、传感器、显示驱动器等设备的通信,所以学会SPI能驱动各种模块传感器。