航模程序设计

简介

  由于目前还没有绘制PCB,因此采用的是最小系统和模块进行拼接的方式测试程序。
  
硬件部分:
  测试核心板:接收机:STM32F407VGT6核心板。遥控:正点原子精英板
  电调:淘宝 店家进口杂物爱好者 20-25A,2-3S,价格12-15元
  电机:淘宝 店家进口杂物爱好者 1804无刷电机,价格14元
  摇杆:买的坏遥控拆下来的,估价6元

软件部分:
  定时器及PWM相关知识
  ADC相关知识
  SPI及串口相关知识
  NRF24L01的相关函数使用
  操作系统:FreeRTOS
  开发环境:CubeMX+MDK5
  开发语言:C语言
  我在工程模板中定义了一个sys.h的头文件,主要是一些数据类型的重新命名及兼容F103和F407的代码,文件如下:

#ifndef  _SYS_H
#define  _SYS_H


// 适配不同芯片的移植 
// 带FreeRTOS操作系统
// CubeMX+MDK开发环境
//-----------------------------------------------------------------------------------------------------------------
#ifdef  STM32F103xE
#include "stm32f1xx_hal.h"
#endif

#ifdef  STM32F407xx
#include "stm32f4xx_hal.h"
#endif

#include "stdint.h"

#include "FreeRTOS.h"
#include "cmsis_os.h"

#include "main.h"
//-----------------------------------------------------------------------------------------------------------------



//定义一些常用的数据类型短关键字 
//-----------------------------------------------------------------------------------------------------------------
typedef                      int32_t         s32;
typedef                      int16_t         s16;
typedef                      int8_t          s8;

typedef const                int32_t         sc32;  
typedef const                int16_t         sc16;  
typedef const                int8_t          sc8;  

typedef __IO                 int32_t         vs32;
typedef __IO                 int16_t         vs16;
typedef __IO                 int8_t          vs8;
 
typedef __I                  int32_t         vsc32;  
typedef __I                  int16_t         vsc16; 
typedef __I                  int8_t          vsc8;   

typedef                      uint32_t        u32;
typedef                      uint16_t        u16;
typedef                      uint8_t         u8;

typedef const                uint32_t        uc32;  
typedef const                uint16_t        uc16;  
typedef const                uint8_t         uc8; 

typedef __IO                 uint32_t        vu32;
typedef __IO                 uint16_t        vu16;
typedef __IO                 uint8_t         vu8;

typedef __I                  uint32_t        vuc32;  
typedef __I                  uint16_t        vuc16; 
typedef __I                  uint8_t         vuc8;  	
//-----------------------------------------------------------------------------------------------------------------


#endif

  在单板测试之前都是使用F407核心板,在两者相互通信时,精英板为遥控方,F407核心板为接收方。

1. CubeMX搭建基于FreeRTOS工程模板

  此部分参考我的“STM32之FreeRTOS学习笔记”中的1. CubeMX创建FreeRTOS工程模板,并且先可以新建跑马灯任务进行测试。注意:调试接口和时钟也要配置,这是基本操作,这里就不赘述了。

2. LED及按键配置

2.1 CubeMX配置

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210705102850124.png
  由于KEY_UP按下为高电平,因此初始状态让它下拉到低电平,而KEY_0按下为低电平,因此初始为上拉。LED低电平点亮,初始设置为高。

2.2 驱动函数及宏定义编写

  经过上面的配置,我们就已经可以正常的使用IO口了,首先来编写LED相关头文件及源文件。
led.h如下:

#ifndef  _LED_H
#define  _LED_H


#define LED0_ON()   HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET)	
#define LED0_OFF()  HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET)	

#define LED1_ON()   HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET)
#define LED1_OFF()  HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)	

#endif

无led.c

  按键相关函数主要是移植正点原子的程序代码。
key.h如下:

#ifndef __KEY_H
#define __KEY_H

#include "sys.h"

#define KEY0_PRES	  1		//KEY0 按下 
#define KEY_UP_PRES	2		//KEY1 按下


#define  KEY0    HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)
#define  KEY_UP  HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)


