stm32基于两个SI24R1(NRF24L01)的遥控控制原理(可移植diy遥控小车)

SI24R1是一款性能出色且应用广泛的国产2.4GHz无线收发芯片,SI24R1在软件和硬件上完全兼容 Nordic 公司的 nRF24L01+。这意味着,大多数为 nRF24L01+ 编写的代码和现有电路板,可以无缝迁移或直接使用 SI24R1,项目移植和开发门槛大大降低。

SI24R1支持多种发射功率等级。在需要更远通信距离时,可以设置为最高的+7dBm;在近距离或对功耗要求极严苛的场景,可以调低功率以节省能耗。

芯片集成了ARQ(自动重传请求)基带协议引擎,自动处理数据包的应答、重传和校验。这能减轻主控MCU的负担,简化编程,并提高通信的可靠性。

下面这个表格汇总了它的核心特性:

特性类别 具体参数
📡 工作频段 2400-2525MHz ISM 频段
📶 发射功率 可调,最高 +7dBm
💨 数据传输速率 2Mbps, 1Mbps, 250Kbps
🔋 功耗表现 关断模式 0.7μA ,待机模式 15μA
工作电压 1.9V - 3.6V
🤝 接口与协议 SPI 接口,内置 ARQ 基带协议引擎
🏭 封装形式 QFN20 或 COB 封装

今天我们用之前开源的stm32四轴飞行器的代码来介绍一下stm32基于两个SI24R1(NRF24L01)的遥控控制原理以及遥控代码的移植教程,这样大家就可以通过两个32和两个SI24R1(NRF24L01)自行制作遥控赛车以及各种遥控小玩意了。这里的SI24R1是通过SPI通信的,SPI通信原理之前我也说过一点,感兴趣的可去之前的文章自行观看。其实我们想要使用遥控部分的代码,主要需要自己diy的部分就是下图中两个摇杆和几个按键以及自己diy想要显示的oled内容,其它的SI24R1通信原理部分都是复制粘贴即可,当然感兴趣的也可以深入了解一下。

1.1.首先看遥控器端的主函数main.c,其中必须移植的代码已经在注释里标出来了,接下来我们一一介绍必须移植的代码部分。

cs 复制代码
extern u8 ADC_CALIBRATOR_OK; 

int main(void)
{
	USART_Config();//串口初始化,非必须移植,调试用
	//printf("usart is ready\r\n");
	SysTick_Init();//系统时钟初始化
	OLED_Init();//显示屏初始化,建议移植
	NVIC_Config();//中断优先级管理初始化,必须移植
	BUTTON_Config();//按键初始化,必须移植
	LED_Config();//led灯初始化,非必须移植,指示灯
	LED_On(LED1);//点亮led1
	SI24R1_Config();//NRF24L01初始化,必须移植
	TIM_OCTigrConfig();//定时器初始化,必须移植
	ADC_DmaConfig();//这个必须移植且最后一个配置,因为一旦配置好这个,ADC就会开始工作了,则DMA会开始每个一段时间产生中断,或者先关闭总中断,最后所有都配置完毕后在打开总中断

	WaitFlY_Connection();//SI24R1等待连接/对频函数
	Display_init();//oled初始化显示函数
	
	while(1)
	{
		Display_Update();//OLED更新显示
		ReconnectionFly();//断线重连
	}
}

1.2.OLED的代码需要的可以私聊获取,就不单独讲了,首先看一下中断优先级管理,其中除了系统时钟使用到中断以外,两个SI24R1连接后IRQ引脚也会触发外部中断配置一下模式,提示一些信息(LED,可自定义)以及清除标志位等,还有DMA将ADC的数据运输完成会触发中断将摇杆的信息发送出去,以及按键触发的外部中断将按键的信息发送出去。

cs 复制代码
//中断优先级组
#define NVIC_PRIORITY_GROUP_3          3 /* Preemption: 4 bits / Subpriority: 0 bits                        */
#define NVIC_PRIORITY_GROUP_4          4 /* Preemption: 3 bits / Subpriority: 1 bits                        */
#define NVIC_PRIORITY_GROUP_5          5 /* Preemption: 2 bits / Subpriority: 2 bits                        */
#define NVIC_PRIORITY_GROUP_6          6 /* Preemption: 1 bits / Subpriority: 3 bits                        */
#define NVIC_PRIORITY_GROUP_7          7 /* Preemption: 0 bits / Subpriority: 4 bits                        */


//为所有用到的中断配置抢占优先级和响应优先级
void NVIC_Config(void)
{
	//SysTick_IRQn中断由于在有些中断函数中也会使用到,所以它的优先级是最高的
	NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5, 0, 0)); 			//SysTick_IRQn中断
	NVIC_SetPriority(EXTI1_IRQn, NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5, 1, 0));				  //EXTI1_IRQn中断,SI24R1的IRQ引脚中断
	NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5, 1, 1));	//DMA1_Channel1_IRQn中断,DMA将ADC数据传输完成中断
	NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5, 2, 2));			//EXTI15_10_IRQn中断,按键触发中断(优先级必须小于EXTI1_IRQn否则会导致SI24R1l连接中断)
}

1.3.因为按键的初始化代码就是对gpio的初始化,就不细看了我们只需知道是哪几个gpio口即可,后面会说按键触发外部中断的代码,接下来看SI24R1的代码,这部分主要是SI24R1的初始化代码以及一些模式配置,发送数据,连接对频的包装函数这些对于我们使用来说都是不用动的,直接移植过去即可,唯一可以diy的就是SI24R1的IRQ引脚触发的外部中断里面的led指示灯可根据自己喜好diy。这里的逻辑是:

  • LED1闪烁:连接成功但未解锁

  • LED1常亮:已解锁并通信中

  • LED1熄灭:通信中断

cs 复制代码
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通道的地址,同时为了能正常收到应答信号,此地址一般都和上面的地址配置相同

//发生中断时,根据STATUS寄存器中的值来判断是哪个中断源触发了IRQ中断
#define TX_DS			  0x20			//数据发送完成中断
#define RX_DR			  0x40			//数据接收完成中断
#define MAX_RT			0x10		  //数据包重发次数超过设定值中断
#define AddrMax     50        //SI24R1最后一位地址最大值
//=====================================全局变量定义区========================================
vu8 sta;								     //接收从STATUS寄存器中返回的值
u8 RxBuf[RX_PLOAD_WIDTH];		 //数据接收缓冲区,数据发送缓冲区
u8 FLYDataRx_OK = 0;         //飞机数据接收完成
u8 FLY_Connect_OK = 0;       //遥控与飞机已连接
u8 Reconnection_flag = 0;    //遥控与飞机已断开需要重连
extern vu8 ButtonMask;			 //用来保存哪个按键刚被按下了,原始定义在:button.c文件中
extern u16 TX_CNT ;          //遥控数据发送计数,原始定义在:senddata.c文件中
extern u16 TX_ERROR ;        //遥控数据发送失败计数,原始定义在:senddata.c文件中
extern u8 ADC_CALIBRATOR_OK; //遥控通道ADC校准标志,原始文件在:adc_dma.h中
//========================================================================================

