D调的华丽 发表于 2017-9-10 22:00:12

Arduino DIY航模遥控器第一步 ---搞定nRF24L01!

原创发贴!


    论坛上看到几位大神DIY航模遥控器,特别是罗莉的方案,很给力很亲民。
针对arduino简单强大的用途,打算DIY一款基于arduino的航模遥控器。
    arduino对外设的扩展性非常方便,可以扩展蓝牙接口,通过手机蓝牙连接遥控器进行
参数设置,这样代码中对于屏幕和菜单部分可以简化很多,代码逐步完善,毕竟要工作
不可能那么快。后续手机端打算写个安卓的apk来对遥控器进行设置。
    要搞定DIY遥控,亲民价格中基本绕不开nrf24L01这个东东。arduino针对这个芯片
有不少人写过库,但是在此处使用有很多不方便的地方,所以我重写了nrf的库,后面直接贴代码出来
大家复制粘贴就可以使用。代码我已尽量完善的写了注释,方便大家理解。




    以下代码,可以通过arduino的串口助手直接对nRF24L01的寄存器进行读写操作,方便理解无线模块的控制,无线模块控制没弄懂
写完整的遥控器代码完全是空中楼阁。


下面代码复制粘贴在ardino的新工程中
#include "nrf.h"

/************************************************************************
* 代码禁止应用商业用途,转载需说明原著作者
* 版权 D调的华丽
* 2017-09-10
**********************************************************************/
/************************************************************************
* //引脚定义在nrf.h中,可以根据实际情况修改
#define CE 9   //模式控制
#define CSN 13   //SS片选 ,LOW工作
#define SCK 11//时钟信号
#define MISO 12
#define MOSI 10
#define IRQ 2//中断信号

**************************************************************************
*调试nRF24L01时,可直接串口对nRF24L01的寄存器进行访问,比如要读取
*0x00地址的设置时,直接串口字符串模式 输入R00@aa;读取时,@后的aa为格式补位,可随意输入16进制数
*前面寄存器地址00是16进制格式的0x00,注意16进制数只能用小写,大写支持没加进代码里。
*************************************************************************/



String inputString = "";            //定义串口指令接收字符串
boolean stringComplete = false;   //串口接收指令完后置true
boolean flagRe = false;             //串口接收过程中的辅助标志
byte Order;                      //指令存放地址 ,0位为'W'-写或'R'-读;1位为欲操作的寄存器地址,2位为操作值

//////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
delay(500);
NRF_begin();                  //启动虚拟SPI端口
inputString.reserve(200);   //串口接收中断开启,最大缓存区200

Serial.println("NRF begin!");

}

/////////////////////串口中断服务程序/////////////////////////////
void serialEvent() {
while (Serial.available()) {    //串口数据传入时
   
    char inChar = (char)Serial.read();   //读取一个字节
    if(!flagRe)         //默认标志flagRe为0,第一次进入,进行报文头识别,必须为W 或 R 才会进行后续接收
    {
      if((inChar == 'W') || (inChar == 'R')){
      flagRe = true;         //遇到报文头,标志置1,开启接收过程
      }
    }
    if(flagRe){
       inputString += inChar;            //接收报文
       if (inChar == '\n'){             // 遇到结束符结束
      stringComplete = true;          //接收完一条报文后处理标志开启,进行报文处理
      flagRe = false;               //复位接收标志
      }
      }
}
}

/////////////////////两位字符拼接成1位byte数的函数///////////////////////////////////////
//由于串口接收过来的数据是ASCII格式,要对寄存器和数据进行十六进制翻译

byte CharToHex(char Hnum,char Lnum)
{
byte re = 0;
if(Hnum >=0x30 && Hnum <= 0x39){re |= (Hnum - 0x30) << 4;}
else if(Hnum >= 'a' && Hnum <= 'f'){re |= (Hnum - 87) << 4;}
else return 0;
if(Lnum >=0x30 && Lnum <= 0x39){re |= (Lnum - 0x30);}
else if(Lnum >= 'a' && Lnum <= 'f'){re |= (Lnum - 87);}
else return 0;
return re;
   
}

