無線通信NRF24L01---使用筆記

關於NRF24L01的資料,網上已很詳盡了。這篇文章不細紅了描述各知識點。

這篇文章,旨在把各處主要知識點膠接起來,梳理成一套完整的步驟,使器件快速上手匯入工作使用。

 

將按操作順序,拆分成6個步驟,只註解重點和暗坑。以方便自己進行知識管理,亦方便同行兄弟查閱。

代碼方面,儘量做到一行代碼一行解釋。(如有訂正和疑問,可留言交流,將迅速回復。)

  • 一、思維導圖
  • 二、引腳連接,解釋
  • 三、SPI初始化,函數
  • 四、NRF參數寫入
  • 五、中斷處理函數
  • 六、發送數據
  • 七、接收數據

一、思維導圖

工作模式、功耗、命令字等等這些,不用文字囉嗦,直接思維導圖更有效;

花兩天時間翻閱各種零星資料,對主要點進行了組織,包括了工作模式、命令字等圖例,認真過一遍,心裏馬上就能有個框框。

點擊一下圖片能變成清晰的大圖。當然,右擊保存就更方便查看了。

 


第二部分: 引腳連接,解釋

明確一個,NRF24L01和SI24R1的引腳、程序是通用的,無需任何修改,兩者間通信也互通,已測試確認!

共8個引腳:

  1. GND:接地
  2. VCC:3.3V。1.9~3.6V都成,不要接5V,馬上燒!
  3. CE:     模式控制。其高低電平,配合CONFIG寄存器PRIM_RX和PWR_UP兩個位,可切換工作狀態: 發送、接收 、待機 。
  4. IRQ:    中斷信號引腳。發生以下三種中斷時,引腳電平被NRF拉低:發送成功、接收到數據、已達最大重發次數
  5. CSN:   SPI的CS片選引腳
  6. SCK:   SPI的時鐘線引腳
  7. MOSI: SPI的主出從入數據引腳
  8. MISO: SPI的主入從出數據引腳

原代碼程序中,有個連接測試函數,可以作模塊的連接,沒有貼上來,留郵箱發最新版本代碼。


第三部分:SPI初始化、函數封裝

NRF的配置,就是把各種參數(數值),如頻道,速率,目標地址等,用SPI方式寫到指定地址(芯片的寄存器).

 這個寫入配置的動作,拆分開來看,理解爲兩部分,是在操作兩種通信,別混亂。

首先主機按NRF datasheet的要求,設置和通過SPI通信,向NRF芯片特定地址(寄存器)寫入參數值;

而這些寫入的數值,就是用於控制NRF與別一個NRF的通信參數。

這兩個通信,理解一下~

1:主機和NRF間的通信:       

  • 使用SPI,主機完成對NRF操控,所有操控其實就是4個操作:寫參數、讀參數、寫要發送的數據,讀出收到的數據
  • 上面的操控,分拆到SPI的操作上,就是常用的6個函數:SPI初始化、字節收發、寫1字節,讀1字節,寫N字節,讀N字節;
  • 寫入參數:是NRF與NRF間通信的參數值, 如頻道 ,速率,CRC校檢,自動回答,自動重發,目標地址....
  • 讀取參數,主要是STATUS狀態寄存器的值,用於判斷中斷源;
  • 寫入發送數據,把待發送的數據寫到TX_FIFO. NRF按包發送數據,包中有效數據最大32字節;要手動做分包處理;
  • 讀出收到數據,檢測到RX_DR中斷髮生後(IRQ引腳被拉低),用SPI把RX_FIFO緩衝區的值,讀取存放到指自已的緩衝區;
  • NRF的SPI通信速度可達10MHz,但爲保證數據傳輸的完整不掉包,儘量不要超過8MHz;
  • NRF要求SPI通信時,在上升沿採樣數據,要注意時序。

2:NRF和NRF間的收發通信:

  • NRF按主機剛纔寫到芯片的參數值開始工作(手動),通過電波傳輸到另一個芯片中或接收別一芯片的數據(自動)。
  • 這部份通信我們要做的,僅是控制CE引腳的高低電平,配合CONFIG寄存器,使NRF在接收、發送、待機三種狀態切換。
  • NRF間的無線通信,基本沒我們什麼事。

 

開始碼代碼, 工作順序:  GPIO初始化  >  SPI初始化  >  SPI收發函數  >  寫入NRF通信用的參數  >  中斷函數  >  收發函數

GPIO初始化代碼:

