NRF24L01双向传输(一对一)

您所在的位置:网站首页 麻雀的说明 NRF24L01双向传输(一对一)

NRF24L01双向传输(一对一)

2024-07-14 15:41:58| 来源: 网络整理| 查看: 265

NRF24L01双向传输(一对一) 简介

本文章记录两个NRF24L01无线模块实现双向传输的软件设计~ 为什么可以双向传输呢?这要归功于它具有Enhanced ShockBurst,可以工作在主接收和主发送模式,在主接收方可以将我们自己的数据附在ACK Packet实现双向传输,所以此次双向传输会将两个NRF分别设置成主接收和主发送。 · 实物(模块非单独芯片): 在这里插入图片描述 网上最便宜的就这种不带运放的模块,大家如果想用NRF24L01的话,建议大家使用加了运放的模块 ,传输距离会比较远一点,没运放的话距离10米就开始出现丢包现象了。另外,进口的和国产的用起来距离一样,所以优先选择国产,因为进口的还贵点。 ·芯片引脚定义说明(芯片): 在这里插入图片描述 ·**数据包格式 **: 在这里插入图片描述 这里没必要介绍NRF24L01了,相信要用这芯片或者模块的朋友应该提前了解过了。 · 寄存器表 该芯片有命令寄存器和功能寄存器。在使用每一款芯片之前,要养成查看数据手册的习惯。这是我自己边看边翻译的,可能有翻译地不对的,见谅哈。 命令寄存器表:

命令名命令字#数据字节操作R_REGISTER000A AAAA1到5,低位在前读命令/状态寄存器W_REGISTER001A AAAA1到5,低位在前写命令/状态寄存器R_RX_PAYLOAD0110 00011到32,低位在前读RX_Payload(1到32个字节)W_TX_PAYLOAD1010 00001到32,低位在前写TX_Payload(1到32个字节)FLUSH_TX1110 00010Flush TX FIFO,发送模式使用FLUSH_RX1110 00100Flush RX FIFO,接收模式使用REUSE_TX_PL1110 00110重用上次发送的载荷(数据包)R_RX_PL_WID0110 00001读取RX payload宽度W_ACK_PAYLOAD1010 1PPP1到32,低位在前写载荷(这些载荷数据同应答包一起从PPP通道发出)W_TX_PAYLOAD_NO ACK1011 00001到32,低位在前失能自动应答,用于发送模式NOP1111 11110无操作,可用于读取状态寄存器

上面的AAAAA就是后面即将看到的功能寄存器的映射地址。 功能寄存器地址映射表:

地址助记符位复位值类型描述00CONFIG配置寄存器Reserved70R/w只能为0MASK_RX_DR60R/w接收中断标志。0:使能MASK_TX_D50R/w发送中断标志。0:使能MASK_MAX_RT40R/w最大重发次数中断。0:使能EN_CRC31R/w使能CRCCRCO20R/wCRC编码方案选择,1~2bytesPWR_UP10R/w1:上电,0:下电PRIM_RX00R/w收发控制。1:PRX;0:PTX01EN_AA使能自动应答Reserved7:600R/w只能为0ENAA_P551R/w使能通道5自动应答机制ENAA_P441R/w使能通道4自动应答机制ENAA_P331R/w使能通道3自动应答机制ENAA_P221R/w使能通道2自动应答机制ENAA_P111R/w使能通道1自动应答机制ENAA_P001R/w使能通道0自动应答机制02EN_RXADDR使能 RX 地址Reserved7:600R/w只能为0ERX_P550R/w使能通道5ERX_P440R/w使能通道4ERX_P330R/w使能通道3ERX_P220R/w使能通道2ERX_P110R/w使能通道1ERX_P000R/w使能通道003SETUP_AW设置地址宽度(所有通道的地址)Reserved7:2000000R/w只能为0AW1:011R/w收发地址宽度,’11’-5bytes04SETUP_RETR设置自动重发射机制ARD7:40000R/w自动重发延时’0001’-等待500usARC3:00011R/w自动重发计数05RF_CH设置RF频宽Reserved70R/w只能为0RF_CH6:00000010R/w设置RF的工作频宽06RF_SETUPRF设置寄存器CONT_WAVE70R/w为1使能连续传送载波Reserved60R/w只能为0RF_DR_LOW50R/w设置RF数据250kbpsPLL_LOCK40R/w仅使用于测试?RF_DR_HIGH31R/w‘00’-1M,’01’-2M,’10’-250kbpsRF_PWR2:111R/w设置RF输出增益 ’00’-18dbm,’11’-0dbmObsolete0无意义07STATUS状态寄存器Reserved70R/w只能为0RX_DR60R/w数据就绪RXFIFO中断,写1清除TX_DS50R/w数据发送RXFIFO中断,写1清除MAX_RT40R/w最大字节重发中断,写1清除RX_P_NO3:1111R数据通道号TX_FULL00RTX FIFO满标志(满为1)08OBSERVE_TX发射监测寄存器PLOS_CNT7:40R丢包计数ARC_CNT3:00R重发射数据包次数09RPD接收电源检测Reserved7:1000000R只能为0RPD00R接收电源检测0ARX_ADDR_P039:0E7E7E7E7E7R/W通道0接收地址(5个字节)0BRX_ADDR_P139:0C2C2C2C2C2R/W通道1接收地址(5个字节)0CRX_ADDR_P27:0C3R/W通道2接收地址(1个字节(低))0DRX_ADDR_P37:0C4R/W通道3接收地址(1个字节(低))0ERX_ADDR_P47:0C5R/W通道4接收地址(1个字节(低))0FRX_ADDR_P57:0C6R/W通道5接收地址(1个字节(低))10TX_ADDR39:0E7E7E7E7E7R/W发射地址,仅适用于PTX11RX_PW_P0在通道0中RX_Payload的字节个数Reserved7:600R/W只能为0RX_PW_P05:00R/W接收数据通道0字节数12RX_PW_P1………在通道1中RX_Payload的字节个数13RX_PW_P2………在通道2中RX_Payload的字节个数14RX_PW_P3………在通道3中RX_Payload的字节个数15RX_PW_P4………在通道4中RX_Payload的字节个数16RX_PW_P5………在通道5中RX_Payload的字节个数17FIFO_STATUSFIFIO状态寄存器Reserved70R/W只能为0TX_REUSE60R重用TX PayloadTX_FULL50RTX FIFO满标志TX_EMPTY41RTX FIFO空标志Reserved3:200R/W只能为0RX_FULL10RRX FIFO满标志RX_EMPTY01RRX FIFO空标志1CDYNPD使能动态数据包长度Reserved7:60R/W只能为0DPL_P550R/W使能pipe 5动态数据包长度DPL_P440R/W使能pipe 4动态数据包长度DPL_P330R/W使能pipe 3动态数据包长度DPL_P220R/W使能pipe 2动态数据包长度DPL_P110R/W使能pipe 1动态数据包长度DPL_P000R/W使能pipe 0动态数据包长度1DFEATURER/W特征寄存器Reserved7:30R/W只能为0EN_DPL20R/W使能动态数据包长度EN_ACK_PAY10R/W使能数据包应答EN_DYN_ACK00R/W使能写发送数据包非应答命令

如果NRF24L01用作具有Enhanced ShockedBurst 的PTX设备,那么将TX_ADDR寄存器里边的数值(地址)设置成通道0接收地址,使两个相同。

接下来看看该芯片的时序图: (1) 写时序 在这里插入图片描述 (2) 读时序 在这里插入图片描述 注:C7-C0 表示命令,S7~S0表示状态,Dx为数据位。 从时序上可以知道,读写时序是:先拉低片选CSN,在SCK的第一个上升沿开始传输数据;SCK在无效的状态下为低电平。这就给我们提示:在配置硬件SPI的时候要配置成SPI模式0(时钟极性0,时钟相位0)。 那么现在根据当前了解到的知识,配置STM32的SPI,这里使用标准库函数:

//初始化24L01的IO口 //这个程序是在开发板正点原子精英板F103上面调试的,SPI2的总线上挂载两个设备,将另一个设备(W25Q)的片选拉高,不选中该设备,防止它的影响 #define W25Q_CSN_PIN GPIO_Pin_12 #define NRF_CSN_PIN GPIO_Pin_7 //初始化相关的IO口(片选以及SPI) void MySPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOG, ENABLE); //使能PB,G端口时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//SPI2时钟使能 GPIO_InitStructure.GPIO_Pin = W25Q_CSN_PIN; //PB12上拉 防止W25X的干扰 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化指定IO GPIO_SetBits(GPIOB,W25Q_CSN_PIN);//上拉 GPIO_InitStructure.GPIO_Pin = NRF_CSN_PIN|NRF_CE_PIN; //PG8 7 推挽 GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化指定IO GPIO_InitStructure.GPIO_Pin = NRF_IRQ_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PG6 输入 GPIO_Init(GPIOG, &GPIO_InitStructure); GPIO_ResetBits(GPIOG,NRF_IRQ_PIN|NRF_CSN_PIN|NRF_CE_PIN);//PG6,7,8上拉 /****************************SPI2_Init*******************************/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉 SPI_Cmd(SPI2, DISABLE); // SPI外设不使能 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI设置为双线双向全双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI主机 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //发送接收8位帧结构 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第1个时钟沿 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由软件控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //定义波特率预分频的值:波特率预分频值为16 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式 SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 SPI_Cmd(SPI2, ENABLE); //使能SPI外设 /********************************************************************/ } //SPIx 读写一个字节 //TxData:要写入的字节 //返回值:读取到的字节 u8 SPI2_ReadWriteByte(u8 TxData) { u8 retry=0; while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位 { retry++; if(retry>200)return 0; } SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据 retry=0; while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位 { retry++; if(retry>200)return 0; } return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据 }