void loop() {

int flagV = 0,flagEnd = 0;   //定义@符位置标记和结束符;的位置标记
if(stringComplete)   //串口中断事件中接收到一条完整报文后进行下列处理
{
    flagV = inputString.indexOf('@');          //查找@的位置
    flagEnd = inputString.indexOf(';');      //查找;的位置
    if(flagV == 3 && flagEnd == 6)             //根据如:"W0a@1f;"的报文格式,@位于第3,;位于第6,再次判断报文可靠
    {
      Order = inputString.charAt(0);          //如报文格式正确,将W或R写入第一位
      Order = CharToHex(inputString.charAt(1),inputString.charAt(2));//寄存器地址写入第二位
      Order = CharToHex(inputString.charAt(4),inputString.charAt(5));//值写入第三位
      }
      else
      {                                                         //否则输出错误的原因
      Serial.print(inputString);
      Serial.print("flagV is:");Serial.print(flagV);Serial.print("flagEnd is:");Serial.println(flagEnd);
      Serial.println("Enter Err! Enter like Wff@ff; or Rff@ff;");
      }

      if(Order == 'W')          //写命令时执行
      {
      Serial.print(inputString);   //回传执行的命令
      SPI_RW_Reg(Order+0x20,Order);//写入值到相应的寄存器中,注意,nRF24L01的写操作要加0x20的操作指令偏移,不明白去看芯片手册~~
      Serial.print("Write 0x"); Serial.print(Order,HEX); Serial.println(" OK!");
      Serial.print(Order,HEX); Serial.print(" Now is: ");
      byte ccc = SPI_Read(Order);         //写入完成后读取一次确认
      if(ccc<16)
      Serial.print("0x0");else Serial.print("0x");
      Serial.println(ccc,HEX);
      }
      if(Order == 'R')      //读命令时执行
      {
      Serial.print(inputString);
      byte aaa = SPI_Read(Order);            //读取
      Serial.print("Read 0x"); Serial.print(Order,HEX); Serial.print(" is: ");
      if(aaa<16)
      Serial.print("0x0");else Serial.print("0x");
      Serial.println(aaa,HEX);
      }

      //数据复位
      Serial.println("");
      stringComplete = false;
      inputString = "";
      Order = 0;
      Order = 0;
      Order = 0;
}
}


下面代码复制粘贴后文件名保存为 nrf.h 放在ardino的libraries下可以新建一个RNF04L01的文件夹下
#ifndef _NRF_H_
#define _NRF_H_

#include "Arduino.h"

//引脚
#define CE 9   //模式控制
#define CSN 13   //SS片选 ,LOW工作
#define SCK 11
#define MISO 12
#define MOSI 10
#define IRQ 2//中断信号


#define TX_ADR_WIDTH    5   // 5bytes TX(RX) address width

#define TX_PLOAD_WIDTH32// 32 bytes TX payload

#define READ_REG      0x00// Define read command to register
#define WRITE_REG       0x20// Define write command to register
#define RD_RX_PLOAD   0x61// Define RX payload register address
#define WR_TX_PLOAD   0xA0// Define TX payload register address
#define FLUSH_TX      0xE1// Define flush TX register command
#define FLUSH_RX      0xE2// Define flush RX register command
#define REUSE_TX_PL   0xE3// Define reuse TX payload register command
#define NOP             0xFF// Define No Operation, might be used to read status register