/**********************************************************************
初始化SI24R1
***********************************************************************/
void SI24R1_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructus;
	EXTI_InitTypeDef EXTI_initStructure;
	
	//初始化SPI接口
	SPI_Config();
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
	
	//初始化CE引脚
	GPIO_InitStructus.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructus.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructus.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructus);

	//配置IRQ引脚
	GPIO_InitStructus.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructus.GPIO_Mode = GPIO_Mode_IPU; //nRF中断产生时,IRQ引脚会被拉低,所以这里要配置成上拉输入
	GPIO_InitStructus.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructus);

	EXTI_initStructure.EXTI_Line = EXTI_Line1;
	EXTI_initStructure.EXTI_LineCmd = ENABLE;
	EXTI_initStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_initStructure.EXTI_Trigger = EXTI_Trigger_Falling;	//下降沿触发外部中断
	EXTI_Init(&EXTI_initStructure);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);//开启GPIO管脚的中断线路
	NVIC_EnableIRQ(EXTI1_IRQn);

	CE_LOW;	//拉低CE,注意:读/写nRF寄存器均需要将CE拉低,使其进入待机或者掉电模式才可以
	
	//初始化SI24R1
	SPI_Write_Byte(WRITE_REG_CMD + SETUP_AW, 0x03);							//配置通信地址的长度,默认值时0x03,即地址长度为5字节
	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); 	
	SPI_Write_Byte(WRITE_REG_CMD + SETUP_RETR, 0x1a); 						//自动重发延迟为500+86us,重发次数10次
	SPI_Write_Byte(WRITE_REG_CMD + EN_AA, 0x01);      						//接收数据后,只允许频道0自动应答
	SPI_Write_Byte(WRITE_REG_CMD + EN_RXADDR, 0x01);  						//只允许频道0接收数据
	
	SPI_Write_Byte(WRITE_REG_CMD + RF_SETUP, 0x27);   						//设置发射速率为2MHZ,发射功率为最大值0dB
	SPI_Write_Byte(WRITE_REG_CMD + RF_CH, 30);        						//设置通道通信频率,工作通道频率可由以下公式计算得出:Fo=(2400+RF-CH)MHz.并且射频收发器工作的频率范围从2.400-2.525GHz
	SPI_Write_Byte(WRITE_REG_CMD + RX_PW_P0, RX_PLOAD_WIDTH); 				//设置接收数据长度,本次设置为5字节,只有接收的数据达到此长度时,才会触发RX_DS中断
	
	SPI_Write_Byte(WRITE_REG_CMD + CONFIG, 0x0f);   						//默认处于接收模式

	CE_HIGH;
}

//IRQ引脚对应的EXTI外部中断处理函数
void EXTI1_IRQHandler(void)
{
	CE_LOW;		//拉低CE,以便读取SI24R1中STATUS中的数据
	sta = SPI_Read_Byte(READ_REG_CMD+STATUS);		//读取STATUS中的数据,以便判断是由什么中断源触发的IRQ中断
	
	if(sta & TX_DS)     //数据发送成功,并且收到了应答信号
	{										
		RX_Mode();		//将SI24R1的模式改为接收模式,等待接收数据
		if(ButtonMask & 0x01)
		{
			LED_On(LED1);	//LED1常亮表示飞机已经解锁成功,并正在进行数据通讯,这里可自行diy
		}else
		{
			LED_Toggle(LED1);	//LED1闪烁表示飞机加锁模式中,但是和飞机通讯成功,这里可自行diy
		}
		SPI_Write_Byte(WRITE_REG_CMD+STATUS,TX_DS);	//清除TX_DS中断
		SPI_Write_Byte(FLUSH_TX,0xff);              //清空TX_FIFO
	}
	else if(sta & RX_DR)  	//数据接收成功
	{	
		FLY_Connect_OK	= 1;		 //飞机与遥控器已连接
		SI24R1_ReceivePacket(RxBuf);	//将数据从RX端的FIFO中读取出来
		FLYDataRx_OK 	= 1;
		SPI_Write_Byte(WRITE_REG_CMD+STATUS,RX_DR);	//清除RX_DR中断
	}
	else if(sta & MAX_RT)   	//触发了MAX_RT中断
	{		
		if(TX_ERROR++>100)         //遥控数据丢帧计数
		{
			Reconnection_flag = 1;		 //重连标志置位
			FLY_Connect_OK = 0 ;       //飞机与遥控器断开连接
		}
		LED_Off(LED1);			 //LED1灭时,表示和飞机通讯中断,这里可自行diy
		RX_Mode();					 //将SI24R1的模式改为接收模式,等待接收数据
		SPI_Write_Byte(WRITE_REG_CMD+STATUS,MAX_RT);	  //清除MAX_RT中断
		SPI_Write_Byte(FLUSH_TX,0xff);     //清空TX_FIFO
	}
	EXTI_ClearITPendingBit(EXTI_Line1);
}

/**********************************************************************
配置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
}

/* 遥控器飞机对频(其实是对地址) */
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);
		}
	}
}

//断线重连函数
void ReconnectionFly(void)
{
	if(Reconnection_flag)
	{
		ConnectingDisplay();
		if(FLY_Connect_OK)        //遥控已重新连接
		{
			Display_init();
			Reconnection_flag = 0;  //断线重连标志复位            
			return;
		}
		SI24R1_Config();
	}
}

1.4.然后看一下定时器初始化,这里主要利用定时器TIM4的通道4的输出比较功能,给ADC1提供采样触发信号,也就是定时器4中断触发一次,adc就采样一次摇杆信息,这里触发周期为:100ms,也是直接复制移植使用即可。

cs 复制代码
void TIM_OCTigrConfig(void)
{
	TIM_TimeBaseInitTypeDef TIM_timeBaseStucture;
	TIM_OCInitTypeDef TIM_ocInitStructure;
	
	u16 period 	= 10000;										//设置周期值100ms发送一次
	u16 pluse 	= 5000;											//设置CRR值,这里的值可以随便设置,不影响ADC采样周期,但要保证在正确的范围内
	
	//使能TIM4的时钟,TIM4是挂在APB1总线上的,注意TIM4的时钟是72M的
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	
	TIM_timeBaseStucture.TIM_ClockDivision = TIM_CKD_DIV1;      	//分频因子,输出给定时器的ETRP数字滤波器提供时钟
	TIM_timeBaseStucture.TIM_Prescaler = 720-1;                		//预分频,给TIMx_CNT驱动的时钟120
	TIM_timeBaseStucture.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
	TIM_timeBaseStucture.TIM_Period = period;						//设置周期,给ARR赋值
	TIM_TimeBaseInit(TIM4,&TIM_timeBaseStucture);
	
	//配置TIM4通道4的输出比较
	TIM_ocInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//PWM输出模式为PWM1
	TIM_ocInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//设置有效电平的极性
	TIM_ocInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//使能通道输出
	TIM_ocInitStructure.TIM_Pulse = pluse;							//设置PWM的脉冲宽度值,即CRR值
	TIM_OC4Init(TIM4,&TIM_ocInitStructure);
	
	TIM_ARRPreloadConfig(TIM4,ENABLE);								//使能TIM4的寄存器ARR的预装载功能,DISABLE时将会使改变ARR值时立即生效
	TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Enable);				//使能TIM4通道1的CCR的预装载功能,DISABLE时将会使改变CRR值时立即生效
	
	//使能TIM4定时器
	TIM_Cmd(TIM4,ENABLE);
}

1.5.接下来看一下**ADC(采集摇杆信息用)**的代码,这部分包括了ADC的初始化(包含DMA)和ADC通道的校准函数(消除误差用),也是直接移植过去即可,看到这里你会发现初始化函数都说完了,主循环里也没有遥控的代码,需要我们修改移植的遥控代码到底在哪?别急,他们都在中断里,另外封装了函数,下面就说。