u8 KEY_Scan(u8 mode);  	//按键扫描函数

#endif

key.c如下:

#include "key.h"
#include "led.h"


//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0按下
//KEY_UP_PRES,WK_UP按下 
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	
	u8 key0=KEY0;
	u8 keyup=KEY_UP;
	
	if(mode)key_up=1;  //支持连按		 
	
	if(key_up&&(key0==0||keyup==1))
	{
		osDelay(5);
		key_up=0;
		
		if(key0==0)return KEY0_PRES;
		
		else if(keyup==1)return KEY_UP_PRES; 
	}
	else if(key0==1&&keyup==0)key_up=1; 	     
	
	return 0;// 无按键按下
}


// 
// 
// 按键测试代码
//-----------------------------------------------------------------------------------------------------------------
void KEY_Test()
{
	  u8 key=0;
	  static u8 tmp=0;
	
	  key=KEY_Scan(0);
	  switch(key)
	  {
		case KEY0_PRES:
		{
		   tmp=~tmp;
		   if(tmp)
		   {
			  LED0_ON();
			}
			else
			{
			  LED0_OFF();
			}
		    break;
		  }
					
		  case KEY_UP_PRES:
		  {
			tmp=~tmp;
			if(tmp)
			{
			  LED1_ON();
			}
			else
			{
			   LED1_OFF();
			}
			break;
	       }
	   }
}
//-----------------------------------------------------------------------------------------------------------------

  经过上面的配置,就可以用两个按键分别点亮熄灭两个小灯了。

3. PWM配置

3.1 CubeMX配置

  由于定时器 TIM1被用来给HAL库提供时钟基准,因此采用TIM2来配置PWM,配置过程如下:
在这里插入图片描述
在这里插入图片描述

第1步:点击TIM2
第2步:时钟选择内部
第3步:开启通道1到4
第4步:由于TIM2挂载在APB1上,时钟为84M,因此分频为8400,即一个时钟周期为100us,向上计数,重装载值200,即PWM周期为20ms,并使能重装载值。
第5步:设置PWM为模式1,即向上计数时,值cnt小于CCR1为有效电平,否则为无效电平。初始占空比设置为0,使能输出比较预装载,有效电平设置为高电平。

3.2 驱动函数及宏定义编写

pwm.h如下:

#ifndef  _PWM_H
#define  _PWM_H

// 代码移植及修改部分
// 适用于F407VGT6
// TIM2挂载在APB1总线上,时钟为84M
// 当前时钟分频为8400,即走一个时钟周期为100us
// 需要将PWM周期控制在20ms,因此重装载值要为200
// 控制占空比为1000-2000us,也就是10-20个时钟周期
//-----------------------------------------------------------------------------------------------------------------
#define definePWM_Small          10
#define definePWM_Big            20
//-----------------------------------------------------------------------------------------------------------------

void PWM_Tesk(void);

#endif

pwm.c文件如下:

#include "pwm.h"
#include "key.h"
#include "led.h"

extern TIM_HandleTypeDef htim2;

void PWM_Tesk()
{
	    u8 key=0,i=0;
		static u8 cnt = definePWM_Small;
		static u8 tmp=0;
    
		key = KEY_Scan(0);
		switch(key)
		{
		  case KEY0_PRES:
			{
				 tmp=~tmp;
				 if(tmp)
				 {
					  __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,definePWM_Small);
					  LED0_OFF();
				 }
				 else
				 {
					  __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,definePWM_Big);
				      LED0_ON();
				 }
				 break;
			 }
					
			 case KEY_UP_PRES:
			 {
				  cnt++;
				  __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,cnt);
				  if(cnt>=definePWM_Big)
				  {
					  cnt=definePWM_Small;
					  for(i=0;i<5;i++)
						{
							LED1_ON();
					    osDelay(50);
						  LED1_OFF();
					    osDelay(50);
						}
				  }
				  tmp=0;
				  break;
			 }
		}
}

  测试代码主要功能介绍:由于航模PWM控制模式都是采用周期为20ms,占空比1-2ms来实现控制的,本测试代码中,KEY0是用来设置最大最小占空比的,先按下KEY0为最小占空比,再按下则为最大占空比。相当于电机停止和电机满转功能,可用于电调的校准。KEY_UP则是按一下增加一次电机的转速,一共十个档位.(是根据TIM分频系数来决定的,此例程中十个时钟周期就是1ms,测试代码默认最低占空比为1ms,因此第10档就是2ms)最后面的tmp=0是为了按了KEY_UP之后,再去按KEY0的时候能保证第一次按下是电机停止的占空比,防止按完KEY_UP之后不下心误触KEY0电机直接满转对人造成伤害。KEY_UP里面的闪灯操作是为了提示电机速度已经最高,KEY0的亮灯也是提示目前是电机最高速度。注意:使用pwm的话,main函数在FreeRTOS系统调度之前要调用HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);启动PWM。第二个参数是指定pwm通道。

