STM32
直播中

张勇

8年用户 1548经验值
私信 关注
[问答]

如何用STM32最小系统和模块进行拼接的方式去测试航模程序呢

如何用STM32最小系统和模块进行拼接的方式去测试航模程序呢?怎样去设计一种航模程序呢?

回帖(3)

申换换

2021-12-17 10:52:28
简介


  由于目前还没有绘制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配置






  由于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=%drn",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为低电平,第一个时钟边沿采样。
举报

张英

2021-12-17 10:53:34
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!=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);            //返回状态值
}
举报

张琳

2021-12-17 10:53:41
//在指定位置读出指定长度的数据
//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           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           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=%drn",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();


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

更多回帖

×
20
完善资料,
赚取积分