正点原子探索者原理图_正点原子【STM32-F407探索者】第三十八章 无线通信实验...

1)资料下载:点击资料即可下载

2)对正点原子Linux感兴趣的同学可以加群讨论:935446741

3)关注正点原子公众号,获取最新资料更新

245eea9cb5039d527fe6224fd5bd80e3.png

ALIENTKE 探索者 STM32F4 开发板带有一个无线模块(WIRELESS)接口,采用 8 脚插

针方式与开发板连接,可以用来连接 NRF24L01/RFID 等无线模块。本章我们将以 NRF24L01

模块为例向大家介绍如何在 ALIENTEK 探索者 STM32F4 开发板上实现无线通信。在本章中,

我们将使用两块探索者 STM32F4 开发板,一块用于发送收据,另外一块用于接收,从而实现

无线数据传输。本章分为如下几个部分:

38.1 NRF24L01 无线模块简介

38.2 硬件设计

38.3 软件设计

38.4 下载验证

38.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,该模块已经被很多公司大量使用,成熟度和稳定性都是

相当不错的。该模块的外形和引脚图如图 38.1.1 所示:

5fa4d21d1926c340f0a35ca5f88a72f0.png
图 38.1.1 NRF24L01 无线模块外观引脚图

模块 VCC 脚的电压范围为 1.9~3.6V,建议不要超过 3.6V,否则可能烧坏模块,一般用 3.3V

电压比较合适。除了 VCC 和 GND 脚,其他引脚都可以和 5V 单片机的 IO 口直连,正是因为

其 兼容 5V 单片机的 IO,故使用上具有很大优势。

关于 NRF24L01 的详细介绍,请参考 NRF24L01 的技术手册。

38.2 硬件设计

本章实验功能简介:开机的时候先检测 NRF24L01 模块是否存在,在检测到 NRF24L01

模块之后,根据 KEY0 和 KEY1 的设置来决定模块的工作模式,在设定好工作模式之后,就会

不停的发送/接收数据,同样用 DS0 来指示程序正在运行。

所要用到的硬件资源如下:

1) 指示灯 DS0

2) KEY0 和 KEY1 按键

3) TFTLCD 模块

4) NRF24L01 模块

NRF24L01 模块属于外部模块,这里我们仅介绍开发板上 NRF24L01 模块接口和 STM32F4

的连接情况,他们的连接关系如图 38.2.1 所示:

4c73b7a0c7bb41937370c9ae7bc92670.png
图 38.2.1 NRF24L01 模块接口与 STM32F4 连接原理图

这里NRF24L01也是使用的SPI1,和W25Q128共用一个SPI接口,所以在使用的时候,他

们分时复用SPI1。本章我们需要把W25Q128的片选信号置高,以防止这个器件对NRF24L01

的通信造成干扰。另外,NRF_IRQ和RS485_RE共用了PG8,所以,他们不能同时使用,不过

我们一般用不到NRF_IRQ这个信号,因此,RS485和NRF一般也可以同时使用。

由于无线通信实验是双向的,所以至少要有两个模块同时能工作,这里我们使用2套

ALIENTEK探索者STM32F4开发板来向大家演示。

38.3 软件设计

打开本章实验工程可以看到,我们在工程中添加了 spi 底层驱动函数,因为 NRF24L01 是

SPI 通信接口。同时,我们增加了 24l01.c 源文件以及包含了对应的头文件用来编写 NRF24L01

底层驱动函数。

打开 24l01.c 文件,代码如下:

const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址

const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址

//针对 NRF24L01 修改 SPI1 驱动

void NRF24L01_SPI_Init(void)

{

__HAL_SPI_DISABLE(&SPI1_Handler); //先关闭 SPI1

SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟空闲状态低电平

SPI1_Handler.Init.CLKPhase=SPI_PHASE_1EDGE;

//串行同步时钟的第 1 个跳变沿(上升或下降)数据被采样

HAL_SPI_Init(&SPI1_Handler);

__HAL_SPI_ENABLE(&SPI1_Handler);//使能 SPI1

}

//初始化 24L01 的 IO 口

void NRF24L01_Init(void)

{

GPIO_InitTypeDef GPIO_Initure;

__HAL_RCC_GPIOB_CLK_ENABLE();

//开启 GPIOB 时钟

__HAL_RCC_GPIOG_CLK_ENABLE();

//开启 GPIOG 时钟

//GPIOB14 初始化设置:推挽输出

GPIO_Initure.Pin=GPIO_PIN_14;

//PB14

GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_Init(GPIOB,&GPIO_Initure);

//初始化

//GPIOG6,7 推挽输出

GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;

//PG6,7

HAL_GPIO_Init(GPIOG,&GPIO_Initure);

//初始化

//GPIOG.8 上拉输入

GPIO_Initure.Pin=GPIO_PIN_8;

//PG8

GPIO_Initure.Mode=GPIO_MODE_INPUT;

//输入

HAL_GPIO_Init(GPIOG,&GPIO_Initure);

//初始化

HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET);

