打印

【连载】STM32开发指南--第三十七章 无线通信实验

[复制链接]
5174|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
正点原子|  楼主 | 2013-3-26 22:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子 于 2013-3-26 22:54 编辑

第三十七章  无线通信实验
ALIENTKE战舰STM32开发板带有一个2.4G无线模块(NRF24L01模块)通信接口,采用8脚插针方式与开发板连接。本章我们将以NRF24L01模块为例向大家介绍如何在ALIENTEK战舰STM32开发板上实现无线通信。在本章中,我们将使用两块战舰STM32开发板,一块用于发送收据,另外一块用于接收,从而实现无线数据传输。本章分为如下几个部分:
37.1 NRF24L01无线模块简介
37.2 硬件设计
37.3 软件设计
37.4 下载验证

37.1 NRF24L01无线模块简介
NRF24L01无线模块,采用的芯片是NRF24L01,该芯片的主要特点如下:
1)2.4G全球开放的ISM频段,免许可证使用。
2)最高工作速率2Mbps,高校的GFSK调制,抗干扰能力强。
3)125个可选的频道,满足多点通信和调频通信的需要。
4)内置CRC检错和点对多点的通信地址控制。
5)低工作电压(1.9~3.6V)。
6)可设置自动应答,确保数据可靠传输。
该芯片通过SPI与外部MCU通信,最大的SPI速度可以达到10Mhz。本章我们用到的模块是深圳云佳科技生产的NRF24L01,该模块已经被很多公司大量使用,成熟度和稳定性都是相当不错的。该模块的外形和引脚图如图37.1.1所示:

图37.1.1 NRF24L01无线模块外观引脚图
模块VCC脚的电压范围为1.9~3.6V,建议不要超过3.6V,否则可能烧坏模块,一般用3.3V电压比较合适。除了VCC和GND脚,其他引脚都可以和5V单片机的IO口直连,正是因为其兼容5V单片机的IO,故使用上具有很大优势。
关于NRF24L01的详细介绍,请参考NRF24L01的技术手册。
37.2 硬件设计  
本章实验功能简介:开机的时候先检测NRF24L01模块是否存在,在检测到NRF24L01模块之后,根据KEY0和KEY1的设置来决定模块的工作模式,在设定好工作模式之后,就会不停的发送/接收数据,同样用DS0来指示程序正在运行。
所要用到的硬件资源如下:
1)  指示灯DS0  
2)  KEY0和KEY1按键
3) TFTLCD模块
4)  NRF24L01模块
NRF24L01模块属于外部模块,这里我们仅介绍开发板上NRF24L01模块接口和STM32的连接情况,他们的连接关系如图37.2.1所示:

图37.2.1 NRF24L01模块接口与STM32连接原理图
       这里NRF24L01也是使用的SPI2,和W25Q64以及SD卡等共用一个SPI接口,所以在使用的时候,他们分时复用SPI2。本章我们需要把SD卡和W25Q64的片选信号置高,以防止这两个器件对NRF24L01的通信造成干扰。
由于无线通信实验是双向的,所以至少要有两个模块同时能工作,这里我们使用2套ALIENTEK战舰STM32开发板来向大家演示。
37.3 软件设计
打开上一章的工程,首先在HARDWARE文件夹下新建一个NRF24L01的文件夹。然后新建一个24l01.c和24l01.h的文件保存在NRF24L01文件夹下,并将这个文件夹加入头文件包含路径。
       打开24l01.c文件,输入如下代码:
#include "24l01.h"
#include "lcd.h"
#include "delay.h"
#include "spi.h"
const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址
const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址
//初始化24L01的IO口
void NRF24L01_Init(void)
{
    RCC->APB2ENR|=1<<3;     //使能PORTB时钟        
    RCC->APB2ENR|=1<<5;     //使能PORTD时钟
    RCC->APB2ENR|=1<<8;     //使能PORTG时钟
    //这里pb12和pd2拉高,是为了防止互相影响 .
    //因为他们共用一个SPI口.   
    GPIOB->CRH&=0XFFF0FFFF;
    GPIOB->CRH|=0X00030000; //PB12 推挽         
    GPIOB->ODR|=1<<12;      //PB12上拉 防止W25X的干扰                 
    GPIOD->CRL&=0XFFFFF0FF;
    GPIOD->CRL|=0X00000300; //PD2 推挽     
    GPIOD->ODR|=1<<2;       //PD2上拉   禁止SD卡的干扰

    GPIOG->CRL&=0X00FFFFFF;
    GPIOG->CRL|=0X33000000; //PG6 7 推挽        
    GPIOG->CRH&=0XFFFFFFF0;
    GPIOG->CRH|=0X00000008; //PG8 输入     
    GPIOG->ODR|=7<<6;       //PG6 7 8 上拉  
    SPI2_Init();            //初始化SPI   
    //针对NRF的特点修改SPI的设置
    SPI2->CR1&=~(1<<6);     //SPI设备失能
    SPI2->CR1&=~(1<<1);     //空闲模式下SCK为0 CPOL=0
    SPI2->CR1&=~(1<<0);     //数据采样从第1个时间边沿开始,CPHA=0  
    SPI2->CR1|=1<<6;        //SPI设备使能   
    NRF24L01_CE=0;          //使能24L01
    NRF24L01_CSN=1;         //SPI片选取消               
}
//检测24L01是否存在
//返回值:0,成功;1,失败   
u8 NRF24L01_Check(void)
{
    u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
    u8 i;
    SPI2_SetSpeed(SPI_SPEED_4); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)     NRF24L01_Write_Buf(WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址.   
    NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址  
    for(i=0;i<5;i++)if(buf!=0XA5)break;                                
    if(i!=5)return 1;//检测24L01错误   
    return 0;       //检测到24L01
}      
//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
    u8 status;  
   NRF24L01_CSN=0;                 //使能SPI传输
    status =SPI2_ReadWriteByte(reg);//发送寄存器号
    SPI2_ReadWriteByte(value);      //写入寄存器的值
    NRF24L01_CSN=1;                 //禁止SPI传输     
    return(status);                 //返回状态值
}
//读取SPI寄存器值
//reg:要读的寄存器
u8 NRF24L01_Read_Reg(u8 reg)
{
    u8 reg_val;     
    NRF24L01_CSN = 0;          //使能SPI传输      
    SPI2_ReadWriteByte(reg);   //发送寄存器号
    reg_val=SPI2_ReadWriteByte(0XFF);//读取寄存器内容
    NRF24L01_CSN = 1;          //禁止SPI传输           
    return(reg_val);           //返回状态值
}   
//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
    u8 status,u8_ctr;         
    NRF24L01_CSN = 0;           //使能SPI传输
    status=SPI2_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值      
    for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI2_ReadWriteByte(0XFF);
//读出数据
    NRF24L01_CSN=1;       //关闭SPI传输
    return status;        //返回读到的状态值
}
//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//len:数据长度
//返回值,此次读到的状态寄存器值
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
    u8 status,u8_ctr;      
    NRF24L01_CSN = 0;          //使能SPI传输
    status = SPI2_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值
    for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI2_ReadWriteByte(*pBuf++); //写入数据     NRF24L01_CSN = 1;       //关闭SPI传输
    return status;          //返回读到的状态值
}                  
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf)
{
    u8 sta;
    SPI2_SetSpeed(SPI_SPEED_8);//spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)   
    NRF24L01_CE=0;
    NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);
//写数据到TX BUF  32个字节
    NRF24L01_CE=1;//启动发送      
    while(NRF24L01_IRQ!=0);//等待发送完成
    sta=NRF24L01_Read_Reg(STATUS);  //读取状态寄存器的值      
    NRF24L01_Write_Reg(WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
    if(sta&MAX_TX)//达到最大重发次数
    {
        NRF24L01_Write_Reg(FLUSH_TX,0xff);//清除TX FIFO寄存器
        return MAX_TX;
    }
    if(sta&TX_OK) return TX_OK;//发送完成
    return 0xff;//其他原因发送失败
}
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf)
{
    u8 sta;                                       
    SPI2_SetSpeed(SPI_SPEED_8); //spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)   
    sta=NRF24L01_Read_Reg(STATUS);//读取状态寄存器的值      
    NRF24L01_Write_Reg(WRITE_REG+STATUS,sta); //清除TX_DS或MAX_RT中断标志
    if(sta&RX_OK)//接收到数据
    {
        NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
        NRF24L01_Write_Reg(FLUSH_RX,0xff);//清除RX FIFO寄存器
        return 0;
    }      
    return 1;//没收到任何数据
}                       
实验32 无线通信实验.rar (157.58 KB)