4. 摇杆按键程序设计

  前面我们用按键代替摇杆控制了电机,但是按键始终是没有摇杆方便,因此我又买了一个航模摇杆,不要用那种蘑菇头的,手感极差,用航模遥控上面那种的。由于摇杆涉及到ADC,所以我们可通过CubeMX配置ADC。

4.1 CubeMX配置ADC

在这里插入图片描述

第一步:点击ADC1
第二步:设置通道,可勾选多个,我这里测试就只勾了一个
第三步:时钟分频系统会默认,不管他,也可以自己计算,STM32的ADC对时钟是有要求的。设置分辨率为12位,使能连续转换和DMA(注意:使能DMA要先进行第四步配置DMA)
第四步:配置DMA
在这里插入图片描述
先点击1处添加ADC1才会出现第二步的框框内容,点击第2个框框
在这里插入图片描述
设置为全字,即32位数据宽度。注意:右边的Mode选择circular
第五步:勾选ADC中断
在这里插入图片描述
ADC的配置就已经完毕了,为了便于调试,我们还可以开启串口,配置如下:
在这里插入图片描述
第二步模式选择异步即可。
  因为要用到printf()函数,因此我们要进行串口重定向设置,下面这段代码就是,直接复制粘贴在一个头文件里,然后调用它即可,我是新建了usart1.c和.h文件,不要用usart.h,会和系统文件冲突。
usart1.h:

#ifndef _USART_H
#define _USART_H

#include "stdio.h"
#include "sys.h"
extern UART_HandleTypeDef huart1;

#endif

usart1.c:

#include "usart1.h"

// 串口重定向
// 实现printf输出
// 需要先配置串口
//----------------------------------------------------------------------------------------------------------------
int fputc(int ch, FILE *f)  //轮询方式,超时机制,输出到串口函数重定义
{  	
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, sizeof(ch), 0xFFFF);
  return ch;
}
/*HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)*/
int fgetc(FILE *f)   //轮询方式,超时机制,接收到串口函数重定义
{
	uint8_t ch;	
	HAL_UART_Receive(&huart1, (uint8_t *)&ch, sizeof(ch), 0xFFFF);	
	return ch;
}

//-----------------------------------------------------------------------------------------------------------------

  经过以上的配置,我们就可以直接生成代码了,接下来就可以进行相关的测试代码编写及宏定义了。

4.2 驱动函数及宏定义编写

adc1.h如下:

#ifndef  __ADC1_H
#define  __ADC1_H

#include "sys.h"


extern UART_HandleTypeDef huart1;


extern uint32_t ADC_1;
		//各采样30次,故30*2为60
extern uint32_t ADC_Value[30];
extern uint8_t i;


void ADC_DMA_Test(void);


#endif

adc1.c代码如下:

#include "adc1.h"
#include "usart1.h"

extern TIM_HandleTypeDef htim2;


uint32_t ADC_1;
		//各采样30次,故30*2为60
uint32_t ADC_Value[30];
uint8_t i;



