STM32CubeMX系列08——SPI通信(W25Q64、NRF24L01无线模块)

article/2025/8/22 1:42:49

文章目录

  • 1. 准备工作
    • 1.1. 所用硬件
    • 1.2. SPI 简介
    • 1.3. 生成工程
      • 1.3.1. 创建工程选择主控
      • 1.3.2. 系统配置
      • 1.3.3. 配置工程目录
  • 2. 读写EEPROM实验(W25Q64)
    • 2.1. W25Q64 简介
    • 2.2. 代码实现
  • 3. NRF24L01无线模块通信
    • 3.1. 模块简介
    • 3.2. SPI 配置
      • 3.2.1. SPI1 配置
      • 3.2.2. SPI2 配置
    • 3.3. 代码实现
      • 3.3.1. 添加驱动代码
      • 3.3.2. 驱动修改
    • 3.4. 主函数
    • 3.5. 测试

====>>> 文章汇总(有代码汇总) <<<====

1. 准备工作

1.1. 所用硬件

读写EEPROM实验(W25Q64):正点原子Mini开发板,主控STM32F103RCT6

通信实验:再加一个普中的,主控STM32F103ZET6。

1.2. SPI 简介

SPI(Serial Peripheral interface) 串行外围设备接口

  1. 由 Motorola公司开发
  2. 高速的,全双工,同步的通信总线
  3. 需要四根线
  4. 时钟最多可以到 18Mhz

SPI 接口一般使用 4 条线通信:

  • MISO 主设备数据输入,从设备数据输出
  • MOSI 主设备数据输出,从设备数据输入
  • SCLK 时钟信号,由主设备产生
  • CS 从设备片选信号,由主设备控制

SPI 也可以有一对多的情况,根据CS片选信号选择是对哪个从机发送或者接收数据。
在这里插入图片描述

1.3. 生成工程

1.3.1. 创建工程选择主控

在这里插入图片描述

1.3.2. 系统配置

配置时钟源
在这里插入图片描述
配置debug模式(如果需要ST-Link下载及调试可以勾选)
在这里插入图片描述
配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)
在这里插入图片描述

1.3.3. 配置工程目录

在这里插入图片描述
在这里插入图片描述

2. 读写EEPROM实验(W25Q64)

EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。

2.1. W25Q64 简介

原理图
在这里插入图片描述
芯片引脚说明:

  1. CS 片选引脚。低电平表示选中。
  2. DO SPI数据输出接口
  3. WP 硬件写保护引脚,输入高电平可以正常写入数据,输入低电平禁止写入。
  4. GND 公共地
  5. DI SPI数据输入接口
  6. CLK SPI时钟接口
  7. HOLD 状态保存接口,输入低电平禁止操作芯片,输入高电平可正常操作芯片。
  8. VCC 电源接口,2.7-3.6电源

存储说明
W25Q64,其中64表示芯片的存储容量是64M bit,也就是 8M 字节(B)

  1. 整个芯片 8M字节 被分为 128个块,每个块 64kb;
  2. 每个块 64k字节 被分为 16个扇区,每个扇区 4K字节(4096字节) ;
  3. 每个扇区 4K字节 被分为 16个页,每个页 256字节。

2.2. 代码实现

PA2为片选信号,设置其为推挽输出即可。
在这里插入图片描述
SPI 配置
在这里插入图片描述

分频系数为4,因为 SPI 时钟最多可以到 18Mhz,而这里时钟是72Mhz,经过四分频之后刚好是18Mhz。

串口重定向也配置一下,方便观察–>串口重定向配置<–

这部分代码很多,因此,生成工程后。

  1. 在工程文件夹中单独创建一个icode文件,用来存放我们自己的代码。
  2. 在icode文件夹下,再创建一个W25Q64文件夹,存放W25Q64相关代码。
  3. 在W25Q64文件夹中创建w25qxx.cw25qxx.h两个文件。
    在这里插入图片描述

添加源文件和头文件路径

在这里插入图片描述
在这里插入图片描述

编写代码如下:
w25qxx.c