《STM32开发指南》第三十七章 无线通信实验.rar (681.43 KB)




沙发
正点原子|  楼主 | 2013-3-26 22:51 | 只看该作者
本帖最后由 正点原子 于 2013-3-26 22:53 编辑

//该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了      
void NRF24L01_RX_Mode(void)
{
    NRF24L01_CE=0;   
    NRF24L01_Write_Buf(WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);
//写RX节点地址   
    NRF24L01_Write_Reg(WRITE_REG+EN_AA,0x01);    //使能通道0的自动应答   
    NRF24L01_Write_Reg(WRITE_REG+EN_RXADDR,0x01);//使能通道0的接收地址      
    NRF24L01_Write_Reg(WRITE_REG+RF_CH,40);      //设置RF通信频率        
    NRF24L01_Write_Reg(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);
//选择通道0的有效数据宽度     
    NRF24L01_Write_Reg(WRITE_REG+RF_SETUP,0x0f);
//设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
    NRF24L01_Write_Reg(WRITE_REG+CONFIG, 0x0f);
//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式
    NRF24L01_CE = 1; //CE为高,进入接收模式
}                       
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,
//波特率和LNA HCURR
//PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了      
//CE为高大于10us,则启动发送.   
void NRF24L01_TX_Mode(void)
{                                                      
    NRF24L01_CE=0;      
    NRF24L01_Write_Buf(WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);
//写TX节点地址
    NRF24L01_Write_Buf(WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);
//设置TX节点地址,主要为了使能ACK     
    NRF24L01_Write_Reg(WRITE_REG+EN_AA,0x01);     //使能通道0的自动应答   
    NRF24L01_Write_Reg(WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址  
    NRF24L01_Write_Reg(WRITE_REG+SETUP_RETR,0x1a);
//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
    NRF24L01_Write_Reg(WRITE_REG+RF_CH,40);       //设置RF通道为40
    NRF24L01_Write_Reg(WRITE_REG+RF_SETUP,0x0f);  
//设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
    NRF24L01_Write_Reg(WRITE_REG+CONFIG,0x0e);   
//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
    NRF24L01_CE=1;//CE为高,10us后启动发送
}
此部分代码我们不多介绍,在这里强调一个要注意的地方,在NRF24L01_Init函数里面,我们调用了SPI2_Init()函数,该函数我们在第二十八章曾有提到,在第二十八章的设置里面,SCK空闲时为高,但是NRF24L01的SPI通信时序如图37.3.1所示


图37.3.1 NRF24L01读写操作时序
上图中Cn代表指令位,Sn代表状态寄存器位,Dn代表数据位。从图中可以看出,SCK空闲的时候是低电平的,而数据在SCK的上升沿被读写。所以,我们需要设置SPI的CPOL和CPHA均为0,来满足NRF24L01对SPI操作的要求。所以,我们在NRF24L01_Init函数里面又单独添加了将CPOL和CPHA设置为0的代码。保存24l01.c文件,加入到HARDWARE组下。接下来打开24l01.h,输入如下代码:
#ifndef __24L01_H
#define __24L01_H                     
#include "sys.h"   
//NRF24L01寄存器操作命令
#define READ_REG        0x00  //读配置寄存器,低5位为寄存器地址
......//省略部分定义
#define FIFO_STATUS     0x17  //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;
//bit1,RX FIFO满标志;bit2,3,保留 bit4,TX FIFO空标志;bit5,TX FIFO满标志;
//bit6,1, 循环发送上一数据包.0,不循环;
//24L01操作线
#define NRF24L01_CE   PGout(6) //24L01片选信号
#define NRF24L01_CSN  PGout(7) //SPI片选信号     
#define NRF24L01_IRQ  PGin(8)  //IRQ主机数据输入
//24L01发送接收数据宽度定义
#define TX_ADR_WIDTH    5       //5字节的地址宽度
#define RX_ADR_WIDTH    5       //5字节的地址宽度
#define TX_PLOAD_WIDTH  32     //32字节的用户数据宽度
#define RX_PLOAD_WIDTH  32     //32字节的用户数据宽度
void NRF24L01_Init(void);                 //初始化
void NRF24L01_RX_Mode(void);       //配置为接收模式
void NRF24L01_TX_Mode(void);              //配置为发送模式
u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 u8s); //写数据区
u8 NRF24L01_Read_Buf(u8 reg, u8 *pBuf, u8 u8s);  //读数据区              
u8 NRF24L01_Read_Reg(u8 reg);                                   //读寄存器
u8 NRF24L01_Write_Reg(u8 reg, u8 value);              //写寄存器
u8 NRF24L01_Check(void);                                     //检查24L01是否存在
u8 NRF24L01_TxPacket(u8 *txbuf);                         //发送一个包的数据
u8 NRF24L01_RxPacket(u8 *rxbuf);                        //接收一个包的数据
#endif
部分代码,主要定义了一些24L01的命令字(这里我们省略了一部分),以及函数声明,这里还通过TX_PLOAD_WIDTH和RX_PLOAD_WIDTH决定了发射和接收的数据宽度,也就是我们每次发射和接受的有效字节数。NRF24L01每次最多传输32个字节,再多的字节传输则需要多次传送。
保存24l01.h文件,接下来我们在主函数里面写入我们的实现代码,来达到我们所要求的功能。打开test.c文件在该文件内修改main函数如下:
int main(void)
{                     
       u8 key,mode;
       u16 t=0;               
       u8 tmp_buf[33];  
      Stm32_Clock_Init(9);    //系统时钟设置
       uart_init(72,9600);      //串口初始化为9600
       delay_init(72);                  //延时初始化
       LED_Init();                 //初始化与LED连接的硬件接口
       LCD_Init();                  //初始化LCD
       usmart_dev.init(72);      //初始化USMART        
      KEY_Init();                  //按键初始化
      NRF24L01_Init();      //初始化NRF24L01
       POINT_COLOR=RED;//设置字体为红色
       LCD_ShowString(60,50,200,16,16,"WarShip STM32");   
       LCD_ShowString(60,70,200,16,16,"NRF24L01 TEST");   
       LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
       LCD_ShowString(60,110,200,16,16,"2012/9/13");              
      while(NRF24L01_Check())   //检查NRF24L01是否在位.
       {
              LCD_ShowString(60,130,200,16,16,"NRF24L01 Error"); delay_ms(200);              
              LCD_Fill(60,130,239,130+16,WHITE); delay_ms(200);         
       }                                                         
       LCD_ShowString(60,130,200,16,16,"NRF24L01 OK");
      while(1)//在该部分确定进入哪个模式!
       {
              key=KEY_Scan(0);
              if(key==KEY_RIGHT)
              {
                     mode=0; break;                  
              }else if(key==KEY_DOWN)
              {
                     mode=1; break;                    
              }
              t++;
              if(t==100)LCD_ShowString(10,150,230,16,16,"KEY0:RX_Mode  KEY1:TX_Mode"
); //闪烁显示提示信息
             if(t==200)
              {     
                     LCD_Fill(10,150,230,150+16,WHITE); t=0;                    
              }
              delay_ms(5);     
       }   
      LCD_Fill(10,150,240,166,WHITE);//清空上面的显示               
      POINT_COLOR=BLUE;//设置字体为蓝色   
       if(mode==0)//RX模式
       {
              LCD_ShowString(60,150,200,16,16,"NRF24L01 RX_Mode");  
              LCD_ShowString(60,170,200,16,16,"Received DATA:");   
              NRF24L01_RX_Mode();               
              while(1)
              {                                                                     
                     if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
                     {
                            tmp_buf[32]=0;//加入字符串结束符
                            LCD_ShowString(0,190,239,32,16,tmp_buf);   
                     }else delay_us(100);         
                     t++;
                     if(t==10000) {t=0; LED0=!LED0;}//大约1s钟改变一次状态                    
              };   
       }else//TX模式
       {                                                   
              LCD_ShowString(60,150,200,16,16,"NRF24L01 TX_Mode");  
              NRF24L01_TX_Mode();
              mode=' ';//从空格键开始  
              while(1)
              {                                             
                     if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
                     {
                            LCD_ShowString(60,170,239,32,16,"Sended DATA:");     
                            LCD_ShowString(0,190,239,32,16,tmp_buf);
                            key=mode;
                            for(t=0;t<32;t++)
                            {
                                   key++;
                                   if(key>('~'))key=' ';
                                   tmp_buf[t]=key;     
                            }
                            mode++;
                            if(mode>'~')mode=' ';   
                            tmp_buf[32]=0;//加入结束符                 
                     }else
                     {                                                                        
                           LCD_ShowString(60,170,239,32,16,"Send Failed ");
                            LCD_Fill(0,188,240,218,WHITE);//清空上面的显示                  
                     };
                     LED0=!LED0; delay_ms(1500);                                                
              };
       }     
}
至此,我们整个实验的软件设计就完成了。
37.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,可以看到LCD显示如图37.4.1所示的内容(默认NRF24L01已经接上了):