//***************************************************//
// SPI(nRF24L01) registers(addresses)
#define CONFIG          0x00// 'Config' register address
#define EN_AA         0x01// 'Enable Auto Acknowledgment' register address
#define EN_RXADDR       0x02// 'Enabled RX addresses' register address
#define SETUP_AW      0x03// 'Setup address width' register address
#define SETUP_RETR      0x04// 'Setup Auto. Retrans' register address
#define RF_CH         0x05// 'RF channel' register address
#define RF_SETUP      0x06// 'RF setup' register address
#define STATUS          0x07// 'Status' register address
#define OBSERVE_TX      0x08// 'Observe TX' register address
#define CD            0x09// 'Carrier Detect' register address
#define RX_ADDR_P0      0x0A// 'RX address pipe0' register address
#define RX_ADDR_P1      0x0B// 'RX address pipe1' register address
#define RX_ADDR_P2      0x0C// 'RX address pipe2' register address
#define RX_ADDR_P3      0x0D// 'RX address pipe3' register address
#define RX_ADDR_P4      0x0E// 'RX address pipe4' register address
#define RX_ADDR_P5      0x0F// 'RX address pipe5' register address
#define TX_ADDR         0x10// 'TX address' register address
#define RX_PW_P0      0x11// 'RX payload width, pipe0' register address
#define RX_PW_P1      0x12// 'RX payload width, pipe1' register address
#define RX_PW_P2      0x13// 'RX payload width, pipe2' register address
#define RX_PW_P3      0x14// 'RX payload width, pipe3' register address
#define RX_PW_P4      0x15// 'RX payload width, pipe4' register address
#define RX_PW_P5      0x16// 'RX payload width, pipe5' register address
#define FIFO_STATUS   0x17// 'FIFO Status Register' register address
#define STA_MARK_RX   0X40
#define STA_MARK_TX   0X20
#define STA_MARK_MX   0X10      



void NRF_begin();

byte SPI_RW(byte BYTE);                              // Single SPI read/write
byte SPI_Read(byte reg);                               // Read one byte from nRF24L01
byte SPI_RW_Reg(byte reg, byte BYTE);                  // Write one byte to register 'reg'
byte SPI_Write_Buf(byte reg, byte *pBuf, byte bytes);// Writes multiply bytes to one register
byte SPI_Read_Buf(byte reg, byte *pBuf, byte bytes);   // Read multiply bytes from one register

void RX_Mode(void);
void TX_Mode(void);
void NRF_power(byte P);
void NRF_size(byte l);
void NRF_channel(byte c);
void NRF_init();//未写完
void NRF_test();//未写完



#endif

下面代码复制粘贴后文件名保存为 nrf.cpp 放在ardino的libraries下可以新建一个RNF04L01的文件夹下
#include "nrf.h"

byte rx_buf;
byte tx_buf;
byte TX_ADDRESS= {0x34,0x43,0x10,0x10,0x01};

void NRF_begin()
{
//SPI.begin();

pinMode(CSN, OUTPUT);
pinMode(SCK, OUTPUT);
pinMode(MOSI, OUTPUT);
pinMode(CE, OUTPUT);
pinMode(MISO, INPUT);

digitalWrite(CSN,HIGH);
digitalWrite(CE,LOW);
digitalWrite(SCK,LOW);

//NRF_init();
}

byte SPI_RW(byte BYTE)
{
//SPI.transfer(BYTE);
byte bit_ctr;

for(bit_ctr=0;bit_ctr<8;bit_ctr++)
{
          if((BYTE&0x80)==0){digitalWrite(MOSI,LOW);} else {digitalWrite(MOSI,HIGH);}
         // MOSI=(BYTE&0x80);
          BYTE = (BYTE<<1);
          //delayMicroseconds(500);
          digitalWrite(SCK,HIGH);
         // delayMicroseconds(500);
          BYTE |= digitalRead(MISO);
         // delayMicroseconds(500);
          digitalWrite(SCK,LOW);
         // delayMicroseconds(500);
         
}
return(BYTE);
}