/** spi1.c**  Created on: Oct 29, 2022*      Author: Haozi**  使用的芯片为:W25Q64**  芯片容量及地址说明:总容量(8M字节)*  	单位			大小			比例			数量*  	 页		  	  256字节		  最小单位*  	扇区	  4K字节(4096字节)		16页			2048个*  	 块			  64K字节		  16个扇区			128个**  芯片引脚说明:*  	1. CS   片选引脚。低电平表示选中。*      2. DO   SPI数据输出接口*      3. WP   硬件写保护引脚,输入高电平可以正常写入数据,输入低电平禁止写入。*      4. GND  公共地*      5. DI   SPI数据输入接口*      6. CLK  SPI时钟接口*      7,HOLD 状态保存接口,输入低电平禁止操作芯片,输入高电平可正常操作芯片。*      8. VCC  电源接口,2.7-3.6电源**  本例程,引脚接口。*  	1. CS   GPIO PA2.------------------- 需要操作的*  	2. DO   SPI1 MISO ------------------ 需要操作的*  	3. SP   接VCC*  	4. GND  接地*  	5. DI   SPI1 MOSI ------------------ 需要操作的*  	6. CLK  SPI1 CLK ------------------- 需要操作的*  	7,HOLD VCC3.3*      8. VCC  VCC3.3*/#include "main.h"
#include "stm32f1xx_it.h"
#include "w25qxx.h"
#include "spi.h"// 定义使用的芯片型号
uint16_t W25QXX_TYPE = W25Q64;/** @brief	CS使能控制函数** @param	a:0为低电平 表示有效* 			a:其他值为高电平 表示无效*/
void W25QXX_CS(uint8_t a)
{if(a==0)HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);elseHAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
}/** @brief	SPI1总线读写一个字节** @param	TxData:写入的字节** @return	读出的字节*/
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{uint8_t Rxdata;HAL_SPI_TransmitReceive(&hspi1, &TxData, &Rxdata, 1, 1000);return Rxdata;
}/** @brief	读取芯片ID** @note	高8位是厂商代号(本程序不判断厂商代号)、低8位是容量大小* 			0XEF13型号为W25Q80* 			0XEF14型号为W25Q16* 			0XEF15型号为W25Q32* 			0XEF16型号为W25Q64* 			0XEF17型号为W25Q128* 			0XEF18型号为W25Q256** @return	读出的字节*/
uint16_t W25QXX_ReadID(void)
{uint16_t Temp = 0;W25QXX_CS(0);SPI1_ReadWriteByte(0x90);	// 发送读取ID命令SPI1_ReadWriteByte(0x00);SPI1_ReadWriteByte(0x00);SPI1_ReadWriteByte(0x00);Temp |= SPI1_ReadWriteByte(0xFF)<<8;Temp |= SPI1_ReadWriteByte(0xFF);W25QXX_CS(1);return Temp;
}/** @brief	读取W25QXX的状态寄存器** @note	W25QXX一共有3个状态寄存器* 				状态寄存器1:BIT7 6  5  4   3   2   1   0* 							 SPR  RV TB BP2 BP1 BP0 WEL BUSY* 							 SPR:				默认0,状态寄存器保护位,配合WP使用* 							 TB,BP2,BP1,BP0:	FLASH区域写保护设置* 							 WEL:				写使能锁定* 							 BUSY:				忙标记位(1,忙;0,空闲)* 							 默认:				0x00* 				状态寄存器2:BIT7 6   5   4   3   2   1  0* 							 SUS  CMP LB3 LB2 LB1 (R) QE SRP1* 				状态寄存器3:BIT7     6    5     4   3  2    1   0* 							 HOLD/RST DRV1 DRV0 (R) (R) WPS (R) (R)** @param	regno:状态寄存器号。范:1~3** @return	状态寄存器值*/
uint8_t W25QXX_ReadSR(uint8_t regno)
{uint8_t byte = 0,command = 0;switch(regno){case 1:command = W25X_ReadStatusReg1;break;case 2:command = W25X_ReadStatusReg2;break;case 3:command = W25X_ReadStatusReg3;break;default:command = W25X_ReadStatusReg1;break;}W25QXX_CS(0);SPI1_ReadWriteByte(command);byte = SPI1_ReadWriteByte(0Xff);W25QXX_CS(1);return byte;
}/** @brief	写W25QXX状态寄存器** @note	W25QXX一共有3个状态寄存器* 				状态寄存器1:BIT7 6  5  4   3   2   1   0* 							 SPR  RV TB BP2 BP1 BP0 WEL BUSY* 							 SPR:				默认0,状态寄存器保护位,配合WP使用* 							 TB,BP2,BP1,BP0:	FLASH区域写保护设置* 							 WEL:				写使能锁定* 							 BUSY:				忙标记位(1,忙;0,空闲)* 							 默认:				0x00* 				状态寄存器2:BIT7 6   5   4   3   2   1  0* 							 SUS  CMP LB3 LB2 LB1 (R) QE SRP1* 				状态寄存器3:BIT7     6    5     4   3  2    1   0* 							 HOLD/RST DRV1 DRV0 (R) (R) WPS (R) (R)** @param	regno:状态寄存器号。范:1~3* @param	sr:写入的值** @return	状态寄存器值*/
void W25QXX_Write_SR(uint8_t regno, uint8_t sr)
{uint8_t command=0;switch(regno){case 1:command=W25X_WriteStatusReg1;break;case 2:command=W25X_WriteStatusReg2;break;case 3:command=W25X_WriteStatusReg3;break;default:command=W25X_WriteStatusReg1;break;}W25QXX_CS(0);SPI1_ReadWriteByte(command);SPI1_ReadWriteByte(sr);W25QXX_CS(1);
}/** @brief	W25QXX写使能 将WEL置位*/
void W25QXX_Write_Enable(void)
{W25QXX_CS(0);SPI1_ReadWriteByte(W25X_WriteEnable);W25QXX_CS(1);
}/** @brief	W25QXX写禁止 将WEL清零*/
void W25QXX_Write_Disable(void)
{W25QXX_CS(0);SPI1_ReadWriteByte(W25X_WriteDisable);W25QXX_CS(1);
}/** @brief	初始化SPI FLASH的IO口** @return	0:识别成功。1:识别失败*/
uint8_t W25QXX_Init(void)
{uint8_t temp;W25QXX_CS(1);W25QXX_TYPE = W25QXX_ReadID();// SPI FLASH为W25Q256时才用设置为4字节地址模式if(W25QXX_TYPE == W25Q256){// 读取状态寄存器3,判断地址模式temp = W25QXX_ReadSR(3);// 如果不是4字节地址模式,则进入4字节地址模式if((temp&0x01) == 0){W25QXX_CS(0);// 发送进入4字节地址模式指令SPI1_ReadWriteByte(W25X_Enable4ByteAddr);W25QXX_CS(1);}}if(W25QXX_TYPE==W25Q256||W25QXX_TYPE==W25Q128||W25QXX_TYPE==W25Q64||W25QXX_TYPE==W25Q32||W25QXX_TYPE==W25Q16||W25QXX_TYPE==W25Q80)return 0;elsereturn 1;
}/** @brief	读取SPI FLASH。** @note	在指定地址开始读取指定长度的数据。** @param	pBuffer			数据存储区* @param	ReadAddr		开始读取的地址(24bit)* @param	NumByteToRead	要读取的字节数(最大65535)**/
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{uint16_t i;W25QXX_CS(0);SPI1_ReadWriteByte(W25X_ReadData);if(W25QXX_TYPE == W25Q256){// 如果是W25Q256的话地址为4字节的,要发送最高8位SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>24));}SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16));	// 发送24bit地址SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));SPI1_ReadWriteByte((uint8_t)ReadAddr);for(i = 0; i < NumByteToRead; i++){pBuffer[i] = SPI1_ReadWriteByte(0XFF); // 循环读数}W25QXX_CS(1);
}/** @brief	等待空闲*/
void W25QXX_Wait_Busy(void)
{while((W25QXX_ReadSR(1)&0x01)==0x01);
}/** @brief	SPI在一页(0~65535)内写入少于256个字节的数据** @note	在指定地址开始写入最大256字节的数据** @param	pBuffer			数据存储区* @param	WriteAddr		开始写入的地址(24bit)* @param	NumByteToWrite	要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!**/
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint16_t i;W25QXX_Write_Enable();W25QXX_CS(0);SPI1_ReadWriteByte(W25X_PageProgram);//发送写页命令if(W25QXX_TYPE==W25Q256)//如果是W25Q256的话地址为4字节的,要发送最高8位{SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>24));}SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>16));//发送24bit地址SPI1_ReadWriteByte((uint8_t)((WriteAddr)>>8));SPI1_ReadWriteByte((uint8_t)WriteAddr);for(i = 0; i < NumByteToWrite; i++)SPI1_ReadWriteByte(pBuffer[i]);W25QXX_CS(1);W25QXX_Wait_Busy();
}/** @brief	无检验写SPI FLASH** @note	必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!* 			具有自动换页功能。在指定地址开始写入指定长度的数据,但是要确保地址不越界!** @param	pBuffer			数据存储区* @param	WriteAddr		开始写入的地址(24bit)* @param	NumByteToWrite	要写入的字节数(最大65535)**/
void W25QXX_Write_NoCheck(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint16_t pageremain;// 计算单页剩余的字节数pageremain = 256-WriteAddr%256;if(NumByteToWrite <= pageremain)pageremain = NumByteToWrite;	// 不大于256个字节while(1){W25QXX_Write_Page(pBuffer, WriteAddr, pageremain);if(NumByteToWrite == pageremain)break;else{pBuffer += pageremain;WriteAddr += pageremain;NumByteToWrite -= pageremain; // 减去已经写入了的字节数if(NumByteToWrite > 256)pageremain = 256; // 一次可以写入256个字节elsepageremain = NumByteToWrite; // 不够256个字节了}}
}/** @brief	写SPI FLASH** @note	在指定地址开始写入指定长度的数据。相比于上面的函数,该函数带擦除操作!** @param	pBuffer			数据存储区* @param	WriteAddr		开始写入的地址(24bit)* @param	NumByteToWrite	要写入的字节数(最大65535)**/
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint32_t secpos;uint16_t secoff;uint16_t secremain;uint16_t i;uint8_t* W25QXX_BUF;W25QXX_BUF = W25QXX_BUFFER;secpos = WriteAddr / 4096;	// 扇区地址secoff = WriteAddr % 4096;	// 在扇区内的偏移secremain = 4096 - secoff;	// 扇区剩余空间大小if(NumByteToWrite <= secremain)secremain = NumByteToWrite;	// 不大于4096个字节while(1){W25QXX_Read(W25QXX_BUF, secpos*4096, 4096);		// 读出整个扇区的内容for(i=0; i<secremain; i++)	// 校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;	// 需要擦除}if(i<secremain)	// 需要擦除{W25QXX_Erase_Sector(secpos);	// 擦除这个扇区for(i=0; i<secremain; i++)		// 复制{W25QXX_BUF[i+secoff] = pBuffer[i];}W25QXX_Write_NoCheck(W25QXX_BUF, secpos*4096, 4096); // 写入整个扇区}elseW25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain);	// 写已经擦除了的,直接写入扇区剩余区间.if(NumByteToWrite == secremain)break;	// 写入结束了else		// 写入未结束{secpos++;	// 扇区地址增1secoff=0;	// 偏移位置为0pBuffer += secremain; 	// 指针偏移WriteAddr += secremain;	// 写地址偏移NumByteToWrite -= secremain;	// 字节数递减if(NumByteToWrite > 4096)secremain = 4096;		// 下一个扇区还是写不完elsesecremain = NumByteToWrite;	// 下一个扇区可以写完了}}
}/** @brief	擦除整个芯片** @note	等待时间超长...**/
void W25QXX_Erase_Chip(void)
{W25QXX_Write_Enable();W25QXX_Wait_Busy();W25QXX_CS(0);SPI1_ReadWriteByte(W25X_ChipErase);W25QXX_CS(1);W25QXX_Wait_Busy();
}/** @brief	擦除一个扇区** @note	擦除一个扇区的最少时间:150ms** @param	Dst_Addr	扇区地址 根据实际容量设置**/
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{Dst_Addr *= 4096;W25QXX_Write_Enable();W25QXX_Wait_Busy();W25QXX_CS(0);SPI1_ReadWriteByte(W25X_SectorErase);if(W25QXX_TYPE == W25Q256){SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>24));}SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16));SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));SPI1_ReadWriteByte((uint8_t)Dst_Addr);W25QXX_CS(1);W25QXX_Wait_Busy();
}

