最近膠囊內窺鏡項目中用到了業界常用的無線收發模塊,即恩智浦公司nRF系列無線收發模塊,該模塊當前有好幾種選擇,比如nRF24L01只有無線收發模塊,需要外部MCU進行驅動及數據收發,還有nRF24LE1自帶單片機內核,即單片機集成在收發模塊內。另外還有nRF24xx+USB模塊,這種模塊使用起來更方便,數據收發后直接跟上位機通信。nRF系列常用模塊有nRF24L01、nRF24LE1,當前項目架構是內部膠囊使用的是nRF24LE1無線模塊,而外部接收儀(fpga做主控)使用的是nRF24L01模塊。這兩種無線模塊的寄存器配置完全兼容。
下面針對nRF24L01的驅動及數據收發的FPGA實現進行總結。
首先本人之前從未用過nRF2401,因此先看起nRF2401的數據手冊。英文手冊的特點是內容詳實,想查的內容手冊里肯定全有,當然也有特殊情況,比如在調試OV公司的相機模塊時,OV公司的datasheet真的是shit,反復查找資料結合中文資料以及調試推斷才搞清楚出圖的幾個最重要寄存器的配置。據說咨詢OV的FAE需要付費。
閑話少說,繼續2401的FPGA驅動配置。英文手冊內容詳實,但缺點是零散分散,想快速找到配置方式,需要反復閱讀,並結合調試進行確認。比如我需要快速對2401進行初始化並將2401配置到接收模式,如果看英文手冊你會雲里霧里好一會兒,另外FPGA在配置時對各個控制信號的時序需要詳細說明,而手冊上只有SPI的時序圖及相關參數,不能一下子看出各個信號之間的時序要求。因此本人首先結合中文資料理清了nRF2401的初始化流程和接收模式的配置要求(即寄存器配置相關參數),然后結合單片機代碼和英文手冊確認每一個寄存器的配置,最后將200行的單片機c代碼轉換成我需要的verilog代碼。(ps:HDL玩轉嵌套循環真是累啊,C代碼輕松幾個函數,verilog需要兜兜轉轉一大圈)
下面首先粘貼上單片機C代碼:
#include <reg52.h> #include <intrins.h> #define uint unsigned int #define uchar unsigned char #define TX_ADDR_WITDH 5 //發送地址寬度設置為5個字節 #define RX_ADDR_WITDH 5 //接收地址寬度設置為5個字節 #define TX_DATA_WITDH 2 //發送數據寬度 #define RX_DATA_WITDH 2 //接收數據寬度 //以下為命令寄存器 #define R_REGISTER 0x00 // 讀寄存器 #define W_REGISTER 0x20 // 寫寄存器 #define R_RX_PLOAD 0x61 // 讀RX FIFO有效數據,1-32字節,當讀數據完成后,數據被清除,應用於接收模式 #define W_TX_PLOAD 0xA0 // 寫TX FIFO有效數據,1-32字節,寫操作從字節0開始,應用於發射模式 #define FLUSH_TX 0xE1 // 清除TX FIFO寄存器,應用於發射模式 #define FLUSH_RX 0xE2 // 清除RX FIFO寄存器,應用於接收模式 #define REUSE_TX_PL 0xE3 // 重新使用上一包有效數據,當CE為高過程中,數據包被不斷的重新發射 #define NOP 0xFF // 空操作,可以用來讀狀態寄存器 //以下為寄存器地址 #define CONFIG 0x00 // 配置寄存器 #define EN_AA 0x01 // “自動應答”功能寄存 #define EN_RX_ADDR 0x02 // 接收通道使能寄存器 #define SETUP_AW 0x03 // 地址寬度設置寄存器 #define SETUP_RETR 0x04 // 自動重發設置寄存器 #define RF_CH 0x05 // 射頻通道頻率設置寄存器 #define RF_SETUP 0x06 // 射頻設置寄存器 #define STATUS 0x07 // 狀態寄存器 #define OBSERVE_TX 0x08 // 發送檢測寄存器 #define CD 0x09 // 載波檢測寄存器 #define RX_ADDR_P0 0x0A // 數據通道0接收地址寄存器 #define RX_ADDR_P1 0x0B // 數據通道1接收地址寄存器 #define RX_ADDR_P2 0x0C // 數據通道2接收地址寄存器 #define RX_ADDR_P3 0x0D // 數據通道3接收地址寄存器 #define RX_ADDR_P4 0x0E // 數據通道4接收地址寄存器 #define RX_ADDR_P5 0x0F // 數據通道5接收地址寄存器 #define TX_ADDR 0x10 // 發送地址寄存器 #define RX_PW_P0 0x11 // 數據通道0有效數據寬度設置寄存器 #define RX_PW_P1 0x12 // 數據通道1有效數據寬度設置寄存器 #define RX_PW_P2 0x13 // 數據通道2有效數據寬度設置寄存器 #define RX_PW_P3 0x14 // 數據通道3有效數據寬度設置寄存器 #define RX_PW_P4 0x15 // 數據通道4有效數據寬度設置寄存器 #define RX_PW_P5 0x16 // 數據通道5有效數據寬度設置寄存器 #define FIFO_STATUS 0x17 // FIFO狀態寄存器 uchar bdata sta; // 狀態變量 #define RX_DR (sta & 0x40) // 接收成功中斷標志 0100 0000(sta^6) #define TX_DS (sta & 0x20) // 發射成功中斷標志 0010 0000 (sta^5) #define MAX_RT (sta & 0x10) // 重發溢出中斷標志 0001 0000 (sta^4) //nRF24L01引腳定義 sbit MISO=P2^3; sbit IRQ=P2^2; sbit SCK=P2^4; sbit MOSI=P2^1; sbit CE=P2^5; sbit CSN=P2^0; //外圍引腳定義 sbit LED=P3^7; uchar code TX_Addr[TX_ADDR_WITDH]={0x34,0x43,0x10,0x10,0x01}; uchar code RX_Addr[RX_ADDR_WITDH]={0x34,0x43,0x10,0x10,0x01}; uchar RX_Buffer[RX_DATA_WITDH]={0}; uchar m=0; void _delay_us(int x) { int i,j; for (j=0;j<x;j++) for (i=0;i<12;i++); } void _delay_ms(int x) { int i,j; for (j=0;j<x;j++) for (i=0;i<120;i++); } //SPI時序函數(1) uchar SPI_RW(uchar byte) { uchar i; for(i=0;i<8;i++) { if(byte&0x80) MOSI=1; else MOSI=0; byte<<=1; SCK=1; if(MISO) byte|=0x01; SCK=0; } return byte; } //從寄存器中讀一字節(3) uchar SPI_R_byte(uchar reg) { uchar reg_value; CSN=0; SPI_RW(reg); reg_value=SPI_RW(0); CSN=1; return reg_value; } //從寄存器中讀多個字節(4) uchar SPI_R_DBuffer(uchar reg,uchar *Dat_Buffer,uchar Dlen) { uchar status,i; CSN=0; status=SPI_RW(reg); for(i=0;i<Dlen;i++) { Dat_Buffer[i]=SPI_RW(0); } CSN=1; return status; } //往寄存器中寫多個字節(5) uchar SPI_W_DBuffer(uchar reg,uchar *TX_Dat_Buffer,uchar Dlen) { uchar status,i; CSN=0; status=SPI_RW(reg); for(i=0;i<Dlen;i++) { SPI_RW(TX_Dat_Buffer[i]); } CSN=1; return status; } //往寄存器寫一字節(2) uchar SPI_W_Reg(uchar reg,uchar value) { uchar status;//返回狀態 CSN=0;//SPI片選 status=SPI_RW(reg);//寫入寄存器地址,同時讀取狀態 SPI_RW(value);//寫入一字節 CSN=1;// return status;//返回狀態 } //nRF24L01初始化 void nRF24L01_Init(void) { _delay_us(100); CE=0; CSN=1; SCK=0; IRQ=1; SPI_W_DBuffer(W_REGISTER+TX_ADDR,TX_Addr,TX_ADDR_WITDH);//本機地址 SPI_W_DBuffer(W_REGISTER+RX_ADDR_P0,RX_Addr,RX_ADDR_WITDH);//接收地址 SPI_W_Reg(W_REGISTER+EN_AA,0x01); //自動應答 SPI_W_Reg(W_REGISTER+EN_RX_ADDR,0x01);//通道一 SPI_W_Reg(W_REGISTER+RF_CH,0);//頻道 SPI_W_Reg(W_REGISTER+RX_PW_P0,RX_DATA_WITDH);//接收數據長度 SPI_W_Reg(W_REGISTER+RF_SETUP,0x07);//發射速率 } void nRF24L01_Set_RX_Mode() { CE=0; SPI_W_Reg(W_REGISTER+CONFIG,0x0f); CE=1; _delay_us(300); } uchar nRF24L01_RX(uchar *rx_buf) { uchar value=0; sta=SPI_R_byte(STATUS); if(RX_DR) { CE=0; SPI_R_DBuffer(R_RX_PLOAD,rx_buf,RX_DATA_WITDH); SPI_W_Reg(W_REGISTER+STATUS,sta); value=1; CSN=0; SPI_W_Reg(FLUSH_RX,0x00); CSN=1; LED=0; _delay_ms(100); LED=1; _delay_ms(100); } return value; } void main() { nRF24L01_Init(); LED=1; while(1) { nRF24L01_Set_RX_Mode(); nRF24L01_RX(RX_Buffer); } } }
如上所示,代碼里幾個常用函數功能概述:
_delay_us:延時函數us級
_delay_ms:延時函數ms級
SPI_RW:SPI讀寫時序(最底層spi時序)
SPI_R_byte: 從寄存器里讀出一個字節(后面會反復讀2401的STATUS寄存器)
SPI_R_DBuffer:連續 讀出多個字節,當配置到接收模式后,連續從2401的RX_FIFO中讀取數據;
SPI_W_DBuffer:連續寫入多個字節,對2401進行收發地址初始化時會用到;
SPI_W_Reg:往寄存器里寫入一個字節
nRF24L01_Init:2401初始化
nRF24L01_Set_RX_Mode:2401設置到接收模式
nRF24L01_RX:2401在接收模式下接收RX_FIFO里的數據
// ------------------------------------------------------------------------------
正是反復閱讀上述C代碼后,提取了各個信號之間的時序關系,並結合英文手冊第8章的spi時序參數才理清整個初始化和配置接收模式的狀態機流程。還是寫C的好,干FPGA搞多重循環不容易,調試也麻煩,FPGA沒有單步調試,遇到問題都是看片內資源多不多,多的話可以加大signalTap或者chipscope的采樣深度,不夠的話需要拉測試引腳,用示波器或者邏輯分析儀來玩轉,邏輯分析儀還不是每家公司都有的,畢竟很貴嘛。
下面先粘貼FPGA的.v代碼
// nRF2401 ctrl_mod `timescale 1 ns / 1 ps module nrf2401_receive_mod ( input clk, // 24MHz-> 4MHz input rst_n, // nRF2401 Interrupt
input IRQ_in, // to spi_driver_2401.v output reg rece_config_en, // tx_en input rece_config_data_done, // tx_rdy output [7:0] rece_config_data_out, // tx_db output reg read_en, // rx_en input rx_data_done, // rx_rdy input [7:0] rx_data, // rx_db output reg spi_cs_out, output reg spi_ce_out, output reg rece_init_done, output reg rece_data_done
);
endmodule
spi驅動代碼如下`timescale 1 ns/1 ps
module spi_driver_2401 ( input clk, input rst_n, // spi interface input spi_din, // MISO output reg spi_dout, // MOSI output spi_clk, // SCK output spi_cs, // chip select // to nrf2401_receive_mod.v
input spi_tx_en, // send enable output reg spi_tx_rdy, // send done input [7:0] spi_tx_db, // send byte input spi_rx_en, // receive enable output reg spi_rx_rdy, // receive done output [7:0] spi_rx_db, // receive byte
input spi_cs_in // ); endmodule
spi驅動很簡單,就是將數據發送出去或者接收進來,都是在發送使能和接收使能下將數據發送或者接收進來。數據收發結束后會產生一個時鍾周期的完成信號,通知上一級模塊。具體可見上述代碼。
綜上所述,nRF24L01的接收模塊代碼如上所示,再在nrf2401_receive_mod.v和spi_driver_2401.v上封一層頂層就OK了。
詳細工程代碼可見我下面的網盤分享鏈接,利用Quartus13.0編譯的工程,帶signalTap信號觀察窗口。
另外接收模式的初始化流程我會在發送模式里一並寫了,此處只貼上代碼和基本的說明。
鑒於需要的朋友比較多,大家可加我qq:806518565(或微信搜索806518565也行),謝謝啦!
By 我有風衣~~
~~~~~~~~~~~~~~~~下回見~~~~~~~~~~~~~~~~~~
下回寫上發送模式的FPGA配置實現!