SPI时序--51单片机与NRF24L01通信

前面学习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”),修改后就好了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值