w23qxx.h

/** spi1.h**  Created on: Oct 29, 2022*      Author: Haozi*/
#ifndef MYPROJECT_W25Q64_W25QXX_H_
#define MYPROJECT_W25Q64_W25QXX_H_#include "main.h"// 25系列FLASH芯片厂商与容量代号(厂商代号EF)
#define W25Q80 			0XEF13
#define W25Q16 			0XEF14
#define W25Q32 			0XEF15
#define W25Q64 			0XEF16
#define W25Q128 		0XEF17
#define W25Q256 		0XEF18
#define EX_FLASH_ADD 	0x000000 		// W25Q64的地址是24位宽
extern uint16_t W25QXX_TYPE;			// 定义W25QXX芯片型号
extern SPI_HandleTypeDef hspi1;// ********************* 指令表 ************************* //
// 写使能 与 写禁止
#define W25X_WriteEnable 			0x06
#define W25X_WriteDisable 			0x04
// 读取状态寄存器123的命令
#define W25X_ReadStatusReg1 		0x05
#define W25X_ReadStatusReg2 		0x35
#define W25X_ReadStatusReg3 		0x15
// 写状态寄存器123的命令
#define W25X_WriteStatusReg1 		0x01
#define W25X_WriteStatusReg2 		0x31
#define W25X_WriteStatusReg3 		0x11
// 读取数据指令
#define W25X_ReadData 				0x03
#define W25X_FastReadData 			0x0B
#define W25X_FastReadDual 			0x3B
#define W25X_PageProgram 			0x02
#define W25X_BlockErase 			0xD8
// 扇区擦除指令
#define W25X_SectorErase 			0x20
// 片擦除命令
#define W25X_ChipErase 			0xC7
#define W25X_PowerDown 			0xB9
#define W25X_ReleasePowerDown 	0xAB
#define W25X_DeviceID 				0xAB
#define W25X_ManufactDeviceID 	0x90
#define W25X_JedecDeviceID 		0x9F
// 进入4字节地址模式指令
#define W25X_Enable4ByteAddr 		0xB7
#define W25X_Exit4ByteAddr 		0xE9void W25QXX_CS(uint8_t a);							// W25QXX片选引脚控制
uint8_t SPI1_ReadWriteByte(uint8_t TxData);		// SPI1总线底层读写
uint16_t W25QXX_ReadID(void);						// 读取FLASH ID
uint8_t W25QXX_ReadSR(uint8_t regno);				// 读取状态寄存器
void W25QXX_Write_SR(uint8_t regno,uint8_t sr);	// 写状态寄存器
void W25QXX_Write_Enable(void);					// 写使能
void W25QXX_Write_Disable(void);					// 写保护
uint8_t W25QXX_Init(void);							// 初始化W25QXX函数
void W25QXX_Wait_Busy(void);						// 等待空闲
// 读取flash
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);
// 写入flash
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
// 擦除flash
void W25QXX_Erase_Chip(void);						// 整片擦除
void W25QXX_Erase_Sector(uint32_t Dst_Addr);		// 扇区擦除#endif /* MYPROJECT_W25Q64_W25QXX_H_ */

