最近胶囊内窥镜项目中用到了业界常用的无线收发模块,即恩智浦公司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配置实现!