STM32CubeIDE实现nRF24L01通信(C和C++混合编程)

一、软硬件版本及技术点

  • STM32F103ZET6的开发板(正点原子战舰和精英板)

  • STM32CubeIDE

  • nRF24L01(云佳科技)

  • C与C++混编,c++仅使用了class以及string,比较好恢复为c程序

  • FreeRTOS

  • SPI

二、STM32CubeIDE的工程创建及软件配置

1.打开软件后,点击File→newSTM32Project

2.选择芯片类型
在这里插入图片描述
3.输入项目名、选择为C++目标语言,完成
在这里插入图片描述

4.添加头文件路径、源文件路径等

可以直接做第三节的内容,之后再添加头文件路径,源文件路径等,随着工程的增加要随时添加这部分内容

右击工程名,点击最后的属性propertise后得到如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gHwSYmxZ-1648863808076)(resource/image/image_2995vaonfBHG1gTaYvaSKy.png)]
点击Add增加头文件的路径,没有C++的头文件路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RWzmS2K4-1648863808083)(resource/image/image_h6qEBwCsDHufdXwJ2xQdK2.png)]
这里的C++头文件路径可以照着工程中的Includes添加,如下图,把框出的路径添加到头文件路径。

添加的头文件路径会在这个文件夹中显示**(但是不知道为什么上面的几个文件夹的路径虽然创建工程就显示但是还需要挨个手动添加??)**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HCHlIE40-1648863808085)(resource/image/image_ETirALVcjj2MxfaPwuvvR.png)]
增加的源文件路径也要添加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2lrcEm3-1648863808087)(resource/image/image_shFWASbXNX4kTcxU8P5tAp.png)]

三、STM32CubeIDE配置系统参数

3.1系统时钟配置

采用外部高速晶振和低速外部晶振
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOwLKEHb-1648863808090)(resource/image/image_5dPf7cY2ToJi6nek4DqChn.png)]
在这里插入图片描述

3.2时基配置

debug选用串口线,时基由于使用了FreeRTOS,不建议使用systick
在这里插入图片描述

3.3配置SPI2

采用STM32的SPI2,全双工主机(master)模式

射频芯片的最高为10MHz,SPI速度设置为以下

根据射频芯片的时序图特征更改CPOL以及CPHA

配置如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EnIDNasm-1648863808097)(resource/image/image_sbdGFkLALyzR3PJRVgrps8.png)]
需要增加射频芯片使用的CSN、CE和IRQ引脚,这里要做对应的设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fOZqfQ2s-1648863808100)(resource/image/image_kVYD9Z5RroibPDpzPuTzMK.png)]

3.4配置串口1

配置重点是需要重定向printf函数,<stdio.h>中的printf默认是输出到终端,对于嵌入式设备需要对其重定向,此处重定向到串口1

//生成工程后在对应的uart.c文件中加入重定向代码
//代码增加的地方需要在如下类似的两个宏定义之间,否则重新生成代码将覆盖
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */

#include "stdio.h"//

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1, (uint8_t*)&ch,1,HAL_MAX_DELAY);
    return ch;
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5fPMxh5n-1648864262939)(resource/image/image_4Nns7GQpyLnBYyJsJBgHmy.png)]

3.5配置FreeRTOS

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLBJbxqP-1648863808104)(resource/image/image_eTfdy8gxQtZQET69opDeyd.png)]
在这里插入图片描述
在这里插入图片描述

3.6其他设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eaYwR15x-1648863808110)(resource/image/image_dNB3yadfHa13c8zZWyomRT.png)]
每次修改.ioc文件后都要保存后重新生成代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r43Xog6k-1648863808112)(resource/image/image_dsxBvqTgh57mXJmhU5Uh7C.png)]

四、nRF24L01功能实现

  • 头文件 nRF24L01.h
/*
 * nRF24L01.h
 *
 *  Created on: Mar 28, 2022
 *      Author: MBW
 */

#ifndef NRF2401_NRF24L01_H_
#define NRF2401_NRF24L01_H_