/*** SPI通信引腳, CS, SCK, MOSI, MISO  ***/
GPIOSet (NRF24L01_SPI_CSN_GPIO,  NRF24L01_SPI_CSN_PIN,   G_MODE_OUT , G_OTYPE_PP,  G_OSPEED_25M,  G_PUPD_UP ,     0);            // cs
GPIOSet (NRF24L01_SPI_CLK_GPIO , NRF24L01_SPI_CLK_PIN ,  G_MODE_AF ,  G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN ,   G_AF_SPI2 );  // sck
GPIOSet (NRF24L01_SPI_MOSI_GPIO ,NRF24L01_SPI_MOSI_PIN , G_MODE_AF ,  G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN ,   G_AF_SPI2 );  // mosi
GPIOSet (NRF24L01_SPI_MISO_GPIO ,NRF24L01_SPI_MISO_PIN , G_MODE_AF ,  G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN ,   G_AF_SPI2 );  // miso	
    
/*** NRF控制引腳, CE, IRQ ***/
GPIOSet (NRF24L01_CE_GPIO ,      NRF24L01_CE_PIN ,       G_MODE_OUT , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN ,   0);           // CE
GPIOSet (NRF24L01_IRQ_GPIO ,     NRF24L01_IRQ_PIN ,      G_MODE_IN  , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_UP   ,   0);           // IRQ   NRF24L01芯片的IRQ引腳下拉能力很弱,注意外部上拉電阻大小,及MCU的上下拉
    

GPIOSet是個自己封裝的初始化函數,不用管,重點看其中的參數就好,注意各上下拉。

 

SPI 初始化代碼:

/*** SPI通信部分 ***/	
CSN_HIGH;                      //失能NRF
NRF24L01_SPI_EN_CLOCK ;        // 時鐘
NRF24L01_SPIX->CR1 = 0;        // 清0
NRF24L01_SPIX->CR1 |= 0<<0;    // 採樣沿數, NRF要求上升沿採樣   CPHA:時鐘相位,0x1=在第2個時鐘邊沿進行數據採樣, 
NRF24L01_SPIX->CR1 |= 0<<1;    // 時鐘線閒時極性,  CPOL:時鐘極性,0x1=空閒狀態時,SCK保持高電平
NRF24L01_SPIX->CR1 |= 1<<2;    // 主從模式,       0=從,1=主
NRF24L01_SPIX->CR1 |= 2<<3;    // 波特率控制[5:3], 0=fPCLK/2,  1=/4倍  2=/8  3/16
NRF24L01_SPIX->CR1 |= 0<<7;    // LSB先行,        0=MSB,  1=LSB
NRF24L01_SPIX->CR1 |= 1<<8;    // 內部從器件選擇,根據9位設置(失能內部NSS)
NRF24L01_SPIX->CR1 |= 1<<9;    // 軟件從器件管理 :  0=禁止軟件管理從設備, 1=使能軟件從器件管理(軟件NSS)
NRF24L01_SPIX->CR1 |= 0<<11;   // 數據幀格式,       0=8位,  1=16位	

NRF24L01_SPIX->CR1 |= 1<<6;    // 使能

SPI 初始化重點:

  • 一行一註釋,注意細看
  • NRF要求在上升沿時採集數值。這就要閒時電平和採樣沿要配合好。
  • NRF的SPI可達10MHz, 但實際使用時,不要超過8MHz。這個在SPI的波特率中可控制,因爲SPI通信速率受限低速一方。

插個話題,爲什麼使用寄存器操作編程?因爲:使用寄存器=簡單+清晰,你看,不是嗎?

 

SPI收發函數

/*****************************************************************************
*函  數: u8 SPI_RW(u8 Data)
*功  能: SPI寫入一個字節,並返回一個字節
*參  數: 要寫入的一字節
*返回值: 返回一字節數據
*****************************************************************************/
u8 SPI_SendByte(u8 Data)
{
    u8 retry =0;
    while((NRF24L01_SPIX ->SR & 2) == 0){   // 理解方式,應該把前式的結果理解爲一個寄存器位值,如果這個位值是等號後面的值,就等待
        retry++;
        if(retry>200) return 0;
    }
    NRF24L01_SPIX ->DR = Data;
	
    retry=0;
	while((NRF24L01_SPIX->SR & 1) == 0 ){
        retry++;
        if(retry>200) return 0;
    }
	return NRF24L01_SPIX->DR ;
}

