關於NRF24L01的資料,網上已很詳盡了。這篇文章不細紅了描述各知識點。
這篇文章,旨在把各處主要知識點膠接起來,梳理成一套完整的步驟,使器件快速上手匯入工作使用。
將按操作順序,拆分成6個步驟,只註解重點和暗坑。以方便自己進行知識管理,亦方便同行兄弟查閱。
代碼方面,儘量做到一行代碼一行解釋。(如有訂正和疑問,可留言交流,將迅速回復。)
- 一、思維導圖
- 二、引腳連接,解釋
- 三、SPI初始化,函數
- 四、NRF參數寫入
- 五、中斷處理函數
- 六、發送數據
- 七、接收數據
一、思維導圖
工作模式、功耗、命令字等等這些,不用文字囉嗦,直接思維導圖更有效;
花兩天時間翻閱各種零星資料,對主要點進行了組織,包括了工作模式、命令字等圖例,認真過一遍,心裏馬上就能有個框框。
點擊一下圖片能變成清晰的大圖。當然,右擊保存就更方便查看了。
第二部分: 引腳連接,解釋
明確一個,NRF24L01和SI24R1的引腳、程序是通用的,無需任何修改,兩者間通信也互通,已測試確認!
共8個引腳:
- GND:接地
- VCC:3.3V。1.9~3.6V都成,不要接5V,馬上燒!
- CE: 模式控制。其高低電平,配合CONFIG寄存器PRIM_RX和PWR_UP兩個位,可切換工作狀態: 發送、接收 、待機 。
- IRQ: 中斷信號引腳。發生以下三種中斷時,引腳電平被NRF拉低:發送成功、接收到數據、已達最大重發次數。
- CSN: SPI的CS片選引腳
- SCK: SPI的時鐘線引腳
- MOSI: SPI的主出從入數據引腳
- 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掉棄。
最後,原來想把原工程代碼上傳上來的,但下載時要消耗積分...
哪位朋友如果需要原代碼的,留個郵箱地址 ,給你發最新測試修改好的工程文件。
寫了三四個小時,累~~~