#include <stdint.h>
#define nRF_ASSERT

/* Exported macro ------------------------------------------------------------*/
#ifdef  nRF_ASSERT
/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr If expr is false, it calls assert_failed function
  *         which reports the name of the source file and the source
  *         line number of the call that failed.
  *         If expr is true, it returns no value.
  * @retval None
  */
#define nRF_assert(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);

#else
#define nRF_assert(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */

//use to call C++ function in C file
extern "C" void nRF24L01_wrapper();
extern "C" void nRF24L01_receive();
extern "C" void call_onIRQ();
extern "C" void nRF24L01_callRXmode();


namespace nRF {
//settings
#define nRF_SINGLE_MAXLEN 32
#define ADDRESS_LENGTH 5
#define MAX_TX      0x10
#define TX_OK       0x20
#define RX_OK       0x40
#define TX_ERROR        0x01
#define RX_ERROR        0x02
#define NO_PACKET       0x04
#define NOT_EXIST       0x08

//commands macro
#define NRF_READ_REG    0x00 // Define read command to register
#define NRF_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

//register address
#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 

class nRF24L01 final {
#define RX_ADDRESS 0
#define TX_ADDRESS 1
public:
  nRF24L01();
  virtual ~nRF24L01();

  void onIRQ();         //callback function when enable interrupt
  uint8_t nRFSendMsg(uint8_t* msg, uint8_t msglen); //send or receive message
  uint8_t nRFRecvMsg(uint8_t* msg, uint8_t msglen, uint32_t timeout);
  uint8_t nRFexistDev();   //check if nRF is exist
  void setEnableCE(void(*func)()); //set GPIO control function
  void setDisableCE(void(*func)());
  void setEnableCSN(void(*func)());
  void setDisableCSN(void(*func)());
  void setIrq(uint8_t (*func)());
  void setAddress(uint8_t txorrx, uint8_t* addr, uint8_t len);
  void getAddress(uint8_t txorrx, uint8_t* addr, uint8_t len);
  void setRxMode();
  void setTxMode();
  
private:
  uint8_t nRFWriteReg(uint8_t reg, uint8_t val); //write or read register
  uint8_t nRFReadReg(uint8_t reg);


  uint8_t nRFWriteBuf(uint8_t reg, uint8_t* msg, uint8_t msglen); //send or receive message
  uint8_t nRFReadBuf(uint8_t reg, uint8_t* msg, uint8_t msglen); //send or receive message
  void nRFSPIInit();

  uint8_t nRFSendPacket(uint8_t* packet, uint8_t len);
  uint8_t nRFRecvPacket(uint8_t* packet, uint8_t len);
  void (*enableCE)();
  void (*disableCE)();
  void (*enableCSN)();
  void (*disableCSN)();
  uint8_t (*getIRQ)();
  uint8_t rx_address[5]; 
  uint8_t tx_address[5]; 

};

extern nRF24L01* g_nrf;

} /* namespace nRF */

#endif /* NRF2401_NRF24L01_H_ */

/*
 * nRF24L01.h
 *
 *  Created on: Mar 28, 2022
 *      Author: MBW
 */

#ifndef NRF2401_NRF24L01_H_
#define NRF2401_NRF24L01_H_

#include <stdint.h>
#define nRF_ASSERT

/* Exported macro ------------------------------------------------------------*/
#ifdef  nRF_ASSERT
/**
  * @brief  The assert_param macro is used for function's parameters check.
  * @param  expr If expr is false, it calls assert_failed function
  *         which reports the name of the source file and the source
  *         line number of the call that failed.
  *         If expr is true, it returns no value.
  * @retval None
  */
#define nRF_assert(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);

#else
#define nRF_assert(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */

//use to call C++ function in C file
extern "C" void nRF24L01_wrapper();
extern "C" void nRF24L01_receive();
extern "C" void call_onIRQ();
extern "C" void nRF24L01_callRXmode();


