小马哥robofly四轴无人机代码解读:NRF24无线通信一对多通信-无线传参

首先献上NRF24L01的中文开发手册:https://pan.baidu.com/s/1exzhUFWcM6Q4R9JpYWnaYw
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:%d\r\n",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:%d\r\n",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:%d\r\n",TX_ADDRESS[4],preaddr);
			}
//			printf("Fly connect OK!!!\r\n");
			return;
		}else if(cnt++ < 10)
		{
			PID_ReadFlash();   //读取上一次保存的飞机的SI24R1地址
			preaddr = TX_ADDRESS[TX_ADR_WIDTH-1];
			SI24R1_Config();
			delay_ms(50);
//			printf("Flash read SI24R1addr:%d\r\n",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

看下面可以看到相同频率干扰之大!
在这里插入图片描述

  • 4
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值