ESP32使用nRF24L01

我用VSCODE中的Platformio中的arduino框架开发。

先说几点感悟,感觉这个模块有点玄学,我是淘宝买的模块,不知道是否质量稳定。

有时程序仅仅加上读FIFO寄存器的一行代码,就通讯失灵;同一块版烧录同一段代码,有时通讯成功有时失败,不知为何。

我没用库,自己写的驱动程序,感觉效果也不错,多长的句子也能立刻接收。

#include <Arduino.h>
#include <SPI.h>
#include "nrf24l01.h"
#include <string>
/*ESP32普通版是接收机*/
#define pinCE 32  //这两个引脚要单独定义,其他引脚直接用SPI库的默认定义就可。
#define pinIRQ 33 

SPIClass *vspi = NULL; //这行代码就能启动SPI库的通讯协议了
void write_Register(uint8_t addr, uint8_t data);
void write_Register(uint8_t addr, uint8_t* data, uint8_t data_len);
void write_Register(uint8_t cmd);
uint8_t read_Register(uint8_t addr);
void read_Register(uint8_t addr, uint8_t byte_num, uint8_t *a);
void RX_Payload(char *rec_data, uint8_t len);
void TX_Payload(const char data[]);
void carrier_wave();
void init_PTX();
void init_PRX();
void check_SPI();
uint8_t TX_ADDRESS[3]={0x1A,0x1A,0x1A};
uint8_t RX_ADDRESS[3]={0x1A,0x1A,0x1A};
bool wave_detected=false;

上面是本次项目的所有函数了。

接收机主函数:

void setup() {
  Serial.begin(115200);
  vspi = new SPIClass(VSPI);
  vspi->begin(14,12,13,15); // 绑定端口引脚
  pinMode(vspi->pinSS(), OUTPUT);
  pinMode(pinCE, OUTPUT);
  pinMode(pinIRQ, INPUT);
  init_PRX();
  attachInterrupt(pinIRQ,carrier_wave,FALLING);
  digitalWrite(pinCE,HIGH);
  write_Register(FLUSH_RX);
}

void loop() {

  if (wave_detected)
  {
      char *rec_data = new char[32]{0};
      uint8_t len = read_Register(R_RX_PL_WID);
      RX_Payload(rec_data, len);
      uint8_t i;
      for (i = 0; i < len; i++)
        Serial.print(rec_data[i]);
      delete[] rec_data;

      write_Register(STATUS_NRF, _BV(RX_DR) | _BV(TX_DS)); // 清除RX中断标志
      write_Register(FLUSH_RX);
      wave_detected = false;
  }
}

接收机我用了中断,nrf24l01的IRQ脚在接收完成正确的信号后会从高电平转低电平,触发中断。接收机只有一种情况会中断,就是RX_DR=1时。注意esp32的中断引脚不要设置成INPUT_PULLUP,强上拉效力有点强,好多时候导致nrf24l01失灵。

这次通讯我用了nrf24l01的动态字节长度功能,所以接收机需要先用R_RX_PL_WID读取字节长度。

void write_Register(uint8_t addr, uint8_t data)
{
  digitalWrite(vspi->pinSS(), LOW);
  vspi->transfer(W_REGISTER | addr);
  vspi->transfer(data);
  digitalWrite(vspi->pinSS(), HIGH);
  vspi->endTransaction();
}
uint8_t read_Register(uint8_t addr)
{
  uint8_t val;
  digitalWrite(vspi->pinSS(), LOW);
  vspi->transfer(R_REGISTER | addr);
  val = vspi->transfer(NOPP);
  digitalWrite(vspi->pinSS(), HIGH);
  vspi->endTransaction();
  return val;
}

这些间接使用的函数我就拿两个出来做例子,看看SPI库如何使用以及nrf24l01的SPI传输机制。

只有几个情况比较特殊,一个是写入/读取FIFO寄存器的信息,一个是使用Activate0x73这种功能。读取RX_FIFO寄存器的信息代码如下:

void RX_Payload(char *rec_data,uint8_t len)
{
  digitalWrite(vspi->pinSS(), LOW);
  vspi->transfer(R_RX_PAYLOAD);
  while (len--)
  {
    *(rec_data++) = vspi->transfer(NOPP);
  }
  digitalWrite(vspi->pinSS(), HIGH);
  vspi->endTransaction();
}

