經過兩日的調試nRF24L01終于調試成功。耐心,細心。有想法。資料。數據手冊。總就會成功。
下面是調試的基本方法:
24L01是收發雙方都需要編程的器件,這就對調試方法產生了一定的要求,如果兩塊一起調,那么通訊不成功,根本不知道是發的問題還是收的問題,不好意思的說,我當時也是沒理清調試思路才浪費了一天時間看著模塊干瞪眼。并且燒壞了兩個模塊。(還好這個模塊可以》。。。。。)。
所以正確的調試方法應該是先調試發送方,能保證發送正確,再去調接收,這樣就可以有針對性的解決問題。
至于怎么去調發送方,先說下發送方的工作流程:
??配置寄存器使芯片工作于發送模式后拉高CE端至少10us
??讀狀態寄存器STATUS
??判斷是否是發送完成標志位置位
??清標志
??清數據緩沖
網上的程序我也看過,大多都是成品,發送方發送-等應答-(自動重發)-觸發中斷。可是這樣的流程就已經把接收方給牽涉進來了,就是說一定要接收方正確收到數據并且回送應答信號之后發送方才能觸發中斷,結束一次完整的發送。可是這跟我們的初衷不相符,我們想單獨調試發送,完全拋開接收,這樣就要去配置一些參數來取消自動應答,取消自動重發,讓發送方達到發出數據就算成功的目的。
SPI_RW_Reg(WRITE_REG + EN_AA, 0x00); // 失能通道0自動應答
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x00); // 失能接收通道0
SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x00); // 失能自動重發
1有了以上這三個配置,發送方的流程就變成了發送-觸發中斷。這樣就拋開了接收方,可以專心去調試發送,可是怎么樣才知道發送是否成功呢,要用到另外兩個寄存器,STATUS和FIFO_STATUS。
這樣就很清晰了,我們可以通過讀取STATUS的值來判斷是哪個事件觸發了中斷,寄存器4、5、6位分別對應自動重發完成中斷,數據發送完成中斷,數據接收完成中斷。也就是說,在之前的配置下,如果數據成功發送,那么STATUS的值應該為0x2e。這樣就可以作為一個檢測標準,另外一個標準可以看FIFO_STATUS寄存器,第5位的描述:發送緩沖器滿標志,1為滿,0為有可用空間;第4位的描述:發送緩沖器空標志,1為空,0為有數據;同樣可以看到接收緩沖器的對應標志。這樣在數據發送成功后,發送寄存器當然應該是空的,接收緩沖因為在之前已經失能,所以也應該是空,也就是說成功發送之后的FIFO_STATUS寄存器值應該是0x11。
有了這兩個檢測標準,我們即使不用接收方也可以確定發送方是否成功發送。當發送方調試成功之后,在程序里讓它一直發送,然后我們就可以去調試接收方,思路是一樣的,同樣說下接收方工作流程先。
??配置寄存器使芯片工作于接收模式后拉高CE端至少130us
??讀狀態寄存器STATUS
??判斷是否是接收完成標志位置位
??清標志
??讀取數據緩沖區的數據
??清數據緩沖
然后在初始化配置寄存器的時候要和發送方保持一致,比較重要的是要失能自動應答,使能通道0接收:
SPI_RW_Reg(WRITE_REG + EN_AA, 0x00); // 失能通道0自動應答
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 接收要使能接收通道0
這樣就可以了,接收方就可以進入接收模式去接收數據了,這次的調試就會靈活一些,因為是接收數據,可以在接收方添加一個顯示設備把數據直觀的顯示出來,去對照看是否正確,當然還可以使用和發送方一樣的方法:觀察STATUS和FIFO_STATUS的值,對照寄存器描述,接收正確時STATUS的值應該是0x40,對于FIFO_STATUS的情況就多了些,因為數據寬度的不同也會造成寄存器的值不一樣,24L01最大支持32字節寬度,就是說一次通訊最多可以傳輸32個字節的數據,在這種情況下,接收成功讀數據之前寄存器值應該為0x12,讀數據之后就會變成0x11;如果數據寬度定義的小于32字節,那么接收成功讀數據之前寄存器值應該為0x10,讀數據之后就會變成0x11。這個看起來挺復雜,其實很清晰,大家可以試著分析下,對照數據手冊分析每個位的狀態就可以得到結果。
好了,到這里對nRF24L01的調試基本上就算通了,但是要明白這些只是調試方法,最終的產品如果不加上應答和重發的話那么數據的穩定性是很難保證的,所以在基本的通訊建立之后就要把發送的配置改為:
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); //使能接收通道0自動應答
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 使能接收通道0
SPI_RW_Reg(WRITE_REG + SETUP_RETR, 0x1a); // 自動重發10次,間隔500us
接收方的配置也要更改:
SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // 失能通道0自動應答
SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 接收要使能接收通道0
這樣發送和接收就進入了一個標準狀態,發送-等應答-(自動重發)-觸發中斷;接收-應答-觸發中斷,一切按部就班,程序里加上自己的應用部分就能實現很多功能了。
其實自己摸索單片機已經有一年了,但沒在網上寫過筆記,終于覺得既然整天都在各種網站找資料,也看人家的博客,自己也寫寫筆記,一方面給自己看,一方面作為分享和供批評。
STM32這塊片子接觸也有半年了,還沒真正做自己的作品,這陣子和一個朋友想做一下一個基于STM32的小作品,要用到無線控制,決定用NRF24L01+這片芯片,主要也是這片常用,簡單,也便宜。
一個小插曲是在淘寶上淘模塊的時候發現,相當多的店子都寫明了是Si24R91(其實是應該是Si24R1,那個好像是不存在的)這一代替芯片,其實看了手冊,單單從功能上,Si24R1是提升了一些,屬于向下兼容NRF24L01,但是不解的是淘寶發回來的芯片上印的卻是NRF24L01,通過比對調試寄存器的內容確實是NRF24L01(這才讓我不解,商家搞什么名堂)。
STM32F103VET6有倆SPI接口,選擇用SPI1來驅動NRF24L01.
1.配置STM32的SPI1接口:
1)開啟GPIOA,AFIO時鐘
2)配置GPIOA對應Pin為SPI1的MISO,MOSI,CLK,NSS(SPI1的NSS已經為他用,故用PA1軟件控制NRF24L01的CSN),CE(NRF24L01使能端口),IRQ--NRF24L01的中斷信號
3)配置SPI模式
2.仔細讀手冊(重點是時序和寄存器)
3.編寫用SPI1控制NRF的函數數個
4.用以上的函數編寫功能函數
OK!但 這是說的。真正做起來的時候飽受打擊。串口轉USB線壞了,無奈只能LCD屏來顯示調試信息,增大了代碼負擔。后來J-Link有壞了,差點放棄,好在重寫了固件還能復活。
1)2)步的代碼:
void SPI1_Config(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1|RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE); //使能SPI1時鐘
// PA5--CLK PA7--MOSI 復用推挽輸入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//PA6--MISO 輸入浮空
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
SPI1_CE_LOW();
SPI1_NRF_CSN_HIGH();//拉高CSN失能片選
SPI_InitTypeDefSPI_InitStructure; //聲明用來初始化的結構體
SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;//全雙工
SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //主模式
SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //一次傳輸8位
SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //空閑電平低電平
SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //第一個上升沿采樣
SPI_InitStructure.SPI_NSS =SPI_NSS_Soft; //NSS管理為軟件件模式
SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_8;//波特率預分頻8 9MHz
SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //數據傳輸低位在前
SPI_InitStructure.SPI_CRCPolynomial =7; //CRC校驗方式
SPI_Init(SPI1,&SPI_InitStructure); //初始化
//SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);
SPI_Cmd(SPI1, ENABLE); //使能SPI1
}//SPI1_Config()
*操作NRF24L01寄存器的時候要在待機模式,CE=0的情況下,沒發一個命令或數據,都會返回SPI一個字節,此字節是STAUS狀態寄存器的內容,所以寫命令的函數要發后,讀回一字節,不然,發完命令,讀數據的時候會出錯。下面就是SPI寫1 BYTE的函數。
u8 SPI_RW_Byte(SPI_TypeDef* SPIx,unsigned char Byte)
{
while( SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_TXE) == RESET); //查發送緩沖器是否為空,空即可以發送
SPI_I2S_SendData(SPIx,Byte); //庫函數:發送一個字節
//當SPI接收緩沖器為空時等待
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) ==RESET);
returnSPI_I2S_ReceiveData(SPIx);
}//SPI_RW_Byte()
*下面一個是發寫命令+指定字節數的數據的函數
u8 SPI_NRF_Write(SPI_TypeDef* SPIx,char CMD,unsigned char*WBuff,unsigned char ByteNUM)
{
unsigned chari,status;
SPI1_CE_LOW();
SPI1_NRF_CSN_LOW();//使能片選
status=SPI_RW_Byte( SPIx , CMD);
for(i=0;i
{
SPI_RW_Byte( SPIx,*WBuff++);
//printf("寫入第%d個數據\r\n",ByteNUM);
}
SPI1_NRF_CSN_HIGH();//
return status;
}//SPI_NRF_Write()
*發讀命令+指定字節數的數據的函數
u8 SPI_NRF_Read(SPI_TypeDef* SPIx,char CMD,unsigned char*RBuff,unsigned char ByteNUM)
{
unsigned char i,status ;
SPI1_CE_LOW();
SPI1_NRF_CSN_LOW();
status=SPI_RW_Byte( SPIx , CMD);
for(i=0;i< ByteNUM ;i++)
{
RBuff=SPI_RW_Byte(SPIx,NOP); // 取接收緩沖器,一個字節
//printf("讀出第%d個數據\r\n",ByteNUM);
LCD_Num_6x12_O(100,20*(i+1),RBuff,WHITE);
}
SPI1_NRF_CSN_HIGH();
return status;
}//SPI_NRF_Read()
下面是NRF24L01兩種模式TX RX的配置
void SPI_NRF_MOD_TX(void)
{
u8TX_Array[5];
u8_TX_RX_ADDR_[5]={0xB3,0xB4,0xB5,0xB6,0x05};
SPI1_CE_LOW();//CE=0待機模式
TX_Array[0]=0x03;//設置地址寬度11--5字節 10--4字節 01-3字節 00--不合法
SPI_NRF_Write(SPI1,W_REGISTER+SETUP_AW,TX_Array,1);
TX_Array[0]=0xf3;//建立自動重發間隔‘1111‘--等待4000+86us 15次
SPI_NRF_Write(SPI1,W_REGISTER+SETUP_RETR,TX_Array,1);
TX_Array[0]=0x02;//射頻通道 X0000010
SPI_NRF_Write(SPI1,W_REGISTER+RF_CH,TX_Array,1);
TX_Array[0]=0x0f;//射頻參數寄存器 00001111 2Mbps 發射功率 00-18dBm 01-12dBm 10-6dBm 11-0dBm 1--低噪聲放大器增益
SPI_NRF_Write(SPI1,W_REGISTER+RF_SETUP,TX_Array,1);
TX_Array[0]=0x3f;//xx11 11110-5接收通道允許
SPI_NRF_Write(SPI1,W_REGISTER+EN_RXADDR,TX_Array,1);
TX_Array[0]=0x3f;//xx11 11110-5通道允許自動應答
SPI_NRF_Write(SPI1,W_REGISTER+EN_AA,TX_Array,1);
SPI_NRF_Write(SPI1,W_REGISTER+TX_ADDR,_TX_RX_ADDR_,5);//寫入接收發送數據的地址,這個地址是接收端收件的憑證
SPI_NRF_Write(SPI1,W_REGISTER+RX_ADDR_P0,_TX_RX_ADDR_,5);//寫入接收發送數據的地址,這個地址是接收端收件的憑證
TX_Array[0]=0x0e;//中斷全開 發送模式 PRIM_RX=0PWR_UP=1
SPI_NRF_Write(SPI1,W_REGISTER+CONFIG,TX_Array,1);
TX_Array[0]=0xfe;//1111 xxxx STATUS寄存器寫‘1’清除所有標志
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,TX_Array,1);
SPI1_CE_HIGH();//CE=1使能發射模式
Delay_us(100);//CE拉高需要一定的延時才能進行發送 延時之后即可通過SPI接口發送TX_PLD
}
void SPI_NRF_MOD_RX(void)
{
u8TX_Array[5];
u8_TX_RX_ADDR_[5]={0xB3,0xB4,0xB5,0xB6,0x05};
SPI1_CE_LOW();//CE=0待機模式
TX_Array[0]=0x03;//允許接收通道00000011
SPI_NRF_Write(SPI1,W_REGISTER+EN_RXADDR,TX_Array,1);
TX_Array[0]=0x03;//設置地址寬度11--5字節 10--4字節 01-3字節 00--不合法
SPI_NRF_Write(SPI1,W_REGISTER+SETUP_AW,TX_Array,1);
TX_Array[0]=0x20;//射頻通道 X0000010
SPI_NRF_Write(SPI1,W_REGISTER+RF_CH,TX_Array,1);
TX_Array[0]=0x0f;//射頻參數寄存器 00001111 2Mbps 發射功率 00-18dBm 01-12dBm 10-6dBm 11-0dBm 1--低噪聲放大器增益
SPI_NRF_Write(SPI1,W_REGISTER+RF_SETUP,TX_Array,1);
TX_Array[0]=0x3f;//xx11 11110-5通道允許自動應答
SPI_NRF_Write(SPI1,W_REGISTER+EN_AA,TX_Array,1);
TX_Array[0]=0x04;//xx11 1111數據通道0 有效數據寬度 (1-32)字節
SPI_NRF_Write(SPI1,W_REGISTER+RX_PW_P0,TX_Array,1);
TX_Array[0]=0xfe;//1111 xxxxSTATUS寄存器 寫‘1’清除所有標志
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,TX_Array,1);
SPI_NRF_Write(SPI1,W_REGISTER+TX_ADDR,_TX_RX_ADDR_,5);//寫入接收發送數據的地址,這個地址是接收端收件的憑證
SPI_NRF_Write(SPI1,W_REGISTER+RX_ADDR_P0,_TX_RX_ADDR_,5);//寫入接收發送數據的地址,這個地址是接收端收件的憑證
TX_Array[0]=0x0f;//接收模式 PRIM_RX=1 PWR_UP=1允許接收終端
SPI_NRF_Write(SPI1,W_REGISTER+CONFIG,TX_Array,1);
SPI1_CE_HIGH();//CE=1使能發射模式
Delay_us(100);//CE拉高需要一定的延時才能進行發送 延時之后即可通過SPI接口發送TX_PLD
//輪詢中斷24L01中斷的到來 NRF_Read_IRQ()
}
下面是兩種模式的測試
ErrorStatus SPI_NRF_TX_DATAS(u8* TBuff,u8 ByteNUM)
{
u8 Status[1];
do{
SPI1_CE_LOW();//拉低待機
SPI_NRF_Write(SPI1,W_TX_PAYLOAD,TBuff,ByteNUM);//發送TBuff數組
SPI1_CE_HIGH();//拉低待機
}while(NRF_Read_IRQ()!=0);//中斷產生時,IRQ引腳低電平
SPI_NRF_Write(SPI1, FLUSH_TX,TBuff,0);
SPI1_CE_LOW();//拉低待機
Delay_us(100);
SPI_NRF_Read(SPI1,R_REGISTER+STATUS,Status,1);//讀取Status
LCD_Num_6x12_O(200,20,Status[0],WHITE);
if(Status[0]&0x10)
{
Status[0]&=0x10;
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,Status,1);//
LCD_Str_6x12_O_P(220 , 10 ,"TxError!", WHITE);//重發超時 發送失敗
return ERROR;
}
else
{
Status[0]&=0x20;
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,Status,1);//
LCD_Str_6x12_O_P(240 , 10 ,"TxSuccess", WHITE);//發送成功
return SUCCESS;
}
}
ErrorStatus SPI_NRF_RX_DATAS(u8* RBuff)
{
ErrorStatusRX_Status=SUCCESS;
u8Status[1];
while(NRF_Read_IRQ()!=0);//中斷產生時,IRQ引腳低電平
SPI1_CE_LOW();//拉低待機,才能操作寄存器
Delay_us(100);
SPI_NRF_Read(SPI1,R_REGISTER+STATUS,Status,1);//讀取Status
switch(Status[0]&0x0e)
{
case 0x0e: RX_Status=ERROR;break; //RX_FIFO 空
default :LCD_Str_6x12_O_P(200 , 10 ,"RxSuccess!", WHITE);//RX_FIFO非空
break;
}
SPI_NRF_Read(SPI1,R_RX_PAYLOAD,RBuff,4);//讀RX_FIFO
SPI_NRF_Write(SPI1,W_REGISTER+STATUS,Status,1);//處理狀態寄存器標志
return RX_Status;
}
特別要注意的是在發射模式的時候,應該先拉低CE,先在TX FIFO里寫入要發射的數據,再拉高CE真正發射。
|