STM32CubeMX series 08 - SPI communication (W25Q64, NRF24L01 wireless module)

====>>> Article summary (with code summary) <<<====

1. Preparations

1.1. Hardware used

Reading and writing EEPROM experiment (W25Q64): punctual atom Mini development board, master STM32F103RCT6

Communication experiment: add a common Chinese, the main control STM32F103ZET6.

1.2. Introduction to SPI

SPI (Serial Peripheral interface) serial peripheral interface

  1. Developed by Motorola
  2. High-speed, full-duplex, synchronous communication bus
  3. four wires are required
  4. Clock up to 18Mhz

The SPI interface generally uses 4 wires for communication:

  • MISO Master data input, slave data output
  • MOSI master data output, slave data input
  • SCLK clock signal, generated by the master
  • CS Slave device chip select signal, controlled by the master device

SPI can also have a one-to-many situation, and select which slave to send or receive data to according to the CS chip select signal.
insert image description here

1.3. Generate project

1.3.1. Create project selection master

insert image description here

1.3.2. System configuration

Configure the clock source
insert image description here
Configure the debug mode (you can check it if you need ST-Link download and debug)
insert image description here
Configure the clock tree (you can directly enter 72 in HCLK, and then press Enter to automatically configure)
insert image description here

1.3.3. Configuration project directory

insert image description here
insert image description here

2. Experiment of reading and writing EEPROM (W25Q64)

EEPROM (Electrically Erasable Programmable read only memory) refers to electrically erasable programmable read-only memory. It is a memory chip that does not lose data after power failure.

2.1. Introduction to W25Q64

Schematic
insert image description here
chip pin description:

  1. CS Chip select pin. Low level means selected.
  2. DO SPI data output interface
  3. WP Hardware write protection pin, input high level can write data normally, input low level prohibits writing.
  4. GND public ground
  5. DI SPI data input interface
  6. CLK SPI clock interface
  7. HOLD state storage interface, input low level to prohibit operation of the chip, input high level to operate the chip normally.
  8. VCC power interface, 2.7-3.6 power supply

The storage description is
W25Q64, where 64 means that the storage capacity of the chip is 64M bit, that is 8M 字节(B).

  1. The whole chip 8M bytes is divided into 128 blocks, each block is 64kb;
  2. Each block of 64k bytes is divided into 16 sectors, each sector is 4K bytes (4096 bytes);
  3. Each sector of 4K bytes is divided into 16 pages of 256 bytes each.

2.2. Code implementation

PA2 is chip select signal , just set it as push-pull output.
insert image description here
SPI configuration
insert image description here

The frequency division factor is 4, because the SPI clock can reach up to 18Mhz, and the clock here is 72Mhz, which is exactly 18Mhz after frequency division by four.

Configure serial port redirection for easy observation –>Serial port redirection configuration<–

This part of the code is a lot, so after generating the project.

  1. Create a separate file in the project folder icodeto store our own code.
  2. Under the icode folder, create another one W25Q64文件夹to store W25Q64 related codes.
  3. w25qxx.cCreate w25qxx.htwo files in the W25Q64 folder .
    insert image description here

Add source and header file paths

insert image description here
insert image description here

