NXP JN5169使用硬件SPI读写NRF24L01



一、SPI 介绍

        SPI 总线允许 JN5169 与外围设备之间进行高速同步数据传输。 JN5169 在 SPI 总线上作为主设备运行,并且在 JN5169 CPU 的控制下,连接到 SPI 总线的所有其他设备都应该是从设备。 SPI 总线包括以下功能:

  • 全双工 3 线同步数据传输
  • 可编程传输速率(最高16 Mbit / s)
  • 可编程数据传输长度,最大32位
  • 标准SPI总线模式0、1、2和3
  • 手动或自动从机选择生成(最多3个从机)
  • 可屏蔽事务完成中断
  • LSB 优先或 MSB 优先数据传输
  • 支持延迟的读取边缘

SPI总线框图:

在这里插入图片描述


        SPI 总线采用简单的移位寄存器数据传输方案。 数据以先进先出的方式移出和移入有源设备,从而允许 SPI 总线设备同时发送和接收数据。 主机从机输入或主机从机输出数据传输与 JN5169 生成的时钟信号 SPICLK 有关。
        JN5169 提供 3 个从机选择 SPISEL0 至 SPISEL2,以允许总线上的三个 SPI 总线外设。 在 DI019 上访问 SPISEL0。 根据配置,在 DIO0 或 DIO14 上访问 SPISEL1。 在 DIO1 或 DIO15 上访问SPISEL2。 这是在软件控制下启用的。 下表详细说明了根据配置,哪些 DIO 用于 SPISEL 信号。

在这里插入图片描述


        该接口可以在没有软件干预的情况下从 1 位传输到 32 位,并且可以在需要时在传输之间保持从选择线有效,从而可以执行更长的传输。
        当器件复位有效时,所有 SPI 总线主控引脚均配置为输入,且其上拉电阻处于活动状态。 引脚保持此状态,直到使能 SPI 总线主模块或将引脚配置为其他用途为止。

典型的 JN5169 SPI 总线外设连接

在这里插入图片描述


        SPI 总线上的数据传输速率由 SPICLK 信号确定。 JN5169 支持以时钟分频器选择的 16 MHz 至 125 kHz 的可选数据速率进行传输。 SPICLK 时钟相位和极性均可配置。 时钟相位确定 JN5169 使用 SPICLK 的哪个边沿在 SPIMOSI 线上显示新数据。 另一边将用于从 SPIMISO 线读取数据。 该接口应针对要访问的 SPI 总线从机进行适当配置。

时钟极性相位SPI 模式描述
000空闲时 SPICLK 为低电平-第一个边沿为正。 有效数据在第一个时钟之前在 SPIMOSI 上输出,并在每个负沿改变。 SPIMISO 在每个上升沿进行采样。
011空闲时 SPICLK 为低电平–第一个边沿为正。 有效数据在 SPIMOSI 的每个上升沿输出。 对 SPIMISO 的每个下降沿进行采样。
102空闲时 SPICLK 为高电平–第一个边沿为负。 有效数据在第一个时钟沿之前在 SPIMOSI 上输出,并在每个上升沿更改。 对 SPIMISO 的每个下降沿进行采样。
113空闲时 SPICLK 为高电平–第一个边沿为负。 有效数据在 SPIMOSI 的每个下降沿输出。 SPIMISO 在每个上升沿进行采样。

        如果系统中要使用多条 SPISEL 线,则必须以数字顺序从 SPISEL0 开始使用它们。 如果需要,可以在事务之间自动将 SPISEL 线置为无效,或者可以在多个事务中保持置位。 对于诸如存储器这样的设备,主设备可以通过不断提供 SPICLK 转换来接收大量数据,选择线保持置位的能力是一个优势,因为它使从设备在整个传输过程中均保持使能状态。
        将从 SPI 总线设置为正确的配置开始事务,然后选择从设备。 传输开始时(1 位至 32 位),数据被放入 FIFO 数据缓冲区并被移出时钟,同时生成相应的 SPICLK 转换。 由于传输是全双工的,因此从机发送的数据位数是相同的。 可以读取此传输期间接收到的数据(1 位至 32 位)。 如果主机仅需要提供多个 SPICLK 转换以允许从机发送数据,则主机应使用伪数据执行发送。 事务完成时可以生成中断,或者可以轮询接口。
        如果从设备希望向 JN5169 发出信号,表明它有要提供的数据,则可以将其连接到可作为中断使能的 DIO 引脚之一。
        下图显示了复杂的 SPI 总线传输,它可以通过 SPI 总线主接口从闪存设备读取数据。 对于许多单独的 SPI 总线访问,从选择线必须保持低电平,因此必须使用手动从选择模式。 然后可以在传输开始时声明所需的从选择(低电平有效)。 可以使用一系列的 8 位和 32 位传输来向 Flash 设备发出命令和地址,然后读回数据。 最后,可以取消选择从属选择以结束事务。