//PB14 输出 1,防止 SPI FLASH 干扰 NRF 的通信

SPI1_Init();

//初始化 SPI1

NRF24L01_SPI_Init();

//针对 NRF 的特点修改 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;

SPI1_SetSpeed(SPI_BAUDRATEPRESCALER_8);

//spi 速度为 10.5Mhz((24L01 的最大 SPI 时钟为 10Mhz,这里大一点没关系)

NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入 5 个字节的地址.

NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址

for(i=0;i<5;i++)if(buf[i]!=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 =SPI1_ReadWriteByte(reg);//发送寄存器号

SPI1_ReadWriteByte(value);

//写入寄存器的值

NRF24L01_CSN=1; //禁止 SPI 传输

return(status);

//返回状态值

}

//读取 SPI 寄存器值

//reg:要读的寄存器

u8 NRF24L01_Read_Reg(u8 reg)

{

u8 reg_val;

NRF24L01_CSN=0; //使能 SPI 传输

SPI1_ReadWriteByte(reg);

//发送寄存器号

reg_val=SPI1_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=SPI1_ReadWriteByte(reg);//发送寄存器值(位置),并读取状态值

for(u8_ctr=0;u8_ctr<len;u8_ctr++)pBuf[u8_ctr]=SPI1_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 = SPI1_ReadWriteByte(reg);

//发送寄存器值(位置),并读取状态值

for(u8_ctr=0; u8_ctr<len; u8_ctr++)SPI1_ReadWriteByte(*pBuf++); //写入数据

NRF24L01_CSN=1; //关闭 SPI 传输

return status;

//返回读到的状态值

}

//启动 NRF24L01 发送一次数据

//txbuf:待发送数据首地址

//返回值:发送完成状况

u8 NRF24L01_TxPacket(u8 *txbuf)

{

u8 sta;

SPI1_SetSpeed(SPI_BAUDRATEPRESCALER_8);

//spi 速度为 6.75Mhz(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(NRF_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;

SPI1_SetSpeed(SPI_BAUDRATEPRESCALER_8);

//spi 速度为 6.75Mhz(24L01 的最大 SPI 时钟为 10Mhz)

sta=NRF24L01_Read_Reg(STATUS); //读取状态寄存器的值

NRF24L01_Write_Reg(NRF_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;//没收到任何数据

}

//该函数初始化 NRF24L01 到 RX 模式

//设置 RX 地址,写 RX 数据宽度,选择 RF 频道,波特率和 LNA HCURR

//当 CE 变高后,即进入 RX 模式,并可以接收数据了

void NRF24L01_RX_Mode(void)

{

NRF24L01_CE=0;

NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,

RX_ADR_WIDTH);//写 RX 节点地址

NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道 0 的自动应答

NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);

//使能通道 0 的接收地址

NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置 RF 通信频率

NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);

//选择通道 0 的有效数据宽度

NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);

//设置 TX 发射参数,0db 增益,2Mbps,低噪声增益开启

NRF24L01_Write_Reg(NRF_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(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,

TX_ADR_WIDTH);//写 TX 节点地址

NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,

RX_ADR_WIDTH); //设置 TX 节点地址,主要为了使能 ACK

NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01); //使能通道 0 的自动应答

NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);

//使能通道 0 的接收地址

NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);

//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10 次

NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40); //设置 RF 通道为 40

NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);

//设置 TX 发射参数,0db 增益,2Mbps,低噪声增益开启

NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);

//配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断

NRF24L01_CE=1;//CE 为高,10us 后启动发送

}

此部分代码我们不多介绍,在这里强调一个要注意的地方,在 NRF24L01_Init 函数里面,

我们调用了 SPI1_Init()函数,该函数我们在第三十章曾有提到,在第三十章的设置里面,SCK

空闲时为高,但是 NRF24L01 的 SPI 通信时序如图 38.3.1 所示:

0f5ddc156de9a8ed912a759164360911.png
图 38.3.1 NRF24L01 读写操作时序

上图中 Cn 代表指令位,Sn 代表状态寄存器位,Dn 代表数据位。从图中可以看出,SCK 空

闲的时候是低电平的,而数据在 SCK 的上升沿被读写。所以,我们需要设置 SPI 的 CPOL 和 CPHA

