STM32
直播中

kmno4

12年用户 902经验值
私信 关注
[问答]

如何实现NRF24无线通信一对多通信?

如何实现NRF24无线通信一对多通信?

回帖(1)

王丽婧

2021-12-17 14:28:02
NRF24无线通信最多可以发32个字节,因为它是无线的所以感觉很便捷。无线传参传输完rol、pit、yaw、thr、alt等值之后还有剩余的空间,可以利用起来。比如,无线调参,通过串口发送最先调的内环PID值给遥控器,然后遥控器通过NRF24传输给无人机进而改变无人机的PID值,方便调参。但是我在进行操作的时候写入flash里面,写一次经常不回成功,要写入2次或以上,有时还需要重启才能真正改变PID值,有bug,为了知道PID值是否改变你也可以在无人机那里将PID值参数给遥控器。说到PID调参,后期在出调参总结。调了半个月都调不好。
在调试的时候你也可以通过无线传输观察波形,如下:




接下来就讲一下我踩过的坑:
下面的TX_PAYLO_WIDTH RX_PAYLO_WIDTH 收发双方一定要相同





那发送地址和接收地址有是干啥的呢?o(´^`)o











所以发送端和接收端的地址要相同,那无人机和遥控器端的发送地址和接收地址是否需要相同?
经过我的测试,前4个一定要相同,最后一个最好相同,因为在小马哥的robofly的代码中,他可能是为了提高遥控器无人机对频的成功概率或者速度,对发送地址和接收地址的第五个进行了改变,并将其写入flash(后面后讲到)


#define SI24R1AddrMax 50 //NRF最后一个字节地址最大为50


uint8_t SI24R1addr = 0xFF; //初始化NRF最后一字节地址


uint8_t SI24R1_TX_DATA[TX_PAYLO_WIDTH];//NRF发送缓冲区
uint8_t SI24R1_RX_DATA[RX_PAYLO_WIDTH];//NRF接收缓冲区


uint8_t TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x06}; //发送地址
uint8_t RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x06}; //接收地址


配置相关引脚,并检测nrf24是否在线路上,如果不在线就是焊接错误了(95%是焊接错误,不要怀疑就是95%,在焊接元器件的时候我有70%的时间在焊接NRF24,因为它是无线的,看不见摸不着最难搞)至于这个函数SI24R1_Check(); //检查SI24R1是否与MCU通信 ,往NRF24里面某个可读可写的寄存器里写入读出即可


/*****************************************************************************
* 函  数:void SI24R1_Init(void)
* 功  能:NRF引脚GPIO初始化
* 参  数:无
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1_Init(void)
{
        GPIO_InitTypeDef GPIO_InitStruct;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOA,ENABLE);
       
        /*   配置CSN引脚   */
        GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12;
        GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
        GPIO_Init(GPIOB,&GPIO_InitStruct);
        GPIO_ResetBits(GPIOB,GPIO_Pin_12);
       
        /*  配置CE引脚  */
        GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
        GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
        GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
        GPIO_Init(GPIOA,&GPIO_InitStruct);
        GPIO_ResetBits(GPIOA,GPIO_Pin_8);
               
        SPI_GPIO_Init(); //SPI2初始化


        SI24R1_Check(); //检查SI24R1是否与MCU通信                                    


        SI24R1_CSN_HIGH; //失能NRF
        SI24R1_CE_LOW;          //待机模式
}


切换NRF24的工作模式:


/*****************************************************************************
* 函  数:void SI24R1set_Mode(uint8_t mode)
* 功  能:切换SI24R1的工作模式模式
* 参  数:无
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1set_Mode(uint8_t mode)
{
        if(mode == IT_TX)
        {
                SI24R1_CE_LOW;
                SI24R1_write_reg(W_REGISTER+CONFIG,IT_TX);
                SI24R1_write_reg(W_REGISTER+STATUS,0X7E); //清除所有中断,防止一进去发送模式就触发中断       
                SI24R1_CE_HIGH;
//                Delay_us(15);
        }
        else
        {
                SI24R1_CE_LOW;
                SI24R1_write_reg(W_REGISTER+CONFIG,IT_RX);//配置为接收模式
                SI24R1_write_reg(W_REGISTER+STATUS,0X7E); //清除所有中断,防止一进去接收模式就触发中断
                SI24R1_CE_HIGH;
                Delay_us(200);
        }               
}


下面的函数中值得注意的是或者说决你能否成功通信的是:通道是否自动应答、通道的有效数据宽度(因为只有接收达到这个宽度才会触发中断)和发射通道RF的频率


/*****************************************************************************
* 函  数:void SI24R1_Config(void)
* 功  能:SI24R1基本参数配置,并初始化为接收模式
* 参  数:无
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1_Config(void)
{
        SI24R1_CE_LOW;
        SI24R1_write_reg(W_REGISTER+SETUP_AW, 0x03); //配置通信地址的长度,默认值时0x03,即地址长度为5字节
        SI24R1_Write_Buf(W_REGISTER+TX_ADDR,(uint8_t*)TX_ADDRESS,TX_ADR_WIDTH); //写TX节点地址
        SI24R1_Write_Buf(W_REGISTER+RX_ADDR_P0,(uint8_t*)TX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK
        SI24R1_write_reg(W_REGISTER+SETUP_RETR,0x1A); //设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 0x1A
       
        SI24R1_write_reg(W_REGISTER+EN_RXADDR,0x01);//使能通道0的接收地址  
        SI24R1_write_reg(W_REGISTER+EN_AA,0x01); //使能通道0自动应答
        SI24R1_write_reg(W_REGISTER+RX_PW_P0,RX_PAYLO_WIDTH);//选择通道0的有效数据宽度  
        SI24R1_Write_Buf(W_REGISTER+RX_ADDR_P0,(uint8_t*)RX_ADDRESS,RX_ADR_WIDTH); //写RX节点地址
        SI24R1_write_reg(W_REGISTER+RF_CH,60); //设置RF通道为40hz(1-64Hz都可以)
        SI24R1_write_reg(W_REGISTER+RF_SETUP,0x27); //设置TX发射参数,0db增益,2Mbps,低噪声增益关闭 (注意:低噪声增益关闭/开启直接影响通信,要开启都开启,要关闭都关闭0x0f)
       
        SI24R1set_Mode(IT_RX); //默认为接收模式  2021-3-6 接收->发送
       
        SI24R1_CE_HIGH;
}


NRF24发送一包数据:


/*****************************************************************************
* 函  数:uint8_t SI24R1_TxPacket(uint8_t *txbuf)
* 功  能:SI24R1发送一包数据
* 参  数:txbuf:要发送数据地址
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1_TxPacket(uint8_t *txbuf)
{
        SI24R1_CE_LOW;       
        SI24R1_Write_Buf(W_REGISTER+TX_ADDR,(uint8_t*)TX_ADDRESS,TX_ADR_WIDTH);  //写TX节点地址
        SI24R1_Write_Buf(W_REGISTER+RX_ADDR_P0,(uint8_t*)TX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK
        SI24R1_Write_Buf(W_RX_PAYLOAD,txbuf,TX_PAYLO_WIDTH); //写数据到TX_BUFF
        SI24R1_write_reg(W_REGISTER+CONFIG,0x0e);        //设置为发送模式,开启所有中断
        SI24R1_write_reg(W_REGISTER+STATUS,0X7E); //清除所有中断,防止一进去发送模式就触发中断
        SI24R1_CE_HIGH;
        Delay_us(10);  //CE持续高电平10us
}


NRF24接收一包数据:


/*****************************************************************************
* 函  数:uint8_t SI24R1_RxPacket(uint8_t *rxbuf)
* 功  能:SI24R1接收一包数据
* 参  数:rxbuf:接收数据存储地址
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1_RxPacket(uint8_t *rxbuf)
{
        SI24R1_CE_LOW;
        SI24R1_Read_Buf(R_RX_PAYLOAD,rxbuf,TX_PAYLO_WIDTH);//读取RX的有效数据
        SI24R1_write_reg(FLUSH_RX,0xff); //清除RX FIFO(注意:这句话很必要)
        SI24R1_CE_HIGH;
}


注意robofly的代码中发送和接收地址的第五个就在这里面改变了,经过debug, SI24R1addr =0x16
给飞机获取上的SI24R1获取一个地址:


/*****************************************************************************
* 函  数:void SI24R1_GetAddr(void)
* 功  能:给飞机获取上的SI24R1获取一个地址
* 参  数:无
* 返回值:无
* 备  注:此函数需要与遥控器的对频函数联合使用否者SI24R1通信不成功,
          如果自己做的的遥控器可直接用固定地址
*****************************************************************************/
void SI24R1_GetAddr(void)
{
        if(SI24R1addr > SI24R1AddrMax)//当 SI24R1addr大于10,就说明次时SI24R1还未初始化完成
        {
                srand(SysTick->VAL);//给随机数种子
//                printf("SysTick->VAL:%drn",SysTick->VAL);
                SI24R1addr = rand()%SI24R1AddrMax;//随机获取SI24R1最后一位地址(地址:0~50)
                PID_WriteFlash();//保存此地址Flash
        }else if(SI24R1addr != TX_ADDRESS[TX_ADR_WIDTH-1])
        {
                TX_ADDRESS[TX_ADR_WIDTH-1] = SI24R1addr;
                RX_ADDRESS[TX_ADR_WIDTH-1] = SI24R1addr;
                SI24R1_Config();
//                printf("SI24R1Addr:%drn",SI24R1addr);
        }
}


上面是无人机的NRF24代码,下面是遥控器的相关代码:
配置引脚->NRF24配置->NRF24发送函数|NRF24接收函数->对频函数









u8  TX_ADDRESS[TX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0xFF};        //此地址用来识别接收端哪个RX通道可以接收发送出去的数据包
u8  RX_ADDRESS[RX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0xFF};        //此地址用来配置本机SI24R1的RX0通道的地址,同时为了能正常收到应答信号,此地址一般都和上面的地址配置相同


可以看到我的遥控器和无人机的发送接收地址不完全相同


/**********************************************************************
配置SI24R1为RX模式,准备开始接收数据
***********************************************************************/
void RX_Mode(void)
{
        CE_LOW;                                                                                        //拉低CE,进入待机模式,准备开始往SI24R1中的寄存器中写入数据
       
        SPI_Write_Byte(WRITE_REG_CMD + CONFIG, 0x0f);         //配置为接收模式
        SPI_Write_Byte(WRITE_REG_CMD + STATUS, 0x7e);        //写0111 xxxx 给STATUS,清除所有中断标志,防止一进入接收模式就触发中断
       
        CE_HIGH;                                                                             //拉高CE,准备接受从外部发送过来的数据
}
/**********************************************************************
从SI24R1的RX的FIFO中读取一组数据包
输入参数rx_buf:FIFO中读取到的数据的保存区域首地址
***********************************************************************/
void SI24R1_ReceivePacket(u8* rx_buf)
{
        CE_LOW;
        SPI_Read_Buf(RD_RX_PLOAD,rx_buf,RX_PLOAD_WIDTH);        //从RX端的FIFO中读取数据,并存入指定的区域,注意:读取完FIFO中的数据后,SI24R1会自动清除其中的数据
        SPI_Write_Byte(FLUSH_RX,0xff);                           //清除接收FIFO(很必要)
        CE_HIGH;                                                                                        //重新拉高CE,让其重新处于接收模式,准备接收下一个数据
}
/**********************************************************************
配置SI24R1为TX模式,并发送一个数据包
输入参数tfbuf:即将要发送出去的数据区首地址
***********************************************************************/
void SI24R1_SendPacket(u8* tfbuf)
{
        CE_LOW;                                                                                                                                        //拉低CE,进入待机模式,准备开始往SI24R1中的寄存器中写入数据
       
//        SPI_Write_Buf(WRITE_REG_CMD + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH);         //装载接收端地址,由于这里只有一个通道通讯,不用改变接收端的SI24R1的接收通道地址,所以,这句可以注释掉
        SPI_Write_Buf(WR_TX_PLOAD, tfbuf, TX_PLOAD_WIDTH);                         //将数据写入TX端的FIFO中,写入的个数与TX_PLOAD_WIDTH设置值相同
       
        SPI_Write_Byte(WRITE_REG_CMD + CONFIG, 0x0e);                                 //将SI24R1配置成发射模式
        SPI_Write_Byte(WRITE_REG_CMD + STATUS, 0x7e);                                //写0111 xxxx 给STATUS,清除所有中断标志,防止一进入发射模式就触发中断
       
        CE_HIGH;                                                                                                        //拉高CE,准备发射TX端FIFO中的数据
       
        delay_ms(1);                                                                                                //CE拉高后,需要延迟至少130us
}


在下面的飞机对频函数中同样是改变了发送接收地址中的第五个值。进入一个while循环直到遇到return才会跳出(也就是它一定进入那个条件判断里面)


/* 遥控器飞机对频(其实是对地址) */
void WaitFlY_Connection(void)
{
        static u8 cnt = 0,preaddr;
        ConnectingDisplay();//断线连接状态显示
        while(1)
        {
                if(FLY_Connect_OK)
                {
                        cnt = 0;
                        if(preaddr != TX_ADDRESS[TX_ADR_WIDTH-1])
                        {
                                PID_WriteFlash();  
                                //保存上一次连接的飞机的SI24R1地址
//                                printf("Address save :%d preaddr:%drn",TX_ADDRESS[4],preaddr);
                        }
//                        printf("Fly connect OK!!!rn");
                        return;
                }else if(cnt++ < 10)
                {
                        PID_ReadFlash();   //读取上一次保存的飞机的SI24R1地址
                        preaddr = TX_ADDRESS[TX_ADR_WIDTH-1];
                        SI24R1_Config();
                        delay_ms(50);
//                        printf("Flash read SI24R1addr:%drn",TX_ADDRESS[4]);
                }
                else
                {
                        TX_ADDRESS[TX_ADR_WIDTH-1]++ ;
                        RX_ADDRESS[TX_ADR_WIDTH-1]++ ;
                        if(TX_ADDRESS[TX_ADR_WIDTH-1]>AddrMax && RX_ADDRESS[TX_ADR_WIDTH-1]>AddrMax)
                        {
                                TX_ADDRESS[TX_ADR_WIDTH-1] = 0x00;
                                RX_ADDRESS[TX_ADR_WIDTH-1] = 0x00;
                        }
                        SPI_Write_Buf(WRITE_REG_CMD + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);           
                        SPI_Write_Buf(WRITE_REG_CMD + RX_ADDR_P0, RX_ADDRESS, RX_ADR_WIDTH);
                        delay_ms(100);
                }
                if(ADC_CALIBRATOR_OK)
                {
                        OLED_ShowString(byte(2),line4,"Calibration",8);
                }
                else
                {
                        OLED_ShowString(byte(2),line4,"Connecting...",8);
                }
        }
}




下面来讲一下无人机发送数据给遥控器和正点原子开发板,然后开发板再传输数据到上位机上
其实我这个都是用的通道0,通道0的发送地址和通道0的接收地址,然后还能和2个或者2个以上的从机进行无线通信而互相之间没有干扰,那就要做到和每个从机的通信频率不一样,而且相差较大。其中关键的代码如下:





因为默认的发送频率是60HZ,所以第一次SendToRemote(); 是发送给遥控器的,第二次发送前先改变一下频率,改为10HZ,然后再发送,发送完成之后再改回来。
参考:https://blog.csdn.net/u012780337/article/details/90638629
那如果是同一个发送频率,不同的通道应该也能实现相同的效果










具体可以参考:一对多通信https://blog.csdn.net/m0_37968313/article/details/99453557
https://blog.csdn.net/m0_37968313/article/details/98219062
看下面可以看到相同频率干扰之大!


举报

更多回帖

发帖
×
20
完善资料,
赚取积分