在主函数main.c中测试

/* USER CODE BEGIN Includes */
#include "w25qxx.h"
/* USER CODE END Includes *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);/*** @brief  The application entry point.* @retval int*/
int main(void)
{HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI1_Init();MX_USART1_UART_Init();/* USER CODE BEGIN WHILE */// 初始化W25QXX_Init();// 芯片flash大小uint32_t FLASH_SIZE = 8*1024*1024;	// FLASH 大小8M字节printf("------------- 读取芯片ID实验 --------------- \r\n");uint16_t Deceive_ID;Deceive_ID = W25QXX_ReadID();if (Deceive_ID == 0){printf("Read Deceive_ID fail \r\n");} else {printf("Deceive_ID is %X \r\n", Deceive_ID); // 显示芯片ID}printf("------------- 读写 字节实验 --------------- \r\n");uint8_t string[] = {"HAOZI TEST"};uint8_t getStringBuf[sizeof(string)] = {"&&&&&&&&&&"};		// 初始值W25QXX_Write(string, 0, sizeof(string));W25QXX_Read(getStringBuf, 0, sizeof(string));if (getStringBuf[0] == '&'){printf("Read string fail \r\n");} else {printf("Read string is: %s \r\n", getStringBuf);}printf("------------- 读写 浮点数实验 --------------- \r\n");// 浮点数 读写测试union float_union{float float_num;			// 浮点数占4个字节double double_num;			// 双精度浮点数占8个字节uint8_t buf[8];				// 定义 8个字节 的空间};union float_union write_float_data;	// 用来写union float_union read_float_data;	// 用来读// 先测试第一个 浮点数write_float_data.float_num = 3.1415f;read_float_data.float_num = 0;W25QXX_Write(write_float_data.buf, 20, 4);W25QXX_Read(read_float_data.buf, 20, 4);if(read_float_data.float_num == 0){printf("Read float fail \r\n");} else {printf("Read float data is %f \r\n", read_float_data.float_num);}// 再测试第二个 双精度浮点数write_float_data.double_num = 3.1415;read_float_data.double_num = 0;W25QXX_Write(write_float_data.buf, 20, 8);W25QXX_Read(read_float_data.buf, 20, 8);if(read_float_data.float_num == 0){printf("Read double fail \r\n");} else {printf("Read double data is %.15f \r\n", read_float_data.double_num);}while (1){}
}