cs 复制代码
u8 ADC_CALIBRATOR_OK;  //遥控通道ADC校准标志
//用来保存ADC各通道转换完成后的数据
vu16 ADC_ConvertedValue[4];  //遥控通道ADC值
vu16 ADC_Calibrator[4];      //遥控通道ADC校准值
void ADC_DmaConfig(void)
{
	GPIO_InitTypeDef GPIO_initStructure;
	DMA_InitTypeDef DMA_initStructure;
	ADC_InitTypeDef ADC_initStructure;
	
	//开启DMA1时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//配置DMA1通道1,将ADC采样转换得到的数据传输到内存数组中
	DMA_initStructure.DMA_BufferSize = 4;								//每次传输的数据的个数,传输完时触发中断
	DMA_initStructure.DMA_DIR = DMA_DIR_PeripheralSRC;					//传输方向为:外设->内存
	DMA_initStructure.DMA_M2M = DMA_M2M_Disable;								//失能内存到内存的传输方式
	DMA_initStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;				//数据保存到内存中数组的首地址(这里因为ADC_ConvertedValue是数组名,所以不用加&)
	DMA_initStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//以16位为单位进行数据的传输
	DMA_initStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//内存地址自增(这里地址是每次增加1,因为是以半字(16位)作为单位传输的)
	DMA_initStructure.DMA_Mode = DMA_Mode_Circular;								//循环传输的方式,这里必须为循环传输方式,否则会导致DMA只能传输一次
	DMA_initStructure.DMA_PeripheralBaseAddr = ((u32)&ADC1->DR);				//&ADC1->DR
	DMA_initStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//以半字为单位进行数据的传输
	DMA_initStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址固定
	DMA_initStructure.DMA_Priority = DMA_Priority_Medium;						//DMA通道1的优先级设置为中级,(这个优先级是当同一个DMA的不同通道同时有传输数据的要求时,优先级高的先进行传输)
	DMA_Init(DMA1_Channel1,&DMA_initStructure);
	
	DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);								//打开DMA通道1数据传输完成中断
	NVIC_EnableIRQ(DMA1_Channel1_IRQn);											//打开NVIC中对应的DMA通道1的中断通道
	
	//开启DMA1的通道1
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	//使能ADC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
	
	GPIO_initStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;	//ADC1的通道0,1,2,3
	GPIO_initStructure.GPIO_Mode = GPIO_Mode_AIN;		//ADC输入管脚需要为模拟输入模式
	GPIO_Init(GPIOA,&GPIO_initStructure);
	
	//配置ADC1
	ADC_initStructure.ADC_ContinuousConvMode = DISABLE;			//单次采样模式,每次由TIM4的CCR触发采样开始
	ADC_initStructure.ADC_DataAlign = ADC_DataAlign_Right;					//数据对齐模式为:右对齐
	ADC_initStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;		//TIM4的通道4的CCR触发采样
	ADC_initStructure.ADC_Mode = ADC_Mode_Independent;							//各通道独立
	ADC_initStructure.ADC_NbrOfChannel = 4;									    //一共要采样的通道的数目
	ADC_initStructure.ADC_ScanConvMode = ENABLE; //打开扫描模式,由于这里有四个通道要采集,所以开始用扫描模式
	ADC_Init(ADC1,&ADC_initStructure);
	
	//开启ADC
	ADC_Cmd(ADC1,ENABLE);
	
	//开启ADC------DMA数据传输通道
	ADC_DMACmd(ADC1,ENABLE);
	
	//配置ADC采样参考时钟的预分频值
	RCC_ADCCLKConfig(RCC_PCLK2_Div8);											
	
	//配置ADC的规则通道的采样顺序和采样时间
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);	
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_239Cycles5);
	
	ADC_ResetCalibration(ADC1);										//重置ADC采样校准器,防止出现较大的误差
	while(ADC_GetCalibrationStatus(ADC1));							//等待校准成功
	ADC_StartCalibration(ADC1);										//开启ADC采样状态
	while(ADC_GetCalibrationStatus(ADC1));							//等到开启成功
	
	//使能外部触发ADC采样
	ADC_ExternalTrigConvCmd(ADC1,ENABLE);
	
}

//ADC通道校准函数,60次采样平均,消除随机误差
void ADC_Calibration(void)
{
	static u8 cnt=0;// 采样计数
	static vs32 temp1=0,temp2=0,temp3=0,temp4=0;// 各通道累加和
	if(ADC_ConvertedValue[2]>1900&&ADC_ConvertedValue[2]<2196&&ADC_ConvertedValue[3]>1900&&ADC_ConvertedValue[3]<2196)
	{
		if(0==cnt)
		{
			temp1 = 0;
			temp2 = 0;
			temp3 = 0;
			temp4 = 0;
			cnt = 0;
		}
		temp1 += (1000 + ADC_ConvertedValue[0]*1000/4096);
		temp2 += (1000 + ADC_ConvertedValue[1]*500/4096);
		temp3 += (1000 + ADC_ConvertedValue[2]*1000/4096);
		temp4 += (1000 + ADC_ConvertedValue[3]*1000/4096);
		cnt++;
		if(cnt >= 60)
		{
			ADC_Calibrator[0] = temp1/cnt;
			ADC_Calibrator[1] = temp2/cnt;
			ADC_Calibrator[2] = temp3/cnt;
			ADC_Calibrator[3] = temp4/cnt;
			ADC_CALIBRATOR_OK = 0;
			cnt = 0;
			PID_WriteFlash();
		}
	}
}

1.6.接下来我们看按键中断和DMA中断,这里基本上也是不用动直接移植过去即可,因为按键触发的动作代码需要我们diy的都在被遥控端,遥控端只需将按下按键的信息发送过去即可,在这里我们也能看到四个按键使用的gpio口,然后数据丢包函数直接移植过去即可,最后DMA中断函数先判断需不需要adc校准,不需要就直接将摇杆adc数据发送出去,需要就先校准再发送。这里区分摇杆和按键是通过数据打包的前导码来区分的,区分方式如下:

  • 0x01 = ADC采样定时触发(摇杆数据)

  • 0x08 = 按键触发(按钮状态变化)

cs 复制代码
/*
0x01:连续的控制数据(摇杆位置)ADC采样触发发送
0x08:离散的事件数据(按钮状态)按键触发发送
*/