/*****************************************************************************
*函  數:u8 Nrf24l01_WriteReg(u8 reg,u8 value)
*功  能:向指定寄存器地址,寫一個字節數據
*參  數:reg: 寄存器地址
*       val:  要寫入的值
*返回值:status
*****************************************************************************/
u8 Nrf24l01_WriteReg(u8 reg,u8 value)
{
	u8 status;    
	CSN_LOW;
	status = SPI_SendByte(reg) ;
	SPI_SendByte(value);
	CSN_HIGH;    
	return status;
}

/*****************************************************************************
*函  數:u8 NRF24l01_read_reg(u8 reg)
*功  能:向指定寄存器地址,讀出一字節數據
*參  數:reg: 寄存器地址
*返回值:reg_val(第二個讀取到的字節)
*****************************************************************************/
u8 Nrf24l01_ReadReg(u8 reg)
{
	u8 reg_val;    
	CSN_LOW;
	SPI_SendByte(reg);
	reg_val = SPI_SendByte(0xFF);
	CSN_HIGH;    
	return reg_val;
}

/*****************************************************************************
*函  數:u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
*功  能:寫一組數據到寄存器
*參  數:reg: 寄存器地址
*       pBuf: 要寫入數據的地址
*        len:  要寫入的數據長度
*返回值:status
*備  注:NRF2401代碼移植只需把SPI驅動修改成自己的即可
*****************************************************************************/
u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
{
	u8 status;    
	CSN_LOW;
	status = SPI_SendByte(reg);
	for(u8 i=0; i<len; i++)	{
		SPI_SendByte(pBuf[i]);		
	}
	CSN_HIGH;    
	return status;
}

/*****************************************************************************
*函  數: u8 vNrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
*功  能: 向指定寄存器地址,讀出指定長度的數據
*參  數: reg :    寄存器地址
*        pBuf :   數據存放緩衝區
*        len  :   讀取的字節數量
*返回值: status : 設備狀態字
*****************************************************************************/
u8 Nrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len)
{
	u8 status;	
	CSN_LOW;
	status = SPI_SendByte(reg);
	for(u8 i = 0; i<len ;i++){
		pBuf[i] = SPI_SendByte(0xFF);		
	}
	CSN_HIGH;    
	return status;
}

第四部分:NRF24L01參數寫入

使用上面初始化的SPI,和剛封裝好的幾個函數,就可以把需要的參數,寫到NRF特定的地址(寄存器), 完成對其配置。

NRF24L01 參數配置代碼:

/*** NRF24L01通信配置 30,2M,***/    
CE_LOW;                                                          // 熱待機模式, 只有在ce置低時,才能配置寄存器
//delayUs(2000);                                                 // PowerDown 切換爲 PowerUp需要1.5ms 
Nrf24l01_WriteReg(W_REGISTER + RF_CH,        30);                // 射頻通道,即頻率(0-125)
Nrf24l01_WriteReg(W_REGISTER + RF_SETUP,   0x0F);                // 設置TX發射參數,0db增益,2Mbps,低噪聲增益關閉 (注意:低噪聲增益關閉/開啓直接影響通信,要開啓都開啓,要關閉都關閉0x0f)0x07
Nrf24l01_WriteReg(W_REGISTER + SETUP_AW,   0x03);                // 地址長度,默認值時0x03,即5字節
Nrf24l01_WriteBuf(W_REGISTER + TX_ADDR,    (u8*)TX_ADDRESS, 5);  // 寫TX節點地址, 地址寬度:5字節,40位 
Nrf24l01_WriteBuf(W_REGISTER + RX_ADDR_P0, (u8*)TX_ADDRESS, 5);  // 設置TX節點地址,主要爲了使能ACK,, 地址寬度:5字節,40位
Nrf24l01_WriteReg(W_REGISTER + SETUP_RETR, 0x0A);                // 設置自動重發間隔時間:500us + 86us;最大自動重發次數:10次 0x1A	
Nrf24l01_WriteReg(W_REGISTER + EN_RXADDR,  0x01);                // 使能通道0的接收地址  
Nrf24l01_WriteReg(W_REGISTER + EN_AA,      0x01);                // 使能通道0自動應答
Nrf24l01_WriteReg(W_REGISTER + RX_PW_P0,     32);                // 選擇通道0的有效數據寬度  
Nrf24l01_WriteReg(FLUSH_TX,                0xff);                // 清除TX_FIFO
Nrf24l01_WriteReg(W_REGISTER+STATUS,       0X7E);                // 清除所有中斷,防止一進去接收模式就觸發中斷    
Nrf24l01_WriteReg(W_REGISTER+CONFIG,       0x0F);                // 配置爲接收模式    
CE_HIGH;   	                                                 // CE置高,進入狀態