图37.4.1 选择工作模式界面
通过KEY0和KEY1来选择NRF24L01模块所要进入的工作模式,我们两个开发板一个选择发送,一个选择接收就可以了。
设置好后通信界面如图37.4.2所示:


图37.4.2 通信界面
上图中,左侧的图片来自开发板A,工作在发送模式。右侧的图片来自开发板B,工作在接收模式,A发送,B接收。图中左右图片的数据不一样,是因为我们拍照的时间不一样导致的。

使用特权

评论回复
板凳
wuwenjie5516| | 2013-12-6 20:51 | 只看该作者
请指导,,那个发射的tmp_buf具体的内容在哪里呢??怎么看

使用特权

评论回复
地板
正点原子|  楼主 | 2014-1-14 16:03 | 只看该作者
在main函数

使用特权

评论回复
5
gaoshumingchan| | 2014-8-10 23:03 | 只看该作者
有没有发送字符串的函数,接收到直接通过USART1输出的

使用特权

评论回复
6
longfenghugui| | 2014-8-21 15:22 | 只看该作者

使用特权

评论回复
7
wodezpyzz| | 2015-3-21 10:21 | 只看该作者
请问SPI2_ReadWriteByte(reg);和SPI2_SetSpeed(SPI_SPEED_4);这个两个子函数怎么没有定义呢?