namespace nRF {
//settings
#define nRF_SINGLE_MAXLEN 32
#define ADDRESS_LENGTH 5
#define MAX_TX      0x10
#define TX_OK       0x20
#define RX_OK       0x40
#define TX_ERROR        0x01
#define RX_ERROR        0x02
#define NO_PACKET       0x04
#define NOT_EXIST       0x08

//commands macro
#define NRF_READ_REG    0x00 // Define read command to register
#define NRF_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

//register address
#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 

class nRF24L01 final {
#define RX_ADDRESS 0
#define TX_ADDRESS 1
public:
  nRF24L01();
  virtual ~nRF24L01();

  void onIRQ();         //callback function when enable interrupt
  uint8_t nRFSendMsg(uint8_t* msg, uint8_t msglen); //send or receive message
  uint8_t nRFRecvMsg(uint8_t* msg, uint8_t msglen, uint32_t timeout);
  uint8_t nRFexistDev();   //check if nRF is exist
  void setEnableCE(void(*func)()); //set GPIO control function
  void setDisableCE(void(*func)());
  void setEnableCSN(void(*func)());
  void setDisableCSN(void(*func)());
  void setIrq(uint8_t (*func)());
  void setAddress(uint8_t txorrx, uint8_t* addr, uint8_t len);
  void getAddress(uint8_t txorrx, uint8_t* addr, uint8_t len);
  void setRxMode();
  void setTxMode();
  
private:
  uint8_t nRFWriteReg(uint8_t reg, uint8_t val); //write or read register
  uint8_t nRFReadReg(uint8_t reg);


  uint8_t nRFWriteBuf(uint8_t reg, uint8_t* msg, uint8_t msglen); //send or receive message
  uint8_t nRFReadBuf(uint8_t reg, uint8_t* msg, uint8_t msglen); //send or receive message
  void nRFSPIInit();

  uint8_t nRFSendPacket(uint8_t* packet, uint8_t len);
  uint8_t nRFRecvPacket(uint8_t* packet, uint8_t len);
  void (*enableCE)();
  void (*disableCE)();
  void (*enableCSN)();
  void (*disableCSN)();
  uint8_t (*getIRQ)();
  uint8_t rx_address[5]; 
  uint8_t tx_address[5]; 

};

extern nRF24L01* g_nrf;

} /* namespace nRF */

#endif /* NRF2401_NRF24L01_H_ */

  • nRF24L01.cpp 源文件
/*
 * nRF24L01.cpp
 *
 *  Created on: Mar 28, 2022
 *      Author: MBW
 */

#include <nRF24L01.h>
#include "spi.h"
#include "gpio.h"
#include <string>
#include <stdio.h>