重點:

  • CE引腳置低,方可配置NRF寄存器!  各教程都這教的,但測試過不置低也可正常配置。
  • RF_CH,:頻道,2.4G爲基址,1M爲間隔值,如上面的30,代表使用2.430G的頻率進行芯片間通信。可設值0~125。
  • RF_SETUP: 發射功率可配置,說白了就是的耗電程度。接收狀態的功率是不能設置的,所以接收纔是耗電的大頭。數據傳輸率,常說的空中速率,一般設置2M
  • 地址長度,5字節,這個很獨特
  • TX_ADDR:數據要發送到的目標地址
  • RX_ADDR_P0: 接收通道0的接收地址,接收這個地址設備發來的數據,這裏設爲和TX_ADDR一致,是爲了自動應答。
  • NRF共有6個接收通道,p0~p5, 可同時監聽6個不同地址的信號,p0通道也用於作自動應答作用。
  • TX_FIFO, 發送數據緩衝區,96字節,32字節爲一組,共3組,可理解爲緩衝區可存放3組發送數據。
  • RX_FIFO, 同上,兩個FIFO相互獨立。 雖然是FIFO,雖然有3組,但最好還是一包一包收發,狀態切換時間難把握。
  • 倒數第二行,配置爲接收模式,注意,這個時候還沒開始工作的,還處於PownDown狀態,狀態和模式是兩回事。
  • 最後一行,CE置高10us後,才進入工作狀態,因之前配置的是接收模式,所以將進入接收狀態

第五部分:中斷處理函數

爲什麼要先說中斷?感覺先了解了中斷,那麼發送、接收就更好理解。

其實不應該叫中斷的,但這樣好理解,還是遵從約定俗成吧。

中斷時, IRQ電平被拉低,是由NRF控制產生的,三種情況可觸發:發送成功、達到重發最大次數、接收到數據

發送成功:

  • 1:PTX發送數據後,馬上開始計時,130us後切換到接收模式,此時計時還在繼續
  • 2:PRX在收到數據後,經CRC校檢,數據完整後發回ACK信號
  • 3:PTX在規定時間內收到ACK信號,則置位TX_DS標誌,IRQ引腳被拉低

達到重發最大次數:

  • 1:PTX發送數據後,馬上開始計時,130us後切換到接收模式,此時計時還在繼續
  • 2:PTX在規定時間內,沒收到ACK信號,原因很多:如PRX沒收到數據,CRC校驗錯誤....
  • 3:PTX再次發送一次數據,重複步驟1
  • 4:SETUP_RETR寄存器可設置重發的次數,達到最大次數後,MAX_RT位被置位,IRQ引腳被拉低。

接收到數據:

  • PRX收到數據,經CRC校檢,數據完整,有效數據存放到RX_FIFO,RX_DR位置高,IRQ引腳被拉低。

 

說說清理中斷,要清理的中斷有兩處:

  • NRF的中斷位,上面的三種標誌中斷位,都在寄存器STATUS中,各位置1可清0,IRQ引腳即回到高電平。
  • 單片機mcu的中斷位,清理外部中斷線標誌位,否則會在中斷函數裏死循環。

中斷處理函數代碼:

void NRF24L01_IRQ_IRQHANDLER(void)
{                    
    u8  status=0 ;

    CE_LOW;                                               // 拉低CE,以便讀取NRF中STATUS中的數據	
    status = Nrf24l01_ReadReg(R_REGISTER + STATUS);       // 讀取STATUS中的數據,以便判斷是由什麼中斷源觸發的IRQ中斷

    /*** 發送完成中斷 ***/
    if(status & STATUS_TX){	                                     
        Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_TX);  // 清NRF中斷:發送完成
        Nrf24l01_WriteReg(FLUSH_TX, 0xff);                // 清發送緩衝區:TX_FIFO
        printf("\r\n發送成功!!!!\r\n");
        vNrf24l01_RxMode ();                              // 切換爲接收狀態
    }

    /*** 接收完成中斷 ***/
    if(status & STATUS_RX){ 
        memset (NRF_RX_DATA , 0, 32);           
        Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 讀數據
        Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX);              // 清NRF中斷:收到數據
        Nrf24l01_WriteReg(FLUSH_RX,0xff);                             // 清除RX FIFO(注意:這句話很必要)   	

        printf("\r\n接收到數據: %s\r\n", NRF_RX_DATA);	        
        vNrf24l01_RxMode ();                             // 切換爲接收狀態
    }

    /*** 最大重發次數中斷 ***/
    if(status & STATUS_MAX){	
        Nrf24l01_WriteReg(W_REGISTER+STATUS, 0x70);       // 清NRF中斷:三個
        Nrf24l01_WriteReg(FLUSH_TX, 0xff);                // 清除TX_FIFO      

        printf("\r\n發送失敗,達到最大重發次數!!!\r\n"); 
        vNrf24l01_RxMode();                               // 切換爲接收狀態
    }

    EXTI->PR |= NRF24L01_IRQ_PIN ;                        // 清理外部中斷線標誌位
}    