void ADC_DMA_Test(void)
{
	  uint8_t adc1;
	//放个延迟,防止程序运行第一次读出数据有误
		HAL_Delay(100);
		ADC_1=0;
		for(i=0,ADC_1=0;i<30;)
		{
		   	ADC_1+=ADC_Value[i++];   
		}
        printf("ADC数据如下\n");		
    //除以30为求30次平均ADC值,乘以3.3为以3.3电压为基准,除以4096为ADC配置为12位
		ADC_1/=30;
		
		adc1=(uint8_t)(ADC_1/16);
		adc1/=10;
		adc1/=2;
		adc1-=1;
		adc1+=10;		
		__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,adc1);
		printf("ADC_IN0=%d\r\n",adc1);
    /* USER CODE END WHILE */


    /* USER CODE BEGIN 3 */
}

  由于开启了ADC中断及DMA搬运,因此不需要CPU进行处理,我们只需要获取搬运后的的数据,将数据处理到10到20的范围内与占空比对应即可,然后赋值给调节占空比的函数。注意:使用ADC的话虽然设置了连续转换,但是刚开始在main函数处也需要先启动,调用HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,30);函数即可。
  经过上面的配置,摇杆就可以成功控制点击的转速,摇杆杆位拨到最下方为停止,慢慢往上转速越来越高,最上方则达到最高转速。

5. NRF24L01程序设计

  在单板上我们已经完成航模应该具有的基本功能了,包括舵机的控制其实是和电机一样的,因此我们现在就差无线通信了,无线通信涉及2方面的知识点,SPI通信和NRF24L01模块的使用

5.1 NRF24L01引脚介绍

  一共8个引脚,电源引脚就不用说,供电3.3V。
SPI三根线:发送接收和时钟线
CSN:片选引脚,拉低此引脚则选中挂载在SPI总线上的器件
CE:NRF的传输使能引脚,写0不传输,写1开始传输,一般用在NRF写寄存器的时候,先失能,写寄存器,再使能。
IRQ:中断引脚,当发送完成NRF会将此引脚置0

5.2 Cube配置SPI

  我们先配置遥控方(正点原子精英板)的SPI驱动,如图:
在这里插入图片描述
第二步:打开全双工
第三步:配置为主机数据单位为8bit,高位在前
第四步:时钟分频256,根据NRF的SPI特性配置为空闲状态SCL为低电平,第一个时钟边沿采样。

5.3 NRF24L01驱动及功能函数代码编写

nrf24l01.h:

#ifndef __24L01_H
#define __24L01_H

#include "sys.h"

//
//NRF24L01寄存器操作命令
#define NRF_READ_REG    0x00  //读配置寄存器,低5位为寄存器地址
#define NRF_WRITE_REG   0x20  //写配置寄存器,低5位为寄存器地址
#define RD_RX_PLOAD     0x61  //读RX有效数据,1~32字节
#define WR_TX_PLOAD     0xA0  //写TX有效数据,1~32字节
#define FLUSH_TX        0xE1  //清除TX FIFO寄存器.发射模式下用
#define FLUSH_RX        0xE2  //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL     0xE3  //重新使用上一包数据,CE为高,数据包被不断发送.
#define NOP             0xFF  //空操作,可以用来读状态寄存器	 
//SPI(NRF24L01)寄存器地址
#define CONFIG          0x00  //配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能;
                              //bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能
#define EN_AA           0x01  //使能自动应答功能  bit0~5,对应通道0~5
#define EN_RXADDR       0x02  //接收地址允许,bit0~5,对应通道0~5
#define SETUP_AW        0x03  //设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节;
#define SETUP_RETR      0x04  //建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us
#define RF_CH           0x05  //RF通道,bit6:0,工作通道频率;
#define RF_SETUP        0x06  //RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益
#define STATUS          0x07  //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发
                              //bit5:数据发送完成中断;bit6:接收数据中断;
#define MAX_TX  		0x10  //达到最大发送次数中断
#define TX_OK   		0x20  //TX发送完成中断
#define RX_OK   		0x40  //接收到数据中断