namespace nRF {

nRF24L01* g_nrf = nullptr; //global nrf pointer

nRF24L01::nRF24L01() {

  tx_address[0] = 0xE1; //default address when user not set address
  tx_address[1] = 0xE2;
  tx_address[2] = 0xE3;
  tx_address[3] = 0xE4;
  tx_address[4] = 0xE5;

  rx_address[0] = 0xE1;
  rx_address[1] = 0xE2;
  rx_address[2] = 0xE3;
  rx_address[3] = 0xE4;
  rx_address[4] = 0xE5;

  nRFSPIInit();       //special setting for nRF24L01 device about spi 

}

nRF24L01::~nRF24L01() {
  //do nothing
}

void nRF24L01::setAddress(uint8_t txorrx, uint8_t* addr, uint8_t len) {
  nRF_assert(len >= ADDRESS_LENGTH + 1); // limit len <= 5
  if (txorrx == TX_ADDRESS) {
    tx_address[0] = addr[0];
    tx_address[1] = addr[1];
    tx_address[2] = addr[2];
    tx_address[3] = addr[3];
    tx_address[4] = addr[4];
  } else {
    rx_address[0] = addr[0];
    rx_address[1] = addr[1];
    rx_address[2] = addr[2];
    rx_address[3] = addr[3];
    rx_address[4] = addr[4];
  }
}

void nRF24L01::getAddress(uint8_t txorrx, uint8_t* addr, uint8_t len) {
  nRF_assert(len >= ADDRESS_LENGTH + 1);
  if (txorrx == TX_ADDRESS) {
    addr[0] = tx_address[0];
    addr[1] = tx_address[1];
    addr[2] = tx_address[2];
    addr[3] = tx_address[3];
    addr[4] = tx_address[4];
    addr[5] = 0;
  } else {
    addr[0] = rx_address[0];
    addr[1] = rx_address[1];
    addr[2] = rx_address[2];
    addr[3] = rx_address[3];
    addr[4] = rx_address[4];
    addr[5] = 0;
  }
}

void nRF24L01::setRxMode() {
  disableCE(); //power down state or standby state to configure nRF24L01
  nRFWriteBuf(NRF_WRITE_REG + RX_ADDR_P0, rx_address, ADDRESS_LENGTH);
  nRFWriteReg(NRF_WRITE_REG + EN_AA, 0x01);
  nRFWriteReg(NRF_WRITE_REG + EN_RXADDR, 0x01);
  nRFWriteReg(NRF_WRITE_REG + RF_CH, 40);
  nRFWriteReg(NRF_WRITE_REG + RX_PW_P0, nRF_SINGLE_MAXLEN);
  nRFWriteReg(NRF_WRITE_REG + RF_SETUP, 0x0F);
  nRFWriteReg(NRF_WRITE_REG + CONFIG, 0x0F);
  nRFWriteReg(FLUSH_TX, 0XAA);
  nRFWriteReg(FLUSH_RX, 0XAA);
  enableCE();
  HAL_Delay(1);
}

void nRF24L01::setTxMode() {
  disableCE(); //configure
  nRFWriteBuf(NRF_WRITE_REG + TX_ADDR, tx_address, ADDRESS_LENGTH);
  nRFWriteBuf(NRF_WRITE_REG + RX_ADDR_P0, rx_address, ADDRESS_LENGTH);
  nRFWriteReg(NRF_WRITE_REG + EN_AA, 0x01);
  nRFWriteReg(NRF_WRITE_REG + EN_RXADDR, 0x01);
  nRFWriteReg(NRF_WRITE_REG + SETUP_RETR, 0x1a);
  nRFWriteReg(NRF_WRITE_REG + RF_CH, 40);
  nRFWriteReg(NRF_WRITE_REG + RF_SETUP, 0x0F);
  nRFWriteReg(NRF_WRITE_REG + CONFIG, 0x0E);
  nRFWriteReg(FLUSH_TX, 0XAA);
  nRFWriteReg(FLUSH_RX, 0XAA);
  enableCE();
  HAL_Delay(1);
}

//when interrupt happen, call this function
void nRF24L01::onIRQ() {
  uint8_t msg[33] = {0};
  nRF::g_nrf->nRFRecvMsg(msg, 32 , 60*1000);
  msg[32] = 0;
  printf("irq get msg: %s\r\n", msg);
}

//wrapper C++ function for using in C file
extern "C" void call_onIRQ() {
  nRF::g_nrf->onIRQ();
}

//set irq GPIO
void nRF24L01::setIrq(uint8_t (*func)()) {
  getIRQ = func;
}

//send msg longer than 32 bytes
uint8_t nRF24L01::nRFSendMsg(uint8_t* msg, uint8_t msglen) {
  uint8_t packetnum = msglen / nRF_SINGLE_MAXLEN;
  uint8_t txresult;
  for (int i = 0; i < packetnum; i++) {
    txresult = nRFSendPacket(msg + i * nRF_SINGLE_MAXLEN, nRF_SINGLE_MAXLEN);
    if (txresult != TX_OK) return txresult;
  }
  if (msglen % nRF_SINGLE_MAXLEN) {
    txresult = nRFSendPacket(msg + packetnum * nRF_SINGLE_MAXLEN, nRF_SINGLE_MAXLEN);
    if (txresult != TX_OK) return txresult;
  }
  return SUCCESS;
}

//send a packet less than 32 bytes
uint8_t nRF24L01::nRFSendPacket(uint8_t* packet, uint8_t len) {
  disableCE();
  nRFWriteBuf(WR_TX_PLOAD , packet, len);    //actually , len must be 32 if send packet
  nRFWriteReg(NRF_WRITE_REG + CONFIG, 0x0E); //set to txmode
  enableCE();  //enable TX
  HAL_Delay(1);
  while(getIRQ() != DISABLE); //wait for TX_OK or MAX_TX
  disableCE();
  HAL_Delay(1);
  uint8_t status = nRFReadReg(NRF_READ_REG + STATUS); //get STATUS 
  nRFWriteReg(NRF_WRITE_REG + STATUS, status); //clear interrupt
  nRFWriteReg(FLUSH_TX, 0xff); //clear TX FIFO
  if (status & MAX_TX) {
    return MAX_TX;
  }
  if (status & TX_OK) {
    return TX_OK;
  }
  return TX_ERROR;
}

uint8_t nRF24L01::nRFRecvPacket(uint8_t* packet, uint8_t len) {
  uint8_t status = nRFReadReg(NRF_READ_REG + STATUS);
  nRFWriteReg(NRF_WRITE_REG + STATUS, status);  //clear interrupt
  //auto clear FIFO after read RX FIFO
  if (status & RX_OK) {
    nRFReadBuf(RD_RX_PLOAD, packet, len);
    return SUCCESS;
  }
  return NO_PACKET;
}

//receive the designated length msg in timeout
uint8_t nRF24L01::nRFRecvMsg(uint8_t* msg, uint8_t msglen, uint32_t timeout) {
  uint8_t packetnum = msglen / nRF_SINGLE_MAXLEN;
  uint8_t cnt = 0;
  uint32_t tickstart = HAL_GetTick();

  while (HAL_GetTick()-tickstart < timeout) {
    if (cnt < packetnum) {
      int ret = nRFRecvPacket(msg + cnt * nRF_SINGLE_MAXLEN, nRF_SINGLE_MAXLEN);
      if (ret == SUCCESS)  cnt++;
    } else {
      if (msglen % nRF_SINGLE_MAXLEN == 0) return SUCCESS;
      int ret = nRFRecvPacket(msg + packetnum * nRF_SINGLE_MAXLEN, msglen % nRF_SINGLE_MAXLEN);
      if (ret == SUCCESS)  return SUCCESS;
    }
  }
  return RX_ERROR;
}

uint8_t nRF24L01::nRFWriteBuf(uint8_t reg, uint8_t* msg, uint8_t msglen) {
  uint8_t status;
  uint8_t nouse;
  nRF_assert(msglen <= nRF_SINGLE_MAXLEN);
  disableCSN();
  HAL_SPI_TransmitReceive(&hspi2, &reg, &status,1, 10);
  for (int i = 0; i < msglen; i++) HAL_SPI_TransmitReceive(&hspi2, msg+i, &nouse , 1, 10);
  enableCSN();
  return status;
}

uint8_t nRF24L01::nRFReadBuf(uint8_t reg, uint8_t* msg, uint8_t msglen) {
  uint8_t status;
  uint8_t nouse;
  nRF_assert(msglen <= nRF_SINGLE_MAXLEN);
  disableCSN();
  HAL_SPI_TransmitReceive(&hspi2, &reg, &status, 1, 10);
  for (int i = 0; i < msglen; i++) HAL_SPI_TransmitReceive(&hspi2, &nouse, msg+i, 1, 10);
  enableCSN();
  return status;
}

uint8_t nRF24L01::nRFWriteReg(uint8_t reg, uint8_t val) {
  uint8_t status;
  uint8_t nouse;
  disableCSN();
  HAL_SPI_TransmitReceive(&hspi2, &reg, &status, 1, 10);
  HAL_SPI_TransmitReceive(&hspi2, &val, &nouse, 1, 10);
  enableCSN();
  return status;
}

uint8_t nRF24L01::nRFReadReg(uint8_t reg) {
  uint8_t regval;
  uint8_t nouse;
  disableCSN();
  HAL_SPI_TransmitReceive(&hspi2, &reg, &nouse, 1, 10);
  HAL_SPI_TransmitReceive(&hspi2, &nouse, &regval, 1, 10);
  enableCSN();
  return regval;
}

//check if connect the device
uint8_t nRF24L01::nRFexist() {
  uint8_t buf[5] = {0,0,0,0,0};
  disableCE();
  nRFWriteBuf(NRF_WRITE_REG+TX_ADDR, tx_address, ADDRESS_LENGTH);
  nRFReadBuf(NRF_READ_REG+TX_ADDR, buf, ADDRESS_LENGTH);
  for (int i = 0; i < ADDRESS_LENGTH; i++) {
    if (buf[i] != tx_address[i]) return NOT_EXIST;
  }
  return SUCCESS;
}

//special for SPI setting
void nRF24L01::nRFSPIInit() {
  __HAL_SPI_DISABLE(&hspi2);
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
    HAL_SPI_Init(&hspi2);
  __HAL_SPI_ENABLE(&hspi2);
}

void nRF24L01::setEnableCE(void(*func)()) {
  enableCE = func;
}

void nRF24L01::setDisableCE(void(*func)()) {
  disableCE = func;
  disableCE();
}

void nRF24L01::setEnableCSN(void(*func)()) {
  enableCSN = func;
  enableCSN();
}

void nRF24L01::setDisableCSN(void(*func)()) {
  disableCSN = func;
}

} /* namespace nRF */