至此,SPI的初始化配置和通行API函数已经写好,接下来define下片选以及CE使能的IO口:

#define NRF24L01_CE PGout(8) //24L01片选信号 #define NRF24L01_CSN PGout(7) //SPI片选信号 /******下面的中断引脚程序上不用,调试的时候用过而已******/ #define NRF24L01_IRQ PGin(6) //IRQ主机数据输入

接下来就进一步根据时序图来编写读写函数了:

/*************************************************************** * 写寄存器 ****************************************************************/ uint8_t Write_Reg(uint8_t reg, uint8_t value) { uint8_t status; NRF24L01_CSN = 0; /* 选通器件 */ status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */ SPI2_ReadWriteByte(value); /* 写数据 */ NRF24L01_CSN = 1; /* 禁止该器件 */ return status; } /*************************************************************** * 读寄存器 ****************************************************************/ uint8_t Read_Reg(uint8_t reg) { uint8_t reg_val; NRF24L01_CSN = 0; /* 选通器件 */ SPI2_ReadWriteByte(reg); /* 写寄存器地址 */ reg_val = SPI2_ReadWriteByte(0); /* 读取该寄存器返回数据 */ NRF24L01_CSN = 1; /* 禁止该器件 */ return reg_val; }

扩展至连续读连续写,那么还有下面函数:

