首先献上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
看下面可以看到相同频率干扰之大!