最近在一个项目中用到了nRF24L01这个无线2.4G收发芯片,项目中有主机和分机,默认都是使用数据通道0,主机通过nRF24L01发送数据后,对应地址的分机在收到数据后会返回一个确认数据包给主机(注意:这个确认数据包并不是nRF24L01自动应答时的数据包,而是自定义的一个数据包,说明了就是双方都能进行收发),在经过长时间的摸索之后,终于将接收和发送都调通了,基本的SPI驱动我使用的是正点原子的教程,我是使用的中断法来处理相应的收发工作。
我的软件硬件环境大致如下:
服务端:STM32F103VET6+u/COS-III
从机端:STM32F103RBT6+u/COS-III
库函数是使用的3.5版本的。
在这里我只是列出主机部分的代码,从机上的都是差不多的。
下面是nRF24L01的驱动部分
头文件部分(24l01.h):
#ifndef __24L01_H
#define __24L01_H
#include "config.h"
#if nRF24L01_EN > 0u
#include "sys_temp.h"
/*---------------------------------------------------------------------------------------------
NRF24L01寄存器操作命令及寄存器地址
-----------------------------------------------------------------------------------------------*/
#define SPI_READ_REG 0x00 //读配置寄存器,低5位为寄存器地址
#define SPI_WRITE_REG 0x20 //写配置寄存器,低5位为寄存器地址
#define RD_RX_PLOAD 0x61 //读RX有效数据,1~32字节
#define WR_TX_PLOAD 0xA0 //写TX有效数据,1~32字节
#define FLUSH_TX 0xE1 //清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX 0xE2 //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL 0xE3 //重新使用上一包数据,CE为高,数据包被不断发送.
#define NOP 0xFF //空操作,可以用来读状态寄存器
#define CONFIG 0x00 //配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
//bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
#define EN_AA 0x01 //使能自动应答功能 bit0~5,对应通道0~5
#define EN_RXADDR 0x02 //接收地址允许,bit0~5,对应通道0~5
#define SETUP_AW 0x03 //设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
#define SETUP_RETR 0x04 //建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
#define RF_CH 0x05 //RF通道,bit6:0,工作通道频率;
#define RF_SETUP 0x06 //RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define STATUS 0x07 //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,自动重发完成中断
//bit5:数据发送完成中断;bit6:接收数据完成中断;
#define MAX_TX 0x10 //达到最大发送次数中断,即自动重发完成中断
#define TX_OK 0x20 //TX发送完成中断,即数据发送完成中断
#define RX_OK 0x40 //接收到数据中断,即数据接收完成中断
#define OBSERVE_TX 0x08 //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD 0x09 //载波检测寄存器,bit0,载波检测;
#define RX_ADDR_P0 0x0A //数据通道0接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P1 0x0B //数据通道1接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P2 0x0C //数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P3 0x0D //数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P4 0x0E //数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P5 0x0F //数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define TX_ADDR 0x10 //发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
#define RX_PW_P0 0x11 //接收数据通道0有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P1 0x12 //接收数据通道1有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P2 0x13 //接收数据通道2有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P3 0x14 //接收数据通道3有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P4 0x15 //接收数据通道4有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P5 0x16 //接收数据通道5有效数据宽度(1~32字节),设置为0则非法
#define FIFO_STATUS 0x17 //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留
//bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环;
/*---------------------------------------------------------------------------------------------
24L01的用到的单片机引脚
-----------------------------------------------------------------------------------------------*/
#define NRF24L01_SPI_Periph_CLK RCC_APB2Periph_GPIOB //无线2.4G模块用到的引脚的外设时钟源
#define NRF24L01_SPI_GPIO_SRC GPIOB
#define NRF24L01_SPI_SCK_PIN GPIO_Pin_13 //SCK时钟
#define NRF24L01_SPI_MISO_PIN GPIO_Pin_14 //主机输入,从机输出
#define NRF24L01_SPI_MOSI_PIN GPIO_Pin_15 //主机输出,从机输入
#define NRF24L01_CE_PIN GPIO_Pin_10 //24L01芯片使能信号
#define NRF24L01_IRQ_PIN GPIO_Pin_11 //IRQ主机数据输入
#define NRF24L01_CSN_PIN GPIO_Pin_12 //SPI片选
/*---------------------------------------------------------------------------------------------
24L01芯片使能信号和片选信号操作
-----------------------------------------------------------------------------------------------*/
#define NRF24L01_CE PBout(10) //24L01芯片使能信号
#define NRF24L01_CSN PBout(12) //SPI片选信号
#define NRF24L01_IRQ PBin(11) //IRQ主机数据输入
/*---------------------------------------------------------------------------------------------
24L01中断线配置,当接收到数据,发送数据,达到最大重发次数时都会触发中断
相应的状态寄存器会置位1,注意需要手动清除中断,写1清零
-----------------------------------------------------------------------------------------------*/
#define NRF24L01_INT_SOURCE_PORT GPIO_PortSourceGPIOB //中断引脚组
#define NRF24L01_INT_IRQ EXTI15_10_IRQn //中断号
#define NRF24L01_STATUS_INT_SOURCE GPIO_PinSource11 //中断源
#define NRF24L01_STATUS_LINE EXTI_Line11 //中断线
#define NRF24L01_STATUS_INT_MODE EXTI_Trigger_Falling //中断触发方式
/*---------------------------------------------------------------------------------------------
24L01发送接收数据宽度定义
-----------------------------------------------------------------------------------------------*/
#define TX_ADR_WIDTH 5 //5字节的地址宽度
#define RX_ADR_WIDTH 5 //5字节的地址宽度
#define TX_PLOAD_WIDTH 32 //32字节的用户数据宽度
#define RX_PLOAD_WIDTH 32 //32字节的用户数据宽度
/*---------------------------------------------------------------------------------------------
24L01相关操作函数定义
-----------------------------------------------------------------------------------------------*/
void NRF24L01_Init(void);//初始化
void RX_Mode(void);//配置为接收模式
//void TX_Mode(void);//配置为发送模式(原始定义)
void TX_Mode(u8 * addr);//配置为发送模式
static u8 SPIx_ReadWriteByte(u8 TxData);
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 u8s);//写数据区
u8 NRF24L01_Read_Buf(u8 reg, u8 *pBuf, u8 u8s);//读数据区
u8 NRF24L01_Read_Reg(u8 reg); //读寄存器
u8 NRF24L01_Write_Reg(u8 reg, u8 value);//写寄存器
u8 NRF24L01_Check(void);//检查24L01是否存在
u8 NRF24L01_TxPacket(u8 *txbuf);//发送一个包的数据
//u8 NRF24L01_RxPacket(u8 *rxbuf);//接收一个包的数据(原始定义)
u8 NRF24L01_RxPacket(u8 *rxbuf, u8 *chl);//接收一个包的数据
#endif /* nRF24L01_EN */
#endif /* __24L01_H */
源文件部分(24l01.c):
/******************** (C) COPYRIGHT 2015 ASTO ***************************
@* 文件名 :24l01.c
@* 描述 :nRF24L01驱动程序
@* 开发平台:STM32F103VET6主控制MCU
@* 硬件连接:SPI2
@* 库版本 :ST3.5.0
@* 作者 :
@* 公司网址:www.test.com
@* 总部网址:www.test.com
**********************************************************************************/
#include "config.h"
#if nRF24L01_EN > 0u
#include "sys_temp.h"
#include "24l01.h"
#include "delay.h"
const u8 RX0_Address[RX_ADR_WIDTH]={0x01,0x01,0x01,0x01,0x01}; //接收方通道0地址
/*
const u8 RX1_Address[RX_ADR_WIDTH]={0x02,0x20,0x20,0x20,0x20}; //接收方通道1地址
const u8 RX2_Address[1] = {0x03}; //接收方通道2地址
const u8 RX3_Address[1] = {0x04}; //接收方通道3地址
const u8 RX4_Address[1] = {0x05}; //接收方通道4地址
const u8 RX5_Address[1] = {0x06}; //接收方通道5地址
*/
/**
@函数名称:EXTI_Configuration()
@函数功能:nRF24L01的IRQ中断配置(只响应接收数据的中断)
@输入:无
@输出:无
@调用:内部调用
**/
static void EXTI_Configuration(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//中断优先级设置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = NRF24L01_INT_IRQ; //10-15的中断线共享一个中断处理程序
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优行级
NVIC_Init(&NVIC_InitStructure);
//中断线配置EXTI_Line11-->PB11
GPIO_EXTILineConfig(NRF24L01_INT_SOURCE_PORT, NRF24L01_STATUS_INT_SOURCE); //设置中断源引脚
EXTI_InitStructure.EXTI_Line = NRF24L01_STATUS_LINE; //中断线设置
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断方式
EXTI_InitStructure.EXTI_Trigger = NRF24L01_STATUS_INT_MODE;//模式,这里设置为下降沿触发
EXTI_Init(&EXTI_InitStructure);
}
/**
@* 函数名:NRF24L01_SPI_Init()
@* 描述 :初始化SPI2端口及基模式,用于操作nRF24L01无线2.4G模块
@* 输入 :无
@* 输出 : 无
@* 调用 :内部调用
*/
static void NRF24L01_SPI_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
//注意将普通IO作为中断线时必须开启AFIO时钟
RCC_APB2PeriphClockCmd(NRF24L01_SPI_Periph_CLK|RCC_APB2Periph_AFIO, ENABLE);//开启GPIOB的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//开启SPI2的时钟
/*配置 SPI的SCK, MISO, MOSI引脚,GPIOB^13,GPIOB^14,GPIOB^15 */
GPIO_InitStructure.GPIO_Pin = NRF24L01_SPI_SCK_PIN| NRF24L01_SPI_MISO_PIN | NRF24L01_SPI_MOSI_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用输出功能
GPIO_Init(NRF24L01_SPI_GPIO_SRC, &GPIO_InitStructure);
/*GPIOB^10为nRF24L01的CE引脚,CSN引脚GPIOB^12 */
GPIO_InitStructure.GPIO_Pin = NRF24L01_CE_PIN|NRF24L01_CSN_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽输出
GPIO_Init(NRF24L01_SPI_GPIO_SRC, &GPIO_InitStructure);
/* GIOB^11为nRF24L01的IRQ中断输入引脚 */
GPIO_InitStructure.GPIO_Pin = NRF24L01_IRQ_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮动输入
GPIO_Init(NRF24L01_SPI_GPIO_SRC, &GPIO_InitStructure);
SPI_Cmd(SPI2,DISABLE);/*先失能,然后再使能*/
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex ; /* 全双工 */
SPI_InitStructure.SPI_Mode = SPI_Mode_Master ; /*当前的设备为主机模式*/
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low ; /* 时钟极性为低,即SPI空闲时,SCK为低电平 */
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge ; /* 时钟相位,第一个时钟沿(也就是奇数边沿)捕捉数据 */
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; /* 数据宽度 */
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB ; /* 低地址存放最高有效字节 */
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; /*配置片选为软件控制方式*/
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; /* 时钟分频为8分频,即72MHz/8=9MHz */
SPI_InitStructure.SPI_CRCPolynomial = 7; /* 校验多项式,这个不起作用 */
SPI_Init(SPI2, &SPI_InitStructure);
//使能SPI2
SPI_Cmd(SPI2,ENABLE);
EXTI_Configuration();//配置中断线
}
/**
@* 函数名:SPIx_ReadWriteByte()
@* 描述 :SPI2 读写一个字节
@* 输入 :TxData:要写入的字节
@* 输出 : 读取到的字节
@* 调用 :内部调用
*/
static u8 SPIx_ReadWriteByte(u8 TxData) {
/** 原来的实现方式
u8 retry=0;
while((SPI2->SR&1<<1)==0) {//等待发送区空
retry++;
if(retry>200)return 0;
}
SPI2->DR=TxData; //发送一个byte
retry=0;
while((SPI2->SR&1<<0)==0) { //等待接收完一个byte
retry++;
if(retry>200)return 0;
}
return SPI2->DR; //返回收到的数据 */
/* Loop while DR register in not empty */
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
/* Send byte through the SPI2 peripheral */
SPI_I2S_SendData(SPI2, TxData);
/* Wait to receive a byte */
while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
/* Return the byte read from the SPI bus */
return SPI_I2S_ReceiveData(SPI2);
}
/**
@* 函数名:NRF24L01_Init()
@* 描述 :初始化24L01的相关IO口
@* 输入 :无
@* 输出 : 无
@* 调用 :外部板级支持包调用
*/
void NRF24L01_Init(void) {
NRF24L01_SPI_Init(); //初始化SPI2
NRF24L01_CE=0; //使能24L01
NRF24L01_CSN=1; //SPI片选取消
}
/**
@* 函数名:NRF24L01_Check()
@* 描述 :检测24L01是否存在
@* 输入 :无
@* 输出 : 0:成功,1:失败
@* 调用 :外部板级支持包调用
*/
u8 NRF24L01_Check(void) {
u8 buf[5]={0x21,0x21,0x21,0x21,0x21};
u8 buf1[5];
u8 i;
//SPIx_SetSpeed(SPI_SPEED_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)
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]!=0x21)break; //判断读出的数据和写入的数据是否完全一致
if(i!=5)return 1;//检测24L01错误
return 0; //执行到这里,表示成功检测到24L01
}
/**
@* 函数名:NRF24L01_Write_Reg()
@* 描述 :SPI写寄存器
@* 输入 :reg: 指定的寄存器地址
@* value: 要写入的值
@* 输出 : 返回寄存器的状态值
@* 调用 :内部调用
*/
u8 NRF24L01_Write_Reg(u8 reg,u8 value) {
u8 status;
NRF24L01_CSN=0; //使能SPI传输
status =SPIx_ReadWriteByte(reg); //发送寄存器号
SPIx_ReadWriteByte(value); //写入寄存器的值
NRF24L01_CSN=1; //禁止SPI传输
return(status); //返回状态值
}
/**
@* 函数名:NRF24L01_Read_Reg()
@* 描述 :读取SPI寄存器值
@* 输入 :reg: 指定的寄存器地址
@* 输出 : 返回寄存器的状态值
@* 调用 :内部调用
*/
u8 NRF24L01_Read_Reg(u8 reg) {
u8 reg_val;
NRF24L01_CSN = 0; //使能SPI传输
SPIx_ReadWriteByte(reg); //发送寄存器号
reg_val=SPIx_ReadWriteByte(0XFF); //读取寄存器内容
NRF24L01_CSN = 1; //禁止SPI传输
return(reg_val); //返回状态值
}
/**
@* 函数名:NRF24L01_Read_Buf()
@* 描述 :在指定位置读出指定长度的数据
@* 输入 :reg:指定的寄存器位置
@* pBuf:数据指针用来存储读到的数据,一般是数组
@* len:数据长度
@* 输出 : 此次读到的状态寄存器值
@* 调用 :内部调用
*/
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len) {
u8 status,u8_ctr;
NRF24L01_CSN = 0; //使能SPI传输
status=SPIx_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
for(u8_ctr=0;u8_ctr>1;
//清除TX_DS或MAX_RT中断标志,注意这里是写1清零,只有清零后设备才能正常通讯
NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,sta);
if(sta&RX_OK) { //接收到数据
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
NRF24L01_Write_Reg(FLUSH_RX, 0xff);//清除RX FIFO寄存器
return 0;
}
return 1;//没收到任何数据
}
/**
@* 函数名:RX_Mode()
@* 描述 :该函数初始化NRF24L01到RX模式
@* 设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
@* 当CE变高后,即进入RX模式,并可以接收数据了
@* 输入 :无
@* 输出 : 无
@* 调用 :外部板级支持包调用
*/
void RX_Mode(void) {
NRF24L01_CE=0; //CE为0进入待机模式
//配置通道的接收
NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)RX0_Address,RX_ADR_WIDTH);//写RX节点地址(通道0),也就是表示用哪个通道接收数据
//======================注意以下被注释掉的内容可作参考和=========================================
//NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P1,(u8*)RX1_Address,RX_ADR_WIDTH);//写RX节点地址(通道1),也就是表示用哪个通道接收数据
//NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P2,(u8*)RX2_Address,1);//写RX节点地址(通道2),也就是表示用哪个通道接收数据
//NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P3,(u8*)RX3_Address,1);//写RX节点地址(通道3),也就是表示用哪个通道接收数据
//NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P4,(u8*)RX4_Address,1);//写RX节点地址(通道4),也就是表示用哪个通道接收数据
//NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P5,(u8*)RX5_Address,1);//写RX节点地址(通道5),也就是表示用哪个通道接收数据
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA, 0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址,共有6个通道,高两位保留固定为00
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,0); //设置RF通信频率
NRF24L01_Write_Reg(SPI_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);//选择通道0的有效数据宽度
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x07);//设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG, 0x0f);//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
NRF24L01_CE = 1; //CE为高,进入接收模式
}
/**
@* 函数名:TX_Mode()
@* 描述 :该函数初始化NRF24L01到TX模式
@* 设置TX地址,写TX数据宽度,设置RX自动应答的地址,
@* 填充TX发送数据,选择RF频道,波特率和LNA HCURR,PWR_UP,CRC使能
@* 当CE变高后,即进入RX模式,并可以接收数据了
@* CE为高大于10us,则启动发送
@* 输入 :addr,要发送数据的目标地址
@* 输出 : 无
@* 调用 :外部板级支持包调用
*/
void TX_Mode(u8 * addr) {
NRF24L01_CE=0; //CE为0进入待机模式
NRF24L01_Write_Buf(SPI_WRITE_REG+TX_ADDR, addr, TX_ADR_WIDTH);//写TX节点地址,也就是接收方的地址(目标地址)
//在发送端,数据通道0被用作接收应答信号,因此数据通道0的接收地址必须要与发送端的地址相同以确保收到正确的应答信号
NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)addr, RX_ADR_WIDTH);
//先单独测试发送方,看发送是否正常
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答
NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址
NRF24L01_Write_Reg(SPI_WRITE_REG+SETUP_RETR, 0x1a); //使能自动重发,设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,0); //设置RF通道为40
NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x07); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启
NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG,0x0e); //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
NRF24L01_CE=1;//CE为高,10us后启动发送
}
#endif
/******************************************END OF FILE*************************************************/
中断处理函数(stm32f10x_it.c)
#if nRF24L01_EN > 0u
#include "24l01.h"
/**
@*功能简介:I/O线中断处理函数,处理nRF24L01的数据接收中断,IO口是PB11
@*参数:None
@*返回值:None
*/
void EXTI15_10_IRQHandler(void)
{
OSIntEnter(); //用于统计中断的嵌套层数,对嵌套层数+1,请注意:这适用于有内核参与的中断
if (EXTI_GetITStatus(NRF24L01_STATUS_LINE)!=RESET) {
EXTI_ClearITPendingBit(NRF24L01_STATUS_LINE);//清除中断标志位
if (!GPIO_ReadInputDataBit(NRF24L01_SPI_GPIO_SRC, NRF24L01_IRQ_PIN)) {//如果为低电平表示产生中断
u8 sta; u8 sta2;
sta=NRF24L01_Read_Reg(STATUS); //读取nRF24L01状态寄存器的值
sta2=NRF24L01_Read_Reg(FIFO_STATUS);//读取FIFO状态寄存器
//状态寄存器的bit1-bit3是表示接收到数据的通道号,最大值为6
//*chl=(0x0e&sta)>>1;
NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志,注意这里是写1清零,只有清零后设备才能正常通讯
if(sta & RX_OK) { //此次中断表示是接收到了数据
OS_ERR err;
USART1_SendData((u8*)"Feedback received!", 19);
//直接发信号量给任务,这种方式比使用中介信号量更快,更节省资源
//第二个参数:1. OS_OPT_POST_NONE表明在发布任务信号量之后调用任务调度程序
// 2. OS_OPT_POST_NO_SCHED则OSTaskSemPost后不会调用调度程序
OSTaskSemPost(&nRF24L01_TCB,
OS_OPT_POST_NONE,
&err);
}else if (sta & MAX_TX) { //此次中断表示达到最大重发次数后必须手动清除TX FIFO寄存器
USART1_SendData((u8 *)"Max retransmission reached!", 28);
NRF24L01_Write_Reg(FLUSH_TX,0xff);
}
//如果数据发送成功,则相应的寄存器值为下列值,可以调试用:
//FIFO_STATUS: 0x11
//STATUS: 0x2E
else if (sta & TX_OK) { //此次中断表示发送数据成功,无需手动清除TX FIFO寄存器
USART1_SendData((u8 *)"Send data OK!", 14);
RX_Mode(); //数据成功发送后直接转为接收模式
}
}
}
OSIntExit();//对嵌套层数减1,在退出中断前启动任务调度(适用于有u/COS-III内核参与的中断)
}
#endi
在中断处理函数中,当收到数据的中断产生时,也就是RX_DR位置高时,我会将接收数据的工作交给某个任务去做,这里我使用的是发布信号量的方法,这样可以尽量减少中断处理的时间。这里我把RX_Mode();这句放在中断处理函数中,也就是数据发送完成后将nRF24L01转换为接收模式,也是出于无奈,因为我在函数NRF24L01_TxPacket中如果使用以下语句,就会出现问题:
while(NRF24L01_IRQ!=0);//等待发送完成,IRQ变为0后表示发送完成或者达到最大重发次数
后来我就不用这一句,然后让主程序执行其他的语句,当产生中断后(无论怎样都会产生一种中断,要么发送成功,要么达到最大重传次数)再将nRF24L01的模式变为接收模式,不知这样做是否合理,还望看过的朋友指点一下。
nRF24L01_TCB任务控制块的任务代码主函数(接收数据):
#if nRF24L01_EN > 0u
/**
@* 函数名:Task_nRF24L01_Sendback_Process()
@* 描述 : 无线2.4G模块nRF24L01接收反馈数据包处理任务,优先级为4
@* 当Main_Task或USART1_Task或Wi-Fi模块确定是要以无线2.4G方式发送数据给客户端后,会等待客户端设备的回应
@* 以确定此次操作是否成功并同时会启动一个定时器,此任务采用等待信号量方式确认是否有客户端的回复
@* 如果收到回复,则会根据上位机发送数据给主控制MCU的方式将回应数据包回发给上位机。
@*
@* 发送数据给上位机的方式有以下几种:
@* *****************************
@* * 1. 网络方式 *
@* * 2. 串口方式 *
@* * 3. Wi-Fi方式 *
@* *****************************
@* 输入 :p_arg: 创建任务时赋给任务的参数,该参数可以是任意类型的
@* 输出 : 无
*/
void Task_nRF24L01_Sendback_Process(void *p_arg) {
OS_ERR err; //用于记录错误代号
//u8 chl=0; //接收到数据的通道号
CPU_TS ts;
u8 n24l01_buf[32]; //接收2.4G数据缓冲区
(void)p_arg; //保存创建任务控制块时传递的任务参数
while(NRF24L01_Check()) { //如果检测不到24L01模块则打印出错信息
printf("No nRF24L01 device is found!\r\n");
}
RX_Mode(); //默认为接收模式
OSTimeDlyHMSM(0, 0, 0, 2, OS_OPT_TIME_HMSM_STRICT,&err); //至少延时130微秒后,nRF24L01模块进入接收状态
while(DEF_ON) {
IWDG_Feed(); //每个任务都会调用看门狗程序
//*************************使用中断方式*************************************
OSTaskSemPend(0,
OS_OPT_PEND_BLOCKING,
&ts, //信号量等待的时间
&err);
switch(err) { //处理Pend的结果
case OS_ERR_NONE:
OSTmrDel(&ClientDeviceTimeout_Tmr,&err); //正确接收到反馈数据包则删除定时器
NRF24L01_Read_Buf(RD_RX_PLOAD, n24l01_buf, RX_PLOAD_WIDTH); //从nRF24L01中读取接收到的数据
NRF24L01_Write_Reg(FLUSH_RX, 0xff); //清除RX FIFO接收数据寄存器
switch(snd_type){ //根据上位机发送指令的方式分别处理
#if ETHNET_EN > 0u
case Ethernet: //以太网方式
if (custom_udp_send.cus_pbuf!=NULL) {
custom_udp_send.cus_pbuf->payload=n24l01_buf; //得到反馈数据包
custom_udp_send.cus_pbuf->tot_len=custom_udp_send.cus_pbuf->len=sizeof(n24l01_buf);
udp_sendto(custom_udp_send.cus_udp_pcb, //使用当前pbuf发送反馈数据包到客户端
custom_udp_send.cus_pbuf,
custom_udp_send.cus_ip_addr,
custom_udp_send.cus_udp_port);
pbuf_free(custom_udp_send.cus_pbuf); //释放缓冲池中的当前pbuf对象
custom_udp_send.cus_pbuf=NULL;
}
break;
#endif
#if RS232_EN > 0u
case RS232: //RS232串口方式
USART1_SendData(n24l01_buf, sizeof(n24l01_buf));
break;
#endif
#if HF_LPB100_EN > 0u
case WiFi: //Wi-Fi方式(需要Wi-Fi模块作为服务端)
USART3_SendData(n24l01_buf, sizeof(n24l01_buf));
break;
#endif
default:
break;
}
break;
case OS_ERR_PEND_ABORT: //等待(挂起)状态被其他任务打断
break;
case OS_ERR_OBJ_DEL: //内核对象已被删除
break;
default:
break;
}
// //*************************原来的方式,使用查询法****************************
//
// if(NRF24L01_RxPacket(n24l01_buf, &chl)==0) { //如果正确接收到数据
// //nRF24L01_BUF[31]=chl; //最后一个字节表示模块接收到数据的通道号(暂时不用)
// USART1_SendData(n24l01_buf,sizeof(n24l01_buf)); //测试接收到的数据
// OSTmrDel(&ClientDeviceTimeout_Tmr,&err); //正确接收到反馈数据包则删除定时器
//
// switch(snd_type){ //根据上位机发送指令的方式分别处理
// #if ETHNET_EN > 0u
// case Ethernet: //以太网方式
// if (custom_udp_send.cus_pbuf!=NULL) {
// custom_udp_send.cus_pbuf->payload=n24l01_buf; //得到反馈数据包
// custom_udp_send.cus_pbuf->tot_len=custom_udp_send.cus_pbuf->len=sizeof(n24l01_buf);
// udp_sendto(custom_udp_send.cus_udp_pcb, //使用当前pbuf发送反馈数据包到客户端
// custom_udp_send.cus_pbuf,
// custom_udp_send.cus_ip_addr,
// custom_udp_send.cus_udp_port);
// pbuf_free(custom_udp_send.cus_pbuf); //释放缓冲池中的当前pbuf对象
// custom_udp_send.cus_pbuf=NULL;
// }
// break;
// #endif
//
// #if RS232_EN > 0u
// case RS232: //RS232串口方式
// USART1_SendData(n24l01_buf, sizeof(n24l01_buf));
// break;
// #endif
//
// #if HF_LPB100_EN > 0u
// case WiFi: //Wi-Fi方式(需要Wi-Fi模块作为服务端)
// USART3_SendData(n24l01_buf, sizeof(n24l01_buf));
// break;
// #endif
//
// default:
// break;
// }
// }
//
// OSTimeDlyHMSM(0, 0, 0, 1, OS_OPT_TIME_HMSM_NON_STRICT,&err); //阻塞1毫秒,即延时1个时钟节拍
}
}
#endif
发送数据的代码就比较简单了,这里只列出部分代码:
#if nRF24L01_EN > 0u
TX_Mode(addr_t); //设置nRF24L01模块为发送模式
NRF24L01_TxPacket(arg);
OSTimeDlyHMSM(0, 0, 0, 1, OS_OPT_TIME_HMSM_STRICT,&err); //根据手册说明,至少延时10微秒后开始发送,这里延时长了一些
//******其他的方式发送数据******
//NRF24L01_CE=0;
//NRF24L01_Write_Buf(WR_TX_PLOAD, arg, TX_PLOAD_WIDTH); //写数据到TX BUF 最长32个字节
//NRF24L01_CE=1; //启动发送,10微秒后会发送数据
//OSTimeDlyHMSM(0, 0, 0, 20, OS_OPT_TIME_HMSM_STRICT,&err); //延时以等待数据发送完成并产生中断,至少拉高CE10微秒才开始发送
//RX_Mode(); //设置nRF24L01为接收模式,至少延时130微秒
//OSTimeDlyHMSM(0, 0, 0, 2, OS_OPT_TIME_HMSM_STRICT,&err);
#endif