#define OBSERVE_TX      0x08  //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器
#define CD              0x09  //载波检测寄存器,bit0,载波检测;
#define RX_ADDR_P0      0x0A  //数据通道0接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P1      0x0B  //数据通道1接收地址,最大长度5个字节,低字节在前
#define RX_ADDR_P2      0x0C  //数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P3      0x0D  //数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P4      0x0E  //数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P5      0x0F  //数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等;
#define TX_ADDR         0x10  //发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等
#define RX_PW_P0        0x11  //接收数据通道0有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P1        0x12  //接收数据通道1有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P2        0x13  //接收数据通道2有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P3        0x14  //接收数据通道3有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P4        0x15  //接收数据通道4有效数据宽度(1~32字节),设置为0则非法
#define RX_PW_P5        0x16  //接收数据通道5有效数据宽度(1~32字节),设置为0则非法
#define NRF_FIFO_STATUS 0x17  //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留
                              //bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环;
//
//24L01操作线
#define NRF24L01_CE(ONorOFF)          if(ONorOFF)                                                        \
                                         HAL_GPIO_WritePin(NRF_CE_GPIO_Port,NRF_CE_Pin,GPIO_PIN_SET);    \
                                      else                                                               \
                                         HAL_GPIO_WritePin(NRF_CE_GPIO_Port,NRF_CE_Pin,GPIO_PIN_RESET);  //24L01片选信号

#define NRF24L01_CSN(ONorOFF)         if(ONorOFF)                                                        \
                                         HAL_GPIO_WritePin(NRF_CS_GPIO_Port,NRF_CS_Pin,GPIO_PIN_SET);    \
                                      else                                                               \
                                         HAL_GPIO_WritePin(NRF_CS_GPIO_Port,NRF_CS_Pin,GPIO_PIN_RESET);  //24L01片选信号 //SPI片选信号	   
																			
#define NRF24L01_IRQ                  HAL_GPIO_ReadPin(NRF_IRQ_GPIO_Port,NRF_IRQ_Pin)                 

																			
//24L01发送接收数据宽度定义
#define TX_ADR_WIDTH    5   	//5字节的地址宽度
#define RX_ADR_WIDTH    5   	//5字节的地址宽度
#define TX_PLOAD_WIDTH  32  	//32字节的用户数据宽度
#define RX_PLOAD_WIDTH  32  	//32字节的用户数据宽度
									   	   
void nrf24l01_Test(void);
void NRF24L01_Init(void);//初始化
void NRF24L01_RX_Mode(void);//配置为接收模式
void NRF24L01_TX_Mode(void);//配置为发送模式
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 u8s);//写数据区
u8 NRF24L01_Read_Buf(u8 reg, u8 *pBuf, u8 u8s);//读数据区		  
u8 NRF24L01_Read_Reg(u8 reg);			//读寄存器
u8 NRF24L01_Write_Reg(u8 reg, u8 value);//写寄存器
u8 NRF24L01_Check(void);//检查24L01是否存在
u8 NRF24L01_TxPacket(u8 *txbuf);//发送一个包的数据
u8 NRF24L01_RxPacket(u8 *rxbuf);//接收一个包的数据

#endif

nrf24l01.c:

#include "spi2.h"
#include "nrf24l01.h"
#include "usart1.h"
#include "adc1.h"
//#include "led.h"

extern SPI_HandleTypeDef hspi2;


const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址
const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址

//针对NRF24L01修改SPI1驱动
void NRF24L01_SPI_Init(void)
{
    __HAL_SPI_DISABLE(&hspi2);               //先关闭SPI2
    hspi2.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平
    hspi2.Init.CLKPhase=SPI_PHASE_1EDGE;     //串行同步时钟的第1个跳变沿(上升或下降)数据被采样
    HAL_SPI_Init(&hspi2);
    __HAL_SPI_ENABLE(&hspi2);                //使能SPI2
}