u16 TX_CNT = 0;                   //遥控数据发送计数
u16 TX_ERROR = 0;                 //遥控数据发送失败计数
float TX_ERROR_PERCENT = 0;       //遥控数据丢包率
extern u8 FLY_Connect_OK;         //飞机数据接收完成
extern u8 Reconnection_flag;      //遥控与飞机已连接
extern u8 ADC_CALIBRATOR_OK;      //遥控通道ADC校准标志,原始文件在:adc_dma.h中
extern u8 packetData[11];			 //打包后待发送的数据包,原始定义在:nRF.c文件中
extern vu8 ButtonMask;				 //标记哪个按钮被点击了,原始定义在:button.c文件中
extern vu16 ADC_ConvertedValue[4];	 //用来保存ADC各通道转换完成后的数据,原始定义在:adc_dma.c中
//按键外部中断处理函数
//按键可在这里自行diy选择
void EXTI15_10_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line14) != RESET){
		if((GPIOB->IDR & GPIO_Pin_14) == RESET){				//注意:这里不能用==SET来判断,因为SET = !RESET,也就是说SET的数字代表值是非零
			delay_ms(10);										//延迟20ms
			if((GPIOB->IDR & GPIO_Pin_14) == RESET){			//再检测一下按键的电平状态,若此时还是处于低电平状态,说明按键不是由于抖动触发的
				while((GPIOB->IDR & GPIO_Pin_14) == RESET);		//等待按钮释放
				if(ADC_ConvertedValue[1]>=100)	//油门拉倒最低,触发KEY1才是有效的(油门大于100就直接返回退出)
					return;
				ButtonMask ^= 0x01;														//按键1第一次被按下时为0x01,再次被按下时为0x00,以此重复
				PackData(0x08);									//打包数据,并且前导码为0x08
				SI24R1_SendPacket(packetData);
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line14);					//清除EXTI中断标志
	}else if(EXTI_GetITStatus(EXTI_Line15) != RESET){
		if((GPIOB->IDR & GPIO_Pin_15) == RESET){
			delay_ms(10);										//延迟20ms
			if((GPIOB->IDR & GPIO_Pin_15) == RESET){			//再检测一下按键的电平状态,若此时还是处于低电平状态,说明按键不是由于抖动触发的
				while((GPIOB->IDR & GPIO_Pin_15) == RESET);
				ButtonMask ^= 0x02;								//按键2被按下
				PackData(0x08);
				SI24R1_SendPacket(packetData);
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line15);
	}else if(EXTI_GetITStatus(EXTI_Line13) != RESET){
		if((GPIOB->IDR & GPIO_Pin_13) == RESET){
			delay_ms(10);										//延迟20ms
			if((GPIOB->IDR & GPIO_Pin_13) == RESET){			//再检测一下按键的电平状态,若此时还是处于低电平状态,说明按键不是由于抖动触发的
				while((GPIOB->IDR & GPIO_Pin_13) == RESET);
				ButtonMask |= 0x04;								//按键3被按下
				PackData(0x08);
				SI24R1_SendPacket(packetData);
			}
			ButtonMask &= ~0x04;
		}
		EXTI_ClearITPendingBit(EXTI_Line13);
	}else if(EXTI_GetITStatus(EXTI_Line12) != RESET){
		if((GPIOB->IDR & GPIO_Pin_12)== RESET){
			delay_ms(10);										//延迟20ms
			if((GPIOB->IDR & GPIO_Pin_12)== RESET){				//再检测一下按键的电平状态,若此时还是处于低电平状态,说明按键不是由于抖动触发的
				while((GPIOB->IDR & GPIO_Pin_12)== RESET);
				ButtonMask ^= 0x08;								//按键4被按下
				PackData(0x08);
				SI24R1_SendPacket(packetData);
			}
		//	ButtonMask &= ~0x08;
		}
		EXTI_ClearITPendingBit(EXTI_Line12);
	}
}
//求遥控数据丢包率
void Get_TxErrorPercent(void)
{
		if(TX_CNT++ == 100) //满100清零
		{
			TX_CNT = 0; 
			TX_ERROR = 0;
		}
		TX_ERROR_PERCENT = (float)TX_ERROR/(float)TX_CNT;
		if(TX_ERROR_PERCENT == 1)
		{
			Reconnection_flag = 1;			   //重连标志置位
			FLY_Connect_OK = 0 ;           //飞机与遥控器断开连接
		}
//		printf("TX_CNT:%d TX_ERROR:%d baifen:%0.2f\r\n",TX_CNT,TX_ERROR,TX_ERROR_PERCENT);
}

//摇杆的adc数据就是从这里发送出去的
//DMA1通道1中断通道处理函数,触发此中断时,说明已经将数据从ADC转移到内存数组中,可以触发数据发送了
void DMA1_Channel1_IRQHandler(void)
{
	if(DMA_GetITStatus(DMA1_IT_TC1)==SET)
	{
		CAL();//	检查是否满足校准触发条件
		if(ADC_CALIBRATOR_OK)
		{
			ADC_Calibration();//满足校准触发条件,执行校准
		}
		PackData(0x01);			//打包数据,并且前导码为0x01
		SI24R1_SendPacket(packetData);// 发送数据包
		Get_TxErrorPercent();   //求遥控数据丢包率
//		printf("BattV:%0.2f\r\n",ADC_ConvertedValue[4]*3.3*2/4096);
		DMA_ClearITPendingBit(DMA1_IT_TC1);		//清除DMA1通道1传输完成中断
	}
}

1.7.最后看数据打包函数,也就是遥控端我们对遥控的diy就是在这里进行的,这里四个函数,第一个ADC摇杆消抖函数,注释很详细就不细说了,第三个是接受被遥控端的信息,第四个是adc校准标志位判断函数用来判断是否满足校准条件的函数,重点说一下第二个数据打包函数,摇杆的ADC信息就是在这里进行打包发送出去的,因为ADC采集时0-4096的数据所以我们要将它转换成我们想要的数值,例如归一化到0-1000的范围内这样就可以对应被控制端想要控制的数据(例如遥控车的四个电机的PWM,或是舵机的0-180°的PWM,)在这里你可以对摇杆的四个ADC值自行diy转换成你想在被控制端使用的控制量。

cs 复制代码
FLY_TYPE FLY;

u8 SENSER_OFFSET_FLAG;

u8 packetData[12];		//打包后待发送的数据包,这个长度必须和接收端SI24R1的接收通道定义的有效数据长度相同,否则接收不到数据
u8 dataPID = 0;			  //数据包识别PID	
vu16 accelerator = 0;	//记录油门上一次的数值,用于下拉油门的时候,防止动力损失过大造成的失衡

extern u8 RxBuf[RX_PLOAD_WIDTH];	
extern vu8 ButtonMask;				//用来保存哪个按键刚被按下了,原始定义在:button.c文件中
extern vu16 ADC_ConvertedValue[4];	//用来保存ADC各通道转换完成后的数据,原始定义在:adc_dma.c中
extern u8 ADC_CALIBRATOR_OK;        //遥控通道ADC校准标志,原始文件在:adc_dma.h中
extern vu16 ADC_Calibrator[4];      //遥控通道ADC校准值,原始定义在:adc_dma.c中
vu16 ADC_Value[4];
/**************************************************************************************
打包数据

注意按照下面的通讯协议进行打包数据:以字节为单位:
前导码-按键MASK--ADC1低8--ADC1高8--ADC2低8--ADC2高8--ADC3低8--ADC3高8--ADC4低8--ADC4高8--数据包标识--校验码0xa5
其中:前导码只有0x01和0x08才表示有效的数据包,0x01表示此数据包是由ADC采样完成触发的,
0x08表示此数据包是由遥控器上的按键触发的,
数据包标识用于接收端SI24R1识别是否是同一数据包的作用(这在飞机上主要用于当遥控信号中断时,自动开始降落。)
**************************************************************************************/

/*
摇杆数据死区处理和限幅函数:
当摇杆值在(L, R)范围内时,强制输出中心值1500,消除摇杆微小抖动导致的控制指令波动
*/
vu16 ADC_ValueLimit(vu16 value,vu16 L,vu16 R,vu16 min,vu16 max)
{
	vu16 result = 0;
	if(value>L&&value<R) 
		value= 1500;
	if(value < L)
		value += 1500-L;
	if(value > R)
		value -= 1500-L;
	
	value = (value<=min)?min:value;//防止越界
	value = (value>=max)?max:value;//防止越界

	result = value;
	return result;
}

//摇杆adc数据打包,摇杆数据就在这里进行diy
void PackData(u8 firstByte)
{
	
	// 通道0:YAW(偏航)
	ADC_Value[0] = 1500 + ((1000+ADC_ConvertedValue[0]*1000/4096) - ADC_Calibrator[0]);// 得到1100-1900范围,1500为中心
	ADC_Value[0] = ADC_ValueLimit(ADC_Value[0],1470,1530,1100,1900);
  // 通道1:油门
	ADC_Value[1] = (ADC_ConvertedValue[1]*1000/4096); //将油门归一化0~1000;
	// 通道2:ROLL(横滚) 
	ADC_Value[2] = 1500 + ((1000+(ADC_ConvertedValue[2])*1000/4096) - ADC_Calibrator[2] );// 得到1100-1900范围,1500为中心
	ADC_Value[2] = ADC_ValueLimit(ADC_Value[2],1470,1530,1100,1900);
	// 通道3:PITCH(俯仰)
	ADC_Value[3] = 1500 + ((1000+(ADC_ConvertedValue[3])*1000/4096) - ADC_Calibrator[3] );// 得到1100-1900范围,1500为中心
	ADC_Value[3] = ADC_ValueLimit(ADC_Value[3],1470,1530,1100,1900);

	
	//数据包识别PID自增,并且超过200时自动归零
	if(dataPID>=200){
		dataPID = 0;
	}else{
		dataPID++;
	}
	
//=========直接采用指针操作内存中的数值将16位转成8位,速度快,并且不会发生精度截取的现象,还要注意,STM32是小端地址============
	packetData[0] = firstByte;					  //前导码
	packetData[1] = ButtonMask;                   //按键
	packetData[2] = *(((u8*)ADC_Value)+0);        //YAW角
	packetData[3] = *(((u8*)ADC_Value)+1);
	packetData[4] = *(((u8*)ADC_Value)+2);		    //油门数据打包
	packetData[5] = *(((u8*)ADC_Value)+3);
	packetData[6] = *(((u8*)ADC_Value)+4);        //ROLL角
	packetData[7] = *(((u8*)ADC_Value)+5);
	packetData[8] = *(((u8*)ADC_Value)+6);        //PITCH角
	packetData[9] = *(((u8*)ADC_Value)+7);
	packetData[10] = dataPID;					  //这个非常重要,这是防止飞机逃脱遥控的保证
	packetData[11] = 0xa5;						  //校验码:1010 0101
}
	

//接收飞机发送过来的数值
void ReceiveDataAnalysis(void)
{
	if(RxBuf[0]==0xFF )
	{
		SENSER_OFFSET_FLAG = RxBuf[1];
		FLY.Thr = ((RxBuf[2]<<8)|RxBuf[3]);
		FLY.Yaw = ((s16)(RxBuf[4]<<8)|RxBuf[5])/100;
		FLY.Pit = ((s16)(RxBuf[6]<<8)|RxBuf[7])/100;
		FLY.Rol = ((s16)(RxBuf[8]<<8)|RxBuf[9])/100;
		FLY.Alt = ((s16)(RxBuf[10]<<8)|RxBuf[11])/100;
		FLY.BattV = ((s16)(RxBuf[12]<<8)|RxBuf[13]);
//		printf("SENSER_OFFSET_FLAG:0x%x\r\n",SENSER_OFFSET_FLAG);
//		printf("Thr:%d Yaw:%d Rol:%d Pit:%d\r\n",Thr,Yaw,Rol,Pit);
	}
}

void CAL(void)//遥控器校准触发标志位函数
{
	if(ADC_ConvertedValue[2]<6 && ADC_ConvertedValue[3]<6 && ADC_ConvertedValue[1]<6)
	{
		ADC_CALIBRATOR_OK = 1;
//		printf("遥控器校准!\r\n");
	}
}

可以发现其实遥控器的代码需要自己diy的地方不多,基本上就只需要将四个摇杆adc的值转换成自己想控制的量的范围即可,然后就是oled显示的部分可能需要改成你自己想显示的东西,这方面留给大家自行diy了就不细说了。最后总结一下遥控器的控制逻辑:

🎮 控制逻辑详解

遥控器数据流:

  1. 定时触发:TIM4触发摇杆ADC采样 → DMA传输完成中断 → 打包发送

  2. 按键触发:按键中断 → 立即打包发送

  3. 状态反馈:接收小车回传数据 → 解析传感器信息

2.1.被遥控端代码我们就不从main.c一一介绍了,我们直接看使用到的部分,先看一下被遥控端的SI24R1代码,这部分代码和遥控端一样,是初始化代码和各种封装好的功能代码,直接复制移植即可。

cs 复制代码
#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,0x01}; //发送地址
uint8_t RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //接收地址

/*****************************************************************************
* 函  数: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; 	 //待机模式
}

/*****************************************************************************
* 函  数:uint8_t SI24R1_write_reg(uint8_t reg,uint8_t value)
* 功  能:写一字节数据到寄存器
* 参  数:reg: 寄存器地址
*         val:  要写入的数据
* 返回值:status
* 备  注:SI24R1代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
uint8_t SI24R1_write_reg(uint8_t reg,uint8_t value)
{
	uint8_t status;
	
	SI24R1_CSN_LOW;
	status=SPI2_WriteReadByte(reg);
	SPI2_WriteReadByte(value);
	SI24R1_CSN_HIGH;
	
	return status;
}

/*****************************************************************************
* 函  数:uint8_t SI24R1_read_reg(uint8_t reg)
* 功  能:读一字节数据到寄存器
* 参  数:reg: 寄存器地址
* 返回值:reg_val
* 备  注:SI24R1代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
uint8_t SI24R1_read_reg(uint8_t reg)
{
	uint8_t reg_val;
	
	SI24R1_CSN_LOW;
	SPI2_WriteReadByte(reg);
	reg_val = SPI2_WriteReadByte(0xff);
	SI24R1_CSN_HIGH;
	
	return reg_val;
}

/*****************************************************************************
* 函  数:uint8_t SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
* 功  能:写一组数据到寄存器
* 参  数:reg: 寄存器地址
*         pBuf: 要写入数据的地址
*         len:  要写入的数据长度
* 返回值:status
* 备  注:SI24R1代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
uint8_t SI24R1_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
	uint8_t status;
	int i;
	
	SI24R1_CSN_LOW;
	status = SPI2_WriteReadByte(reg);
	for( i=0;i<len;i++)
	{
		SPI2_WriteReadByte(*pBuf);
		pBuf++;
	}
	SI24R1_CSN_HIGH;
	
	return status;
}

/*****************************************************************************
* 函  数:uint8_t SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
* 功  能:读一组数据到寄存器
* 参  数:reg: 	寄存器地址
*         pBuf: 要读取数据的地址
*         len:  	要读取的数据长度
* 返回值:status
* 备  注:SI24R1代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
uint8_t SI24R1_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
	uint8_t status;
	int i;
	
	SI24R1_CSN_LOW;
	status = SPI2_WriteReadByte(reg);
	for(i = 0;i < len ;i++)
	{
		*pBuf = SPI2_WriteReadByte(0xff);
		pBuf++;
	}
	SI24R1_CSN_HIGH;
	
	return status;
}

/*****************************************************************************
* 函  数: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;
	}
	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);
	}		
}

/*****************************************************************************
* 函  数: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,30); //设置RF通道为40hz(1-64Hz都可以)
	SI24R1_write_reg(W_REGISTER+RF_SETUP,0x27); //设置TX发射参数,0db增益,2Mbps,低噪声增益关闭 (注意:低噪声增益关闭/开启直接影响通信,要开启都开启,要关闭都关闭0x0f)
	
	SI24R1set_Mode(IT_RX); //默认为接收模式
	
	SI24R1_CE_HIGH;
}	

/*****************************************************************************
* 函  数: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
}

/*****************************************************************************
* 函  数: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;
}

/*****************************************************************************
* 函  数:uint8_t SI24R1_testConnection(void)
* 功  能:检查SI24R1与MCU的SPI总线是否通信正常
* 参  数:无
* 返回值:1已连接 0未连接
* 备  注:无
*****************************************************************************/
uint8_t SI24R1_testConnection(void)
{
	uint8_t buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
	uint8_t i; 	 
	SI24R1_Write_Buf(W_REGISTER+TX_ADDR,buf,5); //写入5个字节的地址.	
	SI24R1_Read_Buf(TX_ADDR,buf,5); //读出写入的地址  
	for(i=0;i<5;i++)
	if(buf[i]!=0XA5)break;	 							   
	if(i!=5)return 0; //检测24L01错误	
	return 1;	//检测到24L01
}

/*****************************************************************************
* 函  数:void SI24R1_Check(void)
* 功  能:检测SI24R1是否连接
* 参  数:无
* 返回值:无
* 备  注:无
*****************************************************************************/
void SI24R1_Check(void)
{
	while(!SI24R1_testConnection())
	{
		printf("\r SI24R1 no connect...\r\n");
		RGB_LED_Red();//红灯常亮
	}
}

/*****************************************************************************
* 函  数: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);
	}
}

/*****************************************************************************
* 函  数:void SI24R1_Test(void)
* 功  能:SI24R1通信测试函数
* 参  数:无
* 返回值:无 
* 备  注:测试时用
*****************************************************************************/
void SI24R1_Test(void)
{
	uint8_t t=0;
	static uint8_t mode,key;
	mode = ' ';
	key=mode;
	for(t=0;t<32;t++)
	{
		key++;
		if(key>('~'))key=' ';
		SI24R1_TX_DATA[t]=key;	
	}
	mode++; 
	if(mode>'~')mode=' ';  	  		
	SI24R1_TxPacket(SI24R1_TX_DATA);
}

2.2.然后看一下SI24R1的IRQ引脚中断代码,遥控器发送过来的信息就是在这里被接收的。

cs 复制代码
/*****************************************************************************
* 函  数:void EXTI2_IRQHandler(void)
* 功  能:SI24R1(全双工)的外中断处理函数
* 参  数:无
* 返回值:无
* 备  注: SI24R1的所有中断事件都在此函数中给予相应的处理
*****************************************************************************/
void EXTI2_IRQHandler(void)
{                    
	uint8_t sta;
	if(EXTI_GetITStatus(EXTI_Line2) != RESET )
	{
		 SI24R1_CE_LOW;//拉低CE,以便读取NRF中STATUS中的数据
		 sta = SI24R1_read_reg(R_REGISTER+STATUS); //读取STATUS中的数据,以便判断是由什么中断源触发的IRQ中断
		
		/* 发送完成中断 TX_OK */
		if(sta & TX_OK)                                   
		{										
			SI24R1set_Mode(IT_RX);	
			SI24R1_write_reg(W_REGISTER+STATUS,TX_OK); //清除发送完成标志·
			SI24R1_write_reg(FLUSH_TX,0xff); //清除TX_FIFO
//			printf("Sent OK!!!!\r\n");
		}
		/* 接收完成中断 RX_OK */
		if(sta & RX_OK) 
		{	
			//接收遥控器的信息
			SI24R1_RxPacket(SI24R1_RX_DATA);	
			Remote_Data_ReceiveAnalysis();
			SI24R1_write_reg(W_REGISTER+STATUS,RX_OK); //清除发送完成标志·
//			printf("Receive OK!!!!\r\n");	
		}
		/* 达到最大重发次数中断  MAX_TX */
		if(sta & MAX_TX)                                  
		{											
			SI24R1set_Mode(IT_RX);
			SI24R1_write_reg(W_REGISTER+STATUS,MAX_TX);//清除接达到最大重发标志
			SI24R1_write_reg(FLUSH_TX,0xff); //清除TX_FIFO
//			printf("Sent Max Data!!!\r\n"); 
		}
		EXTI_ClearITPendingBit(EXTI_Line2);
	}
}

2.3.接下来看一下remotedata.c的代码 ,摇杆和按键的信息就是在这里被接受并解析的,但是这里摇杆的信息只被接受并未使用,而按键的信息是在这里接受并被使用的,所以这里可以对按键控制的行为自行进行diy,就在代码这个函数里void Button_command(uint8_t Button)进行diy自己想要按键按下实现的功能即可。(例如上锁/解锁等操作)

cs 复制代码
uint8_t DataID;	//数据包ID
RC_TYPE RC_Control;//遥控器输入

void Button_command(uint8_t Button);

/***********************************************************************************************************************
* 函  数:void Remote_Data_ReceiveAnalysis(void)
* 功  能:遥控器数据包解析
* 参  数:无
* 返回值:无
* 备  注:通信协议:
         前导码-按键MASK--ADC1低8--ADC1高8--ADC2低8--ADC2高8--ADC3低8--ADC3高8--ADC4低8--ADC4高8--数据包标识--校验码0xa5;
         前导码只有0x01和0x08才表示有效的数据包,0x01表示此数据包是由ADC采样完成触发的,0x08表示此数据包是由遥控器上的按
         键触发的;
		 数据包标识用于识别是否是同一数据包的作用(这在飞机上主要用于当遥控信号中断时,自动开始降落。)
************************************************************************************************************************/
void Remote_Data_ReceiveAnalysis(void)
{
	SI24R1_Controlflag = 1;
	if(SI24R1_RX_DATA[11]!=0xa5)	//验证校验码是否为0xa5
		return;
	
	if(SI24R1_RX_DATA[0] & 0x01) //当数据包是由遥控器的ADC采样完成时触发发送时
	{								       
		RC_Control.YAW      = SI24R1_RX_DATA[3]<<8|SI24R1_RX_DATA[2];		//ADC0
		RC_Control.THROTTLE = SI24R1_RX_DATA[5]<<8|SI24R1_RX_DATA[4]; 	//ADC1
		RC_Control.ROLL     = SI24R1_RX_DATA[7]<<8|SI24R1_RX_DATA[6];	  //ADC2
		RC_Control.PITCH    = SI24R1_RX_DATA[9]<<8|SI24R1_RX_DATA[8];		//ADC3
	}
	else if(SI24R1_RX_DATA[0] & 0x08) //当数据包是由遥控器的按键触发发送时
	{							
		Button_command(SI24R1_RX_DATA[1]); //ButtonMask按键命令解析
	}
	DataID = SI24R1_RX_DATA[10];//将数据包识别PID值取出,覆盖之前的值,以表示信号链接正常
}

/**************************************************************************************************************
* 函  数:void Button_command(uint8_t Button)
* 功  能:按键命令解析
* 参  数:Button 按键命令数值
* 返回值:无
* 备  注:遥控器共有四个按键 K1 ,K2 ,K3 ,K4
* K1 	:解锁上锁按键
* K2 	: 用户自定义功能按键
* K3 	: 传感器校准按键
* K4 	: 模式选择按键
* 其中K1,K2,K3,K4 对应ButtonMask的低4位,K1对应0位 K2对应1位 K3对应2位 K4对应3位
* 0x00的时候表示4个按键都没有被触发,0000 1111 表示KEY1~KEY4按键都被按下
* 按键每按一次对应位取反一次(详细操作请参考遥控器源码 senddata.c )      
***************************************************************************************************************/
void Button_command(uint8_t Button)
{
	static uint8_t PreButton = 0;//前一个按键值

	//遥控器K1按键
	if((PreButton&0x01) != (Button&0x01)) 
	{
		if(Button&0x01)//飞机解锁
		{
			Airplane_Enable = 1;
			RGB_LED_FLY();
			RGB_LED_Off();
		}else //飞机上锁
		{
			Airplane_Enable = 0;
			RGB_LED_Off();
		}
	}
	//遥控器K2按键
	if((PreButton&0x02) != (Button&0x02)) 
	{
		/*
			用户自行定义,可用来调试
		*/
	}
	//遥控器K3按键
	if(Button&0x04) //陀螺仪加速度计校准
	{
		SENSER_FLAG_SET(GYRO_OFFSET);
	}
	//遥控器K4按键
	if((PreButton&0x08) != (Button&0x08))
	{
		if(Button&0x08)
		{
			SENSER_FLAG_SET(FLY_MODE); //无头模式
		}else 
		{
			SENSER_FLAG_RESET(FLY_MODE); //有头模式
		}
	}
	PreButton = Button;
}

/**************************************************************************************************************
* 函  数:void UnControl_Land(void)
* 功  能:信号中断紧急降落
* 参  数:无
* 返回值:无
* 备  注:粗略处理,有待完善
***************************************************************************************************************/
void UnControl_Land(void)       
{
	RC_Control.THROTTLE -= 6;
	if(RC_Control.THROTTLE <= 150)
		RC_Control.THROTTLE = 150;
}

/**************************************************************************************************************
* 函  数:void SI24R1_SingalCheck(void)
* 功  能:信号中断检测
* 参  数:无
* 返回值:无
* 备  注:如果飞机处于解锁状态但是,当前数据包的ID等于前一个数据包的ID,这就说明遥控器与飞机断开连接
***************************************************************************************************************/
void SI24R1_SingalCheck(void)
{
	static uint8_t PreDataID = 250; 
	
	if(SI24R1_Controlflag)
	{
		if(Airplane_Enable && DataID == PreDataID)//飞机与遥控断开连接
		{
			UnControl_Land(); //紧急降落处理
			RGB_LED_Red(); 	  //红灯常亮报警
		}else if(Airplane_Enable && !BATT_LEDflag)//飞机遥控连接正常
		{
			RGB_LED_FLY(); 	  //飞行指示灯
		}
		PreDataID = DataID;
	}
}

/**************************************************************************************************************
* 函  数:void SendToRemote(void)
* 功  能:飞机状态数据发送给遥控器
* 参  数:无
* 返回值:无
* 备  注:注意:SI24R1单次发送最大32个字节,请勿越界
***************************************************************************************************************/
void SendToRemote(void)
{
	int16_t temp; 	
	if(Airplane_Enable)
	{
		SENSER_FLAG_SET(FLY_ENABLE); //解锁模式置位
	}
	else
	{
		SENSER_FLAG_RESET(FLY_ENABLE); //上锁模式复位
	}
	SI24R1_TX_DATA[0] = 0xFF;//帧头
	
	SI24R1_TX_DATA[1] = SENSER_OFFSET_FLAG; //标志位组
	
	temp = (u16)RC_Control.THROTTLE; //油门
	SI24R1_TX_DATA[2] = Byte1(temp);
	SI24R1_TX_DATA[3] = Byte0(temp);
	temp = (int)(Att_Angle.yaw*100); //航向
	SI24R1_TX_DATA[4] = Byte1(temp);
	SI24R1_TX_DATA[5] = Byte0(temp);
	temp = (int)(Att_Angle.pit*100); //俯仰
	SI24R1_TX_DATA[6] = Byte1(temp);
	SI24R1_TX_DATA[7] = Byte0(temp);
	temp = (int)(Att_Angle.rol*100); //横滚
	SI24R1_TX_DATA[8] = Byte1(temp);
	SI24R1_TX_DATA[9] = Byte0(temp);
	temp = (int)(FBM.AltitudeFilter*100); //高度留待
	SI24R1_TX_DATA[10] = Byte1(temp);
	SI24R1_TX_DATA[11] = Byte0(temp);
	temp = (int)(BAT.BattMeasureV*100); //飞机电池电压
	SI24R1_TX_DATA[12] = Byte1(temp);
	SI24R1_TX_DATA[13] = Byte0(temp);
	
	SI24R1_TxPacket(SI24R1_TX_DATA); //SI24R1发送函数
}

2.4.接下来看一下摇杆信息控制部分的代码 ,因为我这里是因无人机的控制部分代码为例,使用了串级PID,所以看起来比较复杂,但其实代码里的这四个rc_in->THROTTLE,rc_in->ROLL,rc_in->PITCH,rc_in->YAW参数就是两个摇杆上下左右四个参数,如果你是控制四轮四电机小车的话,只需用这四个值来diy控制四个电机的pwm即可。

cs 复制代码
//角度环PID 
PID_TYPE PID_ROL_Angle;
PID_TYPE PID_PIT_Angle;
PID_TYPE PID_YAW_Angle;
//角速度环PID 
PID_TYPE PID_ROL_Rate;
PID_TYPE PID_PIT_Rate;
PID_TYPE PID_YAW_Rate;
//高度环PID 
PID_TYPE PID_ALT_Rate;
PID_TYPE PID_ALT;

float Pre_THROTTLE,THROTTLE;
float Moto_PWM_1=0.0f,Moto_PWM_2=0.0f,Moto_PWM_3=0.0f,Moto_PWM_4=0.0f;
uint8_t SI24R1_Controlflag = 1,Airplane_Enable;

/******************************************************************************************
*函  数:void Control(FLOAT_ANGLE *att_in,FLOAT_XYZ *gyr_in, RC_TYPE *rc_in, uint8_t armed)
*功  能:姿态控制,角度环控制和角速度环控制
*参  数:att_in:测量值
*        gry_in: MPU6050读取的角速度值
*        rc_in : 遥控器设定值
*        armed: 记录命令
*返回值:无
*备  注:RoboFly 小四轴机头与电机示意图	
					 机头(Y+)
					   
				  M1    ↑    M2
					\   |   /
					 \  |  /
					  \ | /
			    ------------------------+------------------------>X+	
					  / | \
					 /  |  \
					/   |   \
				  M4    |    M3

	
	1. M1 M3电机逆时针旋转,M2 M4电机顺时针旋转
	2. X:是MPU6050的 X 轴,Y:是MPU6050的 Y 轴,Z轴正方向垂直 X-Y 面,竖直向上
	3. 绕 X 轴旋转为 PITCH 角 
	   绕 Y 轴旋转为 ROLL  角 
	   绕 Z 轴旋转为 YAW   角
	4. 自己DIY时进行动力分配可以一个轴一个轴的分配,切勿三个轴同时分配。
*******************************************************************************************/
void Control(FLOAT_ANGLE *att_in,FLOAT_XYZ *gyr_in, RC_TYPE *rc_in, uint8_t armed)
{
	FLOAT_ANGLE Measure_Angle,Target_Angle;
	Measure_Angle.rol = att_in->rol; //陀螺仪测量的实时横滚角
	Measure_Angle.pit = att_in->pit; //陀螺仪测量的实时俯仰角
	Measure_Angle.yaw = att_in->yaw; //陀螺仪测量的实时偏航角
	// 将遥控器PWM信号(1000-2000)转换为角度指令(-41.67° ~ +41.67°)
	Target_Angle.rol = (float)((rc_in->ROLL-1500)/12.0f); //遥控器输入的目标横滚
	Target_Angle.pit = (float)((rc_in->PITCH-1500)/12.0f);//遥控器输入的目标俯仰
	Target_Angle.yaw = (float)((1500-rc_in->YAW)/12.0f);  //遥控器输入的目标偏航
  
	if(!SI24R1_Controlflag)
	{
		Target_Angle.yaw = 0; // 无线控制失效时锁定偏航
	}else if(fabs(Target_Angle.yaw )<4)
	{
		Target_Angle.yaw = 0;// 小指令死区,避免微小漂移
	}
	
	if(1 == (GET_FLAG(FLY_MODE))) 
	{
		Yaw_Carefree(&Target_Angle,&Measure_Angle);//无头模式
	}

	//角度环(外环)
	PID_Postion_Cal(&PID_ROL_Angle,Target_Angle.rol,Measure_Angle.rol);//ROLL角度环PID (输入角度 输出角速度)
	PID_Postion_Cal(&PID_PIT_Angle,Target_Angle.pit,Measure_Angle.pit);//PITH角度环PID (输入角度 输出角速度)
	PID_Postion_Cal(&PID_YAW_Angle,Target_Angle.yaw,Measure_Angle.yaw);//YAW角度环PID  (输入角度 输出角速度)
	
	//角速度环(内环)
	PID_Postion_Cal(&PID_ROL_Rate,PID_ROL_Angle.OutPut,(gyr_in->Y*RadtoDeg));   //ROLL角速度环PID (输入角度环的输出,输出电机控制量)
	PID_Postion_Cal(&PID_PIT_Rate,PID_PIT_Angle.OutPut,-(gyr_in->X*RadtoDeg));  //PITH角速度环PID (输入角度环的输出,输出电机控制量)
	PID_Postion_Cal(&PID_YAW_Rate,-PID_YAW_Angle.OutPut,-(gyr_in->Z*RadtoDeg)); //YAW角速度环PID  (输入角度,输出电机控制量)
	
	//动力分配(自己DIY时动力分配一定要好好研究,动力分配搞错飞机肯定飞不起来!!!)
	/*混控原理:
    油门:所有电机基础推力
    横滚:1、4号电机加速,2、3号电机减速(绕X轴旋转)
    俯仰:1、2号电机加速,3、4号电机减速(绕Y轴旋转)
    偏航:1、3号电机加速,2、4号电机减速(反扭矩差动)*/
	if(rc_in->THROTTLE>180&&armed)//当油门大于150时和飞机解锁时动力分配才生效
	{                                                                                  
		Moto_PWM_1 = rc_in->THROTTLE + PID_ROL_Rate.OutPut + PID_PIT_Rate.OutPut + PID_YAW_Rate.OutPut;
		Moto_PWM_2 = rc_in->THROTTLE - PID_ROL_Rate.OutPut + PID_PIT_Rate.OutPut - PID_YAW_Rate.OutPut;  
		Moto_PWM_3 = rc_in->THROTTLE - PID_ROL_Rate.OutPut - PID_PIT_Rate.OutPut + PID_YAW_Rate.OutPut;   
		Moto_PWM_4 = rc_in->THROTTLE + PID_ROL_Rate.OutPut - PID_PIT_Rate.OutPut - PID_YAW_Rate.OutPut;
	} 
	
	else
	{
		Moto_PWM_1 = 0;
		Moto_PWM_2 = 0;
		Moto_PWM_3 = 0;
		Moto_PWM_4 = 0;
	}
   Moto_Pwm(Moto_PWM_1,Moto_PWM_2,Moto_PWM_3,Moto_PWM_4); //将此数值分配到定时器,输出对应占空比的PWM波
}

/******************************************************************************************
* 函  数:int16_t Yaw_Control(float TARGET_YAW)
* 功  能:航向角不回中控制
* 参  数:TARGET_YAW 目标航向角
* 返回值:针对目标航向角计算出的航向角
* 备  注:由于遥控器航向舵会自动回中,所以需要对目标航向角进行不回中处理;
*******************************************************************************************/
int16_t Yaw_Control(float TARGET_YAW)
{
	static int16_t YAW=0; //根据目标航向角计算出的不回中角度
	if(Airplane_Enable) 
	{
		if(SI24R1_Controlflag) //遥控器控制航向角
		{
			if(TARGET_YAW>2) //目标航向角为正时YAW增大
				YAW +=2;
			if(TARGET_YAW<-2) //目标航向角为负时YAW减小
				YAW -=2;	
		}
	}
	return YAW;
}

/******************************************************************************************
* 函  数:void Yaw_Carefree(FLOAT_ANGLE *Target_Angle, const FLOAT_ANGLE *Measure_Angle)
* 功  能:无头角度控制(新手友好)
* 参  数:*Target_Angle 指向目标姿态角的指针
*         *Measure_Angle 测量姿态角的指针
* 返回值:针对目标航向角计算出的航向角
* 备  注:无头模式需调用此函数
*******************************************************************************************/
void Yaw_Carefree(FLOAT_ANGLE *Target_Angle, const FLOAT_ANGLE *Measure_Angle)
{
	float yawRad = fabs(Measure_Angle->yaw) * DegtoRad;
	float cosy = cosf(yawRad);
	float siny = sinf(yawRad);
	float originalRoll = Target_Angle->rol;
	float originalPitch = Target_Angle->pit;
  // 将遥控器指令从世界坐标系转换到机体坐标系
	// 无论飞机朝向如何,前进都是相对于起飞时的方向
	Target_Angle->rol = originalRoll * cosy + originalPitch * siny;
	Target_Angle->pit = originalPitch * cosy - originalRoll * siny;
}
/******************************************************************************************
* 函  数:void Safety_Check(void)
* 功  能:飞机姿态安全监测
* 参  数:无
* 返回值:无
* 备  注:如果飞机角度和加速度异常就将飞机上锁并停止电机,防止电机狂转打坏桨叶
*******************************************************************************************/
void Safety_Check(void)
{
  if((fabs(Att_Angle.pit)>45.0f||fabs(Att_Angle.rol)>45.0f) && (fabs(Acc_filt.X)>9.0f||fabs(Acc_filt.Y)>9.0f))
	{
		 Airplane_Enable = 0;
		 Moto_PWM_1 = 0;
		 Moto_PWM_2 = 0;
		 Moto_PWM_3 = 0;
		 Moto_PWM_4 = 0;
	}
}

2.5.最后再在主函数 里调用一下使用到的函数即可,这里的摇杆控制信息就是在这个函数里被调用的Control(&Att_Angle,&Gyr_rad,&RC_Control,Airplane_Enable),这里主函数我只列出来使用到的部分了,具体代码可去我之前开源的四轴飞行器参考。

cs 复制代码
NvicConfig(); //系统中断优先级管理
Delay_Init(); //系统延时初始化
TIM_Init();	//系统时基初始化
Exit_Init(); //外部中断初始化,SI24R1(2.4G)遥控器用
SI24R1_Init(); //SI24R1(2.4G)初始化(RGB红)
MOTOR_Init(); //电机PWM输出初始化

	while(1)
	{
		Control(&Att_Angle,&Gyr_rad,&RC_Control,Airplane_Enable); //RC_Control就是摇杆数值
        SI24R1_SingalCheck(); //2.4G通信检测
		SendToRemote(); //发送数据给遥控器
        SI24R1_GetAddr(); //分配2.4G地址
	}

本次分享到这里就结束了,如果对你有帮助的话,希望能换你一个点赞!

相关推荐
应用市场2 小时前
ARM编译器深度解析:从Keil到VSCode的STM32开发之
arm开发·vscode·stm32
太阳人7982 小时前
MIPI D-PHY/C-PHY接收器压力眼图测试介绍
功能测试·嵌入式硬件·音视频·硬件工程
清风6666664 小时前
基于单片机的智慧校园自动打铃系统设计
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
czhaii4 小时前
51的DSP来了, 100MHz, STC32G144K246
stm32·单片机·fpga开发
2301_800399724 小时前
stm32 printf重定向到USART
java·stm32·算法
小龙报4 小时前
《嵌入式成长系列之51单片机 --- Keil5创建工程》
c语言·开发语言·c++·单片机·嵌入式硬件·51单片机·学习方法
莫桑晚-为尚天4 小时前
触控芯片核心:DRV与SENS信号解析
嵌入式硬件·硬件工程·软件工程
点灯小铭4 小时前
基于单片机的自行车速度与里程检测报警系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
天天爱吃肉82186 小时前
新能源汽车动力系统在环(HIL)半实物仿真测试台架深度解析
人工智能·python·嵌入式硬件·汽车