Write the code as follows:
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);
	else
		HAL_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;
	else
		return 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个字节
			else
				pageremain = 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); // 写入整个扇区
		}else
			W25QXX_Write_NoCheck(pBuffer, WriteAddr, secremain);	// 写已经擦除了的,直接写入扇区剩余区间.
		if(NumByteToWrite == secremain)
			break;	// 写入结束了
		else		// 写入未结束
		{
    
    
			secpos++;	// 扇区地址增1
			secoff=0;	// 偏移位置为0
			pBuffer += secremain; 	// 指针偏移
			WriteAddr += secremain;	// 写地址偏移
			NumByteToWrite -= secremain;	// 字节数递减
			if(NumByteToWrite > 4096)
				secremain = 4096;		// 下一个扇区还是写不完
			else
				secremain = 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 		0xE9


void 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.ctest in main function

/* 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)
  {
    
    
  }
}

Effect verification
Compile and burn
Link serial port assistant
insert image description here

3. NRF24L01 wireless module communication

3.1. Module Introduction

Introduction

  1. is a wireless communication module;
  2. Work in the free and open 2.4GHz frequency band;
  3. The communication rate can reach up to 2Mbps;
  4. The communication between the MCU and the module uses the SPI interface.

The module on the right in the figure below is. In fact, there are many similar modules, and the working principles and codes are basically the same. For example, the module on the left is made in China. The test code can be used universally .
Please add a picture description
For example, I have all these domestic ones. A few years ago, when they first came out in his store, they were cheaper, so I bought a pair of all of them, HaHaHaHaHaHaHa! ! !
insert image description here

In fact, my code was also changed from the routines on his official website. Its code was written by myself, and it was written in the standard library, but now I use CubeMX and use the HAL library to write code, so I changed it. Use it. If there are routines that require other models of master control, you can go and have a look.
Official website URL (the company remembers to settle the advertising fee, thank you!): http://www.gisemi.com/

Module interface (eight pins in total):

  1. CSN: the chip select line of the chip, the chip works at low level;
  2. SCK: chip-controlled clock line (SPI clock);
  3. MISO: chip control data line (MISO of SPI);
  4. MOSI: chip control data line (MOSI of SPI);
  5. IRQ: Interrupt signal, the NRF24L01 chip receives data, or sends data, etc., will generate a falling edge interrupt;
  6. CE: The mode control line of the chip determines the working state of the chip.

MCU development board connection:

Communication requires two development boards and two modules.

  • Development board 1: punctual atom Mini development board, the main control is STM32F103RCT6.
  • Development board 2: Puzhong-Zhunrui-Z100 development board, the main control is STM32F103ZET6.

3.2. SPI configuration

Because I use two different master controls here, I need to create two projects, which are the same as the first chapter, just choose a different master control.
If you are using two of the same master, choose the same master.

The specific SPI configuration does not need to distinguish whether it is used for sending or receiving. It only needs to configure the SPI used and related enable pins according to the schematic diagram.

3.2.1. SPI1 configuration

The punctual atom Mini development board uses SPI1 for communication, and the schematic diagram is shown in the figure.
insert image description here
Here SPI1 is used for communication.
insert image description here

NRF24L01 requires a clock rate not to exceed 8Mhz.

For the remaining three pins, IRQ corresponds to a pull-up input, and the control line and chip select are push-pull outputs.
insert image description here
At the same time, configure the serial port redirection for receiving, which is convenient for observation.
===>>>Serial port configuration<<<===

3.2.2. SPI2 configuration

The Puzhong-Zhunrui-Z100 development board uses SPI2 for communication, and the schematic diagram is shown in the figure.
insert image description here
Here SPI2 is used for communication.
insert image description here
For the remaining three pins, IRQ corresponds to a pull-up input, and the control line and chip select are push-pull outputs.
insert image description here
At the same time, configure the serial port redirection for receiving, which is convenient for observation.
===>>>Serial port configuration<<<===

Note that if the two use two different SPIs, for example, SPI1 and SPI2 are used here, because the clocks of the two SPIs are different, so the frequency division coefficients cannot be the same, and the frequency and other information after configuration must be guaranteed it's the same.

SPI1 of STM32 is on APB2, SPI2 and SPI3 are on APB1, the highest frequency of APB1 is 36MHz, and the highest frequency of APB2 is 72MHz.

3.3. Code implementation

3.3.1. Add driver code

After generating the project, add the driver code of NRF24L01 to the project (both projects are required).
===>>>Driver code (no points required, download directly)<<<===

icodeCreate a folder in the project directory , create NRF24L01a folder in it, and add nrf24L01.hand nrf24l01.cfiles in it (both projects are required).
insert image description here
Add the code and header file path in Keil (many previous articles have been written, so I won’t show the picture here).

3.3.2. Driver modification

In fact, there are not many modifications. Both the sender and the receiver are the same.

  1. In nrf24L01.hthe file, modify which SPI you are using.
  2. In nrf24L01.hthe file, modify the definition of CS, CE, and IRQ pins. If you use the same Label as me in the graphical configuration, you don't need to modify it.
  3. In nrf24L01.cthe file, modify which SPI is used (only one place on the diagram).
    insert image description here
    insert image description here

3.4. Main function

sender

/* 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( );		// 检测nRF24L01
	NRF24L01_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 */
}

Receiving end

/* 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( );		// 检测nRF24L01
	NRF24L01_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. Testing

First test the sending end.
The development board is connected to the serial port debugging assistant.
Reset the development board.

  1. If the module is not plugged in at the beginning, it will always check whether the module exists, and prompt that it does not exist 0;
  2. After the detection is found, a prompt is output;
  3. Afterwards, the module keeps sending data and gives a prompt of successful sending;
  4. Whether it is received or not, it will always be sent.

insert image description here
Then test the receiving end.
Power off the sending end first, and only turn on the receiving end. You can see that the initialization steps are the same as above. But after the initialization is complete, it will wait to receive data.
insert image description here
Then open the sender, you can receive the data sent by the sender.
insert image description here
Unplug the transmitter module, and the data cannot be received again.

Put the driver code again, welcome to like it! ! ! ===>>>Driver code (no points required, download directly)<<<===

After working on this module for a day and a half, I finally finished writing this chapter of the blog.

Guess you like

Origin blog.csdn.net/weixin_46253745/article/details/127851093