//初始化24L01的IO口
void NRF24L01_Init(void)
{
  // 下面这段只有正点原子精英板才需要
	// GPIOB12初始化设置:推挽输出
	// 正点原子的NRF和SPIFlash公用SPI2,为了不干扰NRF通信,故取消FLASH片选
  //-----------------------------------------------------------------------------------------------------------------
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟
	//GPIOB12初始化设置:推挽输出
    GPIO_Initure.Pin=GPIO_PIN_12; 			//PB12
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);//PB12输出1,防止SPI FLASH干扰NRF的通信
  //-----------------------------------------------------------------------------------------------------------------
    
    NRF24L01_SPI_Init();                //针对NRF的特点修改SPI的设置	
	NRF24L01_CE(0); 			              //使能24L01
	NRF24L01_CSN(1);			                //SPI片选取消	 		 	 
}
//检测24L01是否存在
//返回值:0,成功;1,失败	
u8 NRF24L01_Check(void)
{
	u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
	u8 i;
	
	SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8); //spi速度为10.5Mhz((24L01的最大SPI时钟为10Mhz,这里大一点没关系)    	 
	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.	
	NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址  
	for(i=0;i<5;i++)if(buf[i]!=0XA5)break;	 							   
	if(i!=5)return 1;//检测24L01错误	
	return 0;		 //检测到24L01
}	 	 
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
	u8 status;	
   	NRF24L01_CSN(0);                //使能SPI传输
  	status =SPI2_ReadWriteByte(reg);//发送寄存器号 
  	SPI2_ReadWriteByte(value);      //写入寄存器的值
  	NRF24L01_CSN(1);                 //禁止SPI传输	   
  	return(status);       		    //返回状态值
}
//读取SPI寄存器值
//reg:要读的寄存器
u8 NRF24L01_Read_Reg(u8 reg)
{
	u8 reg_val;	    
   	NRF24L01_CSN(0);             //使能SPI传输		
  	SPI2_ReadWriteByte(reg);    //发送寄存器号
  	reg_val=SPI2_ReadWriteByte(0XFF);//读取寄存器内容
  	NRF24L01_CSN(1);             //禁止SPI传输	
  		    
  	return(reg_val);            //返回状态值
}	
//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值 
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
	u8 status,u8_ctr;	       
  	NRF24L01_CSN(0);            //使能SPI传输
  	status=SPI2_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值   	   
	for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI2_ReadWriteByte(0XFF);//读出数据
  	NRF24L01_CSN(1);            //关闭SPI传输
  	return status;             //返回读到的状态值
}
//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
	u8 status,u8_ctr;	    
	NRF24L01_CSN(0);             //使能SPI传输
  	status = SPI2_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
  	for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI2_ReadWriteByte(*pBuf++); //写入数据	 
  	NRF24L01_CSN(1);             //关闭SPI传输
  	return status;              //返回读到的状态值
}				   
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf)
{
	u8 sta;
 	SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8); //spi速度为6.75Mhz(24L01的最大SPI时钟为10Mhz)   
	NRF24L01_CE(0); 
  	NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF  32个字节
 	NRF24L01_CE(1);                         //启动发送	   
	while(NRF24L01_IRQ==1);                 //等待发送完成
	sta=NRF24L01_Read_Reg(STATUS);          //读取状态寄存器的值	   
	NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
	if(sta&MAX_TX)                          //达到最大重发次数
	{
		NRF24L01_Write_Reg(FLUSH_TX,0xff);  //清除TX FIFO寄存器 
		return MAX_TX; 
	}
	if(sta&TX_OK)                           //发送完成
	{
		return TX_OK;
	}
	return 0xff;//其他原因发送失败
}
//启动NRF24L01接收一次数据
//txbuf:待发送数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
	u8 sta;		    							   
	SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8); //spi速度为6.75Mhz(24L01的最大SPI时钟为10Mhz)   
	sta=NRF24L01_Read_Reg(STATUS);          //读取状态寄存器的值    	 
	NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
	if(sta&RX_OK)//接收到数据
	{
		NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
		NRF24L01_Write_Reg(FLUSH_RX,0xff);  //清除RX FIFO寄存器 
		return 0; 
	}	   
	
	return 1;//没收到任何数据
}					    
//该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了		   
void NRF24L01_RX_Mode(void)
{
	NRF24L01_CE(0); 	  
  	NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
	  
  	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);       //使能通道0的自动应答    
  	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);   //使能通道0的接收地址  	 
  	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);	        //设置RF通信频率		  
  	NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);//选择通道0的有效数据宽度 	    
  	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);    //设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
  	NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);     //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式 
  	NRF24L01_CE(1);  //CE为高,进入接收模式 
}						 
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
//PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了		   
//CE为高大于10us,则启动发送.	 
void NRF24L01_TX_Mode(void)
{														 
	NRF24L01_CE(0); 	    
	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址 
	NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK	  

	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);     //使能通道0的自动应答    
	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址  
	NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);       //设置RF通道为40
	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);  //设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
	NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);    //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
	NRF24L01_CE(1); //CE为高,10us后启动发送
}


