前面我們介紹過單片機的幾種通信協議,并且初步了解如何操作寄存器進而控制芯片的工作等等。那么,今天我們將利用之前的知識來對無線收發模塊編寫驅動程序。
首先,介紹我們今天用到的無線收發模塊——NRF24L01芯片
nRF24L01簡介 :nRF24L01是由NORDIC生產的工作在2.4GHz~2.5GHz的ISM 頻段的單片無線收發器芯片。無線收發器包括:頻率發生器、增強型“SchockBurst”模式控制器、功率放大器、晶體振蕩器、調制器和解調器。輸出功率頻道選擇和協議的設置可以通過SPI 接口進行設置。幾乎可以連接到各種單片機芯片,并完成無線數據傳送工作。
管腳圖:
各個管腳的具體功能可自行參考數據手冊,由于高頻信號對線路設計要求十分嚴格,我們沒有必要去手焊電路,直接采用現成模塊即可。
接線圖:
從它的接線圖也很容易可以看出它是利用SPI與單片機進行通信的,而且有5根線需要與單片機I/O口連接。
以上為硬件部分連接,接下來具體講一講 軟件設計 。
我們拿到一塊芯片,肯定要先看它的數據手冊,了解它是如何工作的,然后還得重點了解一下這塊芯片到底有哪些寄存器,這些寄存器的地址是什么,控制字是什么,時序圖怎么樣。所以我們從寄存器開始入手。
nRF24L01有多達25個寄存器,其中大部分是8位的,有兩個數據緩沖器是32字節的,還有3個是40位的(發送地址和通道0、通道1接收地址),我們選擇其中幾個寄存器來進行說明。
名稱:CONFIG 地址:00H
作用:可以用來決定是否屏蔽發送完成中斷、接收完成中斷、最大重發次數中斷,還有芯片是上電還是掉電,工作在發射模式還是接收模式等。
名稱:EN_AA 地址:01H
作用:用來使能各個通道的應答
名稱:RX_ADDR 地址:02H
作用:接收地址允許位
名稱:SETUP_AW 地址:03H
作用:用來設置地址寬度(3、4、5字節)
名稱:SETUP_RETR 地址:04H
作用:建立自動重發,高四位決定自動重發的延時,低四位決定重發次數
名稱:STATUS 地址:07H
作用:這是一個狀態寄存器,從名字可以看出這個寄存器是描述狀態的。高4位可讀可寫,最高位保留,其中bit6 ~ bit4分別可以產生接收數據中斷、發送數據中斷、最大重發次數中斷。bit3~bit1可以判斷是哪個通道獲得數據,只能讀不能寫,最低位保留。
名稱:RX_ADDR_P0 地址:0AH
作用:它是數據通道0的接收地址,最多5個字節,從低字節開始,字節數量由前面的SETUP_AW決定,還有10H的發送地址,需要與這個地址一致
還有一些其他的寄存器就不多說了。這些寄存器大部分是比較重要的,我們在寫芯片的初始化程序的時候,就是對這些寄存器進行配置。(注意:我們在寫芯片的驅動程序,基本上是從芯片的初始化開始的,而芯片要初始化,當然要搞清楚它的各個寄存器地址、作用,才能進行配置)
上面提到的自動重發可能會不理解,原來一個發送過程并不只是發送機獨自一個人的事情。一開始, 發送機發送連同身份地址在內的數據包給接收機,接收機收到數據并確認正確后,就以剛才發送機發來的身份地址回發一個確認信號,而此時發送機會自動切換到接收狀態,當收到確認信號與通道0的地址進行比較,地址相吻合,就知道對方收到數據,這樣一次發送數據才算完畢。當然,這個過程是模塊內部自動完成的,不用人為干預。因此這就叫做增強模式。在這個過程中發送雙方都最少各有一次發送和接收的過程。這樣就保證了數據可靠收到。如果發送一次,接收機沒有收到,模塊具有允許重發,和重發次數設置寄存器,當你設置了允許重發,并設置好重發次數。在發送機發送完-一個數據包,等待會兒, 如果沒有接收到應答信號, 發送機就會重發一次,如此直到重發完你設定的次數,再收不到它就產生一個中斷信號給IRQ,告訴模塊沒有發送成功,由程序根據情況再決定是否重發。
除了寄存器之外,還有芯片的一些命令格式也要注意
那么要如何來理解上面這張表呢?我們可以看一下。指令R_REGISTER和指令W_REGISTER的前三位可以確定是讀還是寫,如果是000就是讀,如果是001就是寫,后面的5位是寄存器的地址。合起來就構成了一個8位字節,就可以確定對哪個寄存器進行讀或寫了。我們可以將R_REGISTER宏定義為0x00,如果某個寄存器A的地址為0x12,那么指令(R_REGISTER+A)不就是對寄存器A進行讀的操作嗎?那有人可能會問,萬一寄存器地址很大,他們加起來不就會讓高三位超過000和001嗎?在這里是不可能的,后面的5位地址可以有32個寄存器,而這塊芯片沒有這么多寄存器,因此不會超過。前兩個指令也就很好理解了。
后面的幾個指令都是專用指令,只要發送相應的代碼,就會執行右邊說明的操作,比如發送指令W_RX_PAYLOAD,那么就是往TX FIFO寄存器里寫數據。也很好理解。
指令和寄存器都介紹完了,因此,我們可以先建一個頭文件,把各個寄存器的地址和指令先用宏定義好。(部分截圖如下)
這些寄存器的名稱、作用和地址都是從數據手冊上可以直接獲取的,我們以宏的形式將它們封裝好。
然后是它的時序圖
上圖就是NRF24L01模塊與單片機建立通訊的時序圖。看懂它,是用單片機控制這個模塊,與它建立通訊的基礎條件。很多串行通訊設備都采用這種方式,也叫SPI.它一般有四根連線:第一根是片選信號線,單片機可以同時連接多個功能相同或不同的模塊,為了節省輸入輸出(I/0)信號線的資源,一般都將多個模塊連接在相同的SCK、MOSI、 MIS0線 上,這叫串行總線。那單片機發出指令后,模塊怎么知道指令是發給自己的而不是發給其它模塊的呢?這就靠每個模塊上和單片機專線連接的CSN片選信號線,平時CSN是 高電位的,當把這根線的電位拉低那一時刻,模塊就知道單片機要給自己發指令了。
從時序圖上我們可以看出 ,讀操作的時候,主機先發送指令位,同時接收從機的狀態寄存器數據,然后再接收從機發送的數據。寫操作時,一樣是主機先發送指令位,同時接收從機的狀態寄存器數據,然后再發送你要的數據。
而且,根據經驗,在利用SPI通信時,我們至少要寫這兩部分的程序,一個是發送/接收一個字節,這一部分的寫法是固定的,完全可以照抄。另一個是根據時序,對芯片的讀寫操作。
但是在這里,稍稍有點不同的是,因為有兩個寄存器是32個字節的,還有3個是5個字節的,其他的都是8位的寄存器,因此,這些寄存器的讀寫程序要單獨寫。根據以上分析,我們可以寫出如下4個程序。
uint SPI_RW(uint dat) //利用SPI單獨寫一個字節,并返回狀態值
/ 這是最基本的一個函數,幾乎所有函數都要調用這個函數,它就是將一個8位字節從高位開始 ,一位一位的放到數據總線上,由于左移位后會補0,因此低位也可以順便接收來自MISO總線上的數據,作為返回值。 /
當然,執行一次這個函數并不算是一次通信,一次通信至少要發送兩個字節,地址和指令,于是有了下面的函數。
uchar NRF24L01_Read(uchar reg) //讀出一個寄存器的數據,發送寄存器地址(包括讀寫位+地址)用以選擇寄存器,然后執行空操作NOP,目的是讀出數據。(空操作是一種指令格式,上面有提到)
uint NRF24L01_write_Reg(uchar reg, ucharvalue) //往一個寄存器里寫一個字節,并且返回寄存器狀態值
uint SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars)
/ 讀出緩沖寄存器里的數據,最多32個字節,這個與上面的讀其實是類似的,只不過這個加了個for循環,目的是讀出多個字節 /
uint SPI_Write_Buf(uchar reg, uchar *pBuf, uchar uchars)
/ 往緩沖寄存器里寫數據,至多32個字節 /
以上幾個程序的編程思路是比較經典的編程思路,從發送字節開始,到芯片發送寄存器地址和命令。(源碼將在文末附上)
寫完這幾個函數,也就完成了工作的一半了。然后我們還要編寫一個發射成功的判斷函數和一個接收成功的判斷函數,可以通過這個函數的返回值來判斷是否接收或者發送成功。
在main函數的第一句,我們還可以編寫一個函數,用來檢測24L01是否存在。當然,不寫這個也可以。
前面部分的編程都只能算是準備階段,從芯片的初始化開始才能算是真正的編程。
所以,接下來,我們得利用前面寫好的這些函數,來對芯片進行初始化。
初始化主要有以下幾個方面
(1)設置接收數據長度
(2)寫TX節點地址
(3)設置RX節點地址,主要為了使能ACK
(4)使能通道0的自動應答(多通道暫不考慮)
(5)使能通道0的接收地址
(6)設置自動重發間隔時間和最大自動重發次數
(7)設置RF通道為0 收發必須一致,0為2.4GHz
(8)設置TX發射參數,0db增益,1Mbps,低噪聲增益開啟
(9)配置基本工作模式的參數;PWR_UP,EN_CRC,16BIT_CRC,接收模式,開啟所有中斷
這些可以參考數據手冊的寄存器部分。
到這里,我們就可以在main函數里開始調用前面編寫的程序了。
進入main函數,首先是調用初始化程序,然后就可以讀取緩沖寄存器里的值或者是寫入數據。main函數部分根據用戶需要編寫。比如發射機可以用按鍵按下來發送數據,接收機則通過查看IRQ管腳來判斷是否接收到數據,接收到數據后再執行你要的程序,比如控制小燈亮等等。
最后,我們來理一下編程思路
①首先,我們先明確了它是利用SPI的通信方式來對芯片控制的。不管是什么通信,無非就是讀和寫操作而已。讀和寫無非就是發送地址+命令。通常有一位是讀寫方向位的,這個查看手冊就可以知道。因此,肯定要先編寫一個“發送函數”,把字節發送出去,其實也就是單片機通過MOMI輸出高低電平而已。然后,前面也提到過,發送一個字節是沒有意義的,一次通信至少要包含兩個字節,第一個字節包含了讀寫的方向位以及待操作寄存器的地址,第二個字節是具體的命令。我們將發送這兩個字節的語句,結合時序“打包”在一起,就是對芯片寄存器的讀寫函數了。寫可以理解成先發送地址(同時返回狀態值),再發送命令。讀可以理解成先發送地址,再執行空語句(執行空語句的目的是為了讀出寄存器值)。
②上一步是實現了對寄存器讀寫一個字節,但是我們知道有的寄存器不止一個字節,比如發送地址和接收地址都有5個字節,發射接收緩沖器有32個字節等。因此我們可以編寫一個函數,連續讀出或者寫入多個字節,實現也很簡單,就是定義一個數組,讀的話就把數據讀出放入到這個數組中,寫的話就是把你數組里的內容寫進去。
③以上兩步是如何實現讀和寫。這還不夠,我們還得搞清楚具體讀寫什么內容。這就得回到它的各個寄存器上去了。我們要根據手冊的說明,看各個寄存器有什么用,這在前面已經講過了。主要是在初始化程序中比較重要。
④光有讀和寫不夠,我們還得知道每一次通信是否成功。因此,還得編寫兩個函數。比如接收,我們要先讀出狀態寄存器STATUS的值,來判斷是否接收成功,接收成功是會產生接收中斷的,我們看相應的位就能知道有沒有接收成功了。如果接收成功,我們把數據轉移到我們自己定義的一個數組中,方便使用。如果是發送,我們只要把要發送的數據放在數組里,然后發送出去即可,然后還要讀取狀態寄存器的值,看是否發送成功,或者是否達到最大重發次數,返回相應的值。
⑤有了上面的這些基礎,我們就可以在main函數里先進行初始化,然后寫我們的發射程序和接收程序了。在發射函數里,每次先配置一下寄存器CONFIG,然后再利用我們前面的程序發射。接收比較簡單,只要看IRQ是否產生中斷即可。
以上就是整個編程思路。
-
單片機
+關注
關注
6043文章
44621瀏覽量
638583 -
通信協議
+關注
關注
28文章
915瀏覽量
40440 -
寄存器
+關注
關注
31文章
5363瀏覽量
121178 -
發生器
+關注
關注
4文章
1368瀏覽量
61858 -
nRF24L01
+關注
關注
17文章
331瀏覽量
69655
發布評論請先 登錄
相關推薦
評論