1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html 4)对正点原子STM32感兴趣的同学可以加群讨论:879133275
第四十一章 无线通信实验本章,我们将介绍如何使用2.4G无线模块NRF24L01实现无线通信。将使用两块STM32开发板,一块用于发送,一块用于接收,从而实现无线数据传输,并把数据显示在LCD上。本章分为如下几个小节: 41.1 SPI&NRF24L01无线模块介绍 41.2 硬件设计 41.3 程序设计 41.4 下载验证
41.1 SPI&NRF24L01无线模块介绍41.1.1 NRF24L01简介 NRF24L01无线模块,采用的芯片是NRF24L01+。该芯片是由NORDIC公司生产,并且集成NORDIC自家的Enhance ShortBurst协议,主要特点如下: 1)2.4G全球开放的ISM频段,免许可证使用 2)最高工作速率2Mbps,高效的GFSK调制,抗干扰能力强 3)126个可选的频道,满足多点通信和调频通信的需要 4)6个数据通道可支持点对多点的通信地址控制 5)低工作电压(1.9~3.6V) 6)硬件CRC和自动处理字头 7)可设置自动应答,确保数据可靠传输 由于高速信号是由芯片内部的射频协议处理后进行无线高速通信,对MCU的时钟频率要求不高,只需要对NRF24L01某些寄存器进行配置即可。芯片与外部MCU是通过SPI通信接口进行数据通信,并且最大的SPI速度可达10MHz。 这个芯片是NRF24L01的升级版。相比NRF24L01,升级版支持250k,1M,2M三种传输速率;支持更多种功率配置,根据不同应用有效节省功耗;稳定性及可靠性更高。 该模块的外形和引脚图如图41.1.1.1所示:
图41.1.1.1 NRF24L01无线模块外形和引脚图 模块VCC脚的电压范围为1.9~3.6V,建议不要超过3.6V,否则可能烧坏模块,一般用3.3V电压比较合适。除了VCC和GND脚,其他引脚都可以和5V单片机的IO口直连,正是因为其兼容5V单片机的IO,所以使用上具有很大优势。 具体引脚介绍如表41.1.1.1所示。 模块引脚 GND VCC CE CSN SCK MOSI MISO IRQ 功能说明 地线 3.3V电源线 使能端 片选 时钟 数据输出 数据输入 中断 表41.1.1.1 引脚介绍表 引脚部分主要分为电源相关的VCC和GND,SPI通信接口相关的CSN/SCK/MOSI/MISO,模式选择相关的CE,中断相关的IRQ。CE引脚会与CONFIG寄存器共同控制NRF24L01进入某个工作模式。IRQ引脚会在寄存器的配置下生效,当收到数据、成功发送数据或达到最大重发次数时,IRQ引脚会变为低电平。 SPI通信接口将会在41.1.4小节进行讲解。 NRF24L01的Enhance ShockBurstTM模式具体表现在自动应答和重发机制,发送端要求接收端在接收到数据后要有应答信号,便于发送端检测有无数据丢失,一旦有数据丢失,则通过重发功能将丢失的数据恢复,这个过程无需MCU。Enhance ShockBurstTM模式可以通过EN_AA寄存器进行配置。 接下来看一下Enhanced ShockBurstTM模式下NRF24L01通信图,如图41.1.1.2所示:
图41.1.1.2 NRF24L01通信图 这里我们抽离PTX6和PRX出来,分析一下通信过程。 PTX6作为发送端,它就需要设置发送地址,可以看到TX_ADDR为0x7878787878,PRX作为接收端,它使能接收通道0并设置接收通道0接收地址0x7878787878。通信时,发送端发送数据接收端接收到数据并记录TX地址接收端以TX地址为目的地址发送应答信号发送端会以通道0接收应答信号。 NRF24L01规定:发送端中的数据通道0是用来接收接收端发送的应答信号,所以数据通道0的接收地址要与发送地址要相同才能确保收到正确的应答信号,这里十分重要,必须要在相关寄存器中配置正确。 41.1.2 NRF24L01工作模式介绍 NRF24L01作为无线通信模块,功耗问题十分重要,有数据发送与空闲状态下能耗肯定是需要调整,所以设计者给芯片设计了多种工作模块,如表41.1.2.1所示:
NRF24L01工作模式是由CE引脚和CONFIG寄存器的PWR_UP位和PRIM_RX位共同控制。CE引脚在前面也说到是模式控制线,而PWR_UP位是上电位,PRIM_RX位可以理解为配置身份位(TX or RX)。可以看到发送模式有两种,待机模式也有两种,功耗上各不相同,没有标红的发送模式和待机模式I是官方推荐使用,更加节能,但是本实验用到的模式就是上表中标红色部分,因为标红的的模式使用起来更加方便。单看发送模式,使用官方推荐的发送模式,你要发送三级TX_FIFO数据需要产生三个边沿信号(CE从高电平变为低电平)。而我们使用的发送模式,从CE引脚的操作上看,只需要拉高,就可以把所有TX_FIFO里的数据发送完成。 NRF24L01的发送和接收都有三级FIFO,每一级FIFO就有32个字节。发送和接收都是对FIFO进行操作,并且最大操作的数据量就是一级FIFO即32字节。发送时,只需要把数据存进TX_FIFO并按照发送模式下的操作(参考NRF24L01工作模式表中的发送模式)即可让NRF24L01启动发射,这个发射过程就包括:无线系统上电,启动内部16MHz时钟,无线发送数据打包,高速发送数据。接收时,也是通过读取RX_FIFO里的内容。 41.1.3 NRF24L01寄存器 在这里简单介绍一下本实验用到的NRF24L01比较重要的寄存器。 配置寄存器(CONFIG) 寄存器地址0x01,复位值为0x80,用来配置NRF24L01工作状态以及中断相关,描述如图41.1.2.1所示:
图41.1.2.1 配置寄存器图 需要配置成发送模式,可以把该寄存器赋值为0x0E,如果配置成接收模式,可以把该寄存器赋值为0x0F。无论是发送模式还是接收模式,都使能16位CRC以及使能接收中断、发送中断和最大重发次数中断,这里发送端和接收端配置需要一致。 自动应答功能寄存器(EN_AA) 寄存器地址0x01,复位值为0x3F,用来设置通道0~5的自动应答功能,描述如图41.1.2.3所示:
图41.1.2.3 自动应答功能寄存器图 本实验,接收端是以数据通道0作为接收通道,并且前面也提及Enhanced ShockBurstTM模式的自动应答流程,接收端接收到数据后,需要回复应答信号,通过该寄存器ENAA_P0置1即可实现。另外,使能自动应答也相当于配置成Enhanced模式,所以发送端也需要进行自动应答允许。 接收地址允许寄存器(EN_RXADDR) 寄存器地址0x02,复位值为0x03,用于使能接收通道0~5,描述如图41.1.2.4所示:
图41.1.2.4 接收地址允许寄存器图 前面也说到接收端使用的是通道0进行接收数据,所以ERX_P0需要置1处理。同样的,发送端也需要使能数据通道0来接收应答信号。 地址宽度设置寄存器(SETUP_AW) 寄存器地址0x03,复位值为0x03,对接收/发送地址宽度设置位,描述如图41.1.2.5所示:
图41.1.2.5 地址宽度设置寄存器图 本实验中,无论是发送地址还是接收地址都是使用5字节,也就是默认设置便是使用5字节宽度的地址。 自动重发配置寄存器(SETUP_RETR) 寄存器地址0x04,复位值为0x00,对发送端的自动重发数值和延时进行设置,描述如图41.1.2.6所示: 参数 位 描述 ADR 7:4 自动重发延时: 0000~111186 us + 250 * (ARD + 1) us ARC 3:0 自动重发计数 0000~1111自动重发次数。0代表禁止 图41.1.2.6 自动重发配置寄存器图 本实验中,直接对该寄存器写入0x1A,即自动重发间隔时间为586us,最大自动重发次数为10次。在使能了MAX_RT中断时,连续重发10次还是发送失败的时候,IRQ中断引脚就会拉低。 射频频率设置寄存器(RF_CH) 寄存器地址0x05,复位值为0x05,对NRF24L01的频段进行设置,描述如图41.1.2.7所示:
图41.1.2.7 射频频率设置寄存器图 频率计算公式:2400 + RF_CH(MHz) 本实验中,直接对该寄存器写入40即射频频率为2440MHz。通信双方该寄存器必须配置一样才能通信成功。 发射参数设置寄存器(RF_SETUP) 寄存器地址0x06,复位值为0x0E,对NRF24L01的发射功率、无线速率进行设置,描述如图41.1.2.8所示:
图41.1.2.8 发射参数设置寄存器图 本实验中,直接对该寄存器写入0x0F即射频输出功率为0dBm增益,传输速率为2MHz。发送端和接收端该寄存器的配置需一样。功率越小耗电越少,同等条件下,传输距离越小,这里我们设置射频部分功耗为最大,当然大家可以根据实际应用而选择对应的功率配置。 状态寄存器(STATUS) 地址0x07,复位值为0x0E,反应NRF24L01当前工作状态,描述如图41.1.2.2所示:
图41.1.2.2 状态寄存器图 该寄存器作为查询作用,作为发送端,发送完数据后,可以查询一下TX_DS位状态便知是否成功发送数据,发送数据异常时,也可以通过查询MAX_RT位状态获知是否达到最大重发次数。作为接收端,就可以通过查询RX_OK位状态获知是否接收到数据。我们查询相关位后都需要将该位置1清除中断。 此外,我们还用到设置接收通道0地址寄存器RX_ADDR_P0(0x0A)和发送地址设置寄存器TX_ADDR(0x10)以及接收通道0有效数据看度设置寄存器RX_PW_P0(0x11),由于这三个寄存器比较简单,所以这里就不列出来了。 41.1.4 SPI接口简介 SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。 SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32H750也有SPI接口,其框图如图41.1.3.1所示:
图41.1.3.1 STM32H750的SPI框图 图中我们挑出了4处和我们本章例程相关的位置进行重点介绍,其中①处是SPI的对外信号,一般由4根信号线组成(MCK是I2S用的,不属于SPI信号线): MISO(SDI): 主设备数据输入,从设备数据输出。 MOSI(SDO):主设备数据输出,从设备数据输入。 SCK(CK):时钟信号,由主设备产生。 SS(WS):从设备片选信号(也称CS),由主设备控制。 图中②处是SPI的时钟生成器,将来自spi_ker_ck的时钟进行分频输出,最终产生SCK信号,并驱动RX/TX移位寄存器接收/发送数据。SPI1/2/3的spi_ker_ck时通过RCC_D2CCIP1R寄存器的SPI123SEL[2:0]设置,默认为0,则spi_ker_ck来自pll1_q_ck,为200Mhz。 图中③处重点由:TX移位寄存器、RX移位寄存器等组成,分别用于发送数据和接收数据, TxFIFO用于发送数据,RxFIFO用于接收数据,我们可以设置FIFO大小为1,这样写入TXDR的数据就可以直接输出到TX移位寄存器,在SCK时钟的驱动下,输出到MOSI脚。同理,当FIFO大小为1时,RX移位寄存器接收到的数据,也会直接输出给RXDR寄存器。 图中④处是SPI的一堆控制寄存器,用于设置SPI的各种工作情况(或模式),该区域由spi_pclk时钟驱动。一般我们需要合理的设置这些寄存器,SPI才可以正常使用。 SPI总线有四种工作模式,如表41.1.3.1所示:
表41.1.3.1 SPI四种工作模式 其中CPOL和CPHA分别控制SCK的时钟极性和相位时钟,可以通过相关寄存器设置。 不同时钟/相位下的总线数据传输时序如图41.1.3.2所示:
图41.1.3.2 不同时钟相位下的总线传输时序(CPHA=0/1) SPI在驱动不同的器件时,必须注意其支持的工作模式,需要设置合适的SPI工作模式,才可以正常通信。 对于STM32H7来说,SPI的MSB和LSB是可以配置的,通过SPI_CFG2的LSBFIRST位进行控制,当该位为1时,表示LSB在前;当该位为0时,表示MSB在前; STM32H7的SPI功能很强大,SPI时钟最高可以到133Mhz,支持DMA,可以配置为SPI协议或者I2S协议(支持全双工I2S)。 本章,我们将使用STM32H750的SPI来驱动NRF24L01无线模块。这里对SPI我们只简单介绍一下SPI的使用,STM32H750的SPI详细介绍请参考《STM32H7xx考手册》第2180页,第50章。 SPI主模式配置步骤 1)配置相关引脚的复用功能,使能SPI2时钟。 我们要用SPI2,第一步就要使能SPI2的时钟,SPI2的时钟通过APB1LENR的第14位来设置。其次要设置SPI2的相关引脚为复用(AF5)输出,这样才会连接到SPI2上。这里我们使用的是PB13、14、15这3个(SCK.、MISO、MOSI,CS使用软件管理方式),所以设置这三个为复用IO,复用功能为AF5。 2)设置SPI2的波特率和数据格式。 这一步通过SPI2_CFG1来设置,SPI2的波特率(就是SCK的频率,最大133Mhz)通过SPI2_CFG1寄存器的MBR[2:0]来设置,可以设置为spi_ker_ck的2~256分频。而数据格式通过SPI2_CFG1寄存器的DSIZE[4:0]位设置,数据长度等于DSIZE[4:0]+1,比如,我们要设置为8位数据格式,则设置DSIZE[4:0]=7即可。 3)设置SPI2工作模式。 这一步通过SPI2_CFG2来设置,我们设置SPI2为主机模式,全双工,然后通过CPOL和CPHA位来设置SCK时钟极性及采样方式。并设置SPI2的SS(即CS)控制方式为软件控制。 4)使能SPI2。 通过SPI2_CR1的bit0来设置,以启动SPI2,在启动之后,我们就可以开始SPI通讯了。 41.2 硬件设计
- 例程功能 开机的时候先检测NRF24L01模块是否存在,在检测到NRF24L01模块之后,根据KEY0和KEY1的设置来决定模块的工作模式。在设定好工作模式之后,就会不停的发送/接收数据,同时在LCD上面显示相关信息。LED0闪烁用于提示程序正在运行。
- 硬件资源 1)LED灯 LED0 – PB4 2 ) 独立按键 KEY0 – PA1 KEY1 – PA15 3)2.4G无线模块 NRF24L01模块 4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 5)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 6)SPI2(连接在PB13/PB14/PB15上)
- 原理图 NRF24L01模块与STM32的连接关系,如下图所示:
图41.2.1 NRF24L01模块接口与STM32连接原理图 这里NRF24L01使用的是SPI2,连接在PB13/PB14/PB15上。注意:NRF_IRQ和GBC_LED共用了PC3,所以,他们不能同时使用,需要分时复用。 由于无线通信实验是双向的,所以至少要有两个模块同时能工作,这里我们使用2套开发板来向大家演示。 41.3 程序设计 NRF24L01配置步骤 1)SPI参数初始化(工作模式、数据时钟极性、时钟相位等)。 HAL库通过调用SPI初始化函数HAL_SPI_Init完成对SPI参数初始化,详见例程源码。 注意:该函数会调用:HAL_SPI_MspInit函数来完成对SPI底层的初始化,包括:SPI及GPIO时钟使能、GPIO模式设置等。 2)使能SPI时钟和配置相关引脚的复用功能以及NRF24L01的其他相关管脚。 本实验用到SPI2,使用PB13、PB14和PB15作为SPI_SCK、SPI_MISO和SPI_MOSI,以及NRF24L01的CE、CSN和IRQ分别对应PC0,PE0和PC3,因此需要先使能SPI2、GPIOC和GPIOE时钟。参考代码如下: __HAL_RCC_GPIOx_CLK_ENABLE(); /* 使能GPIOx时钟,x=A……K / __HAL_RCC_SPI2_CLK_ENABLE (); / 使能SPI时钟 */ GPIO模式设置通过调用HAL_GPIO_Init函数实现,详见本例程源码。 3)使能SPI 通过__HAL_SPI_ENABLE函数使能SPI,便可进行数据传输。 4)SPI传输数据 通过HAL_SPI_Transmit函数进行发送数据。 通过HAL_SPI_Receive函数进行接收数据。 也可以通过HAL_SPI_TransmitReceive函数进行发送与接收操作。 5)编写NRF24L01的读写函数 基于SPI的读写函数的基础上,编写NRF24L01的读写函数。 6)编写NRF24L01接收模式与发送模式函数 通过查看寄存器,编写配置NRF24L01接收和发送模式的函数。 41.3.1 程序流程图
图41.3.1.1无线通信实验程序流程图 41.3.2 程序解析
- SPI驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。SPI驱动源码包括两个文件:spi.c和spi.h。 首先介绍的是spi.h文件,具体定义如下:
/* SPI2 引脚 定义 */
#define SPI2_SCK_GPIO_PORT GPIOB
#define SPI2_SCK_GPIO_PIN GPIO_PIN_13
#define SPI2_SCK_GPIO_AF GPIO_AF5_SPI2
#define SPI2_SCK_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MISO_GPIO_PORT GPIOB
#define SPI2_MISO_GPIO_PIN GPIO_PIN_14
#define SPI2_MISO_GPIO_AF GPIO_AF5_SPI2
#define SPI2_MISO_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SPI2_MOSI_GPIO_PORT GPIOB
#define SPI2_MOSI_GPIO_PIN GPIO_PIN_15
#define SPI2_MOSI_GPIO_AF GPIO_AF5_SPI2
#define SPI2_MOSI_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/* SPI2相关定义 */
#define SPI2_SPI SPI2
#define SPI2_SPI_CLK_ENABLE()
do{ __HAL_RCC_SPI2_CLK_ENABLE(); }while(0) /* SPI2时钟使能 */
上面的宏定义是SPI2的接口(PB13\PB14\PB15)的相关宏定义。
下面介绍spi.c文件,首先是SPI初始化相关函数,具体如下:
SPI_HandleTypeDef g_spi_handle; /* SPI句柄 */
/**
* @brief SPI初始化代码
* @note 主机模式,8位数据,禁止硬件片选
* @param 无
* @retval 无
*/
void spi2_init(void)
{
g_spi_handle.Instance = SPI2; /* SP2 */
/* 设置SPI工作模式,设置为主模式 */
g_spi_handle.Init.Mode = SPI_MODE_MASTER;
/* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
g_spi_handle.Init.Direction = SPI_DIRECTION_2LINES;
/* 设置SPI的数据大小:SPI发送接收8位帧结构 */
g_spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
/* 串行同步时钟的空闲状态为高电平 */
g_spi_handle.Init.CLKPolarity = SPI_POLARITY_HIGH;
/* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
g_spi_handle.Init.CLKPhase = SPI_PHASE_2EDGE;
/* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
g_spi_handle.Init.NSS = SPI_NSS_SOFT;
g_spi_handle.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* NSS信号脉冲失能 */
/* SPI主模式IO状态保持使能 */
g_spi_handle.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
/* 定义波特率预分频的值:波特率预分频值为256 */
g_spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
/* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
g_spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;
/* 关闭TI模式 */
g_spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
/* 关闭硬件CRC校验 */
g_spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
g_spi_handle.Init.CRCPolynomial = 7; /* CRC值计算的多项式 */
HAL_SPI_Init(&g_spi_handle);
__HAL_SPI_ENABLE(&g_spi_handle); /* 使能SPI2 */
spi2_read_write_byte(0Xff); /* 启动传输 */
}
/**
* @brief SPI底层驱动,时钟使能,引脚配置
* @param hspi:SPI句柄
* @note 此函数会被HAL_SPI_Init()调用
* @retval 无
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef gpio_init_struct;
RCC_PeriphCLKInitTypeDef rcc_periph_clk_init;
SPI2_SPI_CLK_ENABLE(); /* SPI2时钟使能 */
SPI2_SCK_GPIO_CLK_ENABLE(); /* SPI2_SCK脚时钟使能 */
SPI2_MISO_GPIO_CLK_ENABLE(); /* SPI2_MISO脚时钟使能 */
SPI2_MOSI_GPIO_CLK_ENABLE(); /* SPI2_MOSI脚时钟使能 */
/* 设置SPI2的时钟源 */
rcc_periph_clk_init.PeriphClockSelection = RCC_PERIPHCLK_SPI2;
/* SPI2时钟源使用PLL1Q */
rcc_periph_clk_init.Spi123ClockSelection = RCC_SPI123CLKSOURCE_PLL;
HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init);
gpio_init_struct.Pin = SPI2_SCK_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
gpio_init_struct.Alternate = SPI2_SCK_GPIO_AF; /* 复用 */
HAL_GPIO_Init(SPI2_SCK_GPIO_PORT, &gpio_init_struct); /* 初始化SCK引脚 */
gpio_init_struct.Pin = SPI2_MISO_GPIO_PIN;
gpio_init_struct.Alternate = SPI2_MISO_GPIO_AF; /* 复用 */
HAL_GPIO_Init(SPI2_MISO_GPIO_PORT, &gpio_init_struct); /* 初始化MISO引脚 */
gpio_init_struct.Pin = SPI2_MOSI_GPIO_PIN;
gpio_init_struct.Alternate = SPI2_MOSI_GPIO_AF; /* 复用 */
HAL_GPIO_Init(SPI2_MOSI_GPIO_PORT, &gpio_init_struct); /* 初始化MOSI引脚 */
}
上面介绍的是SPI2初始化函数(SPI2_Init)和SPI初始化回调函数HAL_SPI_MspInit。
最后介绍的是SPI2速度设置函数和SPI2读写一个字节数据函数,具体如下:
/**
* @brief SPI2速度设置函数
* @note SPI2时钟选择来自pll1_q_ck, 为240Mhz
* SPI速度 = spi_ker_ck / 2^(speed + 1)
* @param speed: SPI2时钟分频系数,SPI_BAUDRATEPRESCALER_2
~SPI_BAUDRATEPRESCALER_256
* @retval 无
*/
void spi2_set_speed(uint32_t speed)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(speed)); /* 判断有效性 */
__HAL_SPI_DISABLE(&g_spi_handle); /* 关闭SPI */
g_spi_handle.Instance->CFG1 &= ~(0X7 CFG1寄存器的位30-28来设置。形参speed是SPI2时钟分频系数,取值范围是:SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_256。 而spi2_read_write_byte函数主要是通过调用HAL库中SPI发送接收函数HAL_SPI_TransmitReceive来实现数据的发送和接收。 2. NRF24L01驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。NRF24L01驱动源码包括两个文件:nrf24l01.c和nrf24l01.h。 我们要在spi.c文件中封装好的函数的基础上进行调用,实现nrf24l01的发送与接收。下面先看一下nrf24l01.h文件中定义的信息,其代码如下: /* NRF24L01 操作引脚 定义(不包含SPI_SCK/MISO/MISO等三根线) */
#define NRF24L01_CE_GPIO_PORT GPIOC
#define NRF24L01_CE_GPIO_PIN GPIO_PIN_0
#define NRF24L01_CE_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define NRF24L01_CSN_GPIO_PORT GPIOE
#define NRF24L01_CSN_GPIO_PIN GPIO_PIN_0
#define NRF24L01_CSN_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define NRF24L01_IRQ_GPIO_PORT GPIOC
#define NRF24L01_IRQ_GPIO_PIN GPIO_PIN_3
#define NRF24L01_IRQ_GPIO_CLK_ENABLE()
do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
/* 24L01操作线 */
#define NRF24L01_CE(x) do{ x ? \
HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT,NRF24L01_CE_GPIO_PIN,GPIO_PIN_SET):\
HAL_GPIO_WritePin(NRF24L01_CE_GPIO_PORT,NRF24L01_CE_GPIO_PIN,GPIO_PIN_RESET);\
}while(0) /* 24L01模式选择信号 */
#define NRF24L01_CSN(x) do{ x ? \
HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT,NRF24L01_CSN_GPIO_PIN,GPIO_PIN_SET):\
HAL_GPIO_WritePin(NRF24L01_CSN_GPIO_PORT,
NRF24L01_CSN_GPIO_PIN,GPIO_PIN_RESET);\
}while(0) /* 24L01片选信号 */
#define NRF24L01_IRQ HAL_GPIO_ReadPin(NRF24L01_IRQ_GPIO_PORT,
NRF24L01_IRQ_GPIO_PIN) /* IRQ主机数据输入 */
以上除了有NRF24L01的引脚定义及引脚操作函数,此外还有一些NRF24L01寄存器操作命令以及其寄存器地址,由于篇幅太大,所以这里就不列出来了,大家可以去看一下工程文件。
下面看一下NRF24L01的初始化函数,其定义如下:
/**
* @brief 针对NRF24L01修改SPI2驱动
* @param 无
* @retval 无
*/
void nrf24l01_spi_init(void)
{
__HAL_SPI_DISABLE(&g_spi_handle); /* 先关闭SPI2 */
/* 串行同步时钟的空闲状态为低电平 */
g_spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;
/* 串行同步时钟的第1个跳变沿(上升或下降)数据被采样 */
g_spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;
HAL_SPI_Init(&g_spi_handle);
__HAL_SPI_ENABLE(&g_spi_handle); /* 使能SPI2 */
}
/**
* @brief 初始化24L01的IO口
* @note 将SPI2模式改成SCK空闲低电平,及SPI 模式0
* @param 无
* @retval 无
*/
void nrf24l01_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
NRF24L01_CE_GPIO_CLK_ENABLE(); /* CE脚时钟使能 */
NRF24L01_CSN_GPIO_CLK_ENABLE(); /* CSN脚时钟使能 */
NRF24L01_IRQ_GPIO_CLK_ENABLE(); /* IRQ脚时钟使能 */
gpio_init_struct.Pin = NRF24L01_CE_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
HAL_GPIO_Init(NRF24L01_CE_GPIO_PORT, &gpio_init_struct); /* 初始化CE引脚 */
gpio_init_struct.Pin = NRF24L01_CSN_GPIO_PIN;
HAL_GPIO_Init(NRF24L01_CSN_GPIO_PORT, &gpio_init_struct); /* 初始化CS引脚 */
gpio_init_struct.Pin = NRF24L01_IRQ_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
HAL_GPIO_Init(NRF24L01_IRQ_GPIO_PORT, &gpio_init_struct); /* 初始化CE引脚 */
spi2_init(); /* 初始化SPI2 */
nrf24l01_spi_init(); /* 针对NRF的特点修改SPI的设置 */
NRF24L01_CE(0); /* 使能24L01 */
NRF24L01_CSN(1); /* SPI片选取消 */
}
在初始化函数中,我们主要对该模块用到的管脚进行配置以及从初始化工作以及需要调用spi.c文件中的spi_init函数对SPI1的引脚进行初始化。现在让我们看一下 NRF24L01的工作时序图见下图41.3.2.1所示。 
图41.3.2.1 NRF24L01的工作时序图 大家可以比对一下前面章节的SPI工作时序图,符合工作模式1的时序,即在奇数边沿上升沿进行数据的采集。所以我们调用nrf24l01_spi_init函数针对NRF的特点修改SPI的设置。该函数就是将SPI的工作模式配置成串行同步时钟空闲状态为低电平,在奇数边沿数据被采集,也就是前面SPI实验章节中SPI的工作模式0。看看工作时序图的某些标号意义,Cn代表指令位,Sn代表状态寄存器位,Dn代表数据位。 下面介绍一下NRF24L01的读写函数,其代码如下:
/**
* @brief NRF24L01写寄存器
* @param reg : 寄存器地址
* @param value : 写入寄存器的值
* @retval 状态寄存器值
*/
static uint8_t nrf24l01_write_reg(uint8_t reg, uint8_t value)
{
uint8_t status;
NRF24L01_CSN(0); /* 使能SPI传输 */
status = spi2_read_write_byte(reg); /* 发送寄存器号 */
spi2_read_write_byte(value); /* 写入寄存器的值 */
NRF24L01_CSN(1); /* 禁止SPI传输 */
return status; /* 返回状态值 */
}
/**
* @brief NRF24L01读寄存器
* @param reg : 寄存器地址
* @retval 读取到的寄存器值;
*/
static uint8_t nrf24l01_read_reg(uint8_t reg)
{
uint8_t reg_val;
NRF24L01_CSN(0); /* 使能SPI传输 */
spi2_read_write_byte(reg); /* 发送寄存器号 */
reg_val = spi2_read_write_byte(0XFF); /* 读取寄存器内容 */
NRF24L01_CSN(1); /* 禁止SPI传输 */
return reg_val; /* 返回状态值 */
}
/**
* @brief 在指定位置读出指定长度的数据
* @param reg : 寄存器地址
* @param pbuf : 数据指针
* @param len : 数据长度
* @retval 状态寄存器值
*/
static uint8_t nrf24l01_read_buf(uint8_t reg, uint8_t *pbuf, uint8_t len)
{
uint8_t status, i;
NRF24L01_CSN(0); /* 使能SPI传输 */
status = spi2_read_write_byte(reg); /* 发送寄存器值(位置),并读取状态值 */
for (i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?