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);
}
/*
最后回复下,以上代码,大家觉得nrf24l01的串口透传是不是已经在眼前了~;P
其实串口透传就这么简单。 本帖最后由 a461624201 于 2018-6-30 17:18 编辑
多说一句,这个程序的引脚更改,必须在nrf.h中,在arduono代码中更改无效。 谢谢老师分享!请教个问题,想要实现电脑与多个终端的通讯,比如电脑发送001#+led,001#终端收到信息后,led闪烁,并向电脑发送001#,用来确认。
谢谢老师。 二楼放两本无线模块的参考资料,相信我,看懂这两本,有这两本就够了~~
好东西 感谢分享~~· 早就买了,搞不定就丢一边去了。
谢谢楼主分享,先收藏有空现试着学。
能实现双向通信嘛 靳靳 发表于 2017-9-12 15:47
能实现双向通信嘛
能 ..o k ... 难得的好资料~~~感谢分享! 能否吧萝莉的程序修改到ARDUINO上,做萝莉用51程序搞不好,还是开源的好用 我几年买的NRF24没读出数据,可能以前折腾坏了,这个和淘宝里卖的那种USB转SPI很类似,可以控制,也可以测SPIIC好坏。 像这样,要么没连接好,要么芯片坏了。 本帖最后由 a461624201 于 2018-1-29 14:10 编辑
出现一个问题,当连接后,NRF的CSN端一直为4.5V,这样久了会不会损坏芯片,要不要增加个电阻把电压拉低到3.2V? 精华里的精华,先收藏,以后学习。目前水平还有距离。 你好,看到你在“极客工坊”上发布的“arduino DIY航模遥控器”的文章。我最近想开发一款简易航模遥控器:用于室内固定翼轻型机,遥控距离50米之内。接收器要求越轻越好,最好在1.5g左右。通道只需2个,1个用于输出模拟电压量以控制一个小电机,另一个也是输出模拟的正负电压量,以控制尾翼的小电磁线圈进行左右摆动。 本人不懂这方面的知识,请问您能否给与帮助。谢谢 D调的华丽 发表于 2017-9-10 22:09
最后回复下,以上代码,大家觉得nrf24l01的串口透传是不是已经在眼前了~
其实串口透传就这么简单。
强大的气场,楼主好牛,好好学习,天天向上。
页:
[1]
2