SPI总线波形示例:使用模式0从Flash 读取

在这里插入图片描述



二、实现代码

1、轮询模式

① SPI.c

#define MSB		FALSE		//spi从最高位开始传输
#define LSB		TRUE		//spi从最低位开始传输

/**
 * SPI时钟分频系数 = 2 * u8ClockDivider
 * SPI速率 = 外设时钟 / SPI时钟分频系数 = 外设时钟 / (2 * u8ClockDivider)
 * u8ClockDivider = 外设时钟 / SPI速率 / 2
 */
#define CLOCK_FREQUENCY		16		//外设时钟16M
#define SPI_FREQUENCY		 1			//设定SPI速率1M
#define CLOCK_DIVISOR		CLOCK_FREQUENCY / SPI_FREQUENCY	/ 2	//计算当前SPI速率的时钟分频系数

PUBLIC void vSPI_Init()
{
	vAHI_SpiConfigure(1,		//从机数量1
						MSB,	//高位传输
						FALSE,	//SPI模式0
						FALSE,
						CLOCK_DIVISOR,		//1M
						FALSE,	//禁止SPI中断
						FALSE);	//禁止自动选择从机
}

//SPI传输接收1个字节
PUBLIC uint8 vSPI_Transfer_1Byte(uint8 dat)
{
    uint8 ReceiveData = 0;

    while(bAHI_SpiPollBusy());				/* 等待总线空闲                 */
    vAHI_SpiStartTransfer(7, dat);			/* 发送7+1=8位数据             */
    while(bAHI_SpiPollBusy());				/* 等待数据发送完毕             */
    ReceiveData = u8AHI_SpiReadTransfer8();	/* 读8位数据                   */

    return (ReceiveData); 					/* 返回接收到的数据             */
}

② NRF24L01.c

#define CE			1 << 1
#define IRQ			1 << 2

uint8 TX_ADDRESS[TX_ADR_WIDTH] = { 0x04, 0x03, 0x02, 0x01, 0x00 }; //本地地址
uint8 RX_ADDRESS[RX_ADR_WIDTH] = { 0x04, 0x03, 0x02, 0x01, 0x00 }; //接收地址

void vCbSysCtrl (uint32 u32Device, uint32 u32ItemBitmap)
{
    if(E_AHI_DEVICE_SYSCTRL == u32Device){
    	if(E_AHI_DIO2_INT == u32ItemBitmap){	//IRQ中断
    		vPrintf("NRF IRQ Interrupt!\n");
    	}
    }
}

void vDIO_Init(void) {
	vAHI_DioSetPullup(CE | IRQ, 0);
	vAHI_DioSetDirection(IRQ, CE);

	vAHI_DioInterruptEdge(0, IRQ);     // 下降沿触发
	vAHI_DioInterruptEnable(IRQ, 0);
	vAHI_SysCtrlRegisterCallback(vCbSysCtrl);//IRQ中断
}