效果验证
编译、烧录
链接串口助手
在这里插入图片描述

3. NRF24L01无线模块通信

3.1. 模块简介

简介

  1. 是一种无线通信模块;
  2. 工作在免费开放的2.4GHz频段;
  3. 通信速率可以达到最高2Mbps;
  4. MCU与该模块通信采用 SPI 接口通信。

下图中右侧模块就是。实际上有很多类似的模块,工作原理和代码基本都是一样的,比如左边的模块是个国产的。实测代码都能通用
请添加图片描述
比如这些国产的我都有,几年前他店里刚上架的时候比较便宜,就把所有的都买了一对,HaHaHaHaHaHa!!!
在这里插入图片描述

实际上,我的代码也是在他官网的例程改过来的,它的代码是自己写的,还是用标准库写的,但是现在都用CubeMX,用HAL库写代码,所以我就改了改拿来用。有需要其他型号主控的例程可以去看看。
官网网址(公司记得结下广告费,谢谢!):http://www.gisemi.com/

模块接口(一共八个引脚):

  1. CSN:芯片的片选线,低电平芯片工作;
  2. SCK:芯片控制的时钟线(SPI的时钟);
  3. MISO:芯片控制数据线(SPI的MISO);
  4. MOSI:芯片控制数据线(SPI的MOSI);
  5. IRQ:中断信号,NRF24L01芯片收到数据、或者发送完数据等等一些情况会产生下降沿中断;
  6. CE:芯片的模式控制线,决定了芯片的工作状态。