// 写数据BYTE到reg寄存器
byte SPI_RW_Reg(byte reg, byte BYTE)
{
byte Status;
digitalWrite(CSN,LOW);
Status = SPI_RW(reg);
delayMicroseconds(20);
SPI_RW(BYTE);
digitalWrite(CSN,HIGH);

return(Status);

}

//从reg寄存器读数据出来
byte SPI_Read(byte reg)
{
    byte reg_val;
    digitalWrite(CSN,LOW);
    SPI_RW(reg);
    reg_val = SPI_RW(0);
    digitalWrite(CSN,HIGH);

    return(reg_val);
   
    }

//从reg寄存器中读出bytes个字节
byte SPI_Read_Buf(byte reg, byte *pBuf, byte bytes)
{
byte Status,byte_ctr;
digitalWrite(CSN,LOW);
Status = SPI_RW(reg);

for(byte_ctr = 0; byte_ctr < bytes; byte_ctr++)
{
    pBuf = SPI_RW(0);
    }
   digitalWrite(CSN,HIGH);
      //Serial.println("send OK!");
   return(Status);
}

//把pBuf缓存中的数据写入到nRF24L01中
byte SPI_Write_Buf(byte reg, byte *pBuf, byte bytes)
{
    byte Status,byte_ctr;

    digitalWrite(CSN,LOW);
    Status = SPI_RW(reg);
   
    for(byte_ctr=0; byte_ctr<bytes; byte_ctr++)
      SPI_RW(*pBuf++);
      
    digitalWrite(CSN,HIGH);
   
    return(Status);
   
    }


//设置为接收模式
void RX_Mode(void)
{
digitalWrite(CE,LOW);
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // Use the same address on the RX device as the TX device
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01);      // Enable Auto.Ack:Pipe0
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);// Enable Pipe0
SPI_RW_Reg(WRITE_REG + RF_CH, 40);      // Select RF channel 40
SPI_RW_Reg(WRITE_REG + RX_PW_P0, TX_PLOAD_WIDTH); // Select same RX payload width as TX Payload width
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07);   // TX_PWR:0dBm, Datarate:2Mbps, LNA:HCURR
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0f);   // Set PWR_UP bit, enable CRC(2 bytes) & Prim:RX. RX_DR enabled..

digitalWrite(CE,HIGH);
   
}

void TX_Mode(void)
{
digitalWrite(CE,LOW);

SPI_Write_Buf(WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH);    // Writes TX_Address to nRF24L01
SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, TX_ADDRESS, TX_ADR_WIDTH); // RX_Addr0 same as TX_Adr for Auto.Ack
SPI_Write_Buf(WR_TX_PLOAD, tx_buf, TX_PLOAD_WIDTH); // Writes data to TX payload

SPI_RW_Reg(WRITE_REG + EN_AA, 0x01);      // Enable Auto.Ack:Pipe0
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01);// Enable Pipe0
SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x1a); // 500us + 86us, 10 retrans...
SPI_RW_Reg(WRITE_REG + RF_CH, 40);      // Select RF channel 40
SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07);   // TX_PWR:0dBm, Datarate:2Mbps, LNA:HCURR
SPI_RW_Reg(WRITE_REG + CONFIG, 0x0e);

digitalWrite(CE,HIGH);

}

voidNRF_power(byte P)
{
   digitalWrite(CE,LOW);
   
if(P==3)SPI_RW_Reg(0x06,0x27);   //0db 修正之前注释错误
else if(P==2)SPI_RW_Reg(0x06,0x25);    //-6db
else if(P==1)SPI_RW_Reg(0x06,0x23);    //-12db
else if(P==0)SPI_RW_Reg(0x06,0x21);    //-18db

digitalWrite(CE,HIGH);
}

void NRF_size(byte l)
{
digitalWrite(CE,LOW);
SPI_RW_Reg(0x11,l);
digitalWrite(CE,HIGH);
}
void NRF_channel(byte c)
{
digitalWrite(CE,LOW);
SPI_RW_Reg(0x05,c);
digitalWrite(CE,HIGH);
}