//用于C文件调用C++,作为对应C++代码的wrapper
//函数前需要加上 extern "C" ,表示按照C方式编译
//可新建位置或者放在源文件中

extern "C" void nRF24L01_callSend() {

  std::string msg = "hello nrf24 hello nrf24 hello nrf24 hello nrf24";
  printf("send msg begin\r\n");
  nRF::nRF24L01 nrf1;
  nrf1.setEnableCE(setCE);
  nrf1.setDisableCE(resetCE);
  nrf1.setEnableCSN(setCSN);
  nrf1.setDisableCSN(resetCSN);
  nrf1.setIrq(getIrq);

  HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);

  if (nrf1.nRFexistDev() == 0) {
    nrf1.setTxMode();
    uint8_t ret = nrf1.nRFSendMsg((uint8_t*)msg.c_str(), msg.length());
    if (ret == SUCCESS) printf("send result = %d\r\n", ret);
    else {
      printf("send failed [errorcode:0X%X]\r\n", ret);
    }
  } else {
    printf("no device!\r\n");
  }
  printf("send msg end\r\n");
}

extern "C" void nRF24L01_callRXmode() {
  nRF::g_nrf = new nRF::nRF24L01;
  nRF::g_nrf->setEnableCE(setCE);
  nRF::g_nrf->setDisableCE(resetCE);
  nRF::g_nrf->setEnableCSN(setCSN);
  nRF::g_nrf->setDisableCSN(resetCSN);
  printf("rx mode wait interrupt\r\n");

  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

  nRF::g_nrf->setRxMode();

}