/**************************************************************** * 写缓冲区---------------------------------- *****************************************************************/ uint8_t Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uchars) { uint8_t i; uint8_t status; NRF24L01_CSN = 0; /* 选通器件 */ status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */ for(i=0; i uint8_t i; uint8_t status; NRF24L01_CSN = 0; /* 选通器件 */ status = SPI2_ReadWriteByte(reg); /* 写寄存器地址 */ for(i=0; i MySPI2_Init(); NRF24L01_CE = 0; Write_Reg(NRF_WRITE_REG + SETUP_AW, 0x03); Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(uint8_t *)RX_ADDRESS,5); Write_Buf(NRF_WRITE_REG+TX_ADDR,(uint8_t *)TX_ADDRESS,5); Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道0的自动应答 Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址 Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a); //设置自动重发间隔时间:500us;最大自动重发次数:10次 2M波特率下 Write_Reg(NRF_WRITE_REG+RF_CH,ch); //设置RF通道为CHANAL Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f); //设置TX发射参数,0db增益,2Mbps,低噪声增益开启 if(mode==1) //PRX { Write_Reg(NRF_WRITE_REG + CONFIG, 0x0f); // IRQ收发完成中断开启,16位CRC,主接收 Write_Reg(FLUSH_TX,0xff); Write_Reg(FLUSH_RX,0xff); Write_Reg(NRF_WRITE_REG+0x1c,0x01); Write_Reg(NRF_WRITE_REG+0x1d,0x06); } else if(mode==2) //TX2 { Write_Reg(NRF_WRITE_REG + CONFIG, 0x0e); // IRQ收发完成中断开启,16位CRC,主发送 Write_Reg(FLUSH_TX,0xff); Write_Reg(FLUSH_RX,0xff); Write_Reg(NRF_WRITE_REG+0x1c,0x01); Write_Reg(NRF_WRITE_REG+0x1d,0x06); } NRF24L01_CE = 1; }

数据传输涉及到发射数据包和接收数据包,另外,有必要检测NRF是否初始化正常,还需要检测环节。

//就是试着通过SPI写一串数据,然后再读出来,对比下,没问题返回1 u8 NRF_Check(void)//检查NRF模块是否正常工作 { u8 buf1[5]; u8 i; /*写入5个字节的地址. */ Write_Buf(NRF_WRITE_REG+TX_ADDR,(uint8_t *)TX_ADDRESS,5); /*读出写入的地址 */ Read_Buf(TX_ADDR,buf1,5); /*比较*/ for(i=0;i NRF24L01_CE = 0; //可向别的模块(地址)发射数据 Write_Buf(NRF_WRITE_REG + RX_ADDR_P0, TX_ADDRESS, 5); // 装载接收端地址 Write_Buf(WR_TX_PLOAD, tx_buf, len); // 装载数据 NRF24L01_CE = 1; //置高CE,激发数据发送 } void NRF_Send_Data(u8 *data , u8 length) { TxPacket(data,length); //while(NRF24L01_IRQ!=0); } void Receive_Data(void)//检查是否有通信事件 { u8 sta = Read_Reg(NRF_READ_REG + NRFRegSTATUS); //while(NRF24L01_IRQ!=0);//等待发送完成 //接收到数据包后,再对数据进行辨别 if(sta & (1 if(sta & 0x01) //TX FIFO FULL { Write_Reg(FLUSH_TX,0xff); } } //很多单片机或者是其他芯片,清除状态为都是往相应位写1 Write_Reg(NRF_WRITE_REG + NRFRegSTATUS, sta);//写1清除状态寄存器 sta = Read_Reg(NRF_READ_REG + NRFRegSTATUS); } void Data_Receive_PRO(void) { if((NRF24L01RXDATA[0] == 0x01) && (NRF24L01RXDATA[1]==0x02))//帧头 { Rec_ADC_Raw_Val = ((uint16_t)NRF24L01RXDATA[2] u8 blink_flag2=0; //简单点,省去配置通用定时器的麻烦,直接用滴答定时器 //注意这里开启滴答定时器中断就不要用正点原子的delay.c了 //一个完美的程序尽量不去用延时,占用CPU资源 //不精确延时可以自己写 SysTick_Config(SystemCoreClock / 1000);//开启滴答定时器中断(72000000/1000)/72MHz = 1ms,即定时1ms中断一次 uart_init(115200); //串口初始化为115200 LED_Init(); //初始化与LED连接的硬件接口 //下面的函数第一个入口参数为PTX,表示设置为主发送 //其实,作为两个nRF实现双向通讯,另一方只需要设置为PRX即可。 NRF_Init(PTX,80); //初始化NRF24L01 while(NRF_Check()==0) printf("NRF24L01 disconnected!/n"); while(1) { if(ms_10>100)//每100ms发射一次数据包,闪一次灯,表明正常执行 { ms_10 = 0; /*调试用****/ if(blink_flag2 == 0) { PEout(5) = 0; blink_flag2 = 1; } else if(blink_flag2 == 1) { PEout(5) = 1; blink_flag2 = 0; } /*****只自加这个元素*******/ Txdata_buffer1[2]++; NRF_Send_Data(Txdata_buffer1,7); } if(ms_2>2)//每2ms检查NRF是否有通信事件 { Receive_Data(); ms_2 =0; } if(ms_500>500)//每0.5秒led灯闪一次,串口打印接收到的ADC原始值。 { if(blink_flag == 0){ PBout(5) = 0; blink_flag = 1; } else{ PBout(5) = 1; blink_flag = 0; } printf("the Raw ADC val is %d.\n",Rec_ADC_Raw_Val); ms_500 = 0; } } }

上面的ms_2,ms_10,ms_500在文件stm32f10x_it.c中的滴答定时器中断更新。

void SysTick_Handler(void) { ms_10++; ms_500++; ms_2++; }

(2)主接收主程序demo:

//平台用的是潘多拉开发板stm32L475 int main(void) { HAL_Init(); SystemClock_Config(); //初始化系统时钟为80M delay_init(80); //初始化延时函数 80M系统时钟 Usart1_Init(115200); ADC1IN3_Init(); LED_Init(); //初始化LED NRF_Init(PRX,80); while(NRF_Check()==0); LCD_Init();//IIC接口屏 LCD_Clear(WHITE); TIM2_Init(100 - 1, 8000 - 1);//10ms //Iwdg_Init(); while(1) { LED_Function();//每0.5秒LED闪一次 Schedule_100ms();//每100ms在显示屏上面,这里只显示了在主发送方那边自加的Txdata_buffer1[2] Schedule_Rec_data();//每10ms检查有没有接收到数据 Schedule_Send_data();//每50ms发送数据包 } } void Schedule_Send_data(void) { uint8_t temp[10]; if(count_Send_data>5) { temp[0] = 0x01; temp[1] = 0x02; temp[2] = adc_test/256; temp[3] = adc_test%256; NRF_Send_Data(temp, 4); count_Send_data = 0; } } void Schedule_Rec_data(void) { if(count_Rec_data>1) { Receive_Data(); count_Rec_data = 0; } } void Schedule_100ms(void) { if(counter_100ms > 10) { Get_ADC(); Get_AHT10_Data(); LCD_ShowNum(100, 200, Rec_Test_Buf[0] , 5, 16); counter_100ms = 0; } } 总结 两个NRF24L01双向通讯,初始化程序除了配置寄存器的最低位不一样之外,其他都一样。(最低位切换PTX和PRX)一开始没设置收发地址宽度,导致两个模块通讯不上,查找了挺久,最后看了寄存器表试着设置了就OK了。上面的主接收主程序没写全,不过相信大家应该知道怎么写,跟主发送的差不多。本程序在两个开发板上已经调试正常运行


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