因为payLoad长度是不定的,所以要单独写一个函数。我试过开发芯片手册说的三层FIFO功能,最多96字节,写入payload在TX_FIFO时可以一口气写入96字节不中断,但接收RX_FIFO时,直接一口气读出来是不行的,我也试过分别三次上拉下拉CSN线来读取,也是不行,结果都是只有一层FIFO内的32字节信息。搞了很长时间都不行,我放弃这样做,采用一次读32字节,完成一次操作,重新等待下一次信号传输这样的手法,效果很好。

void TX_Payload(const char data[])
{
  /*往TX FIFO里面填写传输信息*/
  int len = strlen(data);
  Serial.printf("TX payload length is %d\n",len);
  write_Register(FLUSH_TX);
  uint8_t TX_times=(len-1)/32+1;
  while (TX_times--)
  {
    uint8_t k;
    k = len < 32 ? len : 32;
    digitalWrite(vspi->pinSS(), LOW);
    vspi->transfer(W_TX_PAYLOAD);
    while (k--)
    {
      vspi->transfer(*(data++));
    }
    digitalWrite(vspi->pinSS(), HIGH);
    vspi->endTransaction();

    digitalWrite(pinCE, HIGH);
    while (digitalRead(pinIRQ));
    digitalWrite(pinCE, LOW);

    uint8_t status_info = read_Register(STATUS_NRF);
    if (status_info & _BV(TX_DS))
    {
      Serial.printf("Transmited %d\n", TX_times);
      write_Register(STATUS_NRF, _BV(TX_DS) | _BV(RX_DR) | _BV(MAX_RT));
    }

    if (status_info & _BV(MAX_RT))
    {
      Serial.println("Retransmit timeout");
      write_Register(STATUS_NRF, _BV(TX_DS) | _BV(RX_DR) | _BV(MAX_RT));
      return;
    }
    delayMicroseconds(300);
  }
}

写入TX_FIFO时,允许一次发很长的信息,几百字节都行,我的思路是将TX_Payload拆包,拆成一个个32字节逐次发送,还比较实用可靠,看下面的传输结果。

左边是发送机的反馈信息,右边是接收机接收的信息。发送机我用了esp32s3,接收机我用esp32普通版,注意引脚会有稍稍不同,其他大抵一样。222字节程序拆了7次传输,但接收机还是很快全部接收出来。

最后附上初始化nrf24l01的代码:

void init_PTX()
{
  write_Register(CONFIG, _BV(EN_CRC) | _BV(PWR_UP));
  write_Register(EN_AA, _BV(ENAA_P0));//使能pipe0的自动应答
  write_Register(EN_RXADDR, _BV(ERX_P0)); // 用Pipe0的通道接收
  write_Register(SETUP_AW, 0x01);//地址位长度3bytes
  write_Register(SETUP_RETR,0x03);//3次自动重发送,250us重发延迟时间
  write_Register(RF_CH, 0x02);//发射频率为2402Mhz
  // write_Register(RF_SETUP, _BV(LNA_HCURR) | _BV(1) | _BV(2));// 空中速率调慢至1Mbps
  write_Register(STATUS_NRF,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT));//状态寄存器的几个触发值复位
  write_Register(RX_ADDR_P0,TX_ADDRESS,3);//设置pipe0的通讯地址值
  write_Register(TX_ADDR,TX_ADDRESS,3);
  // write_Register(RX_PW_P0,0x20);//设置pipe0接收32byte长度的payload
  digitalWrite(vspi->pinSS(), LOW);
  vspi->transfer(ACTIVATE);
  vspi->transfer(0x73);
  digitalWrite(vspi->pinSS(), HIGH);
  write_Register(FEATURE,_BV(EN_DPL));
  write_Register(DYNPD,_BV(DPL_P0));//pipe0设置动态长度Payload

}

注意下半部分activate的操作,跟普通写入寄存器不一样。写入activate后要马上写入0x73,拉高CSN线后就成功开启nrf24l01的隐藏功能,后面可以像操作普通寄存器一样随意激活nrf24l01的其他隐藏功能,不用重新activate,我这儿只激活了动态payload长度这一个隐藏功能。

总结:发送机和接收机设置成一样的有:发送接收地址、地址长度、RF_CH的通讯频率、payload长度、空中传输速率、自动应答。但个人觉得nrf24l01的传输有时有点玄学,代码都不变的情况下,有时行有时不行,重新烧录同一套代码也是有时行有时不行,按esp32上的rst按钮重新上程序后,又行,不知道是不是我有什么遗漏了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值