extern "C" void nRF24L01_receiveTimout() {
  uint8_t msg[50] = {0};
  nRF::nRF24L01 nrf1;
  nrf1.setEnableCE(setCE);
  nrf1.setDisableCE(resetCE);
  nrf1.setEnableCSN(setCSN);
  nrf1.setDisableCSN(resetCSN);
  nrf1.setIrq(getIrq);


  printf("waiting msg...\r\n");


  nrf1.setRxMode();

  nrf1.nRFRecvMsg(msg, 32 , 60*1000); //waiting 60s

  printf("msg : %s\r\n", msg);

}

//wrapper C++ function for using in C file
extern "C" void call_onIRQ() {
  nRF::g_nrf->onIRQ();
}

五、代码的使用方式

  1. 生成的main.c文件要更名为main.cpp (每次更改ioc重新生成代码之前先改回main.c,生成后再改为main.cpp,如果不改,则会另外生成一个main.c)

    main.c改为main.cpp 后要把 void MX_FREERTOS_Init(void) 前加上 extern "C" ,因为这个函数本身是freertos.c文件中定义的,main.c更名为main.cpp找不到对应的符号,因为C和C++编译为不同的符号

  2. 无论发送还是接收都要进行如下的函数的处理,即绑定使用的引脚对应的GPIO
    nRF::g_nrf->setEnableCE(setCE);
    nRF::g_nrf->setDisableCE(resetCE);
    nRF::g_nrf->setEnableCSN(setCSN);
    nRF::g_nrf->setDisableCSN(resetCSN);