MCU 开发板连接:

通信需要两个开发板和两个模块。

  • 开发板1:正点原子Mini开发板,主控STM32F103RCT6。
  • 开发板2:普中-准瑞-Z100开发板,主控STM32F103ZET6。

3.2. SPI 配置

因为我这里用的两个不同的主控,所以需要创建两次工程,都和第一章一样,选择不同的主控即可。
如果你用的两个同样的主控,就选择一样的主控。

具体的 SPI 配置不用区分是 用于发送的还是用于接收的,只需要按照原理图把使用的SPI及相关使能引脚配置好就可以了。

3.2.1. SPI1 配置

正点原子Mini开发板 使用的是SPI1进行通信,原理图如图所示。
在这里插入图片描述
这里用的是 SPI1 进行通信。
在这里插入图片描述

NRF24L01要求时钟速率不能超过8Mhz。

其余三个引脚,IRQ对应的是上拉输入,控制线和片选为推挽输出。
在这里插入图片描述
同时,接收把串口重定向也配置一下, 方便观察。
===>>>串口配置<<<===

3.2.2. SPI2 配置

普中-准瑞-Z100开发板 使用的是SPI2进行通信,原理图如图所示。
在这里插入图片描述
这里用的是 SPI2 进行通信。
在这里插入图片描述
其余三个引脚,IRQ对应的是上拉输入,控制线和片选为推挽输出。
在这里插入图片描述
同时,接收把串口重定向也配置一下, 方便观察。
===>>>串口配置<<<===

注意,如果两个使用的是两个不同的SPI,比如这里用的是SPI1和SPI2,因为两个SPI的时钟不一样,因此分频系数也不能一样,要保证配置完之后的频率和其他信息是一样的。

STM32的SPI1在APB2上,SPI2和SPI3在APB1上,APB1的最高频率是36MHz,APB2的最高频率是72MHz。

3.3. 代码实现

3.3.1. 添加驱动代码

生成工程后,把 NRF24L01的驱动代码添加到工程(两个工程都需要)。
===>>>驱动代码(无需积分,直接下载)<<<===

在工程目录创建icode文件夹,在里面再创建NRF24L01文件夹,在里面添加nrf24L01.hnrf24l01.c文件(两个工程都需要)。
在这里插入图片描述
在Keil中 添加代码及头文件路径(前面很多文章都有写,这里就不放图了)。

3.3.2. 驱动修改

其实修改的内容不多。发送端和接收端都一样。

  1. nrf24L01.h文件中,修改你使用的是哪个SPI。
  2. nrf24L01.h文件中,修改CS、CE、IRQ引脚的定义。如果你在图形化配置时和我用了一样的Label,则无需修改。
  3. nrf24L01.c文件中,修改使用的是哪个SPI(只有图上的一个地方)。
    在这里插入图片描述
    在这里插入图片描述

3.4. 主函数

发送端