使用特权

评论回复
8
正点原子|  楼主 | 2015-3-30 19:18 | 只看该作者
wodezpyzz 发表于 2015-3-21 10:21
请问SPI2_ReadWriteByte(reg);和SPI2_SetSpeed(SPI_SPEED_4);这个两个子函数怎么没有定义呢?
...

这是在spi.c里面定义的。

使用特权

评论回复
9
Vitality1| | 2015-3-30 20:20 | 只看该作者
2.4G无线模块(NRF24L01模块)通信接口

使用特权

评论回复
10
quray1985| | 2015-3-30 23:04 | 只看该作者
看这pcb是用Altium Designer画的
想请教楼主,天线如何用Altium Designer来设计啊

使用特权

评论回复
11
cylnpy150411| | 2015-4-1 11:06 | 只看该作者
学习学习。

使用特权

评论回复
12
15901179594| | 2015-4-30 14:58 | 只看该作者
能收发短信吗?

使用特权

评论回复
13
15901179594| | 2015-4-30 15:01 | 只看该作者
楼主,我请教一下我要做一个能收发短信的板子用stm32f207zet6,还要用什么?请赐教。谢谢!

使用特权

评论回复
14
perry_peng| | 2015-4-30 15:05 | 只看该作者

谢谢分享

使用特权

评论回复
15
myxiexing| | 2015-5-1 00:30 | 只看该作者
好东东,谢谢分析.


..

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:我的STM32开发板店铺:http://openedv.taobao.com 我的技术论坛论坛:www.openedv.com

91

主题

264

帖子

71

粉丝