均为 0,来满足 NRF24L01 对 SPI 操作的要求。所以,我们在 NRF24L01_Init 函数里面又单独添

加了将 CPOL 和 CPHA 设置为 0 的函数 NRF24L01_SPI_Init。这里主要是修改了下面两行代码;

SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步时钟的空闲状态为低电平

SPI1_Handler.Init.CLKPhase=SPI_PHASE_1EDGE;

//串行同步时钟的第 1 个跳变沿(上升或下降)数据被采样

接下来我们看看 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);

//初始化

......//省略部分函数申明

u8 NRF24L01_RxPacket(u8 *rxbuf);

//接收一个包的数据

#endif

部分代码,主要定义了一些 24L01 的命令字(这里我们省略了一部分),以及函数声明,这

里还通过 TX_PLOAD_WIDTH 和 RX_PLOAD_WIDTH 决定了发射和接收的数据宽度,也就是我们每次

发射和接受的有效字节数。NRF24L01 每次最多传输 32 个字节,再多的字节传输则需要多次传

送。

最后我们看看主函数:

int main(void)

{

u8 key,mode;u16 t=0; u8 tmp_buf[33];

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(336,8,2,7);

//设置时钟,168Mhz

delay_init(168);

//初始化延时函数

uart_init(115200);

//初始化 USART

usmart_dev.init(84);

//初始化 USMART

LED_Init();

//初始化 LED

KEY_Init();

//初始化 KEY

LCD_Init();

//初始化 LCD

NRF24L01_Init();

//初始化 NRF24L01

POINT_COLOR=RED;

LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");

LCD_ShowString(30,70,200,16,16,"NRF24L01 TEST");

LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");

LCD_ShowString(30,110,200,16,16,"2017/4/15");

while(NRF24L01_Check())

{

LCD_ShowString(30,130,200,16,16,"NRF24L01 Error");

delay_ms(200);

LCD_Fill(30,130,239,130+16,WHITE);

delay_ms(200);

}

LCD_ShowString(30,130,200,16,16,"NRF24L01 OK");

while(1)

{

key=KEY_Scan(0);

if(key==KEY0_PRES)

{

mode=0; break;

}else if(key==KEY1_PRES)

{

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(30,150,200,16,16,"NRF24L01 RX_Mode");

LCD_ShowString(30,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,lcddev.width-1,32,16,tmp_buf);

}else delay_us(100);

t++;

if(t==10000)//大约 1s 钟改变一次状态

{

t=0;LED0=!LED0;

}

};

}else//TX 模式

{

LCD_ShowString(30,150,200,16,16,"NRF24L01 TX_Mode");

NRF24L01_TX_Mode();

mode=' ';//从空格键开始

while(1)

{

if(NRF24L01_TxPacket(tmp_buf)==TX_OK)

{

LCD_ShowString(30,170,239,32,16,"Sended DATA:");

LCD_ShowString(0,190,lcddev.width-1,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_Fill(0,170,lcddev.width,170+16*3,WHITE);//清空显示

LCD_ShowString(30,170,lcddev.width-1,32,16,"Send Failed ");

};

LED0=!LED0;delay_ms(1500);

};

}

}

以上代码,我们就实现了 38.2 节所介绍的功能,程序运行时先通过 NRF24L01_Check 函

数检测 NRF24L01 是否存在,如果存在,则让用户选择发送模式(KEY1)还是接收模式(KEY0),

在确定模式之后,设置 NRF24L01 的工作模式,然后执行相应的数据发送/接收处理。

至此,我们整个实验的软件设计就完成了。

38.4 下载验证

在代码编译成功之后,我们通过下载代码到 ALIENTEK 探索者 STM32F4 开发板上,可以

看到 LCD 显示如图 38.4.1 所示的内容(假定 NRF24L01 模块已经接上开发板):

cca87edd75d25bea39ffff3f7064357a.png
图 38.4.1 选择工作模式界面

通过 KEY0 和 KEY1 来选择 NRF24L01 模块所要进入的工作模式,我们两个开发板一个选

择发送,一个选择接收就可以了。设置好后通信界面如图 38.4.2 和图 38.4.3 所示:

8ef0dce04ad5d279b0ec6d5b30c72e9f.png
图 38.4.2 开发板 A 发送数据

ab9e33439d66186ad3230056bcd714b4.png
图 38.4.3 开发板 B 接收数据

图 38.4.2 来自开发板 A,工作在发送模式。图 38.4.3 来自开发板 B,工作在接收模式,A

发送,B 接收。可以看到收发数据是一致的,说明实验成功。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值