前面学习NRF24L01出现了一些问题,寻求了大家帮助,现在我学习完了所以把代码分享出来让还在学习的道友少走弯路,有点问题,就是我有点懒,所以我只分享代码和可能遇到的问题,spi和nrf24l01的时序和操作步骤请自行对照时序图和数据手册。
通信第一步:SPI时序
nrf和单片机通信通过spi连接,而51没有spi接口,因此只能模拟spi时序,spi有四种通信方式,我使用的是之前自己编写的模拟时序,简单配置后四种方式都可以使用(这只是理论上,因为实际我只测试了方式0,大家测试如果都可以使用的话告诉我一声)下面是代码:
SPI.c文件
#include "SPI.h"
bit CPOL;
bit CPHA;
bit GD;
static void Delay10us() //@12MHz
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
void SPI_init(unsigned char MS_1,bit MS_2){ //MS_1为模式选择(0,1,2,3),MS_2为高低位选择(0:低位在前,1:高位在前)
switch(MS_1){
case 0:
CPOL = 0;
CPHA = 0;
break;
case 1:
CPOL = 0;
CPHA = 1;
break;
case 2:
CPOL = 1;
CPHA = 0;
break;
case 3:
CPOL = 1;
CPHA = 1;
break;
}
GD = MS_2;
SCLK = CPOL;
// CS = 1;
MOSI = 1;
MISO = 1;
}
unsigned char spi_RW(unsigned char spi_data){
unsigned char tval=0x00,i;
// CS = 0;
for(i=0;i<8;i++)
{
if(CPHA)
{
Delay10us();
SCLK = ~CPOL;
if(GD)
MOSI = 0x80&spi_data;
else
MOSI = 0x01&spi_data;
Delay10us();
Delay10us();
SCLK = CPOL;
if(GD)
{
tval <<= 1;
if(MISO)
tval |= 0x01;
spi_data <<= 1;
}
else
{
tval >>= 1;
if(MISO)
tval |= 0x80;
spi_data >>= 1;
}
Delay10us();
}
else
{
if(GD)
MOSI = 0x80&spi_data;
else
MOSI = 0x01&spi_data;
Delay10us();
SCLK = ~CPOL;
if(GD)
{
tval <<= 1;
if(MISO)
tval |= 0x01;
spi_data <<= 1;
}
else
{
tval >>= 1;
if(MISO)
tval |= 0x80;
spi_data >>= 1;
}
Delay10us();
Delay10us();
SCLK = CPOL;
Delay10us();
}
}
// CS = 1;
return tval;
}
SPI.h文件
#ifndef ___S____P___I___H__
#define ___S____P___I___H__
#include "STC.h"
#include <intrins.h>
sbit SCLK = P3^6;
sbit MOSI = P2^2;
sbit MISO = P2^0;
//sbit CS = P1^3;
void SPI_init(unsigned char MS_1,bit MS_2); //MS_1为模式选择(0,1,2,3),MS_2为高低位选择(0:低位在前,1:高位在前)
unsigned char spi_RW(unsigned char spi_data); //发送接收函数
#endif
代码说明:此函数不包含片选,但是预留了位置,想用的去掉注释就可以使用(不建议这样,因为有的连续写数据应该不允许CS中途变化);
函数说明
SPI_init函数为初始化函数,参数1为工作方式,参数2为高位在前还是低位在前,如果你感觉写0 1不直观可以加个宏定义,或者修改MS_2为unsigned char类型,再把c文件的初始化函数的GD=MS_2改为(比方使用字符M和L来表示高位和低位)if(MS_2 == 'M')GD = 1;else GD=0;即可(反正我是懒得改,毕竟有注释,如果这样还搞错那还不如找豆腐撞死算了)
spi_RW为SPI读写函数,写一字节同时可以接收一字节,参数为写字节,返回值为接收到的字节(感觉别扭的自己把spi改为大写,我是以前写的spi时序自己忘了用的大写,nrf用的小写因为多觉得麻烦就把这个改为小写了)
第二步配置nrf24l01
直接上代码:
nrf24l01.c
#include "nrf24l01.h"
#define NRF_WRITE_REG 0x20
#define RX_ADDR_P0 0x0a
#define RX_ADDR_P1 0x0b
#define RX_ADDR_P2 0x0c
#define RX_ADDR_P3 0x0d
#define RX_ADDR_P4 0x0e
#define RX_ADDR_P5 0x0f
#define TX_ADDR 0x10
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
unsigned char TR_D_NUM;
unsigned char TRX_addr1[]={0xc3,0xe4,0x6a,0xde,0xc6};
static void Delay10us() //@12MHz
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
void NRF_init(unsigned char datn){ //spi总线相关设置参数为传输有效数据宽度,传输数据字节数范围1~32字节
SPI_init(0,1); //SPI工作方式0,高位在前
TR_D_NUM = datn;
nrf_cs = 1;
nrf_ce = 0;
}
unsigned char nrf_w_byte(unsigned char commd,unsigned char dat){
unsigned char val1;
commd += NRF_WRITE_REG;
nrf_cs = 0;
val1 = spi_RW(commd);
spi_RW(dat);
nrf_cs = 1;
Delay10us();
return val1;
}
unsigned char nrf_r_byte(unsigned char commd,unsigned char dat){
unsigned char val1;
nrf_cs = 0;
spi_RW(commd);
val1 = spi_RW(dat);
nrf_cs = 1;
Delay10us();
return val1;
}
unsigned char nrf_w_str(unsigned char commd,unsigned char *ndat,unsigned char nnum){//地址命令,数据数组,数据字节
unsigned char i,val;
nrf_cs = 0;
val = spi_RW(commd);
for(i=0;i<nnum;i++){
spi_RW(ndat[i]);
}
nrf_cs = 1;
Delay10us();
return val;
}
unsigned char nrf_r_str(unsigned char commd,unsigned char *ndat,unsigned char nnum){
unsigned char i,val;
nrf_cs = 0;
val = spi_RW(commd);
for(i=0;i<nnum;i++){
ndat[i] = spi_RW(0xff);
}
nrf_cs = 1;
Delay10us();
return val;
}
/* 数据处理, 参数为状态寄存器值, 处理完成后参数地址保存的为接收通道值 返回值为中断标志位:
*0无中断
*1重发超时中断
*2数据发送完成中断
*3超时+发送完成
*4接收中断
*5接收+超时
*6接收+发送
*7接收+超时+发送
*/
unsigned char NRF_pro(unsigned char *RX_NO){
unsigned char val1;
val1 = *RX_NO;
*RX_NO &= 0x0f;
*RX_NO >>= 1;
val1 >>= 4;
return val1;
}
unsigned char NRF_status(){//读取状态寄存器
unsigned char val1;
nrf_cs = 0;
val1 = spi_RW(0xff);
nrf_cs = 1;
Delay10us();
return val1;
}
void NRF_clear(unsigned char mod_reg){ //FIFO寄存器清除操作参数mod_reg为T表示发送寄存器清0,R为接受寄存器清0
switch(mod_reg){
case 'T':
nrf_cs = 0;
spi_RW(0xe1);
// spi_RW(0xff);
nrf_cs = 1;
Delay10us();
break;
case 'R':
nrf_cs = 0;
spi_RW(0xe2);
// spi_RW(0xff);
nrf_cs = 1;
Delay10us();
break;
}
}
unsigned char NRF_send(unsigned char *dat){ //返回值为T为成功,C为超时,F为失败
unsigned char j,stat;
nrf_ce = 0;
nrf_w_str(0xa0,dat,TR_D_NUM);
nrf_ce = 1;
Delay10us();
Delay10us();
nrf_ce = 0;
while(nrf_irq);
NRF_clear('T');
stat = NRF_status();
nrf_w_byte(0x07,stat);//status
j=NRF_pro(&stat);
switch(j){
case 1: return 'C';
case 2: return 'T';
default: return 'F';
}
}
unsigned char NRF_accept(unsigned char *dat){ //接收函数,参数为接收数组,返回值为接收通道,没有接收返回0xff
unsigned char adat;
adat = NRF_status();
nrf_w_byte(0x07,adat);//status
if(adat&0x40){
nrf_r_str(0x61,dat,TR_D_NUM);
NRF_clear('R');
NRF_pro(&adat);
return adat;
}
return 0xff;
}
void NRF_TX_MODE(){//发送端设置
nrf_w_str(TX_ADDR+NRF_WRITE_REG,TRX_addr1,5);//tx_addr设置发送地址
nrf_w_str(RX_ADDR_P0+NRF_WRITE_REG,TRX_addr1,5);//rx_addr_p0 设置接收ACK响应地址
nrf_w_byte(0x01,0x01);//en_aa
nrf_w_byte(0x02,0x01);//en_rx_addr
nrf_w_byte(0x04,0x0a);//setup_retr
nrf_w_byte(0x05,0x3a);//rf_ch
nrf_w_byte(0x03,0x03);//setup_aw
NRF_clear('T');
nrf_w_byte(RX_PW_P0,TR_D_NUM);//rx_pw_p0设置接收有效数据宽度
nrf_w_byte(0x06,0x0f);//rf_setup
nrf_w_byte(0x07,0x7e);//status
nrf_w_byte(0x00,0x0a);//config
}
void NRF_RX_MODE(){//接收端设置
nrf_w_byte(0x01,0x02);//en_aa
nrf_w_byte(0x02,0x02);//en_rx_addr
nrf_w_byte(0x03,0x03);//setup_aw
nrf_w_byte(0x04,0x0a);//setup_retr
nrf_w_byte(0x05,0x3a);//rf_ch
nrf_w_byte(0x06,0x0f);//rf_setup
nrf_w_byte(0x07,0x7e);//status
nrf_w_str(RX_ADDR_P1+NRF_WRITE_REG,TRX_addr1,5);//rx_addr_p1 设置接收地址
nrf_w_byte(RX_PW_P1,TR_D_NUM);//rx_pw_p1 设置接收有效数据宽度
NRF_clear('R');
nrf_w_byte(0x00,0x0b);//config
nrf_ce = 1;
}
nrf24l01.h
#ifndef __N_R_F_24_L__01_H_
#define __N_R_F_24_L__01_H_
#include "STC.h"
#include "SPI.h"
#include <intrins.h>
sbit nrf_cs = P3^7;
sbit nrf_ce = P1^0;
sbit nrf_irq = P3^3;
void NRF_init(unsigned char datn); //spi总线相关设置参数为传输有效数据宽度范围:1~32字节
unsigned char NRF_accept(unsigned char *dat); //接收函数,参数为接收数组,返回值为接收通道,没有接收返回0xff
unsigned char NRF_send(unsigned char *dat); //返回值为T为成功,C为超时,F为其他原因失败
unsigned char NRF_status();//读取状态寄存器
void NRF_TX_MODE();//发送端设置
void NRF_RX_MODE();//接收端设置
#endif
函数说明:各函数说明注释有,如果只用于简单一对一通信可以直接调用,我简单说一下h文件中函数用法,NRF_init和下面的NRF_T(R)X_MODE是初始化函数(我宏定义用的少,所以要配置可以根据手册修改),在主循环之外调用,NRF_status为NRF状态寄存器读取函数用于判断NRF状态,重点说下面这两个:
NRF_send为发送函数(注意:返回值为状态寄存器高四位,因最高位始终为0,所以返回值总共有7种可能,我只是选了其中的超时和发送成功标志,如果用于多机通信可能全部都要用到,当然,觉得花里胡哨的可以删除掉直接返回状态寄存器值)
NRF_accept为接收函数(注意:返回值为接收通道,如果没有接收到数据返回0xff,当然,觉得花里胡哨的依旧可以删除掉直接返回状态寄存器值 ,还有更重要的,因为此函数并没有用到NRF_24L01的引脚IRQ,所以此函数可以放到主函数轮询是否有数据接收,除此之外也可以将IRQ引脚连接在外部中断上,然后将此函数放在中断函数中调用也可以)
以上就是所有代码,然后说说我调试过程中遇到的问题
1.坑我最深的当属电路,买的开发板带无线接口,电路原理图上也有对应电路图,结果就是不通,搞得我都怀疑人生了,用万用表测量结果电源电压只有1v,我以为只是电源问题外接了3.3v电压,结果还是不行,又头疼半天,然后用通断档测量发现引脚之间都不相同,后面用杜邦线才调试成功。
2.通信成功后我调试了发送和接收函数,发现传输乱码,后面加上串口发送状态寄存器值发现值是 1E,0E,0E,0F...查阅手册也没查出为什么会出现1E,所以初始化我为状态寄存器加上清零操作,根据状态寄存器值我发现了乱码原因,就是后面的0F,因为我发送数据后没清除寄存器导致寄存器溢出乱码(现象是所有字节都变成了最后接收到的字节,就是我传输的是数组"ijklmnop",结果1602显示的是“pppppppp”),修改后就好了