// nrf24l01测试例程
void nrf24l01_Test(void)
{
}

  由于正点原子的板子SPI Flash和NRF共用spi2,因此主函数处还要调用函数NRF24L01_Init()函数来配置Flash的片选引脚然后拉高,避免两个器件相互干扰。这个哈数还调用了一个NRF24L01_SPI_Init();这是正点原子为了让它的SPI配置兼容FLASH和NRF,因为两个器件的SPI的通信协议不同。如果你的SPI是我上面说的那样配置,那这句删掉即可。
  现在我们来写例程,本来也是在nrf24l01.c最后面的测试例程写,后面发现通信不正常,所以直接写在了任务里面,之前的都是写的测试函数直接在任务中调用,任务函数如下:

void Fun_LED2(void *argument)
{
  uint8_t adc1,i;
	
  for(;;)
  {
		 for(i=0,ADC_1=0;i<30;)
		 {
		   	ADC_1+=ADC_Value[i++];   
		 }
		
     //除以30为求30次平均ADC值,乘以3.3为以3.3电压为基准,除以4096为ADC配置为12位
	 	 ADC_1/=30;
		
		 adc1=(uint8_t)(ADC_1/16);
		 adc1/=10;
		 adc1/=2;
		 adc1-=1;
		 adc1+=10;
		 printf("ADC_IN0=%d\r\n",adc1);
		 if(NRF24L01_TxPacket(&adc1)==TX_OK)
	   { 
       printf("发送完成");
     }
     osDelay(1);
	}
}

  这是直接用之前跑马灯的任务函数改了一下内容,所以任务名还是跑马灯的。

  main函数进行任务调度之前可以检查一下NRF是否正常,代码如下:

	HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,30);
	
	while(NRF24L01_Check())
  {
		printf("NRF Wait...");
  }
	printf("NRF OK");
	NRF24L01_TX_Mode();

  注意:一定要在mian函数进行任务调度之前打开ADC的DMA传输哦。
  到这里基本就完成两者的通信了,什么?接收机的还没讲?不用讲了,通信这部分的nrf文件是一模一样的,只需要注意的是,遥控方只负责采集摇杆ADC发送出去,接收方只负责接收发送方的数据并且输出PWM即可。下面贴出接收机的任务函数和mian函数初始化部分。

void key0_Task(void *argument)
{
	u8 i,nrfData;
  /* USER CODE BEGIN key0_Task */
  /* Infinite loop */
  for(;;)
  {	
        if(NRF24L01_RxPacket(&nrfData)==0)//一旦接收到信息,则执行下面代码提示用户,因为最小系统没集成串口,所以用LED来看状态.
		{
	      __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,nrfData);
		  for(i=0;i<10;i++)
	    { 
		    LED1_ON();
		    HAL_Delay(10);
		    LED1_OFF();
		    HAL_Delay(10);
	    }	
		// ADC_DMA_Test();
		// PWM_Tesk();
		osDelay(10);
		}
	}					
  /* USER CODE END key0_Task */
}

main函数任务调度之前调用下面这段

HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
  /* USER CODE BEGIN 2 */
  while(NRF24L01_Check())
  {
		LED0_ON();
  }
	for(i=0;i<10;i++)
	{ 
		LED0_ON();
		HAL_Delay(50);
		LED0_OFF();
		HAL_Delay(50);
	}
	NRF24L01_RX_Mode();

  目前先写这么多,后面要做一个完整的遥控的话包括显示屏这些都得弄。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值