完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
NRF24L01双向传输(一对一)
简介 本文章记录两个NRF24L01无线模块实现双向传输的软件设计~ 为什么可以双向传输呢?这要归功于它具有Enhanced ShockBurst,可以工作在主接收和主发送模式,在主接收方可以将我们自己的数据附在ACK Packet实现双向传输,所以此次双向传输会将两个NRF分别设置成主接收和主发送。 · 实物(模块非单独芯片): 网上最便宜的就这种不带运放的模块,大家如果想用NRF24L01的话,建议大家使用加了运放的模块 ,传输距离会比较远一点,没运放的话距离10米就开始出现丢包现象了。另外,进口的和国产的用起来距离一样,所以优先选择国产,因为进口的还贵点。 ·芯片引脚定义说明(芯片): ·**数据包格式 **: 这里没必要介绍NRF24L01了,相信要用这芯片或者模块的朋友应该提前了解过了。 · 寄存器表 该芯片有命令寄存器和功能寄存器。在使用每一款芯片之前,要养成查看数据手册的习惯。这是我自己边看边翻译的,可能有翻译地不对的,见谅哈。 命令寄存器表: [tr]命令名命令字#数据字节操作[/tr]
功能寄存器地址映射表: [tr]地址助记符位复位值类型描述[/tr]
接下来看看该芯片的时序图: (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 SPI2_ReadWriteByte(pBuf); /* 写数据 */ } NRF24L01_CSN = 1; /* 禁止该器件 */ return status; } /**************************************************************** * 读缓冲区------------------------------- ****************************************************************/ uint8_t Read_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 pBuf = SPI2_ReadWriteByte(0); /* 读取返回数据 */ } NRF24L01_CSN = 1; /* 禁止该器件 */ return status; } |
|||||
|
|||||
至此,单片机与NRF芯片的通信手段已经搭好了,可以开始配置NRF24L01(+)了。
首先声明寄存器 #define TX_ADR_WIDTH 5 #define RX_ADR_WIDTH 5 #define RX_PLOAD_WIDTH 50 //最大64 #define TX_PLOAD_WIDTH 50 //最大64 //***************************************NRF24L01寄存器指令******************************************************* #define NRF_READ_REG 0x00 // 读寄存器指令 #define NRF_WRITE_REG 0x20 // 写寄存器指令 #define R_RX_PL_WID 0x60 // 读取RX payload宽度指令 #define RD_RX_PLOAD 0x61 // 读取接收数据指令 #define WR_TX_PLOAD 0xA0 // 写待发数据指令 #define FLUSH_TX 0xE1 // 冲洗发送 FIFO指令 #define FLUSH_RX 0xE2 // 冲洗接收 FIFO指令 #define REUSE_TX_PL 0xE3 // 定义重复装载数据指令 #define NOP 0xFF // 保留 //*************************************(nRF24L01)寄存器映射地址**************************************************** #define CONFIG 0x00 // 配置收发状态,CRC校验模式以及收发状态响应方式 #define EN_AA 0x01 // 自动应答功能设置 #define EN_RXADDR 0x02 // 可用信道设置 #define SETUP_AW 0x03 // 收发地址宽度设置 #define SETUP_RETR 0x04 // 自动重发功能设置 #define RF_CH 0x05 // 工作频率设置 #define RF_SETUP 0x06 // 发射速率、功耗功能设置 #define NRFRegSTATUS 0x07 // 状态寄存器 #define OBSERVE_TX 0x08 // 发送监测功能 #define CD 0x09 // 地址检测 #define RX_ADDR_P0 0x0A // 频道0接收数据地址 #define RX_ADDR_P1 0x0B // 频道1接收数据地址 #define RX_ADDR_P2 0x0C // 频道2接收数据地址 #define RX_ADDR_P3 0x0D // 频道3接收数据地址 #define RX_ADDR_P4 0x0E // 频道4接收数据地址 #define RX_ADDR_P5 0x0F // 频道5接收数据地址 #define TX_ADDR 0x10 // 发送地址寄存器 #define RX_PW_P0 0x11 // 接收频道0接收数据长度 #define RX_PW_P1 0x12 // 接收频道1接收数据长度 #define RX_PW_P2 0x13 // 接收频道2接收数据长度 #define RX_PW_P3 0x14 // 接收频道3接收数据长度 #define RX_PW_P4 0x15 // 接收频道4接收数据长度 #define RX_PW_P5 0x16 // 接收频道5接收数据长度 #define FIFO_STATUS 0x17 // FIFO栈入栈出状态寄存器设置 #define DYNPD 0x1C // 使能动态数据包长度 #define FEATURE 0x1D // 主要使能DYNPD 下面就开始写NRF的初始化函数: void NRF_Init(u8 mode, u8 ch)//初始化,mode=PTX/PRX,ch频宽 { 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<5;i++) { if(buf1!=TX_ADDRESS) break; } if(i==5) return 1; //MCU与NRF成功连接 else return 0; //MCU与NRF不正常连接 } /**************************************************************** *打包发送 *****************************************************************/ void TxPacket(uint8_t * tx_buf, uint8_t len) { 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< u8 rx_len = Read_Reg(R_RX_PL_WID); Read_Buf(RD_RX_PLOAD,NRF24L01RXDATA,rx_len); //Data_Receive_PROGRAM printf("..."); Data_Receive_PRO(); } 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]<<8) + NRF24L01RXDATA[3]; } else return; } 至此,NRF24L01的驱动程序已经准备好了,接下来主程序调用: (1)主发送主程序demo: #include "led.h" #include "sys.h" #include "usart.h" #include "24l01.h" uint16_t ms_2=0,ms_10=0,ms_500=0; uint8_t Txdata_buffer1[64]={0x01,0x02,0x12,0x34,0x56,0x78,0x9A,0xBC,0xDE}; uint16_t Rec_ADC_Raw_Val; static u8 blink_flag=0; int main(void) { 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; } } 总结
|
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1752 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1611 浏览 1 评论
1052 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1666 浏览 2 评论
1924浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
711浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
560浏览 3评论
583浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
544浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 12:17 , Processed in 1.115068 second(s), Total 81, Slave 64 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号