第六部分:發送數據

NRF的數據收發,是一包一包進行的,一包(幀)數據:包括了前導碼、目標地址、包控制域、有效數據、CRC, 但我們只管有效數據,其它的不用我們負責,NRF發送時自動打包,接收到數據時自動拆包。

每一包的有效數據最大爲32個字節。當然,也可以只發一個字節的數據。

要發送的數據大於32字節,就要分包進行,自行手動分包處理。

因爲在配置部分時,已配置好了頻道,速率,重發次數等各種參數,在需要發送數據時,只要往芯片寫入要發送的數據和地址,然後切換爲發送狀態,芯片就會自動發送。

發送成功(收到ack),會產生TX_DS中斷。

發送失敗了(達到最大重發次數), 也會產生MAR_RT中斷。

在中斷函數裏,根據情況作處理就好。

發送數據代碼:

void vNrf24l01_TxPacket(u8 *txbuf)
{
    CE_LOW; 
    Nrf24l01_WriteBuf(W_TX_PAYLOAD,           txbuf,          32);   // 寫數據到TX_BUFF    
    Nrf24l01_WriteBuf(W_REGISTER+TX_ADDR,    (u8*)TX_ADDRESS,  5);   // 寫入要發送的目標地址 
    Nrf24l01_WriteBuf(W_REGISTER+RX_ADDR_P0, (u8*)TX_ADDRESS,  5);   // 通道0的地址設爲和目標地址一致,以接收自動回覆auto_ack信號 
    Nrf24l01_WriteReg(W_REGISTER+CONFIG,      0x0E);	             // 設置爲發送模式,開啓所有中斷	    
    CE_HIGH;	
}

發送就這幾句!

重點:RX_ADDR_P0的地址和TX_ARRD一樣,目的是自動應答。有個前提,在配置中已使能自動應答。

把操作封裝成一個函數,要發什麼,就往函數裏掉數據就好,每次不要大於32字節。


第七部分:接收數據

當系統或程序運行時,大部分時間是運行在接收狀態下的。如:

  • 第四個部分配置步驟完成後,程序已處在接收狀態。
  • 第五個部分,在中斷處理函數中,三種中斷處理後,也切換爲接收狀態,當然,也可以切換爲更省電的待機狀態。

NRF有6個接收通道,指在可同時監聽接收同一個頻道,同一速率的6個不同設備的數據。

常用的只是通道P0, 如果只使能了通道P0,那就只能接收到P0中地址設備發來的數據。

可以使能全部6個通道,設置6個不同設備地址,就可以監聽接收6個設備發來的數據(同一時間,只能接收其中1個設備的數據);

注意,接收數據,指在接收中斷髮生後,我們從RX_FIFO中把數據讀存到主機。而中斷髮生前的監聽接收,NRF自動完成。

接收數據的代碼:

接收本來就是最簡單的,沒啥特別代碼,下面的代碼,只是在中斷處理函數裏,再貼出來而已。

Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 讀數據到數組
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX);              // 清NRF中斷:收到數據
Nrf24l01_WriteReg(FLUSH_RX,0xff);                             // 清除RX FIFO(注意:這句話很必要)  

把接收到的數據,先存放NRF_RX_DATA數組,再進行處理,不要在中斷函數中處理。

讀完後,記得要清理RX_FIFO,不然它一直佔用NRF的緩衝區。RX_FIFO共96字節,分成32字節3組,NRF每次接收到數據就存放到最後面的一組中,當存滿了3組,後面再接收到的數據,就會被NRF掉棄。


最後,原來想把原工程代碼上傳上來的,但下載時要消耗積分...

哪位朋友如果需要原代碼的,留個郵箱地址 ,給你發最新測試修改好的工程文件

 

寫了三四個小時,累~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章