/* USER CODE BEGIN Includes */
#include "nrf24l01.h"
/* USER CODE END Includes */int main(void)
{/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI2_Init();MX_USART1_UART_Init();/* USER CODE BEGIN WHILE */NRF24L01_Gpio_Init( );	// 初始化片选及模式引脚NRF24L01_check( );		// 检测nRF24L01NRF24L01_Init( );		// 初始化模块NRF24L01_Set_Mode( MODE_TX );		// 发送模式uint8_t index = 0;uint8_t txData[12] = {"0.success \r\n"};	// 12字节while (1){// 发送固定字符,2S一包NRF24L01_TxPacket( txData, 12 );// 发送完成之后,给个提示,方便调试printf("txdata is: %d%s", txData[0], &txData[1]);// 修改发送的信息index = index + 1;txData[0] = index;// 延迟一段时间再次发送HAL_Delay(2000);/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

接收端

/* USER CODE BEGIN Includes */
#include "nrf24l01.h"
/* USER CODE END Includes */int main(void)
{HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI1_Init();MX_USART1_UART_Init();/* USER CODE BEGIN WHILE */NRF24L01_Gpio_Init( );	// 初始化片选及模式引脚NRF24L01_check( );		// 检测nRF24L01NRF24L01_Init( );		// 初始化模块NRF24L01_Set_Mode( MODE_RX );			// 接收模式uint8_t reLen = 0;						// 接收到的数据长度uint8_t nrf24l01RxBuffer[ 32 ] = { 0 };	// 接收缓存while (1){reLen = NRF24L01_RxPacket( nrf24l01RxBuffer );		// 接收字节if( 0 != reLen ){printf("rxData is: %d%s \r\n", nrf24l01RxBuffer[0], &nrf24l01RxBuffer[1]);}/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

3.5. 测试

先测试发送端
开发板与串口调试助手连接。
复位开发板。

  1. 刚开始不插上模块,会一直检测模块是否存在,并提示不存在0;
  2. 检测找到后,输出提示;
  3. 之后模块一直发送数据,并给出发送成功提示;
  4. 不管接不接收都会一直发送。

在这里插入图片描述
然后测试接收端
先把发送端断电,只打开接收端,可以看到初始化步骤和上面一样。但是初始化完成之后会一直等待接收数据。
在这里插入图片描述
然后打开发送端,就可以接收到发送端发送出来的数据。
在这里插入图片描述
拔掉发送端模块,再次接收不到数据。

再放一次驱动代码,欢迎点赞!!!===>>>驱动代码(无需积分,直接下载)<<<===

这模块搞了一天半,终于把博客这章写完了。


http://chatgpt.dhexx.cn/article/Odl8roy6.shtml

相关文章

STM32驱动W25Q64读写数据

STM32驱动W25Q64读写数据 一&#xff0e;基本参数二&#xff0e;通信协议1、时序&#xff1a;2、代码&#xff1a; 三&#xff0e;引脚定义与接线1、引脚定义2、对应接线 四&#xff0e;主要代码五&#xff0e;操作步骤与现象六&#xff0e;总结 一&#xff0e;基本参数 1.采用…

学习日记——W25Q64 FLASH—QSPI

W25Q64串行FLASH基础知识 大小&#xff1a;8M&#xff08;Byte&#xff09;&#xff08;128块&#xff08;Block&#xff09;&#xff0c;每块64K字节&#xff0c;每块16个扇区&#xff08;Sector&#xff09;&#xff0c;每个扇区4K字 节&#xff0c;每个扇区16页&#xff0c…

(STM32)W25Q64存储模块

1bit表示一个二进制位&#xff0c;1Byte表示8个二进制位。 每一个字节需要一个地址&#xff0c;所以24位地址&#xff0c;2^24次方最大寻址范围是16M。 W25Q256是特殊型号&#xff0c;切换4字节寻址模式才能使用后面的16M空间。 HOLD&#xff1a;芯片正常读写数据时&#xff…

SPIW25Q64(精华版)

1. SPI总线 1.1 W25Q64 1.1 W25Q64简介 W25Q64 是一种具有SPI接口的FlASH存储器&#xff0c;具有32768个可编程页(Page256B)&#xff0c;2048个可擦除扇区(Sector16*Page)&#xff0c;128个可擦除块(Block16*Sector)&#xff0c;容量为128*16*16*256B8MB(64bit)&#xff0c;…

W25Q64简介(译)

W25Q64是华邦公司推出的大容量SPI FLASH产品&#xff0c;其容量为64Mb。该25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块&#xff0c;每个块大小为64K字节&#xff0c;每个块又分为16个扇区&#xff0c;每个扇区4K个字节。W25Q…

W25Q64Flash芯片

W25Q64Flash芯片STM32操作 通讯方式&#xff1a;SPI通讯 大小&#xff1a;64是bit 换算字节是8M&#xff08;Byte&#xff09;&#xff08;128块&#xff08;Block&#xff09;&#xff0c;每块16个扇区&#xff08;Sector&#xff09;&#xff0c;每个扇区4K字节&#xff08;…

SPI通讯介绍 以及读写W25Q64(块,扇区,页的区别)

附工程百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1nCgNb5OyGpABAL657-gX0A?pwd6666 提取码&#xff1a;6666 介绍:摩托罗拉开发的一种通用数据总线, 四根通讯线SCK(串行时钟总线),MOSI(主机输出从机输入),MISO(主机输入从机输出),SS(从机选择)而且是同步全双…

W25Q64调试

简介 W25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块&#xff0c;每个块大小为64K字节&#xff0c;每个块又分为16个扇区&#xff0c;每个扇区4K个字节。 引脚介绍 串行数据输入、输出和 IOs&#xff08;DI、DO 和 IO0、IO1、…

软件SPI读写W25Q64硬件SPI读写W25Q64

目录 软件SPI读写W25Q64 MySPI W25Q64 主函数 硬件SPI读写W25Q64 软件SPI读写W25Q64 程序整体框架&#xff1a; SPI模块包含通信引脚封装&#xff0c;初始化&#xff0c;SPI三个基本时序单元&#xff08;起始&#xff0c;终止&#xff0c;交换一个字节&#xff09; W2…

W25Q64Flash芯片STM32操作

1、W25Q64Flash芯片介绍 通讯方式&#xff1a;SPI通讯 大小&#xff1a;8M&#xff08;Byte&#xff09; &#xff08;128块&#xff08;Block&#xff09;&#xff0c;每块64K字节&#xff0c;每块16个扇区&#xff08;Sector&#xff09;&#xff0c;每个扇区4K字节&…

STM32学习笔记(十一)丨SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片)

本篇文章包含的内容 一、SPI的通信协议及其原理1.1 SPI简介1.2 SPI通信的硬件连接1.3 SPI的时序基本单元1.3.1 起始条件和终止条件1.3.2 交换字节&#xff08;模式0&#xff0c;先移入&#xff0c;再移出&#xff09;1.3.3 交换字节&#xff08;模式1&#xff0c;先移出&#x…

看单片机原理图-外部FLASHW25Q64

系列文章目录 看单片机原理图-最小系统 看单片机原理图-最小系统电源电路 看单片机原理图-输入输出电路LED指示、按键输入 看单片机原理图-红外遥控、EEPROM 看单片机原理图-FLASH 文章目录 系列文章目录前言一、FLASH 前言 硬件&#xff1a;百问网100ASK_STM32F103_MINI开发…

W25Q64 Flash芯片原理与应用方案(含W25Q64中文数据手册)

W25Q64是华邦公司推出的大容量SPI FLASH产品&#xff0c;其容量为64Mb&#xff08;8MB&#xff09;&#xff0c;应用较为广泛。 W25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块&#xff0c;每个块大小为64K字节&#xff0c;每个…

cortex_m3_stm32嵌入式学习笔记(二十一):SPI实验(通信总线)

SPI 是英语 Serial Peripheral interface 的缩写&#xff0c;顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口主要应用在 EEPROM&#xff0c; FLASH&#xff0c;实时时钟&#xff0c; AD 转换器&#xff0c;还有数字信号处理器和数…

STM-32:SPI通信协议/W25Q64简介—软件SPI读写W25Q64

目录 一、SPI简介1.1电路模式1.2通信原理1.3SPI时序基本单元1.3.1起始和终止1.3.2交换字节 二、W25Q642.1W25Q64简介2.2W25Q64硬件电路2.3W25Q64框图2.4Flash操作注意事项 三、软件SPI读写W25Q643.1接线图3.2程序代码 一、SPI简介 SPI是串行外设接口&#xff08;Serial Periph…

STM32CubeMx之硬件SPI驱动W25Q64

STM32CubeMx之硬件SPI驱动W25Q64 1.SPI简介 SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&a…

SPI读写串行FLASH(W25Q64)

文章目录 1、SPI协议1、硬件连接2、通讯时序3、不同的通信模式 2、W25Q64介绍3、SPI读写驱动编写4、源码 1、SPI协议 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface)&#xff0c;即串行外围设备接口&#xff0c;是一种高速全双工的通信总线。它被广泛地…

STM32入门开发: 介绍SPI总线、读写W25Q64(FLASH)(硬件+模拟时序)

一、环境介绍 编程软件: keil5 操作系统: win10 MCU型号: STM32F103ZET6 STM32编程方式: 寄存器开发 (方便程序移植到其他单片机) SPI总线: STM32本身支持SPI硬件时序&#xff0c;本文示例代码里同时采用模拟时序和硬件时序两种方式读写W25Q64。 模拟时序更加方便移植到…

树莓派系统介绍

树莓派是一个微型计算机&#xff0c;和普通的电脑没有什么区别&#xff0c;只是体积更小&#xff0c;只有卡片大小&#xff0c;存储能力和计算能力会差一点&#xff0c;主要用于学习&#xff0c;实验所用。 是电脑就要安装操作系统&#xff0c;树莓派官方推荐了两种系统&#…

树莓派 zero linux,树莓派 zero基本调试

回家之前就从网上购买了一堆设备&#xff0c;回去也不能闲着&#xff0c;可以利用家里相对齐全的准备安装调试。结果人还没回来&#xff0c;东西先到了。 购买的核心装备是树莓派zero w&#xff0c;虽然已经知道它比家族大哥树莓派小不少&#xff0c;但拿到手里还是惊奇它的小巧…