//SPI写寄存器
//reg:指定寄存器地址
//value:写入的值
uint8 vSPI_RW_Reg(uint8 reg, uint8 value) {
	uint8 status;
	vAHI_SpiSelect(1);		//选择从机1
	status = vSPI_Transfer_1Byte(reg); //返回从MISO读出的数据,status应为上次向该寄存器内写的value
	vSPI_Transfer_1Byte(value);        //写入寄存器的值
	vAHI_SpiSelect(0);		//释放从机1
	return (status);       // 返回状态值
}

//读取SPI寄存器值
//reg:要读的寄存器
uint8 vSPI_Read(uint8 reg) {
	uint8 reg_val;
	vAHI_SpiSelect(1);		//选择从机1
	vSPI_Transfer_1Byte(reg);         // 发送寄存器号
	reg_val = vSPI_Transfer_1Byte(NOP); // 读取寄存器内容
	vAHI_SpiSelect(0);		//释放从机1
	return (reg_val);     // 返回状态值
}

//在指定位置写指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//bytes:数据长度
//返回值,此次读到的状态寄存器值
uint8 vSPI_Write_Buf(uint8 reg, uint8 *pBuf, uint8 bytes) {
	uint8 status, byte_ctr;
	vAHI_SpiSelect(1);		//选择从机1
	status = vSPI_Transfer_1Byte(reg);		// 发送寄存器值(位置),并读取状态值

	//Delay10us();
	for (byte_ctr = 0; byte_ctr < bytes; byte_ctr++) { // 写入数据
		vSPI_Transfer_1Byte(*pBuf++);
	}

	vAHI_SpiSelect(0);		//释放从机1
	return (status); // 返回读到的状态值
}

//在指定位置读出指定长度的数据
//reg:寄存器(位置)
//*pBuf:数据指针
//bytes:数据长度
//返回值,此次读到的状态寄存器值
uint8 vSPI_Read_Buf(uint8 reg, uint8 *pBuf, uint8 bytes) {
	uint8 status, byte_ctr;
	vAHI_SpiSelect(1);		//选择从机1
	status = vSPI_Transfer_1Byte(reg); // 发送寄存器值(位置),并读取状态值

	for (byte_ctr = 0; byte_ctr < bytes; byte_ctr++) {
		pBuf[byte_ctr] = vSPI_Transfer_1Byte(NOP); // 读出数据
	}

	vAHI_SpiSelect(0);		//释放从机1
	return (status); // 返回读到的状态值
}

//检测24L01是否存在
//返回值:0,成功;1,失败
uint8 vNRF24L01_Check(void) {
	uint8 buf[5] = { 0xA5, 0xA5, 0xA5, 0xA5, 0xA5 };
	uint8 buf1[5];
	uint8 i;

	vAHI_DioSetOutput(0, CE);
	vSPI_Write_Buf(NRF_WRITE_REG + TX_ADDR, buf, 5);
	vSPI_Read_Buf(TX_ADDR, buf1, 5); //读出写入的地址
	vAHI_DioSetOutput(CE, 0);

	for (i = 0; i < 5; i++)
		if (buf1[i] != 0xA5)
			break;

	if (i != 5) {
		vPrintf("NFR24L01 Device is Connected fail!\r\n");
		return (1); //检测24L01错误
	} else {
		vPrintf("NFR24L01 Device is Connected OK!\r\n");
		return (0);	//检测到24L01
	}

}