//gpio.c中设置了这几个函数,也可以直接传入HAL_GPIO_WritePIN()
void setCE() {HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_SET);}
void resetCE() {HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_RESET);}
void setCSN() {HAL_GPIO_WritePin(GPIOG, GPIO_PIN_7, GPIO_PIN_SET);}
void resetCSN() {HAL_GPIO_WritePin(GPIOG, GPIO_PIN_7, GPIO_PIN_RESET);}
uint8_t getIrq() { return HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6);}

3. C和C++混合编程,采用的FreeRTOS创建任务,可以在对应的任务中增加Cwrapper后的代码

作为发送:

可以开辟一个缓冲区,用于填入发送的数据,然后调用nRF24L01_callSend 函数,进行发送。

作为接收:

  1. nRF24L01_receiveTimout 这个可以设置为在一定时间内接收一定大小的数据

  2. 或者采用中断的方式,收到数据后存储到指定的缓冲区,收集一定的数据后进行数据的处理

    中断函数尽量处理不高耗时的任务,中断可只做数据接收后放到指定的开辟的缓冲区

    处理缓冲区的工作在工作线程中执行,中断只负责转移数据

六、遇到的问题

问题1

  1. 不能够正确的写入到nRF24L01的寄存器,或者部分写入

  2. 读取数据超时

  3. 每次读取的错误数据是相同的,问题可复现

//第一块模块

//STM32CUBEIde读出的内容
send msg begin
txbuf : 0xE5 0xB5 0xE2 0xA0 0xB2 
rxbuf : 0xE5 0xB5 0xE2 0xA0 0xB2 
enaa :0x7
enrxaddr :0x7
setup :0x46
rfch :0x88
rfsetup :0x1
config = 0x2A
status = 0xE
send msg end



//正点原子代码读出的寄存器内容
txbuf : 0xb2 0xe5 0xb5 0xe2 0xa0   //0xb2数据位置错了,其他没问题
rxbuf : 0xb2 0xe5 0xb5 0xe2 0xa0 
EN_AA = 7
EN_RXADDR = 7
SETUP RETRY = 46
RF_CH = 88
RF_SETUP = F   //不同
CONFIG = 2A
STAUTS = E

//第二块模块

//STM32CUBEIde读出的内容
send msg begin
txbuf : 0xE7 0xE7 0xE7 0xE7 0xE7 
rxbuf : 0xE7 0xE7 0xE7 0xE7 0xE7 
enaa :0x3F
enrxaddr :0x3
setup :0x3
rfch :0x2
rfsetup :0x0
config = 0x8
status = 0xE
send msg end

//正点原子代码读出的寄存器内容
txbuf : 0xe7 0xe7 0xe7 0xe7 0xe7  //不具代表性
rxbuf : 0xe7 0xe7 0xe7 0xe7 0xe7 
EN_AA = 3F
EN_RXADDR = 3
SETUP RETRY = 3
RF_CH = 2
RF_SETUP = F   //不同
CONFIG = 8
STAUTS = E


//第二块第第二次测试

//STM32CUBEIde读出的内容
send msg begin
txbuf : 0x43 0x10 0x10 0x1 0x34 
rxbuf : 0x43 0x10 0x10 0x1 0x34 
enaa :0x1
enrxaddr :0x1
setup :0x1A
rfch :0x40
rfsetup :0x0
config = 0xE
status = 0xE
send msg end