void NRF_init()
{
digitalWrite(CE,LOW);
//SCK=0;
SPI_RW_Reg(0x01,0x00); //禁止 自动应答
SPI_RW_Reg(0x02,0x01); //允许 P0信道
SPI_RW_Reg(0x04,0x00); //禁止 自动重发
TX_Mode();      
NRF_channel(66);
NRF_power(1);
NRF_size(11);
digitalWrite(CE,HIGH);
//TX_address(address);
//RX_address(address);
}
/*








D调的华丽 发表于 2017-9-10 22:09:09

最后回复下,以上代码,大家觉得nrf24l01的串口透传是不是已经在眼前了~;P
其实串口透传就这么简单。

a461624201 发表于 2018-6-15 13:15:28

本帖最后由 a461624201 于 2018-6-30 17:18 编辑

多说一句,这个程序的引脚更改,必须在nrf.h中,在arduono代码中更改无效。

redtxd 发表于 2018-8-23 14:16:29

谢谢老师分享!请教个问题,想要实现电脑与多个终端的通讯,比如电脑发送001#+led,001#终端收到信息后,led闪烁,并向电脑发送001#,用来确认。

谢谢老师。

D调的华丽 发表于 2017-9-10 22:01:29

二楼放两本无线模块的参考资料,相信我,看懂这两本,有这两本就够了~~









lala5 发表于 2017-9-11 15:21:43

好东西 感谢分享~~·

leisd 发表于 2017-9-11 15:46:04

早就买了,搞不定就丢一边去了。
谢谢楼主分享,先收藏有空现试着学。

靳靳 发表于 2017-9-12 15:47:52

能实现双向通信嘛

mm168 发表于 2017-9-12 18:50:12

靳靳 发表于 2017-9-12 15:47
能实现双向通信嘛

能 ..o k ...

lala5 发表于 2018-1-25 14:04:06

难得的好资料~~~感谢分享!

太行摄狼 发表于 2018-1-27 21:38:03

能否吧萝莉的程序修改到ARDUINO上,做萝莉用51程序搞不好,还是开源的好用

a461624201 发表于 2018-1-29 00:20:45

我几年买的NRF24没读出数据,可能以前折腾坏了,这个和淘宝里卖的那种USB转SPI很类似,可以控制,也可以测SPIIC好坏。

a461624201 发表于 2018-1-29 01:22:31

像这样,要么没连接好,要么芯片坏了。

a461624201 发表于 2018-1-29 13:40:27

本帖最后由 a461624201 于 2018-1-29 14:10 编辑

出现一个问题,当连接后,NRF的CSN端一直为4.5V,这样久了会不会损坏芯片,要不要增加个电阻把电压拉低到3.2V?

MWD--文化易人 发表于 2018-3-2 18:54:28

精华里的精华,先收藏,以后学习。目前水平还有距离。

fenzhg 发表于 2018-3-22 14:03:08

你好,看到你在“极客工坊”上发布的“arduino DIY航模遥控器”的文章。我最近想开发一款简易航模遥控器:用于室内固定翼轻型机,遥控距离50米之内。接收器要求越轻越好,最好在1.5g左右。通道只需2个,1个用于输出模拟电压量以控制一个小电机,另一个也是输出模拟的正负电压量,以控制尾翼的小电磁线圈进行左右摆动。 本人不懂这方面的知识,请问您能否给与帮助。谢谢

方恨少 发表于 2018-3-23 19:45:58

D调的华丽 发表于 2017-9-10 22:09
最后回复下,以上代码,大家觉得nrf24l01的串口透传是不是已经在眼前了~
其实串口透传就这么简单。

强大的气场,楼主好牛,好好学习,天天向上。
页: [1] 2
查看完整版本: Arduino DIY航模遥控器第一步 ---搞定nRF24L01!