//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入TX模式,并可以发送数据了
void vTX_Mode(void) {
	vAHI_DioSetOutput(0, CE);
	vSPI_Write_Buf(NRF_WRITE_REG + TX_ADDR, TX_ADDRESS, TX_ADR_WIDTH); //写TX节点的地址
	vSPI_Write_Buf(NRF_WRITE_REG + RX_ADDR_P0, TX_ADDRESS, RX_ADR_WIDTH); //写RX节点的地址
	vSPI_RW_Reg(NRF_WRITE_REG + EN_AA, 0x01); //使能自动应答
	vSPI_RW_Reg(NRF_WRITE_REG + EN_RXADDR, 0x01); //接收数据通道0允许
	vSPI_RW_Reg(NRF_WRITE_REG + SETUP_RETR, 0x1b); // 自动重发延时500us + 86us,自动重发10次
	vSPI_RW_Reg(NRF_WRITE_REG + RF_CH, 40); //设置工作通道频率
	vSPI_RW_Reg(NRF_WRITE_REG + RF_SETUP, 0x07); // 设置发射速率2MHz,发射功率为最大值0dB
	vSPI_RW_Reg(NRF_WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度
	vSPI_RW_Reg(NRF_WRITE_REG + CONFIG, 0x0e); CRC使能,16位校验,上电,发送模式
	vSPI_RW_Reg(FLUSH_RX, NOP);
	vAHI_DioSetOutput(CE, 0);
	vAHI_DelayXus(10);
}

③ NRF24L01.h

#include <jendefs.h>

void vDIO_Init(void);
//检测24L01是否存在
//返回值:0,成功;1,失败
uint8 vNRF24L01_Check(void);
//读取SPI寄存器值
//reg:要读的寄存器
uint8 vSPI_Read(uint8 reg);
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入TX模式,并可以发送数据了
void vTX_Mode(void);

#define TX_ADR_WIDTH 5 		// 5 uints TX address width
#define RX_ADR_WIDTH 5 		// 5 uints RX address width
#define TX_PLOAD_WIDTH 32   // 32 uints TX payload
#define RX_PLOAD_WIDTH 32   // 32 uints TX payload

#ifndef NOP
	#define NOP 0xFF
#endif
/*******************命令寄存器***************************/
#define NRF_READ_REG    0x00       // Define read command to register
#define NRF_WRITE_REG   0x20      // Define write command to register
#define RD_RX_PLOAD     0x61    // Define RX payload register address
#define WR_TX_PLOAD     0xA0    // Define TX payload register address
#define FLUSH_TX        0xE1       // Define flush TX register command
#define FLUSH_RX        0xE2       // Define flush RX register command
#define REUSE_TX_PL     0xE3    // Define reuse TX payload register command
#define NOP             0xFF            // Define No Operation, might be used to read status register
/******************寄存器地址****************************/
#define CONFIG          0x00         // 'Config' register addres
#define EN_AA           0x01          // 'Enable Auto Acknowledgment' register address
#define EN_RXADDR       0x02      // 'Enabled RX addresses' register address
#define SETUP_AW        0x03       // 'Setup address width' register address
#define SETUP_RETR      0x04     // 'Setup Auto. Retrans' register address
#define RF_CH           0x05          // 'RF channel' register address
#define RF_SETUP        0x06       // 'RF setup' register address
#define STATUS          0x07         // 'Status' register address
#define OBSERVE_TX      0x08     // 'Observe TX' register address
#define CD              0x09             // 'Carrier Detect' register address
#define RX_ADDR_P0      0x0A     // 'RX address pipe0' register address
#define RX_ADDR_P1      0x0B     // 'RX address pipe1' register address
#define RX_ADDR_P2      0x0C     // 'RX address pipe2' register address
#define RX_ADDR_P3      0x0D     // 'RX address pipe3' register address
#define RX_ADDR_P4      0x0E     // 'RX address pipe4' register address
#define RX_ADDR_P5      0x0F     // 'RX address pipe5' register address
#define TX_ADDR         0x10        // 'TX address' register address
#define RX_PW_P0        0x11       // 'RX payload width, pipe0' register address
#define RX_PW_P1        0x12       // 'RX payload width, pipe1' register address
#define RX_PW_P2        0x13       // 'RX payload width, pipe2' register address
#define RX_PW_P3        0x14       // 'RX payload width, pipe3' register address
#define RX_PW_P4        0x15       // 'RX payload width, pipe4' register address
#define RX_PW_P5        0x16       // 'RX payload width, pipe5' register address
#define FIFO_STATUS     0x17    // 'FIFO Status Register' register address

④ uart.c

串口格式化输出:串口格式化输出
串口代码:UART0 2线模式发送数据

⑤ Main.c

PUBLIC void AppColdStart(void) {
	uint8 sta;

	/*等待系统时钟切换为外部32MHz晶振*/
	while (bAHI_GetClkSource() == TRUE);
	/*优化闪存等待状态*/
	vAHI_OptimiseWaitStates();

	vAHI_WatchdogStop();
	(void) u32AHI_Init();

	vSPI_Init();
	vUartInit();
	vDIO_Init();

	vAHI_DelayXms(500);

	vPrintf("System init!\n");
	vNRF24L01_Check();
	vTX_Mode();

	while (1) {
		vPrintf("\r\n\r\n");
		vAHI_DelayXms(2000);

		sta = vSPI_Read(CONFIG);
		vPrintf("NRF CONFIG REG: %x \n", sta);
		sta = vSPI_Read(EN_AA);
		vPrintf("NRF EN_AA REG: %x \n", sta);
		sta = vSPI_Read(EN_RXADDR);
		vPrintf("NRF EN_RXADDR REG: %x \n", sta);
		sta = vSPI_Read(SETUP_RETR);
		vPrintf("NRF SETUP_RETR REG: %x \n", sta);
		sta = vSPI_Read(RF_CH);
		vPrintf("NRF RF_CH REG: %x \n", sta);
		sta = vSPI_Read(RF_SETUP);
		vPrintf("NRF RF_SETUP REG: %x \n", sta);
	}
}

PUBLIC void AppWarmStart(void) {
	AppColdStart();
}

⑥ 效果图

在这里插入图片描述



2、中断模式

① SPI.c

#define MSB		FALSE		//spi从最高位开始传输
#define LSB		TRUE		//spi从最低位开始传输

/**
 * SPI时钟分频系数 = 2 * u8ClockDivider
 * SPI速率 = 外设时钟 / SPI时钟分频系数 = 外设时钟 / (2 * u8ClockDivider)
 * u8ClockDivider = 外设时钟 / SPI速率 / 2
 */
#define CLOCK_FREQUENCY		16		//外设时钟16M
#define SPI_FREQUENCY		 1			//设定SPI速率1M
#define CLOCK_DIVISOR		CLOCK_FREQUENCY / SPI_FREQUENCY	/ 2	//计算当前SPI速率的时钟分频系数

bool flag;					//数据传输状态

void vCbSpiMasterInt(uint32 u32Device, uint32 u32ItemBitmap)
{
    if(E_AHI_DEVICE_SPIM == u32Device){			//SPI主机中断
    	if(E_AHI_SPIM_TX_MASK == u32ItemBitmap){	//传输完成
    		flag = FALSE;
    	}
    }
}

PUBLIC void vSPI_Init()
{
	vAHI_SpiConfigure(1,		//从机数量1
						MSB,	//高位传输
						FALSE,	//SPI模式0
						FALSE,
						CLOCK_DIVISOR,		//1M
						TRUE,	//开启SPI中断
						FALSE);	//禁止自动选择从机
	vAHI_SpiRegisterCallback(vCbSpiMasterInt);
	flag = TRUE;			//初始化数据传输状态
}


//SPI传输接收1个字节
PUBLIC uint8 vSPI_Transfer_1Byte(uint8 dat)
{
	uint8 ReceiveData = 0;		//SPI接收的数据
    flag = TRUE;
    vAHI_SpiStartTransfer(7, dat);			/* 发送7+1=8位数据             */
    vAHI_DelayXus(10);			//必须加延时,等待中断处理完成
    while(flag);				/* 等待数据发送完毕             */
    ReceiveData = u8AHI_SpiReadTransfer8();	/* 读8位数据                   */
    return (ReceiveData); 					/* 返回接收到的数据             */
}

滴答定时器延时代码:x us延时程序实现

其他部分代码和轮询模式一致!

② 效果图

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝蚊鸭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值