This code is suitable for small wireless transceiver modules composed of NRF24L01 and STC15F204EA (STC15L204EA), and can also be applied to general 51 microcontrollers communicating with NRF24L01.
A few days ago, I bought a small wireless transceiver module online. The source code provided by the seller was very simple and not particularly useful, so I ported the STM3224l01 program from ATOM, which is more complete and practical.
Since there are almost no pins available to display transmission and reception content, serial communication is used to display the content.
Applicable module image:
Main wiring diagram:
First, for serial display, I collected the following code. The reason it’s so complex is that this chip doesn’t have a serial port function.
First is the H file:
#ifndef _UART_H
#define _UART_H
#define MCU_FREQ 11059200 // Set crystal frequency
#define UART_BUAD 38400
#define ON 1
#define OFF 0
#define UART_TX_PIN P31
#define UART_TX_SET(n) UART_TX_PIN = n
#define UART_TX_HIGH() UART_TX_SET(1)
#define UART_TX_LOW() UART_TX_SET(0)
#define UART_TX_FLIP() UART_TX_PIN = !UART_TX_PIN
#define UART_RX_PIN P30
#define UART_RX_SET(n) UART_RX_PIN = n
#define UART_RX_HIGH() UART_RX_SET(1)
#define UART_RX_LOW() UART_RX_SET(0)
#define UART_RX_FLIP() UART_RX_PIN = !UART_RX_PIN
void uartInit(void);
void uartSendString(char *pS);
void uartSendNum(int num);
#endif
In the file, you need to set the crystal frequency and baud rate correctly, which are MCU_FREQ and UART_BUAD respectively. Once set, they enable correct transmission and reception.
Next is the C file:
#include "stdio.h"
#include "uart.h"
#include "15f204ea.h" // Header file provided by STC official website
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned char BYTE;
static bit bUartFlag;
/******************************************************************************/
// Function name: uartInit
// Input parameters: none
// Output parameters: none
// Function: Set up timer0 operating mode
/******************************************************************************/
void uartInit(void)
{
/*
* Set timer0 as 16-bit auto-reload timer
*/
AUXR |= 0x80; // Timer0 in 1T mode
TMOD &= 0xF0; // Set timer to mode 0 (16-bit auto-reload)
TL0 = (0xFFFF - MCU_FREQ / UART_BUAD) & 0xFF; // Set timer initial value
TH0 = ((0xFFFF - MCU_FREQ / UART_BUAD) >> 8) & 0xFF; // Set timer initial value
TR0 = 0; // Timer0 starts counting
ET0 = 0; // Enable timer0 interrupt
EA = 1;
}
/******************************************************************************/
// Function name: uartSendData
// Input parameters: ucData: byte to send
// Output parameters: none
// Function: Use serial port to send one byte of data
/******************************************************************************/
void uartSendData(u8 ucData)
{
u8 ucCnt;
UART_TX_LOW(); // Serial start bit begins
TR0 = 1; // Timer0 starts counting
ET0 = 1; // Enable timer0 interrupt
bUartFlag = ON;
while(bUartFlag == ON);
/*
* Starting from the lowest bit, output data through serial port
*/
for (ucCnt = 0; ucCnt < 8; ucCnt++) {
UART_TX_SET(ucData & 0x01);
ucData >>= 1;
bUartFlag = ON;
while(bUartFlag == ON);
}
UART_TX_HIGH(); // Send serial stop bit
bUartFlag = ON;
while(bUartFlag == ON);
TR0 = 0; // Timer0 stops counting
ET0 = 0; // Disable timer0 interrupt
}
/******************************************************************************/
// Function name: uartSendString
// Input parameters: pS: string's address
// Output parameters: none
// Function: Send string through serial output
/******************************************************************************/
void uartSendString(char *pS)
{
while (*pS) // Check for string end marker
{
uartSendData(*pS++); // Send current character
}
uartSendData('\r');
uartSendData('\n');
}
void uartSendNum(int num){ // Use sprintf function to print integer (can also print decimal)
char temp[14];
sprintf(temp,"%d",num);
uartSendString(temp);
}
/******************************************************************************/
// Function name: time0ISR
// Input parameters: none
// Output parameters: none
// Function: Serial port 0 service function
/******************************************************************************/
void time0ISR(void) interrupt 1 using 1
{
EA = 0;
bUartFlag = OFF;
EA = 1;
}
This code calls 15f204ea.h, which is a header file I downloaded from the STC website. For ease of reference, I won’t list the entire file here as it’s too long.
Now that we’ve handled the serial display, let’s move on to the 24l01 program code.
First is the H file:
#ifndef __24L01_H
#define __24L01_H
#include "15f204ea.h"
#define u8 unsigned char
#define u16 unsigned int
typedef unsigned char uchar;
typedef unsigned char uint;
/*nRF24L01 pin definitions*/
sbit CE = P1^4;
sbit CSN = P1^5;
sbit SCK = P1^2;
sbit MOSI = P1^3;
sbit MISO = P1^0;
sbit IRQ = P1^1;
//NRF24L01 register operation commands
#define READ_NRF_REG 0x00 //Read configuration register, lower 5 bits are register address
#define WRITE_NRF_REG 0x20 //Write configuration register, lower 5 bits are register address
#define RD_RX_PLOAD 0x61 //Read RX valid data, 1~32 bytes
#define WR_TX_PLOAD 0xA0 //Write TX valid data, 1~32 bytes
#define FLUSH_TX 0xE1 //Clear TX FIFO register. Used in transmit mode
#define FLUSH_RX 0xE2 //Clear RX FIFO register. Used in receive mode
#define REUSE_TX_PL 0xE3 //Reuse last sent packet, CE is high, data packet is continuously sent
#define NOP 0xFF //No operation, can be used to read status register
//SPI(NRF24L01) register addresses
#define CONFIG 0x00 //Configuration register address; bit0:1 receive mode, 0 transmit mode; bit1:power select; bit2:CRC mode; bit3:CRC enable;
//bit4:interrupt MAX_RT (max retransmit interruption) enable; bit5:interrupt TX_DS enable; bit6:interrupt RX_DR enable
#define EN_AA 0x01 //Enable auto-acknowledge function bit0~5, corresponding to channels 0~5
#define EN_RXADDR 0x02 //Receive address allow, bit0~5, corresponding to channels 0~5
#define SETUP_AW 0x03 //Set address width (all data channels): bit1,0:00,3 bytes; 01,4 bytes; 02,5 bytes;
#define SETUP_RETR 0x04 //Set auto retransmit; bit3:0, auto retransmit counter; bit7:4, auto retransmit delay 250*x+86us
#define RF_CH 0x05 //RF channel, bit6:0, working channel frequency;
#define RF_SETUP 0x06 //RF register; bit3:transmission rate(0:1Mbps,1:2Mbps); bit2:1, transmission power; bit0:low noise amplifier gain
#define STATUS 0x07 //Status register; bit0:TX FIFO full flag; bit3:1, receive data channel number (max:6); bit4, reached max retransmit
//bit5:data send complete interrupt; bit6:receive data interrupt
#define MAX_TX 0x10 //Reached maximum send times interrupt
#define TX_OK 0x20 //TX send complete interrupt
#define RX_OK 0x40 //Received data interrupt
#define OBSERVE_TX 0x08 //Send detection register, bit7:4, data packet loss counter; bit3:0, retransmit counter
#define CD 0x09 //Carrier detection register, bit0, carrier detection;
#define RX_ADDR_P0 0x0A //Data channel 0 receive address, max length 5 bytes, low byte first
#define RX_ADDR_P1 0x0B //Data channel 1 receive address, max length 5 bytes, low byte first
#define RX_ADDR_P2 0x0C //Data channel 2 receive address, lowest byte can be set, high bytes must be equal to RX_ADDR_P1[39:8];
#define RX_ADDR_P3 0x0D //Data channel 3 receive address, lowest byte can be set, high bytes must be equal to RX_ADDR_P1[39:8];
#define RX_ADDR_P4 0x0E //Data channel 4 receive address, lowest byte can be set, high bytes must be equal to RX_ADDR_P1[39:8];
#define RX_ADDR_P5 0x0F //Data channel 5 receive address, lowest byte can be set, high bytes must be equal to RX_ADDR_P1[39:8];
#define TX_ADDR 0x10 //Send address (low byte first), under ShockBurstTM mode, RX_ADDR_P0 equals this address
#define RX_PW_P0 0x11 //Receive data channel 0 valid data width (1~32 bytes), setting to 0 is invalid
#define RX_PW_P1 0x12 //Receive data channel 1 valid data width (1~32 bytes), setting to 0 is invalid
#define RX_PW_P2 0x13 //Receive data channel 2 valid data width (1~32 bytes), setting to 0 is invalid
#define RX_PW_P3 0x14 //Receive data channel 3 valid data width (1~32 bytes), setting to 0 is invalid
#define RX_PW_P4 0x15 //Receive data channel 4 valid data width (1~32 bytes), setting to 0 is invalid
#define RX_PW_P5 0x16 //Receive data channel 5 valid data width (1~32 bytes), setting to 0 is invalid
#define FIFO_STATUS 0x17 //FIFO status register; bit0, RX FIFO register empty flag; bit1, RX FIFO full flag; bit2,3, reserved
//bit4, TX FIFO empty flag; bit5, TX FIFO full flag; bit6,1, cycle send previous data packet. 0, don't cycle;
//////////////////////////////////////////////////////////////////////////////
//24L01 operation lines
#define NRF24L01_CE CE //24L01 chip select signal
#define NRF24L01_CSN CSN //SPI chip select signal
#define NRF24L01_IRQ IRQ //IRQ host data input
//24L01 send and receive data width definitions
#define TX_ADR_WIDTH 5 //5 bytes address width
#define RX_ADR_WIDTH 5 //5 bytes address width
#define TX_PLOAD_WIDTH 32 //20 bytes user data width
#define RX_PLOAD_WIDTH 32 //20 bytes user data width
void NRF24L01_Init(void);//Initialization
void RX_Mode(void);//Configure as receive mode
void TX_Mode(void);//Configure as transmit mode
u8 NRF24L01_Check(void);//Check if 24L01 exists
u8 NRF24L01_TxPacket(u8 *txbuf);//Send a packet of data
u8 NRF24L01_RxPacket(u8 *rxbuf);//Receive a packet of data
#endif
Next is the C file:
#include "15f204ea.h"
#include "24l01.h"
#include "intrins.h"
const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x03}; //Transmit address
const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x03}; //Receive address
/******************************************************************************************
/*Delay function
/******************************************************************************************/
void inerDelay_us(unsigned char n)
{
for(;n>0;n--)
_nop_();
}
//****************************************************************************************
/*NRF24L01 initialization
//***************************************************************************************/
void NRF24L01_Init(void)
{
inerDelay_us(100);
CE=0; // chip enable
CSN=1; // Spi disable
SCK=0; //
}
/****************************************************************************************************
/*Function: uint SPI_RW(uint uchar)
/*Function: NRF24L01 SPI write sequence
/****************************************************************************************************/
uint SPI_RW(uint uchar)
{
uint bit_ctr;
for(bit_ctr=0;bit_ctr<8;bit_ctr++) // output 8-bit
{
MOSI = (uchar & 0x80); // output 'uchar', MSB to MOSI
uchar = (uchar << 1); // shift next bit into MSB..
SCK = 1; // Set SCK high..
uchar |= MISO; // capture current MISO bit
SCK = 0; // ..then set SCK low again
}
return(uchar); // return read uchar
}
/****************************************************************************************************
/*Function: uchar SPI_Read(uchar reg)
/*Function: NRF24L01 SPI sequence
/****************************************************************************************************/
uchar NRF24L01_Read_Reg(uchar reg)
{
uchar reg_val;
CSN = 0; // CSN low, initialize SPI communication...
SPI_RW(reg); // Select register to read from..
reg_val = SPI_RW(0); // ..then read registervalue
CSN = 1; // CSN high, terminate SPI communication
return(reg_val); // return register value
}
/****************************************************************************************************/
/*Function: NRF24L01 read and write register function
/****************************************************************************************************/
uint NRF24L01_Write_Reg(uchar reg, uchar value)
{
uint status;
CSN = 0; // CSN low, init SPI transaction
status = SPI_RW(reg); // select register
SPI_RW(value); // ..and write value to it..
CSN = 1; // CSN high again
return(status); // return nRF24L01 status uchar
}
/****************************************************************************************************/
/*Function: uint SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars)
/*Function: Used to read data, reg: register address, pBuf: destination data address, uchars: number of bytes to read
/****************************************************************************************************/
uint NRF24L01_Read_Buf(uchar reg, uchar *pBuf, uchar uchars)
{
uint status,uchar_ctr;
CSN = 0; // Set CSN low, init SPI tranaction
status = SPI_RW(reg); // Select register to write to and read status uchar
for(uchar_ctr=0;uchar_ctr<uchars;uchar_ctr++)
pBuf[uchar_ctr] = SPI_RW(0); //
CSN = 1;
return(status); // return nRF24L01 status uchar
}
/*********************************************************************************************************
/*Function: uint SPI_Write_Buf(uchar reg, uchar *pBuf, uchar uchars)
/*Function: Used to write data: register address, pBuf: data to write, uchars: number of bytes to write
/*********************************************************************************************************/
uint NRF24L01_Write_Buf(uchar reg, uchar *pBuf, uchar uchars)
{
uint status,uchar_ctr;
CSN = 0; //SPI enable
status = SPI_RW(reg);
for(uchar_ctr=0; uchar_ctr<uchars; uchar_ctr++) //
SPI_RW(*pBuf++);
CSN = 1; //Close SPI
return(status); //
}
//Check if 24L01 exists
//Return value: 0, success; 1, failure
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
NRF24L01_Write_Buf(WRITE_NRF_REG+TX_ADDR,buf,5);//Write 5 bytes address
NRF24L01_Read_Buf(TX_ADDR,buf,5); //Read the written address
for(i=0;i<5;i++) if(buf[i]!=0XA5)break;
if(i!=5)return 1;//24L01 detection error
return 0; //24L01 detected
}
u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
NRF24L01_CE=0;
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//Write data to TX BUF, 32 bytes
NRF24L01_CE=1;//Start transmission
while(NRF24L01_IRQ!=0);//Wait for transmission to complete
sta=NRF24L01_Read_Reg(STATUS); //Read status register value
NRF24L01_Write_Reg(WRITE_NRF_REG+STATUS,sta); //Clear TX_DS or MAX_RT interrupt flag
if(sta&MAX_TX)//Maximum retransmission reached
{
NRF24L01_Write_Reg(FLUSH_TX,0xff);//Clear TX FIFO register
return MAX_TX;
}
if(sta&TX_OK)//Transmission completed
{
return TX_OK;
}
return 0xff;//Other reasons for transmission failure
}
//Start NRF24L01 to send data once
//txbuf: data source address to be sent
//Return value: 0, reception complete; others, error code
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
sta=NRF24L01_Read_Reg(STATUS); //Read status register value
NRF24L01_Write_Reg(WRITE_NRF_REG+STATUS,sta); //Clear TX_DS or MAX_RT interrupt flag
if(sta&RX_OK)//Data received
{
NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//Read data
NRF24L01_Write_Reg(FLUSH_RX,0xff);//Clear RX FIFO register
return 0;
}
return 1;//No data received
}
//This function initializes NRF24L01 to RX mode
//Set RX address, write RX data width, select RF channel, baud rate and LNA HCURR
//When CE becomes high, it enters RX mode and can receive data
void RX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(WRITE_NRF_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//Write RX node address
NRF24L01_Write_Reg(WRITE_NRF_REG+EN_AA,0x01); //Enable channel 0 auto-acknowledge
NRF24L01_Write_Reg(WRITE_NRF_REG+EN_RXADDR,0x01);//Enable channel 0 receive address
NRF24L01_Write_Reg(WRITE_NRF_REG+RF_CH,40); //Set RF communication frequency
NRF24L01_Write_Reg(WRITE_NRF_REG+RX_PW_P0,RX_PLOAD_WIDTH);//Select channel 0 valid data width
NRF24L01_Write_Reg(WRITE_NRF_REG+RF_SETUP,0x0f);//Set TX transmission parameters, 0db gain, 2Mbps, low noise gain enabled
NRF24L01_Write_Reg(WRITE_NRF_REG+CONFIG, 0x0f);//Configure basic working mode parameters: PWR_UP, EN_CRC, 16BIT_CRC, receive mode
NRF24L01_CE = 1; //CE is high, enter receive mode
}
//This function initializes NRF24L01 to TX mode
//Set TX address, write TX data width, set RX auto-acknowledge address, fill TX send data, select RF channel, baud rate and LNA HCURR
//PWR_UP, CRC enable
//When CE becomes high, it enters RX mode and can receive data
//CE high for more than 10us will start transmission
void TX_Mode(void)
{
NRF24L01_CE=0;
NRF24L01_Write_Buf(WRITE_NRF_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//Write TX node address
NRF24L01_Write_Buf(WRITE_NRF_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //Set TX node address, mainly to enable ACK
NRF24L01_Write_Reg(WRITE_NRF_REG+EN_AA,0x01); //Enable channel 0 auto-acknowledge
NRF24L01_Write_Reg(WRITE_NRF_REG+EN_RXADDR,0x01); //Enable channel 0 receive address
NRF24L01_Write_Reg(WRITE_NRF_REG+SETUP_RETR,0x1a);//Set auto retransmit interval time: 500us + 86us; maximum auto retransmit times: 10
NRF24L01_Write_Reg(WRITE_NRF_REG+RF_CH,40); //Set RF channel to 40
NRF24L01_Write_Reg(WRITE_NRF_REG+RF_SETUP,0x0f); //Set TX transmission parameters, 0db gain, 2Mbps, low noise gain enabled
NRF24L01_Write_Reg(WRITE_NRF_REG+CONFIG,0x0e); //Configure basic working mode parameters: PWR_UP, EN_CRC, 16BIT_CRC, receive mode, enable all interrupts
NRF24L01_CE=1;//CE is high, start transmission after 10us
inerDelay_us(20);
}
Next is the main program debugging section:
#include "15f204ea.h"
#include "24l01.h"
#include "uart.h"
#include "intrins.h"
void delay500ms(void) //Error -0.000000000063us
{
unsigned char a,b,c;
for(c=212;c>0;c--)
for(b=160;b>0;b--)
for(a=80;a>0;a--);
_nop_(); //if Keil, require use intrins.h
}
void delay100us(void) //Error -0.083188657407us
{
unsigned char a,b;
for(b=58;b>0;b--)
for(a=8;a>0;a--);
}
void main(){
u8 tmp_buf[33];
u8 key,mode;
u16 t=0;
delay500ms();
uartInit();
uartSendString("Test");
uartSendNum(1234);
NRF24L01_Init();
while(NRF24L01_Check())//Cannot detect 24L01
{
uartSendString("Initialization failed");
delay500ms();
uartSendString("Please check");
delay500ms();
}
uartSendString("Starting");
if(0){
RX_Mode();
uartSendString("Receive mode");
while(1){
if(NRF24L01_RxPacket(tmp_buf)==0){
tmp_buf[32]=0;//Add string terminator
uartSendString(tmp_buf);
}
else delay100us();
}
}
else{
TX_Mode();
uartSendString("Transmit mode");
mode=' ';//Start from space character
while(1){
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
uartSendString(tmp_buf);
key=mode;
for(t=0;t<32;t++)
{
key++;
if(key>('~'))key=' ';
tmp_buf[t]=key;
}
mode++;
if(mode>'~')mode=' ';
tmp_buf[32]=0;//Add terminator
}
else{
uartSendString("Send failed");
};
delay500ms();
}
}
}
Set the crystal to 11.0594MHZ, then write the program.
First, connect the serial port, then power on. After half a second, the program will send serial data “Test” and the number “1234” to the computer. Note that the serial working frequency is 38400.
If you can receive serial data correctly, then everything is fine; otherwise, there’s an issue with the frequency settings or the data cable.
The program will then perform a check. Upon success, it returns “Starting”; upon failure, it displays “Initialization failed” and “Please check”. Failure is either because the nrf24l01 is damaged or the pins are connected incorrectly.
Depending on the value in the if statement’s parentheses (0 or 1), the program will enter either receive mode or transmit mode.
In receive mode, the program will send “Receive mode” through the serial port. If it receives data, it will return the data content.
In transmit mode, the program will send a code (a sequence of changing characters) to the specified address. If transmission is successful, it returns the length of the data sent and the data itself. When sending fails, it will display “Send failed”.
If sending fails, it might be because the receiver doesn’t exist. In this case, NRF24L01_TxPacket returns 10 (maximum retransmission attempts) instead of 32 (data bit count), so an error is reported.
It could also be due to other reasons, in which case NRF24L01 returns 0xff, indicating other causes of failure. In this situation, the problem is unclear and needs careful investigation.
By writing the transmit program to one chip and the receive program to another, you can see the effect ^_^
Thus, NRF24L01 and STC15F204EA (STC15L204EA) communication is achieved.