MyException - 我的异常网
当前位置:我的异常网» Verilog » nRF2401无线模块接收_FPGA实现~

nRF2401无线模块接收_FPGA实现~

www.MyException.Cn  网友分享于:2013-08-16  浏览:0次
nRF2401无线模块接收_FPGA实现~~

    最近胶囊内窥镜项目中用到了业界常用的无线收发模块,即恩智浦公司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,
    
    input             IRQ_in,
    
    // spi_driver_2401
    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,
    
    //
    input             spi_din,  // 
    output reg        spi_dout,
    output            spi_clk,
    output            spi_cs,
    
    input             spi_tx_en,
    output reg        spi_tx_rdy,
    input  [7:0]      spi_tx_db,
    
    input             spi_rx_en,
    output reg        spi_rx_rdy,
    output [7:0]      spi_rx_db,
    
    input             spi_cs_in
);
endmodule 

 spi驱动很简单,就是将数据发送出去或者接收进来,都是在发送使能和接收使能下将数据发送或者接收进来。数据收发结束后会产生一个时钟周期的完成信号,通知上一级模块。具体可见上述代码。

 

综上所述,nRF24L01的接收模块代码如上所示,再在nrf2401_receive_mod.v和spi_driver_2401.v上封一层顶层就OK了。

详细工程代码可见我下面的网盘分享链接,利用Quartus13.0编译的工程,带signalTap信号观察窗口。

另外接收模式的初始化流程我会在发送模式里一并写了,此处只贴上代码和基本的说明。

 By 我有风衣~~

~~~~~~~~~~~~~~~~下回见~~~~~~~~~~~~~~~~~~

下回写上发送模式的FPGA配置实现!

 

文章评论

Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员都该阅读的书
程序员都该阅读的书
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
Java程序员必看电影
Java程序员必看电影
老程序员的下场
老程序员的下场
 程序员的样子
程序员的样子
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
我是如何打败拖延症的
我是如何打败拖延症的
旅行,写作,编程
旅行,写作,编程
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
每天工作4小时的程序员
每天工作4小时的程序员
鲜为人知的编程真相
鲜为人知的编程真相
为什么程序员都是夜猫子
为什么程序员都是夜猫子
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些争议最大的编程观点
那些争议最大的编程观点
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
漫画:程序员的工作
漫画:程序员的工作
如何成为一名黑客
如何成为一名黑客
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
编程语言是女人
编程语言是女人
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
10个调试和排错的小建议
10个调试和排错的小建议
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
程序员应该关注的一些事儿
程序员应该关注的一些事儿
代码女神横空出世
代码女神横空出世
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序员的鄙视链
程序员的鄙视链
我的丈夫是个程序员
我的丈夫是个程序员
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有