//正点原子代码读出的寄存器内容
txbuf : 0x34 0x43 0x10 0x10 0x1  //有区别
rxbuf : 0x34 0x43 0x10 0x10 0x1 
EN_AA = 1
EN_RXADDR = 1
SETUP RETRY = 1A
RF_CH = 40
RF_SETUP = F //不同
CONFIG = E
STAUTS = E

解决方法

  1. 更改了disable CRC

  2. 项目中更改了HAL_SPI_TransmitHAL_SPI_TransmitReceive,可能是由于前者仅发送处理接收区,导致SPI同步收到的数据未被清除(仅猜测)

问题2

硬件错误,按下按键后发送第一次之后即发生Hard_Fault

解决方式:

增加线程栈空间为512

问题3

接收不到数据,发送端表示已经重发引起中断

解决方式:

  1. 经过测试发现,每次写给TX_FIFO必须写入32字节,否则nRF24L0不会发送!!

  2. 每次进入发送或者接收状态要清除缓冲区,防止错误导致缓冲区存在数据

七、建议

没事干的话改成纯c吧,C++没啥必要哈哈哈

参考资料

STM32—cubeMX+HAL库的SPI接口使用_夜风~的博客-CSDN博客_cubemx spi

正点原子库函数手册

nRF24L01的手册

STM32CubeIDE使用技巧(FreeRTOS点亮一盏灯)_重拾十年梦的博客-CSDN博客_cubeide自动补全

STM32L051C8T6 HAL库 + nRF24L01 收发案例(硬件SPI通讯)_Ch_champion的博客-CSDN博客_hal spi实例

使用STM32CubeMX开发三:按键中断实验 - 无网不进 - 博客园 (cnblogs.com)

attribute((weak)) 简介及作用_侵蚀昨天的博客-CSDN博客___attribute__((weak))

使用stm32cubeIDE git代码(gitee)_violet1714的博客-CSDN博客

stm32 FreeRTOS 某个任务一直不被运行_zhuimeng_ruili的博客-CSDN博客_freertos任务不运行

STM32CubeMX FreeRTOS堆栈分配、调试技巧 - 云+社区 - 腾讯云 (tencent.com)

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在STM32CubeIDE中使用nRF24L01模块进行通信,您可以按照以下步骤进行操作: 1. 首先,确保您已正确连接nRF24L01模块到STM32微控制器。通常情况下,您需要连接SPI接口的SCK、MISO、MOSI和CSN引脚,以及CE引脚和IRQ引脚。 2. 在STM32CubeIDE中创建一个新的工程,并选择适合您的STM32微控制器型号。 3. 在工程中添加适当的驱动程序来支持SPI接口。您可以使用STM32Cube库中的HAL库函数来简化与SPI外设的通信。 4. 在代码中初始化SPI接口,并配置nRF24L01模块。您需要设置SPI的速度、数据位数等,并设置nRF24L01的寄存器来配置其工作方式,例如设置频道、地址等。 5. 在发送端,在代码中设置发送数据包,并使用SPI接口将数据写入nRF24L01的发送缓冲区。您可以使用HAL库函数来实现SPI的发送操作。 6. 在接收端,在代码中配置nRF24L01为接收模式,并使用SPI接口读取nRF24L01的接收缓冲区中的数据。您可以使用HAL库函数来实现SPI的接收操作。 7. 使用适当的输出方式处理接收到的数据。您可以选择将数据通过串口输出、LCD显示或其他方式进行处理和显示。 请注意,以上步骤只是一个基本的指导,具体实现可能因所选的STM32微控制器型号、nRF24L01模块的型号和编程风格而有所不同。您可能需要参考nRF24L01STM32的相关文档和例程来获取更详细的信息和代码示例。同时,您还需要了解nRF24L01通信协议和寄存器设置,以便正确配置和操作该模块。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值